본문 바로가기
Terraform/atlantis

Github에서 Atlantis를 이용한 Terraform 협업 환경 구축하기(2)

by isn`t 2022. 12. 11.

Intro


Github에서 Atlantis를 이용한 Terraform 협업 환경 구축하기 1

지난 글에서는 Atlantis를 소개하고 PR 기반의 협업 환경을 구축하는 과정을 데모와 함께 소개했습니다. 이번 글에서는 Atlantis를 실제로 사용하며 보다 활용도를 높일 수 있는 내용을 몇 가지 공유해보려고 합니다.

워크플로 표준화


terraform
├── modules
├── script
    ├──init_service
└── services
    └── microservice1
        ├── dev
            ├── main.tf
        ├── staging
        └── prod
    ├──microservice2
    ├──microservice3

...

(예시입니다)

테라폼을 이용한 관리 범위를 어디까지 가져갈 것인지에 따라 달라지겠지만, 저희 팀의 경우 AWS ECS 를 기반으로 서비스가 올라갑니다. 새로운 마이크로서비스를 생성하기 위해서는 Taskdefinition과 ECS Service가 정의되어야 하며, 특히 Taskdefinition 는 제각기 다른 값을 설정해야 하므로 아무런 가이드 없이 여러 사람의 손을 거치게 되면 시간이 지날 수록 관리가 굉장히 어려워질 수 있습니다.

때문에 각 microservice를 생성하기 위한 terraform 코드를 모듈을 이용하여 템플릿화 해두고, 새로운 마이크로서비스를 생성하기 위해 별도의 스크립트를 실행하면 새로운 하위 디렉토리에 템플릿을 구성해주는 방식으로 활용할 수 있습니다.

쿠버네티스 생태계에 비교하자면 helm chart를 활용하여 각 마이크로서비스의 명세를 템플릿화 하는 것과 같은 맥락이지만, ECS 환경, 특히 Fargate를 이용하는 경우에는 더더욱 이러한 외부 도구의 도움을 기대할 수 없기 때문에 이러한 방식을 적용하였습니다.

위와 같이 템플릿과 모듈을 이용하여 표준화된 환경에서는 테라폼 코드를 잘 다루지 못하는 개발조직에서도 ECS에 새로운 서비스를 추가하기 위해 테라폼 코드를 깊게 들여다 볼 필요가 없습니다. init 스크립트를 실행하여 템플릿을 찍어내고, 변경하고자 하는 value만 수정한 뒤 적욯할 수 있기 때문입니다.

atlantis.yaml


왜 필요한가?

ECS의 service 단위로 하위 디렉토리를 관리하게 되면 시간이 지날수록 마이크로서비스가 늘어나고 관리할 디렉토리가 많아지게 됩니다. 여기서 문제가 될 수 있는 점은, 오래 전에 생성한 서비스는 예전 테라폼 버전을 사용하고 있고, 시간이 지나면서 테라폼 파일은 점차 업데이트 되면서 버전에 대한 의존성이 생기게 된다는 점입니다.

물론 모든 테라폼 파일에서 최신 버전을 사용하고, deprecated 된 기능들과 새로 추가된 기능들을 꾸준히 업데이트해주면 좋겠지만 실제로 수백 수천 개에 달하는 각 서비스들을 이러한 방식으로 관리하는 것은 현실적으로 어렵습니다.

What is atlantis.yaml?

atlantis.yaml은 repository 레벨에서 동작하며, atlantis가 각 프로젝트 별로 "어떻게 동작할 것인가"를 정의하는 파일입니다. 레포지토리에서 많은 테라폼 프로젝트가 관리되고 있는 경우에 유용합니다.

github의 데모에서 atlantis.yaml 파일의 예시를 확인하실 수 있습니다.

주요 기능

  • auto plan의 비활성화
  • 프로젝트의 자동 구성
  • 병렬 실행
  • 하위 경로의 프로젝트별 버전/속성 관리

이외에도 다양한 기능을 제공하며 각각의 옵션으로 세밀한 동작 제어가 가능합니다. 옵션별, 환경별로 자세한 설명은 링크를 참조 바랍니다.

version: 3
automerge: true
delete_source_branch_on_merge: true
parallel_plan: true
parallel_apply: true
projects:
- name: my-project-name
  branch: /main/
  dir: .
  workspace: default
  terraform_version: v0.11.0
  delete_source_branch_on_merge: true
  repo_locking: true
  autoplan:
    when_modified: ["*.tf", "../modules/**/*.tf"]
    enabled: true
  apply_requirements: [mergeable, approved]
  workflow: myworkflow
workflows:
  myworkflow:
    plan:
      steps:
      - run: my-custom-command arg1 arg2
      - init
      - plan:
          extra_args: ["-lock", "false"]
      - run: my-custom-command arg1 arg2
    apply:
      steps:
      - run: echo hi
      - apply
allowed_regexp_prefixes:
- dev/
- staging/

주의사항

  • 반드시 repository의 루트 경로에 atlantis.yaml이 위치해야 합니다.
  • 오직 atlantis.yaml 이라는 파일 명만 가질 수 있습니다. atlantis.yml, .atlantis.yaml 등은 인식하지 않습니다.
  • workflow를 정의할 때 시크릿이 노출되지 않도록 작성해야 합니다.

atlantis locking


atlantis는 pull request 기반으로 동작합니다. github에서 PR을 생성하며 작업하다 보면 여러 개의 브랜치가 생겨나게 됩니다. 특히 여러 작업자가 동시에 여러 브랜치에서 작업하게 되면 필연적으로 conflict가 발생하게 되는데요, 인프라 레벨의 코드인 terraform 코드에서 동시 작업으로 인하여 브랜치간 sync가 깨지고 동일한 인프라에 대하여 여러 브랜치에서 각기 다른 변경 사항이 발생한다면 이는 치명적인 장애 포인트가 될 것입니다.

atlantis는 이러한 충돌 상황을 방지하기 위하여 Locking을 제공합니다. A 브랜치에서 특정 리소스에 대한 terraform 코드가 업데이트되고 plan이 이루어졌다면, B 브랜치에서 해당 리소스에 대한 plan-apply를 할 수 없도록 lock을 건 후 안내 메세지를 출력합니다.

잠금을 해제하는 방법은 간단합니다. 우선권을 가지고 있는 A 브랜치에서 atlantis unlock을 comment로 추가한 후, B 브랜치에서 다시 한 번 atlantis plan을 실행하면 됩니다.

atlantis에서 제공하는 lock 기능은 기능적으로 보았을 때 terraform state lock과 유사하나, 실제로는 terraform state lock보다 상위 레이어에서, 별개로 동작하는 기능입니다. atlantis lock이 동작함으로써 terraform state lock에 직접적인 영향을 끼치지 않습니다.

주의사항

  • unlock을 실행하기 전에 항상 히스토리에 대하여 명확히 파악할 필요가 있으며, 작업 영역이 겹치게 된 다른 작업자에게도 사실을 공유해야 합니다.
  • 작업이 마무리된 후 필요에 따라 브랜치를 merge하거나, 추가 작업이 남아있다면 lock을 건 후 기존 브랜치를 unlock해야 합니다.

multi account


atlantis에서 별도로 multi account를 관리하는 기능을 추상화하여 편리하게 제공하고 있는 것은 아닙니다. 다만 mutli account를 이용한 관리의 필요성이 있을 경우 terraform 코드 내에서 기존의 IAM Role을 assume하여 구현할 수 있습니다.

provider "aws" {
    region = "ap-northeast-2"
    version = "~> 4.0"

assume_role {
    role_arn = var.assume_role_arn
    session_name = var.atlantis_iam_user
    }
}

개인적으로는 이러한 경우 Atlantis 동작을 위한 환경과 계정이 분리되는 것이 바람직할 것으로 생각합니다. 예를 들면 다음과 같은 구조가 될 수 있습니다.

중앙의 계정에서 각 계정으로 assume role을 할 수 있도록 구성하고, atlantis는 구축에 필요한 권한(ex.Administrator) 을 assume 받아 각 환경에 terraform으로 AWS 리소스를 배포합니다. 물론 위의 구조는 작성자의 개인적인 의견으로, 다양한 구조로 구성될 수 있습니다.

github 데모에서 atlantis를 이용하여 위의 구조와 같은 multi account 구성에 대한 샘플을 업데이트 중에 있습니다.

결론


EKS와 ECS 기반에서 동작하는 서비스를 모두 다루어보면서 든 생각은, atlantis의 활용도 및 효율이 서비스가 동작하는 플랫폼에 따라서도 천차만별일 수 있다는 점입니다. 도입부에서도 언급하였지만, AWS EKS 등의 쿠버네티스 기반의 플랫폼에서 서비스가 운영중인 경우 kubernetes eco system에서 지원되는 폭넓고 검증된 오픈소스를 이용하여 굳이 atlantis가 애플리케이션 레벨의 배포까지 개입하지 않더라도 워크플로의 자동화와 표준화를 이루어 낼 수 있습니다.

그러나 ECS는 이러한 대안을 적용함에 있어 한계가 명확합니다. docker를 기반으로 동작하는 관리형 서비스이기는 하나, AWS에서 한 번 패키징하여 출시한 서비스인데다가 특히나 Fargate의 경우는 쿠버네티스 생태계만큼 자유롭고 폭 넓은 선택지가 있는 생태계를 누리기 어렵습니다. 때문에 새로운 마이크로서비스가 올라가기 위한 과정에서도 terraform이 개입을 피할 수 없으며, 이러한 빈도가 잦은 만큼 atlantis를 이용한 자동화된 워크플로우는 애플리케이션이 동작할 기반환경 배포 단계에서 큰 효율을 낼 수 있다고 생각합니다.

쿠버네티스 환경에서 helm과 argoCD를 이용하여 이루어 낼 수 있는 표준화/자동화를 전부는 아니지만 일부분 terraform의 영역에서 도와줄 수 있는 것이 atlantis이며, 세밀한 동작을 애플리케이션 별로 명세할 수 있는 파일이 바로 atlantis.yaml 입니다.