読書メモ:つくって、壊して、直して学ぶ Kubernetes 入門

読書メモ:つくって、壊して、直して学ぶ Kubernetes 入門

読書メモ。

動かなくっても、もう怖くない!トラブルシューティングを体験しながら学ぶ、実践的入門書。

本書は、Kubernetes の実践的な知識をハンズオン形式で解説する書籍です。本書の特徴は、壊れにくい Kubernetes をあえて壊しながら学ぶことで、初心者が挫折しやすいトラブルシューティングの知識や対応力が身に付けられることです。初心者でも、経験者でも、今度こそ Kubernetes がわかる!マンガや図解を多く掲載しているため視覚的に理解したい方にもおすすめです。


本書を読むにあたって #

本書で利用しているマニフェストやコードなどは以下のリポジトリにアップロードされています。

https://github.com/aoi1/bbf-kubernetes

Chapter 1: Docker コンテナをつくってみる #

docker run --rm --detach --publish 8080:80 --name web nginx:1.25.3

> Unable to find image 'nginx:1.25.3' locally
> 1.25.3: Pulling from library/nginx
> 13e7597894b3: Download complete
> bca0a94f9783: Download complete
> ad1f53eee07d: Download complete
> 2e49d9ae23c1: Download complete
> 47229761a693: Download complete
> b70338a86458: Download complete
> f546e941f15b: Download complete
> Digest: sha256:c7a6ad68be85142c7fe1089e48faa1e7c7166a194caa9180ddea66345876b9d2
> Status: Downloaded newer image for nginx:1.25.3
> af5f5deab3c951eef0dc07ee08b351a8e3a02e87b0a3ca4dc11662be7609d9b4

Docker イメージはアプリケーションを実行するために必要なすべての依存関係、設定、スクリプト、バイナリなどの集合体です。イメージはイメージ・レイヤを複数重ねることでできています。Pull complete と数行にわたって書かれていたと思いますが、1行ごとに該当レイヤをダウンロードしています。

docker ps

> CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS         PORTS                                     NAMES
> af5f5deab3c9   nginx:1.25.3   "/docker-entrypoint.…"   5 minutes ago   Up 5 minutes   0.0.0.0:8080->80/tcp, [::]:8080->80/tcp   web
docker stop af5f5deab3c9
docker images

> IMAGE          ID             DISK USAGE   CONTENT SIZE   EXTRA
> nginx:1.25.3   c7a6ad68be85        274MB         67.2MB
docker image rm c7a6ad68be85

> Untagged: nginx:1.25.3
> Deleted: sha256:c7a6ad68be85142c7fe1089e48faa1e7c7166a194caa9180ddea66345876b9d2

ベストプラクティス:マルチステージビルドを行う #

コンテナはなるべく軽いほうが起動・更新が早くて良いとされています。また脆弱性を減らすためにコンテナに同梱するライブラリやアプリケーションは少ないほうが良いともされています。

これらを実現するためのベストプラクティスが「マルチステージビルド」です。ステージというある程度まとまった単位でプログラムをビルドし、ビルド成果物のみ最終ステージでイメージに同梱することでプログラムの実行に必要な最小限のファイルでコンテナを起動できます。

Dockerfile

FROM golang:1.21.1 AS builder
WORKDIR /app
COPY <<-EOF main.go
package main
import "fmt"
func main() { fmt.Println("Hello world!") }
EOF
ENV CGO_ENABLED=0
RUN go mod init hello \
&& go mod tidy \
&& go build -o hello main.go

FROM scratch # Docker 公式が用意している最小イメージ
COPY --from=builder /app/hello /hello
CMD ["/hello"]

マルチステージビルドを行うことで、この Docker イメージから起動されるコンテナでは hello というバイナリのみ同梱されます。

コラム:なぜマイクロサービスアーキテクチャ? #

アプリケーションのアーキテクチャを厳密に考えず、開発を素朴に行っていくと、1つの実行プログラムに全部入りのアプリケーションができます。これをモノリシック(monolithic; 1つのでかい岩で構成されている)アーキテクチャと呼びます。シンプルで導入しやすいですが、サービスが大きくなるにつれて次のような問題が出てきます。

  1. たとえ小さな修正でもアプリケーションのデプロイは必ず全体をデプロイする必要があるため、顧客への価値提供が遅くなる
  2. アプリケーションの起動やビルドが遅く、開発効率が悪くなる

デプロイに関する " 調整 " が増えていき、複合的な問題として「開発者を増やしても思ったように開発が進まない」という問題もあるでしょう。

これらの問題を解決するために登場したのがマイクロサービスアーキテクチャです。小さなサービスの単位でビルド、デプロイできるようになるため、モノリシックアーキテクチャが抱えている多くの問題を解決します。

  1. マイクロサービスごとにデプロイが可能になるため、新機能や機能修正を細かくデプロイできるようになる
  2. マイクロサービスのビルドや起動が早くなり、開発効率が上がる

デプロイが各マイクロサービスに閉じるため、コミュニケーションコストはある程度に抑えながら開発者を増やしていくことで開発スピードを上げていくことができるでしょう。

マイクロサービスにおけるメリットは、そのままコンテナ技術を利用することで最大限に活かすことができます。1つのマイクロサービスを1つのコンテナにすることで、コンテナごとにデプロイしたり、開発・修正のサイクルを素早く回したりできるようになります。より早く、より良いものを顧客に提供したい、という競争の中で、サービス規模が拡大していく中でマイクロサービスアーキテクチャが選択され、コンテナ技術が好まれるのは当然のことと言えるでしょう。

Chapter 2: KUbernetes クラスタをつくってみる #

Docker の登場によりコンテナをつくって壊しやすくなりました。その結果、人々はたくさんのコンテナを管理しなければならなくなります。コンテナは非常に便利ですが、本番で多くのコンテナを管理しようとすると、次のような問題に直面するでしょう。

  • 障害発生時に、各コンテナの設定・復旧をするのが大変
  • コンテナの仕様を個々に管理するのが大変
  • サーバが複数台あるときに、どのサーバでコンテナを起動させるべきかを決めるのが大変

これらの問題を解決するための手段の1つとして「Kubernetes を使う」ということが挙げられます。逆に言えば、コンテナを使っているからといって必ずしも Kubernetes が最適な手段とは限りません。アプリケーションをコンテナ化しただけで、コンテナの使い方が仮想マシン時代と変わらないケースも見受けられます。

Kubernetes のアーキテクチャ概要 #

Kubernetes は大きく Control Plane と Worker Node の2つの役割に分かれます。Control Plane によって決まった内容に応じて、Worker Node では実際にコンテナを起動する、といった流れになります。Worker Node は略して Node と呼ばれることもあります。

Control Plane には kube-apiserver が含まれています。Kubernetes を扱うユーザは kube-apiserver にアクセスして Kubernetes の操作を行います。

kube-apiserver へ簡単にアクセスするためのツールとして、kubectl が用意されています。

大事な要素として「Control Plane は Worker Node に直接指示しない」ということがあります。Worker Node が Control Plane に問い合わせる方式をとることで、Control Plane が壊れても、即座に Worker Node 上に起動するコンテナが破壊されるわけではありません。

ローカルにクラスタを構築する #

ローカルに簡単にクラスタを構築するために利用できるツールを紹介します。

  • minikube
  • kind
    • https://kind.sigs.k8s.io/
    • Docker in Docker といって Docker の環境の中に Docker を立ち上げてクラスタを作っているため、Docker が必須となります。
  • k3s
    • https://k3s.io/
    • 軽量で起動が早いという特徴があります。

本書ではマルチノード環境をハンズオンで必要とするため、マルチノード環境を構築できるツールを用意すると良いでしょう。minikube と kind がこれに当てはまります。本書では筆者の好みもあり、kind を使います。

kubectl と kind をインストールする #

Kubernetes クラスタ構築にあたって kubectl は必須ではありませんが、構築後の確認のためにも kubectl を使えると良いでしょう。

https://kubernetes.io/ja/docs/tasks/tools/#kubectl

そして kind をインストールしましょう。

https://kind.sigs.k8s.io/docs/user/quick-start/#installation

HomeBrew を利用している場合は次のコマンドでインストールできます。

brew install kubernetes-cli # kubectl
brew install kind # kind

インストールされたことを確認します。

kubectl version
> Client Version: v1.36.2
> Kustomize Version: v5.8.1

kind version
> kind v0.32.0 go1.26.3 darwin/arm64

Kubernetes クラスタを構築する #

kind では DockerHub の kindest/node (https://hub.docker.com/r/kindest/node/tags)というリポジトリに上がっている任意のイメージを選択して環境を構築できるため、本書執筆時点で最新の v1.29.0 を利用します。

kind create cluster --image=kindest/node:v1.29.0

> Creating cluster "kind" ...
>  ✓ Ensuring node image (kindest/node:v1.29.0) 🖼
>  ✓ Preparing nodes 📦
>  ✓ Writing configuration 📜
>  ✓ Starting control-plane 🕹️
>  ✓ Installing CNI 🔌
>  ✓ Installing StorageClass 💾
> Set kubectl context to "kind-kind"
> You can now use your cluster with:
>
> kubectl cluster-info --context kind-kind
>
> Have a nice day! 👋

kubectl を利用してクラスタと接続できることを確認しましょう。

kubectl cluster-info --context kind-kind

> Kubernetes control plane is running at https://127.0.0.1:54690
> CoreDNS is running at https://127.0.0.1:54690/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

> To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

Kubernetes クラスタのセットアップが完了していることが確認できました。

kubectl の config #

今後開発するうえで、kubectl の config の場所と、何が書かれているかについて知っておくと良いでしょう。とくに staging/production でクラスタが異なる場合、Kubernetes クラスタのアップデートで config 情報を書き換える必要があることなどが考えられる環境で開発している場合、知っておくと良い知識です。kubectl の config 情報は home ディレクトリの .kube/config に記載されています。

cat ~/.kube/config

apiVersion: v1
clusters:
contexts:
- context:
    cluster: kind-kind
    user: kind-kind
  name: kind-kindcurrent-context: kind-kind
kind: Config
preferences: {}
users:
- name: kind-kind
  user:
    client-certificate-data: LS0tLS1C ........
    client-key-data: LS0tLS1C ........

複数クラスタと接続する必要がある場合は、この config ファイルに複数クラスタの情報が書かれます。「context」と書かれていることに注目してください。クラスタの設定情報ごとに名前のついたコンテキストが作成されます。このコンテキストを切り替えることでクラスタごとに利用する config の内容を使い分けます。

たとえば、kind で作ったクラスタとの接続確認は kubectl cluster-info --context kind-kind というコマンドを使用しました。--context オプションで利用するクラスタのコンテキストを指定しています。本書では1つのクラスタしか利用しないため、紹介するコマンドにはいずれも --context オプションは付けていません。

Kubernetes クラスタを消す #

作ったクラスタを消しましょう。消すのは簡単で kind delete cluster と打つだけです。

kind delete cluster

> Deleting cluster "kind" ...
> Deleted nodes: ["kind-control-plane"]

Chapter 3: 全体像の説明 #

本格的なハンズオンに入るために本書の全体の流れを説明します。

Chapter 4: アプリケーションを Kubernetes クラスタ上につくる #

Kubernetes リソースにはさまざまな種類がありますが、最小構成単位として Pod というリソースがあります。こちらが Pod リソースを作成するためのマニフェストです。

chapter-04/nginx.yaml

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
    - name: nginx
      image: nginx:1.25.3
      ports:
        - containerPort: 80

今回はコンテナを1つしか指定していませんが、Pod は複数コンテナをまとめて起動できます。たとえば A というサービスと、ログを転送するサービスがあったときに、これらは1つの Pod として起動することが多いです(一般的に、このようにメインのサービスに付随するようなプログラムを「サイドカー」と呼びます)。

起動・リリースのタイミングをあわせたいとか、ローカルファイルにアクセスしたいときに同じ Pod にします。

Pod を作成するにあたって、重要な概念に “Namespace” というものがあります。Kubernetes において、Namespace は単一クラスタ内のリソース群を分離するメカニズムを提供します。たとえば、リソースの名前は Namespace 内で一意である必要がありますが、Namespace 間では一意である必要はありません。また、Namespace ごとに権限を分けることもできます。

このように、あるまとまった単位でリソースをまとめたい要件があるときに Namespace を使います。すべてのリソースが Namespace を利用できるわけではなく、例えばクラスタワイドに作成するリソース(例:Node)は Namespace の適用範囲外です。

ハンズオンではデフォルトで作成される default Namespace を利用することにしますが、一般的な本番運用環境では default Namespace を利用することはほとんどありません。

また、kube-system Namespace についても覚えておくと良いです。Control Plane や Worker Node で起動する Kubernetes のシステムコンポーネントの Pod が利用する Namespace です。クラスタを起動した状態で確認すると、いくつかの Pod が起動していることが分かります。

kubectl get pod --namespace kube-system

> NAME                                         READY   STATUS    RESTARTS   AGE
> etcd-kind-control-plane                      0/1     Running   0          3s
> kube-apiserver-kind-control-plane            0/1     Running   0          3s
> kube-controller-manager-kind-control-plane   0/1     Running   0          4s
> kube-scheduler-kind-control-plane            0/1     Running   0          4s

Pod を動かしてみよう #

Pod を作成する前に、クラスタが起動できているか、まずは確認してみましょう。

kubectl get nodes

> NAME                 STATUS   ROLES           AGE   VERSION
> kind-control-plane   Ready    control-plane   16m   v1.29.0

kind get clusters

> kind # デフォルトで作成されるクラスタ名が kind です。

利用するマニフェストは次のとおりです。

chapter-04/myapp.yaml

apiVersion: v1
kind: Pod
metadata:
  name: myapp
  labels:
    app: myapp
spec:
  containers:
    - name: hello-server
      image: blux2/hello-server:1.0
      ports:
        - containerPort: 8080

まずは既存の Pod が存在しないことを確認します。

kubectl get pod --namespace default

No resources found in default namespace.

つづいて、マニフェストを適用します。

kubectl apply --filename chapter-04/myapp.yaml --namespace default

> pod/myapp created

Pod が作成できていることを確認しましょう。

kubectl get pod --namespace default

> NAME    READY   STATUS    RESTARTS   AGE
> myapp   1/1     Running   0          11s

STATUS が Running になっていることが確認できていれば Pod の作成完了です。(ContainerCreating などの Running 以外が表示されたとしても、しばらく待っていれば Running になるはずです。)

コラム:なぜ kubectl run ではないのか #

docker のときは docker run だったのに、今回はなぜ kubectl run ではないのかと疑問に思う方もいるかも知れません。実際 kubectl run というコマンドは存在しますし、コンテナを起動することもできます。

kubectl run myapp2 --image=blux2/hello-server:1.0 --namespace default

マニフェストファイルを用意せず実行できる kubectl run は一時的な Pod の利用(とくにデバッグ時)に使われることが多いです。

通常 Pod を起動するときにはマニフェストファイルを用意して kubectl apply を使いましょう。

Chapter 5: トラブルシューティングガイドと kubectl コマンドの使い方 #

トラブルシューティングに役立つ Pod の STATUS カラム #

kubectl get pod で得られる STATUS カラムにはトラブルシューティング時に役立つ情報が出力されます。それぞれのメッセージとその意味を記載します。

STATUS 意味
Pending Kubernetes クラスタからは Pod の作成は許可されたものの、1つ以上のコンテナが準備中であることを意味しています。Pod 起動直後にこの STATUS が表示されることがありますが、長時間この STATUS である場合は以上を疑いましょう。Pod の Events を参照し、ヒントが書かれていないか確認しましょう。
Running Pod がノードにスケジュールされ、すべてのコンテナがサウ性された状態です。少なくとも1つのコンテナがまだ実行中、起動または再起動のプロセス中です。常時起動が想定される Pod であれば正常な STATUS です。
Completed Pod 内のすべてのコンテナが完了した状態です。再起動はされません。
Unknown 何らかの理由で Pod の状態を取得できなかったことを表しています。この STATUS は通常、Pod が実行されるべきノードとの通史ねらーが原因で発生します。
ErrImage Pull Image の取得に失敗したことを表しています。Pod の Events を参照し、ヒントが書かれていないか確認しましょう。
Error コンテナが異常終了したことを表しています。Pod のログを参照し、ヒントが書かれていないか確認しましょう。
OOMKilled コンテナが Out Of Memory(OOM) で終了したことを表しています。Pod の仕様リソースを増やしましょう。
Terminating Pod が削除中の状態を表しています。Terminating を繰り返す場合は以上と考えましょう。Pod の Events を参照し、ヒントが書かれていないか確認しましょう。

リソースを取得する:kubectl get #

リソースの情報を取得することがトラブルシューティングガイドの第一歩です。

kubectl get pod --namespace default

> NAME     READY   STATUS    RESTARTS   AGE
> myapp    1/1     Running   0          37m
> myapp2   1/1     Running   0          3s

リソース名を指定して特定のリソース情報のみを取得することも可能です。

kubectl get pod myapp --namespace default

> NAME    READY   STATUS    RESTARTS   AGE
> myapp   1/1     Running   0          38m

リソースの詳細を取得する:kubectl describe #

kubectl get よりも詳しい情報が欲しい場合に利用しましょう。とくに Events の内容はトラブルシューティングに役立ちます(ただし Events は一定時間で消えてしまいます)。

kubectl describe pod myapp --namespace default

> Name:             myapp
> Namespace:        default
> Priority:         0
> Service Account:  default
> Node:             kind-control-plane/172.18.0.2
> Start Time:       Tue, 21 Jul 2026 15:41:41 +0900
> Labels:           app=myapp
> Annotations:      <none>
> Status:           Running
> IP:               10.244.0.5
> IPs:
>   IP:  10.244.0.5
> Containers:
>   hello-server:
>     Container ID:   containerd://267b8d45d08dbade6daf14e7ab43b1e6d5115c4603171546c351d77ba85da85d
>     Image:          blux2/hello-server:1.0
>     Image ID:       docker.io/blux2/hello-server@sha256:35ab584cbe96a15ad1fb6212824b3220935d6ac9d25b3703ba259973fac5697d
>     Port:           8080/TCP
>     Host Port:      0/TCP
>     State:          Running
>       Started:      Tue, 21 Jul 2026 15:41:46 +0900
>     Ready:          True
>     Restart Count:  0
>     Environment:    <none>
>     Mounts:
>       /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-xzg8q (ro)
> Conditions:
>   Type                        Status
>   PodReadyToStartContainers   True
>   Initialized                 True
>   Ready                       True
>   ContainersReady             True
>   PodScheduled                True
> Volumes:
>   kube-api-access-xzg8q:
>     Type:                    Projected (a volume that contains injected data from multiple sources)
>     TokenExpirationSeconds:  3607
>     ConfigMapName:           kube-root-ca.crt
>     Optional:                false
>     DownwardAPI:             true
> QoS Class:                   BestEffort
> Node-Selectors:              <none>
> Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
>                              node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
> Events:
>   Type    Reason     Age   From               Message
>   ----    ------     ----  ----               -------
>   Normal  Scheduled  41m   default-scheduler  Successfully assigned default/myapp to kind-control-plane
>   Normal  Pulling    41m   kubelet            spec.containers{hello-server}: Pulling image "blux2/hello-server:1.0"
>   Normal  Pulled     41m   kubelet            spec.containers{hello-server}: Successfully pulled image "blux2/hello-server:1.0" in 4.744s (4.744s including waiting)
>   Normal  Created    41m   kubelet            spec.containers{hello-server}: Created container hello-server
>   Normal  Started    41m   kubelet            spec.containers{hello-server}: Started container hello-server

コンテナのログを取得する:kubectl logs #

ここで出力されるログはコンテナの標準出力のログになります。

kubectl logs myapp --namespace default

> 2026/07/21 06:41:46 Starting server on port 8080

デバッグ用のサイドカーコンテナを立ち上げる:kubectl debug #

コンテナは起動を早くするために軽量化したり、セキュリティリスクを低減するために必要最低限なツールしか同梱していないことも多いです。結果として、デバッグしたくてもツールどころかシェルが入っていないケースも考えられます。

そこでデバッグ用コンテナを起動することで、多様なデバッグツールを利用できるようになります。デバッグ用コンテナは任意のイメージを指定できるため、自分用のカスタムコンテナイメージを作ってもよいでしょう。

次のコマンドで curl のデバッグ用コンテナを立ち上げ、myapp のローカルホストに接続してみます。

kubectl debug --stdin --tty myapp --image=curlimages/curl:8.4.0 --target=hello-server --namespace default -- sh

> Targeting container "hello-server". If you don't see processes from this container it may be because the container runtime doesn't support this feature.
> Defaulting debug container name to debugger-p7hbc.
> All commands and output from this session will be recorded in container logs, including credentials and sensitive information passed through the command prompt.
> If you don't see a command prompt, try pressing enter.

shell が立ち上がったら curl コマンドを実行してみます。対象コンテナからレスポンスが正常に返ることが確認できます。

curl localhost:8080

> Hello, world!

デバッグ対象の Pod と同じネットワーク、同じボリュームを参照でき非常に便利です。特にネットワークの問題は障害分岐点が多いため、まずはローカルで接続可能か確認する、といった使い方ができます。

コンテナにログインする:kubectl exec #

コンテナにシェルが入っていれば直接対象のコンテナにログインできます。しかしシェルが入っていないことも多いため、どんな Pod に対しても使えるわけではありません。

ポートフォワードでアクセス:kubectl port-forward #

Pod には Kubernetes クラスタ内用の IP アドレスが割り当てられます。そのため、何もしないとクラスタ外からのアクセスができません。後述する Service というリソースを利用することでクラスタ外からアクセス可能にすることはできますが、ここでは kubectl を使ってお手軽にアクセスしてみましょう。

kubectl port-forward myapp 5555:8080 --namespace default

> Forwarding from 127.0.0.1:5555 -> 8080
> Forwarding from [::1]:5555 -> 8080

ここで別のターミナルを開き、curl してみます。

curl localhost:5555

> Hello, world!

クラスタ外からのリクエストに対して、レスポンスが返っていることが分かります。

マニフェストをその場で編集する:kubectl edit #

マニフェストを修正できます。その場で簡単に修正できる反面、修正履歴を残しにくいため推奨されていません。一刻を争うケース以外では、正規のデプロイ手順を踏んで kubectl apply する形が望ましいです。

リソースを削除する:kubectl delete #

指定したリソースを削除するコマンドです。本番環境での削除行為はよっぽどのことが無い限り行いたくないものですが、意外と利用するコマンドです。

kubectl には「Pod を再起動する」というコマンドがないため、kubectl delete コマンドで代替します。

本番環境でのアプリケーションは通常「Deployment」というリソースを使って Pod を冗長化しています。ある特定の Pod だけハングしてしまった、というときにこのコマンドで Pod を削除します。Deployment を利用していれば自動的に削除した Pod が再度作成されるようになるので、Pod を削除しても問題ないケースが多いです。

その他のコマンド #

コマンドはたくさんあります。kubectl cheat sheet にわかりやすくまとまっています。

https://kubernetes.io/docs/reference/kubectl/quick-reference/

最後にこれまで作成した Pod を掃除しておきましょう。

kubectl delete pod myapp myapp2 myapp3 --namespace default

> pod "myapp" deleted from default namespace
> pod "myapp2" deleted from default namespace
> pod "myapp3" deleted from default namespace

kubectl get pod --namespace default

> No resources found in default namespace.

役立つツールの紹介 #

デバッグしてみよう #

デバッグ時はクラスタの最も内側から原因を切り分けていくのをおすすめします。

  1. Pod
  2. ReplicaSet
  3. Deployment
  4. Service
  5. Ingress

このハンズオンでは「なぜ動かなくなったか」を調査し、直すところまで行います。

まずは正常に動く Pod を作成します。

kubectl apply --filename chapter-05/myapp.yaml --namespace default

kubectl get pod myapp --namespace default

> NAME    READY   STATUS    RESTARTS   AGE
> myapp   1/1     Running   0          13s

早速壊します。

kubectl apply --filename chapter-05/pod-destruction.yaml --namespace default

kubectl get pod myapp --namespace default

> NAME    READY   STATUS         RESTARTS   AGE
> myapp   0/1     ErrImagePull   0          88s

Pod の情報は取得できているため、Pod のリソースは作成できていることがわかります。リソースが作成できていない場合は kubectl get pod を実行してもなにも表示されません。リソース作成は完了したものの Pod が Running になっていませんね。

describe で得られる情報にエラーの原因が書かれていることがあります。調べてみましょう。

kubectl describe pod myapp --namespace default

> Name:             myapp
> Namespace:        default
> Priority:         0
> Service Account:  default
> Node:             kind-control-plane/172.18.0.2
> Start Time:       Tue, 21 Jul 2026 17:00:18 +0900
> Labels:           <none>
> Annotations:      <none>
> Status:           Running
> IP:               10.244.0.8
> IPs:
>   IP:  10.244.0.8
> Containers:
>   hello-server:
>     Container ID:   containerd://fba58c012d0d26251707f9e57f38598b5a885cee83beef84983c0e6b68057c71
>     Image:          blux2/hello-server:1.1
>     Image ID:       docker.io/blux2/hello-server@sha256:35ab584cbe96a15ad1fb6212824b3220935d6ac9d25b3703ba259973fac5697d
>     Port:           8080/TCP
>     Host Port:      0/TCP
>     State:          Waiting
>       Reason:       CrashLoopBackOff
>     Last State:     Terminated
>       Reason:       Error
>       Exit Code:    2
>       Started:      Tue, 21 Jul 2026 17:00:18 +0900
>       Finished:     Tue, 21 Jul 2026 17:01:16 +0900
>     Ready:          False
>     Restart Count:  0
>     Environment:    <none>
>     Mounts:
>       /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-99grp (ro)
> Conditions:
>   Type                        Status
>   PodReadyToStartContainers   True
>   Initialized                 True
>   Ready                       False
>   ContainersReady             False
>   PodScheduled                True
> Volumes:
>   kube-api-access-99grp:
>     Type:                    Projected (a volume that contains injected data from multiple sources)
>     TokenExpirationSeconds:  3607
>     ConfigMapName:           kube-root-ca.crt
>     Optional:                false
>     DownwardAPI:             true
> QoS Class:                   BestEffort
> Node-Selectors:              <none>
> Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
>                              node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
> Events:
>   Type     Reason     Age                   From               Message
>   ----     ------     ----                  ----               -------
>   Normal   Scheduled  3m34s                 default-scheduler  Successfully assigned default/myapp to kind-control-plane
>   Normal   Pulled     3m34s                 kubelet            spec.containers{hello-server}: Container image "blux2/hello-server:1.0" already present on machine
>   Normal   Created    3m34s                 kubelet            spec.containers{hello-server}: Created container hello-server
>   Normal   Started    3m34s                 kubelet            spec.containers{hello-server}: Started container hello-server
>   Normal   Killing    2m36s                 kubelet            spec.containers{hello-server}: Container hello-server definition changed, will be restarted
>   Normal   Pulling    114s (x3 over 2m36s)  kubelet            spec.containers{hello-server}: Pulling image "blux2/hello-server:1.1"
>   Warning  Failed     113s (x3 over 2m34s)  kubelet            spec.containers{hello-server}: Failed to pull image "blux2/hello-server:1.1": rpc error: code = NotFound desc = failed to pull and unpack image "docker.io/blux2/hello-server:1.1": failed to resolve reference "docker.io/blux2/hello-server:1.1": docker.io/blux2/hello-server:1.1: not found
>   Warning  Failed     113s (x3 over 2m34s)  kubelet            spec.containers{hello-server}: Error: ErrImagePull
>   Normal   BackOff    75s (x3 over 2m34s)   kubelet            spec.containers{hello-server}: Back-off pulling image "blux2/hello-server:1.1"
>   Warning  Failed     75s (x3 over 2m34s)   kubelet            spec.containers{hello-server}: Error: ImagePullBackOff
>   Warning  BackOff    27s (x6 over 101s)    kubelet            spec.containers{hello-server}: Back-off restarting failed container hello-server in pod myapp_default(e8e30841-42bf-482e-895a-7ab3091a2c0c)

Containers: State: Reason: を見ると CrashLoopBackOff と書かれており、Events: には Failed to pull image "blux2/hello-server:1.1" のログがあります。

リポジトリのページを開きタグを見てみると、1.1 は存在しないことが分かります。これが原因です。

https://hub.docker.com/r/blux2/hello-server/tags

リポジトリ名のタグをタイポしてしまうなど、現場でもたまにあるミスです。

今回のハンズオンは終わりです。最後に掃除をします。

kubectl delete --filename chapter-05/pod-destruction.yaml --namespace default

kubectl get pod --namespace default

> No resources found in default namespace.

Chapter 6: Kubernetes リソースをつくって壊そう #

Pod のライフサイクルを知ろう #

Pod はマニフェストが登録されてから Node にスケジュールされ、kubelet がコンテナを起動し、異常があったり完了条件を満たしたりする場合、終了して一生を遂げます。

Pod を冗長化するための ReplicaSet と Deployment #

これまで Pod の説明やトラブルシューティングを行ってきましたが、実際の運用では Pod を直接作ることは推奨されていません。Pod 単体ではコンテナの冗長化ができないので、本番環境での運用には向きません。そこで利用するのが Deployment というリソースです。

Deployment は ReplicaSet というリソースを作り、ReplicaSet が Pod を作ります。

Deployment
├ v1: ReplicaSet
└ v2: ReplicaSet
      ├ Pod
      └ Pod

ReplicaSet は指定した数の Pod を複製するリソースです。Pod リソースと異なるところは、Pod を複製できるところです。複製する Pod の数を replicas で指定できます。

ReplicaSet のマニフェストです。

chapter-06/replicaset.yaml

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: httpserver
  labels:
    app: httpserver
spec:
  replicas: 3 # Pod を3つ作る
  selector:
    matchLabels:
      app: httpserver # template の labels と一致している必要がある
  template:
    metadata:
      labels:
        app: httpserver
    spec:
      containers:
        - name: nginx
          image: nginx:1.25.3
kubectl apply --filename chapter-06/replicaset.yaml --namespace default

kubectl get pod --namespace default

> NAME               READY   STATUS              RESTARTS   AGE
> httpserver-2zv5t   0/1     ContainerCreating   0          5s
> httpserver-grbr5   0/1     ContainerCreating   0          5s
> httpserver-mt78n   0/1     ContainerCreating   0          5s

次のコマンドで ReplicaSet のリソースを直接参照できます。

kubectl get replicaset --namespace default

> NAME         DESIRED   CURRENT   READY   AGE
> httpserver   3         3         3       53s

最後に掃除をしておきましょう。

kubectl delete replicaset httpserver --namespace default

実は ReplicaSet も直接利用することは推奨されていません。より本番運用に向いている Deployment を利用することが推奨されています。

本番の運用環境では単に Pod を複製して冗長性を担保するだけではなく、Pod の更新時に「無停止で更新する」ということを求められることが多いでしょう。コンテナイメージが v1 の Pod を作成する ReplicaSet のコンテナイメージを v2 にしたいとすると、コンテナイメージ v2 用の ReplicaSet を作る必要がありますよね。v1 と v2 の切り替えをうまくするためにはこれらをひもづけるさらなる上位概念が必要になります。これが Deployment です。

Deployment のマニフェストです。

chapter-06/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.24.0
          ports:
            - containerPort: 80
kubectl apply --filename chapter-06/deployment.yaml --namespace default

それぞれの単位で確認できます。

kubectl get deployment --namespace default

> NAME               READY   UP-TO-DATE   AVAILABLE   AGE
> nginx-deployment   2/3     3            2           30s

kubectl get replicaset --namespace default

> NAME                          DESIRED   CURRENT   READY   AGE
> nginx-deployment-595dff4799   3         3         3       33s

kubectl get pod --namespace default

> NAME                                READY   STATUS              RESTARTS   AGE
> nginx-deployment-595dff4799-4h4hw   0/1     ContainerCreating   0          36s
> nginx-deployment-595dff4799-4jphv   0/1     ContainerCreating   0          36s
> nginx-deployment-595dff4799-wwjk7   0/1     ContainerCreating   0          36s

Deployment では StrategyType で、新規バージョン追加時の挙動を制御することができます。Recreate と RollingUpdate の2つが選択可能です。Recreate は全部の Pod を同時に更新し、逆に RollingUpdate は Pod を順番に更新する方法です。

RollingUpdate を選択した場合、RollingUpdateStrategy を記載することができます。RollingUpdateStrategy で指定できるのは maxUnavailable と maxSurge の2つです。

maxUnavailable は「最大いくつの Pod を同時にシャットダウンできるか」を指定します。デフォルトで指定される 25% とは「Pod 全体の 25% まで同時にシャットダウン可能」という意味です。例えば4つ Pod があれば、一度の Rolling Update で1つずつ Pod を再作成します。maxSurge は「最大いくつの Pod を新規作成できるか」を指定します。

Rolling Update ではアプリケーションのアップデートを行うために、古い Pod をシャットダウンしながら更新先の新しい Pod を作っていきます。一度に必要な数の新規 Pod を同時に作ればよいと思われるかもしれませんが、新旧の Pod が同時に存在する時間は Kubernetes クラスタ環境に2倍の Pod が必要になります。それだけクラスタのキャパシティが必要になり、またコストもかかります。(筆者は maxSurge の設定のせいで Pod の数が増えすぎて全ノードのキャパシティが枯渇してしまい、Rolling Update が終わらなくなるということがありました。みなさんも気をつけましょう。)

chapter-06/deployment-recreate.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 10
  strategy:
    type: Recreate # Recreate で指定
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.24.0
          ports:
            - containerPort: 80
          lifecycle:
            preStop:
              exec:
                command: ["/bin/sh", "-c", "sleep 10"]

chapter-06/deployment-rollingupdate.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  strategy:
    type: RollingUpdate # RollingUpdate で指定
    rollingUpdate:
      maxUnavailable: 25%
      maxSurge: 100%
  replicas: 10
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.24.0
          ports:
            - containerPort: 80
          lifecycle:
            preStop:
              exec:
                command: ["/bin/sh", "-c", "sleep 10"]

rollingUpdate: maxSurge: 100% で apply すると、途中、Pod の数が倍になっていることが観察できます。

maxSurge が 100% というのは、元あった Pod の数と同じ数だけ新規 Pod を作成することを言っています。これが Pod の更新が最も早く、かつ安全な方法ではあります。しかし、必要なリソースが倍になるため、使用する際はリソースキャパシティに注意しましょう。

Deployment をつくって壊そう #

chapter-06/deployment-hello-server.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-server
  labels:
    app: hello-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hello-server
  template:
    metadata:
      labels:
        app: hello-server
    spec:
      containers:
        - name: hello-server
          image: blux2/hello-server:1.0
          ports:
            - containerPort: 8080
kubectl apply --filename chapter-06/deployment-hello-server.yaml --namespace default

kubectl get pod --namespace default

> NAME                            READY   STATUS    RESTARTS   AGE
> hello-server-6cc6b44795-9m5xq   1/1     Running   0          23s
> hello-server-6cc6b44795-jkw5c   1/1     Running   0          23s
> hello-server-6cc6b44795-qhfhm   1/1     Running   0          23s

Pod を1つ消してみます。すぐに代わりの Pod が作られたことが確認できます。

kubectl delete pod hello-server-6cc6b44795-9m5xq --namespace default

kubectl get pod --namespace default

> NAME                            READY   STATUS    RESTARTS   AGE
> hello-server-6cc6b44795-gm6xw   1/1     Running   0          2s
> hello-server-6cc6b44795-jkw5c   1/1     Running   0          69s
> hello-server-6cc6b44795-qhfhm   1/1     Running   0          69s

この状態で、別のターミナルを開いてポートフォワードしておきます。

kubectl port-forward deployments/hello-server 8080:8080

もとのターミナルから curl すると正常にアクセスできていることがわかります。

curl localhost:8080

> Hello, world!

次のマニフェストを適用してみます。

chapter-06/deployment-hello-server-rollingupdate.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-server
  labels:
    app: hello-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hello-server
  template:
    metadata:
      labels:
        app: hello-server
    spec:
      containers:
        - name: hello-server
          image: blux2/hello-server:1.3
          ports:
            - containerPort: 8080
kubectl apply --filename chapter-06/deployment-hello-server-rollingupdate.yaml --namespace default

kubectl get pod --namespace default

> NAME                            READY   STATUS             RESTARTS   AGE
> hello-server-6cc6b44795-gm6xw   1/1     Running            0          6m9s
> hello-server-6cc6b44795-jkw5c   1/1     Running            0          7m16s
> hello-server-6cc6b44795-qhfhm   1/1     Running            0          7m16s
> hello-server-6fb85ff748-vc7rv   0/1     ImagePullBackOff   0          30s

Pod が1つ増え、エラーが出ています。しかしアプリケーションが壊れたわけではありません。再び curl してみるとレスポンスが返ってきます。

curl localhost:8080

> Hello, world!

状況を詳しく見ていきます。まずは Deployment の状況を確認しましょう。

kubectl get deployment --namespace default

> NAME           READY   UP-TO-DATE   AVAILABLE   AGE
> hello-server   3/3     1            3           8m52s

UP-TO-DATE が 1 になっています。これは古いバージョンの Pod をそのままに、新規バージョンの Pod を1個作成中にエラーになっていることを表しています。

デフォルトの場合「maxUnavailable: 25%、maxSurge: 25%」です。Pod が3つであれば、25% とは 0.75 個です。maxUnavailable は切り下げ、maxSurge は切り上げとなります。この場合 maxUnavailable: 0、maxSurge: 1 となりまう。

つまり新規 Pod は同時に1つまで作成できますが、新規 Pod が正常に作成完了しない限り、次の古い Pod を消すことができません(新しい Pod が1つ増えると Pod 総数が4になり、Pod を1つ消せるようになります)。

次のコマンドを実行して ReplicaSet を見ると、新旧バージョンそれぞれいくつ Pod が作られているかわかります。

kubectl get replicaset --namespace default

> NAME                      DESIRED   CURRENT   READY   AGE
> hello-server-6cc6b44795   3         3         3       13m
> hello-server-6fb85ff748   1         1         0       6m37s

古いバージョンの Pod が残ってくれているおかげでアプリケーションに疎通ができています。

エラーの原因は ErrImagePull で、describe で確認すると Chapter 5 と同様に存在しないイメージタグ(1.3)を指定していることが原因です。

タグを 1.2 に修正した後 apply します。その後、状態を見てみます。

kubectl get deployment,replicaset,pod --namespace default

> NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
> deployment.apps/hello-server   3/3     3            3           18m
>
> NAME                                      DESIRED   CURRENT   READY   AGE
> replicaset.apps/hello-server-5d6fd6dbb9   3         3         3       92s
> replicaset.apps/hello-server-6cc6b44795   0         0         0       18m
> replicaset.apps/hello-server-6fb85ff748   0         0         0       11m
>
> NAME                                READY   STATUS    RESTARTS   AGE
> pod/hello-server-5d6fd6dbb9-92cz2   1/1     Running   0          93s
> pod/hello-server-5d6fd6dbb9-gcst5   1/1     Running   0          83s
> pod/hello-server-5d6fd6dbb9-jdqdv   1/1     Running   0          84s

無事、Rolling Update が完了しました。ポートフォワードを接続し、curl してみます。

curl localhost:8080

> Hello, world! Let's learn Kubernetes!

アプリケーションが更新されていることも確認できました。

最後に掃除をしておきましょう。

kubectl delete --filename chapter-06/deployment-hello-server-rollingupdate.yaml --namespace default

Pod へのアクセスを助ける Service #

Deployment は IP アドレスをもたないため、Deployment で作ったリソースにアクセスするためには IP アドレスが割り振られている Pod 個々にアクセスする必要があります。それでは、せっかく Rolling Update の機能があってもアクセスしている Pod が消えてしまえば接続は途切れてしまいます。

Deployment で作成した複数 Pod へのアクセスを適切にルーティングしてもらうために、Service というリソースを作成します。

最も簡単な Service のマニフェストは次のようになります。

chapter-06/service.yaml

apiVersion: v1
kind: Service
metadata:
  name: hello-server-service
spec:
  selector:
    app: hello-server # Service を利用したい Pod のラベルと一致させる
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080 # 利用するコンテナが解放している Port を指定する

Service だけ作成しても動きません。Service に接続する Deployment も作成して動作確認します。

kubectl apply --filename chapter-06/deployment-hello-server.yaml --namespace default
kubectl apply --filename chapter-06/service.yaml --namespace default

kubectl get service,deployment --namespace default

> NAME                           TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
> service/hello-server-service   ClusterIP   10.96.78.139   <none>        8080/TCP   16s
> service/kubernetes             ClusterIP   10.96.0.1      <none>        443/TCP    3h1m
>
> NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
> deployment.apps/hello-server   3/3     3            3           32s

これでポートフォワードして curl すると、Service 経由で Pod にアクセスするようになります。

kubectl get service の出力に TYPE というカラムがあります。Service にはいくつかの Type があり、作成時に指定できます。Type を指定しない場合はデフォルトで ClusterIP が指定されます。

  • ClusterIP: クラスタ内部の IP アドレスで Service を公開します。この Type で指定された IP アドレスはクラスタ内部からしか疎通できません。Ingress というリソースを利用することで外部公開が可能になります。
  • NodePort: すべての Node の IP アドレスで指定したポート番号(NordPort)を公開します。
  • LoadBalancer: 外部ロードバランサを用いて外部 IP アドレスを公開します。ロードバランサは別で用意する必要があります。
  • ExternalName: Service を externalName フィールドの内容にマッピングします(例えば、ホスト名が api.example.com)。このマッピングにより、クラスタの DNS サーバがその外部ホスト名の値をもつ CNAME レコードを返すように設定されます。

いまは ClusterIP です。クラスタ内で通信ができることを確認しましょう。

クラスタ内に新たな Pod を作成し、curl を叩きます。宛先は kubectl get service に表示される hello-server-service の CLUSTER-IP です。

kubectl run curl --image curlimages/curl --rm --stdin --tty --restart=Never --command -- curl 10.96.78.139:8080

> Hello, world!pod "curl" deleted from default namespace

ClusterIP でのアクセスが確認できました。最後に掃除をします。

kubectl delete --filename chapter-06/deployment-hello-server.yaml --namespace default
kubectl delete --filename chapter-06/service.yaml --namespace default

次は NodePort でアクセスしましょう。NodePort を利用するとクラスタ外からもアクセスが可能となるため、ポートフォワードは不要になります。

chapter-06/service-nodeport.yaml

apiVersion: v1
kind: Service
metadata:
  name: hello-server-external
spec:
  type: NodePort
  selector:
    app: hello-server
  ports:
    - port: 8080
      targetPort: 8080
      nodePort: 30599
kubectl apply --filename chapter-06/deployment-hello-server.yaml --namespace default
kubectl apply --filename chapter-06/service-nodeport.yaml --namespace default

kubectl get service,deployment --namespace default

> NAME                            TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
> service/hello-server-external   NodePort    10.96.61.135   <none>        8080:30599/TCP   23s
> service/kubernetes              ClusterIP   10.96.0.1      <none>        443/TCP          3h14m
>
> NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
> deployment.apps/hello-server   3/3     3            3           23s

アクセスしてみましょう。まずは Node の IP を取得します。

kubectl get nodes -o jsonpath='{.items[*].status.addresses[?(@.type=="InternalIP")].address}'

> 172.18.0.2

取得した InternalIP を利用してアクセスします。Pod の起動に少し時間がかかることがあるので、レスポンスが返ってこない場合は何度か curl コマンドを実行してみてください。

curl 172.18.0.2:30599
# kind の場合 curl localhost:30599 でアクセスできます

NodePort は全 Node に対して Port をひもづけます。ポートフォワード不要で便利ですが、NodePort は Node が故障などで利用できなくなると使えなくなってしまいます。ローカルの開発環境で使うには便利で良いですが、本番運用環境では ClusterIP や LoadBalancer を利用するのが良いでしょう。

以下で掃除をしておきます。

kubectl delete --filename chapter-06/deployment-hello-server.yaml --namespace default
kubectl delete --filename chapter-06/service-nodeport.yaml --namespace default

Service を利用した DNS #

クラスタ内アクセスをするとき、IP アドレスでアクセスすると IP アドレスが変わると接続できなくなってしまいます。Kubernetes では Service 用の DNS レコードを自動で作成してくれるため、FQDN を覚えておくと便利です。

通常、Service は次のコマンドで接続が可能です。

<Service 名>.<Namespace 名>.svc.cluster.local

試してみましょう。まずは Deployment と Service を作成します。

kubectl apply --filename chapter-06/deployment-hello-server.yaml --namespace default
kubectl apply --filename chapter-06/service.yaml --namespace default

つづいて、Pod 内から FQDN に対して curl します。

kubectl --namespace default run curl --image curlimages/curl --rm --stdin --tty --restart=Never --command -- curl hello-server-service.default.svc.cluster.local:8080

> Hello, world!pod "curl" deleted from default namespace

掃除をしておきます。

kubectl delete --filename chapter-06/deployment-hello-server.yaml --namespace default
kubectl delete --filename chapter-06/service.yaml --namespace default

Pod の外部から情報を読み込む ConfigMap #

環境変数など、コンテナの外部から値を設定したいときに利用するリソースです。ConfigMap を利用する方法は3つあります。

  1. コンテナ内のコマンドの引数として読み込む
  2. コンテナの環境変数として読み込む
  3. ボリュームを利用してアプリケーションのファイルとして読み込む

よく利用する2と3を説明していきます。

コンテナの環境変数として読み込む #

環境変数を利用してアプリケーションに値を渡す方法です。

ポート番号を外部から指定できるように実装を変更した hello-server(1.4)コンテナがあります。これを呼び出すマニフェストが次です。

chapter-06/configmap/hello-server-env.yaml

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-server
  labels:
    app: hello-server
spec:
  replicas: 1
  selector:
    matchLabels:
      app: hello-server
  template:
    metadata:
      labels:
        app: hello-server
    spec:
      containers:
        - name: hello-server
          image: blux2/hello-server:1.4
          env:
            - name: PORT
              valueFrom:
                configMapKeyRef:
                  name: hello-server-configmap
                  key: PORT
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: hello-server-configmap
data:
  PORT: "8081"

このようにして ConfigMap 経由で値を渡すことが可能ですが、ConfigMap 経由で設定した環境変数は、アプリケーションを再起動しないとアプリケーションには反映できません。

環境変数を変更するために毎回アプリケーションの再作成が必要だと、サービスの運用が現実的ではないですよね。次に紹介する「ボリュームを利用してアプリケーションのファイルとして読み込む」方法では、アプリケーションの再作成なしで ConfigMap の内容を再読み込みできます。

ボリュームを利用してアプリケーションのファイルとして読み込む #

Pod にはボリュームを設定することができ、消えてほしくないファイルを保存したり、Pod 間でファイルを共有したりするファイルシステムを利用するために使用します。

hello-server(1.5)では、アクセスしたときのメッセージを外部から変更できるように実装を変えました。対応するマニフェストが次です。

chapter-06/configmap/hello-server-volume.yaml

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-server
  labels:
    app: hello-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hello-server
  template:
    metadata:
      labels:
        app: hello-server
    spec:
      containers:
        - name: hello-server
          image: blux2/hello-server:1.5
          volumeMounts:
            - name: hello-server-config
              mountPath: /etc/config
      volumes:
        - name: hello-server-config
          configMap:
            name: hello-server-configmap
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: hello-server-configmap
data:
  myconfig.txt: |-
    I am hungry.

機密データを扱うための Secret #

例えば、データベースのパスワードをはじめとした機密データについて、ConfigMap を参照できる人が全員アクセスできるのはセキュリティ上望ましくないですよね。

そこで Secret というリソースを使用することで、アクセス権を分けられます。Secret のデータは Base64 でエンコードして登録する必要があります。

Secret を Pod に読み込む方法は2種類あります。

  1. コンテナの環境変数として読み込む
  2. ボリュームを利用してコンテナに設定ファイルを読み込む

コンテナの環境変数として読み込む #

まずは Secret のデータを作成しましょう。(macOS は brew install base64 する必要があります)

echo -n 'admin' | base64
> YWRtaW4=

echo -n 'admin123' | base64
> YWRtaW4xMjM=

chapter-06/secret/nginx-sample.yaml

---
apiVersion: v1
kind: Pod
metadata:
  name: nginx-sample
spec:
  containers:
    - name: nginx-container
      image: nginx:1.25.3
      env:
        - name: USERNAME
          valueFrom:
            secretKeyRef:
              name: nginx-secret
              key: username
        - name: PASSWORD
          valueFrom:
            secretKeyRef:
              name: nginx-secret
              key: password
---
apiVersion: v1
kind: Secret
metadata:
  name: nginx-secret
type: Opaque
data:
  username: YWRtaW4= # 'admin' の Base64 エンコード値
  password: YWRtaW4xMjM= # 'admin123' の Base64 エンコード値

apply 後、コンテナにログインして echo すると環境変数が読み込まれていることが確認できます。

kubectl exec -it nginx-sample -- /bin/sh

echo $USERNAME
> admin

echo $PASSWORD
> admin123

ボリュームを利用してコンテナに設定ファイルを読み込む #

このマニフェストでは、 NGINX コンテナ内の /etc/config というパスに Secret を読み込んでいます。

chapter-06/secret/nginx-volume.yaml

apiVersion: v1
kind: Pod
metadata:
  name: nginx-sample
spec:
  containers:
    - name: nginx-container
      image: nginx:1.25.3
      volumeMounts:
        - name: nginx-secret
          mountPath: /etc/config
  volumes:
    - name: nginx-secret
      secret:
        secretName: nginx-secret
---
apiVersion: v1
kind: Secret
metadata:
  name: nginx-secret
data:
  server.key: ZU05a3UzZWNDcFVMOXpQb0lJdUcycHRaWkM1Q3U0WkNRWFJ5bWxIYWpZdlp5ZmZwTTYK

apply 後、コンテナにログインして環境変数を参照してみましょう。

kubectl exec --stdin --tty nginx-sample -- /bin/sh

cat /etc/config/server.key
> eM9ku3ecCpUL9zPoIIuG2ptZZC5Cu4ZCQXRYmlHajYvZyffpM6

1回限りのタスクを実行するための Job #

Job は1回限り実行したい Pod に利用します。Job を実行すると、Pod の実行が成功するまで指定した回数リトライを実行します。また、Pod は複数同時に実行することも可能です。

この Job は Ubuntu 22.04 で date コマンド(日時が表示されます)を打つだけの簡単なジョブです。

chapter-06/job.yaml

apiVersion: batch/v1
kind: Job
metadata:
  name: date-checker
spec:
  template:
    spec:
      containers:
        - name: date
          image: ubuntu:22.04
          command: ["date"]
      restartPolicy: Never
  backoffLimit: 4
kubectl apply --filename chapter-06/job.yaml --namespace default

kubectl get job --namespace default

> NAME           COMPLETIONS   DURATION   AGE
> date-checker   1/1           3s         5s

kubectl get pod --namespace default

> NAME                 READY   STATUS              RESTARTS   AGE
> date-checker-pwh6m   0/1     ContainerCreating   0          8s

Pod の Ready カラムを見ると、Ready の個数が 0/1 となっています。1/1 のような状態に見慣れていると異常が発生していると誤解してしまいそうですが、Pod の Ready ステータスは「コンテナが起動中」を意味しています。Job が完了するとコンテナは停止するため、0/1 となるのが想定通りです。

Pod 名をコピーし、ログを確認しましょう。date コマンドの結果が書かれています。

kubectl logs date-checker-pwh6m --namespace default

> Tue Jul 21 11:40:56 UTC 2026

Job の詳細を見てみましょう。

kubectl describe job date-checker --namespace default

> Name:             date-checker
> Namespace:        default
> Selector:         batch.kubernetes.io/controller-uid=25336c5d-b80c-425b-ab0e-31cbdfa643e0
> Labels:           batch.kubernetes.io/controller-uid=25336c5d-b80c-425b-ab0e-31cbdfa643e0
>                   batch.kubernetes.io/job-name=date-checker
>                   controller-uid=25336c5d-b80c-425b-ab0e-31cbdfa643e0
>                   job-name=date-checker
> Annotations:      <none>
> Parallelism:      1
> Completions:      1
> Completion Mode:  NonIndexed
> Suspend:          false
> Backoff Limit:    4
> Start Time:       Tue, 21 Jul 2026 20:40:43 +0900
> Completed At:     Tue, 21 Jul 2026 20:40:59 +0900
> Duration:         16s
> Pods Statuses:    0 Active (0 Ready) / 1 Succeeded / 0 Failed # 1 件が Succeeded となっている
> Pod Template:
>   Labels:  batch.kubernetes.io/controller-uid=25336c5d-b80c-425b-ab0e-31cbdfa643e0
>            batch.kubernetes.io/job-name=date-checker
>            controller-uid=25336c5d-b80c-425b-ab0e-31cbdfa643e0
>            job-name=date-checker
>   Containers:
>    date:
>     Image:      ubuntu:22.04
>     Port:       <none>
>     Host Port:  <none>
>     Command:
>       date
>     Environment:   <none>
>     Mounts:        <none>
>   Volumes:         <none>
>   Node-Selectors:  <none>
>   Tolerations:     <none>
> Events:
>   Type    Reason            Age   From            Message
>   ----    ------            ----  ----            -------
>   Normal  SuccessfulCreate  81s   job-controller  Created pod: date-checker-pwh6m
>   Normal  Completed         65s   job-controller  Job completed

「1 Succeeded」と Job の実行結果が成功していることがわかります。

後片付けです。

kubectl delete --filename chapter-06/job.yaml --namespace default

Job を定期的に実行するための CronJob #

Linux に慣れている方はご存知の cron と同様の動きをする Job になります。定期的に実行したいジョブがある場合、このリソースを作成します。

CronJob は Job を作成し、Job は Pod を作成します。

マニフェストを見てみましょう。

chapter-06/cronjob.yaml

apiVersion: batch/v1
kind: CronJob
metadata:
  name: date
spec:
  schedule: "*/2 * * * *" # ここに書かれたスケジュールに従って Job が作成される
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: date
              image: ubuntu:22.04
              command: ["date"]
          restartPolicy: Never
kubectl apply --filename chapter-06/cronjob.yaml --namespace default

kubectl get cronjob --namespace default

> NAME   SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
> date   */2 * * * *   False     0        58s             95s

後片付けです。

kubectl delete --filename chapter-06/cronjob.yaml --namespace default

Chapter 7: 安全なステートレス・アプリケーションをつくるには #

Kubenertes はアプリケーションのヘルスチェックを行い、ヘルシーではないときに自動で Service や Pod を制御する仕組みがあります。

これから説明する3種類の Probe(探査する、調査する、という意味の英語)はそれぞれ用途にあった使い方をすると、とても強力に働いてくれます。

  • Readiness probe
  • Liveness probe
  • Startup probe

Readnes probe #

コンテナが起動することと、トラフィックが受けられる状態になることは必ずしも一致しません。例えば、起動が重いアプリケーションは起動開始してからトラフィックを受け取れるようになるまでにしばらく待ってもらう必要があります。このようにコンテナが Ready になるまでの時間やエンドポイントを制御するのが Readiness probe です。

chapter-07/pod-readiness.yaml

apiVersion: v1
kind: Pod
metadata:
  labels:
    app: httpserver
  name: httpserver-readiness
spec:
  containers:
    - name: httpserver
      image: blux2/delayfailserver:1.1
      readinessProbe: # readinessProbe の設定を書く
        httpGet:
          path: /healthz
          port: 8080
        initialDelaySeconds: 5
        periodSeconds: 5

このマニフェストでは、/healthz というヘルスチェック用エンドポイントの 8080 番ポートに対して5秒に1度ヘルスチェック用リクエストを送るという設定になっています。initialDelaySeconds は最初の Probe が実施されるまでに5秒待つということを言っています。リクエストの HTTP レスポンスが 200 以上 400 未満は Readine Probe 成功と見なされ、それ以外は失敗と見なされます。

Readiness との名前になっていますが、コンテナ起動時のみならず Pod のライフサイクルすべてにおいて、この Probe は有効です。Readiness probe に失敗すると、Service リソースの接続対象から外され、トラフィックを受けなくなります。

Liveness probe #

Liveness probe は Readiness probe と似ていますが、Probe が失敗したときの挙動が変わります。Readiness probe は Service から接続を外すのに対して、Liveness probe は Pad を再起動します。これは「Pod がハングしてしまい、再起動で直る」といったケースが想定される場合に有効です。

逆に Liveness probe は再起動を無限に繰り返してしまうリスクがあるため、安易に導入することはおすすめしません。

マニフェストを見ていきましょう。Readiness probe とほとんど同じになります。

chapter-07/pod-liveness.yaml

apiVersion: v1
kind: Pod
metadata:
  labels:
    app: httpserver
  name: httpserver-liveness
spec:
  containers:
    - name: httpserver
      image: blux2/delayfailserver:1.1
      livenessProbe:
        httpGet:
          path: /healthz
          port: 8080
        initialDelaySeconds: 5
        periodSeconds: 5

Liveness probe と Readiness probe は同時に設定することも可能です。ただし、Liveness probe は Readiness probe を待つといった挙動はしないため、Readiness を先に実行したい場合は、initialDelaySeconds を調整するか、後述する Startup probe を使用する必要があります。

一般には次のように Readiness probe が先に実行されることが推奨されます。

readinessProbe:
  httpGet:
    path: /healthz
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 5
livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
  initialDelaySeconds: 30
  periodSeconds: 5

Startup probe #

Startup probe は、Pod の初回起動時のみに利用する Probe です。起動が遅いアプリケーションなどに使用することが想定されます。マニフェストは Readiness/Liveness とほぼ同じです。

startupProbe:
  httpGet:
    path: /healthz
    port: liveness-port
  failureThreshould: 30
  periodSeconds: 10

このマニフェストでは最大 30 秒 × 10 回 = 300 秒コンテナの起動を待つ設定になります。

アプリケーションに適切なリソースを指定しよう #

デフォルトで指定できるリソースは CPU・メモリ・Ephemeral Storage です。ここではよく使うであろうメモリと CPU について説明します。

マニフェストは次のようになります。

chapter-07/pod-resource-handson.yaml

apiVersion: v1
kind: Pod
metadata:
  labels:
    app: hello-server
  name: hello-server
spec:
  containers:
    - name: hello-server
      image: blux2/hello-server:1.6
      resources:
        requests:
          memory: "64Mi"
          cpu: "10m"
        limits:
          memory: "64Mi"
          cpu: "10m"

Resource requests でコンテナリソース使用量を要求します。Kubernetes スケジューラはこの値をみてスケジュールする Node を決めます。Requests の値が確保できる Node を調べ、該当する Node にスケジュールします。どの Node も Requests にかかれている量が確保できなければ、Pod がスケジュールされることはありません。

Resource limits でコンテナが使用できるリソース使用量の上限を指定します。コンテナはこれを超えてリソースを使用することはできません。メモリが上限値を超える場合、Out Of Memory(OOM) で Pod は kill されます。CPU が上限値を超えた場合、即座に Pod が kill されるということはありません。その代わりにスロットリングが発生し、アプリケーションの動作が遅くなります。

Pod の Quality of Service (QoS) Classes #

リソース設定に関連して QoS は覚えておいたほうが良い Kubernetes の機能です。Node のメモリが完全に枯渇してしまうと、その Node に乗っている全てのコンテナが起動できなくなってしまうのを防ぐため、OOM Killer という役割のプログラムがいます。OOM Killer は QoS に応じて OOMKill する Pod の優先順位を決定し、必要に応じて優先度の低い Pod から OOMKill します。

QoS クラスには3種類あります。BestEffort、Burstable、Guaranteed の順位 OOMkill が発生します。

  • Guaranteed: Pod 内のコンテナすべてにリソースの requests と limits が指定されている。さらにメモリの requests=limits、CPU も requests=limits となる値が指定されている。
  • Burstable: Pod 内のコンテナのうち少なくとも1つはメモリまたは CPU の requests/limits が指定されている
  • BestEffort: Guaranteed でも Burstable でもないもの。リソースに何も指定していない。

次のコマンドでその Pod の QoS クラスを知ることができます。

kubectl get pod <Pod 名> --output jsonpath='{.status.qosClass}' --namespace default

アプリケーションをスケールさせよう #

アプリケーションのアクセスが増えると、1つの Pod では負荷に耐えられなくなってきます。

アプリケーションをスケールさせることで安定性をあげましょう。一般に水平スケールと垂直スケールの2種類の方法があります。

水平スケールとは、同時に利用できるアプリケーションを増やすことを言います。例えば1サーバへのアクセス負荷を分散させるために複数台サーバを用意するようなケースです。垂直スケールとは使用リソースを増やすことです。例えば、アプリケーションが起動するために必要なメモリが増えた場合、アプリケーションが使えるメモリを増やすようなケースです。

Kubernetes では自動で水平スケール、垂直スケールを行うことができます。

水平スケール:Horizontal Pod Autoscaler #

HPA (Horizontal Pod Autoscaler) を利用することで自動的に Pod 数を増減できます。HPA は通常 CPU やメモリの値に応じて Pod 数を増減しますが、任意のメトリクスを利用して増減させることも可能です。

HPA を利用するためには metrics-server をインストールする必要があります。せっかくなのでインストールして HPA を動かしてみましょう。

# インストールコマンド
kubectl apply --filename https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.6.4/components.yaml

# kind を使っている場合は次のコマンドも実行
kubectl patch --namespace kube-system deployment metrics-server --type=json \
  --patch '[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'

metrics-server が正常に起動していることを確かめましょう。

kubectl get deployment metrics-server --namespace kube-system

> NAME             READY   UP-TO-DATE   AVAILABLE   AGE
> metrics-server   1/1     1            1           40s

READY が 1/1、AVAILABLE が 1 になっていればオッケーです。次のようにマニフェストを書くことで水平スケールを実現します。

chapter-07/hpa-hello-server.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hpa-handson
  labels:
    app: hello-server
spec:
  selector:
    matchLabels:
      app: hello-server
  template:
    metadata:
      labels:
        app: hello-server
    spec:
      containers:
        - name: hello-server
          image: blux2/hello-server:1.8
          ports:
            - containerPort: 8080
          resources:
            requests:
              memory: "10Mi"
              cpu: "5m"
            limits:
              memory: "10Mi"
              cpu: "5m"
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: hello-server-hpa
spec:
  minReplicas: 1 # 👈️
  maxReplicas: 10 # 👈️
  metrics:
    - resource:
        name: cpu
        target:
          averageUtilization: 50 # 👈️
          type: Utilization
      type: Resource
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: hpa-handson
---
apiVersion: v1
kind: Service
metadata:
  name: hello-server-service
spec:
  selector:
    app: hello-server
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080

HPA のマニフェストでは minReplicas と maxReplicas を指定し、どれくらい Pod を増減するか決めます。増減を決めるためのメトリクスは metrics 以下に書きます。

target.averageUtilization にはアプリケーションの望ましい CPU 使用率を書きます。ここでは 50 としているため、CPU 使用率が 50% を下回るように Pod 数を増減させます。

ではこのマニフェストを使って実際にスケールするところを見てみましょう。

kubectl apply --filename chapter-07/hpa-hello-server.yaml --namespace default

しばらく HPA の様子を観察してみましょう。

kubectl get hpa --watch --namespace default

TARGETS が 0% から動かず、REPLICAS も1から増えませんね。わざと負荷をかけて Pod 数が増える様子を見てみましょう。先ほどとは別のターミナルを開き、次のコマンドを実行してください。

kubectl --namespace default run --stdin --tty load-generator --rm --image=busybox:1.28 --restart=Never -- /bin/sh -c "while sleep 0.01; do wget -q -O- http://hello-server-service.default.svc.cluster.local:8080; done"

負荷をかけ続けると、maxReplicas で指定した 10 個まで Pod が増えます。このように、負荷に応じて Pod がスケールするため、急な負荷に対応できるようになります。

kubectl get hpa --watch --namespace default

> NAME               REFERENCE                TARGETS         MINPODS   MAXPODS   REPLICAS   AGE
> hello-server-hpa   Deployment/hpa-handson   <unknown>/50%   1         10        1          16s
> hello-server-hpa   Deployment/hpa-handson   <unknown>/50%   1         10        1          30s
> hello-server-hpa   Deployment/hpa-handson   0%/50%          1         10        1          61s
> hello-server-hpa   Deployment/hpa-handson   20%/50%         1         10        1          106s
> hello-server-hpa   Deployment/hpa-handson   0%/50%          1         10        1          2m1s
> hello-server-hpa   Deployment/hpa-handson   20%/50%         1         10        1          2m45s
> hello-server-hpa   Deployment/hpa-handson   0%/50%          1         10        1          3m15s
> hello-server-hpa   Deployment/hpa-handson   80%/50%         1         10        1          3m30s
> hello-server-hpa   Deployment/hpa-handson   200%/50%        1         10        2          3m45s
> hello-server-hpa   Deployment/hpa-handson   180%/50%        1         10        4          4m
> hello-server-hpa   Deployment/hpa-handson   100%/50%        1         10        4          4m15s
> hello-server-hpa   Deployment/hpa-handson   110%/50%        1         10        8          4m30s
> hello-server-hpa   Deployment/hpa-handson   67%/50%         1         10        9          4m45s
> hello-server-hpa   Deployment/hpa-handson   60%/50%         1         10        10         5m1s

垂直スケール:Vertical Pod Autoscaler #

VPA (Vertical Pod Autoscaler) を利用することで、自動で Resource Requests/Limits の値を変更できます。しかし、VPA は先に説明した HPA と同じリソースに対して同時に利用することはできないため、実際には HPA のみを利用するケースが多いです。

VPA の詳細は公式ドキュメントをあたってください。

Node 退役に備えよう:PodDisruptionBudget(PDB) #

Deployment の仕組みで安全に Pod を更新することができましたが、カバーできるのはあくまで Pod を更新するときだけです。本番の運用環境では Node をメンテナンスするために Node から Pod を退避させるなど、Pod が増えたり減ったりするケースがよくあります。こういったケースでも Pod を安全に対比させるための機能の1つが PDB (Pod Desruption Budget) です。サービスのダウンが発生しても問題ないアプリケーション以外では必須の設定です。

英語をそのまま訳すと「Pod が破壊されるときの予算」です。

予算を設定しておくことで「予算を超えないように」Kubernetes が制御してくれます。それぞれ値は整数値(Pod の個数)かパーセンテージ(Pod の割合)を指定できます。

  • minAvailable:「最低いくつの Pod が利用可能な状態であるか」を指定する方法です。例えば minAvailable を5に指定した場合、5つの Pod が利用可能である必要があります。
  • maxUnavailable:「最大でいくつの Pod が利用不可能な状態であるか」を指定する方法です。例えば maxUnavailable を5に指定した場合、5つの Pod が利用不可能な状態でも問題ありません。

これらの minAvailable ないし maxUnavailable という予算を超えないように退避させる Pod 数を制御します。

例えば次の Deployment を用意したとします。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-server
  labels:
    app: hello-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hello-server
    template:
      metadata:
        labels:
          app: hello-server
      spec:
        containers:
          - name: hello-server
        image: blux2/hello-server:1.8

この Deployment は replicas が3ですが、必ず2つ以上の Pod が存在してほしいという要件があったとします。

この要件を満たすためには、次のような PDB を書くことになるでしょう。

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: hello-server-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: nello-server

この場合、レプリカ数3つのうち2つは必ず利用可能でなければいけません。例えば、何らかの理由で Pod が1つ Pending になってしまった場合、同時に app: hello-server の Pod が乗っている Node から Pod を退避できません。app: hello-server の Pod を Node から退避させることは「予算を越える」ことであり、Node のメンテナンスは Pending が解消するまで待たされます。

Chapter 8: 総復習 - アプリケーションを直そう #

(メモ省略)

Chapter 9: Kubernetes の仕組み、アーキテクチャを理解しよう #

    利用者/管理者
         │ kubectl
┌────────│────────────────────── Kubernetes Cluster ───────────────────────┐
│        │                                                                 │
│  ┌─────│────────────────────── Control Plane ─────────────┐              │
│  │     │                                                  │              │
│  │  ┌─ v ────────────┐       ┌─────────────────────────┐  │              │
│  │  │ kube-apiserver │<───────          etcd           │  │              │
│  │  │                │       └─────────────────────────┘  │              │
│  │  │                │       ┌─────────────────────────┐  │              │
│  │  │                │<───────     kube-scheduler      │  │              │
│  │  │                │       └─────────────────────────┘  │              │
│  │  │                │       ┌─────────────────────────┐  │              │
│  │  │                │<─────── kube-controller-manager │  │              │
│  │  └─ ^ ──────── ^ ─┘       └─────────────────────────┘  │              │
│  │     │          │                                       │              │
│  └─────│──────────│───────────────────────────────────────┘              │
│        │          │                                                      │
│  ┌─────│──────────│─────────── Worker Node 1 ──────────────────────────┐ │
│  │  ┌──│──────┐ ┌─│──────────┐                                         │ │
│  │  │ kubelet │ │ kube-proxy │                                         │ │
│  │  └──│──────┘ └────────────┘                                         │ │
│  │     │                                                               │ │
│  │  ┌──v────────────────┐          ┌─────────────────────────────────┐ │ │
│  │  │ Container Runtime │──────────> Pod 1                           │ │ │
│  │  └─────────┬─────────┘          │ ┌─────────────┐ ┌─────────────┐ │ │ │
│  │            │                    │ │ Container A │ │ Container B │ │ │ │
│  │            │                    │ └─────────────┘ └─────────────┘ │ │ │
│  │            │                    └─────────────────────────────────┘ │ │
│  │            │                    ┌─────────────────────────────────┐ │ │
│  │            └────────────────────> Pod 2                           │ │ │
│  │                                 │ ┌─────────────┐ ┌─────────────┐ │ │ │
│  │                                 │ │ Container C │ │ Container D │ │ │ │
│  │                                 │ └─────────────┘ └─────────────┘ │ │ │
│  │                                 └─────────────────────────────────┘ │ │
│  └─────────────────────────────────────────────────────────────────────┘ │
│  ┌──────────────────────────── Worker Node 2 ─────────────────────────┐  │
│  │                                                                    │  │
│  └────────────────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────────────────┘

Control Plane #

Control Plane のコンポーネントは kube-system の Namespace 内の Pod を参照することで実際に動いていることを確認できます。

kubectl get pod --namespace kube-system

> NAME                                         READY   STATUS    RESTARTS   AGE
> coredns-76f75df574-b5qg5                     1/1     Running   0          21s
> coredns-76f75df574-wgw9t                     1/1     Running   0          21s
> etcd-kind-control-plane                      1/1     Running   0          37s
> kindnet-dcvwp                                1/1     Running   0          21s
> kube-apiserver-kind-control-plane            1/1     Running   0          37s
> kube-controller-manager-kind-control-plane   1/1     Running   0          37s
> kube-proxy-cwx8h                             1/1     Running   0          21s
> kube-scheduler-kind-control-plane            1/1     Running   0          36s

kube-apiserver は REST で通信可能な API サーバです。etcd は分散型キーバリューストアであり、いわゆるデータベースの一種です。Control Plane は API サーバとデータベースでできていると思うと、一般的な Web サービスのような親近感が湧きませんか?

実際、kube-apiserver はユーザ(kubectl)からのリクエストを受けて etcd にデータを保存しています。また、kubectl get では etcd に保存してあるデータを kube-apiserver を通じて受け取っています。これらの操作は、kubectl で確認することもできます。

kubectl get pod に対して、どのようなリクエストが行われているかを見ることができます。https://127.0.0.1:53916/api/v1/namespaces/kube-system/pods?limit=500 に対して GET リクエストが行われ、200 OK のレスポンスが返ってきています。

kubectl get pod --v 7 --namespace kube-system

> I0722 00:25:48.210257   31323 loader.go:407] Config loaded from file:  /Users/me/.kube/config
> I0722 00:25:48.210593   31323 envvar.go:195] "Feature gate default state" feature="AtomicFIFO" enabled=true
> I0722 00:25:48.210600   31323 envvar.go:195] "Feature gate default state" feature="WatchListClient" enabled=true
> I0722 00:25:48.210603   31323 envvar.go:195] "Feature gate default state" feature="ClientsAllowTLSCacheGC" enabled=true
> I0722 00:25:48.210605   31323 envvar.go:195] "Feature gate default state" feature="InformerResourceVersion" enabled=true
> I0722 00:25:48.210607   31323 envvar.go:195] "Feature gate default state" feature="InOrderInformers" enabled=true
> I0722 00:25:48.210609   31323 envvar.go:195] "Feature gate default state" feature="ClientsAllowCARotation" enabled=true
> I0722 00:25:48.210610   31323 envvar.go:195] "Feature gate default state" feature="UnlockWhileProcessingFIFO" enabled=true
> I0722 00:25:48.210612   31323 envvar.go:195] "Feature gate default state" feature="ClientsPreferCBOR" enabled=false
> I0722 00:25:48.210613   31323 envvar.go:195] "Feature gate default state" feature="InOrderInformersBatchProcess" enabled=true
> I0722 00:25:48.210615   31323 envvar.go:195] "Feature gate default state" feature="ClientsAllowCBOR" enabled=false
> I0722 00:25:48.218866   31323 round_trippers.go:527] "Request" verb="GET" url="https://127.0.0.1:53916/api/v1/namespaces/kube-system/pods?limit=500" headers=<
>         Accept: application/json;as=Table;v=v1;g=meta.k8s.io,application/json;as=Table;v=v1beta1;g=meta.k8s.io,application/json
>         User-Agent: kubectl/v1.36.2 (darwin/arm64) kubernetes/24e2b02
> I0722 00:25:48.230038   31323 round_trippers.go:632] "Response" status="200 OK" milliseconds=10
> NAME                                         READY   STATUS    RESTARTS   AGE
> coredns-76f75df574-b5qg5                     1/1     Running   0          2m40s
> coredns-76f75df574-wgw9t                     1/1     Running   0          2m40s
> etcd-kind-control-plane                      1/1     Running   0          2m56s
> kindnet-dcvwp                                1/1     Running   0          2m40s
> kube-apiserver-kind-control-plane            1/1     Running   0          2m56s
> kube-controller-manager-kind-control-plane   1/1     Running   0          2m56s
> kube-proxy-cwx8h                             1/1     Running   0          2m40s
> kube-scheduler-kind-control-plane            1/1     Running   0          2m55s

kube-scheduler は Pod を Node にスケジュールする役割を担っています。

kube-controller-manager は Kubernetes を最低限動かすために必要な複数のコントローラを動かしています。コントローラについてはこれまであまり触れてきませんでしたが、「マニフェストに書かれている内容に応じて動作する」プログラム全般をコントローラと言います。例えば「replicas が3とマニフェストに書かれているので Pod を3つ用意する」のは Replication Controller の仕事です。ほかにも Node が落ちたときに通知してくれる Node Lifecycle Controller などがあります。

Worker Node #

Worker Node は、実際にアプリケーションコンテナの起動を行う Node です。Control Plane は冗長化を考慮して Node が3台程度で動くのに対して、Worker Node は規模によって 100 台動かすこともあります。では、各コンポーネントの詳細を見ていきましょう。

kubelet は Pod に紐づくコンテナを管理します。kubelet が起動している Node に Pod がスケジュールされると、コンテナランタイムに指示してコンテナを起動します。

kube-proxy は Kubernetes Service リソースなどに応じてネットワーク設定を行うコンポーネントです。kube-proxy によってクラスタ内外のネットワークセッションから Pod へのネットワーク通信が可能となります。

Container Runtime は Kubernetes 特有の技術ではありません。コンテナランタイムはソフトウェアの総称であり、具体的には containerd や CRI-O などがあげられます。コンテナを実行するソフトウェアです。

kubectl #

kube-apiserver は RESTful な API サーバですので、kukbectl なしでも curl などを利用して通信することは可能です。kubectl とは kube-apiserver と通信するための CLI ツールで、REST API 呼び出しのラッパーツールです。

kubectl apply してからコンテナが起動するまでの流れ #

kubectl apply --filename pod.yaml すると何が起こっているのか。かなりざっくりした説明にはなりますが、一連の流れはこのようになっています。

  1. kubectl から kube-apiserver に Pod の作成が指示され、etcd にマニフェストで指定した情報が保存されます。
  2. マニフェストに指定された内容をもとにスケジューラがどの Node にコンテナを起動すべきか決定します。
  3. kubelet は自分の Node にコンテナを起動すべきことを検知し、コンテナランタイムに指示してコンテナを起動します。

障害に強い Kubernetes #

Kubernetes は各コンポーネントが自立して動くアーキテクチャとなっています。

このおかげで、たとえば Control Plane のマシンが破壊されたとしてもアプリケーションは動き続けることができます。これは Control Plane が壊れたからと言って、Worker Node 上のコンテナが消えるわけではないからです。ただし、Control Plane が使えなくなるということは、それ以上のコンテナの更新ができなくなります。

実際に体験してみましょう。

既存クラスタがあれば一度削除しておきます。

kind delete cluster

以下のマニフェストを利用してクラスタを構築します。

kind/multinode-nodeport.yaml

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
  - role: worker
    extraPortMappings:
      - containerPort: 30599
        hostPort: 30599
        listenAddress: "127.0.0.1"
        protocol: TCP
  - role: worker
kind create cluster -n multinode-nodeport --config ./kind/multinode-nodeport.yaml --image=kindest/node:v1.29.0

kubectl get node

> NAME                               STATUS   ROLES           AGE   VERSION
> multinode-nodeport-control-plane   Ready    control-plane   28s   v1.29.0
> multinode-nodeport-worker          Ready    <none>          8s    v1.29.0
> multinode-nodeport-worker2         Ready    <none>          7s    v1.29.0

想定通り、マルチノードで立ち上がっています。

hello-server を起動しましょう。マニフェストは以下を利用します。

chapter-09/hello-server.yaml

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-server
  labels:
    app: hello-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hello-server
  template:
    metadata:
      labels:
        app: hello-server
    spec:
      containers:
        - name: hello-server
          image: blux2/hello-server:1.8
          resources:
            requests:
              memory: "256Mi"
              cpu: "10m"
            limits:
              memory: "256Mi"
              cpu: "10m"
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 5
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: hello-server-pdb
spec:
  maxUnavailable: 10%
  selector:
    matchLabels:
      app: hello-server
---
apiVersion: v1
kind: Service
metadata:
  name: hello-server-external
spec:
  type: NodePort
  selector:
    app: hello-server
  ports:
    - port: 8080
      targetPort: 8080
      nodePort: 30599
kubectl apply --filename chapter-09/hello-server.yaml --namespace default

kubectl get pod --namespace default

> NAME                          READY   STATUS              RESTARTS   AGE
> hello-server-965f5b86-89k48   0/1     ContainerCreating   0          12s
> hello-server-965f5b86-s7nnl   0/1     ContainerCreating   0          12s
> hello-server-965f5b86-xqkbx   0/1     ContainerCreating   0          12s

hello-server への疎通を確認しましょう。まずは Node の IP を取得します。

kubectl get node multinode-nodeport-worker -o jsonpath='{.status.addresses[?(@.type=="InternalIP")].address}'

> 172.19.0.3

取得した InternalIP を利用してアクセスしましょう。

curl 172.19.0.3:30599
# kind の場合 curl localhost:30599 でアクセスできます

> Hello, world! Let's learn Kubernetes!

アプリケーションが問題なく動いています。

では、いきなり Control Plane を停止しましょう。kind を利用している方は Control Plane 用の Docker コンテナを止めます。まずは docker コンテナ ID を確認します。

docker ps

> CONTAINER ID   IMAGE                  COMMAND                  CREATED         STATUS         PORTS                        NAMES
> bdb8f0021e94   kindest/node:v1.29.0   "/usr/local/bin/entr…"   7 minutes ago   Up 7 minutes                                multinode-nodeport-worker2
> 9b60dbc81f75   kindest/node:v1.29.0   "/usr/local/bin/entr…"   7 minutes ago   Up 7 minutes   127.0.0.1:56742->6443/tcp    multinode-nodeport-control-plane
> 21bdfd9ec162   kindest/node:v1.29.0   "/usr/local/bin/entr…"   7 minutes ago   Up 7 minutes   127.0.0.1:30599->30599/tcp   multinode-nodeport-worker

multinode-nodeport-control-plane と書かれているコンテナの CONTAINER ID をコピーしてコンテナを止めます。

docker stop 9b60dbc81f75

再び curl するとどうなるでしょうか?問題なくレスポンスが返ってくるはずです。

pod get で STATUS を見てみます。

kubectl get pod --namespace default

> The connection to the server 127.0.0.1:56742 was refused - did you specify the right host or port?

接続できないというエラーになりました。これは Control Plane が停止したため、kube-apiserver に接続できなくなっていることが理由です。

しかし、Control Plane が停止したとしてもコンテナは起動し続けます。kube-apiserver と接続できないためコンテナの更新や Pod 数の増減は行えませんが、少なくともサービスの稼働が即座に損なわれるということはありません。

Kubernetes が障害に強いと言われる理由がわかったでしょうか。

Control Plane を起動し直すことで、今までどおり kubectl が使えるようになります。

docker start 9b60dbc81f75
kubectl get pod --namespace default
NAME                          READY   STATUS    RESTARTS   AGE

> hello-server-965f5b86-89k48   1/1     Running   0          13m
> hello-server-965f5b86-s7nnl   1/1     Running   0          13m
> hello-server-965f5b86-xqkbx   1/1     Running   0          13m

最後にクラスタごと掃除しておきましょう。

kind delete cluster -n multinode-nodeport

デフォルトクラスタを立ち上げ直しておきます。

kind create cluster --image=kindest/node:v1.29.0

Kubernetes を拡張する方法 #

Kubernetes の特徴の1つとして、「Kubernetes ユーザが自分で Kubernetes を拡張できる」ことが挙げられます。ここではどのような仕組みで拡張が可能か、ということを紹介し、ハンズオンは省略します。

これまで Pod や Deployment などの " リソース " を作成してきましたが、Kubernetes が標準で用意しているリソースでは物足りないことがあります。

例えば、Argo CD という OSS は「リポジトリ名、パス名を指定すると指定場所に書かれているマニフェストを参照して自動でデプロイを行う」ということが可能です。しかし、既存のリソースを使ってもこの機能を実現することはできません。「リポジトリ名」「パス名」を指定するリソースと、このリソース内容をもとに「自動デプロイを行う」プログラムが必要になってきます。

ここでいう独自リソースが「Custom Resource (CR)」であり、CR を作るために必要な定義が「Custom Resource Definition (CRD)」です。

また、CR を参照して動くプログラムを「カスタムコントローラ」と言います。ここでいうコントローラとは、kube-controller-manager で説明したコントローラと同じ概念です。

Kubernetes 標準で搭載されている Deployment Controller は kube-controller-manager に内包されていますが、各自が Kubernetes を「カスタム」するために使うコントローラがカスタムコントローラです。

ここで、ConfigMap に必要な情報を書いておき、その情報を読むようなプログラムを書けばよいのでは、と思われるかもしれません。Kubernetes 公式ドキュメントには、「Should I use a ConfigMap or a custom resource?」とう項目で判断基準となる例をあげています。

https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#should-i-use-a-configmap-or-a-custom-resource

次のうち1つでも当てはまるものがあれば ConfigMap を使うと良いと書かれています。

  • 既存の、よく文書化された設定ファイル形式がある。例えば、mysql.cnf や pom.xml
  • 設定全体を ConfigMap の1つのキーに入れたい
  • Pod で動作するプログラムが、自身を設定するためにそのファイルを利用する
  • Kuberntes API ではなく、Pod のファイルや Pod の環境変数を通じて利用したい
  • ファイルが更新された時、Deployment などを使ってローリングアップデートを実施したい

Kubernetes の開発ワークフローを理解しよう #

これまでのハンズオンで Kubernetes をデプロイするために kubectl apply --filename を実施してきましたが、継続的にデプロイするためには次の課題があります。

  • いつ誰がコマンド実施したかわからない
  • コマンドの実施により、マニフェストの衝突が起きてしまう
  • 毎回手動で実行するのでは、手間がかかる。さらにヒューマンエラーも起きやすい

この課題を解決するためのデプロイ方法として、Kubernetes を利用するケースでは大きく分けて CIOps と GitOps があります。いずれの場合でも GitHub など共有リポジトリを利用することでマニフェストの衝突・差分の管理を行っています。

Push 型のデプロイ方法:CIOps #

例えば「main ブランチにマージされたら CI で kubectl apply を実行する」ようなケースを CIOps と呼びます。わかりやすく、実現しやすい方法です。デメリットとしては、CI/CD ツールに強い権限が必要であることや、デプロイ用のスクリプトが長く複雑になりがちということです。

Pull 型のデプロイ方法:GitOps #

一定間隔で対象を Pull し、動作する必要がある内容を Pull したタイミングで kubectl apply するのが Pull 型です。この方法は GitOps と呼ばれます。CIOps と比べると複雑ですが、Pull 型であることで次のメリットが得られます。

  • CIOps では CI から Kubernetes クラスタにマニフェストを適用する性質上、書き込み権限が必要になり、CI の認証情報が盗まれたときのセキュリティリスクになります。GitOps では読み取り権限さえあれば実現可能で、CIOps よりはリスクが低いです。
  • CIOps では CI も CD も CI ツールで実行するため、規模が大きくなると処理が遅くなったり、管理が大変になるかもしれません。GitOps では既存の CI とは切り離した仕組みで実行、管理できます。

ここまで述べた GitOps は概念です。ではどのように実際にワークフローに組み込むのでしょうか?GitOps を実現するためのソフトウェアが OSS として公開されているので代表的なものをあげておきます。

  • Argo CD:https://argoproj.github.io/cd/
    • Custom Resource の仕組みを使って、Argo CD 自身も Kubernetes 上に構築します。
  • Spinnaker:https://spinnaker.io/
    • 元々は Netflix 社が開発したツールです。Argo CD が Kubernetes 向けをうたっているのに対し、Spinnaker は Kubernetes 以外にも主要なクラウドプロバイダに対応していることを売りにしています。
  • FluxCD:https://fluxcd.io/
    • Kubernetes 向けツールです。GitOps を提唱した Weaveworks 社が元々開発をしていました。

Kubenertes のマニフェスト管理 #

マニフェストのファイルが増えてくると管理が大変になります。似たようなマニフェストがあると、最初はコピーアンドペーストで良いとしてもそのうち共通化したくなるでしょう。

マニフェストをよりわかりやすく管理するための方法やツールがあるのでいくつか紹介します。

Helm #

Helm(https://helm.sh/)はパッケージマネージャとなっており、マニフェストを作成する以上のことが可能です。Chart というテンプレートを元に、helm install することで Kubernetes クラスタにマニフェストをデプロイする仕組みになっています。

後述する Kustomize に比べて「テンプレート」と言われたときに想像する形式と近い(例:jinja2 など)ため、シンプルでわかりやすいです。一方でテンプレートに書かれていること以上のことをやりたくなった場合、別の方法を検討する必要が出てきます。

Helm はほかの開発者が開発したカスタムコントローラ用のマニフェストを利用したいケースでとくに有効です。簡単に Helm を使ってみましょう。Grafana というダッシュボードを表示する OSS をインストールします。

まずは Helm をインストールしましょう。

https://helm.sh/ja/docs/intro/install

Helm Chart をインストールする前に Helm Chart Repository を追加する必要があります。次のコマンドで Prometheus 用のリポジトリを追加しましょう。

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

インストール先の namespace を事前に作成しておきます。

kubectl create namespace monitoring

helm install を実行します。次のコマンドでインストールが可能です。helm install <任意のリソース名> --namespace monitoring <Chart 名>

helm install kube-prometheus-stack --namespace monitoring prometheus-community/kube-prometheus-stack

> NAME: kube-prometheus-stack
> LAST DEPLOYED: Wed Jul 22 10:18:55 2026
> NAMESPACE: monitoring
> STATUS: deployed
> REVISION: 1
> DESCRIPTION: Install complete
> TEST SUITE: None
> NOTES:
> kube-prometheus-stack has been installed. Check its status by running:
>   kubectl --namespace monitoring get pods -l "release=kube-prometheus-stack"
>
> Get Grafana 'admin' user password by running:
>   kubectl --namespace monitoring get secrets kube-prometheus-stack-grafana -o jsonpath="{.data.admin-password}" | base64 -d ; echo
>
> Access Grafana local instance:
>
>   export POD_NAME=$(kubectl --namespace monitoring get pod -l "app.kubernetes.io/name=grafana,app.kubernetes.io/instance=kube-prometheus-stack" -oname)
>   kubectl --namespace monitoring port-forward $POD_NAME 3000
>
> Get your grafana admin user password by running:
>   kubectl get secret --namespace monitoring -l app.kubernetes.io/component=admin-secret -o jsonpath="{.items[0].data.admin-password}" | base64 --decode ; echo
>
> Visit https://github.com/prometheus-operator/kube-prometheus for instructions on how to create & configure Alertmanager and Prometheus instances using the Operator.

Get Grafana 'admin' user password by running の下に書かれているコマンドを実行すると Grafana の admin パスワードが取得できます。コピーしておきましょう。

kubectl --namespace monitoring get secrets kube-prometheus-stack-grafana -o jsonpath="{.data.admin-password}" | base64 -d ; echo

しばらくすると、いくつも Pod が立ち上がっていることが確認できます。少し時間がかかるかもしれませんので、気長に待ちましょう。

kubectl get pod --namespace monitoring

せっかくなのでダッシュボードのログイン画面を表示してみましょう。自動生成された Service を使って、ポートフォワードします。

kubectl port-forward service/kube-prometheus-stack-grafana --namespace monitoring 8080:80

ブラウザで http://localhost:8080 にアクセスすると Grafana のログイン画面が表示されます。以下でログインすることもできます。

  • username: admin
  • password: 先ほどコピーしたもの

このままだと Helm の便利さが伝わらないと思うのでもう少し詳しく説明します。Helm Chart はテンプレートだという話をしましたが、次のコマンドを打つとどのような値を設定してカスタマイズ可能かがわかります。

helm show values prometheus-community/kube-prometheus-stack

ここで得られた値がデフォルトの設定になっています。このデフォルト設定を変更するためには変更する設定値を書いた values.yaml をローカルに保存し、helm install の引数として設定します。こうすることで独自カスタマイズされた状態で Custom Controller を環境にデプロイできます。

例えば、admin のデフォルトのパスワードを変更したい場合は以下です。

values.yaml

grafana:
  adminPassword: secure-password

Jsonnet #

https://jsonnet.org/

Helm よりも、後述の Kustomize よりもかなり柔軟性が高いツールとなっています。Jsonnet 自体は Kubernetes に特化したツールなわけではありません。JSON をプログラマブルに扱うことができるため、柔軟性が高く何でもできますが、複雑なことを行おうとするとその分学習コストが必要になり、また独自記法に慣れる必要があります。

Kustomize #

https://kustomize.io/

環境ごとにマニフェストが少しだけ異なる場合、なるべく修正を少なくするために差分だけ管理したいこともあるでしょう。このようなケースで利用できるのが Kustomize です。具体的には、マニフェストの共通部分は base というディレクトリで管理し、環境ごとの差分を overlays というディレクトリ内のマニフェストで管理する、という使い方が可能です。

最終成果物は kustomize というツールを利用してビルドします。kustomize は非常に便利です。

base ディレクトリと overlays ディレクトリがあるという説明をしましたが、ビルドをするときに kustomization.yaml というファイルが参照されます。この kustomization.yaml に、具体的にどのディレクトリ(ファイル)を base として定義し、どのディレクトリ(ファイル)を overlays として定義するかを書きます。まずはディレクトリ構成の例は次のとおりです。この例では base には共通のマニフェストが定義されており、staging/production で各環境固有の設定が書かれています。

hello-server/
├── base/
│   ├── deployment.yaml
│   ├── kustomization.yaml
│   └── service.yaml
└── overlays/
    ├── production/
    │   ├── deployment.yaml
    │   └── kustomization.yaml
    └── staging/
        ├── deployment.yaml
        └── kustomization.yaml

まずは kustomize をインストールしましょう。

https://kubectl.docs.kubernetes.io/installation/kustomize/

このハンズオンでは次のマニフェストを使用します。

chapter-10/hello-server.yaml

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-server
  labels:
    app: hello-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hello-server
  template:
    metadata:
      labels:
        app: hello-server
    spec:
      containers:
        - name: hello-server
          image: blux2/hello-server:1.8
          resources:
            requests:
              memory: "256Mi"
              cpu: "10m"
            limits:
              memory: "256Mi"
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 5
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: hello-server-pdb
spec:
  maxUnavailable: 10%
  selector:
    matchLabels:
      app: hello-server

次の要件を満たすように各ディレクトリ内のマニフェストを書いていきましょう。

  • production と staging 環境にデプロイしたい
  • production の replicas は 10 にしたい
  • production の requests.memory と requests.limit は 1Gi にしたい
  • staging では PodDisruptionBudget は必要ない

このマニフェストを分割して次のようにしたものが完成形です。

chapter-10/kustomize/hello-server/base/kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - deployment.yaml

chapter-10/kustomize/hello-server/base/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-server
  labels:
    app: hello-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hello-server
  template:
    metadata:
      labels:
        app: hello-server
    spec:
      containers:
        - name: hello-server
          image: blux2/hello-server:1.8
          resources:
            requests:
              memory: "256Mi"
              cpu: "10m"
            limits:
              memory: "256Mi"
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 5

chapter-10/kustomize/hello-server/overlays/production/kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ../../base
  - pdb.yaml
patches:
  - path: deployment.yaml

chapter-10/kustomize/hello-server/overlays/production/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-server
spec:
  replicas: 10
  template:
    spec:
      containers:
        - name: hello-server
          resources:
            requests:
              memory: "1Gi"
            limits:
              memory: "1Gi"

chapter-10/kustomize/hello-server/overlays/production/pdb.yaml

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: hello-server-pdb
spec:
  maxUnavailable: 10%
  selector:
    matchLabels:
      app: hello-server

chapter-10/kustomize/hello-server/overlays/staging/kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ../../base

その後、kustomize build を実行しビルド結果を確認します。

kustomize build ./overlays/staging

> apiVersion: apps/v1
> kind: Deployment
> metadata:
>   labels:
>     app: hello-server
>   name: hello-server
> spec:
>   replicas: 3
>   selector:
>     matchLabels:
>       app: hello-server
>   template:
>     metadata:
>       labels:
>         app: hello-server
>     spec:
>       containers:
>       - image: blux2/hello-server:1.8
>         livenessProbe:
>           httpGet:
>             path: /health
>             port: 8080
>           initialDelaySeconds: 10
>           periodSeconds: 5
>         name: hello-server
>         readinessProbe:
>           httpGet:
>             path: /health
>             port: 8080
>           initialDelaySeconds: 5
>           periodSeconds: 5
>         resources:
>           limits:
>             memory: 256Mi
>           requests:
>             cpu: 10m
>             memory: 256Mi

kustomize build ./overlays/production

> apiVersion: apps/v1
> kind: Deployment
> metadata:
>   labels:
>     app: hello-server
>   name: hello-server
> spec:
>   replicas: 10
>   selector:
>     matchLabels:
>       app: hello-server
>   template:
>     metadata:
>       labels:
>         app: hello-server
>     spec:
>       containers:
>       - image: blux2/hello-server:1.8
>         livenessProbe:
>           httpGet:
>             path: /health
>             port: 8080
>           initialDelaySeconds: 10
>           periodSeconds: 5
>         name: hello-server
>         readinessProbe:
>           httpGet:
>             path: /health
>             port: 8080
>           initialDelaySeconds: 5
>           periodSeconds: 5
>         resources:
>           limits:
>             memory: 1Gi
>           requests:
>             cpu: 10m
>             memory: 1Gi
> ---
> apiVersion: policy/v1
> kind: PodDisruptionBudget
> metadata:
>   name: hello-server-pdb
> spec:
>   maxUnavailable: 10%
>   selector:
>     matchLabels:
>       app: hello-server

この出力を使って次のように apply します。今回は staging 環境としてデプロイしてみましょう。

kustomize build ./overlays/staging | kubectl apply --namespace default -f -

これで終わりです。最後に掃除をしておきましょう。今回は kustomize build したマニフェストを apply しているため、delete も同様に kustomize build したマニフェストを使って行います。

kustomize build ./overlays/staging | kubectl delete --namespace default -f -