Kubernetes and Cloud Native Associate (KCNA) #3 Kubernetes Fundamentals 2: API、コンテナ、スケジューリング

#2 では control plane と worker node の構成要素、そして Pod・ReplicaSet・Deployment・Service・Namespace といった中核リソースを整理しました。クラスタの骨格と、その上で動くオブジェクトの種類をつかんだわけです。今回の記事は同じ Domain 1 (Kubernetes Fundamentals、46%) の後半として、それらのオブジェクトを実際に扱う方式を取り上げます。

具体的には三つの軸があります。一つ目は、すべてのリソースが通過する Kubernetes API の構造と、宣言型・命令型の操作方式です。二つ目は、Pod の中で実際に実行される コンテナと、イメージ・レジストリ・ランタイムの関係です。三つ目は、kube-scheduler が Pod をどのノードに置くかを決める スケジューリングの過程です。最後に ConfigMap と Secret で 設定を注入する方法までまとめます。この四つが Domain 1 の残り半分を埋めます。

Kubernetes API: すべてはオブジェクトである #

Kubernetes を一文で要約すると、巨大な API サーバーです。Pod を起動することも、Service を公開することも、ネームスペースを作ることも、すべて apiserver にオブジェクトを登録したり修正したりする行為です。そのため KCNA は、「Kubernetes で X をする」という言葉を「apiserver に X という API オブジェクトを送る」と読み替える感覚を要求します。

すべてのリソースは同じ 5 つの枠の構造を持つ #

Pod であれ Deployment であれ ConfigMap であれ、Kubernetes オブジェクトはほぼ常に同じ四つか五つの枠で構成されます。

フィールド役割
apiVersionこのオブジェクトが属する API group とバージョン (例: v1apps/v1)
kindオブジェクトの種類 (例: PodDeploymentService)
metadata名前・ネームスペース・label・annotation などの識別情報
specユーザーが望む状態 (desired state)。「こうなっているべきだ」
statusシステムが埋める現在の状態 (actual state)。ユーザーは書かない

ここで試験に最もよく出るポイントは、spec と status の区別です。spec はユーザーが宣言する目標であり、status はコントローラーが現在の状態を記録して埋める枠です。Kubernetes のコントローラーは常に statusspec に合わせようと働き、この終わりのない調整過程を reconciliation loop と呼びます。#2 で見た controller-manager が、まさにこのループを回す主体です。

次は Pod マニフェストの最小の形です。

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  namespace: default
spec:
  containers:
    - name: nginx
      image: nginx:1.27

apiVersion: v1kind: Podmetadata.name、そして spec の下のコンテナ定義が見えます。status はここにありません。ユーザーが spec だけ書いて送ると、apiserver が受け取って etcd に保存し、スケジューラと kubelet が仕事を進めたうえで status を埋めます。

API group とバージョン #

apiVersion の値が二つの形に分かれる点を押さえておくと、試験で混乱しません。

  • core groupv1 一つだけ書きます。Pod・Service・ConfigMap・Secret・Namespace といった最も基本的なリソースがここに属します。グループ名が空なので、バージョンだけ書きます。
  • named group<group>/<version> の形です。Deployment・ReplicaSet・StatefulSet・DaemonSet は apps/v1 に、Job・CronJob は batch/v1 に、Ingress は networking.k8s.io/v1 に属します。

バージョン表記には v1 (安定)、v1beta1 (ベータ)、v1alpha1 (アルファ) の成熟度段階があります。安定バージョンほど後方互換が保証されます。KCNA はすべての group を覚えろとは要求しませんが、Pod は core (v1)、Deployment は apps/v1 程度は覚えておくと選択肢の区別に役立ちます。

宣言型 vs 命令型 #

Kubernetes を操作する方式は大きく二つに分かれます。この二つの区別は KCNA の常連の出題ポイントです。

命令型 (imperative): 動作を直接指示する #

命令型は「この動作を今実行せよ」と直接指示する方式です。kubectl runkubectl createkubectl deletekubectl scale といったコマンドがここに属します。

kubectl run nginx --image=nginx:1.27
kubectl create deployment web --image=nginx:1.27
kubectl scale deployment web --replicas=3

速くて直観的ですが、何をしたかがファイルに残りません。同じ状態をもう一度作るにはコマンドをまた覚えて入力しなければならず、変更履歴を追跡しづらくなります。

宣言型 (declarative): 望む状態を宣言する #

宣言型は「最終的にこういう状態になるべきだ」を YAML マニフェストに書いておき、kubectl apply で適用する方式です。

kubectl apply -f deployment.yaml

マニフェスト自体が desired state であり、Kubernetes が現在の状態をその目標に合わせて調整します。同じファイルをもう一度 apply しても安全で (冪等性)、ファイルを Git に置けば変更履歴がそのまま残ります。この性質が #7 で扱う GitOps の土台になります。Kubernetes 運用の定石は 宣言型であり、試験でも「推奨される方式」を問われたら宣言型 (kubectl apply) が答えです。

区分命令型 (imperative)宣言型 (declarative)
代表的なコマンドkubectl runcreatescalekubectl apply -f
指定する対象実行する動作望む最終状態
履歴追跡難しいファイル / Git に残る
再実行衝突しうる冪等で安全
推奨かどうか素早い実習・デバッグ用運用標準

kubectl の正体 #

ここで kubectl の役割をはっきりさせておく必要があります。kubectl はクラスタの一部ではなく、apiserver と HTTP で通信するクライアントにすぎません。kubectl apply を実行すると kubectl が YAML を読んで apiserver に API リクエストを送り、以後のすべての調整は control plane の中で起こります。そのため KCNA は kubectl を「ユーザーが API を便利に呼び出せるよう助けるツール」と定義します。kubectl なしで curl で apiserver を直接呼び出しても結果は同じです。kubectl と Pod の基本操作は 実務トラック #3 で手を動かして扱っておくと、概念が固まります。

コンテナ: Pod の中で実際に動くもの #

#2 で Pod がスケジューリングの最小単位だと述べました。その Pod の中で実際に実行されるものが コンテナです。

イメージとレジストリ #

コンテナは イメージから作られます。イメージはアプリケーションのバイナリ・ライブラリ・ランタイムを一まとめに梱包した読み取り専用のパッケージであり、イメージの標準形式は OCI (Open Container Initiative) が定義します。イメージは レジストリに保存され配布されます。Docker Hub・GitHub Container Registry・Amazon ECR といった場所がレジストリです。

image: nginx:1.27nginx はイメージ名、1.27タグです。タグを省略すると latest がデフォルトで付きますが、latest は「最新」を保証せず、ただのタグ名にすぎないため、運用では明示的なバージョンタグが推奨されます。この点は #7 のデプロイ安定性の議論にもつながります。

コンテナランタイム #

イメージを受け取って実際のプロセスとして実行する主体が コンテナランタイムです。Kubernetes は特定のランタイムに縛られず、CRI (Container Runtime Interface) という標準インターフェースでランタイムと通信し、その場所に containerd や CRI-O が入ります。worker node の kubelet が CRI を通じてランタイムに「このイメージでコンテナを起動せよ」と指示します。ランタイムと CRI の詳しい階層構造は Domain 2 のテーマなので、#4 で深く扱います。ここでは Pod の中のコンテナはランタイムが実行し、kubelet が CRI でそのランタイムを操るという関係だけ押さえておけば十分です。

Pod の中にコンテナが複数あるとき #

Pod はコンテナを 1 個以上収められます。ほとんどは 1 個ですが、二種類の補助コンテナのパターンが試験に登場します。

  • init container。メインコンテナより 先に、順番どおり実行されて終わるコンテナです。設定ファイルのダウンロードや、依存サービスの待機といった準備作業を担います。すべての init container が成功してからメインコンテナが起動します。
  • sidecar。メインコンテナと 並んで実行され、ログ収集・プロキシといった補助機能を加えるコンテナです。Service Mesh のプロキシ注入が代表的な sidecar の事例であり、これは #4 で扱います。

同じ Pod の中のコンテナどうしは、同じネットワークネームスペース (同じ IP、localhost 通信) と同じボリュームを共有するという点が重要です。

スケジューリング: Pod をどのノードに置くか #

新しい Pod が生成されると、最初はどのノードにも配置されていない状態です。この Pod を どの worker node で実行するか決める仕事がスケジューリングであり、その決定を下す control plane コンポーネントが #2 で見た kube-scheduler です。

二段階: filtering と scoring #

kube-scheduler は二段階でノードを選びます。

  1. filtering (フィルタリング)。この Pod を実行 できないノードを除外します。資源 (CPU・メモリ) が不足したノードや、要求条件に合わないノードなどがここで除かれます。残ったノードを feasible node と呼びます。
  2. scoring (スコアリング)。残ったノードに点数を付けて 最も適したノードを選びます。資源が均等に分散するように、または特定のポリシーに合うように点数が計算されます。

filtering で生き残ったノードが一つもなければ、Pod は配置されず Pending 状態にとどまります。試験で「Pod が Pending である」という状況は、しばしば スケジューリング失敗 (資源不足または条件不一致) を指します。

資源要求 (resource requests) がスケジューリングを左右する #

スケジューリングの一つ目の基準は 資源です。Pod のコンテナに resources.requests で必要な CPU・メモリを書くと、スケジューラはその要求を満たす余裕のあるノードだけを候補に残します。

spec:
  containers:
    - name: app
      image: myapp:1.0
      resources:
        requests:
          cpu: "250m"
          memory: "256Mi"
        limits:
          cpu: "500m"
          memory: "512Mi"
  • requests。スケジューリングに使われる値です。「最低これだけは保証されるべきだ」という要求であり、スケジューラはこの値でノードの適合性を判断します。
  • limits。実行中に使える 上限です。スケジューリングではなくランタイムで強制され、メモリ limit を超えるとコンテナが OOM で終了することがあります。

requests はスケジューリング、limits は実行時の制限という区別がよく出題されます。

nodeSelector: 最も単純なノード指定 #

特定のノードにだけ Pod を置きたいとき、最も単純なツールが nodeSelector です。ノードに付いた label と正確に一致する場所にだけ配置します。

spec:
  nodeSelector:
    disktype: ssd

disktype=ssd label が付いたノードにだけ配置されます。条件が単純な一致一つだけなので表現力は弱いですが、その分わかりやすいです。

node affinity / anti-affinity: より柔軟なルール #

nodeSelector より豊かな条件を表現するには affinity を使います。

  • node affinity。「こういう label を持つノードを好む、または要求する」を表現します。requiredDuringScheduling... (必須) と preferredDuringScheduling... (選好) で強度を分けられるので、可能なら置くが無理なら他のノードも許す、といったルールが可能です。
  • pod affinity / anti-affinity。ノードの label ではなく 他の Pod の位置を基準にします。affinity は「特定の Pod と同じノード (または領域) に集めよ」、anti-affinity は「特定の Pod と引き離せ」を意味します。同じアプリのレプリカを互いに異なるノードに散らして可用性を高めるときに anti-affinity を使います。

KCNA のレベルでは、nodeSelector は単純な一致、affinity は選好 / 必須とより複雑なルールという程度の区別で十分です。

taint と toleration: ノードが Pod を押しのける #

ここまでは Pod がノードを選ぶ方向でした。taint と toleration は逆に、ノードが Pod を押しのけるメカニズムです。

  • taint はノードに付ける「目印」です。ノードに taint があると、基本的にそのノードには Pod がスケジューリングされません。ノードが Pod を拒否するわけです。
  • toleration は Pod に付ける「免除権」です。Pod が特定の taint に対する toleration を持つと、その taint が付いたノードにも配置されえます。

つまり taint (ノードの拒否) + toleration (Pod の許可) が対をなしてはじめて、当該ノードに Pod が入れます。control plane ノードに一般のワークロードが立たない理由は、まさに control plane ノードに taint がかかっているからです。専用 GPU ノードを特定の Pod にだけ割り当てる用途にも使われます。

ここで nodeSelector / affinity と taint / toleration の方向を取り違えないことが重要です。affinity 系は Pod がノードを引き寄せる (attract) 側であり、taint / toleration はノードが Pod を押しのける (repel) が、toleration を持つ Pod だけ受け入れる側です。この方向の区別が常連の落とし穴です。

設定の注入: ConfigMap と Secret #

アプリケーションの設定値をイメージの中に埋め込むと、環境ごとにイメージを作り直さなければなりません。Kubernetes は設定をコンテナの 外から注入する二つのリソースを提供します。

ConfigMap: 秘密ではない設定 #

ConfigMap は環境変数・設定ファイル・フラグのような 機微でない設定値をキー・バリューで収めるリソースです。

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  LOG_LEVEL: "info"
  APP_MODE: "production"

Secret: 機微な情報、ただし基本は暗号化ではない #

Secret はパスワード・トークン・証明書のような 機微な情報を収めるリソースで、構造は ConfigMap と似ています。ここで KCNA が必ず押さえる試験ポイントがあります。

Secret の値は base64 でエンコードされるだけで、基本的には暗号化されません。

base64 は誰でも即座にデコードできるエンコードであって暗号化ではありません。したがって Secret 自体が値を安全に保護すると錯覚してはいけません。実際に安全に保管するには、etcd への保存時に暗号化 (encryption at rest) を別途オンにするか、RBAC でアクセスを制限するか、外部の秘密管理ツールを連携させる必要があります。「Secret は基本的に暗号化されない」という文は試験にほぼ必ず出る落とし穴なので、そのまま覚えておくのがよいです。

コンテナに注入する二つの方法 #

ConfigMap であれ Secret であれ、コンテナに渡す方式は二つあります。

方式説明特徴
環境変数 (env)キー・バリューをコンテナの環境変数として注入単純。ただし実行中の変更は反映されない
ボリュームマウント (volume)ConfigMap / Secret をファイルとしてマウントファイル形式が必要なとき。変更が反映されうる

設定値一つ一つを環境変数として入れるか、設定ファイル全体をボリュームとしてマウントするかは、アプリケーションが設定をどう読むかにかかっています。ConfigMap と Secret の実際の使用例は 実務トラック #6 で手を動かして扱っておくと、概念が明確になります。

この記事の試験ポイント整理 #

Domain 1 後半で選択肢として頻繁に突かれる箇所です。

  • 宣言型 vs 命令型kubectl apply は宣言型 (望む状態を宣言、運用標準)、kubectl run/create は命令型 (動作を直接指示)。「推奨される方式」を問われたら宣言型です。
  • spec vs status。spec はユーザーが書く desired state、status はシステムが埋める actual state です。
  • apiVersion。Pod・Service・ConfigMap・Secret は core (v1)、Deployment 系は apps/v1 です。
  • requests vs limits。requests はスケジューリングの基準、limits は実行時の使用上限です。
  • affinity vs taint/toleration。affinity は Pod がノードを引き寄せ、taint/toleration はノードが Pod を押しのけるが toleration を持つ Pod だけ許します。
  • Secret は基本的に暗号化ではなく base64 エンコード。この一文が常連の落とし穴です。

まとめ #

今回の記事でつかんだもの:

  • Kubernetes API。すべてのリソースは apiVersion・kind・metadata・spec・status の同じ構造を持ち、コントローラーが status を spec に合わせて調整します。
  • 宣言型 / 命令型kubectl apply (宣言型、運用標準) と kubectl run/create (命令型) の区別です。kubectl は apiserver と通信するクライアントです。
  • コンテナ。イメージはレジストリに保存され、CRI を通じてランタイムが実行します。Pod はコンテナ 1 個以上を収め、init container・sidecar パターンが存在します。
  • スケジューリング。kube-scheduler が filtering 〜 scoring でノードを選びます。requests・nodeSelector・affinity・taint/toleration が決定に影響します。
  • 設定の注入。ConfigMap (秘密ではない)・Secret (機微、ただし基本は base64 エンコードであって暗号化ではない) を env または volume で注入します。

これで比重 46% の Domain 1 (Kubernetes Fundamentals) を #2 と今回の記事の二編で締めくくりました。最も大きいドメインを越えたので、合格に最も近い点数帯を確保したわけです。

次: Container Orchestration #

Kubernetes 自体の核心は整理しました。これからその下でコンテナを支える層へ降りていきます。

#4 Container Orchestration (22%): ランタイム、セキュリティ、ネットワーキング、ストレージ、Service Mesh では、コンテナランタイム (containerd・CRI-O) と CRI、セキュリティ (RBAC・NetworkPolicy)、ネットワーキング (CNI・Service・DNS)、ストレージ (CSI・PV・PVC)、そして Service Mesh まで、二番目に大きいドメインを整理します。

X