CircleCI 에서 Terraform fmt 수행하기

Golang 에서 소스 코드의 스타일을 맞춰주는 gofmt 커맨드가 있다면 Terraform에는 terraform fmt 커맨드가 있다.

Terraform으로 인프라스트럭처를 코드화 하다보면 각자 스타일에 따라 들여쓰기나 뛰어쓰기를 하게되는데, 이 때 terraform fmt 명령을 사용하면 들여쓰기, 뛰어쓰기를 Terraform의 표준 포맷에 맞게 자동으로 수정해준다.

지금 일하고 있는 팀(글을 포스팅하는 현재는 퇴사했지만…ㅠ_ㅠ)에서는 처음엔 이 fmt 명령을 사용하지 않고 Terraform 파일을 작성했었는데 최근에 포맷을 맞추면 좋겠다는 이야기가 나와서 fmt 를 적용하게 되었다.
개발자 각자가 PR을 올리기 전에 git hook 등으로 fmt 체크를 할 수도 있겠지만 인프라스트럭처를 코드로 작성하기로 했다면 인프라스트럭처 코드 역시 CI 를 통해 자동화된 테스트나 빌드, 혹은 실제 인프라에 적용하는 단계까지 고려해 볼 수 있다.

CircleCI config.yml 작성

결론부터 이야기하면 다음과 같이 terraform validate, terraform fmt, tflint 를 PR이 생성/수정 될 때 마다 CircleCI 에 의해서 자동 실행될 수 있도록 설정해서 사용하고 있다.

다음은 실제 빌드에 사용하고 있는 .circleci/config.yml 파일의 내용이다.
마침 circleci 2.0 을 도입한 직후라 version: 2 를 명시하고 CircleCI 2.0 포맷에 맞춰 작성되어 있다.

{Project Root}/.circleci/config.yml

version: 2

terraform: &terraform
  docker:
    - image: hashicorp/terraform:0.11.1
  working_directory: /tmp/workspace/terraform

jobs:
  validate:
    <<: *terraform
    steps:
      - checkout
      - run:
          name: Validate Terraform configurations
          command: find . -type f -name "*.tf" -exec dirname {} \;|sort -u | while read m; do (terraform validate -check-variables=false "$m" && echo "√ $m") || exit 1 ; done
      - run:
          name: Check if Terraform configurations are properly formatted
          command: files=$(terraform fmt -write=false); if [ -n "$files" ]; then echo "$files"; echo -e "\nSome terraform files need be formatted, run 'terraform fmt' to fix"; exit 1; fi
      - run:
          name: Install tflint
          command: curl -L -o /tmp/tflint.zip https://github.com/wata727/tflint/releases/download/v0.5.2/tflint_linux_amd64.zip && unzip /tmp/tflint.zip -d /usr/local/bin
      - run:
          name: Check Terraform configurations with tflint
          command: tflint

workflows:
  version: 2
  build:
    jobs:
      - validate

위에서 부터 파일을 부분별로 나눠서 살펴보자.

docker image 정의

version: 2

terraform: &terraform
  docker:
    - image: hashicorp/terraform:0.11.1
  working_directory: /tmp/workspace/terraform

hashicorp/terraform:0.11.1 도커 이미지를 사용해서 빌드를 수행한다.
terraform: &terraform 으로 정의한 부분은 아래 소스에서 <<: *terraform 부분에 치환된다.

validate job 정의

jobs:
  validate:
    <<: *terraform
    steps:
      - checkout

validate 잡을 정의한다. project를 repo에서 checkout 한다.

terraform validate 실행

      - run:
          name: Validate Terraform configurations
          command: find . -type f -name "*.tf" -exec dirname {} \;|sort -u | while read m; do (terraform validate -check-variables=false "$m" && echo "√ $m") || exit 1 ; done

*.tf 형식의 파일을 찾아 정렬하고 terraform validate를 실행한다.
terraform validate에서는 Terraform 파일의 문법상 오류를 검증한다.
여기에는 -check-variables=false 옵션을 사용했는데 default인 -check-variables=true 옵션을 사용할 경우 provider 설치 유무 등 모든 required variables 의 입력여부를 체크하기 때문에 이를 통과시키기 위해서 aws provider를 필요한 경로에 모두 설치하는 등의 불필요한 작업을 실행해야 한다. 이 옵션은 체크가 필요한 요건에 따라 수정해서 사용할 수 있겠다.

terraform fmt 실행

      - run:
          name: Check if Terraform configurations are properly formatted
          command: files=$(terraform fmt -write=false); if [ -n "$files" ]; then echo "$files"; echo -e "\nSome terraform files need be formatted, run 'terraform fmt' to fix"; exit 1; fi

terraform fmt 명령을 실행하고 수정이 필요한 파일이 있는 경우 리스트를 화면에 출력하고 exit 1 으로 에러(EXIT_FAILURE) 를 리턴하고 빌드를 종료한다.
fmt 명령은 기본적으로 recursive하게 실행되고 수정이 필요한 파일이 있으면 명령실행과 동시에 수정한다. 개발자가 해당 파일을 확인하고 직접 수정, commit(rebase)할 수 있도록 -write=false 옵션을 사용해서 파일 수정은 하지 않고 fmt 체크만 실행 한다.

tflint 설치

      - run:
          name: Install tflint
          command: curl -L -o /tmp/tflint.zip https://github.com/wata727/tflint/releases/download/v0.5.2/tflint_linux_amd64.zip && unzip /tmp/tflint.zip -d /usr/local/bin

tflint 를 GitHub repo에서 받아와 /usr/local/bin 아래에 설치한다.

tflint 실행

      - run:
          name: Check Terraform configurations with tflint
          command: tflint

tflint를 실행한다. 프로젝트 설명에 따르면, terraform plan 으로 검증할 수 없는 오류들을 tflint가 검증해 준다고 한다. ex) aws_instance.instance_type 이 잘못 입력된 경우
tflint/detector · wata727/tflint · GitHub 에서 terraform plan이 체크하지 못하는 오류를 탐지하는 소스를 확인할 수 있다.

validate job을 실행하는 workflow 정의

workflows:
  version: 2
  build:
    jobs:
      - validate

위에서 정의한 validate job을 실행한다.

이제 CircleCI 적용을 위해서 해당 config.yml 파일 을 git repo에 push 한다.

CircleCI 에 해당 프로젝트를 추가

.circleci/config.yml 파일을 repo에 push 완료했다면,
CircleCI에서 Projects -> Add Project 를 선택해 CI를 반영할 프로젝트를 추가한다.
나는 GitHub – asbubam/2dal-infrastructure 프로젝트를 반영할 예정이므로, 해당 프로젝트를 선택했다.

CircleCI에 프로젝트 추가

CircleCI Platform에 2.0을 선택하고 Start Building 버튼을 클릭한다.

Build에 성공하면

success 레이블 레이블을 확인할 수 있다!

Github PR을 통해 Build를 수행한 경우 다음과 같이 체크 결과를 화면에 출력해준다.
리뷰어는 안심하고 PR을 리뷰할 수 있다. 😉
CircleCI build success for GitHub

Build에 실패하면

failed 레이블 레이블과 함께 오류메시지를 통해서 다음과 같이 수정이 필요한 부분을 확인할 수 있다.

circleci build failed

위의 메시지는 terraform/common/dev_vpc/eip.tf 파일에 fmt 명령 실행이 필요함을 알려준다.

GitHub PR을 통해 Build를 수행할 경우 다음과 같이 체크 결과를 화면에 출력해준다.
CircleCI build failed for GitHub

정리

CircleCI 를 이용해서 Terraform validate, fmt, lint 등을 프로젝트에 변경사항이 있을 때마다 자동 실행하도록 적용해봤다. 이번에 적용한 커맨드 외에도 terraform plan 리포트를 화면에 출력한다거나, 필요에 따라 상황별로 Terraform 커맨드를 자동실행하도록 CI에 연동해 볼 수 있을 것 같다.

설명에 사용된 파일은 GitHub – asbubam/2dal-infrastructure에서 확인할 수 있다.

참고자료