CKAD模擬試験A
模擬試験です
実際のCKADは、120分で課題15–20個を解き、66%を超えれば合格です。このセットも、課題17個・120分・合格ライン66%に合わせました。部分点方式なので、すべて正解する必要はありません。17個のうち12個に合格すると、完了として扱われます。
ヒントを見ずに、まず最後まで解いてみてください。実際の試験にはヒントがありません。詰まった課題は飛ばして、時間が余ったら戻ってくるほうが、得点に有利です。ヒントと正解は、試験を一度終えたあとに、復習用として使ってください。
実際の試験会場で初めて知ると時間を失うこと
- 試験はリモートデスクトップで、課題ごとに指定されたホストに
sshして作業します。ネストしたsshはサポートされません。作業が終わったら、exitで必ず元の場所に戻ってください。 kエイリアスとbashの自動補完がすでに設定されています。開始してすぐにaliasを作れというアドバイスは、今の環境と合いません。このラボのPodも、同じように合わせてあります。yq・curl・wget・manもすでにインストールされています。- ターミナルのコピーは
Ctrl+Shift+C、貼り付けはCtrl+Shift+Vです。 Ctrl+Wはブラウザーのタブを閉じてしまいます。単語を消すときは、Ctrl+Alt+Wを使ってください。- INSERTキーが無効になっているため、vimは
iで入力モードに入る必要があります。 - 問題ごとに配点が異なり、正解に至る経路は複数が許可されます。
ドメイン配分
| ドメイン | 実際の配点 | このセットの課題 |
|---|---|---|
| Application Design and Build | 20% | 課題1–3 |
| Application Deployment | 20% | 課題4–7 |
| Application Observability and Maintenance | 15% | 課題8–10 |
| Application Environment, Configuration and Security | 25% | 課題11–14 |
| Services and Networking | 20% | 課題15–17 |
時間配分について
課題3(CronJob)は、コントローラーが実際に1回動くまで、最大1分かかります。先に作っておいて、他の課題を解いたあとで確認してください。課題4と課題5は、ロールアウトが終わるまで待つ必要があるので、kubectl rollout statusを使うほうが速いです。
この環境での違い
Pod内のクラスターは、kwokctlが起動した1人用のクラスターです。コントロールプレーンは本物なので、マニフェスト・RBAC・スケジューリング・クォータ・CRD・PVバインド・ドレインは、すべて実際に動作します。ただし、ワークロードのコンテナは実行されないため、kubectl exec・logs・port-forwardは使えません。課題もそれを要求しません。
開始する前にkubectl get nodesで、ノード3つがReadyかを確認してください。まだなら、クラスターが起動中です(2分ほどかかります)。
採点
各課題の「確認」ボタンを押すと、採点ツールが稼働中のクラスターで値を再計算して照合します。ファイルに何を書いたかではなく、クラスターがどんな状態かを見ます。正解に至る経路は複数が許可されます。
Initコンテナとサイドカー
ネームスペースappにPodloggerを作成してください。
- emptyDirボリューム
workを置き、すべてのコンテナが/workにマウントします。 - Initコンテナ
seedは、busybox:1.36で/work/seed.txtを作成します。 - コンテナ
appはnginx:1.27です。 - コンテナ
tailerは、busybox:1.36で/work/seed.txtをtailします。
Initコンテナは、spec.initContainersに別に書きます。ボリュームはPodに一度だけ宣言し、3つのコンテナがそれぞれvolumeMountsで使います。
完了回数を指定したJob
ネームスペースappにJobmigrateを作成してください。イメージはbusybox:1.36で、echo doneを実行します。
完了回数4、並列度2、backoffLimit 2、activeDeadlineSeconds 120、restartPolicyはNeverです。JobはCompleteで終わる必要があります。
Jobのcompletionsは、作成したあとで変更できません。値を間違えたなら、削除して作り直してください。restartPolicyはPodテンプレート側に書きます。
CronJob
ネームスペースappにCronJobreportを作成してください。イメージはbusybox:1.36でdateを実行し、restartPolicyはOnFailureです。
スケジュールは*/1 * * * *、concurrencyPolicyはForbid、startingDeadlineSecondsは30、成功の履歴は2つ、失敗の履歴は1つだけ残します。実際に1回以上実行される必要があります。
周期が1分なので、作成した直後はまだ実行の記録がありません。kubectl get cronjob report -n appのLAST SCHEDULE列に時刻が表示されるまで待ってから、採点してください。
ローリングアップデートでイメージを上げる
ネームスペースdeployに、Deploymentapiをnginx:1.27、レプリカ3つで作成してください。戦略はRollingUpdateで、maxSurge 2・maxUnavailable 0です。
そのあと、イメージをnginx:1.28に上げてください。削除して作り直さず、更新する必要があります。
kubectl set image deploy/api <컨테이너>=nginx:1.28(プレースホルダーはコンテナ名です)で更新すると、以前のReplicaSetが履歴として残ります。kubectl rollout statusで、終わるまで待ってください。
ロールバック
ネームスペースdeployに、Deploymentwebをnginx:1.27、レプリカ2つで作成してください。
そのあと、イメージをnginx:1.99-brokenに上げ、誤ったデプロイであることを確認してから、最初のリビジョンに戻してください。戻したあとも、問題のリビジョンは履歴に残っている必要があります。
kubectl rollout history deploy/webでリビジョン番号を確認してから、kubectl rollout undo deploy/web --to-revision=1を使います。削除して作り直すと、履歴が消えます。
ラベルで分けるカナリア
ネームスペースdeployに、Deploymentshop-stable(レプリカ4、Podラベルapp=shop・version=stable、イメージnginx:1.27)とshop-canary(レプリカ1、Podラベルapp=shop・version=canary、イメージnginx:1.28)を作成してください。2つのPodとも、コンテナポートは8080です。
Serviceshopは、ポート80で受けてポート8080に渡し、2つのDeploymentのPodを両方とも指す必要があります。
Serviceのセレクターにversionを入れると、片方しか選ばれません。2つのDeploymentが共通で持つラベル1つだけを、セレクターに使ってください。エンドポイントが5つあるかを確認すれば十分です。
kustomizeのオーバーレイ
/root/exam/kustomize/baseに、Deploymentcart(レプリカ1、イメージnginx:1.27、Podラベルapp=cart)とkustomizationを置いてください。
/root/exam/kustomize/overlayにオーバーレイを作成し、名前の前にprod-を付け、ネームスペースをdeploy-prodに、レプリカを3に、イメージタグを1.28に変更し、ラベルenv=prodを追加してください(セレクターには入れません)。
そのオーバーレイを、クラスターに適用してください。
kubectl kustomize <디렉터리>(プレースホルダーはディレクトリです)で結果をまず目で確認してから、kubectl apply -kで適用してください。セレクターは作成したあとで変更できないので、共通ラベルをセレクターに入れると、次の適用が拒否されます。
3種類のプローブ
ネームスペースobsにDeploymentcheckoutを作成してください。イメージはnginx:1.27、レプリカ2つ、コンテナポートは8080(名前http)です。
- startupProbe: httpGet
/startup:8080、failureThreshold 30、periodSeconds 5 - readinessProbe: httpGet
/ready:8080、initialDelaySeconds 5、periodSeconds 10 - livenessProbe: httpGet
/health:8080、periodSeconds 15、failureThreshold 3、timeoutSeconds 2
3つのプローブは、コンテナスペックの中に並べて書きます。値を1つでも落とすと既定値が入りますが、採点ツールは、問題に書かれた値をそのまま期待します。
Pod一覧をファイルに抽出する
ネームスペースobsで、ラベルapp=checkoutのPodを名前順に並べ替え、1行に<파드이름> <노드이름> <상태>(プレースホルダーはPod名、ノード名、状態です)の形式で、/root/exam/09-pods.txtに保存してください。ヘッダーは入れません。
採点ツールは、このファイルを採点時点のクラスターと照合します。あとでPodを作り直したなら、ファイルも作り直してください。
kubectl get pods -o jsonpathのrangeで必要なフィールドだけを取り出し、sortで並べ替えれば十分です。ノード名は.spec.nodeName、状態は.status.phaseです。
なくなったAPIバージョンへの移行
次のマニフェストは、現在のクラスターが提供していないAPIバージョンで書かれています。現在のバージョンに移行して、ネームスペースobsに適用してください。必要なフィールドが増えているなら、埋める必要があります。
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata: {name: obs-pdb}
spec:
minAvailable: 1
---
apiVersion: batch/v1beta1
kind: CronJob
metadata: {name: obs-cron}
spec:
schedule: "0 * * * *"
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers: [{name: obs, image: busybox:1.36, command: ["sh","-c","date"]}]
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata: {name: obs-ing}
spec:
rules:
- host: obs.example.com
http:
paths:
- path: /
backend:
serviceName: checkout
servicePort: 80
Ingressが指すServicecheckout(ポート80 → コンテナポートhttp)と、IngressClassnginx(controller k8s.io/ingress-nginx)も一緒に作成し、Ingressにそのクラスを指定してください。PDBのセレクターはapp=checkoutです。
kubectl explain <리소스> --recursive(プレースホルダーはリソース名です)で、現在のバージョンのフィールド名を確認できます。policy/v1のPDBはselectorが必須で、networking.k8s.io/v1のIngressはpathTypeとbackend.service構造を要求します。
設定とシークレット値の注入
ネームスペースcfgにConfigMapapp-configを作成してください。キーはAPP_MODE=production、LOG_LEVEL=info、そしてファイル用のキーapp.properties(内容は自由)です。
Secretapp-secret(Opaque)に、キーAPI_TOKENを置いてください。値は自分で決めてください。
Deploymentsvc(イメージnginx:1.27、レプリカ1)は、3つの方式をすべて使います。
envFromで、ConfigMap全体を環境変数として受け取ります。- 環境変数
API_TOKENは、Secretの同じ名前のキーを参照します(平文では書きません)。 - ConfigMapをボリュームとしてマウントしますが、
subPathでapp.properties1つだけを/etc/app/app.propertiesに置きます。
subPathを使うと、ディレクトリではなくファイル1つだけがそのパスに置かれます。mountPathにファイルパスを書き、subPathにキー名を書きます。値を--from-literalで作成すると、base64エンコードはkubectlが自動で行います。
securityContextで絞る
ネームスペースcfgにDeploymenthardenedを作成してください。イメージはnginx:1.27、レプリカ1つです。
Podレベルは、runAsUser 10001、runAsGroup 10001、fsGroup 10001、runAsNonRoot true、seccompProfileはRuntimeDefaultにします。
コンテナレベルは、allowPrivilegeEscalation false、readOnlyRootFilesystem true、capabilitiesはすべてdropして、NET_BIND_SERVICEだけをaddします。
Podレベルとコンテナレベルは、別の場所にあります。capabilitiesとallowPrivilegeEscalation・readOnlyRootFilesystemは、コンテナ側にだけあります。
アプリケーション専用のサービスアカウント
ネームスペースcfgにサービスアカウントapp-saを作成してください。ただし、トークンが自動でマウントされないようにしてください。
Rolecm-readerは、coreグループのconfigmapsに対してget・listだけを許可します。RoleBindingcm-readerでバインドしてください。
Deploymentreader(イメージnginx:1.27、レプリカ1)が、そのサービスアカウントで動くようにしてください。
automountServiceAccountTokenは、ServiceAccountオブジェクトの最上位のフィールドです(specの中ではありません)。Deploymentでは、PodスペックのserviceAccountNameで指定します。
制限の中で動くワークロード
ネームスペースcfg-quotaにLimitRangecfg-limitsを置いてください。コンテナ基準で、max cpu 500m・memory 512Mi、min cpu 50m・memory 64Mi、default cpu 250m・memory 256Mi、defaultRequest cpu 100m・memory 128Miです。
ResourceQuotacfg-quotaの上限は、pods 10、requests.cpu 1、requests.memory 1Gi、limits.cpu 2、limits.memory 2Giです。
Deploymentsized(イメージnginx:1.27、レプリカ2)は、requests cpu 100m・memory 128Mi、limits cpu 250m・memory 256Miで、2つのPodがどちらも起動する必要があります。
LimitRangeのmaxを超える値は、アドミッションで拒否されます。クォータが実際にカウントしているかは、kubectl describe quota cfg-quota -n cfg-quotaのUsed列で確認してください。
ClusterIPとNodePort
ネームスペースnetにDeploymentwebを作成してください。イメージはnginx:1.27、レプリカ3つ、コンテナポートは8080で、名前はhttpです。
Servicewebは、ClusterIPでポート80を受け、ポート名httpに渡します。Serviceweb-nodeportは、NodePortで同じPodを指し、ポート80を受け、ノードポートは30080です。
2つのServiceとも、targetPortには数字ではなく名前を書く必要があります。nodePortは、30000–32767の範囲でのみ直接指定できます。
2つのパスを分けるIngress
ネームスペースnetにDeploymentadmin(イメージnginx:1.27、レプリカ1、コンテナポート8080で名前はhttp)とServiceadmin(ポート80 → http)を作成してください。
IngressClassnginx(controller k8s.io/ingress-nginx)を作成し、Ingressnet-ingで、ホストapp.example.comの/はweb:80に、/adminはadmin:80に送ってください。2つのパスとも、pathTypeはPrefixです。
パスが重なるときは、より具体的なものが先にマッチしますが、採点は2つのルールが正確に書かれているかだけを見ます。バックエンドのServiceが実際にあるかも確認してください。
遮断と許可の3つのポリシー
ネームスペースnetに、NetworkPolicyを3つ作成してください。
deny-all: ネームスペースのすべてのPodについて、入ってくるトラフィックと出ていくトラフィックの両方を遮断します。allow-dns: ネームスペースのすべてのPodが、kube-systemネームスペースへ出ていくUDP 53とTCP 53を許可します。allow-web:app=webのPodについて、app=clientのPodから来るTCP 8080を許可します。
egressをすべて遮断すると、DNSも遮断されて、Podがどんな名前も解決できなくなります。DNSはUDPだけを使うのではなく、応答が大きいとTCPに切り替わるので、両方を開ける必要があります。ネームスペースセレクターは、kubernetes.io/metadata.nameラベルで選べば十分です。