securityContext、リソース、クォータ、QoS
目標
Podがノード上で何をできるか(securityContext、ServiceAccount)とどれだけ使えるか(resources、LimitRange、ResourceQuota)をマニフェストで決め、その結果QoSクラスがどのように決まるかを確認できるようになります。
なぜ重要なのか
コンテナはデフォルトでrootで動きます。イメージがそのように作られているからです。コンテナ脱出の脆弱性が1つでもあると、そのrootがノードのrootにつながりかねません。runAsNonRoot: trueはUID 0で起動しようとするコンテナをそもそも起動させず、capabilities.drop: ["ALL"]はカーネルが与える特権をすべて取り消します。Webサーバーがポート80を開く必要があるなら、NET_BIND_SERVICEだけを再度追加します。これが最小権限の原則の具体的な姿です。
リソース側は2つの層で理解します。requestsはスケジューラーの言語です。ノードに残っている割り当て可能量がrequestsの合計より大きいときに、Podが載ります。実際の使用量とは無関係です。limitsはノードの言語です。CPUはcgroupのクォータでスロットリングされ、メモリは超えた瞬間にOOMKilledになります。そのため、メモリのlimitsを実際のピークより低く設定すると、Podが静かに再起動を繰り返します。
LimitRangeとResourceQuotaは、必ずセットで理解する必要があります。クォータが設定されたネームスペースでrequests/limitsのないPodを作成すると、作成そのものが拒否されます。チームのネームスペースにクォータだけを設定しておくと何も起動しない事故が起きるので、LimitRangeでデフォルト値を補うことが事実上必須です。
ステップ
- ネームスペース
ckad-secureを作成し、その中にServiceAccountapp-saを作成してください。 - Pod
sa-podを作成してください。イメージはnginx:1.27、serviceAccountName: app-sa、automountServiceAccountToken: falseです。 - Pod
nonrootを作成してください。イメージはnginx:1.27、PodレベルのsecurityContextにrunAsNonRoot: true、runAsUser: 1000、fsGroup: 2000を設定してください。 - Pod
hardenedを作成してください。イメージはnginx:1.27、コンテナ名はapp、コンテナレベルのsecurityContextにallowPrivilegeEscalation: false、readOnlyRootFilesystem: true、capabilities.drop: ["ALL"]、capabilities.add: ["NET_BIND_SERVICE"]を設定してください。 - Deployment
apiを作成してください。レプリカは2、ラベルはapp=api、イメージはnginx:1.27、コンテナのresources.requestsはcpu: 100m/memory: 128Mi、resources.limitsはcpu: 500m/memory: 512Miです。 - LimitRange
defaultsを作成してください。type: Containerで、default(limits)はcpu: 200m/memory: 256Mi、defaultRequestはcpu: 100m/memory: 128Mi、maxはcpu: "1"/memory: 1Gi、minはcpu: 50m/memory: 64Miにしてください。 - ResourceQuota
team-quotaを作成してください。requests.cpu: "2"、requests.memory: 4Gi、limits.cpu: "4"、limits.memory: 8Gi、pods: "10"を設定してください。 - Podをさらに2つ作成してください。
qos-guaranteedはrequestsとlimitsがどちらもcpu: 250m/memory: 256Miで同じになるようにし、qos-burstableはrequestsだけをcpu: 100m/memory: 128Miにしてください。どちらもイメージはnginx:1.27です。
参考
kubectl create deployment api --image=nginx:1.27 --replicas=2 -n ckad-secure --dry-run=client -o yamlでひな形を出力してresourcesを手で埋めるか、作成後にkubectl set resources deployment api -n ckad-secure --requests=... --limits=...を使います。- ステップ7のあとは、このネームスペースにrequests/limitsのないPodを作成できません。前のステップのPodを作り直す場合は、LimitRangeがデフォルト値を補ってくれるか確認してください。
- よくある間違い1:
fsGroupやrunAsNonRootをコンテナの下に書くことです。fsGroupはPodレベルにしかありません。 - よくある間違い2:
capabilitiesをPodレベルに書くことです。コンテナレベルにしか存在しません。 - 確認:
kubectl get pod qos-guaranteed -n ckad-secure -o jsonpath='{.status.qosClass}'
ネームスペースとServiceAccountを作成する
ネームスペースckad-secureを作成し、その中にServiceAccount app-saを作成してください。
kubectl create serviceaccountで作成します。ネームスペースを先に作成し、その中に作成します。
PodにServiceAccountを指定し、トークンの自動マウントを無効にする
Pod sa-podを作成してください。イメージはnginx:1.27、serviceAccountName: app-sa、automountServiceAccountToken: falseです。
フィールド名はspec.serviceAccountNameです(serviceAccountは古い別名です)。トークンの自動マウントを無効にするブール値のフィールドも、Podのspecの最上位にあります。
PodレベルのsecurityContextを設定する
Pod nonrootを作成してください。イメージはnginx:1.27、PodレベルのsecurityContextにrunAsNonRoot: true、runAsUser: 1000、fsGroup: 2000を設定してください。
spec.securityContextに入るのは、Pod全体に適用される値です。ボリュームの所有グループを指定するフィールドは、Podレベルにしか存在しません。
コンテナレベルのsecurityContextとcapabilitiesを設定する
Pod hardenedを作成してください。イメージはnginx:1.27、コンテナ名はapp、コンテナレベルのsecurityContextにallowPrivilegeEscalation: false、readOnlyRootFilesystem: true、capabilities.drop: ["ALL"]、capabilities.add: ["NET_BIND_SERVICE"]を設定してください。
capabilitiesはコンテナレベルにしかありません。すべて削除してから必要なものだけを再度追加する方式が、最小権限の原則です。dropとaddは、それぞれ文字列の配列です。
Deploymentにrequestsとlimitsを指定する
Deployment apiを作成してください。レプリカは2、ラベルはapp=api、イメージはnginx:1.27、コンテナのresources.requestsはcpu: 100m / memory: 128Mi、resources.limitsはcpu: 500m / memory: 512Miです。
resourcesはコンテナの下にあります。requestsはスケジューラーが見る値、limitsはランタイムが強制する値です。CPUの単位mは1/1000コアです。
LimitRangeでデフォルト値と上下限を決める
LimitRange defaultsを作成してください。type: Containerで、default(limits)はcpu: 200m / memory: 256Mi、defaultRequestはcpu: 100m / memory: 128Mi、maxはcpu: "1" / memory: 1Gi、minはcpu: 50m / memory: 64Miにしてください。
spec.limitsは配列で、各要素にtypeがあります。デフォルトのlimitsを決めるキーとデフォルトのrequestsを決めるキーは名前が違うので、kubectl explain limitrange.spec.limitsで確認してください。
ResourceQuotaでネームスペースの総量を制限する
ResourceQuota team-quotaを作成してください。requests.cpu: "2"、requests.memory: 4Gi、limits.cpu: "4"、limits.memory: 8Gi、pods: "10"を設定してください。
spec.hardはマップで、キー名がrequests.cpu、limits.memoryのようにドットを含みます。個数の制限は、podsのように文字列の値で指定します。
QoSクラスを意図どおりに作成する(総合)
Podをさらに2つ作成してください。qos-guaranteedはrequestsとlimitsがどちらもcpu: 250m / memory: 256Miで同じになるようにし、qos-burstableはrequestsだけをcpu: 100m / memory: 128Miにしてください。どちらもイメージはnginx:1.27です。
QoSは直接指定するフィールドではなく、requests/limitsの組み合わせで決まる結果の値です。すべてのコンテナでCPU・メモリともにrequestsとlimitsが同じでなければ、最上位のクラスにはなりません。kubectl get pod -o jsonpath='{.status.qosClass}'で確認してください。