교육, 커뮤니티 후기/T101(Terraform 101 Study) 스터디

T101(Terraform 101 Study) 2주차

Somaz 2023. 9. 9. 00:04
728x90
반응형

Overview

이번주는 CloudNet@에서 진행하시는 T101(Terraform 101 Study) 스터디 2주차이다!
 
블로그 내용은 '테라폼으로 시작하는 IaC’ 책을 기준하여 정리하였다.
 

출처 : https://developer.hashicorp.com/terraform/tutorials/aws-get-started/infrastructure-as-code

 


 

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

VPC 확인
subnet 확인

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

출처 : CloudNet@ 스터디

 

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

 

2. Terraform 변수 사용법(use-variable) - AWS

Overview 2023.04.04 - [Hashicorp] - 1. Terraform 기초 : 설치 및 .tf 파일 설명 오늘은 Terraform 의 변수사용법에 대해 알아보려고 한다. Terraform 변수 사용법 먼저 소스코드 주소는 아래와 같다. https://github.com/s

somaz.tistory.com

2023.04.09 - [IaC/Infrastructure Provisioning] - 3. Terraform 다양한 변수(variable, local, data...output, input) - AWS

 

3. Terraform 다양한 변수(variable, local, data...output, input) - AWS

Overview 2023.04.04 - [Hashicorp] - 1. Terraform 기초 : 설치 및 .tf 파일 설명 2023.04.06 - [Hashicorp] - 2. Terraform 변수 사용법(use-variable) 오늘은 테라폼의 다양한 변수들에 대해 공부해보려고 한다. 1. variable 변수

somaz.tistory.com

 


 

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

 

GitHub - terraform-google-modules/terraform-google-cloud-storage: Creates one or more Cloud Storage buckets and assigns basic pe

Creates one or more Cloud Storage buckets and assigns basic permissions on them to arbitrary users - GitHub - terraform-google-modules/terraform-google-cloud-storage: Creates one or more Cloud Stor...

github.com

 
 
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"]

admin
user
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

728x90
반응형