All Articles

Terraform Proejct Directory Structure Best Practice

다른 프로젝트와 마찬가지로 terraform도 프로젝트 디렉토리 구조를 잘 잡는것이 중요하다. 하지만 terraform의 HCL (HashiCorp Configuration Language)은 Python이나 Java같은 프로그래밍 언어가 아니라 말 그대로 설정 언어이기 때문에 (YAML 같은) 어떻게 프로젝트 구조를 잡아야 좋을지 감이 쉽게 오지 않을수 있다.

이미 언급한대로 HCL은 설정언어 이다. 즉 terraform의 HCL 코드 repository는 여러 설정파일 들을 모아놓은 repository라고 생각할 수 있다. 그렇다면 잠시 생각의 범위를 넓혀서 생각해보자. 시스템 설정파일 이나 여러 DevOps task들은 꼭 terraform을 사용할 필요는 없다. Terraform이 나오기 전에는, 예를 들어, Python 스크립트를 사용하기도 했다. Terraform의 HCL을 사용하여 시스템 인프라를 설정하고 terraform 명령어로 실행하는것과 마찬가지로, python을 사용하여 시스템 설정 스크립트를 만들어서 python interpreter로 실행시키는 것이다(물론 python을 사용하는 것보다 terraform을 사용하는 것이 훨씬 쉽고 효과적임으로 terraform을 사용하는 것이지만).

자 이제 terraform 대신에 Python을 사용한다고 가정해보자. 어떻게 프로젝트 구조를 만들것인가? 모든 개발자가 한 파일에 모든 코드를 다 집어넣는 소위 스파게티 코드는 나쁜것임을 안다. 대부분의 개발자들이 각 서버 나 시스템 타입을 기준으로 파일들을 나눌것으로 시작할 것이다. 예를 들어. frontend 서버 설정 스크립트 파일, backend 서버 설정 스크립트 파일, database 설정 스크립트 파일, network 설정 스크립트 파일 등등 으로 나누어서 관리 하는것으로 시작할 것 이다. 그렇게 해야 쉽게 해당 설정들을 파악할 수 있고, 원하는 서버 혹은 리소스에만 변경 사항을 적용되게 하기 용이하다.

Terraform directory 기본 구조


Terraform으로 다시 돌아와서, terraform 프로젝트 구조도 마찬가지가 될것이다. 아래와 같은 점들을 중점으로 디렉토리 구조를 구상하면 된다:

1. 각 컴포넌트 혹은 카테고리 별로 directory가 나뉘어져야 하며 각 directory는 독립적으로 실행 가능해야 한다.

예를 들어, frontend 설정은 frontend 라는 directory에, backend 설정은 backend directory에 포함시킨다. 그리고 각 directory는 독립적으로 실행 될수 있어야 한다. 즉 frontend 설정을 적용 할려면 frontend directory 안에서 terraform apply를 커맨드를 실행시키는 것이다. 이렇게 함으로 frontend 설정 변경이 backend나 다른 시스템 컴포넌트에 의도치 않게 적용되는 것을 막을 수 있다. 이렇게 독립적으로 실행 가능한 directory가 될려면 각 directory마다 독립적인 state file을 가지고 있어야 한다. 이와 관련해서는 아래에서 더 자세히 이야기 할것이다.

2. 각 컴포넌트 (directory)는 필요하면 서로의 설정 사항을 읽을수 있어야 한다.

각 directory는 독립적으로 실행 되어야 하지만 고립되어서 존재 할 수는 없다. frontend 는 backend 에 대해서 알고 있어야 하고 backend 는 database 에 대해서 알고 있어야 한다. 그럼으로 각각 directory에서 다른 directory의 설정 사항을 읽을 수 있어야 한다.

3. Production 그리고 Staging 등 다른 단계의 인프라 설정 또한 다른 directory로 구성되어 서로 독립적인 구조가 되어야 한다.

저자도 처음 terraform을 사용할때는 production 과 staging 등의 단계별 구분을 두지 않고 동일한 terraform 설정 파일들을 사용했었다. 하지만 이렇게 되면 staging에만 적용해야 하는 설정이 production에도 설정되어 버리는 risk가 너무 크다. 그럼으로 production 과 staging 등의 시스템 개발 단계들은 서로 분리해서 설정하는 것이 좋다.

위의 사항들을 중점으로 프론트앤드, API 백앤드, 그리고 RDBMS DB와 Redis 로 구성되어 있는 시스템을 예로 들어 보자. Terraform 의 디렉토리 구조는 아래와 같이 될것이다:

infrastructure
├── global
│   ├── dns
│   ├── iam
│   └── s3
├── mgmt
│   ├── services
│   │   ├── ci
│   │   ├── key-management
│   │   ├── monitoring
│   │   └── vpn
│   └── vpc
├── prod
│   ├── database
│   │   └── aurora
|   │   └── redis
│   ├── services
│   │   ├── backend
│   │   └── frontend
│   └── vpc
└── staging
    ├── database
    │   └── mysql
    │   └── redis
    ├── services
    │   ├── backend
    │   └── frontend
    └── vpc

4개의 상위 directory들은 아래와 같다:

  • prod : Production system infratructure 설정 코드
  • staging : Staging system infrastructure 설정 코드
  • global : Production과 staging 에 모두 사용되는 system infrastrue 설정 코드
  • mgmt : Infrastructure들을 관리 및 보조하는 resource 혹은 infrastructure 들의 설정 코드

각 상위 directory 들은 다시 여러 하의 directory로 나뉘게 된다. 예를 들에 Prod는 아래 3개의 하위 구조로 나뉘어지게 된다:

  • Database : 데이터 베이스 설정 코드들이 위치하게 된다. 위의 경우에는 2개의 데이터베이스 (AWS Aurora 와 redis)가 있음으로 각각 DB에 해당하는 하위 디렉토리가 2개가 생성되어 있다. 그리고 각 하위 디렉토리에는 해당 DB 설정 코드들이 위치하게 된다. 예를 들어, databas/aurora 디렉토리 에는 AWS Aurora DB 설정 코드들이 위치하게 되고 database/redis 디렉토리 에는 redis 설정 코드들이 위치하게 된다.
  • Services : API backend, frontend 등 시스템의 실제 서비스가 운영되는 서버들의 설정 코드들이 위치하게 된다. 위의 경우에는 시스템이 frontend 그리고 API backend 이렇게 2개로 나뉘어져 있음으로 각 서비스에 해당하는 하위 디렉토리가 2개가 생성되어 있다. 그리고 각 하위 디렉토리에는 해당 서버들의 설정 코드들이 위치하게 된다. 예를 들어, services/backend 디렉토리 에는 backend 서비스를 운영하기 위해 사용되는 서버 및 시스템 인프라의 설정 코드들이 위치하게 된다.
  • VPC: 네트워크 관련 설정 코드들이 위치하게 된다. AWS에서는 주로 VPC를 통해 네크워크 관리를 하게 됨으로 VPC라고 이름을 지었다. Frontend, Backend, Database 등의 모든 시스템의 VPC 설정 코드는 이 디렉토리에 위치하게 된다. 네트워크 설정 코드를 따로 상위 디렉토리에 나누어 놓는 이유는, 이렇게 함으로서 네트워크 구조 관리나 파악이 쉽게 되기 때문이다. 각각의 service는 독립적인 네트워크 설정을 가지고 있고, 또 서로 시스템의 보안 방침에 의해 연결되어 있는것이 대부분의 경우인데, 네트워크 설정을 한곳에서 함으로서 네트워크 구조의 설정 개발 및 파악과 유지가 더 쉬워지는 장점이 있다. 저자도 예전에는 VPC 설정을 따로 관리하지 않고 각 service 디렉토리에 포함시켯었는데, 각 service끼리 vpc peering을 통해 연결 되다 보니 구조가 복잡해져서 나중에 전체 구조나 설정 파악이 쉽지 않았다. 그래서 상위 카테고리로 따로 나누고 난 후 훨씬 전체적인 관리가 쉬어졌다.

디렉토리의 파일 구조


각 하의 디렉토리들은 어떠한 파일들로 구성되어야 하는지에 대해서 이야기 해보자. 기본적으로 terraform 파일은 복잡한 로직이 들어가지는 않는다 (복잡한 로직을 HCL이 support 하지도 않고 필요 하지도 않다). 그럼으로 여러 파일로 나눌 필요는 없다. 저자는 아래와 같이 4개 파일로 나눈다:

├── services
│   ├── backend
│   │   ├── backend.tf
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   └── vars.tf
  • backend.tf: 해당 서버 혹은 시스템의 terraform state file 에 대한 meta data를 포함하고 있다. 각 디렉토리가 독립된 terraform 실행 단위이기 때문에 각 디렉토리 마다 독립된 terraform state file이 생생되어야 한다. 그리고 backend.tf 파일에 terraform state file의 대한 정보가 저장되어 있다. 저자의 경우 주로 AWS S3에 terraform state file을 저장 하기 때문에 해당 S3 내용이 포함되어 있다. AWS S3에 state file을 저장하는 방법은 S3로 Terraform State file 관리하기에 자세히 나와있다.
  • main.tf: terraform 설정 코드 파일.
  • vars.tf: Variable 들을 지정해 놓은 파일.
  • outputs.tf: 해당 directory가 실행된후 생성될 output들을 설정해놓은 파일.

다른 directory의 설정 읽어 들이기


위의 내용 대로 directory 구조를 구성하게 되면 가장 염려되는 부분이 다른 directory의 설정을 어떻게 읽어들이는가 하는 점이다. Terraform은 remote_state 통해 외부 설정을 읽어들일수 있게 해준다.

예를 들어, 오직 frontend 서버에서 보낸 http request만 backend 서버에서 수락하도록 설정 해야 한다면, AWS 상에서는 frontend 서버가 속해있는 security group의 id를 backend 서버가 속해있는 security group에서 설정 해줘야 한다. 그러기 위해선 먼저 읽어 들여야 하는 모듈을 지정해주야 한다.

data "terraform_remote_state" "frontend" {
  backend = "s3"
  config {
    bucket  = "tf-state"
    key     = "prod/frontend.state"
    region  = "ap-northeast-2"
    profile = "blog"
  }
}

그 다음 해당 모듈에서 필요한 output을 읽어들여 설정 하면 된다.

resource "aws_security_group_rule" "http_ingress_from_frontend" {
  type                     = "ingress"
  from_port                = 8080
  to_port                  = 8080
  protocol                 = "TCP"
  security_group_id        = "${aws_security_group.backend.id}"
  source_security_group_id = "${data.terraform_remote_state.fronend.security_group_id}"
}

주의 해야 할점은, security_group_id 라는 output을 frontend 모듈에서 꼭 설정을 해줘야만 한다.

# in the output.tf file of frontend module
output "security_group_id" {
  value = "${aws_security_group.frontend.id}"
}