GitHub Actions와 Slack으로 지속적인 Terraform 드리프트 모니터링 구축
Source: Dev.to
대부분의 팀은 Terraform 드리프트를 힘들게 발견합니다 — 배포 전에 누군가 terraform plan을 실행했을 때 예상치 못한 변경 사항이 화면에 가득 뜨는 경우죠. 그때쯤이면 드리프트가 몇 주, 혹은 그보다 더 오래 존재했을 수도 있습니다.
자동으로 잡을 수 있다면 어떨까요? 몇 시간마다 스캔을 실행하고, 중요한 드리프트가 발생했을 때만 Slack 메시지를 받아보며, 잡음은 무시하는 방식 말입니다.
이 튜토리얼이 바로 그 설정을 해줍니다. 최종적으로 다음을 갖게 됩니다:
- 일정에 따라 Terraform 인프라를 스캔하는 GitHub Actions 워크플로우
- High와 Critical 심각도 드리프트에만 알림을 보내는 Slack 알림
- 감사 로그용으로 저장되는 JSON 보고서 (artifact)
- 전부 자동으로 실행되며, 유지보수가 필요 없는 상태
저는 이 작업에 tfdrift를 사용합니다 — 제가 만든 오픈소스 CLI로, 드리프트를 심각도별로 분류합니다. 하지만 GitHub Actions + Slack 패턴은 어떤 드리프트 탐지 방법과도 함께 사용할 수 있습니다.
시작하기 전에 준비해야 할 것:
- Terraform 코드가 들어 있는 GitHub 레포지토리
- AWS 자격증명 (또는 사용 중인 다른 클라우드 제공자)
- 웹훅을 만들 수 있는 Slack 워크스페이스
- Python 3.9 이상 (tfdrift는 Python CLI입니다)
- 약 20분 정도의 시간
1. 알림이 갈 위치 설정하기
api.slack.com/apps로 이동- Create New App → From scratch 클릭
- “Drift Alerts” 같은 이름을 지정하고 워크스페이스 선택
- 사이드바에서 Incoming Webhooks 로 이동 → 토글을 On 으로 전환
- Add New Webhook to Workspace 클릭
- 알림을 받을 채널 선택 (예:
#infra-alerts) - 웹훅 URL 복사 —
https://hooks.slack.com/services/T00000/B00000/XXXX와 같은 형태
이 URL을 저장해 둡니다. 곧 필요합니다.
2. 비밀값(Secrets) 저장하기
클라우드 자격증명과 Slack 웹훅을 워크플로우 파일에 노출되지 않게 GitHub Secrets에 저장합니다.
- 레포지토리 → Settings → Secrets and variables → Actions → New repository secret
- 다음 비밀값을 추가:
| 이름 | 값 |
|---|---|
AWS_ACCESS_KEY_ID | Your AWS access key |
AWS_SECRET_ACCESS_KEY | Your AWS secret key |
AWS_DEFAULT_REGION | us-east-1 (또는 사용 중인 리전) |
SLACK_WEBHOOK_URL | 1단계에서 복사한 웹훅 URL |
AWS SSO나 역할 전환을 사용하는 경우 AWS_ROLE_ARN도 필요할 수 있지만, 기본 접근키 방식으로 시작하기에 충분합니다.
3. GitHub Actions 워크플로우 파일 만들기
레포지토리 루트에 .github/workflows/drift-check.yml 파일을 추가합니다.
name: Terraform Drift Check
on:
schedule:
# 매 6시간마다 실행
- cron: '0 */6 * * *'
workflow_dispatch:
# Actions 탭에서 수동 트리거 허용
jobs:
drift-check:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.7.0
terraform_wrapper: false
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install tfdrift
run: pip install tfdrift
- name: Run drift scan
id: drift_scan
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}
run: |
tfdrift scan \
--path ./infrastructure \
--format json \
--output drift-report.json \
--slack-webhook ${{ secrets.SLACK_WEBHOOK_URL }} \
--quiet
continue-on-error: true
- name: Upload drift report
if: always()
uses: actions/upload-artifact@v4
with:
name: drift-report-${{ github.run_number }}
path: drift-report.json
retention-days: 30
- name: Fail on drift
if: steps.drift_scan.outcome == 'failure'
run: |
echo "::warning::Terraform drift detected! Check the drift report artifact."
exit 1
각 파트 설명
- schedule trigger:
cron: '0 */6 * * *'은 UTC 기준 매 6시간(0시, 6시, 12시, 18시)에 실행됩니다. 필요에 따라 조정하세요.- 매시간:
'0 * * * *' - 매일 자정:
'0 0 * * *'
- 매시간:
- workflow_dispatch: Actions 탭에서 수동으로 실행할 수 있게 해줍니다. 테스트나 즉시 확인이 필요할 때 유용합니다.
- terraform_wrapper: false:
hashicorp/setup-terraform액션은 기본적으로 Terraform 바이너리를 래핑하는데, 이는 JSON 파싱을 방해합니다. false 로 설정하면 원시 바이너리를 사용합니다. - continue-on-error: true:
tfdrift가 드리프트를 발견하면 exit code 1을 반환합니다. 이 옵션을 주면 워크플로우가 중단되지 않고, 이후 단계(artifact 업로드 등)를 수행할 수 있습니다. - —quiet: CI 환경에서는 터미널 출력이 필요 없으므로 억제합니다. 모든 내용은 JSON 보고서에 담깁니다.
- Artifact upload:
if: always()로 설정해 드리프트 여부와 관계없이 JSON 보고서를 저장합니다. 이렇게 하면 지난 30일간 언제든 인프라 상태를 확인할 수 있습니다.
4. 잡음 필터링을 위한 tfdrift 설정
현재 워크플로우는 모든 드리프트를 스캔하고 알림을 보냅니다. 잡음을 줄이려면 레포지토리 루트에 .tfdrift.yml 파일을 추가합니다.
# .tfdrift.yml
scan:
paths:
- ./infrastructure
exclude:
- "**/.terraform/**"
- "**/test/**"
- "**/modules/**"
notifications:
slack:
webhook_url: ${SLACK_WEBHOOK_URL}
channel: "#infra-alerts"
min_severity: high # High와 Critical만 알림
severity:
critical:
- aws_security_group.*.ingress
- aws_security_group.*.egress
- aws_iam_policy.*.policy
- aws_iam_role.*.assume_role_policy
- aws_s3_bucket_public_access_block.*
high:
- aws_instance.*.instance_type
- aws_rds_instance.*.publicly_accessible
- aws_rds_instance.*.storage_encrypted
핵심 설정은 min_severity: high 입니다. 의미는 다음과 같습니다.
- Critical 드리프트 (보안 그룹, IAM 정책 등) → 즉시 Slack 알림
- High 드리프트 (인스턴스 타입, 암호화 등) → 즉시 Slack 알림
- Medium 드리프트 → JSON 보고서에 기록되지만 알림 없음
- Low 드리프트 → JSON 보고서에 기록되지만 알림 없음
이 설정 덕분에 알림량을 100%에서 27%로 줄이면서도 실제 중요한 94%의 변경을 놓치지 않았습니다.
5. 영원히 무시할 드리프트 정의하기
레포지토리 루트에 .tfdriftignore 파일을 만들어, 절대 알림을 받고 싶지 않은 항목을 지정합니다.
# Autoscaling — 설계상 몇 분마다 변함
aws_autoscaling_group.*.desired_capacity
aws_ecs_service.*.desired_count
# 외부 비용 할당 도구가 관리하는 태그
*.tags.CostCenter
*.tags.LastModified
*.tags.UpdatedBy
*.tags.ManagedBy
# Terraform 내부 메타데이터
*.tags.terraform
*.tags_all.*
이 파일이 없으면 자동 스케일링 변경에 대해 지속적으로 알림이 발생합니다. 이는 실제 드리프트가 아니라 정상적인 동작이므로 무시하도록 설정합니다.
6. 워크플로우 실행 및 결과 확인
.github/workflows/drift-check.yml,.tfdrift.yml,.tfdriftignore파일을 커밋하고 푸시합니다.- 레포지토리 → Actions 탭 → Terraform Drift Check → Run workflow 클릭 (수동 실행).
- 실행 과정을 관찰합니다.
드리프트가 있을 경우
#infra-alerts채널에 심각도별 요약이 포함된 Slack 메시지 도착- 워크플로우 실행에 JSON artifact 첨부
- 워크플로우 상태 Failed (exit code 1)
드리프트가 없을 경우
- Slack 메시지 없음 (알릴 것이 없으므로)
- 빈 드리프트 보고서가 포함된 JSON artifact 첨부
- 워크플로우 상태 Success (exit code 0)
7. Slack 알림 예시
tfdrift 가 High 혹은 Critical 드리프트를 발견하면 다음과 같은 메시지를 보냅니다.
⚠️ Terraform Drift Detected — 3 resource(s)
Workspaces scanned: 12
With drift: 2
Severity: critical: 1 | high: 2
🔴 CRITICAL — aws_security_group.api_sg
Action: update | Changed: ingress
🟠 HIGH — aws_instance.web_server
Action: update | Changed: instance_type, ami