GitHub Actions와 Slack으로 지속적인 Terraform 드리프트 모니터링 구축

발행: (2026년 6월 7일 AM 02:50 GMT+9)
10 분 소요
원문: Dev.to

Source: Dev.to

대부분의 팀은 Terraform 드리프트를 힘들게 발견합니다 — 배포 전에 누군가 terraform plan을 실행했을 때 예상치 못한 변경 사항이 화면에 가득 뜨는 경우죠. 그때쯤이면 드리프트가 몇 주, 혹은 그보다 더 오래 존재했을 수도 있습니다.
자동으로 잡을 수 있다면 어떨까요? 몇 시간마다 스캔을 실행하고, 중요한 드리프트가 발생했을 때만 Slack 메시지를 받아보며, 잡음은 무시하는 방식 말입니다.

이 튜토리얼이 바로 그 설정을 해줍니다. 최종적으로 다음을 갖게 됩니다:

  • 일정에 따라 Terraform 인프라를 스캔하는 GitHub Actions 워크플로우
  • HighCritical 심각도 드리프트에만 알림을 보내는 Slack 알림
  • 감사 로그용으로 저장되는 JSON 보고서 (artifact)
  • 전부 자동으로 실행되며, 유지보수가 필요 없는 상태

저는 이 작업에 tfdrift를 사용합니다 — 제가 만든 오픈소스 CLI로, 드리프트를 심각도별로 분류합니다. 하지만 GitHub Actions + Slack 패턴은 어떤 드리프트 탐지 방법과도 함께 사용할 수 있습니다.

시작하기 전에 준비해야 할 것:

  • Terraform 코드가 들어 있는 GitHub 레포지토리
  • AWS 자격증명 (또는 사용 중인 다른 클라우드 제공자)
  • 웹훅을 만들 수 있는 Slack 워크스페이스
  • Python 3.9 이상 (tfdrift는 Python CLI입니다)
  • 약 20분 정도의 시간

1. 알림이 갈 위치 설정하기

  1. api.slack.com/apps 로 이동
  2. Create New AppFrom scratch 클릭
  3. “Drift Alerts” 같은 이름을 지정하고 워크스페이스 선택
  4. 사이드바에서 Incoming Webhooks 로 이동 → 토글을 On 으로 전환
  5. Add New Webhook to Workspace 클릭
  6. 알림을 받을 채널 선택 (예: #infra-alerts)
  7. 웹훅 URL 복사 — https://hooks.slack.com/services/T00000/B00000/XXXX 와 같은 형태

이 URL을 저장해 둡니다. 곧 필요합니다.


2. 비밀값(Secrets) 저장하기

클라우드 자격증명과 Slack 웹훅을 워크플로우 파일에 노출되지 않게 GitHub Secrets에 저장합니다.

  1. 레포지토리 → SettingsSecrets and variablesActionsNew repository secret
  2. 다음 비밀값을 추가:
이름
AWS_ACCESS_KEY_IDYour AWS access key
AWS_SECRET_ACCESS_KEYYour AWS secret key
AWS_DEFAULT_REGIONus-east-1 (또는 사용 중인 리전)
SLACK_WEBHOOK_URL1단계에서 복사한 웹훅 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. 워크플로우 실행 및 결과 확인

  1. .github/workflows/drift-check.yml, .tfdrift.yml, .tfdriftignore 파일을 커밋하고 푸시합니다.
  2. 레포지토리 → Actions 탭 → Terraform Drift CheckRun workflow 클릭 (수동 실행).
  3. 실행 과정을 관찰합니다.

드리프트가 있을 경우

  • #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
0 조회
Back to Blog

관련 글

더 보기 »

모바일 한여름 열풍

!Cover image for Mobile Midsommer Madnesshttps://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploa...