CNPE模擬試験A
한국어 원문으로 표시합니다.
목표
실제 CNPE 와 같은 조건에서 과제 17개를 120분 안에 풉니다. 합격선은 64% 이고 부분 점수제이므로, 17개 중 11개를 통과하면 완료로 처리됩니다.
모의고사입니다. 힌트와 정답지를 보지 말고 먼저 끝까지 풀어 보십시오. 막힌 과제는 표시해 두고 넘어갔다가 남은 시간에 돌아오는 편이 낫습니다. 채점은 언제든 눌러도 되고, 여러 번 눌러도 결과가 달라지지 않습니다.
왜 중요한가
CNPE 는 전문가 등급이고, 묻는 것이 조작법이 아니라 판단입니다. 같은 규칙을 파이프라인에서 막을지 API 서버에서 막을지, 사고가 났을 때 제약을 지울지 만족시킬지, 테넌트에게 무엇을 열어 주고 무엇을 닫아 둘지를 정하는 자리가 과제마다 하나씩 들어 있습니다. 도구는 여러 개가 나오지만 어느 것도 깊게 묻지 않습니다. 대신 어느 도구를 어디에 놓을 것인가를 묻습니다.
시험 환경 (실제 시험에서 확인된 사실)
- 볼 수 있는 문서는
kubernetes.io/docs,kubernetes.io/blog, 그리고 과제마다 따로 주어지는 Quick Reference 링크입니다. 그 링크는 허용 목록에 더해집니다. - 원격 데스크톱 안에서 터미널과 웹 인터페이스를 함께 씁니다. 과제에 따라 브라우저로 콘솔을 여는 것이 더 빠를 때가 있습니다.
- 시험에 나올 수 있는 도구는 Argo, Crossplane, Flagger, Flux, Gatekeeper, Grafana, Istio, Jaeger, Kyverno, Linkerd, OPA, OpenCost, OpenTelemetry, Prometheus, Tekton 입니다.
- 공식 커리큘럼은 낯선 도구가 나와도 시험 중에 제공되는 문서를 보고 다룰 수
있어야 한다고 못박고 있습니다. 그러니 도구 목록을 전부 외우는 것보다,
처음 보는 CRD 의 스키마를
kubectl explain으로 읽어 내는 손이 더 중요합니다. - 터미널 복사는
Ctrl+Shift+C, 붙여넣기는Ctrl+Shift+V입니다.
이 모의고사 환경에서 다른 점
이 실습의 클러스터는 파드 안에서 도는 1인용 클러스터입니다. kube-apiserver 와 컨트롤러 매니저와 스케줄러는 진짜라서 스키마 검증, 승인 정책, RBAC 판정, 쿼터, 스케줄링, 집계 ClusterRole, 축출 API 는 실제로 동작하고 실제로 거부합니다. 다만 컨테이너를 실제로 돌리는 런타임이 없으므로 다음이 다릅니다.
- Argo CD 와 Argo Rollouts 는 CRD 만 등록돼 있고 컨트롤러가 없습니다. 3번 과제의 AppProject 는 파일로만 평가합니다.
- 워크로드의
exec,logs,port-forward는 되지 않습니다. 파드는 뜨지만 안에서 프로세스가 돌지는 않습니다. - Prometheus 서버가 없습니다. 9번과 10번 과제는
promtool과 CRD 로 평가합니다. 실제 수집은 일어나지 않습니다.
노드는 3대이고 각각 CPU 8코어, 메모리 32Gi 이며 존은 zone-0, zone-1,
zone-2 로 하나씩 다릅니다.
단계
GitOps and Continuous Delivery
/root/exam/chart에 Helm 차트를 만드십시오. 차트 이름은paved-app이고 템플릿은 Deployment 하나이며 그 이름은paved-app입니다. 컨테이너 이름은app, 레플리카 수는.Values.replicas, 이미지는.Values.image.repository와.Values.image.digest를@로 이은 것입니다.values.schema.json으로replicas는 2 이상 10 이하의 정수,tier는bronze,silver,gold중 하나,image.digest는sha256:뒤에 16진수 64자리인 문자열만 허용하고 세 값 모두 필수로 만드십시오. 기본값은 각각 3,silver, 그리고 임의의 유효한 다이제스트입니다./root/exam/portal에 kustomize 디렉터리를 만드십시오. Deploymentportal은 레플리카 2, 컨테이너 이름app, 이미지는 다이제스트 고정이며,envFrom으로 ConfigMapportal-config를 통째로 읽습니다. 그 ConfigMap 은 파일로 두지 말고configMapGenerator로 만들되LOG_LEVEL=info와FEATURE_FLAGS=beta두 항목을 담고, 이름 해시 접미사를 끄지 마십시오. 렌더 결과에서 Deployment 가 참조하는 이름과 생성된 ConfigMap 의 이름이 같아야 합니다./root/exam/gitops/appproject.yaml에 Argo CD AppProjectplatform을 쓰십시오. 네임스페이스는argocd입니다.sourceRepos에는 실제 저장소 주소를 적고*를 쓰지 마십시오.destinations는 서버와 네임스페이스를 모두 못박고 네임스페이스에*를 쓰지 마십시오.clusterResourceWhitelist는 비워 두고,namespaceResourceBlacklist에는 코어 그룹의ResourceQuota와LimitRange를 넣으십시오./root/exam/promote를 git 저장소로 만드십시오.envs/staging/deployment.yaml과envs/prod/deployment.yaml두 파일이 있고 둘 다 Deploymentledger이며 컨테이너 이름은app, 이미지는 다이제스트로 고정합니다. 첫 커밋에서는 두 다이제스트가 서로 다릅니다. 그다음 스테이징의 다이제스트를 운영으로 올리는 승격 커밋 하나 를 더 쌓으십시오. 그 커밋에서 스테이징 파일은 바뀌지 않아야 하고, 커밋 메시지에 올린 다이제스트가 들어가야 하며, 작업 트리는 깨끗해야 합니다.
Platform APIs and Self-Service Capabilities
/root/exam/api/environment-crd.yaml에 CRD 를 쓰고 apply 하십시오. 그룹은platform.labhub.io, 종류는Environment, 복수형은environments, 짧은 이름은env, 범위는 Namespaced 입니다. 버전은 둘입니다.v1은 served 이자 저장 버전이고,spec.owner는 필수 문자열,spec.tier는bronze,silver,gold중 하나이며 기본값이bronze,spec.retentionDays는 1 이상 90 이하의 정수이며 기본값이7입니다.v1alpha1은 served 이지만 저장 버전이 아니고spec.owner만 가지며, 폐기 표시를 하고 폐기 경고 문구를 직접 적으십시오. 스키마에 없는 필드는 서버가 거절해야 합니다./root/exam/api/env-defaults.yaml에 Kyverno ClusterPolicyenv-defaults를 쓰십시오. Environment 리소스에만 적용되는 변형(mutate) 규칙으로, 라벨platform.labhub.io/owner를spec.owner값으로, 애너테이션platform.labhub.io/requested-tier를spec.tier값으로 채웁니다. 값을 고정해 적지 말고 요청에서 읽어 채워야 합니다./root/exam/api/env-rbac.yaml에 집계 권한을 쓰고 apply 하십시오. ClusterRoleplatform-env-author는 자체 규칙 없이 라벨platform.labhub.io/aggregate-to-env-author: "true"가 붙은 ClusterRole 을 모읍니다. ClusterRoleplatform-env-author-base에 그 라벨을 붙이고 Environment 에 대해 get, list, watch, create, update, patch 를 허용하되 delete 는 주지 마십시오. 네임스페이스tenant-amber와 ServiceAccountenv-author를 만들고 RoleBindingenv-author로 그 집계 ClusterRole 을 묶으십시오. 어느 규칙에도*를 쓰지 마십시오./root/exam/api/env-status-rbac.yaml에 spec 과 status 의 권한을 나누십시오.tenant-amber에 ServiceAccountenv-controller, Roleenv-controller, RoleBindingenv-controller를 만듭니다. 이 계정은 Environment 를 읽고environments/status를 update, patch 할 수 있지만 Environment 자체를 create, update, patch, delete 할 수는 없습니다. 반대로 7번의env-author는environments/status를 update 할 수 없어야 합니다.
Observability and Operations
/root/exam/ops/platform-rules.yaml에 Prometheus 규칙을 쓰십시오. 그룹 이름은platform-api이고, 기록 규칙platform:request_error:ratio5m은 나눗셈과 5분 구간을 쓰며, 알림 규칙PlatformApiErrorBudgetBurn에는for,labels.severity,annotations.summary,annotations.runbook_url이 있어야 합니다. 그리고/root/exam/ops/platform-rules-test.yaml에 규칙 유닛 테스트를 쓰십시오.rule_files에는 상대 이름platform-rules.yaml만 적고,alert_rule_test를 두 개 이상 두되 하나는 알림이 울리는 시각을, 다른 하나는 울리지 않는 시각(exp_alerts: [])을 확인해야 합니다.promtool check rules와promtool test rules가 모두 통과해야 합니다.- 네임스페이스
platform-system을 만들고/root/exam/ops/servicemonitor.yaml에 ServiceMonitorplatform-api를 써서 적용하십시오. 대상 선택자는app: platform-api,namespaceSelector는platform-system입니다. 엔드포인트는 하나이고 포트 이름은metrics, 수집 주기는30s, 수집 제한 시간은 주기보다 짧아야 합니다.metricRelabelings에는__name__을 보고 특정 지표를drop하는 규칙과, 고카디널리티 라벨을labeldrop으로 떨어뜨리는 규칙이 각각 하나 이상 있어야 합니다. - 네임스페이스
tenant-ochre를 만들어 라벨platform.labhub.io/tenant: ochre를 붙이고, ResourceQuotaochre-quota로requests.cpu를 2,requests.memory를 4Gi 로 제한하십시오. 그 안에 Deploymentsearch를 레플리카 4로 만드십시오. 컨테이너 이름은app, 이미지는 다이제스트 고정, 요청은 CPU 700m 과 메모리 512Mi 입니다. 이 시점에 파드는 다 뜨지 못합니다. 그다음/root/exam/ops/triage.txt에quota_cpu_hard,quota_memory_hard,desired_replicas,max_cpu_per_pod네 줄을키=값형식으로 적으십시오. CPU 는 밀리코어 정수로, 메모리는 Mi 정수로, 마지막 값은 이 쿼터 안에서 레플리카가 전부 뜨려면 파드 하나의 CPU 요청이 얼마 이하여야 하는지를 밀리코어 정수로 적습니다. 단위 문자는 붙이지 마십시오. - 쿼터를 올리지 말고
search의 파드 4개가 모두 Running 이 되게 하십시오. 레플리카 수와 메모리 요청은 그대로 두고, CPU 요청만 11번에서 계산한 값으로 내립니다. 옛 요청값을 쓰는 파드가 남아 있으면 안 됩니다.
Platform Architecture and Infrastructure
- 노드 정확히 두 대 에 라벨
platform.labhub.io/pool=platform과 테인트platform.labhub.io/pool=platform:NoSchedule을 함께 거십시오. 그리고platform-system에 Deploymentportal을 레플리카 3으로 만드십시오. 컨테이너 이름은app, 이미지는 다이제스트 고정, 요청은 CPU 100m 과 메모리 128Mi 입니다. 이 워크로드는 그 풀만 고르고, 그 테인트를 견디며,topology.kubernetes.io/zone을 기준으로maxSkew: 1과whenUnsatisfiable: DoNotSchedule로 퍼져야 합니다. 파드 3개가 모두 풀 노드 위에서 Running 이어야 합니다. - PriorityClass
platform-critical(값 100000 이상)과tenant-batch(값 1000 이하,preemptionPolicy: Never)를 만드십시오. 둘 다globalDefault는 false 입니다.tenant-amber에 ResourceQuotaamber-priority를 걸어platform-critical우선순위를 쓰는 파드를 그 네임스페이스에서 아예 만들 수 없게 하십시오. 그리고 13번의portal이platform-critical을 쓰게 하십시오. platform-system에 PodDisruptionBudgetportal을 만드십시오. 선택자는portal파드를 고르고minAvailable은 2 이며maxUnavailable은 쓰지 않습니다. 유지보수 때 한 번에 한 대만 비울 수 있어야 합니다.
Security and Policy Enforcement
- 네임스페이스
tenant-teal을 만들고 파드 보안 승인의enforce,audit,warn을 모두restricted로 걸되 세 모드의 버전 라벨은latest가 아니라v1.31로 고정하십시오. 그 안에 Deploymentcheckout을 레플리카 2로 만드십시오. 컨테이너 이름은app, 이미지는 다이제스트 고정이며 파드는restricted를 통과해야 합니다. 파드 2개가 실제로 Running 이어야 합니다. - 규칙 하나를 두 곳에서 집행하십시오. 규칙은 "Deployment 에는 라벨
platform.labhub.io/owner가 있어야 한다" 입니다./root/exam/security/owner-policy.yaml에 Kyverno ClusterPolicyrequire-owner를Enforce로 쓰십시오. 파이프라인이kyverno apply로 돌릴 게이트입니다./root/exam/security/owner-vap.yaml에는 ValidatingAdmissionPolicyrequire-owner와 같은 이름의 바인딩을 쓰고 apply 하십시오.validationActions는Deny이고, 적용 범위는 라벨platform.labhub.io/gate=owner가 붙은 네임스페이스뿐입니다. 네임스페이스delivery를 만들어 그 라벨을 붙이십시오. 라벨 없는 Deployment 는delivery에서 거부되고default에서는 통과해야 합니다.
참고
- 서버가 실제로 무엇을 하는지 볼 때는
kubectl apply --dry-run=server를 쓰십시오. 승인 체인을 그대로 태우면서 클러스터에는 아무것도 남기지 않습니다.-o json을 붙이면 기본값이 채워진 결과까지 볼 수 있습니다. - 서브리소스 권한은
kubectl auth can-i update environments.platform.labhub.io --subresource=status처럼--subresource로 물어야 합니다. 자원 이름 뒤에 슬래시를 붙여 적으면 엉뚱한 판정이 나옵니다. - CRD 를 적용한 직후에는
kubectl wait --for=condition=Established crd/...로 등록이 끝나기를 기다리십시오. - 쿼터가 꽉 찬 상태에서 롤링 업데이트를 걸면 새 파드를 만들 자리가 없어 배포가 멈춰 섭니다. 옛 파드를 먼저 비워야 새 파드가 들어갑니다.
- 흔한 실수 셋입니다.
labeldrop규칙에sourceLabels를 적으면 안 됩니다. RoleBinding 없이 ClusterRole 만 만들면 아무 권한도 생기지 않습니다. 그리고 집계 ClusterRole 의rules는 사람이 채우는 자리가 아니라 컨트롤러가 채우는 자리입니다.
차트가 잘못된 값을 스스로 거절하게
helm 은 차트 안에 values.schema.json 이 있으면 template 과 install 앞에서 값을 검증하고, 어긋나면 렌더 자체를 거절합니다. JSON 스키마의 minimum, maximum, enum, pattern, required 를 쓰십시오. 스키마가 걸리는지는 helm template ... --set replicas=1 처럼 일부러 어긋난 값을 넣어 확인합니다.
설정이 바뀌면 배포도 바뀌게
configMapGenerator 는 내용의 해시를 이름 뒤에 붙이고, 같은 kustomization 안에서 그 이름을 참조하는 곳을 자동으로 고쳐 씁니다. Deployment 에는 접미사 없는 원래 이름을 적어 두면 됩니다. disableNameSuffixHash 를 켜면 이 연결이 끊어지고, 설정만 바꿨을 때 파드가 다시 뜨지 않습니다.
프로젝트가 무엇을 허용하는지 못박기
AppProject 는 Application 이 무엇을 어디에 배포할 수 있는지 정하는 담장입니다. sourceRepos 와 destinations 에 별표를 남겨 두면 담장이 없는 것과 같습니다. clusterResourceWhitelist 를 비워 두면 클러스터 범위 자원을 아예 못 만들고, namespaceResourceBlacklist 는 네임스페이스 안이라도 손대지 못할 종류를 적는 자리입니다.
승격을 커밋 하나로 남기기
GitOps 에서 배포는 커밋입니다. 그래서 무엇이 언제 올라갔는지는 로그에 남아야 하고, 되돌리기는 revert 여야 합니다. 스테이징에서 검증한 다이제스트를 그대로 운영에 옮기되, 스테이징 파일은 건드리지 마십시오. 다 하고 나면 git status 가 비어 있어야 합니다. 커밋하지 않은 변경은 배포된 것이 아닙니다.
스키마가 계약이 되게
구조 스키마(structural schema)에 default 를 적으면 서버가 값을 채워 주고, 적지 않은 필드는 서버가 거절합니다. 두 성질이 함께 와야 스키마가 계약이 됩니다. 폐기는 versions 항목의 deprecated 와 deprecationWarning 으로 표시하며, 그 문구는 실제로 kubectl 화면에 Warning 으로 뜹니다. 확인은 kubectl apply --dry-run=server -o json 으로 하십시오.
요청에 빠진 것을 플랫폼이 채우기
셀프서비스는 사용자가 적어야 할 것이 적을수록 좋습니다. 사용자가 이미 적은 값에서 끌어낼 수 있는 것은 플랫폼이 채워야 합니다. Kyverno 의 mutate 규칙에서 {{ request.object... }} 로 요청 본문을 읽을 수 있습니다. 값을 고정해 적으면 채점기가 다른 요청으로 물었을 때 드러납니다. 확인은 kyverno apply <정책> --resource <파일> -o <디렉터리> 로 하십시오.
권한을 조각으로 나눠 모으기
집계 ClusterRole 은 자기 규칙을 적지 않습니다. 라벨 선택자만 적어 두면 컨트롤러가 그 라벨이 붙은 다른 ClusterRole 의 규칙을 모아서 채워 넣습니다. 그래서 나중에 권한을 늘릴 때 이 롤을 고치지 않고 조각을 하나 더 붙이면 됩니다. 집계가 실제로 됐는지는 kubectl get clusterrole <이름> -o jsonpath='{.rules}' 로 확인하십시오. 비어 있으면 라벨이 어긋난 것입니다.
적는 사람과 채우는 사람을 나누기
status 는 컨트롤러가 채우는 자리이고 spec 은 사용자가 적는 자리입니다. RBAC 에서 이 둘은 별개의 자원 이름이라 environments 와 environments/status 를 따로 적습니다. 확인할 때는 자원 이름 뒤에 슬래시를 붙이지 말고 --subresource=status 를 쓰십시오. 슬래시로 적으면 kubectl 이 엉뚱하게 해석해 반대 결과를 냅니다.
알림 규칙을 시험으로 못박기
promtool test rules 는 가짜 시계열을 넣고 규칙을 실제로 평가합니다. input_series 의 값은 '0+3x30' 처럼 시작값, 증가폭, 반복 횟수로 적습니다. 울리는 시각만 확인하면 언제나 울리는 규칙도 통과하므로, 울리지 않아야 할 시각을 exp_alerts: [] 로 함께 적으십시오. rule_files 는 테스트 파일이 있는 디렉터리를 기준으로 찾습니다.
무엇을 재지 않을지 정하기
관측 비용은 수집한 시계열의 수로 정해지고, 그 수는 라벨의 조합으로 폭발합니다. 그래서 수집 설정에는 무엇을 잴지만큼 무엇을 버릴지가 함께 들어가야 합니다. drop 은 sourceLabels 로 고른 값이 regex 에 맞으면 그 지표를 통째로 버리고, labeldrop 은 regex 에 맞는 라벨 이름을 지웁니다. labeldrop 에 sourceLabels 를 적으면 안 됩니다.
쿼터 소진을 재현하고 숫자로 적기
쿼터가 막으면 Deployment 는 오류를 내지 않습니다. ReplicaSet 이 대신 ReplicaFailure 조건을 달고, 파드는 애초에 만들어지지 않습니다. kubectl describe rs 나 kubectl get rs -o yaml 로 그 조건을 보십시오. 분류는 느낌이 아니라 나눗셈입니다. 쿼터 상한을 원하는 레플리카 수로 나누면 파드 하나가 쓸 수 있는 최대치가 나옵니다.
쿼터를 올리지 않고 복구하기
요청을 내리는 것만으로는 끝나지 않습니다. 쿼터가 꽉 찬 상태에서는 새 파드를 만들 자리가 없어 롤링 업데이트가 중간에서 멈춥니다. 옛 요청값을 쓰는 파드가 남아 있는 한 자리는 비지 않습니다. 레플리카를 0으로 내렸다가 다시 올리거나 옛 파드를 먼저 비우십시오. 확인은 파드 수가 아니라 파드마다의 요청값으로 하십시오.
풀을 나누고 워크로드를 퍼뜨리기
라벨은 고르게 하고 테인트는 밀어냅니다. 둘을 함께 걸어야 그 풀이 전용이 됩니다. 라벨만 걸면 아무나 들어오고, 테인트만 걸면 들어올 방법이 없습니다. topologySpreadConstraints 의 labelSelector 는 파드 라벨을 가리켜야 하고, DoNotSchedule 로 두어야 실제로 강제됩니다. 확인은 파드가 어느 노드에 앉았는지로 하십시오.
우선순위를 쓸 수 있는 사람 정하기
PriorityClass 는 클러스터 범위라 만들어 두면 누구나 이름으로 가져다 쓸 수 있습니다. 그래서 우선순위 자체가 아니라 그 우선순위를 쓸 권리를 따로 막아야 합니다. ResourceQuota 의 scopeSelector 에 PriorityClass 스코프를 걸고 상한을 0으로 두면 그 네임스페이스에서는 그 등급의 파드를 아예 만들 수 없습니다. preemptionPolicy: Never 는 자기가 밀려나지 않는다는 뜻이 아니라 남을 밀어내지 않는다는 뜻입니다.
유지보수 중에도 남아 있을 수를 정하기
PodDisruptionBudget 은 스케줄러가 아니라 축출 API 가 봅니다. kubectl drain 과 노드 업그레이드가 그 경로를 지납니다. 레플리카 3에 minAvailable 2 를 걸면 한 번에 한 대만 비울 수 있습니다. 컨트롤러가 계산한 결과는 kubectl get pdb 의 ALLOWED DISRUPTIONS 열에 나오고, 이 값이 0이면 유지보수가 아예 막히며 레플리카 수와 같으면 아무것도 지키지 않는 것입니다.
테넌트 네임스페이스에 기준선 걸기
파드 보안 승인은 Deployment 를 막지 않고 그 Deployment 가 만드는 파드를 막습니다. Deployment 는 만들어졌는데 파드가 없다면 kubectl describe rs 로 사유를 보십시오. restricted 는 파드 수준에 runAsNonRoot 와 seccompProfile 을, 컨테이너 수준에 allowPrivilegeEscalation: false 와 capabilities.drop [ALL] 을 요구합니다. 버전 라벨을 latest 로 두면 클러스터를 올릴 때 기준이 조용히 엄격해집니다.
같은 규칙을 두 곳에서 집행하기
파이프라인 게이트는 빠른 되먹임을 주지만 파이프라인을 거치지 않은 변경은 못 막고, 승인 정책은 무엇으로 들어오든 막지만 개발자는 배포를 눌러 본 뒤에야 압니다. 그래서 같은 규칙을 두 곳에 둡니다. VAP 의 적용 범위는 바인딩이 정하며, matchResources 의 namespaceSelector 로 좁힐 수 있습니다. validationActions 에 Deny 가 없으면 감사만 남기고 통과시킵니다.