Terraform CI/CD 구축 (1): GitHub PR과 Jenkins 연동
PR이 열리면 Jenkins가 머지 결과 기준으로 Terraform Plan을 실행하도록 Webhook과 Pipeline 트리거를 설정한 과정.
시리즈 · Infra2 / 3
- 접속 불가 상태의 LightSail 인스턴스에서 데이터 복구하기
- Terraform CI/CD 구축 (1): GitHub PR과 Jenkins 연동
- Terraform CI/CD 구축 (2): Jenkinsfile로 Plan 결과를 PR에 남기기
사내에 Terraform을 도입하면서 CI/CD를 구축했다. 마침 Jenkins도 함께 도입되었기 때문에 GitHub Actions 대신 Jenkins를 선택했다.
목표
만들고자 한 Flow는 다음과 같다.

- 개발자가 Terraform 코드를 수정해 GitHub에 Pull Request를 올린다.
- GitHub가 PR 이벤트를 Webhook으로 Jenkins에 전송한다.
- Jenkins는 main 브랜치에 머지될 결과를 기준으로 Terraform Plan을 실행한다.
- Terraform Plan 결과를 해당 PR에 댓글로 남긴다.
- 코드 리뷰 완료 후 main 브랜치로 Merge한다.
- 관리자가 Jenkins에서 수동으로 CD(Terraform Apply)를 진행한다.
State 관리: S3와 DynamoDB
terraform init을 하면 terraform.tfstate 파일이 생성된다. Terraform은 이 파일을 기준으로 코드가 변경되었을 때 클라우드 자원에 어떤 수정이 생길지 결정한다. 이 State를 S3와 DynamoDB 기반의 원격 백엔드로 관리했다.
- S3:
terraform.tfstate를 안전하게 보관하고 여러 사용자가 동일한 상태 파일을 공유한다. tfstate에는 sensitive로 선언한 변수의 값까지 모두 포함되기 때문에 Git으로 형상관리해서는 절대 안 된다. - DynamoDB: Lock을 통해 여러 사용자가 동시에 Terraform Apply하는 것을 방지한다.
# backend.tf
terraform {
backend "s3" {
bucket = "mh-tfstate"
key = "security_group/mh_d_apnortheast2/terraform.tfstate"
region = "ap-northeast-2"
encrypt = true
dynamodb_table = "terraform-lock-mh"
}
}
GitHub Webhook 설정
먼저 Jenkins가 PR 이벤트를 수신할 수 있도록 Webhook을 설정한다. 저장소의 Settings → Webhooks → Add webhook에서 다음과 같이 입력한다.
| 항목 | 값 |
|---|---|
| Payload URL | https://<jenkins-domain>/github-webhook/ |
| Content type | application/json |
| Events | Let me select individual events → Pull requests |

이렇게 하면 Pull Request가 열리거나 닫힐 때마다 Webhook이 실행된다. 실행 내역은 Webhook을 클릭한 뒤 상단의 Recent Deliveries에서 확인할 수 있다.
Webhook을 수신하려면 Jenkins 쪽에서 다음 GitHub IP 대역의 443 포트 인바운드를 허용해야 한다.
192.30.252.0/22
185.199.108.0/22
140.82.112.0/20
143.55.64.0/20
Jenkins Pipeline 설정
플러그인 설치
- GitHub Integration Plugin: PR 감지
- Pipeline GitHub Notify Step Plugin: PR의 checks 상태 변경
Pipeline 생성
New Item → Pipeline을 선택하고 Configure에서 다음을 설정한다.
General
- GitHub project 체크, Project url:
https://github.com/Test/Terraform/
Triggers
- GitHub Pull Requests 선택 (GitHub Integration 플러그인 필요)
- Trigger Mode: Hooks with Persisted Data
- Crontab line: 비워 둔다. 경고가 떠도 Webhook으로 수신할 것이므로 상관없다.
- Set status before build: 빌드 시작 전에 PR 상태를 Pending으로 만들어 준다.
- Trigger Events: Pull Request Opened
Pending 상태가 된 PR은 다음과 같이 보인다.

Pipeline
- Definition: Pipeline script from SCM (저장소 안에서 Jenkinsfile을 찾는다)
- SCM: Git
- Repository URL:
https://github.com/Test/Terraform.git - Credentials: 해당 저장소에 권한이 있는 Credentials 선택
- Script Path: Jenkinsfile의 경로. 나는
scripts/jenkinsfile에 두었다.
어느 브랜치의 코드로 Plan을 실행할 것인가
test → main으로 PR을 올렸다고 가정해 보자. 어느 브랜치의 코드로 Terraform Plan을 보여줘야 할까? test일까, main일까?
정답은 main에 test가 머지된 결과다. GitHub는 PR이 생성되면 머지 결과를 가리키는 가상의 ref(refs/pull/<PR-Num>/merge)를 만들어 준다. 이 ref를 체크아웃하도록 Repositories의 고급 설정에서 Refspec과 Branch Specifier를 지정한다.

refs/pull/<PR-Num>/merge를 로컬 저장소의 origin/pr/<PR-Num>이라는 이름으로 가져오고, 그 브랜치를 빌드 대상으로 삼는 설정이다.
설정 요약
General
- GitHub project (체크)
- Project url : https://github.com/Test/Terraform/
Triggers
- GitHub Pull Requests
- Trigger Mode : Hooks with Persisted Data
- Set status before build (체크)
- Trigger Events : Pull Request Opened
Pipeline
- Definition : Pipeline script from SCM
- SCM : Git
- Repositories
- Repository URL : https://github.com/Test/Terraform.git
- Credentials : (Select)
- 고급
- Refspec : +refs/pull/${GITHUB_PR_NUMBER}/merge:refs/remotes/origin/pr/${GITHUB_PR_NUMBER}
- Branches to build : origin/pr/${GITHUB_PR_NUMBER}
- Script Path : scripts/jenkinsfile
여기까지가 Jenkins UI에서 하는 설정이다. 다음 글에서는 Jenkinsfile을 작성해 실제 Pipeline을 만든다.