왜 수동 승인만으로는 부족한가
많은 조직이 프로덕션 배포 전 관리자 승인을 요구한다. 하지만 승인이 슬랙 메시지나 이메일, 티켓 코멘트에 흩어져 있으면 몇 가지 문제가 생긴다. 첫째, 누가 무엇을 근거로 승인했는지 감사 추적이 끊긴다. 둘째, 승인자가 실제 변경 내용(diff, 마이그레이션, 위험도)을 보지 않고 형식적으로 클릭한다. 셋째, 승인 대기가 병목이 되어 오히려 승인 없는 우회 배포를 유발한다. 규제 감사(SOC2, ISO27001)에서 요구하는 건 "승인했다"는 사실이 아니라 변경-승인-배포가 하나의 추적 가능한 체인으로 묶여 있는가이다. 이 체인을 사람 손이 아니라 파이프라인이 강제하도록 만드는 것이 핵심이다.
승인 게이트를 파이프라인에 내장하기
GitHub Actions의 Environment protection rules를 쓰면 특정 환경(production)에 대한 배포 잡을 지정된 리뷰어가 승인해야만 진행되도록 강제할 수 있다. 승인 기록은 워크플로 실행 로그에 남아 감사 추적이 자동으로 확보된다.
name: deploy
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./scripts/build.sh
deploy-prod:
needs: build
runs-on: ubuntu-latest
environment:
name: production # 이 환경에 승인 규칙을 건다
url: https://app.example.com
steps:
- uses: actions/checkout@v4
- name: Deploy
run: ./scripts/deploy.sh
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
Settings → Environments → production 에서 "Required reviewers"를 지정하면 deploy-prod 잡은 승인 전까지 대기 상태로 멈춘다. 배포자 본인이 승인하지 못하도록 리뷰어 풀을 분리하면 "직무 분리(SoD)" 요건도 충족한다.
승인 정책을 코드로 정의한다 (Policy as Code)
승인을 사람에게만 의존하면 규칙이 문서에만 존재한다. 위험도에 따라 승인 요건을 자동 판정하도록 정책을 코드화하면 일관성이 생긴다. 예를 들어 DB 마이그레이션이나 인프라 변경이 포함되면 2인 승인, 순수 애플리케이션 변경이면 1인 승인으로 차등화할 수 있다.
import subprocess, sys, json
def changed_files():
out = subprocess.check_output(
["git", "diff", "--name-only", "origin/main...HEAD"])
return out.decode().splitlines()
HIGH_RISK = ("migrations/", "terraform/", "helm/")
def required_approvals(files):
if any(f.startswith(HIGH_RISK) for f in files):
return 2
return 1
files = changed_files()
n = required_approvals(files)
print(json.dumps({"required_approvals": n,
"high_risk": n == 2}))
sys.exit(0)
이 판정 결과를 파이프라인 초입에서 출력하면, 승인자는 "왜 2인 승인이 필요한지"를 근거와 함께 보게 된다. 판정 로직 자체가 버전 관리되므로 정책 변경 이력도 남는다.
변경 티켓과 배포를 자동으로 연결하기
변경관리(Change Management)의 핵심은 모든 프로덕션 변경이 승인된 변경 요청(CR)에 매핑되는 것이다. 커밋 메시지나 PR 본문에 티켓 ID를 강제하고, 배포 시 해당 티켓의 상태가 "승인됨"인지 API로 검증하면 수동 대조가 사라진다.
#!/usr/bin/env bash
set -euo pipefail
TICKET=$(git log -1 --pretty=%B | grep -oE 'CHG-[0-9]+' | head -1)
if [ -z "$TICKET" ]; then
echo "커밋에 변경 티켓(CHG-xxxx)이 없습니다" >&2
exit 1
fi
STATUS=$(curl -sf -H "Authorization: Bearer $CM_TOKEN" \
"https://cm.internal/api/change/$TICKET" | jq -r '.status')
if [ "$STATUS" != "approved" ]; then
echo "티켓 $TICKET 상태=$STATUS, 배포 차단" >&2
exit 1
fi
echo "티켓 $TICKET 승인 확인, 배포 진행"
비상 배포(Break-glass) 경로 설계
장애 대응 중에는 정상 승인 절차가 오히려 위험하다. 그렇다고 게이트를 끄면 통제가 무너진다. 해법은 별도의 break-glass 경로를 두되, 사용 자체가 강한 흔적을 남기게 하는 것이다.
| 구분 | 일반 배포 | 비상 배포 |
|---|---|---|
| 승인 | 사전 승인 필수 | 사후 승인 허용 |
| 실행 권한 | 일반 배포자 | 온콜 담당자 한정 |
| 알림 | 채널 공지 | 경영진·보안팀 즉시 알림 |
| 사후 조치 | 없음 | 24시간 내 포스트모템 의무 |
비상 경로를 아예 막으면 사람들은 더 위험한 우회로(SSH 직접 접속 등)를 찾는다. 통제된 비상구를 열어두는 편이 안전하다.
주의점
승인 게이트는 만능이 아니다. 몇 가지를 조심해야 한다. 첫째, 승인이 형식화되지 않도록 승인 화면에 diff 요약과 위험 판정을 반드시 노출한다. 둘째, 정책 코드와 티켓 검증 스크립트는 파이프라인 실패 시 fail-closed(차단)로 동작해야 한다. API가 타임아웃됐다고 배포를 통과시키면 게이트가 무의미하다. 셋째, 승인 대기 시간을 메트릭으로 수집해 병목을 관리하라. 통제가 속도를 지나치게 떨어뜨리면 조직은 결국 통제를 우회한다. 목표는 "배포를 어렵게 만드는 것"이 아니라 "안전한 배포를 기본값으로 만드는 것"이다.