7 분 소요

서론

(주)샘튼에서 손 세차장 중개 플랫폼 ‘세차새차’를 운영하고 있는데, 보름님 덕분에 세차새차 외주를 맡게 되었습니다. 제 첫 임무는 개인 AWS 계정으로 세차새차 프로젝트가 돌아가고 있는데, 법인 AWS 계정으로 모든 리소스를 옮기는 것입니다. 콘솔 창으로 하나하나 리소스를 비교하면서 이전하기에는 시간이 많이 들고, 이후 서비스가 확장되고, 인원이 많아질수록 인프라 관리가 힘들어질 것을 예상해 Terraform을 이용했으면 좋겠다고 말씀하셨습니다. AWS 공식 블로그에서도 스타트업은 IaC 사용을 독려하고 있습니다.

이번에 법인 AWS 계정으로 이전하면서 든 생각이 Terraform 정말 좋은 도구인 것 같습니다. 밑에서는 법인 AWS로 이전하면서 겪은 과정을 설명하도록 하겠습니다.

회사 규모와 전략에 맞는 디렉토리 구조를 가져가자

거의 모든 Terraform 레퍼런스는 자신이 속한 회사의 디렉토리를 설명하면서 글을 시작합니다. 대표적으로 Kurly와 Terraform best practice 가 있습니다.

modules 디렉토리는 실제 AWS resource 들을 정의하는 곳이고, env 디렉토리는 dev, qa, stg, prod 등 실제로 프로비저닝되는 환경으로 나누고 module을 이용해 resource를 공장처럼 찍어내는 곳 입니다. 프로비저닝되는 환경은 깃 브랜치 전략을 따라가는 것이 베스트인 것 같습니다. 현재까지 완성된 세차새차 디렉토리는 아래와 같습니다.

개인적으로 위의 디렉토리 구조가 밸런스가 잘 잡혀있는 형태라고 생각합니다. 하지만 추가로 레퍼런스들을 더 찾아본 결과, 팀이 MS 구조를 따르고 있다면 폴더 구조를 MS 단위로 설계하라고 합니다. 만약 세차새차가 규모가 커져 MS 구조를 따른다고 한다면 environments/develop/service1 처럼 밑으로 하나의 뎁스를 더 줘서 ms 단위로 리소스를 찍어낼 수 있을 것 같습니다.

또, 세차새차가 규모가 더 커져 글로벌로 나가게 된다면, 리전 별로 디렉토리를 나눠야 합니다. 그렇다면 위로 뎁스를 하나 더 줘서 us-east-1/environments/develop/service1 처럼 디렉토리 구조를 짤 수 있을 것 같습니다.

이 외에도 정말 다양한 전략이 있습니다. 필요한 권한 별로 디렉토리 구조를 짜는 등등 다양한 전략이 있지만, 팀의 서비스 규모에 맞게 컨벤션을 잘 정한다면 그렇게 큰 문제는 없을 것 같습니다. (단, 프로비저닝 되는 디렉토리와 리소스 정의 디렉토리는 꼭 나눠줘야 할 것 같습니다)

Terraform을 OOP(JAVA)로 생각하기

지금이야 위 디렉토리 구조처럼 environments와 modules로 나누는 것이 당연하다고 생각이 드는데, 처음에는 이 구조를 이해하는 데 엄청 애를 먹었습니다. Terraform tutorial과 인프런 기초 강의에서는 resource를 정의하고 그 디렉토리에서 terraform apply를 쳐서 리소스를 생성했습니다.

실무에서는 module(디렉토리의 module이 아니라, 문법)을 사용해 똑같은 리소스를 찍어낸다고 설명은 하지만, 구체적인 설명이 없었고 또 문서를 봐도 도통 이해하기가 어려웠습니다. 지금은 module과 resource에 대한 이해가 조금 되었고, 이를 자바 언어와 매핑 시켰을 때 이해가 훨씬 잘 되었습니다.

.
├── README.md
├── environments
│   └── global
│       ├── iam
│       │   ├── main.tf -> IAM globalIam = new IAM(field1, field2)
│       │   ├── terraform.tfvars -> field1 = 1, field2 = 2
│       │   └── variables.tf -> int field1, int field2
│      
└── modules
    ├── global
    │   ├── iam
    │   │   ├── main.tf -> class
    │   │   ├── output.tf -> toString()
    │   │   ├── provider.tf -> import
    │   │   └── variables.tf -> field
// modules/global/iam/provider.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

// modules/global/iam/variables.tf
variable "iam_user_names" {
  type        = list(string)
  default     = []
  description = "AWS IAM User names"
}

variable "iam_group_membership" {
  type = list(object({
    membership_name : string
    group_users : list(string)
    group_name : string
  }))
}

// modules/gloabal/iam/main.tf
// resource 집합소
resource "aws_iam_user" "iam_user" {
  for_each = toset(var.iam_user_names)
  name     = each.key
}

resource "aws_iam_user_login_profile" "user_login_profile" {
  for_each   = toset(var.iam_user_names)
  user       = each.key
  depends_on = [aws_iam_user.iam_user]
}

resource "aws_iam_group" "iam_group" {
  for_each = { for group in var.group_and_policy : group.group_name => group }
  name     = each.key
}

// modules/global/iam/output.tf
// 생성된 resource의 output 값
output "password" {
  value = aws_iam_user_login_profile.user_login_profile.*
}

..
import hashicorp.aws.*

public class Iam() {

	int iam_user_names;
	int iam_group_membership;

	public iam_user makeIamUser() {
		..
	}
	
	public iam_group makeAwsIamGroup() {
		..
	}
	
	public String toString() {
		..
	}

}
// environments/global/iam/main.tf
module "user_iam" {
  source                    = "../../../modules/global/iam"
  iam_user_names            = var.user_names
  group_and_policy          = var.group_and_policy
  group_policy_combinations = local.group_policy_combinations
  iam_group_membership      = var.group_memberships
}

// environments/global/iam/terraform.tfvars
user_names = [
  "tf_02ggang9",
  "tf_gilteun",
  "tf_hisumin",
  "tf_jeongyun",
  "tf_pom",
  "tf_Shim",
  "tf_shkisme",
  "tf_suim",
  "tf_wcnc-backend",
]

// JAVA
Iam user_iam = new Iam(user_names, ..)

개별 블록을 사용하지 말고, for_each or count를 이용해 템플릿을 만들어라

테라폼 언어에서 제공하는 for_each와 count를 사용하면 중복되는 코드를 줄일 수 있습니다. 예전에 Terraform 실습할 때 사용했던 코드는 아래와 같습니다.

resource "aws_iam_user" "admin_user" {
  name = var.admin_user_name
}

resource "aws_iam_user" "cicd_user" {
  name = var.cicd_user_name
}

resource "aws_iam_user" "dev_user" {
  name = var.dev_user_name
}

위처럼 짜게 된다면 새로운 팀원이 올 때마다 매번 3줄의 코드가 main.tf에 적히게 됩니다. 또 group에 attach 하는 과정과 변수 추가까지 생각했을 때 어마어마한 코드 중복이 생기게 됩니다.

이를 해결하기 위해 테라폼 문법을 좀 더 공부했고, 아래와 같이 개선할 수 있었습니다.

resource "aws_iam_user" "iam_user" {
  for_each = toset(var.iam_user_names)
  name     = each.key (each.key == each.value)
}

4년 동안 컴공에서 구른 대가 중 하나가 다른 언어를 봤을 때, 대충 어떤 형식으로 굴러가겠군! 정도는 알 수 있다는 것입니다. 딱 봤을 때, list 형태의 iam_user_names에서 중복을 제거하고 루프를 돌면서 iam_user를 만들겠구나 정도는 이해할 수 있습니다.

이렇게 중복되는 코드를 제거하고, 템플릿처럼 코드를 짜기 위해서는 테라폼 문법에 대해 조금 알고 있어야 합니다. 아래처럼 조금 복잡하게 생긴 것도 있지만, 한번 공부하시면 쉽게 이해하실 수 있습니다.

resource "aws_iam_group_membership" "admin_group_membership" {
  for_each = { for gm in var.iam_group_membership : gm.membership_name => gm }
  name     = each.key
  users    = each.value.group_users
  group    = each.value.group_name

  depends_on = [
    aws_iam_user.iam_user,
    aws_iam_group.iam_group
  ]
}

for_each와 count 까지 학습을 해서 템플릿을 만들 수 있으면 테라폼으로 인프라를 관리하기 위한 준비는 끝난 것 같습니다. 나머지는 실제로 코드를 짜고 부딪혀 보면서 경험을 쌓아올리기만 하면 될 것 같습니다.

테라폼이 모든 의존성 순서를 조정 하지는 않는다 (트러블 슈팅)

테라폼은 자기가 알아서 어느정도는 resource를 만들 때 순서를 조정합니다. 하지만 가끔 순서 보장이 안될 때가 있는데 이럴 때는 depends_on을 걸어 해결할 수 있습니다.

terraform plan을 했을 때는 아무런 문제가 없었지만 실제 terraform apply를 했을 때 순서가 꼬여 resource를 제대로 생성하지 못했습니다. 이를 통해서 알 수 있는 점이 terraform plan에서 실제 발생할 수 있는 에러를 모두 잡을 수 없다는 것이였고, 의존성 순서를 완벽히 보장하지 않는다는 것입니다.

하지만 docker compose를 작성해 보신 분들은 바로 depends_on을 떠올리셨을 것이고 이 문제는 쉽게 해결할 수 있었습니다.

이러한 문제를 해결하기 위해 Terraform code도 테스트하는 툴이 있는데, 현재 도입하기에는 학습 곡선이 너무 튈 것 같고, 생산성이 너무 떨어질 것 같아 테스트 툴 도입은 과감히 포기했습니다. 요즘에는 아래의 사진을 머리에 박고 코드를 작성하는 것 같습니다. 테라폼 테스트 툴을 도입하면 물론 안전성이 올라가고 도움이 되겠지만, 시간이 많이 소요되지 않을까? 실제 6월까지 어느 정도 기능이 나와야 하는데 +이지만 결국 -가 아닐까?

Terraform Backend를 사용해 형상 관리를 하자 (트러블 슈팅)

Terraform code를 작성하고 PR을 올리고 시연까지 완료하고 code를 머지했습니다. 그다음에 팀원분이 로컬에 Terraform을 설치하고 apply 돌려봐도 될까요라는 질문을 하셨습니다.

여기서 드는 생각이 terraform apply를 하게 되면, .tfstate 파일이 나오게 되고, 이 파일과 실제 만들어진 인프라 리소스와 비교를 하는 과정이 일어나게 되는데 .gitignore 파일에 .tfstate 파일을 걸어둔 상태였습니다. 따라서 이미 시연 과정에서 IAM user가 만들어졌는데 똑같은 이름으로 terraform apply를 하게 되면 제 로컬에서는 No changes가 떠서 apply를 진행하지 않지만, 다른 팀원분의 로컬에서는 .tfstate 파일이 없어 apply가 진행되게 됩니다.

어떤 에러가 발생할지 몰라 일단 제 로컬에서만 테라폼을 돌리기로 했고, 우선순위가 낮았던 Terraform backend 설정을 먼저 하게 되었습니다. 우선순위가 낮았던 이유는 Terraform을 저 혼자 알고 있었기 때문입니다.

테라폼 백엔드는 위와 같은 상황을 방지하기 위해 remote 저장소에 .tfstate 파일을 저장하고 다른 팀원들과 이 파일을 공유해 형상 비교를 하는 기능입니다. 대표적인 remote 저장소는 AWS의 S3가 있습니다. 얼른 S3를 Terraform code로 만들었고, 아래의 코드를 작성해 Terraform backend를 설정했습니다.

// provider.tf
terraform {
  backend "s3" {
    bucket = "..."
    key    = "environments/develop/terraform.tfstate"
    region = "ap-northeast-2"
  }
}

위의 코드를 작성할 경우 자동으로 s3에 저장된 .tfstate 파일과 실제 인프라를 비교하게 됩니다. 또 저장까지 알아서 해줍니다. dynamoDB를 사용해 locking 파일을 관리하는 것도 포함이 되어야 하는데, 내부적으로 회의 결과 아직까지 락킹까지 신경을 안 써도 될 것 같아 도입은 나중으로 미뤘습니다.

TODO

  • Terraform plan, Terraform apply 파이프라인 구축
  • Terraform 문서 자동화
  • 락킹 파일 관리
  • 데라그란트 도입
  • 일라스틱 빈스톡 테라포밍

결론

법인 AWS 계정으로 이전 테스크를 맡기 전에 Terraform 사용에 관해서 ?를 띄웠습니다. 모듈에 대한 이해가 없었고, 재사용성이 떨어진다고 생각을 했지만 테라폼 언어를 자바 언어로 생각해보니 코드의 윤곽과 디렉토리 구조를 이해할 수 있었습니다. 또, 실제 리소스를 만들고 제거하는 과정이 매우 간단해 테스트하기 굉장히 편했고, 테라폼 문서가 굉장히 잘 되어있어 AWS 서비스를 더욱 깊게 이해할 수 있었습니다.

또, 정말 다양한 장점들을 겪어 지금은 테라폼이 거의 필수라고 생각하고 있는데 다른 분들 생각도 궁금하네요! 다음에는 위의 TODO를 해결한 글을 작성해 보도록 하겠습니다.

참조

[AWS를 쓰는 스타트업 창업자가 자주하는 10가지 실수들 – 이것만은 피하세요! Amazon Web Services](https://aws.amazon.com/ko/blogs/korea/ten-mistakes-founders-make-on-aws-and-how-to-avoid-them/)

terraform-best-practices/examples/large-terraform at master · antonbabenko/terraform-best-practices

DevOps팀의 Terraform 모험

확장 가능한 테라폼 코드 관리

How to Auto Format HCL Terraform Code in Visual Studio Code

Terraform Azure Module 만들기

[Terraform] 테라폼 사용전 알아두면 좋은 Best Practice 8가지

[Terraform 모범 사례를 정리해보았습니다 DevelopersIO](https://dev.classmethod.jp/articles/terraform-bset-practice-kr/)

https://github.com/terraform-docs/terraform-docs

[Terraform] for_each 문법 및 사용 예시(Meta Argument)

[IaC] Terraform 작성 및 배포(2) - for_each, flatten, local values

천만 사용자를 위한 AWS 클라우드 아키텍처 진화하기 - 김준형 솔루션즈 아키텍트(AWS)

General options for all environments - AWS Elastic Beanstalk

How to Launch AWS Elastic beanstalk using Terraform

Terraform을 기반한 AWS 기반 대규모 마이크로서비스 인프라 운영 노하우 - 이용욱(삼성전자)

Terraform 도입과 CI/CD 파이프라인 구축 및 운영

카테고리:

업데이트: