본문 바로가기
Terraform/atlantis

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

by isn`t 2022. 11. 11.

* 이 글은 CloudNet@ Terraform Study(T101)을 진행하며 학습한 내용을 바탕으로 작성하였습니다.

Intro

업무 환경에 테라폼을 사용하면 할수록 늘 새로운 의문점들이 생겨나곤 했습니다. 현재로서도 강력한 기능을 제공하지만 아직 빠르게 발전하고 있는 오픈소스 IaC 도구인 만큼, 다양한 문제 상황을 해결하는 과정에서 '이게 정답이다!'라고 할 수 없으며 커뮤니티를 통해 다양한 사용 경험을 공유하고 나름대로의 방법으로 발전시켜 나가야 한다는 생각이 듭니다.

테라폼은 IaC(Infrastructure as Code) 도구입니다. 동작의 결과물이 인프라스트럭쳐일 뿐, 결국 테라폼 자체는 '코드'인 것인데, 그동안 테라폼을 '코드'로서 대하지 못했다는 생각이 들었습니다. 몇 가지 이유를 꼽아보자면..

  • 구성원들 사이에 코드 리뷰가 전혀 이루어지지 않았음.
  • 업무 특성상 인프라 구성을 혼자서 하는 경우가 많았고, 중간에 새로 합류한 인원이 이미 구성된 테라폼 코드를 볼 일이 없음.
  • 초기 환경만 구성하고 이후에는 AWS 콘솔로 수작업 하는 경우도 많았음. 내 손으로 꾸준히 관리할 것이 아니기 때문에 코드로서 인프라를 어떻게 지속적으로 관리할 것인지 고민할 필요가 없었음.
  • 자연히 변경 이력에 대한 관리도 하지 않았음.

기존 근무하던 포지션의 업무는 AWS 환경의 인프라를 구성해서 고객사에 넘겨주는 것으로 그 역할을 다 했기 때문에 위와 같은 고민을 할 필요가 없었습니다. 그런데 최근에 직접 서비스를 운영하는 조직에서 근무하게 되면서 Atlantis를 사용할 기회가 생겼고, 다수의 구성원이 테라폼 코드를 관리함에 있어 많은 이점을 얻을 수 있다고 느껴 이번 글을 통해 그 사용방법을 소개해보고자 합니다.

Atlantis?

Atlantis는 Pull Request를 통해 Terraform 워크플로우를 자동화할 수 있는 오픈소스 셀프호스팅 도구입니다. 새로운 PR이 발생하면 구성원은 서로의 테라폼 코드를 리뷰하고, PR을 승인하고, 테라폼을 plan, apply 할 수 있습니다.

모든 구성원이 자신의 로컬 환경에서 테라폼을 사용하는 것은 그다지 바람직하지 않다고 생각합니다. 특히 조직의 규모가 커지면 커질수록 그러합니다. 가령 AWS 환경을 테라폼으로 관리하려면 각 작업자에게 AWS 크레덴셜이 필요하다보니 유출 위험도 높고, 테라폼으로 프로비저닝 되고 있는 진행 상태를 함께 공유하며 코드를 리뷰하는 과정을 공유하기도 번거롭습니다. 행여나 상태가 꼬이고 이력 추적이 어려운 상황이라도 발생한다면 정말 난감할 테고요.

Atlantis를 사용하면 이렇게 점차 파편화되고 통합이 어려워 질 수 있는 상황을 워크플로우의 표준화를 통해 해결할 수 있는 도구라고 생각합니다.

Atlantis에 대한 보다 자세한 내용은 공식 웹페이지를 참조하시기를 바랍니다.

Atlantis 구성


Atlantis는 다양한 플랫폼에서 구성하여 동작시킬 수 있습니다. 이 글에서는 간단하게 Atlantis를 위한 별도의 가상머신(EC2)을 사용하겠습니다. 다른 플랫폼을 통한 구성 방법도 링크에 소개되어 있으니 참고하시기 바랍니다.

Atlantis HOL Steps

  1. Github Token 생성
  2. Github Webhook 생성
  3. Atlantis용 EC2 생성
  4. feature branch 생성
  5. Pull Requests
  6. Atlantis 테스트

Step 1. Gihub token 생성


repo 권한을 가진 깃허브 토큰을 생성하고 repo 권한을 부여합니다. Github이 아닌 다른 원격 버전 관리 플랫폼을 사용하는 경우 링크를 확인해주세요.

Step 2. Atlantis용 EC2 생성


제 Github에 Atlantis용 AWS 초기 환경을 구축할 수 있는 Terraform 코드를 업로드 해 두었습니다. 아래 내용을 수정해서 사용하실 수 있습니다.

# variables.tf
...

variable "profile" {
  type = string
  default = "<AWS profile>"
}



variable "key_pair" {
  type = string
  default = "<EC2 pem>"

}

variable "nickname" {
  type = string
  default = "<Nickname>"
}

...

  • AWS Pofile : 테라폼이 AWS 인프라를 프로비저닝하는데 사용할 AWS profile 입니다. ~/.aws/credential 파일을 열어보면 빨간색 상자로 표시한 부분에서 확인 가능합니다. 크레덴셜 입력시 별도로 지정하지 않는 경우 [default]로 설정됩니다.
  • EC2 pem : 사전에 AWS 콘솔에서 생성되어있어야 합니다. EC2에서 사용할 Pem key 이름입니다.
  • Nickname : AWS profile과 동일한 값을 입력하시면 됩니다. 본인의 Nickname을 입력해도 좋습니다. 제가 사용한 예제에서는 AWS 리소스에 태깅하는데 사용하였습니다.

위 설정은 로컬에서 테라폼을 이용하여 Atlantis EC2를 생성하는 데 사용됩니다.

#/script/userdata.sh

#!/bin/bash
mkdir /root/.aws
cat << EOF >> ~/.aws/credentials
[AWS Profile]
aws_access_key_id = <AWS ACCESS KEY>
aws_secret_access_key = <AWS SECRET ACCESS KEY>
EOF

yum install -y yum-utils
yum-config-manager --add-repo https://rpm.releases.hashicorp.com/AmazonLinux/hashicorp.repo
yum -y install terraform
yum -y install git
wget https://github.com/runatlantis/atlantis/releases/download/v0.20.1/atlantis_linux_amd64.zip -P /root
unzip /root/atlantis_linux_amd64.zip -d /root

/root/atlantis server \
--atlantis-url="<GITHUB REPOSITORY URL>" \
--gh-user="<GITHUB USER NAME>" \
--gh-token="<GITHUB TOKEN>" \
--gh-webhook-secret="<GITHUB WEBHOOK SECRET>" \
--repo-allowlist="github.com/<GITHUB USERNAME>/<GITHUB RESPOSITORY NAME>"
  • aws_access_key_id : Atlantis가 테라폼을 사용하여 인프라를 프로비저닝 하기 위한 Access Key 를 입력합니다.
  • aws_secret_access_key : Atlantis가 테라폼을 사용하여 인프라를 프로비저닝 하기 위한 Secret Access Key를 입력합니다.
  • GITHUB REPOSITORY URL : Atlantis 를 이용해서 배포하고자 하는 테라폼 코드의 github 레포지토리 주소
  • GITHUB USER NAME : 본인의 GITHUB 유저네임
  • GITHUB TOKEN : 앞에서 생성한 Github token
  • GITHUB WEBHOOK SECRET : 웹훅 생성시 입력한 secret context
  • GITHUB REPOSITORY : Atlantis 를 이용해서 배포하고자 하는 테라폼 코드의 github 레포지토리 이름. 유저 소유의 모든 레포지토리를 대상으로 하는 경우 * 로 표기할 수 있습니다.

Atlantis EC2의 useradata의 역할입니다. 값을 입력하시고 terraform apply 하시면 다음과 같이 Atlantis가 동작하기 위한 사전 작업을 마친 상태로 EC2가 프로비저닝 됩니다.

일반 EC2에서 userdata.sh를 복사하여 그대로 실행하셔도 결과는 같습니다.

Atlantis Requirements

  • 테라폼 설치
  • AWS Credential 설정
  • git 설치
  • Atlantis 바이너리 다운로드
  • Atlantis 실행

apply가 성공하면 아래와 같이 public ip를 출력합니다.

Step 3. Github Webhook 생성


다른 원격 버전 관리 도구를 이용하는 경우 공식 가이드 링크를 참조 바랍니다.

배포 대상인 Github repository의 Setiings -> Webhooks 탭으로 이동합니다.

Add webhook에서 웹훅을 생성합니다. 각 필드별로 입력할 값은 아래와 같습니다.

  • Payload URL : http://:4141/events 의 포맷으로 입력합니다. Public ip는 step 2에서 terraform apply의 결과로 확인 가능합니다.
  • Content type : application/json
  • Secret : 임의의 문자열을 입력합니다. 웹훅을 처리할 때 비밀번호와 같은 역할을 합니다.
  • Which events would you like to trigger this webhook? : Let me select individual events를 선택하고 아래 값을 체크합니다.
    • Pull request reviews
    • Pushes
    • Issue comments
    • Pull requests

아래 Active webhook 버튼을 클릭해서 웹훅을 생성합니다.

Payload URL의 프로토콜은 https를 사용하거나, atlantis hosted sever에 도메인을 설정하는 등 환경에 따라 변경될 수 있습니다. 실제 프로덕션 환경에 사용하시는 경우 도메인을 매핑하거나 IP를 고정하여 고정된 엔드포인트로 접근할 수 있도록 세팅하는 것을 추천합니다.

4141포트는 Atlantis가 동작하는 포트입니다. EC2의 보안그룹에서도 이 포트는 열려있어야 합니다.

Step 4. feature branch 생성


Atlantis로 배포하고자 하는 테라폼 코드가 있는 레포지토리를 로컬로 clone하고, 브랜치를 생성합니다.

브랜치에서 변경내용을 커밋하고 Push한 뒤, Github에서 PR 생성이 가능합니다.

isnt@DESKTOP-41KS4N1:~/Terraform_study$ git branch atlantistest

isnt@DESKTOP-41KS4N1:~/Terraform_study$ git checkout atlantistest 
Switched to branch 'atlantistest'

isnt@DESKTOP-41KS4N1:~/Terraform_study$ git branch
* atlantistest
  main

isnt@DESKTOP-41KS4N1:~/Terraform_study/week2$ git push origin atlantistest
Total 0 (delta 0), reused 0 (delta 0)
remote: 
remote: Create a pull request for 'atlantistest' on GitHub by visiting:
remote:      https://github.com/TAEKnical/Terraform_study/pull/new/atlantistest
remote: 
To https://github.com/TAEKnical/Terraform_study.git
 * [new branch]      atlantistest -> atlantistest

Step 5. Pull Request


feature 브랜치를 Push하고 나면 Github에서 Pull Request를 생성할 수 있습니다.

코드를 리뷰하는 팀원이 명확하게 파악할 수 있도록 PR의 목적과 수정내용을 적고 PR을 생성합니다.

Merge는 해당 브랜치의 작업이 모두 마무리되기 전까지 진행하지 않습니다. 지금부터는 이 화면에서 atlantis를 동작시켜 테라폼이 인프라를 프로비저닝 하도록 하고, 모든 내용을 팀원이 공유하고 리뷰할 수 있습니다.

atlantis 명령은 Comment를 생성하면 동작합니다. 예시로 atlantis help 를 출력시켜보겠습니다.

위와 같이 커맨드를 입력하고 Comment 버튼을 눌러 등록합니다.

위와 같이 atlantis 명령이 동작함을 확인할 수 있습니다.

Atlantis와 정상적으로 통신이 이루어졌다면 Webhook 탭에서 녹색 체크 표시가 생겨납니다.

Step 6. Atlantis 테스트

테라폼 스터디에서 진행했던 2주차 과제 내용을 Atlantis로 배포해보겠습니다. 다른 예제로도 진행이 가능하지만, Atlantis는 Local state를 지원하지 않습니다. S3든, Terraform Cloud를 사용하든, 반드시 Remote Backend를 먼저 설정해두고 사용해야 합니다.

먼저 atlantis plan 명령을 실행하면 terraform plan의 결과가 코멘트로 생성됩니다.
-d week2 옵션은 해당 레포지토리의 week2 디렉토리를 지칭합니다.

plan으로 생성되는 리소스가 의도한 바와 일치하는지 확인한 후, atlantis apply를 입력합니다.

Output으로 ALB의 public ip를 출력하도록 했는데, 실제로 접속 가능한지 확인해보면

isnt@DESKTOP-41KS4N1:~/test2/Terraform_study/week2$ curl web-alb-1991957376.ap-northeast-2.elb.amazonaws.com
Isnt Jintae.

위와 같이 정상적으로 프로비저닝되어 접속 가능함을 확인할 수 있습니다.

Atlantis의 동작 이력은 코멘트 맨 아래에 지속적으로 업데이트됩니다.

리소스 제거


다소 불편하지만, Atlantis는 Destroy 명령을 atlantis destroy 와 같이 제공하지 않습니다. 때문에 다음의 순서로 리소스를 제거해야 합니다. 이에 대한 이슈는 Github issue에서도 여러 차례 논의되고 있는 상태입니다.

방법 1


Atlantis는 원격 백엔드만을 지원하고, Local state를 지원하지 않기 때문에 테라폼 코드만 가지고 있다면 작업 디렉토리에서 terraform destroy 커맨드를 실행하면 HOL에 사용한 리소스를 destroy 할 수 있습니다.

방법 2


Step 1. PR Close

Plan / Apply 하던 PR을 Close 합니다.

Step 2. destroyAtlantis 브랜치 생성

로컬에서 새로운 브랜치(destroyAtlantis)를 생성하고, provider와 backend, variable 파일 외 리소스와 관련된 tf파일은 모두 제거합니다.


isnt@DESKTOP-41KS4N1:~/test2/Terraform_study/week2$ ls
backend  backend.tf  provider.tf  script  variables.tㄹ

Step 3. 새로운 PR 생성

위와 같은 상태에서 새로운 브랜치를 push하고, Pull Request를 생성합니다.

만약 PR 알림이 보이지 않는다면 직접 생성할 수도 있습니다.

Step 4. 리소스 제거

이렇게 destroyAtlantis 브랜치에 생성된 PR에서 atlantis plan을 실행해보면 모든 리소스가 제거될 것임을 확인할 수 있습니다.

이제 atlantis apply를 실행하고 AWS 콘솔에서 실제로 리소스가 제거되었는지 확인해봅니다.

Trouble Shooting

Atlantis가 공식적으로 destroy 커맨드를 지원하지 않다 보니 간혹 위와 같은 방법으로 리소스를 정리하면 Pending 상태에서 멈추는 경우가 많습니다. 때문에 state파일을 원격 백엔드에 저장해두고, 첫 번째 방법으로 수행하는 편을 개인적으로는 선호합니다.

isnt@DESKTOP-41KS4N1:~/Terraform_study/week2$ terraform destroy --auto-approve
╷
│ Error: Error acquiring the state lock
│ 
│ Error message: ConditionalCheckFailedException: The conditional request failed
│ Lock Info:
│   ID:        1e234f89-ccc5-e1ac-1d00-20dce97f5436
│   Path:      kyle-t101study-tfstate/terraform-backend/terraform.tfstate
│   Operation: OperationTypeApply
│   Who:       root@ip-10-50-0-152.ap-northeast-2.compute.internal
│   Version:   1.3.4
│   Created:   2022-11-11 06:36:17.098000663 +0000 UTC
│   Info:      
│ 
│ 
│ Terraform acquires a state lock to protect the state from being written
│ by multiple users at the same time. Please resolve the issue above and try
│ again. For most commands, you can disable locking with the "-lock=false"
│ flag, but this is not recommended.

만약 위와 같이 terraform destroy 시에 atlantis에 의한 lock이 걸려서 동작하지 않는 경우, lock 옵션을 끄고 아래와 같이 수행합니다.

terraform destroy --auto-approve -lock=false

Atlantis의 부족한 점?


고가용성의 확보

Atlantis도 결국 Self hosted로 사용하기 때문에 고가용성을 확보하는 편이 좋겠으나, 아직은 어려움이 있습니다. Atlantis의 백 단에서는 결국 내부적으로 Terraform을 그대로 실행하는 것임에도 불구하고, 이러한 이유로 현재로서는 Local state를 지원하지 않는 것으로 보입니다. Remote Backend를 사용하면 Atlantis의 서비스 또는 서버가 죽어서 다시 구축하더라도, 상태를 그대로 가져와서 이어 사용할 수 있기 때문에 크게 문제되는 부분은 아니라고 생각합니다.

대기열 지원

Terraform 기반이니 당연하지만 Atlantis도 Locking을 지원합니다. 그 범위가 조금 다른데요, Github에서 생성한 Pull Request가 Merge/Close 될 때 까지 해당 Terraform Workspace 에서의 Plan/Apply 는 Locking 됩니다. 다른 사용자가 Plan/Apply를 진행중인 경우 중첩되지 않도록 하깅 위해서입니다.

Credential은 어디로?

AWS의 경우 테라폼이 동작하기 위해서는 로컬에 AWS Credential이 필요합니다. 환경변수로 주입하거나, 또는 Atlantis가 실행중인 EC2에 Instance Role을 부여할 수도 있겠지요. 어쨌든 크레덴셜을 필요로 하기 때문에 이러한 시크릿을 어떻게 관리할지에 대해서는 조직 내에서 논의가 필요할 것으로 보입니다.

이 글에서 진행한 실습 예제에서는 간단하게 Atlantis를 호스팅하는 EC2에 정적으로 AWS Credential을 넣어 사용했지만, 프로덕션 환경에 적용하고자 한다면 서버에 크레덴셜을 배치하는 것은 권장되는 방법이 아니기 때문이죠.'

 


 

이번 글에서는 Atlantis를 구성하고 어떻게 Terraform을 연계하여 사용할 수 있는지 살펴보았습니다. 실제 운영환경에서는 Github 레포지토리 루트 경로에 atlatnis.yaml을 배치하여 Atlantis의 동작 기준을 명시할 수 있는데요.

다음 글에서는 atlantis.yaml 을 사용하여 멀티 어카운트와 PR Approve, 테라폼 버전 등을 명시하여 Atlantis를 사용할 수 있도록 하는 내용을 다뤄보겠습니다.