当初情急之下手动加上的例外,两年后仍然留在那里
한국어 원문으로 표시합니다.
목표
정책 예외를 기한과 담당자가 붙은 등록부로 관리하고, 등록부를 클러스터의 실제 적용 범위로 바꾸는 스크립트와 기준일을 인자로 받는 만료 검사기, 네임스페이스별 위반 집계, 그리고 부채가 늘어나면 실패하는 게이트까지 직접 만듭니다.
왜 중요한가
정책을 켠 팀이 부딪히는 두 번째 벽은 규칙의 품질이 아니라 예외의 수명입니다. 규칙을 켜면 지금 당장 고칠 수 없는 것들이 반드시 나오고, 그때 선택지는 둘뿐입니다 — 정책을 내리거나, 예외를 만들거나. 정책을 내리면 모두가 풀리고 다시는 올라가지 않습니다. 그래서 예외를 만들지만, 예외에 기한과 담당자가 없으면 '다음 스프린트에 고칠게요' 로 만든 예외가 2년 뒤에도 그대로 남아 규칙 전체를 믿을 수 없게 만듭니다. 예외를 만드는 절차보다 지우는 절차를 먼저 설계해야 하는 이유입니다. 그 설계의 핵심은 등록부를 원본으로 두는 것입니다 — 클러스터에 손으로 붙인 표식은 누가 왜 붙였는지 아무도 모르지만, 저장소의 등록부는 리뷰를 거치고 기한이 붙고 지워진 기록까지 남습니다. 클러스터는 그 등록부의 반영일 뿐이고, 등록부에 없는 표식은 설명할 수 없으므로 지워집니다. 그리고 숫자가 있어야 줄일 수 있습니다. 예외 몇 건, 만료 몇 건, 아직 아무도 모르는 무단 위반 몇 건 — 이 세 숫자를 한 화면에 세우고 상한을 걸어 두면, 부채를 늘리는 변경이 사람의 기억이 아니라 파이프라인에서 멈춥니다.
단계
- 모든 작업은
/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에 ValidatingAdmissionPolicyno-latest-tag를 쓰세요 —apps/v1deployments의CREATE·UPDATE를 잡고, 파드 템플릿의 컨테이너 이미지가:latest로 끝나면 막되, 그 워크로드의 네임스페이스에polexc.io/exempt-<워크로드이름>라벨이 있으면 통과시킵니다(namespaceObject를 봅니다)./root/polexc/binding.yaml에는 ValidatingAdmissionPolicyBindingno-latest-deny를 쓰세요 —validationActions는["Deny"],matchResources.namespaceSelector.matchLabels는polexc.io/enforce: "on"입니다. 둘 다 적용한 뒤polexc-pay와polexc-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-batch와polexc-legacy/promo-web에 한 번씩 보내 두 출력을/root/polexc/01-off.txt에 모으고, 바인딩을 다시 적용해 두세요. /root/polexc/exceptions.txt에 예외 등록부를 쓰세요. 한 줄이 한 항목이고 칸은 정확히 여섯 개, 구분자는|입니다 —namespace|workload|owner|expires|ticket|reason순서이고expires는YYYY-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는 강제 라벨이 없지만 위반을 세는 범위에는 들어갑니다./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한 줄이 나와야 합니다.- 예외가 좁은지 직접 확인합니다.
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은 같은 네임스페이스인데도 거부되어야 합니다. /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하나가 나옵니다).- 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이 되어야 합니다. /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세 줄이 나옵니다./root/polexc/debt.sh를 쓰세요. 인자는<등록부> <적용범위파일> <기준일> <부채상한>네 개이고, 같은 디렉터리의expiry.sh와scan.sh를 불러서 씁니다($(dirname "$0")로 찾습니다). 출력은 정확히 여섯 줄입니다 —EXCEPTIONS <등록부 항목 수>,EXPIRED <만료 수>,DUE <임박 수>,UNMANAGED <무단 위반 합계>,DEBT <EXCEPTIONS + UNMANAGED>, 그리고 마지막에GATE OK또는GATE FAIL.EXPIRED가 하나라도 있거나DEBT가 상한을 넘으면GATE FAIL을 찍고 종료 코드1로 끝냅니다. 아니면GATE OK에0입니다.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도 눈으로 확인하세요.
참고
export KUBECONFIG=/root/.kube/config와kubectl config use-context kwok-lab로 시작합니다. 파드 안 kwok 이 띄우는 진짜 kube-apiserver v1.30.4 이고, 모든 산출물은/root/polexc아래에 둡니다.- kwok 은 파드를 실제로 실행하지 않습니다. 이 실습이 보는 것은 어드미션 판정과 오브젝트 상태뿐이라
kubectl patch ... --dry-run=server로 충분합니다 — 어드미션은 그대로 태우고 오브젝트는 남지 않습니다. - 이 파드에서는 시스템 시각을 바꿀 수 없습니다. 만료를 다루는 스크립트는 기준일을 인자로 받게 만드세요. 그래야 과거와 미래를 둘 다 시험할 수 있고, 같은 등록부가 날마다 다르게 판정되지 않습니다.
- 예외 표식을 워크로드 자신의 라벨에 두지 않는 이유가 있습니다. 표식을 떼는 요청 자체가 정책에 걸려 거부되어 만료된 예외를 거둘 수 없게 됩니다. 네임스페이스는 이 정책의 매치 대상이 아니므로 언제든 붙이고 뗄 수 있습니다.
- 네임스페이스 라벨이 어드미션 평가에 반영되기까지 한두 번의 왕복이 걸립니다. 결과가 예전 같으면 몇 초 뒤 다시 보내 보세요.
- 흔한 실수: 예외를 네임스페이스 단위로 푸는 것. 그러면 그 안에서 앞으로 새로 만들어지는 워크로드까지 영구히 규칙 밖에 놓입니다.
- 흔한 실수: 집계에서 예외로 덮인 위반과 무단 위반을 합쳐 세는 것. 합치면 둘 다 보이지 않게 됩니다.
- 흔한 실수: 거부 메시지를
2>&1없이 파일로 받는 것. 거부와 경고는 표준 오류로 나옵니다. - Validating Admission Policy · Admission Controllers Reference · Common Expression Language in Kubernetes · Policy Reports · Labels and Selectors
규칙을 켜자 지금 당장 고칠 수 없는 것들이 막혔다
모든 작업은 /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 deployments 의 CREATE·UPDATE 를 잡고, 파드 템플릿의 컨테이너 이미지가 :latest 로 끝나면 막되, 그 워크로드의 네임스페이스에 polexc.io/exempt-<워크로드이름> 라벨이 있으면 통과시킵니다(namespaceObject 를 봅니다). /root/polexc/binding.yaml 에는 ValidatingAdmissionPolicyBinding no-latest-deny 를 쓰세요 — validationActions 는 ["Deny"], matchResources.namespaceSelector.matchLabels 는 polexc.io/enforce: "on" 입니다. 둘 다 적용한 뒤 polexc-pay 와 polexc-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-batch 와 polexc-legacy/promo-web 에 한 번씩 보내 두 출력을 /root/polexc/01-off.txt 에 모으고, 바인딩을 다시 적용해 두세요.
정책(무엇을 판정하는가)과 바인딩(어디에 어떤 강도로)은 서로 다른 오브젝트입니다. 그래서 바인딩만 지우면 규칙은 그대로 있는데 아무것도 막히지 않습니다 — 이것이 '정책을 끄면 모두가 풀린다' 의 실체이고, 예외가 필요한 이유입니다. CEL 식에서는 요청 오브젝트를 object, 그 네임스페이스 오브젝트를 namespaceObject 로 읽습니다. 없을 수도 있는 필드는 has(...) 로 먼저 확인하고, 문자열을 이어 붙여 만든 키가 라벨에 있는지는 in 으로 봅니다. --dry-run=server 는 어드미션을 그대로 태우되 오브젝트를 남기지 않습니다. 거부 메시지는 표준 오류로 나오므로 2>&1 이 필요합니다.
예외를 머릿속이 아니라 파일에 적는다
/root/polexc/exceptions.txt 에 예외 등록부를 쓰세요. 한 줄이 한 항목이고 칸은 정확히 여섯 개, 구분자는 | 입니다 — namespace|workload|owner|expires|ticket|reason 순서이고 expires 는 YYYY-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 는 강제 라벨이 없지만 위반을 세는 범위에는 들어갑니다.
예외 등록부가 갖춰야 할 것은 '무엇을 푸는가' 만이 아닙니다. 담당자가 없으면 물어볼 사람이 없고, 만료일이 없으면 지울 근거가 없고, 추적 번호가 없으면 왜 풀었는지 되짚을 수 없습니다. 이 네 가지가 있어야 예외가 스위치가 아니라 부채 목록이 됩니다. 형식은 사람이 읽기 좋으면서 awk -F'|' 나 while IFS='|' read 로 한 번에 갈라지는 것이 좋습니다. 적용 범위를 따로 적는 이유는 뒤에서 드러납니다 — 등록부에 없는 표식을 지우려면 '어디까지 훑을 것인가' 가 정해져 있어야 합니다.
손으로 달아 둔 예외가 등록부 앞에서 지워졌다
/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 한 줄이 나와야 합니다.
여기서 정하는 것은 기술이 아니라 어느 쪽이 원본인가 입니다. 등록부가 원본이면 클러스터는 그 반영일 뿐이라, 등록부에 없는 표식은 설명할 수 없는 것이므로 지워야 합니다. 반대로 클러스터를 원본으로 두면 누가 언제 왜 풀었는지 아무도 모르게 됩니다. 라벨 값에 추적 번호를 넣어 두면 클러스터만 보고도 근거를 되짚을 수 있습니다. 라벨을 지울 때는 키 뒤에 - 를 붙입니다. 네임스페이스의 라벨 키 목록은 kubectl get ns <n> -o json 을 jq 로 훑으면 얻을 수 있습니다. 스크립트를 두 번 돌려도 결과가 같아야 합니다.
예외가 네임스페이스 전체를 풀지 않는지 확인한다
예외가 좁은지 직접 확인합니다. 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 은 같은 네임스페이스인데도 거부되어야 합니다.
예외 범위가 넓어지는 사고는 대부분 조용합니다. 네임스페이스 단위로 푼 예외는 그 안에서 앞으로 새로 만들어지는 워크로드까지 영구히 규칙 밖에 놓는데, 아무 오류도 나지 않으니 몇 달 뒤에야 드러납니다. 그래서 예외를 만든 직후에 풀리지 않아야 할 이웃을 한 번 찔러 보는 것이 절차의 일부여야 합니다. 두 출력이 한 파일에 들어가야 하므로 묶어서 넘기거나 두 번째를 덧붙여 씁니다.
만료일을 적어 두고 아무도 읽지 않으면 없는 것과 같다
/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 하나가 나옵니다).
기준일을 인자로 받는 것이 이 단계의 핵심입니다. 오늘 날짜를 스크립트 안에서 읽으면 같은 등록부가 어느 날 갑자기 다르게 판정되고, 과거 상태를 되짚거나 다음 분기를 미리 시험해 볼 수도 없습니다. 기준일을 밖에서 주면 '지난달 기준으로는 몇 건이었나' 도 답할 수 있습니다. 날짜 비교는 date -d '<날짜>' +%s 로 초로 바꿔 놓고 정수로 견주면 자릿수·월말 문제가 사라집니다. 30일은 30*86400 입니다. 이 파드에서는 시스템 시각을 바꿀 수 없습니다(date -s 는 권한이 없어 실패합니다). 그래서 기준일 인자가 유일한 시험 방법입니다.
만료된 예외를 실제로 거두자 그 워크로드가 다시 막혔다
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 이 되어야 합니다.
예외를 만드는 절차보다 예외를 지우는 절차가 어렵고 중요합니다. 지우는 쪽이 자동으로 돌지 않으면 예외는 조용히 영구화되고, 그때부터 그 규칙은 '켜져 있지만 아무도 믿지 않는 규칙' 이 됩니다. 3단계에서 등록부를 원본으로 삼아 둔 덕에 여기서 할 일은 등록부에서 한 줄을 지우고 같은 스크립트를 다시 돌리는 것뿐입니다 — 회수 로직을 따로 쓰지 않습니다. 등록부를 제자리에서 고칠 때는 임시 파일에 쓴 뒤 옮기세요. 이 단계를 두 번 돌려도 retired.txt 에 같은 줄이 두 번 들어가면 안 됩니다.
위반을 세어 보니 예외보다 무단 쪽이 많았다
/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 세 줄이 나옵니다.
어드미션은 앞으로 들어올 것만 봅니다. 이미 클러스터 안에 있는 것은 아무도 다시 검사하지 않으므로, 규칙을 켠 뒤에도 '지금 몇 개가 어기고 있나' 는 따로 훑어야 알 수 있습니다. 그리고 그 숫자는 반드시 둘로 갈라야 의미가 있습니다 — 예외로 덮인 것은 기한이 있는 부채이고, 무단 위반은 아직 아무도 모르는 구멍입니다. 합쳐 놓으면 둘 다 보이지 않게 됩니다. polexc-sandbox 처럼 강제 라벨이 없는 네임스페이스도 세야 합니다 — 강제가 꺼져 있다는 것이 위반이 없다는 뜻은 아닙니다. 이미지 목록은 kubectl get deploy -o json 을 jq 로 훑으면 한 번에 얻을 수 있습니다.
부채를 한 화면에 세우고 늘어나면 실패하게 만든다
/root/polexc/debt.sh 를 쓰세요. 인자는 <등록부> <적용범위파일> <기준일> <부채상한> 네 개이고, 같은 디렉터리의 expiry.sh 와 scan.sh 를 불러서 씁니다($(dirname "$0") 로 찾습니다). 출력은 정확히 여섯 줄입니다 — EXCEPTIONS <등록부 항목 수>, EXPIRED <만료 수>, DUE <임박 수>, UNMANAGED <무단 위반 합계>, DEBT <EXCEPTIONS + UNMANAGED>, 그리고 마지막에 GATE OK 또는 GATE FAIL. EXPIRED 가 하나라도 있거나 DEBT 가 상한을 넘으면 GATE FAIL 을 찍고 종료 코드 1 로 끝냅니다. 아니면 GATE OK 에 0 입니다. 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 도 눈으로 확인하세요.
이 한 화면이 회의에서 답해야 하는 질문 전부입니다 — 예외가 몇 건인가, 지금 기한을 넘긴 것이 있는가, 곧 넘길 것은 몇 건인가, 그리고 아무도 모르는 위반이 몇 건인가. 숫자가 흩어져 있으면 아무도 보지 않고, 한 줄로 합쳐 버리면 무엇을 고쳐야 할지 알 수 없습니다. 상한을 두는 이유는 '줄이자' 는 말이 저절로 지켜지지 않기 때문입니다. 상한이 있으면 부채가 늘어나는 변경이 파이프라인에서 멈춥니다. 앞 단계의 두 스크립트를 다시 쓰는 것이 중요합니다 — 세는 규칙이 두 군데에 적히면 반드시 한쪽이 먼저 낡습니다. expiry.sh 는 만료가 있으면 종료 코드 1 로 끝납니다. 그 출력을 받아 쓰는 쪽이 그 코드에 걸려 죽지 않게 하세요.