Overview
이번주는 CloudNet@에서 진행하시는 T101(Terraform 101 Study) 스터디 2주차이다!
블로그 내용은 '테라폼으로 시작하는 IaC’ 책을 기준하여 정리하였다.
1. 데이터 소스
데이터 소스는 테라폼으로 정의되지 않은 외부 리소스 또는 저장된 정보를 테라폼 내에서 참조할 때 사용한다.
1-1 데이터 소스 구성
mkdir data-source && cd data-source
mkdir 1-1 && cd 1-1
cat <<'EOT' > main.tf
data "local_file" "abc" {
filename = "${path.module}/abc.txt"
}
EOT
데이터 소스를 정의할 때 사용 가능한 메타인수는 다음과 같다.
- depends_on : 종속성을 선언하며, 선언된 구성요소와의 생성 시점에 대해 정의
- count : 선언된 개수에 따라 여러 리소스를 생성
- for_each : map 또는 set 타입의 데이터 배열의 값을 기준으로 여러 리소스를 생성
- lifecycle : 리소스의 수명주기 관리
실습 확인
# 실습 확인을 위해서 abc.txt 파일 생성
echo "t101 study - 2week" > abc.txt
terraform init && terraform plan && terraform apply -auto-approve
terraform state list
data.local_file.abc
# 테라폼 콘솔 : 데이터 소스 참조 확인
echo "data.local_file.abc" | terraform console
{
"content" = <<-EOT
t101 study - 2week
EOT
"content_base64" = "dDEwMSBzdHVkeSAtIDJ3ZWVrCg=="
"content_base64sha256" = "GTJyISrUCoVCp4KIUl1e1FHeyGeu09qTsFZLsNeCTz8="
"content_base64sha512" = "tp0cRC/xBlJH4txY6Skb/FmexTrlNXq7OLAJXEYJwEvW+obgAeSYPtmPhvYhgwsXTWJ2jpTJkkiqclybnVu8MA=="
"content_md5" = "983a604da76dd282581c6d411f3b8291"
"content_sha1" = "75a47c30031f5fa02f6c4b5170e9539324f95c87"
"content_sha256" = "193272212ad40a8542a78288525d5ed451dec867aed3da93b0564bb0d7824f3f"
"content_sha512" = "b69d1c442ff1065247e2dc58e9291bfc599ec53ae5357abb38b0095c4609c04bd6fa86e001e4983ed98f86f621830b174d62768e94c99248aa725c9b9d5bbc30"
"filename" = "./abc.txt"
"id" = "75a47c30031f5fa02f6c4b5170e9539324f95c87"
}
1-2 데이터 소스 속성 참조
데이터 소스로 읽은 대상을 참조하는 방식은 리소스와 구별되게 data가 앞에 붙는다.
# Terraform Code
data "<리소스 유형>" "<이름>" {
<인수> = <값>
}
# 데이터 소스 참조
data.<리소스 유형>.<이름>.<속성>
코드 예시
- 데이터 소스를 활용해 AWS 가용영역 인수를 정의 → 리전 내에서 사용 가능한 가용영역 목록 가져와서 사용하기
# Declare the data source
data "aws_availability_zones" "available" {
state = "available"
}
resource "aws_subnet" "primary" {
availability_zone = data.aws_availability_zones.available.names[0]
# e.g. ap-northeast-2a
}
resource "aws_subnet" "secondary" {
availability_zone = data.aws_availability_zones.available.names[1]
# e.g. ap-northeast-2b
}
[도전과제1] 위 리전 내에서 사용 가능한 가용영역 목록 가져오기를 사용한 VPC 리소스 생성 실습 진행, 혹은 아무거나 데이터 소스를 사용한 실습 진행
AWS CLI는 자격 증명 및 설정을 저장하는 데 두개의 파일을 사용한다.
`.aws/credentials` 와 `.aws/config` 이다. 이러한 파일은 AWS CLI을 구성할 때 자동으로 생성된다. 그리고 Terrafrom은 이 파일들을 기본적으로 감지하고 사용한다.
# provider.tf
provider "aws" {
region = "ap-northeast-2"
profile = "default"
}
# vpc.tf
# VPC 생성
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16" # 예시 CIDR 블록
tags = {
Name = "main-vpc"
}
}
# Availability Zones 데이터 선언
data "aws_availability_zones" "available" {
state = "available"
}
# Primary 서브넷 생성
resource "aws_subnet" "primary" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24" # 예시 CIDR 블록
availability_zone = data.aws_availability_zones.available.names[0]
map_public_ip_on_launch = true
tags = {
Name = "primary-subnet"
}
}
# Secondary 서브넷 생성
resource "aws_subnet" "secondary" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.2.0/24" # 예시 CIDR 블록
availability_zone = data.aws_availability_zones.available.names[1]
map_public_ip_on_launch = true
tags = {
Name = "secondary-subnet"
}
}
파일을 전부 작성하였다. 이제 생성해본다.
terraform apply -auto-approve
terraform state list
data.aws_availability_zones.available
aws_subnet.primary
aws_subnet.secondary
aws_vpc.main
terraform destroy -auto-approve
terraform state list
`main.tf` 파일 코드 수정
mkdir 1-2 && cd 1-2
cat <<'EOT' > main.tf
resource "local_file" "abc" {
content = "123!"
filename = "${path.module}/abc.txt"
}
data "local_file" "abc" {
filename = local_file.abc.filename
}
resource "local_file" "def" {
content = data.local_file.abc.content
filename = "${path.module}/def.txt"
}
EOT
terraform init && terraform plan && terraform apply -auto-approve
terraform state list
data.local_file.abc
local_file.abc
local_file.def
ls
abc.txt def.txt main.tf terraform.tfstate terraform.tfstate.backup
# 테라폼 콘솔 : 데이터 소스 참조 확인
echo "local_file.abc" | terraform console
echo "local_file.def" | terraform console
# 파일 확인
ls *.txt
diff abc.txt def.txt
# graph 확인
terraform graph > graph.dot
digraph {
compound = "true"
newrank = "true"
subgraph "root" {
"[root] data.local_file.abc (expand)" [label = "data.local_file.abc", shape = "box"]
"[root] local_file.abc (expand)" [label = "local_file.abc", shape = "box"]
"[root] local_file.def (expand)" [label = "local_file.def", shape = "box"]
"[root] provider[\"registry.terraform.io/hashicorp/local\"]" [label = "provider[\"registry.terraform.io/hashicorp/local\"]", shape = "diamond"]
"[root] data.local_file.abc (expand)" -> "[root] local_file.abc (expand)"
"[root] local_file.abc (expand)" -> "[root] provider[\"registry.terraform.io/hashicorp/local\"]"
"[root] local_file.def (expand)" -> "[root] data.local_file.abc (expand)"
"[root] provider[\"registry.terraform.io/hashicorp/local\"] (close)" -> "[root] local_file.def (expand)"
"[root] root" -> "[root] provider[\"registry.terraform.io/hashicorp/local\"] (close)"
}
}
1-3 (추가 실습) az.tf 파일 생성 - az datasource - 링크
cat <<'EOT' > main.tf
data "aws_availability_zones" "available" {
state = "available"
}
EOT
확인
terraform init -upgrade && terraform apply -auto-approve
terraform state list
data.aws_availability_zones.available
terraform state show data.aws_availability_zones.available
# data.aws_availability_zones.available:
data "aws_availability_zones" "available" {
group_names = [
"ap-northeast-2",
]
id = "ap-northeast-2"
names = [
"ap-northeast-2a",
"ap-northeast-2b",
"ap-northeast-2c",
"ap-northeast-2d",
]
state = "available"
zone_ids = [
"apne2-az1",
"apne2-az2",
"apne2-az3",
"apne2-az4",
]
}
terraform console
> data.aws_availability_zones.available
{
"all_availability_zones" = tobool(null)
"exclude_names" = toset(null) /* of string */
"exclude_zone_ids" = toset(null) /* of string */
"filter" = toset(null) /* of object */
"group_names" = toset([
"ap-northeast-2",
])
"id" = "ap-northeast-2"
"names" = tolist([
"ap-northeast-2a",
"ap-northeast-2b",
"ap-northeast-2c",
"ap-northeast-2d",
])
"state" = "available"
"timeouts" = null /* object */
"zone_ids" = tolist([
"apne2-az1",
"apne2-az2",
"apne2-az3",
"apne2-az4",
])
}
2. 입력 변수 Variable
입력 변수는 인프라를 구성하는 데 필요한 속성 값을 정의해 코드의 변경 없이 여러 인프라를 생성하는 데 목적이 있다.
2-1 변수 선언 방식
변수는 variable로 시작되는 블록으로 구성된다.
# variable 블록 선언의 예
variable "<이름>" {
<인수> = <값>
}
variable "image_id" {
type = string
}
변수 설명은 아래의 블로그로 대체하겠다.
2023.04.05 - [IaC/Infrastructure Provisioning] - 2. Terraform 변수 사용법(use-variable) - AWS
2023.04.09 - [IaC/Infrastructure Provisioning] - 3. Terraform 다양한 변수(variable, local, data...output, input) - AWS
2-2 변수 유형
- 기본 유형
- string : 글자 유형
- number : 숫자 유형
- bool : true 또는 false
- any : 명시적으로 모든 유형이 허용됨을 표시
- 집합 유형
- list (<유형>): 인덱스 기반 집합
- map (<유형>): 값 = 속성 기반 집합이며 키값 기준 정렬
- set (<유형>): 값 기반 집합이며 정렬 키값 기준 정렬
- object ({<인수 이름>=<유형>, …})
- tuple ([<유형>, …])
list와 set은 선언하는 형태가 비슷하지만 참조 방식이 인덱스와 키로 각각 차이가 있고, map과 set의 경우 선언된 값이 정렬되는 특징을 가진다.
2-3 유효성 검사
- 변수 블록 내에 validation 블록에서 조건인 condition에 지정되는 규칙이 true 또는 false를 반환해야 하며, error_message는 condition 값의 결과가 false 인 경우 출력되는 메시지를 정의한다.
- regex 함수는 대상의 문자열에 정규식을 적용하고 일치하는 문자열을 반환하는데, 여기에 can 함수를 함께 사용하면 정규식에 일치하지 않는 경우의 오류를 검출한다.
- validation 블록은 중복으로 선언할 수 있다.
cat <<'EOT' > main.tf
variable "image_id" {
type = string
description = "The id of the machine image (AMI) to use for the server."
validation {
condition = length(var.image_id) > 4
error_message = "The image_id value must exceed 4."
}
validation {
# regex(...) fails if it cannot find a match
condition = can(regex("^ami-", var.image_id))
error_message = "The image_id value must starting with \"ami-\"."
}
}
EOT
terraform apply -auto-approve
var.image_id
The id of the machine image (AMI) to use for the server.
Enter a value: ami-
╷
│ Error: Invalid value for variable
│
│ on main.tf line 1:
│ 1: variable "image_id" {
│ ├────────────────
│ │ var.image_id is "ami-"
│
│ The image_id value must exceed 4.
│
│ This was checked by the validation rule at main.tf:5,3-13.
...
terraform apply -auto-approve
var.image_id
The id of the machine image (AMI) to use for the server.
Enter a value: ami-12345
No changes. Your infrastructure matches the configuration.
Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are
needed.
2-4 변수 참조 & 2-5 민감한 변수 취급
variable은 코드 내에서 `var.<이름>`으로 참조된다.
입력 변수의 민감 여부를 선언할 수 있다.
cat <<'EOT' > main.tf
variable "my_password" {
default = "password"
sensitive = true
}
resource "local_file" "abc" {
content = var.my_password
filename = "${path.module}/abc.txt"
}
EOT
실행한다.
terraform apply -auto-approve
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
+ create
Terraform will perform the following actions:
# local_file.abc will be created
+ resource "local_file" "abc" {
+ content = (sensitive value)
+ content_base64sha256 = (known after apply)
+ content_base64sha512 = (known after apply)
+ content_md5 = (known after apply)
+ content_sha1 = (known after apply)
+ content_sha256 = (known after apply)
+ content_sha512 = (known after apply)
+ directory_permission = "0777"
+ file_permission = "0777"
+ filename = "./abc.txt"
+ id = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
local_file.abc: Creating...
local_file.abc: Creation complete after 0s [id=5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8]
그러나 민감한 변수로 지정해도 `terraform.tfstate` 파일에는 결과물이 평문으로 기록되므로 State 파일의 보안에 유의해야 한다.
terraform apply -auto-approve
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
+ create
Terraform will perform the following actions:
# local_file.abc will be created
+ resource "local_file" "abc" {
+ content = (sensitive value)
+ content_base64sha256 = (known after apply)
+ content_base64sha512 = (known after apply)
+ content_md5 = (known after apply)
+ content_sha1 = (known after apply)
+ content_sha256 = (known after apply)
+ content_sha512 = (known after apply)
+ directory_permission = "0777"
+ file_permission = "0777"
+ filename = "./abc.txt"
+ id = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
local_file.abc: Creating...
local_file.abc: Creation complete after 0s [id=5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8]
# 결과물 파일 확인
cat abc.txt ; echo
password
cat terraform.tfstate | grep '"content":'
"content": "password",
2-6 변수 입력 방식과 우선순위
[우선순위 수준]의 숫자가 작을수록 우선순위도 낮다.
우선순위
[우선순위 수준 1] 실행 후 입력
[우선순위 수준 2] variable 블록의 default 값
[우선순위 수준 3] 환경 변수 (TF_VAR 변수 이름)
[우선순위 수준 4] terraform.tfvars에 정의된 변수 선언
[우선순위 수준 5] *.auto.tfvars에 정의된 변수 선언
[우선순위 수준 6] *.auto.tfvars.json에 정의된 변수 선언
[우선순위 수준 7] CLI 실행 시 -var 인수에 지정 또는 -var-file로 파일 지정
[스터디 전용/실습1] VPC + 보안그룹 + EC2 배포 (도전과제2)
목표 : default VPC 대신 직접 VPC를 만들고, 해당 VPC 내에 EC2 1대를 배포!
# provider.tf
provider "aws" {
region = "ap-northeast-2"
profile = "default"
}
# vpc.tf
# VPC 생성
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16" # 예시 CIDR 블록
tags = {
Name = "somaz-vpc"
}
}
# Availability Zones 데이터 선언
data "aws_availability_zones" "available" {
state = "available"
}
# Primary 서브넷 생성
resource "aws_subnet" "primary" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24" # 예시 CIDR 블록
availability_zone = data.aws_availability_zones.available.names[0]
map_public_ip_on_launch = true
tags = {
Name = "somaz-subnet1"
}
}
# Secondary 서브넷 생성
resource "aws_subnet" "secondary" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.2.0/24" # 예시 CIDR 블록
availability_zone = data.aws_availability_zones.available.names[1]
map_public_ip_on_launch = true
tags = {
Name = "somaz-subnet2"
}
}
# 인터넷 게이트웨이 생성
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.main.id
tags = {
Name = "somaz-igw"
}
}
# 라우팅 테이블 생성 후 연결
resource "aws_route_table" "rt" {
vpc_id = aws_vpc.main.id
tags = {
Name = "somaz-rt"
}
}
resource "aws_route_table_association" "rt_association1" {
subnet_id = aws_subnet.primary.id
route_table_id = aws_route_table.rt.id
}
resource "aws_route_table_association" "rt_association2" {
subnet_id = aws_subnet.secondary.id
route_table_id = aws_route_table.rt.id
}
resource "aws_route" "main_route" {
route_table_id = aws_route_table.rt.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
output "aws_vpc_id" {
value = aws_vpc.main.id
}
먼저 vpc.tf부터 배포해준다.
terraform init && terraform fmt && terraform validate
terraform apply -auto-approve
...
aws_vpc_id = "vpc-0eff1e562e4717c21"
terraform state list
data.aws_availability_zones.available
aws_internet_gateway.igw
aws_route.main_route
aws_route_table.rt
aws_route_table_association.rt_association1
aws_route_table_association.rt_association2
aws_subnet.primary
aws_subnet.secondary
aws_vpc.main
# 참고 : aws ec2 describe-subnets --filters "Name=vpc-id,Values=vpc-<자신의 VPC ID>"
VPCID=$(terraform output -raw aws_vpc_id)
aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPCID" | jq
aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPCID" --output table
terraform state show aws_route.main_route
# graph 확인 > graph.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph > graph.dot
이제 보안그룹과 EC2를 배포해준다.
여기서 `protocol = "-1"` 의 의미는 `TCP,UDP,ICMP` 등 모든 트래픽 프로토콜을 허용하거나 거부하기 위한 설정이다.
그렇다면 이게 무슨소리일까?
먼저 `protocol = "-1"` 설정 자체는 트래픽 허용 여부를 결정하지 않는다. 대신 규칙이 적용되는 모든 프로토콜의 범위를 지정한다. 따라서 트래픽이 실제로 허용되거나 거부되는지 여부는 규칙 유형 및 기타 규칙 구성에 따른다.
AWS 보안 그룹은 상태 정자형이며 기본 "모두 거부" 로 시작된다. 즉 NACL처럼 AWS 보안 그룹에서 `"deny"` 규칙을 명시적으로 생성할 수 있는 방법을 없다.
결론적으로 sg_outbound 규칙은 보안 그룹에 연결된 리소스로부터 모든 IP로의 모든 프로토콜 및 포트를 통한 나가는 트래픽을 허용한다.
# sg.tf
resource "aws_security_group" "sg" {
vpc_id = aws_vpc.main.id
name = "somaz-sg"
description = "Somaz SG"
}
resource "aws_security_group_rule" "sg_inbound" {
type = "ingress"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.sg.id
}
resource "aws_security_group_rule" "sg_outbound" {
type = "egress"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.sg.id
}
`data "aws_ami" "amazon_linux2" : aws_ami` 유형의 데이터 소스를 선언하고 로컬이름 amazon_linux2를 할당한다.
most_recent = true : 여러 AMI가 지정된 필터와 일치하는 경우 Terraform이 가장 최근 필터를 사용하도록 보장한다.
즉 최신 버전을 사용한다는 뜻이다.
data "aws_ami" "amazon_linux2" {
most_recent = true
filter {
name = "owner-alias"
values = ["amazon"]
}
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-ebs"]
}
owners = ["amazon"]
}
resource "aws_instance" "ec2" {
depends_on = [
aws_internet_gateway.igw
]
ami = data.aws_ami.amazon_linux2.id
associate_public_ip_address = true
instance_type = "t2.micro"
vpc_security_group_ids = ["${aws_security_group.sg.id}"]
subnet_id = aws_subnet.primary.id
user_data = <<-EOF
#!/bin/bash
wget https://busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/busybox-x86_64
mv busybox-x86_64 busybox
chmod +x busybox
RZAZ=$(curl http://169.254.169.254/latest/meta-data/placement/availability-zone-id)
IID=$(curl 169.254.169.254/latest/meta-data/instance-id)
LIP=$(curl 169.254.169.254/latest/meta-data/local-ipv4)
echo "<h1>RegionAz($RZAZ) : Instance ID($IID) : Private IP($LIP) : Web Server</h1>" > index.html
nohup ./busybox httpd -f -p 80 &
EOF
user_data_replace_on_change = true
tags = {
Name = "somaz-ec2"
}
}
output "ec2_public_ip" {
value = aws_instance.ec2.public_ip
description = "The public IP of the Instance"
}
terraform init && terraform fmt && terraform validate
terraform apply -auto-approve
...
aws_vpc_id = "vpc-0eff1e562e4717c21"
ec2_public_ip = "3.34.189.239"
terraform state list
...
data.aws_ami.amazon_linux2
aws_instance.ec2
# 출력된 EC2 퍼블릭IP로 curl 접속 확인
terraform output -raw ec2_public_ip
3.34.189.239
MYIP=$(terraform output -raw ec2_public_ip)
while true; do curl --connect-timeout 1 http://$MYIP/ ; echo "------------------------------"; date; sleep 1; done
<h1>RegionAz(apne2-az1) : Instance ID(i-00417240bf7adfd26) : Private IP(10.0.1.144) : Web Server</h1>
------------------------------
Fri Sep 8 22:36:50 KST 2023
<h1>RegionAz(apne2-az1) : Instance ID(i-00417240bf7adfd26) : Private IP(10.0.1.144) : Web Server</h1>
------------------------------
Fri Sep 8 22:36:51 KST 2023
<h1>RegionAz(apne2-az1) : Instance ID(i-00417240bf7adfd26) : Private IP(10.0.1.144) : Web Server</h1>
마지막으로 삭제해준다. 지우는게 가장 중요!
terraform destroy -auto-approve
# 확인
terraform state list
3. local 지역 값
코드 내에서 사용자가 지정한 값 또는 속성 값을 가공해 참조 가능한 local (지역 값)은 외부에서 입력되지 않고, 코드 내에서만 가공되어 동작하는 값을 선언한다.
module은 github에 있는 gcp 공식 module을 사용했다.
https://github.com/terraform-google-modules/terraform-google-cloud-storage
locals를 사용해 gcp 리소스를 생성하는 방법을 알아본다.
### provider.tf ###
provider "google" {
credentials = file("../../../key/admin-somaz-service-project-dev.json")
project = var.project
region = var.region
}
provider "google-beta" {
credentials = file("../../../key/admin-somaz-service-project-dev.json")
project = var.project
region = var.region
}
---------------------
### locals.tf ###
## common ##
locals {
default_labels = {
environment = var.environment
terraform = var.terraform
}
default_map_labels = {
environment = {
key1 = var.environment
},
terraform = {
key2 = var.terraform
}
}
buckets_versioning = {
"${var.tf_state_bucket}" = true
"${var.somaz_link}"
}
}
---------------------
### variables.tf ###
## common ##
variable "project" {}
variable "host_project" {}
variable "region" {}
variable "environment" {}
variable "terraform" {}
## terraform tfstate backend ##
variable "tf_state_bucket" {}
## gcs_buckets ##
variable "somaz_link" {}
--------------------------
### somaz.tfvars ###
## common ##
project = "somaz-service-project-dev"
host_project = "somaz-host-project"
region = "asia-northeast3"
environment = "dev"
terraform = "true"
## terraform tfstate backend ##
tf_state_bucket = "somaz-service-project-terraform-remote-tfstate"
## gcs_buckets ##
somaz_link = "dev.somaz.link"
이렇게 bucket을 생성해줄 수 있다.
## GCS Buckets(Cloud Storage=S3) ##
module "gcs_buckets" {
source = "../../../modules/gcs_buckets"
names = keys(local.buckets_versioning)
project_id = var.project
location = var.region
labels = local.default_labels
versioning = local.buckets_versioning
depends_on = [google_dns_record_set.asset_record]
}
아래와 같이 응용도 가능하다.
## common ##
locals {
default_labels = {
environment = var.environment
terraform = var.terraform
}
default_map_labels = {
environment = {
key1 = var.environment
},
terraform = {
key2 = var.terraform
}
}
buckets_versioning = {
"${var.tf_state_bucket}" = true
"${var.somaz.link}" = false
}
patch_cors_values = {
origin = ["*"],
method = ["GET", "POST"],
response_header = ["*"],
max_age_seconds = 3600
}
bucket_cors_settings = {
"${var.tf_state_bucket}" = null
"${var.somaz.link}" = local.patch_cors_values
# ... add other bucket names as keys and assign cors_values if you want to enable CORS for them, else set to null
}
}
-----------------
## GCS Buckets(Cloud Storage=S3) ##
module "gcs_buckets" {
source = "../../../modules/gcs_buckets"
names = keys(local.buckets_versioning)
project_id = var.project
location = var.region
labels = local.default_labels
versioning = local.buckets_versioning
cors = local.bucket_cors_settings
}
아래는 compute engine 생성방법이다.
locals를 활용해 같은 환경의 리소스에 같은 태그값을 줄 수 있다.
## Compute Engine ##
resource "google_compute_address" "bastion_ip" {
name = var.bastion_ip
}
resource "google_compute_instance" "bastion" {
name = var.bastion
machine_type = "e2-small"
labels = local.default_labels
zone = "${var.region}-a"
allow_stopping_for_update = true
tags = [var.prod_nfs_client]
boot_disk {
initialize_params {
image = "ubuntu-os-cloud/ubuntu-2004-lts"
}
}
metadata = {
ssh-keys = "nerdystar:${file("../../../key/somaz-bastion.pub")}"
}
metadata_startup_script = <<-EOF
#!/bin/bash
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl nfs-common mysql-client
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
sudo mkdir -p /home/somaz/prod
sudo mount -t nfs <prod-nfs-server-ip>:/nfs/prod /home/somaz/prod # change <prod-nfs-server-ip>
echo "<prod-nfs-server-ip>:/nfs/prod /home/somaz/prod nfs defaults 0 0" | sudo tee -a /etc/fstab
EOF
network_interface {
network = "projects/${var.host_project}/global/networks/${var.shared_vpc}"
subnetwork = "projects/${var.host_project}/regions/${var.region}/subnetworks/${var.subnet_share}-prod-a"
access_config {
## Include this section to give the VM an external ip ##
nat_ip = google_compute_address.bastion_ip.address
}
}
depends_on = [google_compute_address.bastion_ip]
}
4. 반복문
list 형태의 값 목록이나 Key-Value 형태의 문자열 집합인 데이터가 있는 경우 동일한 내용에 대해 테라폼 구성 정의를 반복적으로 하지 않고 관리할 수 있다.
- `count` : 반복문, 정수 값만큼 리소스나 모듈을 생성
- `for_each` : 반목문, 선언된 key 값 개수만큼 리소스를 생성
- `for` : 복합 형식 값의 형태를 변환하는데 사용 ← for_each와 다름
- `dynamic` : 리소스 내부 속성 블록을 동적인 블록으로 생성
4-1 count
# main.tf
resource "local_file" "abc" {
count = 5
content = "abc${count.index}"
filename = "${path.module}/abc${count.index}.txt"
}
output "fileid" {
value = local_file.abc.*.id
}
output "filename" {
value = local_file.abc.*.filename
}
output "filecontent" {
value = local_file.abc.*.content
}
확인해본다.
terraform init && terraform apply -auto-approve
...
filecontent = [
"abc0",
"abc1",
"abc2",
"abc3",
"abc4",
]
fileid = [
"062c648aaf68174757c50ab1aeebb61e059c1d1b",
"9ee036287b4cfbcfa3b5bbfcf92d46eb5e75df96",
"229d028063a11904f846c91224abaa99113f3a15",
"99bdd81005eaa37fa71ff0787cd1d65f63d3d293",
"5ac835798aa493440692c54c7ab76161f06c1b88",
]
filename = [
"./abc0.txt",
"./abc1.txt",
"./abc2.txt",
"./abc3.txt",
"./abc4.txt",
]
terraform state list
local_file.abc[0]
local_file.abc[1]
local_file.abc[2]
local_file.abc[3]
local_file.abc[4]
echo "local_file.abc[0]" | terraform console
ls *.txt
4-2 for_each
리소스 또는 모듈 블록에서 for_each에 입력된 데이터 형태가 map 또는 set이면, 선언된 key 값 개수만큼 리소스를 생성하게 된다.
## Artifact Registry ##
resource "google_artifact_registry_repository" "repo" {
for_each = toset(var.somaz_repo)
location = var.region
repository_id = each.key
format = "DOCKER" # DOCKER is for Docker images. You can also specify "MAVEN" or "NPM" for Java and JavaScript packages, respectively.
labels = local.default_labels
}
resource "google_artifact_registry_repository" "prod_repo" {
for_each = toset(var.prod_somaz_repo)
location = var.prod_region
repository_id = each.key
format = "DOCKER" # DOCKER is for Docker images. You can also specify "MAVEN" or "NPM" for Java and JavaScript packages, respectively.
labels = local.default_labels
}
## Artifact Registry ##
variable "somaz_repo" {
description = "List of Artifact Registry dsp repository names"
type = list(string)
}
## Prod Artifact Registry ##
variable "prod_somaz_repo" {
description = "List of Artifact Registry prod dsp repository names"
type = list(string)
}
## Artifact Registry ##
somaz_repo = ["somaz-web", "somaz-game", "somaz-was"]
prod_somaz_repo = ["somaz-web", "somaz-game", "somaz-was"]
4-3 for
- 예를 들어 list 값의 포맷을 변경하거나 특정 접두사 prefix를 추가할 수도 있고, output에 원하는 형태로 반복적인 결과를 표현할 수 도 있다.
- list 타입의 경우 값 또는 인덱스와 값을 반환
- map 타입의 경우 키 또는 키와 값에 대해 반환
- set 타입의 경우 키 값에 대해 반환
# main.tf
variable "names" {
default = ["a", "b", "c"]
}
resource "local_file" "abc" {
content = jsonencode(var.names) # 결과 : ["a", "b", "c"]
filename = "${path.module}/abc.txt"
}
terraform apply -auto-approve
terraform state list
ls *.txt
cat abc.txt ;echo
echo "local_file.abc" | terraform console
# 참고 jsonencode Function
echo 'jsonencode({"hello"="world"})' | terraform console
4-4 dynamic
- count 나 for_each 구문을 사용한 리소스 전체를 여러 개 생성하는 것 이외도 리소스 내에 선언되는 구성 블록을 다중으로 작성해야 하는 경우가 있다.
- 예를 들면 AWS Security Group 리소스 구성에 ingress, egress 요소가 리소스 선언 내부에서 블록 형태로 여러 번 정의되는 경우다.
resource "aws_security_group" "example" {
name = "example-security-group"
description = "Example security group"
vpc_id. = aws_vpc.main.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
ipv6_cidr_blocks = ["::/0"]
}
}
(일반적인 블록)
resource "provider_resource" "name" {
name = "some_resource"
some_setting {
key = a_value
}
some_setting {
key = b_value
}
some_setting {
key = c_value
}
some_setting {
key = d_value
}
}
(dynamic 블록 적용)
resource "provider_resource" "name" {
name = "some_resource"
dynamic "some_setting" {
for_each = {
a_key = a_value
b_key = b_value
c_key = c_value
d_key = d_value
}
content {
key = some_setting.value
}
}
}
[도전과제 5] count, for_each 반복문, for문, dynamic문 을 활용해서 리소스(어떤 리소스든지 상관없음)를 배포해보고, 해당 코드를 정리해주세요!
IAM 역할을 생성해보려고 한다.
Admin: 모든 AWS 서비스에 대한 전체 액세스 권한
User: 특정 AWS 서비스에 대한 제한된 액세스(예: EC2 읽기 전용 액세스).
Viewer: CloudWatch 로그를 볼 수 있는 권한만
for_each 사용
variable "roles" {
description = "Roles and their respective policies"
default = {
admin = "arn:aws:iam::aws:policy/AdministratorAccess"
user = "arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess"
viewer = "arn:aws:iam::aws:policy/CloudWatchLogsReadOnlyAccess"
}
}
resource "aws_iam_role" "example" {
for_each = var.roles
name = each.key
assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow",
Principal = {
Service = "ec2.amazonaws.com"
},
Action = "sts:AssumeRole"
}
]
})
}
resource "aws_iam_role_policy_attachment" "example" {
for_each = var.roles
role = aws_iam_role.example[each.key].name
policy_arn = each.value
}
terraform init && terraform fmt && terraform validate
terraform apply -auto-approve
terraform state list
aws_iam_role.example["admin"]
aws_iam_role.example["user"]
aws_iam_role.example["viewer"]
aws_iam_role_policy_attachment.example["admin"]
aws_iam_role_policy_attachment.example["user"]
aws_iam_role_policy_attachment.example["viewer"]
terraform destroy -auto-approve
Count 사용
locals {
roles = [
{ name : "admin", policy : "arn:aws:iam::aws:policy/AdministratorAccess" },
{ name : "user", policy : "arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess" },
{ name : "viewer", policy : "arn:aws:iam::aws:policy/CloudWatchLogsReadOnlyAccess" }
]
}
resource "aws_iam_role" "example" {
count = length(local.roles)
name = local.roles[count.index].name
assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow",
Principal = {
Service = "ec2.amazonaws.com"
},
Action = "sts:AssumeRole"
}
]
})
}
resource "aws_iam_role_policy_attachment" "example" {
count = length(local.roles)
role = aws_iam_role.example[count.index].name
policy_arn = local.roles[count.index].policy
}
terraform init && terraform fmt && terraform validate
terraform apply -auto-approve
terraform state list
aws_iam_role.example[0]
aws_iam_role.example[1]
aws_iam_role.example[2]
aws_iam_role_policy_attachment.example[0]
aws_iam_role_policy_attachment.example[1]
aws_iam_role_policy_attachment.example[2]
terraform destroy -auto-approve
Reference
https://github.com/somaz94
'교육, 커뮤니티 후기 > T101(Terraform 101 Study) 스터디' 카테고리의 다른 글
T101(Terraform 101 Study) 6주차 (2) | 2023.10.14 |
---|---|
T101(Terraform 101 Study) 5주차 (0) | 2023.09.26 |
T101(Terraform 101 Study) 4주차 (0) | 2023.09.22 |
T101(Terraform 101 Study) 3주차 (0) | 2023.09.17 |
T101(Terraform 101 Study) 1주차 (0) | 2023.08.28 |