アプリを起動できたら、次は新しいバージョンへ更新したい。 接続先の名前は変えずに使いたいし、設定値やパスワードも渡す必要があります。
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
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 を超えることもあります。
下で数を変えて進めてみてください。
- v1
- v1
- v1
- v1
tick 0 開始。全部が旧バージョン。
1/4開始。全部が旧バージョン。
既定は maxSurge 25% / maxUnavailable 25% (端数は surge が切り上げ、unavailable が切り捨て)。ここでは個数で指定しています。
maxSurge: 0 にすると、先に古い Pod を消してから新しい Pod を作るので、容量が一時的に減ります。
maxUnavailable: 0 にすると、先に新しい Pod を作ってから古い Pod を消すので、ノードに余裕が要ります。
ドキュメントによると、どちらの値も「もう一方が 0 のときは 0 にできない」ので、両方 0 は apiserver に拒否されます。
実機でも見ます。
前の章の web 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=180skubectl 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 に安定した名前を
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 だったとします (数字は例です)。
- クライアントは、クラスタの DNS (CoreDNS) に
webを問い合わせ、10.96.12.34という答えを受け取る。CoreDNS は「Kubernetes API を watch して、新しい Service ごとに DNS レコードを作る」DNS サーバーです。 - クライアントは
10.96.12.34の 80 番ポートに向けて、最初のパケットを送る。 - このパケットがクライアントのいるノードのカーネルを通るとき、kube-proxy が書いたルール (この教材のクラスタでは、Cilium がカーネルに入れた eBPF プログラム) が宛先を調べる。
10.96.12.34:80宛なら、宛先を10.0.1.17:80か10.0.2.23:80のどちらかに書き換える。 - 書き換わったパケットが、その 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 で扱います。
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
200ConfigMap と Secret: イメージの外にある可変部分
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ネームスペース (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 で別途仕切ります。
参考
- Kubernetes ドキュメント「Pods」「Pod Lifecycle」: https://kubernetes.io/docs/concepts/workloads/pods/ , https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/
- Kubernetes ドキュメント「Deployments」: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/
- Kubernetes ドキュメント「ReplicaSet」: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/
- Kubernetes ドキュメント「Service」: https://kubernetes.io/docs/concepts/services-networking/service/
- Kubernetes ドキュメント「Virtual IPs and Service Proxies」: https://kubernetes.io/docs/reference/networking/virtual-ips/
- Kubernetes ドキュメント「ConfigMaps」: https://kubernetes.io/docs/concepts/configuration/configmap/
- Kubernetes ドキュメント「Secrets」: https://kubernetes.io/docs/concepts/configuration/secret/
- Kubernetes ドキュメント「Namespaces」: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/