LabHub
배우기 러닝패스 코스

정책을 코드로 · 정책 예외와 리포트 · 실습

급해서 손으로 달아 둔 예외가 2년째 남아 있었다

LabHub 에서 이어서 보기

목표

정책 예외를 기한과 담당자가 붙은 등록부로 관리하고, 등록부를 클러스터의 실제 적용 범위로 바꾸는 스크립트와 기준일을 인자로 받는 만료 검사기, 네임스페이스별 위반 집계, 그리고 부채가 늘어나면 실패하는 게이트까지 직접 만듭니다.

왜 중요한가

정책을 켠 팀이 부딪히는 두 번째 벽은 규칙의 품질이 아니라 예외의 수명입니다. 규칙을 켜면 지금 당장 고칠 수 없는 것들이 반드시 나오고, 그때 선택지는 둘뿐입니다 — 정책을 내리거나, 예외를 만들거나. 정책을 내리면 모두가 풀리고 다시는 올라가지 않습니다. 그래서 예외를 만들지만, 예외에 기한과 담당자가 없으면 '다음 스프린트에 고칠게요' 로 만든 예외가 2년 뒤에도 그대로 남아 규칙 전체를 믿을 수 없게 만듭니다. 예외를 만드는 절차보다 지우는 절차를 먼저 설계해야 하는 이유입니다. 그 설계의 핵심은 등록부를 원본으로 두는 것입니다 — 클러스터에 손으로 붙인 표식은 누가 왜 붙였는지 아무도 모르지만, 저장소의 등록부는 리뷰를 거치고 기한이 붙고 지워진 기록까지 남습니다. 클러스터는 그 등록부의 반영일 뿐이고, 등록부에 없는 표식은 설명할 수 없으므로 지워집니다. 그리고 숫자가 있어야 줄일 수 있습니다. 예외 몇 건, 만료 몇 건, 아직 아무도 모르는 무단 위반 몇 건 — 이 세 숫자를 한 화면에 세우고 상한을 걸어 두면, 부채를 늘리는 변경이 사람의 기억이 아니라 파이프라인에서 멈춥니다.

단계

1. 모든 작업은 /root/polexc 에서 합니다(export KUBECONFIG=/root/.kube/config, kubectl config use-context kwok-lab). 먼저 현장을 만드세요 — 네임스페이스 polexc-pay·polexc-legacy·polexc-sandbox 를 만들고 kubectl create deployment 로 여섯 개를 올립니다: polexc-pay/pay-api(registry.internal/pay:1.4), polexc-pay/pay-batch(vendor.example/paybatch:latest), polexc-legacy/billing-api(vendor.example/billing:latest), polexc-legacy/report-gen(vendor.example/report:latest), polexc-legacy/promo-web(vendor.example/promo:latest), polexc-sandbox/scratch-job(vendor.example/scratch:latest). 그다음 /root/polexc/policy.yaml 에 ValidatingAdmissionPolicy no-latest-tag 를 쓰세요 — apps/v1 deploymentsCREATE·UPDATE 를 잡고, 파드 템플릿의 컨테이너 이미지가 :latest 로 끝나면 막되, 그 워크로드의 네임스페이스에 polexc.io/exempt-<워크로드이름> 라벨이 있으면 통과시킵니다(namespaceObject 를 봅니다). /root/polexc/binding.yaml 에는 ValidatingAdmissionPolicyBinding no-latest-deny 를 쓰세요 — validationActions["Deny"], matchResources.namespaceSelector.matchLabelspolexc.io/enforce: "on" 입니다. 둘 다 적용한 뒤 polexc-paypolexc-legacy 에만 polexc.io/enforce=on 라벨을 붙이세요(polexc-sandbox 에는 붙이지 않습니다). 이제 kubectl -n polexc-pay patch deployment pay-batch --type merge -p '{"metadata":{"annotations":{"polexc.io/probe":"1"}}}' --dry-run=server 의 출력을 표준 오류까지 /root/polexc/01-blocked.txt 에 저장하세요. 마지막으로 바인딩만 지우고(kubectl delete -f /root/polexc/binding.yaml) 같은 요청을 pay-batchpolexc-legacy/promo-web 에 한 번씩 보내 두 출력을 /root/polexc/01-off.txt 에 모으고, 바인딩을 다시 적용해 두세요.
2. /root/polexc/exceptions.txt 에 예외 등록부를 쓰세요. 한 줄이 한 항목이고 칸은 정확히 여섯 개, 구분자는 | 입니다 — namespace|workload|owner|expires|ticket|reason 순서이고 expiresYYYY-MM-DD 입니다. 첫 줄에는 # 로 시작하는 머리글을 두어 사람이 읽을 수 있게 하세요(# 로 시작하는 줄과 빈 줄은 도구가 건너뜁니다). 항목은 세 개입니다 — polexc-pay|pay-batch|team-pay|2026-09-10|OPS-2201|근거, polexc-legacy|billing-api|team-billing|2026-10-20|OPS-2188|근거, polexc-legacy|report-gen|team-report|2027-03-31|OPS-2245|근거. 근거는 네 글자 이상으로 직접 쓰세요. 이어서 /root/polexc/scope.txt 에 이 정책의 적용 범위를 한 줄에 하나씩 적으세요 — polexc-pay, polexc-legacy, polexc-sandbox 세 줄입니다. polexc-sandbox 는 강제 라벨이 없지만 위반을 세는 범위에는 들어갑니다.
3. /root/polexc/apply-exceptions.sh 를 쓰세요. 인자는 두 개입니다 — <등록부> <적용범위파일>. 하는 일은 둘입니다. (1) 등록부의 각 항목에 대해 그 네임스페이스에 라벨 polexc.io/exempt-<workload>=<ticket> 을 붙이고 GRANT <namespace>/<workload> <ticket> 을 한 줄 출력합니다(등록부에 적힌 순서 그대로). (2) 적용 범위 파일의 네임스페이스들을 훑어 polexc.io/exempt- 로 시작하는 라벨 중 등록부에 없는 것을 지우고 REVOKE <namespace>/<workload> 를 출력합니다(이 줄들은 <namespace>/<workload> 사전순으로 모아서 GRANT 줄 뒤에 냅니다). 먼저 누군가 급하게 손으로 달아 둔 예외를 재현하세요 — kubectl label ns polexc-legacy polexc.io/exempt-promo-web=MANUAL --overwrite. 그다음 bash /root/polexc/apply-exceptions.sh /root/polexc/exceptions.txt /root/polexc/scope.txt 를 돌려 출력을 /root/polexc/03-apply.txt 에 저장하세요. GRANT 세 줄과 REVOKE polexc-legacy/promo-web 한 줄이 나와야 합니다.
4. 예외가 좁은지 직접 확인합니다. polexc-legacy 에는 지금 billing-api(등록된 예외 있음)와 promo-web(3단계에서 표식이 지워짐)이 함께 있습니다. 두 워크로드에 1단계와 같은 kubectl -n polexc-legacy patch deployment <이름> --type merge -p '{"metadata":{"annotations":{"polexc.io/probe":"4"}}}' --dry-run=server 요청을 차례로 보내 두 출력을 표준 오류까지 /root/polexc/04-narrow.txt 에 모으세요 — billing-api 는 통과하고 promo-web 은 같은 네임스페이스인데도 거부되어야 합니다.
5. /root/polexc/expiry.sh 를 쓰세요. 인자는 <등록부> <기준일 YYYY-MM-DD> 두 개입니다. 등록부에 적힌 순서 그대로 항목마다 한 줄을 냅니다 — <상태> <namespace>/<workload> <expires> <owner> <ticket>. 상태는 셋입니다: 만료일이 기준일보다 이전이면 EXPIRED, 기준일부터 기준일 +30일까지(양끝 포함)면 DUE, 그보다 뒤면 OK 입니다. 종료 코드는 EXPIRED 가 하나라도 있으면 1, 없으면 0 입니다 — 이 코드가 게이트가 됩니다. bash /root/polexc/expiry.sh /root/polexc/exceptions.txt 2026-10-01 을 돌려 출력을 /root/polexc/05-expiry.txt 에 저장하세요 (EXPIRED 하나, DUE 하나, OK 하나가 나옵니다).
6. 5단계가 찾아낸 만료 항목을 실제로 거둡니다. 먼저 그 항목(polexc-pay|pay-batch|...) 줄을 /root/polexc/retired.txt 에 덧붙여 남기고, /root/polexc/exceptions.txt 에서는 지우세요 — 지운 기록이 남아야 나중에 '이건 왜 사라졌나' 에 답할 수 있습니다. 그다음 bash /root/polexc/apply-exceptions.sh /root/polexc/exceptions.txt /root/polexc/scope.txt 를 다시 돌려 출력을 /root/polexc/06-apply.txt 에 저장하세요 (REVOKE polexc-pay/pay-batch 가 나와야 합니다). 마지막으로 4단계와 같은 요청을 polexc-pay/pay-batch 에 보내 출력을 표준 오류까지 /root/polexc/06-revoked.txt 에 저장하세요 — 이제 거부되어야 합니다. bash /root/polexc/expiry.sh /root/polexc/exceptions.txt 2026-10-01 의 종료 코드도 0 이 되어야 합니다.
7. /root/polexc/scan.sh 를 쓰세요. 인자는 <적용범위파일> 하나입니다. 파일에 적힌 순서대로 네임스페이스마다 한 줄을 냅니다 — <namespace> <위반수> <예외수> <무단수>. 위반은 컨테이너 이미지가 :latest 로 끝나는 Deployment 이고, 그중 그 네임스페이스에 polexc.io/exempt-<이름> 라벨이 있는 것이 예외수, 없는 것이 무단수입니다(위반이 없는 네임스페이스도 0 0 0 으로 냅니다). bash /root/polexc/scan.sh /root/polexc/scope.txt 를 돌려 출력을 /root/polexc/scan.txt 에 저장하세요. polexc-pay 1 0 1, polexc-legacy 3 2 1, polexc-sandbox 1 0 1 세 줄이 나옵니다.
8. /root/polexc/debt.sh 를 쓰세요. 인자는 <등록부> <적용범위파일> <기준일> <부채상한> 네 개이고, 같은 디렉터리의 expiry.shscan.sh 를 불러서 씁니다($(dirname "$0") 로 찾습니다). 출력은 정확히 여섯 줄입니다 — EXCEPTIONS <등록부 항목 수>, EXPIRED <만료 수>, DUE <임박 수>, UNMANAGED <무단 위반 합계>, DEBT <EXCEPTIONS + UNMANAGED>, 그리고 마지막에 GATE OK 또는 GATE FAIL. EXPIRED 가 하나라도 있거나 DEBT 가 상한을 넘으면 GATE FAIL 을 찍고 종료 코드 1 로 끝냅니다. 아니면 GATE OK0 입니다. bash /root/polexc/debt.sh /root/polexc/exceptions.txt /root/polexc/scope.txt 2026-10-01 5 를 돌려 출력을 /root/polexc/debt.txt 에 저장하세요 (DEBT 5, GATE OK, 종료 코드 0 이 나옵니다). 같은 명령을 상한 4 로 한 번 더 돌려 GATE FAIL 과 종료 코드 1 도 눈으로 확인하세요.

참고

단계 8개

  1. 규칙을 켜자 지금 당장 고칠 수 없는 것들이 막혔다
  2. 예외를 머릿속이 아니라 파일에 적는다
  3. 손으로 달아 둔 예외가 등록부 앞에서 지워졌다
  4. 예외가 네임스페이스 전체를 풀지 않는지 확인한다
  5. 만료일을 적어 두고 아무도 읽지 않으면 없는 것과 같다
  6. 만료된 예외를 실제로 거두자 그 워크로드가 다시 막혔다
  7. 위반을 세어 보니 예외보다 무단 쪽이 많았다
  8. 부채를 한 화면에 세우고 늘어나면 실패하게 만든다