Kubernetes 解体新書Part 1 Kubernetes の全体像

アプリを動かす基本のリソース

アプリを更新し、接続先を用意し、設定を渡す。目的ごとに Kubernetes のリソースを使い分けます。

  • L1 Web ターミナル
  • 更新 2026年10月6日

アプリを起動できたら、次は新しいバージョンへ更新したい。 接続先の名前は変えずに使いたいし、設定値やパスワードも渡す必要があります。

Kubernetes には、それぞれの目的に合った設定の種類があります。 この章では、更新を担当する Deployment、接続先を用意する Service、設定を渡す ConfigMap と Secret を、1 つのアプリを動かす流れの中で見ていきます。

GOAL この章のゴール

  • Deployment でアプリを更新する流れを説明できる
  • Service で Pod の入れ替えに対応できる理由が分かる
  • 設定値と秘密情報を、用途に応じて Pod に渡せる

Pod: 配置の単位

2 章で、Kubernetes が数える単位は Pod だと説明しました。 ここで、もう少し詳しく見ます。 同じ Pod の中のコンテナは、1 つの IP と 1 つのネットワーク namespace を共有し、localhost で互いに届きます。 ストレージ (volume) も Pod 単位で共有できます。 多くの場合、Pod にはコンテナが 1 つしか入っていません。 ログを転送するエージェントやプロキシを横に置くとき (sidecar) に 2 つ以上になります。

Pod は使い捨てとして設計されています。 Kubernetes のドキュメントは「ある Pod (UID で区別される) が別のノードに『再スケジュール』されることは決して無く、代わりに、ほぼ同じ内容の新しい Pod に置き換えられる」と書いています。 ノードが落ちると、その上の Pod は消え、同じ Pod が別のノードで復活することはありません。 代わりの Pod を作るのは、5 章で見た ReplicaSet controller の仕事です。 Pod を直接 apply することもできますが、4 章で見たとおり、消えたら戻りません。 本番で裸の Pod を置く場面はほとんどありません。

Deployment → ReplicaSet → Pod

Deployment → ReplicaSet → Pod の入れ子。Deployment は世代ごとに ReplicaSet を持ち、ReplicaSet が Pod の数を保つ外側に Deployment (web)。その中に 2 つの ReplicaSet。新しい方 (v2) が Pod を 3 つ持ち、古い方 (v1) は replicas 0 で履歴として残る。Deployment: web (replicas: 3, image: nginx:1.29)世代管理とロールアウト戦略を持つ。Pod は直接作らない。ReplicaSet: web-7c9d8f6b5 (現在、replicas: 3)selector: app=web, pod-template-hash=7c9d8f6b5web-…-abcde10.244.0.12nginxweb-…-fghij10.244.0.13nginxweb-…-klmno10.244.0.14nginx数を保つ。消えたら作る、多ければ消す。イメージやラベルの変更はしない (template は不変)。ReplicaSet: web-5d4c7 (旧、replicas: 0)(無し)履歴として残るrollout undo でこちらを 3 に戻す
図 1Deployment → ReplicaSet → Pod の入れ子。Deployment は世代ごとに ReplicaSet を持ち、古い世代は replicas 0 で履歴として残る。

5 章で作った ReplicaSet は、「この template の Pod を N 個」を保ちます。 前の章で壊した reconciliation loop の主役です。 ただし、ReplicaSet の template を変更しても、既存の Pod は入れ替わりません。 イメージを nginx:1.29.0 から 1.30 に変えたいときは、別の ReplicaSet が要ります。

その「別の ReplicaSet を作り、古い方を縮め、新しい方を伸ばす」をやるのが Deployment です。 Kubernetes のドキュメントの言い方では、「Deployment の PodTemplateSpec を更新して Pod の新しい状態を宣言すると、新しい ReplicaSet が作られ、Deployment は古い ReplicaSet を縮めながら新しい ReplicaSet を段階的に伸ばす」ことになります。 古い ReplicaSet は消さずに残すので、kubectl rollout undo で戻せます。 残す数は revisionHistoryLimit で、既定は 10 です。 ドキュメントは「Deployment の改訂履歴は、それが管理する ReplicaSet に保存される」「この値を 0 にすると、新しいロールアウトを元に戻せなくなる」と書いています。 ドキュメントが挙げる Deployment の用途は、ReplicaSet のロールアウト、新しい状態への更新、前の改訂へのロールバック、台数の増減、更新の一時停止と再開、ロールアウトが止まっているかの確認、古い ReplicaSet の掃除の 7 つです。

Web サーバーのように同じ役割の Pod を更新しながら動かすなら、Deployment が基本です。 データの保存方法や実行の仕方に応じて、StatefulSet や Job なども使い分けます。 5 章で ReplicaSet を直接書いたのは reconciliation loop を見るためで、ドキュメントも「Deployment が所有する ReplicaSet を自分で管理しないこと」と注意しています。 AWS でいえば、Deployment が「起動テンプレートの世代を持つ Auto Scaling グループ」、ReplicaSet が「ある世代の台数維持」、Pod が「インスタンス」です。

ローリングアップデートの進み方

Deployment の既定の更新戦略は RollingUpdate です。 EC2 で 1 台ずつ入れ替えていた手順を、数の制約として表したものです。 進み方を決めるのは 2 つの数だけです。

  • maxSurge: replicas を超えて一時的に増やしてよい数 (既定 25%、割合からの換算は切り上げ)。
  • maxUnavailable: replicas を下回ってよい数 (既定 25%、割合からの換算は切り捨て)。

Deployment controller は、これらの数を使って新旧の Pod を増減させます。 新しい Pod が利用可能になったことを確かめてから、古い Pod を減らします。 minReadySeconds を指定した場合は、Ready になってからその秒数が経つまで待ちます。 ノード障害などによる停止まで防げるわけではなく、終了処理中の Pod を含めると一時的な総数が replicas + maxSurge を超えることもあります。 下で数を変えて進めてみてください。

総数 4 ≤ 5 (replicas + maxSurge)Ready 4 ≥ 3 (replicas − maxUnavailable)
旧 RS (v1)
  • v1
  • v1
  • v1
  • v1
新 RS (v2)

    tick 0 開始。全部が旧バージョン。

    1/4開始。全部が旧バージョン。

    既定は maxSurge 25% / maxUnavailable 25% (端数は surge が切り上げ、unavailable が切り捨て)。ここでは個数で指定しています。

    maxSurge: 0 にすると、先に古い Pod を消してから新しい Pod を作るので、容量が一時的に減ります。 maxUnavailable: 0 にすると、先に新しい Pod を作ってから古い Pod を消すので、ノードに余裕が要ります。 ドキュメントによると、どちらの値も「もう一方が 0 のときは 0 にできない」ので、両方 0 は apiserver に拒否されます。

    実機でも見ます。 前の章の web Deployment が残っていなければ、先に作ります。

    Deployment を用意する (無ければ)
    kubectl apply -f - <<'EOF'
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: web
    spec:
      replicas: 3
      selector:
        matchLabels: { app: web }
      template:
        metadata:
          labels: { app: web }
        spec:
          containers:
            - name: nginx
              image: nginx:1.29.0
    EOF
    kubectl rollout status deployment/web --timeout=180s
    イメージを変えて、ReplicaSet が入れ替わるのを見る
    kubectl set image deployment/web nginx=nginx:1.29.1
    kubectl rollout status deployment/web
    kubectl get replicasets -l app=web

    出力例

    deployment.apps/web image updated
    Waiting for deployment "web" rollout to finish: 1 out of 3 new replicas have been updated...
    deployment "web" successfully rolled out
    NAME             DESIRED   CURRENT   READY   AGE
    web-7c9d8f6b5    0         0         0       5m
    web-5d4c7b8f9    3         3         3       40s

    古い ReplicaSet が 0 0 0 で残っています。 名前の末尾 (5d4c7b8f9) は template のハッシュで、Pod のラベル pod-template-hash と一致します。 ドキュメントによると、このラベルは「Deployment controller が、作成または取り込んだすべての ReplicaSet に付けるもので、1 つの Deployment の子の ReplicaSet が重ならないことを保証する」ためにあります。 ReplicaSet はこのラベルで「自分の Pod」を見分けています。

    Service: 入れ替わる Pod に安定した名前を

    Service は、ラベルで選んだ Pod の集合に 1 つの安定した名前と IP を与える。Pod が入れ替わっても名前と IP は変わらない左にクライアント Pod。中央に Service web (ClusterIP 10.96.12.34、DNS 名 web.default.svc)。右に app=web のラベルを持つ Pod 3 つ。1 つは入れ替わり中。client10.244.0.20curlhttp://webService: webClusterIP 10.96.12.34web.default.svc.cluster.localselector: app=webapp=web10.244.0.12nginxapp=web10.244.0.13nginxapp=web10.244.0.31nginx入れ替わり(IP が変わる)宛先の一覧 (EndpointSlice) は controller が自動で更新する。クライアントは名前しか知らない。
    図 2Service はラベルで選んだ Pod に、1 つの安定した名前と IP を与える。Pod が入れ替わっても名前は変わらない。

    Pod は入れ替わるたびに IP が変わります。 Kubernetes のドキュメントは、この問題を「ある Pod の集まり (バックエンド) がクラスタ内の別の Pod (フロントエンド) に機能を提供するとき、フロントエンドはどの IP に接続すればよいかをどうやって知り、追い続けるのか」と立てています。 EC2 の前に ELB を置いて、クライアントが ELB の名前だけを知っていればよいようにしたのと同じ問題です。 Kubernetes では、この役を Serviceサービスラベルで選んだ Pod の集合に、クラスタの中で使える固定の IP アドレス (ClusterIP) と DNS 名を与えるリソース。用語集で見る が担います。 ドキュメントの定義では「Service API は、Pod の集まりをネットワーク越しに公開するための抽象で、各 Service オブジェクトはエンドポイント (通常は Pod) の論理的な集合と、それらへのアクセス方法の方針を定義する」ものです。 selector で選んだ Pod の集合に、1 つの ClusterIP と DNS 名 (web.default.svc.cluster.local) が付きます。 クライアントは名前だけを知っていればよく、宛先の Pod 一覧は「その Service の controller が selector に一致する Pod を探し続け、EndpointSlice の集合に必要な更新を行う」ことで自動で保たれます。 Service の見つけ方には環境変数と DNS の 2 つがあり、ドキュメントは「ほぼ常に DNS を使うべき」としています。

    ClusterIP 宛の通信が Pod に届くまでを、クライアント Pod が http://web に接続する場面で追います。 ClusterIP が 10.96.12.34、web の Pod が 10.0.1.17 と 10.0.2.23 だったとします (数字は例です)。

    1. クライアントは、クラスタの DNS (CoreDNS) に web を問い合わせ、10.96.12.34 という答えを受け取る。CoreDNS は「Kubernetes API を watch して、新しい Service ごとに DNS レコードを作る」DNS サーバーです。
    2. クライアントは 10.96.12.34 の 80 番ポートに向けて、最初のパケットを送る。
    3. このパケットがクライアントのいるノードのカーネルを通るとき、kube-proxy が書いたルール (この教材のクラスタでは、Cilium がカーネルに入れた eBPF プログラム) が宛先を調べる。10.96.12.34:80 宛なら、宛先を 10.0.1.17:80 か 10.0.2.23:80 のどちらかに書き換える。
    4. 書き換わったパケットが、その Pod に届く。返信は、同じ仕組みが送信元を 10.96.12.34 に戻して、クライアントに返す。

    この流れのどこにも、10.96.12.34 という IP を持つ NIC は出てきません。 ドキュメントの言い方では「固定の宛先に実際にルーティングされる Pod の IP と違い、Service の IP は単一のホストが応答するものではなく、kube-proxy がパケット処理のロジック (Linux の iptables など) を使って、必要に応じて透過的にリダイレクトされる仮想 IP アドレスを定義する」ものです。 Service には ClusterIP (既定) のほかに、各ノードの IP の固定ポートで受ける NodePort、外部のロードバランサーで公開する LoadBalancer、外部の DNS 名への CNAME になる ExternalName という種類があります。 クラスタの外から入るには、これらか Gateway API を使います。 仕組みの詳細は Part 2 で扱います。

    Service を作って名前で届くことを見る
    kubectl expose deployment web --port=80
    kubectl get svc web
    kubectl run -it --rm curl --image=curlimages/curl:8.11.1 --restart=Never -- curl -s -o /dev/null -w '%{http_code}\n' http://web

    出力例

    service/web exposed
    NAME   TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
    web    ClusterIP   10.96.12.34    <none>        80/TCP    1s
    200

    ConfigMap と Secret: イメージの外にある可変部分

    ConfigMap と Secret は、イメージの外にある可変部分を Pod に注入する。環境変数としても、ファイルとしても渡せる左に ConfigMap (設定値) と Secret (秘密情報)。右に Pod。ConfigMap から環境変数とファイルの 2 本、Secret から環境変数とファイルの 2 本の矢印。ConfigMap: web-configLOG_LEVEL: infonginx.conf: | server ...Secret: web-secretDB_PASSWORD: (base64)etcd 上は暗号化の設定次第。RBAC で絞るPod: web環境変数LOG_LEVEL=info DB_PASSWORD=…ファイル (volume mount)/etc/nginx/nginx.conf/var/run/secrets/db/passwordenvfiledocker run の -e と -v に相当。イメージは固定し、可変部分だけ外から差す。
    図 3ConfigMap と Secret は、環境変数としてもファイルとしても Pod に注入できる。docker run の -e と -v に相当する。

    2 章で、イメージは固定し、可変部分は外から注入する、と書きました。 その可変部分の置き場所が ConfigMapコンフィグマップ秘密でない設定値を key: value で持つリソース。環境変数、コマンド引数、またはファイルとして Pod に注入する。用語集で見る と Secretシークレットパスワードやトークンのような少量の秘密情報を持つリソース。ConfigMap と同じ形で注入できる。既定では etcd に暗号化されずに保存される。用語集で見る です。 ドキュメントの定義では、ConfigMap は「秘密でないデータを key-value で保存する API オブジェクトで、Pod は環境変数、コマンドライン引数、volume 上の設定ファイルとして ConfigMap を使える」もので、「環境ごとの設定をコンテナイメージから切り離せる」ことが目的です。 nginx の設定ファイルのように「わざわざボリュームを用意するほどではないもの」は ConfigMap のファイル注入が向いています。 ConfigMap は「大きなデータを持つようには設計されていない」ため、1 MiB の上限があります。

    Secret は「パスワード、トークン、鍵のような少量の機密データを持つオブジェクト」で、「ConfigMap に似ているが、機密データを持つことを明確に意図している」ものです。 値は data 欄に base64 で書きますが、base64 は暗号化ではありません。 ドキュメントは「Kubernetes の Secret は、既定では API サーバーの保存先 (etcd) に暗号化されずに保存される。API にアクセスできる人は誰でも Secret を取得、変更でき、etcd にアクセスできる人も同様である」と警告し、安全に使うための手順として、保存時の暗号化 (encryption at rest) の有効化、最小権限の RBAC、Secret にアクセスできるコンテナの限定、外部の Secret ストアの利用を挙げています。 クラウドの秘密管理 (AWS Secrets Manager など) から同期する External Secrets Operator のような仕組みは、この最後の項目にあたります。

    Namespace: 名前の仕切り

    Namespace は名前の衝突を避け、RBAC と ResourceQuota の単位になる仕切り。ネットワークは既定では仕切らない3 つの Namespace (kube-system、team-a、team-b)。team-a と team-b に同名の web という Deployment と Service がある。下に、既定ではネットワークは繋がる、という注記。Namespace: kube-systemcorednscilium-xxxxxクラスタの部品が住むNamespace: team-aDeployment: webService: webweb.team-a.svcquota: cpu 8, mem 16GiNamespace: team-bDeployment: webService: webweb.team-b.svcquota: cpu 4, mem 8Gi同じ名前 web が 2 つあっても衝突しない。RBAC で「team-a の人は team-a だけ」と絞れる。ただし team-a の Pod から web.team-b.svc には既定で届く (遮るのは NetworkPolicy の仕事)。
    図 4Namespace は名前の衝突を避け、RBAC と ResourceQuota の単位になる。ネットワークは既定では仕切らない。

    Namespaceネームスペース (Kubernetes)リソース名の仕切り。同名のリソースを別の Namespace に置ける。RBAC と ResourceQuota の単位になる。用語集で見る は、リソース名の仕切りです。 ドキュメントの定義では「1 つのクラスタの中でリソースの集まりを分離する仕組みで、リソースの名前は Namespace の中では一意でなければならないが、Namespace をまたいでは一意でなくてよい」ものです。 team-a と team-b の両方に web という Deployment を置けます。 RBAC (誰が何をしてよいかの権限設定) で「この人はこの Namespace だけ」と絞り、ResourceQuota で「この Namespace は CPU 8 コアまで」と区切る単位でもあります。 ドキュメントは「Namespace は、多くの利用者が複数のチームやプロジェクトに分かれている環境のためのもので、利用者が数人から数十人のクラスタでは、Namespace を作ることも考えることも要らないはず」とも書いています。

    クラスタには最初から 4 つの Namespace があります。 default (何も指定しないときの置き場所)、kube-system (Kubernetes 自身が作るオブジェクトの置き場所)、kube-public (認証していないクライアントにも読める)、kube-node-lease (ノードのハートビート用の Lease オブジェクト) です。 Service の DNS 名は <service>.<namespace>.svc.cluster.local の形なので、web とだけ書けば同じ Namespace の Service に解決され、別の Namespace に届くにはフルの名前を使います。

    1 章の Linux の namespace と名前が同じですが、別物です。 Linux の namespace はカーネルの隔離機能、Kubernetes の Namespace は API オブジェクトの名前の仕切りです。 AWS の VPC のような「ネットワークの仕切り」とも違い、team-a の Pod から web.team-b.svc には既定で届きます。 遮りたいときは、通信の許可ルールを書く NetworkPolicy を使います。

    後片付け

    次の章は読むだけなので、ここで消して構いません。 8 章のハンズオンでは最初から作り直します。

    作ったものを消す
    kubectl delete service web
    kubectl delete deployment web

    ふりかえり

    Qイメージを nginx:1.29.0 から 1.30 に変えたとき、新しい ReplicaSet を作るのは?

    ReplicaSet は template を変えません。Deployment controller が新しい ReplicaSet を作り、古い方を 0 に縮めます。

    QService の ClusterIP について正しいのは?

    ClusterIP 宛のパケットは、送信元のノードのカーネルで宛先を Pod の IP に書き換えられて届きます。その IP を持つ NIC はどこにもありません。

    QKubernetes の Namespace が既定で仕切らないものは?

    Namespace は名前と権限と quota の単位です。ネットワークは NetworkPolicy で別途仕切ります。

    参考