CKA模擬試験B
한국어 원문으로 표시합니다.
두 번째 세트입니다
이것은 B 회차이고, A 회차와 겹치는 과제가 하나도 없습니다. 도메인 배분은 A 와 똑같이 맞추었지만 묻는 역량은 전부 다릅니다. A 가 네임스페이스 범위 Role 을 물었다면 여기서는 클러스터 범위 ClusterRole 과 서비스어카운트 토큰을 묻고, A 가 테인트와 톨러레이션을 물었다면 여기서는 영역별 분산과 우선순위를 묻는 식입니다. 한 번 풀어 본 문제를 다시 푸는 것은 연습이 되지 않으므로, A 를 끝낸 다음에 이 세트로 다시 한 번 시간을 재고 치러 보십시오.
실제 CKA 는 120분에 과제 15~20개를 풀고 66% 를 넘으면 합격합니다. 이 세트도 과제 17개·120분·합격선 66% 로 맞추었습니다. 부분 점수제이므로 전부 맞힐 필요가 없습니다. 17개 중 12개를 통과하면 완료로 처리됩니다.
힌트를 보지 말고 먼저 끝까지 풀어 보십시오. 실제 시험에는 힌트가 없습니다. 막히는 과제는 건너뛰었다가 시간이 남으면 돌아오는 편이 점수에 유리합니다. 힌트와 정답은 시험을 한 번 끝낸 뒤에 복습용으로 쓰십시오.
실제 시험장에서 처음 알면 시간을 잃는 것들
- 시험은 원격 데스크톱이고, 과제마다 지정된 호스트로
ssh해서 작업합니다. 중첩 ssh 는 지원되지 않습니다. 작업이 끝나면exit로 반드시 원래 자리로 돌아오십시오. k별칭과 bash 자동완성이 이미 설정되어 있습니다. 시작하자마자 alias 부터 만들라는 조언은 지금 환경과 맞지 않습니다. 이 실습 파드도 같게 맞추어 두었습니다.yq·helm·etcdctl·openssl·curl·man도 이미 깔려 있습니다.- 터미널 복사는
Ctrl+Shift+C, 붙여넣기는Ctrl+Shift+V입니다. Ctrl+W는 브라우저 탭을 닫아 버립니다. 단어를 지울 때는Ctrl+Alt+W를 쓰십시오.- INSERT 키가 막혀 있어 vim 은
i로 입력 모드에 들어가야 합니다. - 문항마다 배점이 다르고, 정답에 이르는 경로는 여러 가지가 허용됩니다.
도메인 배분
| 도메인 | 실제 배점 | 이 세트의 과제 |
|---|---|---|
| Cluster Architecture, Installation and Configuration | 25% | 1~4번 |
| Workloads and Scheduling | 15% | 5~7번 |
| Services and Networking | 20% | 8~10번 |
| Storage | 10% | 11~12번 |
| Troubleshooting | 30% | 13~17번 |
13번부터 17번까지
실제 시험에서는 고장 난 자원이 미리 준비되어 있습니다. 이 환경에는 그 장치가 없으므로
각 과제의 지시문에 문제 매니페스트를 넣어 두었습니다. 먼저 그대로 적용한 다음
증상을 진단하고 고치십시오. 적용하지 않고 처음부터 옳게 만들어도 채점은 통과하지만,
그러면 진단 연습이 되지 않습니다. 13번부터 16번까지는 네임스페이스 broken 을,
17번은 네임스페이스 audit 을 미리 만들어 두어야 매니페스트가 들어갑니다.
파일을 만드는 과제
3번은 /root/exam 아래에 파일을 남깁니다. 디렉터리는 미리 만들어져 있지 않으므로
먼저 만드십시오. 채점기는 파일에 적힌 값을 그대로 믿지 않고 클러스터와 스냅샷에서
값을 다시 계산해 대조합니다.
이 환경에서 다른 점
파드 안의 클러스터는 kwokctl 이 띄운 1인용 클러스터입니다. 컨트롤 플레인은 진짜라서
매니페스트·RBAC·스케줄링·쿼터·CRD·PV 바인딩·드레인은 전부 실제로 동작합니다.
다만 워크로드 컨테이너는 실행되지 않으므로 kubectl exec·logs·port-forward 는
쓸 수 없습니다. 과제도 그것을 요구하지 않습니다. 인터넷도 막혀 있으므로 새 이미지를
받아 오거나 차트 저장소에서 내려받는 일은 할 수 없습니다.
시작 전에 kubectl get nodes 로 노드 3개가 Ready 인지 확인하십시오. 아직이라면
클러스터가 뜨는 중입니다(2분쯤 걸립니다).
채점
각 과제의 확인 버튼을 누르면 채점기가 살아 있는 클러스터에서 값을 다시 계산해 대조합니다. 파일에 무엇을 적었는지가 아니라 클러스터가 어떤 상태인지를 봅니다. 정답에 이르는 경로는 여러 가지가 허용됩니다.
클러스터 범위 읽기 전용 계정
네임스페이스 infra 를 만들고 서비스어카운트 auditor 를 두십시오. 이 서비스어카운트를 쓰는 파드에는 토큰이 자동으로 마운트되지 않아야 합니다.
ClusterRole infra-reader 는 core 그룹의 nodes 와 persistentvolumes, storage.k8s.io 그룹의 storageclasses 에 get·list·watch 를 허용합니다. 그 밖의 권한은 없어야 합니다. ClusterRoleBinding infra-reader 로 그 서비스어카운트에 묶으십시오.
토큰 자동 마운트는 서비스어카운트의 automountServiceAccountToken 으로 끕니다. 권한이 넓어지는 실수는 허용된 것만 확인해서는 드러나지 않으므로, kubectl auth can-i ... --as=system:serviceaccount:infra:auditor 로 되면 안 되는 동사도 함께 물어보십시오. 클러스터 범위 리소스는 네임스페이스를 붙이지 않고 묻습니다.
사용자 인증서 요청과 승인
새 팀원 dev 가 클러스터에 붙을 클라이언트 인증서를 받으려 합니다. CN 이 dev 인 인증서 요청을 만들어 CertificateSigningRequest dev 로 제출하십시오. signerName 은 kubernetes.io/kube-apiserver-client 이고 용도에는 client auth 가 들어가야 하며, 제출한 뒤 승인까지 끝내십시오.
이어서 네임스페이스 dev-team 에서 그 사용자가 파드를 get·list·watch 할 수 있도록 Role dev 와 RoleBinding dev 를 만드십시오. 그 밖의 권한은 없어야 합니다.
요청은 openssl 로 만들고, spec.request 에는 PEM 을 base64 로 한 줄(base64 -w0)로 넣습니다. 승인은 kubectl certificate approve 입니다. 바인딩의 주체 종류는 ServiceAccount 가 아니라 User 입니다.
etcd 스냅샷과 개정 번호
네임스페이스 ops 를 만들고, 그 안에 컨피그맵 pre-snapshot 을 두어 데이터 stage=before 를 넣으십시오.
그다음 이 클러스터 etcd 의 스냅샷을 /root/exam/etcd-snapshot.db 로 저장하고, 그 스냅샷이 담고 있는 개정(revision) 번호만 숫자로 한 줄, /root/exam/etcd-revision.txt 에 적으십시오.
이 클러스터의 etcd 는 127.0.0.1:2379 에서 인증 없이 받습니다. 스냅샷을 뜨는 일은 살아 있는 서버가 필요하고(etcdctl snapshot save), 뜬 파일의 메타데이터를 읽는 일은 파일만 있으면 됩니다(etcdutl snapshot status). 개정 번호는 그 메타데이터 안에 있으니 눈으로 옮겨 적지 말고 뽑아 쓰십시오.
Helm 차트로 릴리스 설치하기
네임스페이스 platform 에 Helm 릴리스 web 을 설치하십시오.
차트는 새로 만들어 /root/exam/charts/web 에 둡니다. 설치 결과로 뜨는 디플로이먼트는 복제본 3개에 이미지가 nginx:1.27 이어야 하고, 라벨 app.kubernetes.io/instance: web 이 붙어 있어야 합니다.
helm create 가 만들어 주는 기본 차트는 이미지 저장소가 nginx 이고 태그는 비어 있어 차트의 appVersion 을 씁니다. 복제본과 태그는 values 파일을 고쳐도 되고 설치할 때 --set 으로 덮어써도 됩니다. 인터넷이 없으므로 차트 저장소에서 받아올 수는 없습니다.
CPU 사용률로 늘고 주는 워크로드
네임스페이스 shop 에 Deployment cart 를 만드십시오. 이미지는 nginx:1.27, 복제본은 2개이고, 컨테이너의 requests.cpu 는 200m 입니다.
그 디플로이먼트를 대상으로 HorizontalPodAutoscaler cart 를 만드십시오. 최소 2개·최대 10개이고, CPU 평균 사용률 70% 를 목표로 하며, 줄일 때는 안정화 시간을 300초로 둡니다.
사용률(Utilization) 목표는 파드에 requests.cpu 가 있어야 계산됩니다. 요청량이 없으면 HPA 는 영원히 unknown 에 머뭅니다. 안정화 시간은 behavior.scaleDown 아래에 있고 이것은 autoscaling/v2 에만 있는 필드입니다.
영역별로 고르게 퍼뜨리기
네임스페이스 shop 에 Deployment feed 를 만드십시오. 이미지는 nginx:1.27, 복제본은 6개, 파드 라벨은 app=feed 입니다.
파드는 노드의 topology.kubernetes.io/zone 라벨 기준으로 고르게 퍼져야 합니다. 영역 사이 개수 차이는 1을 넘지 않아야 하고, 조건을 만족할 수 없으면 스케줄하지 않습니다.
topologySpreadConstraints 에는 네 가지를 적습니다. 무엇을 기준으로 나눌지(topologyKey), 얼마나 어긋나도 되는지(maxSkew), 못 지킬 때 어떻게 할지(whenUnsatisfiable), 그리고 어떤 파드끼리 세는지(labelSelector)입니다. 마지막 것을 빠뜨리면 세는 대상이 달라집니다.
우선순위와 선점 정책
PriorityClass 두 개를 만드십시오.
high-priority: 값 100000, 낮은 우선순위 파드를 밀어낼 수 있고, 설명(description)이 비어 있지 않아야 합니다.low-priority: 값 100, 자기보다 낮은 파드를 절대 밀어내지 않습니다.
둘 다 기본값 클래스가 되어서는 안 됩니다. 이어서 네임스페이스 shop 에 Deployment stream 을 만들어 nginx:1.27 을 복제본 2개로 돌리되, high-priority 를 쓰게 하십시오.
'밀어낼 수 있다' 와 '절대 밀어내지 않는다' 는 preemptionPolicy 로 갈립니다. 기본값 클래스 여부는 globalDefault 이고, 이것을 켜면 클래스를 적지 않은 파드까지 그 우선순위를 받습니다. 이미 뜬 파드의 우선순위는 나중에 바꿔도 반영되지 않습니다.
NodePort 서비스와 세션 고정
네임스페이스 edge 에 Deployment portal 을 만드십시오. 이미지는 nginx:1.27, 복제본 3개, 컨테이너 포트는 8080 입니다.
Service portal 은 NodePort 로 80 번을 받아 8080 으로 넘기고, 노드 포트는 30080 으로 고정합니다. 같은 클라이언트는 같은 파드로 가야 하며 그 유지 시간은 3600초입니다. 노드 밖에서 온 트래픽은 그 노드에 있는 파드로만 보내십시오.
'같은 클라이언트를 같은 파드로' 는 sessionAffinity 이고 유지 시간은 sessionAffinityConfig 아래에 따로 있습니다. '그 노드에 있는 파드로만' 은 externalTrafficPolicy 입니다. 셀렉터가 틀리면 오브젝트는 멀쩡히 만들어지고 엔드포인트만 비니 직접 확인하십시오.
나가는 트래픽 잠그기
네임스페이스 edge 의 모든 파드에 대해 나가는 트래픽을 기본으로 막는 NetworkPolicy default-deny-egress 를 만드십시오.
이어서 NetworkPolicy portal-egress 로 app=portal 파드에 한해 두 가지만 허용하십시오. 하나는 네임스페이스 kube-system 으로 나가는 53번(UDP 와 TCP 둘 다)이고, 다른 하나는 10.40.0.0/16 으로 나가는 TCP 5432 입니다. 다만 그 대역 중 10.40.9.0/24 는 제외합니다.
이름 해석이 막히면 나머지 허용도 소용이 없습니다. DNS 는 UDP 와 TCP 둘 다 열어야 합니다. 주소 범위는 ipBlock 으로 적고 그 안의 except 로 구멍을 냅니다. to 목록의 항목을 나누어 적으면 OR 입니다.
Gateway API 로 들어오는 길 만들기
GatewayClass labhub 을 만드십시오. controllerName 은 labhub.io/gateway 입니다.
네임스페이스 edge 에 Gateway edge-gw 를 만들어, 이름이 http 인 리스너로 80번 HTTP 를 받되 같은 네임스페이스의 라우트만 붙을 수 있게 하십시오. 이어서 HTTPRoute portal 로 호스트 portal.example.com 의 / 를 Service portal(8번에서 만든 서비스입니다)의 80번으로 보내십시오. 경로는 PathPrefix 로 맞춥니다.
Gateway API 는 쿠버네티스 기본 리소스가 아니라 CRD 로 들어오는 별도 표준입니다. 이 클러스터에는 CRD 만 있고 구현체는 없으므로 status 는 채워지지 않습니다. HTTPRoute 는 parentRefs 로 어느 Gateway 에 붙을지 스스로 밝히고, 백엔드는 backendRefs 에 이름과 포트로 적습니다.
노드에 묶인 로컬 볼륨
StorageClass local-node 를 만드십시오. provisioner 는 kubernetes.io/no-provisioner, reclaimPolicy 는 Retain 입니다.
PV pv-local-1 은 5Gi·ReadWriteOnce·local-node 이고, hostPath 가 아니라 local 볼륨으로 경로는 /mnt/local1 이며 노드 lab-node-2 에서만 쓸 수 있습니다. 네임스페이스 data 의 PVC records 는 5Gi·ReadWriteOnce·local-node 로 그 PV 에 묶이게 하십시오.
이어서 같은 네임스페이스에 Deployment archiver 를 만들어 nginx:1.27 을 복제본 1개로 돌리고 그 PVC 를 /records 에 마운트하십시오. 파드 스펙에는 노드를 지정하지 마십시오. 배치는 볼륨이 정하게 둡니다.
local 볼륨은 nodeAffinity 가 없으면 아예 만들어지지 않습니다. 그리고 파드가 그 볼륨에 묶이면 스케줄러가 볼륨의 노드 제약을 그대로 파드에 적용합니다. nodeSelector 를 쓰지 않아도 파드가 그 노드로 가는 이유가 그것입니다.
스테이트풀셋마다 따로 붙는 볼륨
네임스페이스 data 에 헤드리스 Service db 를 만드십시오. 포트는 5432 이고 클러스터 IP 는 갖지 않습니다.
이어서 StatefulSet db 를 만드십시오. 그 서비스를 쓰고, 이미지는 nginx:1.27, 복제본은 3개입니다. 파드마다 1Gi·ReadWriteOnce 볼륨이 따로 붙어야 하며, 그 볼륨 요청 틀의 이름은 data, 스토리지클래스는 db-static, 마운트 위치는 /var/lib/db 입니다.
이 클러스터에는 동적 프로비저너가 없으므로 볼륨은 미리 준비해야 합니다. 파드 세 개가 모두 Running 이 되어야 합니다.
volumeClaimTemplates 가 만드는 PVC 이름은 '볼륨 요청 틀 이름-스테이트풀셋 이름-번호' 입니다. 그 PVC 가 묶이지 못하면 파드는 0번부터 멈춰 서고 다음 파드는 아예 만들어지지 않습니다. 클래스가 같은 PV 를 파드 수만큼 미리 만들어 두십시오.
어느 노드에도 못 앉는 워크로드
먼저 네임스페이스 broken 을 만들고 아래 매니페스트를 그대로 적용하십시오.
apiVersion: apps/v1
kind: Deployment
metadata: {name: ingest, namespace: broken}
spec:
replicas: 2
selector: {matchLabels: {app: ingest}}
template:
metadata: {labels: {app: ingest}}
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- {key: disktype, operator: In, values: ["ssd"]}
containers:
- name: ingest
image: nginx:1.27
파드가 Pending 에서 벗어나지 못합니다. 이 워크로드는 빠른 디스크를 가진 노드에서 돌아야 한다는 요구가 옳으므로 파드 스펙은 그대로 두고 클러스터 쪽을 고쳐 파드 2개가 모두 Running 이 되게 하십시오.
스케줄러가 왜 거절했는지는 파드의 PodScheduled 조건에 문장으로 남습니다. required 조건은 '있으면 좋다' 가 아니라 '없으면 안 앉는다' 입니다. 노드가 무엇을 가졌는지는 라벨로 말합니다.
볼륨을 못 얻는 워크로드
먼저 아래 매니페스트를 그대로 적용하십시오.
apiVersion: v1
kind: PersistentVolume
metadata: {name: pv-archive-old}
spec:
capacity: {storage: 5Gi}
accessModes: ["ReadWriteOnce"]
persistentVolumeReclaimPolicy: Retain
storageClassName: standard
hostPath: {path: /mnt/archive-old}
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: archive, namespace: broken}
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: slow
resources: {requests: {storage: 8Gi}}
---
apiVersion: apps/v1
kind: Deployment
metadata: {name: vault, namespace: broken}
spec:
replicas: 1
selector: {matchLabels: {app: vault}}
template:
metadata: {labels: {app: vault}}
spec:
volumes:
- name: archive
persistentVolumeClaim: {claimName: archive}
containers:
- name: vault
image: nginx:1.27
volumeMounts:
- {name: archive, mountPath: /archive}
파드가 Pending 에서 벗어나지 못합니다. PVC archive 의 요청(8Gi·slow)은 옳은 요구이므로 그대로 두고, 파드가 Running 이 되게 하십시오.
PVC 가 Pending 이면 파드도 앉지 못합니다. 정적 볼륨에서 바인딩이 성립하려면 클래스 이름·접근 모드·용량 셋이 모두 맞아야 하고, 이미 있는 볼륨은 그중 두 가지가 어긋나 있습니다. 이 클러스터에는 동적 프로비저너가 없습니다.
파드가 하나도 만들어지지 않는 워크로드
먼저 아래 매니페스트를 그대로 적용하십시오.
apiVersion: apps/v1
kind: Deployment
metadata: {name: courier, namespace: broken}
spec:
replicas: 2
selector: {matchLabels: {app: courier}}
template:
metadata: {labels: {app: courier}}
spec:
serviceAccountName: courier
containers:
- name: courier
image: nginx:1.27
디플로이먼트는 만들어졌는데 파드가 하나도 생기지 않습니다. 이 워크로드는 전용 신원으로 돌아야 하므로 참조는 그대로 두고 파드 2개가 Running 이 되게 하십시오.
파드가 아예 만들어지지 않을 때는 파드가 아니라 그것을 만들려던 쪽을 봐야 합니다. kubectl describe rs -n broken 의 상태 조건에 어드미션이 거절한 문장이 그대로 남아 있습니다. 서비스어카운트는 네임스페이스마다 따로 있습니다.
바꿀 수 없는 셀렉터
먼저 아래 매니페스트를 그대로 적용하십시오.
apiVersion: apps/v1
kind: Deployment
metadata: {name: payments, namespace: broken}
spec:
replicas: 3
selector: {matchLabels: {app: payment}}
template:
metadata: {labels: {app: payment}}
spec:
containers:
- name: payments
image: nginx:1.27
이 팀의 규약에서 이 워크로드의 라벨은 app=payments 여야 하는데 단수형으로 잘못 나갔습니다. 디플로이먼트 이름은 payments 그대로 두고, 셀렉터와 파드 라벨을 app=payments 로 바꾸십시오. 복제본은 3개이며, 옛 라벨을 단 파드가 남아 있어서는 안 됩니다.
고쳐서 적용하려 하면 apiserver 가 거절합니다. 거절 문구가 어떤 필드를 두고 말하는지 읽어 보십시오. 그 필드는 만들 때 정해지고 나중에 바꿀 수 없으므로, 이름을 유지하면서 값을 바꾸려면 방법이 하나뿐입니다.
권한이 붙지 않는 바인딩
먼저 네임스페이스 audit 을 만들고 아래 매니페스트를 그대로 적용하십시오.
apiVersion: v1
kind: ServiceAccount
metadata: {name: reporter, namespace: audit}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: {name: reader, namespace: audit}
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: {name: reader, namespace: audit}
roleRef: {apiGroup: rbac.authorization.k8s.io, kind: Role, name: pod-reader}
subjects:
- {kind: User, name: reporter}
Role 을 만들어 두었는데도 서비스어카운트 reporter 는 파드를 못 읽습니다. 서비스어카운트와 Role reader 는 지우지 말고, 그 서비스어카운트가 네임스페이스 audit 안에서 파드를 get·list·watch 할 수 있게 하십시오. 그 밖의 권한은 어디에도 없어야 합니다.
틀린 곳이 둘입니다. 하나는 바인딩이 가리키는 역할 이름이고, 다른 하나는 주체의 종류입니다. 서비스어카운트의 이름은 reporter 지만 인증 이름은 그것과 다릅니다. 그리고 roleRef 는 만든 뒤에 바꿀 수 없습니다. 확인은 kubectl auth can-i --as=system:serviceaccount:audit:reporter 로 합니다.