ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 05. 테라폼 팁과 요령: 반복문, 조건문, 배포 및 주의사항
    레거시/Terraform Up and Running 2021. 6. 14. 21:53
    반응형

    이 문서는 책 "테라폼 설치에서 운영까지"을 읽고 작성되었습니다. 최대한 요약해서 책 내용을 최소로 하는데 목표를 두고 있어서, 더 친절하고 정확한 내용을 원하신다면 책을 구매하시는 것을 추천드립니다. => 책 링크

    Terraform은 선언형 언어이다. 이는 인프라의 최종 형태를 추론하는데 적절하지만 반대로 절차적인 작업, 반복하거나, 조건에 따라 분기하거나, 무중단 배포 같은 작업들을 표현하기는 어려운 언어이다. 5장에서는 Terraform을 통해서 반복문, 조건문, 무중단 배포 작업 등에 대해 공부할 수 있다.

    반복문

    Terraform에서 반복문을 표현하는 것 중 가장 간단한 것은 바로 count이다. 예를 들어서 AWS IAM user를 3개를 반복적으로 생성해야 한다고 해보자. 그럼 이렇게 표현할 수 있다.

    provider "aws" {
      region = "us-east-1"
    }
    
    resource "aws_iam_user" "example" {
      count = 3
      name  = "gurumee"
    }

    이 때, AWS IAM user는 안 만들어진다. 왜냐하면, 한 계정당 전역적으로 이름은 유일해야하기 때문이다. 이를 해결하기 위해서 다음과 같이 변경하면 된다.

    provider "aws" {
      region = "us-east-1"
    }
    
    resource "aws_iam_user" "example" {
      count = 3
      name  = "gurumee.${count.index}"
    }

    이렇게 하면 gurumee.0, gurumee.1, gurumee.2 이름의 AWS IAM user가 3개 만들어진다. 그리고 최근 언어들은 모두 for-each 형태의 반복문을 지원하는데 가령 리스트, 맵 등의 컬렉션 데이터 타입을 순회하는 반복문을 뜻한다. Golang으로 따지면 다음의 코드이다.

    user_names := []string{"gurumee", "brenden", "ingoo"}
    for idx, user_name := range user_names {
        fmt.Println("idx: ", idx, " item: ", user_name)
    }

    위와 같은 코드 역시 Terraform에서 지원한다. 먼저 variables.tf를 다음과 같이 만든다.

     

    src/ch05/global/iam/variables.tf

    variable "user_names" {
      type        = list(any)
      description = "create iam users with three names"
      default     = ["gurumee", "brenden", "ingoo"]
    }
    
    # ...

    위는 리스트 데이터 타입을 선언한 것이다. 기본 값으로 세개의 이름이 들어있다. 그 후 main.tf를 다음과 같이 만들면 된다.

     

    src/ch05/global/iam/main.tf

    provider "aws" {
      region = "us-east-1"
    }
    
    resource "aws_iam_user" "example" {
      count = length(var.user_names)
      name  = element(var.user_names, count.index)
    }
    
    # ...

    여기서 length 함수는 리스트 user_names의 길이를 구할 수 있다. 기본 값이 들어가면 count=3이 된다. 또한, element 함수는 첫 번째 인자로 리스트, 두 번째 인자로 접근해야 할 인덱스를 받는다. 따라서, 차례대로 user_names[0], user_names[1], user_names[2]의 이름을 할당하게 된다. 따라서 AWS IAM user는 총 3개, 각각의 이름은 gurumee, brenden, ingoo가 할당된다. Terraform 역시 이러한 for-each 형태의 반복은 리스트 뿐 아니라 맵 데이터 타입도 가능하다.

    조건문

    책에서는 예제가 복잡한데 간소하게 변경해보았다. 먼저 Terraform은 "조건식"을 제공한다.

    condition ? true_val : false_val

    이런식으로 condition이 참이면 true_val을 거짓이면 false_val을 할당하는게 가능하다. 예를 들면 다음 코드를 살펴보자.

     

    src/ch05/global/iam/main.tf

    # ...
    
    resource "aws_iam_user_policy_attachment" "gurumee_cloudwatch_full_access" {
      count      = var.give_gurumee_cloudwatch_full_access ? 1 : 0
      user       = aws_iam_user.example.0.name
      policy_arn = aws_iam_policy.cloudwatch_full_access.arn
    }
    
    resource "aws_iam_user_policy_attachment" "gurumee_cloudwatch_read_only" {
      count      = var.give_gurumee_cloudwatch_full_access ? 0 : 1
      user       = aws_iam_user.example.0.name
      policy_arn = aws_iam_policy.cloudwatch_read_only.arn
    }

    위 코드는 give_gurumee_cloudwatch_full_access 변수가 참이면 AWS CloudWatch에 대한 모든 액세스 권한을, 거짓이면 Read Only 권한을 주는 코드이다. 먼저 give_gurumee_cloudwatch_full_access이라는 변수가 src/ch05/global/iam/variables.tf에 작성되어 있다. 이는 bool 변수이다.

     

    gurumee_cloudwatch_full_accessgive_gurumee_cloudwatch_full_access가 참이면 count 값이 1이 되어 1개 생성된다. 거짓이면 0이라서 생성되지 않는다. 반대로, gurumee_cloudwatch_read_onlygive_gurumee_cloudwatch_full_access가 참이면 count값이 0이 되어 생성되지 않고 거짓이면 1이라서 1개 생성하게 된다. 이렇게 조건문 if-else를 표현할 수 있다.

     

    사실 개인적인 생각으로는 한 조건에 따른 유사한 리소스 묶음을 2개나 만들어야 하는게 비효율적으로 보이긴 한데, 이렇게 쓰라니 넘어가야지..

    무중단 배포하기

    먼저 AWS ELB를 통해서, 무중단 배포 과정을 살펴보자.

    1. Launch Configuration이 새로 만들어진다.
    2. AutoScaling Group이 (1)을 통해서 새로 만들어진다.
    3. (2)가 만들어지면 ELB는 (2)를 참조하게 된다.
    4. 이전에 만들어진 Launch Configuration과 AutoScaling Group은 삭제된다.

    Terraform을 통해서 어떻게 표현할 수 있을까? 이를 위해서 Terraform은 메타 아규먼트로 lifecycle이란 것이 있다. lifecycle에는 다음의 형태로 리소스를 관리할 수 있게 도와준다.

    • create_before_destroy : 대체하는 리소스 생성 후 파괴한다.
    • prevent_destroy : 리소스를 파괴할 수 없게 한다.
    • ignore_changes : 리소스가 변경되도 무시한다.

    여기서 create_before_destroy를 이용하면 무중단 배포를 구현할 수 있다. 다음 코드를 살펴보자.

     

    src/ch05/modules/services/webserver-cluster/main.tf

    # ...
    
    resource "aws_launch_configuration" "example" {
      image_id        = "ami-0d5eff06f840b45e9"
      instance_type   = var.instance_type
      security_groups = [aws_security_group.instance.id]
    
      user_data = templatefile("${path.module}/user-data.tpl", {
        server_port = var.server_port,
        server_text = var.server_text,
        db_address  = data.terraform_remote_state.db.outputs.address,
        db_port     = data.terraform_remote_state.db.outputs.port,
      })
    
      # 여기 주목!
      lifecycle {
        create_before_destroy = true
      }
    }
    
    resource "aws_autoscaling_group" "example" {
      # 여기 주목!  
      name                 = "${var.cluster_name}-${aws_launch_configuration.example.name}"
      launch_configuration = aws_launch_configuration.example.id
      availability_zones   = data.aws_availability_zones.all.names
    
      load_balancers    = [aws_elb.example.name]
      health_check_type = "ELB"
    
      min_size = var.min_size
      max_size = var.max_size
    
      # 여기 주목!
      lifecycle {
        create_before_destroy = true
      }
    
      tag {
        key                 = "Name"
        value               = var.cluster_name
        propagate_at_launch = true
      }
    }
    
    # ...

    주목해야 하는 것은 aws_launch_configuration, aws_autoscaling_group 리소스들의 메타 아규먼트로 lifecyclecreate_before_destroy가 true로 지정된 것이다. 또한 aws_autoscaling_grouplaunch_configuration에 할당된 값이 변경되도 수동으로 인스턴스 개수를 조절하지 않는 한 자동으로 무중단 배포를 하지 않는다.

     

    name이 바뀌지 않아서 그러는데 다행히 aws_launch_configuration는 업데이트 될 때마다 이름이 바뀐다. 그래서 이를 참조하도록 aws_autoscaling_groupname에서 aws_launch_configuration의 이름을 참조하게 만든다.

     

    실제 값을 변경하면 무중단 배포가 일어나는 것을 확인할 수 있다. 다음 업데이트 이전 코드로 인프라스트럭처를 배포한 후 업데이트 이후 코드로 인프라스트럭처를 수정해보자. 변경되는데 5-10분 정도 시간이 걸리니 천천히 확인하면 된다.

     

    업데이트 이전 코드

    src/ch05/prod/services/webserver-cluster/main.tf

    # ...
    
    module "webserver_cluster" {
      source                 = "github.com/gurumee92/today-i-learned//terraform_up_and_running/src/ch05/modules/services/webserver-cluster"
      cluster_name           = "websever-prod"
      db_remote_state_bucket = "gurumee-terraform-state"
      db_remote_state_key    = "prod/data-stores/mysql/terraform.tfstate"
      instance_type          = "m4.large"
      min_size               = 2
      max_size               = 10
      server_port            = 8080
    
      # 이 부분이 바뀐다.
      server_text            = "Hello Version1"
    
      enable_autoscaling     = true
    }

    업데이트 이후 코드

    src/ch05/prod/services/webserver-cluster/main.tf

    # ...
    
    module "webserver_cluster" {
      source                 = "github.com/gurumee92/today-i-learned//terraform_up_and_running/src/ch05/modules/services/webserver-cluster"
      cluster_name           = "websever-prod"
      db_remote_state_bucket = "gurumee-terraform-state"
      db_remote_state_key    = "prod/data-stores/mysql/terraform.tfstate"
      instance_type          = "m4.large"
      min_size               = 2
      max_size               = 10
      server_port            = 8080
    
      # 이 부분이 바뀐다.
      server_text            = "Hello Version2"
    
      enable_autoscaling     = true
    }

    주의 사항

    책에서 소개된 몇 개의 문제점은 다음과 같다.

    • 카운트의 제한
    • 무중단 배포의 제한 사항
    • 유효한 계획의 실패
    • 까다로운 리팩토링
    • 강력한 일관성과 최종 일관성

    이중 count의 제한과 무중단 배포 제한 사항은 2021년 현재 해결되었다. 유효한 계획의 실패는 빈번히 일어난다. A가 AWS Console에서 "gurumee" 계정을 이미 만든 상태에서 B가 Terraform으로 "gurumee" 계정을 만들 때 terraform plan은 성공이지만 terraform apply는 실패한다. 이미 같은 이름의 사용자가 있기 때문이다. 이 문제에 대해서는 책에서는 이렇게 대처할 것을 권고한다.

    1. 테라폼을 사용하기 시작했다면 계속 테라폼으로 관리한다.
    2. 기존 인프라가 있는 경우 terraform import 명령어를 활용한다.

    까다로운 리팩토링 문제는 뭐.. 여느 프로그래밍 언어나 솔루션을 이용하는 스크립트나 동일할 것이다. 책에서는 리팩토링 과정에서 다음을 할 것을 추천하고 있다.

    1. 항상 terraform plan 명령어를 사용한다.
    2. 삭제 전에 생성한다. (create_before_destroy를 적극 활용하자)
    3. 모든 식별자는 불변하다.
    4. 일부 변수는 불변하다.

    마지막 문제는 가끔 발생한다. 특히 terraform apply 옵션에 -parallelism=n이 있다. 이 때 n에 숫자를 넣어주면 n 숫자만큼 병렬적으로 인프라스트럭처를 구성하게 된다. 매우 빠르게 구성할 수 있어서 좋지만 많은 리소스들을 병렬적으로 생성하다보면, A 리소스를 참조하는 B 리소스가 A가 채 생성되기도 전에 생성되서 실패하는 경우가 있다. 이 때는 여러번 실행을 한다든가 병렬 옵션을 제거하는 방법 등으로 일차원적으로 해결할 수 있다. 큰 문제는 아니기 때문에, 이 정도만 알고 넘어가자.

    5장 전체 코드

    5장 전체 코드는 다음 링크에서 확인할 수 있다.

    '레거시 > Terraform Up and Running' 카테고리의 다른 글

    [2판] 01. 왜 테라폼인가  (0) 2021.08.02
    06. 테라폼을 팀에서 사용하기  (0) 2021.06.16
    04. 테라폼 모듈  (0) 2021.06.11
    03. 테라폼 상태 관리  (0) 2021.06.02
    02. 테라폼 시작하기  (0) 2021.05.26
Designed by Tistory.