LabHub
배우기 러닝패스 코스

정책을 코드로 · 정책이 고장 나면 클러스터가 멈춘다 · 실습

웹훅 하나를 Fail 로 바꿨더니 클러스터가 멈췄다

LabHub 에서 이어서 보기

목표

어드미션 웹훅의 적용 범위를 세 층에서 직접 좁혀 보고, 정책 서버가 죽은 상태를 진짜로 만들어 failurePolicytimeoutSeconds 가 무엇을 맞바꾸는지 시간을 재서 확인하고, 클러스터의 모든 웹훅에 대해 '이것이 죽으면 무엇이 멈추는가' 를 답하는 보고서와 검사기를 만듭니다.

왜 중요한가

정책 도입 사고의 기록을 모아 보면 규칙이 틀려서 난 사고는 드뭅니다. 대부분은 둘 중 하나입니다 — 너무 넓게 걸었거나, 고장 났을 때의 동작을 정하지 않았거나. 넓게 거는 비용은 정책 한 번의 실행 시간이 아니라 들어오는 모든 쓰기 요청에 붙는 지연세이고, 클러스터에는 사람이 보내는 요청보다 컨트롤러가 보내는 요청이 훨씬 많습니다. 더 무서운 것은 두 번째입니다. 정책이 kube-system 까지 보도록 걸어 두고 정책 서버가 죽으면, failurePolicy: Fail 아래에서 클러스터는 자기 자신을 복구할 수 없게 됩니다 — 서버를 되살리려는 배포도 그 정책을 지나야 하기 때문입니다. 범위와 고장 모드는 따로 고르는 값이 아니라 한 쌍으로 고르는 값이고, 이 실습은 그 한 쌍을 손으로 돌려 봅니다. 마지막에 만드는 폭발 반경 보고서는 정책을 켜기 전이 아니라 이미 켜 둔 클러스터에서 지금 당장 만들어 봐야 하는 물건입니다.

단계

1. /root/polscope 에서 작업합니다(export KUBECONFIG=/root/.kube/config, kubectl config use-context kwok-lab). 먼저 네임스페이스 scope-app·scope-ops·scope-builtin 을 만들고, 세 곳 모두에 kubectl create sa default -n <네임스페이스>default 서비스 계정을 직접 만드세요(이 클러스터에는 컨트롤러 매니저가 없어 저절로 생기지 않습니다). /root/polscope/webhook-wide.yaml 에 ValidatingWebhookConfiguration scope-wide-audit 을 쓰세요 — 웹훅 이름은 wide.polscope.local, admissionReviewVersions["v1"], sideEffectsNone, failurePolicyIgnore, timeoutSeconds2, clientConfig.urlhttps://192.0.2.77:8443/validate 입니다. rules 는 가장 넓게 — apiGroups: ["*"], apiVersions: ["*"], operations: ["CREATE", "UPDATE"], resources: ["*"], scope: "*" 로 두고 셀렉터는 하나도 두지 않습니다. 시험용 매니페스트 셋도 만드세요 — /root/polscope/pod.yaml(파드 probe-app, 컨테이너 web, 이미지 registry.internal/app:1.0), /root/polscope/pod-guard.yaml(파드 probe-guard, 라벨 polscope.io/guard: "on", 같은 컨테이너), /root/polscope/cm.yaml(ConfigMap probe-cm, 데이터 a: b). 적용한 뒤 네 가지 요청의 소요 시간을 재세요 — kubectl create -n scope-app -f pod.yaml --dry-run=server, kubectl create -n scope-app -f cm.yaml --dry-run=server, kubectl create ns probe-ns --dry-run=server, kubectl get pods -n scope-app. 1초 이상 걸렸으면 SLOW, 아니면 FAST/root/polscope/01-reach.txtpod-create·configmap-create·namespace-create·pod-list 네 줄을 <이름> <낱말> 꼴로 적으세요.
2. /root/polscope/webhook-strict.yaml 에 두 번째 설정 scope-strict 를 쓰세요 — 웹훅 이름은 strict.polscope.local, rulesscope-wide-audit똑같이 가장 넓게, 다른 것은 셋뿐입니다: failurePolicyFail, timeoutSeconds3, clientConfig.url 은 연결이 거부되는 주소 https://127.0.0.1:19443/validate 입니다. scope-wide-audit 은 지우지 말고 그대로 둡니다 — 같은 범위에 고장 모드만 다른 둘이 나란히 서 있는 것이 이 실습의 대조군입니다. 적용한 뒤 네 가지를 시도해 막히는지 보고, 막히면 BLOCK 통과하면 PASS/root/polscope/02-blocked.txt<이름> <낱말> 네 줄을 적으세요 — pod-create-scope-app(kubectl create -n scope-app -f pod.yaml --dry-run=server), configmap-kube-system(kubectl create -n kube-system -f cm.yaml --dry-run=server), namespace-create(kubectl create ns probe-ns --dry-run=server), webhookconfig-edit(kubectl apply -f webhook-strict.yaml 을 한 번 더).
3. 먼저 면제 라벨로 빠져나가 보세요 — kubectl label ns kube-system polscope.io/admission=exempt --overwrite 를 돌려 그 출력을 표준 오류까지 함께 /root/polscope/03-lockout.txt 에 저장하세요(막힙니다). 그다음 /root/polscope/webhook-strict.yamlscope-strictnamespaceSelector.matchExpressions 두 개를 더해 다시 적용하세요 — (1) 키 kubernetes.io/metadata.name, 연산자 NotIn, 값 ["kube-system", "kube-node-lease", "kube-public"], (2) 키 polscope.io/admission, 연산자 NotIn, 값 ["exempt"]. 이제 라벨이 붙습니다 — kube-systemscope-opspolscope.io/admission=exempt 를 거세요. 마지막으로 /root/polscope/pod-guard.yaml 을 세 곳에 --dry-run=server 로 보내 /root/polscope/03-escape.txtkube-system·scope-ops·scope-app 세 줄을 <네임스페이스> <BLOCK|PASS> 꼴로 적으세요.
4. /root/polscope/webhook-strict.yamlscope-strict 를 두 층에서 좁히세요 — rules 는 코어 그룹("")의 v1 pods 에 대한 CREATE·UPDATE 만, scopeNamespaced 로 바꾸고, objectSelector.matchLabelspolscope.io/guard: "on" 라벨이 붙은 객체만 보게 합니다. 3단계의 namespaceSelector 는 그대로 둡니다. 적용한 뒤 네 가지 요청을 다시 보내 /root/polscope/04-narrow.txt<이름> <좁히기전> <좁힌뒤> 네 줄을 적으세요 (좁히기 전 값은 2단계에서 본 것입니다) — pod-guard-scope-app(pod-guard.yamlscope-app 에), pod-plain-scope-app(pod.yamlscope-app 에), configmap-scope-app(cm.yamlscope-app 에), namespace-create(kubectl create ns probe-ns --dry-run=server). 판정은 BLOCK 또는 PASS 입니다.
5. /root/polscope/webhook-strict.yamlscope-strictmatchConditions 두 개를 더해 다시 적용하세요 — skip-kube-system-sa 는 요청자가 system:serviceaccount:kube-system: 으로 시작하는 서비스 계정이면 웹훅을 부르지 않게 하고, skip-break-glass 는 요청자의 그룹에 polscope:break-glass 가 있으면 부르지 않게 합니다. 적용한 뒤 같은 /root/polscope/pod-guard.yamlscope-app 에 세 가지 주체로 보내 /root/polscope/05-conditions.txtkube-system-sa·break-glass·normal 세 줄을 <이름> <BLOCK|PASS> 꼴로 적으세요 — kubectl --as=system:serviceaccount:kube-system:replicaset-controller create -n scope-app -f pod-guard.yaml --dry-run=server, kubectl --as=oncall --as-group=polscope:break-glass --as-group=system:masters create -n scope-app -f pod-guard.yaml --dry-run=server, 그리고 지금 계정 그대로 한 번.
6. /root/polscope/webhook-slow.yaml 에 세 번째 설정 scope-slow 를 쓰세요 — 웹훅 이름 slow.polscope.local, clientConfig.url 은 응답이 아예 오지 않는 주소 https://192.0.2.77:8443/validate, sideEffectsNone, namespaceSelector.matchExpressions 는 키 kubernetes.io/metadata.nameIn ["scope-ops"] 로, objectSelector.matchLabelspolscope.io/slow: "on", rules 는 코어 그룹의 v1 podsCREATE·UPDATE(scope: "Namespaced"), 최종 상태는 failurePolicy: FailtimeoutSeconds: 3 입니다. /root/polscope/pod-slow.yaml 에 파드 probe-slow(라벨 polscope.io/slow: "on", 컨테이너 web, 이미지 registry.internal/app:1.0)를 만드세요. 그리고 /root/polscope/timeout-probe.sh <Fail|Ignore> <초> 를 만드세요 — scope-slow 를 그 두 값으로 잠시 바꾸고, scope-opspod-slow.yaml--dry-run=server 로 한 번 보내 걸린 시간을 재고, 원래 값으로 반드시 되돌린 뒤, BLOCK <초> 또는 PASS <초> 한 줄만 출력합니다(초는 반올림한 정수). 이 스크립트로 세 번 재어 /root/polscope/06-timeout.txt<failurePolicy> <타임아웃> <BLOCK|PASS> <걸린초> 세 줄을 적으세요 — Fail 6, Ignore 6, Fail 4 로 각각 한 번씩입니다.
7. 웹훅 서버가 없는 지금 이대로, 같은 자리에 내장 정책을 세워 비교합니다. 네임스페이스 scope-builtin 에 라벨 pod-security.kubernetes.io/enforce=baseline 을 거세요. /root/polscope/vap-owner.yaml 에 ValidatingAdmissionPolicy polscope-require-owner(코어 그룹 v1 podsCREATE·UPDATE 를 잡고, 파드 메타데이터에 owner 라벨이 있을 것을 요구)와 같은 이름의 ValidatingAdmissionPolicyBinding(validationActions["Deny"], matchResources.namespaceSelectorkubernetes.io/metadata.nameIn ["scope-builtin"])을 함께 쓰고 적용하세요. 시험용 파드 셋을 더 만드세요 — /root/polscope/pod-owned.yaml(파드 probe-owned, 라벨 owner: platform), /root/polscope/pod-host.yaml(파드 probe-host, 같은 라벨에 spec.hostNetwork: true), /root/polscope/pod-both.yaml(파드 probe-both, 라벨 owner: platformpolscope.io/guard: "on"). 컨테이너는 셋 다 web/registry.internal/app:1.0 입니다. 네 매니페스트를 scope-builtin--dry-run=server 로 보내 무엇이 판정했는지를 /root/polscope/07-builtin.txt<파일이름> <낱말> 네 줄로 적으세요 — 낱말은 VAP(ValidatingAdmissionPolicy 가 거부), PSA(PodSecurity 가 거부), WEBHOOK(웹훅 호출 실패), PASS(통과) 중 하나이고, 대상은 pod.yaml·pod-host.yaml·pod-both.yaml·pod-owned.yaml 입니다.
8. /root/polscope/blast-radius.sh 를 만드세요 — 클러스터의 모든 ValidatingWebhookConfiguration 을 훑어 웹훅 하나마다 객체 하나를 담은 JSON 배열을 표준 출력으로 냅니다. 키는 정확히 여덟 개입니다: config(설정 이름) · webhook(웹훅 이름) · failurePolicy(없으면 "Fail") · timeoutSeconds(없으면 10) · resources(그 웹훅의 모든 rules[].resources 를 중복 없이 정렬한 배열) · wildcard(resources"*" 가 있으면 참) · excludesKubeSystem(namespaceSelector.matchExpressions 에 키가 kubernetes.io/metadata.name, 연산자가 NotIn, 값에 kube-system 이 든 항목이 있으면 참) · risk. riskwildcard 가 참이고 failurePolicyFail 이고 excludesKubeSystem 이 거짓이면 "high", 그 밖에 failurePolicyFail 이면 "medium", 나머지는 "low" 입니다. 배열은 config · webhook 순으로 정렬합니다. 그 출력을 /root/polscope/blast-radius.json 에 저장하세요. 그리고 /root/polscope/risky.sh 를 만드세요 — riskhigh 인 것만 <config>/<webhook> 꼴로 한 줄씩 내고, 하나라도 있으면 종료 코드 1, 하나도 없으면 아무것도 내지 않고 0 으로 끝납니다. 두 스크립트 모두 저장된 파일이 아니라 지금 클러스터에 서 있는 오브젝트를 읽어야 합니다.

참고

단계 8개

  1. 웹훅 하나를 넓게 걸었더니 모든 쓰기가 2초씩 늦어졌다
  2. 고장 모드만 Fail 로 바꿨더니 그 범위의 쓰기가 전부 멈췄다
  3. 라벨로 빼려 했더니 라벨 붙이는 일까지 막혀 있었다
  4. 범위를 좁히자 걸리던 넷 중 셋이 웹훅을 지나가지 않게 됐다
  5. 컨트롤러와 당번만 지나가는 문을 낸다
  6. 타임아웃을 6초로 두었더니 장애가 요청마다 6초씩 길어졌다
  7. 웹훅이 전부 죽은 클러스터에서 내장 정책만 판정을 내놓았다
  8. 이것이 죽으면 무엇이 멈추는가를 한 장으로 답한다