Terraform 입문자를 위한 미세 팁

​ ​

클라우드를 활용하는 경우, 인프라 구성 관리 도구로 테라폼을 많이 사용합니다. 오늘은 처음 테라폼을 도입하려고 할때 알아두면 좋은 점들에 대해 정리해보려 합니다.

Procedural vs Declarative

# Ansible
- ec2:
  count: 10
  image: ami-v1
  instance_type: t2.micro

# Terraform
resource "aws_instance" "example" {
   count         = 10
   ami           = "ami-v1"
   instance_type = "t2.micro"
}

위에 나와있는 코드는 Ansible과 Terraform으로 EC2 인스턴스를 구성하는 코드입니다. 만일 여기서 둘의 count 값을 15로 변경한다면 어떻게 변할까요?

먼저 Ansible의 경우, declarative이며 mutable infrastructure를 지향합니다. 따라서 이미 생성된 10개의 인스턴스에 15개의 인스턴스가 추가로 생성되어 총 25개의 인스턴스가 떠있게 됩니다. 반면에 Terraform의 경우, procedural이며 immutable infrastructure를 지향합니다. count를 15로 선언했기 때문에 Terraform은 이전 상태와 비교한다음, 5만큼의 변경에 대해 교체를 수행합니다. 결과적으로 총 15개의 인스턴스가 떠있게 됩니다.

서로 지향하는 성격이 다르다보니 적절한 상황에 사용하거나 함께 사용하면 좋습니다. 예를 들어 Provisioning 단계에서 Terraform을 사용하고 Configuration, Dependency 설정 단계에서 Ansible을 사용하실 수 있습니다.


Terraform vs CloudFormation

AWS를 사용하는 경우, 클라우드 내에서 CloudFormation이라는 서비스를 제공합니다. CloudFormation 역시 Terraform과 같은 기능을 제공하다보니 도입하기 전에 비교를 많이 합니다. 우선 모듈화, 개발, 문서 측면에서는 Terraform이 더 편했습니다. 이외의 큰 차이를 정리하자면 아래와 같습니다.

CloudFormation은 AWS 지원이 빠릅니다. 신규 릴리즈된 서비스나 설정들은 Terraform AWS 모듈에 반영되기까지 시간이 좀 걸립니다. 반면에 CloudFormation은 대부분 바로 지원해주다보니 더 편할 수 있습니다.

Terraform은 다른 클라우드 서비스도 지원합니다(Azure, Google Cloud). 만일 멀티클라우드 이슈에 대한 대응까지 고려하고 있다면 Terraform을 추천드립니다.


Terraform Remote Backend

Terraform은 상태를 Consul, S3, Enterprise 등의 원격 스토리지에 저장할 수 있습니다. 여러 명이 팀으로 일하는 경우, 인프라 변경 상태에 대한 동기화가 필요합니다. 이 경우 Remote Backend를 고려하시면 좋습니다. state 파일은 workspace, env에 따라 서로 다른 파일로 관리할 수 있습니다.


Terraform Migration

이미 생성되어 있는 수 많은 인프라를 한번에 Terraform으로 옮기는 일은 정말 어렵습니다. 우선 모듈마다 점진적으로 마이그레이션 하는 방법을 추천드립니다. Terraform은 아래의 코드처럼 이미 생성되어 있는 리소스를 불러올 수 있습니다.

resource "aws_vpc" "default" {
  # resource configuration...
}

# update remote state
$ terraform import aws_vpc.default vpc-abc12345

또는 data 블럭을 이용해서 id, arn 등의 값을 불러올 수 있습니다. 예를 들어 아래는 Packer로 생성된 가장 최근 버전의 AMI를 불러오는 코드입니다.

data "aws_ami" "app" {
  most_recent = true
  name_regex  = "app-\\d{10}"
  owners      = ["account_number"]
}


Terraform Module

Terraform은 모듈화를 통해 인프라를 재사용할 수 있습니다. 하지만 먼저 기존의 인프라를 어떻게 모듈화할지 많은 고민이 필요합니다. 자주 변경되어야 하는 일부분은 Terraform 관리 대상에서 제외시키는 방법도 있습니다. 또한 인프라 장애 대응이 필요한 부분은 쉽게 HA를 구성할 수 있도록 작성해야 합니다.

module "network" {
   source = "./network"
   name   = "default"
   cidr   = "000.0.0.0/16"

   azs    = ["ap-northeast-2a", "ap-northeast-2c"]
   public_subnets = ["000.0.0.0/22", "111.1.1.1/22"]

   tags = {
      dept    = "mydept"
      service = "app"
   }
}

# common tags
tags = "${merge(var.tags, map("Name", format("%s-public-%s", var.name, var.azs[count.index])))}"

각 모듈은 Input과 Output Variable을 가집니다. 위의 예시는 네트워크에 관련된 모듈입니다. 모듈을 통해 생성된 모든 리소스는 공통된 태그를 통해 관리할 수 있으며 만일 네트워크 구성을 변경해야하는 경우, CIDR 값만 수정하면 됩니다.


Terraform Loop, Conditionls (0.12)

Terraform은 0.12 버전을 기점으로 더 효율적인 코드를 작성할 수 있게 되었습니다. 따라서 새로 시작하신다면 0.12+ 버전 사용을 권장드립니다. 예시를 통해 Terraform에서 루프를 어떻게 정의하는지 설명드리겠습니다. 아래의 예시는 IAM Role에 Policy를 연결하는 코드입니다.

locals {
   lambda_backend_policy_arns = [
      "arn:aws:iam::aws:policy/AmazonRDSFullAccess",
      "arn:aws:iam::aws:policy/CloudWatchFullAccess",
      "arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess",
   ]
}

resource "aws_iam_role_policy_attachment" "attach" {
   count = "${length(local.lambda_backend_policy_arns)}"

   role = "${aws_iam_role.lambda_backend.name}"
   policy_arn = "${local.lambda_backend_arns[count.index]}"
}

이전에는 위와 같이 Array 타입의 인덱스를 통해 Loop를 정의해야 했습니다. 하지만 0.12 버전부터 for-loop, for-each 구문을 지원하기 시작했습니다.

variable "subnet_numbers" {
   default = {
      "ap-northeast-2a" = 1
      "ap-northeast-2b" = 2
      "ap-northeast-2c" = 3
   }
}

resource "aws_subnet" "example" {
   for_each = var.subnet_numbers
   
   vpc_id            = aws_vpc.example.id
   availability_zone = each.key
   cidr_block        = cidrsubset(
      aws_vpc.example.cidr_block, each.value
   )
}

이외에도 찾아보시면 다양한 타입과 연산을 지원합니다. 이 글이 처음 입문하시는데 조금 도움이 되셨으면 좋겠습니다!