CKA模擬試験B
2つ目のセットです
これはB回で、A回と重なる課題は1つもありません。ドメインの配分はAとまったく同じに合わせましたが、問われる能力はすべて異なります。AがネームスペーススコープのRoleを問うたなら、ここではクラスタースコープのClusterRoleとサービスアカウントのトークンを問い、Aがテイントとトレラレーションを問うたなら、ここではゾーン別の分散と優先度を問う、という具合です。一度解いた問題をもう一度解いても練習にならないので、Aを終えてから、このセットで、もう一度時間を計って受けてみてください。
実際のCKAは、120分で課題15–20個を解き、66%を超えると合格です。このセットも、課題17個・120分・合格ライン66%に合わせました。部分点制なので、すべて正解する必要はありません。17個のうち12個を通過すると、完了として扱われます。
ヒントを見ずに、まず最後まで解いてみてください。実際の試験にはヒントがありません。詰まった課題は飛ばして、時間が余ったら戻ってくるほうが、点数に有利です。ヒントと正解は、試験を一度終えたあとの復習用に使ってください。
実際の試験会場で初めて知ると時間を失うこと
- 試験はリモートデスクトップで、課題ごとに指定されたホストに
sshして作業します。ネストしたsshはサポートされていません。作業が終わったら、exitで必ず元の場所に戻ってください。 kエイリアスとbashの自動補完がすでに設定されています。開始してすぐにaliasを作れというアドバイスは、今の環境に合いません。このラボのPodも、同じように設定してあります。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の下にファイルを残します。ディレクトリはあらかじめ作成されていないので、先に作成してください。採点ツールは、ファイルに書かれた値をそのまま信じず、クラスターとスナップショットから値を再計算して照合します。
この環境で異なる点
Pod内のクラスターは、kwokctlが起動した1人用のクラスターです。コントロールプレーンは本物なので、マニフェスト・RBAC・スケジューリング・クォータ・CRD・PVのバインド・ドレインは、すべて実際に動作します。ただし、ワークロードのコンテナは実行されないので、kubectl exec・logs・port-forwardは使えません。課題も、それを要求しません。インターネットも遮断されているので、新しいイメージを取得したり、チャートリポジトリからダウンロードしたりすることはできません。
開始前に、kubectl get nodesでノード3つがReadyかを確認してください。まだなら、クラスターが起動している最中です(2分ほどかかります)。
採点
各課題の確認ボタンを押すと、採点ツールが稼働中のクラスターで値を再計算して照合します。ファイルに何を書いたかではなく、クラスターがどんな状態かを見ます。正解に至る経路は、複数が許容されます。
クラスタースコープの読み取り専用アカウント
ネームスペースinfraを作成し、サービスアカウントauditorを置いてください。このサービスアカウントを使うPodには、トークンが自動でマウントされてはいけません。
ClusterRoleinfra-readerは、coreグループのnodesとpersistentvolumes、storage.k8s.ioグループのstorageclassesに対する、get・list・watchを許可します。それ以外の権限はない必要があります。ClusterRoleBindinginfra-readerで、そのサービスアカウントに結び付けてください。
トークンの自動マウントは、サービスアカウントのautomountServiceAccountTokenで無効にします。権限が広がるミスは、許可されたものだけを確認しても表に出ないので、kubectl auth can-i ... --as=system:serviceaccount:infra:auditorで、許可されてはいけない動詞も一緒に尋ねてください。クラスタースコープのリソースは、ネームスペースを付けずに尋ねます。
ユーザー証明書の要求と承認
新しいチームメンバーdevが、クラスターに接続するためのクライアント証明書を受け取ろうとしています。CNがdevである証明書の要求を作成し、CertificateSigningRequestdevとして提出してください。signerNameはkubernetes.io/kube-apiserver-clientで、用途にはclient authが入っている必要があり、提出したあと、承認まで終えてください。
続けて、ネームスペースdev-teamで、そのユーザーがPodをget・list・watchできるように、RoledevとRoleBindingdevを作成してください。それ以外の権限はない必要があります。
要求はopensslで作成し、spec.requestには、PEMをbase64で1行(base64 -w0)にして入れます。承認はkubectl certificate approveです。バインディングの主体の種類は、ServiceAccountではなくUserです。
etcdスナップショットとリビジョン番号
ネームスペースopsを作成し、その中にConfigMappre-snapshotを置いて、データstage=beforeを入れてください。
そのあと、このクラスターのetcdのスナップショットを/root/exam/etcd-snapshot.dbに保存し、そのスナップショットが含むリビジョン(revision)番号だけを、数値で1行、/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に置きます。インストールの結果として起動するDeploymentは、レプリカ3個で、イメージがnginx:1.27である必要があり、ラベルapp.kubernetes.io/instance: webが付いている必要があります。
helm createが作成してくれるデフォルトのチャートは、イメージのリポジトリがnginxで、タグは空のため、チャートのappVersionを使います。レプリカとタグは、valuesファイルを直してもよく、インストール時に--setで上書きしてもかまいません。インターネットがないので、チャートリポジトリから取得することはできません。
CPU使用率で増減するワークロード
ネームスペースshopにDeploymentcartを作成してください。イメージはnginx:1.27、レプリカは2個で、コンテナのrequests.cpuは200mです。
そのDeploymentを対象に、HorizontalPodAutoscalercartを作成してください。最小2個・最大10個で、CPUの平均使用率70%を目標とし、縮小するときの安定化時間を300秒にします。
使用率(Utilization)の目標は、Podにrequests.cpuがあってはじめて計算されます。要求量がないと、HPAは永遠にunknownのままです。安定化時間はbehavior.scaleDownの下にあり、これはautoscaling/v2にだけあるフィールドです。
ゾーンごとに均等に散らばらせる
ネームスペースshopにDeploymentfeedを作成してください。イメージはnginx:1.27、レプリカは6個、Podラベルはapp=feedです。
Podは、ノードのtopology.kubernetes.io/zoneラベルを基準に、均等に散らばる必要があります。ゾーン間の個数の差は1を超えてはならず、条件を満たせなければ、スケジュールしません。
topologySpreadConstraintsには、4つを書きます。何を基準に分けるか(topologyKey)、どれくらいずれてもよいか(maxSkew)、守れないときにどうするか(whenUnsatisfiable)、そして、どのPod同士で数えるか(labelSelector)です。最後のものを忘れると、数える対象が変わります。
優先度とプリエンプションポリシー
PriorityClassを2つ作成してください。
high-priority: 値は100000で、低い優先度のPodを押し出せて、説明(description)が空でない必要があります。low-priority: 値は100で、自分より低いPodを決して押し出しません。
どちらも、デフォルトのクラスになってはいけません。続けて、ネームスペースshopにDeploymentstreamを作成し、nginx:1.27をレプリカ2個で動かしますが、high-priorityを使うようにしてください。
「押し出せる」と「決して押し出さない」は、preemptionPolicyで分かれます。デフォルトのクラスかどうかはglobalDefaultで、これを有効にすると、クラスを書いていないPodまで、その優先度を受け取ります。すでに起動しているPodの優先度は、あとで変更しても反映されません。
NodePortサービスとセッション固定
ネームスペースedgeにDeploymentportalを作成してください。イメージはnginx:1.27、レプリカ3個、コンテナポートは8080です。
Serviceportalは、NodePortでポート80を受けて、ポート8080に転送し、ノードポートは30080に固定します。同じクライアントは同じPodに行く必要があり、その維持時間は3600秒です。ノードの外から来たトラフィックは、そのノードにあるPodにだけ送ってください。
「同じクライアントを同じPodへ」はsessionAffinityで、維持時間はsessionAffinityConfigの下に別にあります。「そのノードにあるPodにだけ」はexternalTrafficPolicyです。セレクターが間違っていると、オブジェクトは問題なく作成され、エンドポイントだけが空になるので、自分で確認してください。
外向きのトラフィックをロックする
ネームスペースedgeのすべてのPodに対して、外向きのトラフィックをデフォルトで遮断するNetworkPolicydefault-deny-egressを作成してください。
続けて、NetworkPolicyportal-egressで、app=portalのPodに限り、2つだけを許可してください。1つは、ネームスペースkube-systemへ出ていくポート53(UDPとTCPの両方)で、もう1つは、10.40.0.0/16へ出ていくTCP 5432です。ただし、その範囲のうち、10.40.9.0/24は除外します。
名前解決が塞がれると、残りの許可も意味がありません。DNSは、UDPとTCPの両方を開ける必要があります。アドレス範囲はipBlockで書き、その中のexceptで穴を開けます。toのリストの項目を分けて書くと、ORです。
Gateway APIで入ってくる道を作る
GatewayClasslabhubを作成してください。controllerNameはlabhub.io/gatewayです。
ネームスペースedgeにGatewayedge-gwを作成し、名前がhttpのリスナーでポート80のHTTPを受けますが、同じネームスペースのルートだけが付けられるようにしてください。続けて、HTTPRouteportalで、ホストportal.example.comの/を、Serviceportal(課題8で作成したサービスです)のポート80に送ってください。パスはPathPrefixで合わせます。
Gateway APIは、Kubernetesの標準リソースではなく、CRDとして入ってくる別の標準です。このクラスターにはCRDだけがあり、実装はないので、statusは埋まりません。HTTPRouteはparentRefsで、どのGatewayに付くかを自分で明かし、バックエンドはbackendRefsに、名前とポートで書きます。
ノードに結び付いたローカルボリューム
StorageClasslocal-nodeを作成してください。provisionerはkubernetes.io/no-provisioner、reclaimPolicyはRetainです。
PVpv-local-1は、5Gi・ReadWriteOnce・local-nodeで、hostPathではなくlocalボリュームで、パスは/mnt/local1、ノードlab-node-2でのみ使えます。ネームスペースdataのPVCrecordsを、5Gi・ReadWriteOnce・local-nodeで作成して、そのPVにバインドされるようにしてください。
続けて、同じネームスペースにDeploymentarchiverを作成して、nginx:1.27をレプリカ1個で動かし、そのPVCを/recordsにマウントしてください。Podのスペックでは、ノードを指定しないでください。配置は、ボリュームに決めさせます。
localボリュームは、nodeAffinityがなければ、そもそも作成されません。そして、Podがそのボリュームに結び付くと、スケジューラーは、ボリュームのノード制約を、そのままPodに適用します。nodeSelectorを使わなくても、Podがそのノードに行く理由が、それです。
StatefulSetごとに別々に付くボリューム
ネームスペースdataに、ヘッドレスServicedbを作成してください。ポートは5432で、クラスターIPは持ちません。
続けて、StatefulSetdbを作成してください。そのサービスを使い、イメージはnginx:1.27、レプリカは3個です。Podごとに1Gi・ReadWriteOnceのボリュームが別々に付く必要があり、そのボリューム要求のテンプレートの名前はdata、ストレージクラスはdb-static、マウント位置は/var/lib/dbです。
このクラスターには動的プロビジョナーがないので、ボリュームはあらかじめ用意しておく必要があります。3つのPodがすべてRunningになる必要があります。
volumeClaimTemplatesが作成するPVCの名前は、「ボリューム要求のテンプレート名-StatefulSet名-番号」です。そのPVCがバインドできないと、Podは序数0から止まり、次のPodはそもそも作成されません。クラスが同じPVを、Pod数の分だけ、あらかじめ作成しておいてください。
どのノードにも座れないワークロード
まず、ネームスペース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
PodがPendingから抜け出せません。このワークロードは、高速なディスクを持つノードで動くべきだという要求が正しいので、Podのスペックはそのままにして、クラスター側を直し、2つのPodがどちらもRunningになるようにしてください。
スケジューラーがなぜ拒否したかは、Podの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}
PodがPendingから抜け出せません。PVCarchiveの要求(8Gi・slow)は正しい要求なので、そのままにして、PodがRunningになるようにしてください。
PVCがPendingなら、Podも座れません。静的ボリュームでバインドが成立するには、クラス名・アクセスモード・容量の3つがすべて合っている必要があり、すでにあるボリュームは、そのうちの2つがずれています。このクラスターには、動的プロビジョナーがありません。
Podが1つも作成されないワークロード
まず、次のマニフェストをそのまま適用してください。
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
Deploymentは作成されたのに、Podが1つも生まれません。このワークロードは専用の身元で動く必要があるので、参照はそのままにして、2つのPodがRunningになるようにしてください。
Podがそもそも作成されないときは、Podではなく、それを作ろうとした側を見る必要があります。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である必要があるのに、単数形で誤って出てしまいました。Deploymentの名前はpaymentsのままにして、セレクターとPodラベルをapp=paymentsに変更してください。レプリカは3個で、古いラベルを付けたPodが残っていてはいけません。
直して適用しようとすると、apiserverが拒否します。拒否の文言が、どのフィールドについて言っているかを読んでみてください。そのフィールドは作成時に決まり、あとから変更できないので、名前を維持したまま値を変えるには、方法が1つしかありません。
権限が付かないバインディング
まず、ネームスペース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は、Podを読めません。サービスアカウントとRolereaderは削除せずに、そのサービスアカウントが、ネームスペースaudit内でPodをget・list・watchできるようにしてください。それ以外の権限は、どこにもない必要があります。
間違っている場所が2つあります。1つは、バインディングが指すロール名で、もう1つは、主体の種類です。サービスアカウントの名前はreporterですが、認証名はそれとは異なります。そして、roleRefは、作成したあとに変更できません。確認は、kubectl auth can-i --as=system:serviceaccount:audit:reporterで行います。