Kubernetes をゼロからプロレベルまで: kubectl、マニフェスト、Helm、GitOps の 100 の例

Kubernetes をゼロからプロレベルまで: kubectl、マニフェスト、Helm、GitOps の 100 の例

Kubernetes は、複数のマシンからなるクラスター全体でコンテナーを動かします。失敗したコンテナーを再起動し、ノードに分散させ、ダウンタイムなしで新しいバージョンを展開し、負荷に応じてスケールします。この記事では、最初の Pod から本番運用を意識した構成まで、100 の例で進みます。kubectl とコンテキスト、Pod、Deployment とローリングアップデート、Service と Ingress、ConfigMap と Secret、永続ストレージ、Job と CronJob、StatefulSet と DaemonSet、オートスケーリング、リソース要求とプローブ、スケジューリング、RBAC と Pod のセキュリティ、Helm と Kustomize、可観測性とデバッグ、GitOps とバックアップを学びます。正直に伝えると、この記事を準備したコンピューターには Kubernetes クラスターも kubectl もインストールされていなかったため、100 の例はここでは一つも実行していません。マニフェストは現在の Kubernetes API に従って書いていますが、信頼する前にテスト用クラスターでご自身が適用してください。

必要なもの

組み込みの Kubernetes を備えた Docker Desktop、またはローカルの練習用クラスター用の kind か minikube、さらに kubectl と Helm。コンテナーの基礎知識があると役立ちます。このシリーズの Docker の記事が良い出発点です。一部の例では Azure Kubernetes Service などのクラウドプロバイダーと ingress controller を使います。shop.example.com のようなプレースホルダーのホスト名と、<strong-password> のようなプレースホルダーのパスワードを使い、本物の認証情報をマニフェストやコマンドに貼り付けないでください。

例の使い方

bash の例は kubectl、Helm、Kustomize を使います。YAML の例は .yaml ファイルに保存し、本文で指定されたファイル名を使って kubectl apply -f で適用します。Python の例は公式の Kubernetes Python クライアントを使います。

トピック 1: Kubernetes の基本

Kubernetes は制御ループです。目的の状態を YAML で記述すると、現実が一致するまでクラスターが働き続けます。クラスターの構成要素、kubectl クライアント、コンテキスト、名前空間を学びます。

コントロールプレーンとノード

コントロールプレーンは目的の状態を保存し、判断を行います。ワーカーノードはコンテナーを実行します。API サーバーを通じてクラスターと通信し、kubectl はリクエストを送る標準クライアントです。

コンテキストと名前空間

コンテキストは、コマンドが使うクラスター、ユーザー、名前空間を保存します。何かを変更する前に kubectl config current-context で確認し、誤ったクラスターのリソースを削除しないようにします。

マニフェストと宣言的な変更

マニフェストには apiVersion、kind、metadata、spec があります。kubectl apply はファイルとクラスターを比べ、違う部分だけを変更します。これにより変更を繰り返し行え、Git でレビューしやすくなります。

図解

Kubernetes でリクエストがアプリケーションに届く流れ: ユーザーが Ingress に到達し、Ingress が Service へ振り分け、Service は Deployment が稼働させ続ける正常な Pod へトラフィックを送ります
Kubernetes でリクエストがアプリケーションに届く流れ: ユーザーが Ingress に到達し、Ingress が Service へ振り分け、Service は Deployment が稼働させ続ける正常な Pod へトラフィックを送ります

例 1: クライアントとクラスターを確認

kubectl がクラスターと通信できるかを確かめます。

kubectl version --client
kubectl cluster-info
kubectl get nodes -o wide

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 2: kind で練習用クラスターを作成

学習用に自分のコンピューターで動くクラスターです。

kind create cluster --name learn
kubectl config current-context
kubectl get nodes

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 3: コンテキストを切り替える

コマンドがどのクラスターに向くかを選びます。

kubectl config get-contexts
kubectl config use-context kind-learn
kubectl config view --minify --output "jsonpath={.current-context}"

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 4: Namespace を作成

1 つのクラスター内でチームやアプリを分けます。

kubectl create namespace shop
kubectl config set-context --current --namespace=shop
kubectl get pods

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 5: マニフェストを読む

すべてのオブジェクトにある 4 つのトップレベルの項目です。

apiVersion: v1
kind: Namespace
metadata:
  name: shop
  labels:
    team: platform

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 6: マニフェストを適用

kubectl apply で宣言的に変更します。

kubectl apply -f namespace.yaml
kubectl get namespace shop --show-labels
kubectl describe namespace shop

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

トピック 2: Pod: 最小の単位

Pod は、ネットワークアドレスとストレージを共有する 1 つ以上のコンテナーです。ほとんどの Pod は 1 つのコンテナーで動きますが、メインのアプリと並んで動く必要のあるヘルパーを同じ Pod に入れることもできます。

Pod とは

Pod 内のすべてのコンテナーは同じ IP アドレスを共有し、localhost で互いに通信できます。Pod は一時的なものです。1 つが停止すると、クラスターは新しい名前とアドレスで作り直すため、Pod のアドレスに依存してはいけません。

init コンテナーとサイドカー

init コンテナーはメインのコンテナーが始まる前に完了まで実行されます。データベースの起動を待つ用途に向いています。サイドカーはログ記録、プロキシ、ファイル同期のためにメインのコンテナーと並んで動き、ボリュームを共有します。

ログ、イベント、exec

kubectl logs は出力を表示し、kubectl describe はイメージの取得失敗やスケジューリングの失敗などのイベントを表示し、kubectl exec はシェルを開きます。起動の失敗の多くは describe で説明されるので、まず describe を読んでください。

例 7: Pod をすばやく実行

命令型のコマンドで、簡単なテストだけに使います。

kubectl run hello --image=nginx:1.27 --port=80
kubectl get pod hello -o wide

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 8: 宣言的な Pod

デプロイする最小の単位です。

apiVersion: v1
kind: Pod
metadata:
  name: web
  labels:
    app: web
spec:
  containers:
    - name: web
      image: nginx:1.27
      ports:
        - containerPort: 80

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 9: ログとイベントを読む

Pod に何が起きたかを調べます。

kubectl logs web
kubectl logs web --previous
kubectl get events --sort-by=.lastTimestamp

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 10: Pod の中でコマンドを実行

内側からデバッグします。

kubectl exec -it web -- sh
kubectl exec web -- nginx -v

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 11: Pod へのポートフォワード

テストのためにご自身のコンピューターから Pod に接続します。

kubectl port-forward pod/web 8080:80
curl http://localhost:8080

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 12: 2 つのコンテナーがボリュームを共有

サイドカーのパターンです。

apiVersion: v1
kind: Pod
metadata:
  name: sidecar-demo
spec:
  volumes:
    - name: logs
      emptyDir: {}
  containers:
    - name: app
      image: busybox:1.36
      command: ["sh", "-c", "while true; do date >> /logs/app.log; sleep 5; done"]
      volumeMounts:
        - name: logs
          mountPath: /logs
    - name: log-reader
      image: busybox:1.36
      command: ["sh", "-c", "tail -F /logs/app.log"]
      volumeMounts:
        - name: logs
          mountPath: /logs

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 13: init コンテナーが待機

アプリが起動する前に準備の手順を実行します。

apiVersion: v1
kind: Pod
metadata:
  name: init-demo
spec:
  initContainers:
    - name: wait-for-db
      image: busybox:1.36
      command: ["sh", "-c", "until nslookup db; do sleep 2; done"]
  containers:
    - name: app
      image: nginx:1.27

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 14: テスト用の Pod を削除

不要になったオブジェクトを片付けます。

kubectl delete pod hello web sidecar-demo init-demo --ignore-not-found
kubectl get pods

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

トピック 3: Deployment とローリングアップデート

Pod を直接作ることはほとんどありません。Deployment は ReplicaSet を管理します。ReplicaSet は指定数の Pod を稼働させ続け、Deployment は新しいバージョンが古いものを少しずつ置き換えるようにローリングアップデートを行います。

レプリカ数とセレクター

replicas フィールドは必要な Pod の数を設定します。セレクターは Pod のラベルと一致しなければならず、Deployment がどの Pod を所有するかを判断します。一致しない場合は作成時に拒否されます。

ローリングアップデートの戦略

maxSurge は更新中に存在してよい追加の Pod 数、maxUnavailable は欠けてもよい Pod 数を設定します。maxUnavailable を 0 にすると、リリース中も容量を完全に保てます。

ロールアウト履歴とロールバック

変更のたびに新しい ReplicaSet とリビジョン番号が作られます。kubectl rollout undo で前のバージョンに戻り、kubectl rollout history でリビジョンを一覧できるので、特定のものを選べます。

例 15: Deployment がレプリカを維持

ローリングアップデートを自動で管理します。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: nginx:1.27
          ports:
            - containerPort: 80

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 16: Deployment を作成して監視

ロールアウトの状態を確認します。

kubectl apply -f deployment.yaml
kubectl rollout status deployment/web
kubectl get deploy,rs,pods -l app=web

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 17: 手動でスケール

レプリカ数を変えます。

kubectl scale deployment web --replicas=5
kubectl get pods -l app=web -o wide

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 18: 新しいイメージをロールアウト

アプリケーションのバージョンを更新します。

kubectl set image deployment/web web=nginx:1.27.2
kubectl rollout status deployment/web
kubectl rollout history deployment/web

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 19: 問題のあるリリースをロールバック

失敗したロールアウトを元に戻します。

kubectl rollout undo deployment/web
kubectl rollout undo deployment/web --to-revision=1

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 20: ローリングアップデートを制御

maxSurge と maxUnavailable。

spec:
  replicas: 4
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 21: ロールアウトを一時停止と再開

複数の変更をまとめて適用します。

kubectl rollout pause deployment/web
kubectl set resources deployment/web -c web --limits=memory=256Mi
kubectl rollout resume deployment/web

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 22: readiness probe

アプリの準備ができるまでトラフィックを待たせます。

containers:
  - name: web
    image: nginx:1.27
    readinessProbe:
      httpGet:
        path: /
        port: 80
      initialDelaySeconds: 3
      periodSeconds: 5

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

トピック 4: Service とネットワーク

Pod は現れたり消えたりするため、クライアントには安定した名前とアドレスが必要です。Service はラベルで Pod を選び、変わらない仮想 IP と DNS 名を与えます。

Service の種類

ClusterIP はクラスター内からのみ到達できます。NodePort はすべてのノードでポートを開きます。LoadBalancer はクラウドプロバイダーに外部のロードバランサーを要求します。必要を満たす最小の種類を選んでください。

DNS とエンドポイント

すべての Service には web-svc.shop.svc.cluster.local のような DNS 名が付き、同じ名前空間内では短い名前でも解決できます。kubectl get endpoints は Service の背後にある Pod のアドレスを示します。Service が応答しないときに最初に確認すべき点です。

ヘッドレス Service

clusterIP を None に設定すると、DNS は 1 つの仮想 IP ではなく個々の Pod のアドレスを返します。データベースやクラスター化されたシステムは、クライアントが特定のメンバーと話せるようにこれを使います。

例 23: ClusterIP の Service

クラスター内の安定したアドレスです。

apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  type: ClusterIP
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 24: kubectl expose で公開

Service を作る手早い方法です。

kubectl expose deployment web --port=80 --target-port=80 --name=web-svc
kubectl get svc web-svc
kubectl get endpoints web-svc

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 25: 一時的な Pod から Service を呼び出す

クラスター内の DNS を確かめます。

kubectl run tmp-client --rm -it --image=busybox:1.36 --restart=Never -- wget -qO- http://web-svc

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 26: NodePort の Service

すべてのノードでポートを開きます。

apiVersion: v1
kind: Service
metadata:
  name: web-nodeport
spec:
  type: NodePort
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30080

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 27: LoadBalancer の Service

公開アクセス用のクラウドのロードバランサーです。

apiVersion: v1
kind: Service
metadata:
  name: web-public
  annotations:
    service.beta.kubernetes.io/azure-load-balancer-internal: "false"
spec:
  type: LoadBalancer
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 28: 外部アドレスを確認

クラウドが割り当てたアドレスを読みます。

kubectl get svc web-public -o jsonpath="{.status.loadBalancer.ingress[0].ip}"
kubectl get svc web-public -w

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 29: 名前付きポート

ポート名を使えば番号を安全に変えられます。

apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  selector:
    app: api
  ports:
    - name: http
      port: 8000
      targetPort: http

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 30: ヘッドレス Service

個々の Pod のアドレスを取得します。

apiVersion: v1
kind: Service
metadata:
  name: db-headless
spec:
  clusterIP: None
  selector:
    app: db
  ports:
    - port: 5432

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

トピック 5: ConfigMap と Secret による設定

設定はイメージの外に置きましょう。ConfigMap は通常の設定を、Secret は機密の値を保持します。どちらも Pod の中で環境変数やファイルになれるので、イメージを作り直さずに設定を変えられます。

ConfigMap

ConfigMap はキーと値のペアや、ファイル全体を保存します。envFrom ですべてのキーを環境変数として読み込むか、アプリケーションがファイルを読む場合は ConfigMap をディレクトリとしてマウントします。

Secret

Secret は base64 でエンコードされた値を保存します。base64 は暗号化ではなくエンコーディングです。そのため、クラスターの保存時暗号化を有効にし、RBAC で Secret を読める人を制限し、可能な限りファイルとしてマウントしてください。

変更の再読み込み

環境変数は起動時にだけ読まれるため、変更された ConfigMap には新しいロールアウトが必要です。マウントされたファイルは遅れて更新されますが、アプリケーションがそれを読み直す必要があります。設定を変えるときは再読み込みも計画してください。

例 31: リテラル値から ConfigMap

秘密ではない設定を保存します。

kubectl create configmap app-config --from-literal=LOG_LEVEL=info --from-literal=FEATURE_X=on
kubectl get configmap app-config -o yaml

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 32: ConfigMap のマニフェスト

設定を YAML で書きます。

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  LOG_LEVEL: info
  FEATURE_X: "on"

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 33: ConfigMap を環境変数として使う

envFrom ですべてのキーを読み込みます。

containers:
  - name: api
    image: sample-api:1.4.2
    envFrom:
      - configMapRef:
          name: app-config

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 34: ConfigMap をファイルとしてマウント

コンテナー内の設定ファイルです。

containers:
  - name: web
    image: nginx:1.27
    volumeMounts:
      - name: site-config
        mountPath: /etc/nginx/conf.d
        readOnly: true
volumes:
  - name: site-config
    configMap:
      name: nginx-conf

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 35: Secret を作成

パスワードをファイルに書かずに保存します。

kubectl create secret generic db-secret --from-literal=DB_PASSWORD='<strong-password>'
kubectl get secret db-secret -o jsonpath="{.type}"

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 36: Secret のマニフェスト

stringData でプレーンテキストを書けます。

apiVersion: v1
kind: Secret
metadata:
  name: db-secret
type: Opaque
stringData:
  DB_PASSWORD: <strong-password>

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 37: Secret の値を読む

復号された値を確認し、非公開に保ちます。

kubectl get secret db-secret -o jsonpath="{.data.DB_PASSWORD}" | base64 --decode; echo
# Do not print secrets in shared logs or screenshots.

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 38: Secret をファイルとしてマウント

読み取り専用の認証情報ファイルです。

containers:
  - name: api
    image: sample-api:1.4.2
    volumeMounts:
      - name: db-creds
        mountPath: /var/run/secrets/db
        readOnly: true
volumes:
  - name: db-creds
    secret:
      secretName: db-secret

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

トピック 6: 永続ストレージ

コンテナーは再起動するとファイルを失います。PersistentVolumeClaim は Pod の寿命を超えて残るストレージを要求し、StorageClass はそのストレージをどう作るかをクラスターに伝えます。

PV、PVC、StorageClass

PersistentVolume は実際のストレージ、PersistentVolumeClaim はそれへの要求、StorageClass は必要に応じてボリュームを作る方法を定義します。多くのクラスターは既定の StorageClass を備えているため、claim にはサイズだけを指定すれば足りることが多いです。

アクセスモード

ReadWriteOnce は一度に 1 つのノードでボリュームをマウントします。ほとんどのデータベースに適しています。ReadWriteMany は複数のノードから書き込めますが、対応しているストレージは限られます。ストレージが対応しているモードを選んでください。

回収ポリシー

Delete は claim を削除したときにストレージも削除します。Retain はデータを残し、手動での復旧を可能にします。誤った削除から守りたいデータには Retain を使い、いずれの場合も重要なデータはバックアップしてください。

例 39: PersistentVolumeClaim

クラスターにストレージを要求します。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data-pvc
spec:
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 1Gi

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 40: claim を使う Pod

Pod を再起動してもデータが残ります。

apiVersion: v1
kind: Pod
metadata:
  name: writer
spec:
  containers:
    - name: writer
      image: busybox:1.36
      command: ["sh", "-c", "echo hello >> /data/out.txt; sleep 3600"]
      volumeMounts:
        - name: data
          mountPath: /data
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: data-pvc

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 41: バインドされたストレージを確認

claim とボリュームの状態を読みます。

kubectl get pvc data-pvc
kubectl get pv
kubectl describe pvc data-pvc

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 42: StorageClass を選ぶ

ディスクの種類を選びます。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: fast-pvc
spec:
  storageClassName: managed-premium
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 10Gi

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 43: StorageClass を一覧表示

既定のクラスを見つけます。

kubectl get storageclass
kubectl get storageclass -o custom-columns=NAME:.metadata.name,DEFAULT:.metadata.annotations."storageclass\.kubernetes\.io/is-default-class"

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 44: claim を拡張

対応しているクラスではストレージを増やします。

kubectl patch pvc data-pvc -p '{"spec":{"resources":{"requests":{"storage":"2Gi"}}}}'
kubectl get pvc data-pvc

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 45: 回収ポリシー

claim を削除した後もデータを残します。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: archive-pv
spec:
  capacity:
    storage: 5Gi
  accessModes: ["ReadWriteOnce"]
  persistentVolumeReclaimPolicy: Retain
  hostPath:
    path: /srv/archive

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 46: ephemeral storage の上限

暴走する一時ファイルを止めます。

containers:
  - name: worker
    image: busybox:1.36
    resources:
      limits:
        ephemeral-storage: 1Gi

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

トピック 7: Ingress とトラフィックのルーティング

Ingress は、クラスター外からの HTTP と HTTPS のルートを Service に公開します。ホスト名とパスでルーティングし、TLS を終端するため、1 つの公開アドレスで多くのアプリケーションを提供できます。

Ingress とコントローラー

Ingress リソースだけでは何もしません。ingress-nginx などの ingress controller が Ingress オブジェクトを読み、プロキシを設定します。クラスターごとに 1 つのコントローラーを入れ、各 Ingress に ingressClassName を設定します。

TLS 証明書

tls セクションは、証明書と鍵を保持する Secret を指します。cert-manager は証明書を自動で要求し更新できるため、有効期限を手作業で管理する必要はありません。

パスとホストによるルーティング

ルールはホスト名とパスの接頭辞に一致します。最も具体的なパスが優先されるので、/api は API に、/ はウェブサイトに送られます。ルーティングの表が確認しやすいように、ルートはシンプルに保ちましょう。

例 47: ホスト名による Ingress

Service への HTTP ルーティングです。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web-ingress
spec:
  ingressClassName: nginx
  rules:
    - host: shop.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 80

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 48: TLS 付きの Ingress

Secret に保存された証明書による HTTPS です。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web-tls
spec:
  ingressClassName: nginx
  tls:
    - hosts: [shop.example.com]
      secretName: shop-tls
  rules:
    - host: shop.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 80

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 49: ingress controller をインストール

Helm でコントローラーを入れます。

helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress ingress-nginx/ingress-nginx --namespace ingress --create-namespace

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 50: パスによるルーティング

/api と / を別々の Service に送ります。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: shop-routes
spec:
  ingressClassName: nginx
  rules:
    - host: shop.example.com
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: api
                port:
                  number: 8000
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 80

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 51: アップロード用の Ingress アノテーション

1 つのサイトについて、アップロードの上限とタイムアウトを上げます。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: upload-ingress
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "20m"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
spec:
  ingressClassName: nginx
  rules:
    - host: upload.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: uploader
                port:
                  number: 80

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 52: cert-manager の ClusterIssuer

Let's Encrypt に証明書を自動で依頼します。

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-prod-key
    solvers:
      - http01:
          ingress:
            ingressClassName: nginx

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 53: Ingress から証明書を要求

アノテーションで TLS を自動管理します。

metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts: [shop.example.com]
      secretName: shop-tls

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 54: API 用の NetworkPolicy

Web 層だけが API に接続できるようにします。

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-allow-web
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes: [Ingress]
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: web
      ports:
        - port: 8000

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

トピック 8: Job と CronJob

すべてのワークロードが永久に動くわけではありません。Job は成功するまでタスクを実行し、CronJob はクラスターの中で動く cron テーブルのように、スケジュールに従って Job を開始します。

Job と完了数

Job は、要求された数の完了が成功するまで Pod を作ります。backoffLimit は再試行の上限です。Job では Always は許されないため、restartPolicy には Never か OnFailure を使ってください。

並列の作業

parallelism は同時に動く Pod の数、completions は必要な成功回数を設定します。このパターンは、複数のワーカーで項目のバッチを処理します。

CronJob と履歴

CronJob はスケジュールに cron 形式を使います。concurrencyPolicy を Forbid にすると実行の重複を防ぎ、履歴の上限で古い Job を削除してクラスターを整理します。

例 55: Job は完了まで実行

データベースの移行などの一回限りの作業です。

apiVersion: batch/v1
kind: Job
metadata:
  name: db-migrate
spec:
  backoffLimit: 2
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrate
          image: sample-api:1.4.2
          command: ["python", "manage.py", "migrate"]

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 56: Job を実行して待つ

作業が終わるまで待ち、ログを読みます。

kubectl apply -f job.yaml
kubectl wait --for=condition=complete job/db-migrate --timeout=120s
kubectl logs job/db-migrate

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 57: 並列の Job

複数のワーカーで多くの項目を処理します。

apiVersion: batch/v1
kind: Job
metadata:
  name: thumbnails
spec:
  completions: 10
  parallelism: 3
  template:
    spec:
      restartPolicy: OnFailure
      containers:
        - name: worker
          image: thumbnailer:2.0

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 58: スケジュールされた CronJob

毎晩 02:00 にレポートを実行します。

apiVersion: batch/v1
kind: CronJob
metadata:
  name: nightly-report
spec:
  schedule: "0 2 * * *"
  concurrencyPolicy: Forbid
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: report
              image: reporter:1.1

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 59: CronJob の確認と手動実行

過去の実行を確認し、手動で 1 回起動します。

kubectl get cronjob nightly-report
kubectl get jobs --sort-by=.metadata.creationTimestamp
kubectl create job --from=cronjob/nightly-report manual-report-1

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 60: Job の履歴の上限

完了した Job が溜まりすぎないようにします。

spec:
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 5
  startingDeadlineSeconds: 300

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

トピック 9: StatefulSet と DaemonSet

ワークロードによっては、安定したアイデンティティや、ノードごとに 1 つの Pod が必要です。StatefulSet は Pod に順序付きの名前と専用のストレージを与え、DaemonSet は条件に合うすべてのノードで Pod のコピーを 1 つずつ動かします。

StatefulSet のアイデンティティ

Pod は name-0、name-1 などと名付けられ、それぞれ volumeClaimTemplates から自分の claim を保持します。Pod が再起動しても名前は変わりません。データベースやメッセージブローカーに適しています。

順序付きの操作

既定では StatefulSet は Pod を順番に起動・停止します。パーティション付きの更新では、番号の大きい Pod から先に変更できるので、ステートフルなシステムを制御されたカナリアとして更新できます。

DaemonSet

DaemonSet は各ノードに 1 つの Pod を置きます。ログ収集、監視エージェント、ネットワークプラグインに適しています。nodeSelector や tolerations で特定のノードに限定できます。

例 61: StatefulSet

データベース向けの安定した名前とストレージです。

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
spec:
  serviceName: redis
  replicas: 3
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
        - name: redis
          image: redis:7-alpine
          volumeMounts:
            - name: data
              mountPath: /data
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 1Gi

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 62: StatefulSet を確認

序数付きの名前と、Pod ごとの claim です。

kubectl get statefulset redis
kubectl get pods -l app=redis
kubectl get pvc

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 63: DaemonSet

エージェント用に、すべてのノードに 1 つの Pod を置きます。

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-agent
spec:
  selector:
    matchLabels:
      app: node-agent
  template:
    metadata:
      labels:
        app: node-agent
    spec:
      containers:
        - name: agent
          image: busybox:1.36
          command: ["sh", "-c", "sleep 3600"]

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 64: ラベル付きノードの DaemonSet

nodeSelector でノードを選びます。

spec:
  template:
    spec:
      nodeSelector:
        workload: edge

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 65: StatefulSet の partition

まず一部の Pod だけを更新します。

spec:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 2

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 66: StatefulSet を慎重にスケール

縮小した後に claim を確認します。

kubectl get statefulset redis -o jsonpath="{.spec.replicas}"; echo
kubectl scale statefulset redis --replicas=2
kubectl get pvc -l app=redis

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

トピック 10: オートスケーリング

負荷は一日の中で変化します。オートスケーリングは需要が高いときに Pod を増やし、低いときに減らします。使った分だけ払い、ユーザーは速い応答を得られます。

Horizontal Pod Autoscaler

HPA は、平均 CPU 使用率などの指標を目標と比べ、レプリカ数を変えます。使用率はリクエストに対して測られるため、metrics server と Pod の CPU リクエストが必要です。

スケーリングの挙動

behavior フィールドで、HPA がどれくらい速くスケールアップ・ダウンするかを制御します。縮小時の安定化ウィンドウは、短い急増の後に急激に上下するのを防ぎます。

イベント駆動のスケーリング

KEDA はキューの長さやメッセージの遅延といった外部のシグナルでスケールし、作業がないときはゼロまで減らせます。キューを読むワーカーに使います。

例 67: リソース使用量をリアルタイムで確認

kubectl top には metrics server が必要です。

kubectl top nodes
kubectl top pods -l app=web

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 68: CPU で自動スケール

平均負荷が高いときに Pod を増やします。

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 69: コマンドラインで HPA を作成

自動スケールの反応を監視します。

kubectl autoscale deployment web --min=2 --max=10 --cpu-percent=70
kubectl get hpa web -w

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 70: メモリで自動スケール

複数の指標を使います。

spec:
  metrics:
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 75

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 71: HPA の縮小速度を制限

安定化ウィンドウで振動を防ぎます。

spec:
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Pods
          value: 1
          periodSeconds: 60

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 72: イベント駆動のスケーリング

KEDA でキューの長さに応じてワーカーを増減します。

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: worker-scaler
spec:
  scaleTargetRef:
    name: worker
  minReplicaCount: 0
  maxReplicaCount: 20
  triggers:
    - type: rabbitmq
      metadata:
        queueName: orders
        mode: QueueLength
        value: "50"

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

トピック 11: リソース、プローブ、スケジューリング

各コンテナーが必要とする CPU とメモリの量、そして正常かどうかの判断方法を Kubernetes に伝えます。スケジューラーは requests を使って Pod を配置し、プローブは再起動やトラフィック送信の判断をします。

requests と limits

request は、スケジューラーがコンテナーのために確保する量です。limit は使ってよい最大量です。limit を超えたメモリはコンテナーを強制終了させ、超えた CPU は速度を制限されます。実測の使用量に基づいて両方を設定してください。

プローブ

liveness probe は止まったコンテナーを再起動します。readiness probe は準備中のコンテナーを Service のエンドポイントから外します。startup probe は起動の遅いアプリケーションのために他のプローブを待たせます。

分散と中断

トポロジーの分散制約は、レプリカを異なるノードやゾーンに配置します。PodDisruptionBudget は、drain などの計画的な操作で同時に削除できる Pod の数を制限します。

例 73: requests と limits

スケジューラーとカーネルがどう使うか。

containers:
  - name: api
    image: sample-api:1.4.2
    resources:
      requests:
        cpu: 250m
        memory: 256Mi
      limits:
        cpu: "1"
        memory: 512Mi

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 74: liveness probe

止まってしまったコンテナーを再起動します。

livenessProbe:
  httpGet:
    path: /healthz
    port: 8000
  initialDelaySeconds: 10
  periodSeconds: 15
  failureThreshold: 3

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 75: startup probe

起動の遅いアプリに起動時間を与えます。

startupProbe:
  httpGet:
    path: /healthz
    port: 8000
  failureThreshold: 30
  periodSeconds: 10

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 76: PodDisruptionBudget

メンテナンス中も十分なレプリカを残します。

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

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 77: レプリカを複数のノードに分散

すべてのレプリカを 1 台に載せないようにします。

spec:
  topologySpreadConstraints:
    - maxSkew: 1
      topologyKey: kubernetes.io/hostname
      whenUnsatisfiable: ScheduleAnyway
      labelSelector:
        matchLabels:
          app: web

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 78: PriorityClass

クラスターが満杯でも重要な Pod をスケジュールします。

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: business-critical
value: 100000
globalDefault: false
description: "Keep these Pods running when the cluster is full."

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

トピック 12: セキュリティ: RBAC と Pod のセキュリティ

最小権限の原則は、人にもアプリケーションにも適用されます。RBAC は誰が何をできるかを決め、Pod のセキュリティ設定はコンテナーがノード上で何をできるかを制限します。

ServiceAccount、Role、バインディング

ServiceAccount は Pod のアイデンティティです。Role は 1 つの名前空間内のリソースに対して許可される動詞を列挙し、RoleBinding がそれを結び付けます。アプリケーションに必要な動詞だけを与え、アプリケーションに cluster-admin を与えないでください。

アクセスの確認

kubectl auth can-i は権限をテストします。別のユーザーや ServiceAccount として確認することもできます。レビューの手順で実行し、事故の後ではなく、デプロイの前にミスが分かるようにしてください。

Pod のセキュリティ

名前空間のラベルは、restricted などの Pod Security Standards を強制します。これは root のコンテナーや権限昇格をブロックします。capability を削除し、ルートファイルシステムを読み取り専用にする securityContext と組み合わせてください。

例 79: ServiceAccount

Pod の ID で、トークンは無効にします。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: api-sa
automountServiceAccountToken: false

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 80: 最小権限の Role

1 つの namespace の ConfigMap だけを読めます。

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: read-config
  namespace: shop
rules:
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["get", "list"]

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 81: RoleBinding

Role を ServiceAccount に結び付けます。

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: api-read-config
  namespace: shop
subjects:
  - kind: ServiceAccount
    name: api-sa
    namespace: shop
roleRef:
  kind: Role
  name: read-config
  apiGroup: rbac.authorization.k8s.io

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 82: auth can-i で権限を確認

デプロイ前に RBAC を検証します。

kubectl auth can-i list configmaps --as=system:serviceaccount:shop:api-sa -n shop
kubectl auth can-i delete pods --as=system:serviceaccount:shop:api-sa -n shop

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 83: Pod Security Standards

namespace に restricted プロファイルを適用します。

kubectl label namespace shop pod-security.kubernetes.io/enforce=restricted
kubectl label namespace shop pod-security.kubernetes.io/warn=restricted

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 84: 強化された security context

root 以外、読み取り専用、余分な capability なし。

securityContext:
  runAsNonRoot: true
  runAsUser: 10001
  allowPrivilegeEscalation: false
  readOnlyRootFilesystem: true
  capabilities:
    drop: ["ALL"]
  seccompProfile:
    type: RuntimeDefault

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

トピック 13: Helm と Kustomize

実際のアプリケーションには多数のマニフェストと複数の環境があります。Helm はそれらを values 付きの chart としてパッケージ化し、Kustomize は 1 つの base を保ち、環境ごとに小さなオーバーレイを適用します。

Helm の chart と release

chart は既定の値を持つテンプレートのパッケージです。release はインストールされた 1 つのコピーです。helm でインストール、アップグレード、ロールバックを行い、--atomic を使うと失敗したアップグレードが自動で元に戻ります。

環境ごとの values

環境ごとに 1 つの values ファイルを保ち、-f で渡します。インストール前に helm template で描画結果を確認すると、クラスターに何が届くかが正確に分かります。

Kustomize のオーバーレイ

Kustomize は base と、レプリカ数やイメージ、ラベルを変えるオーバーレイを、テンプレートなしで組み合わせます。kubectl apply -k で結果を適用し、kubectl diff で先に変更を確認できます。

例 85: Helm でアプリをインストール

リポジトリの chart を使います。

helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm install shop-cache bitnami/redis --namespace shop --set architecture=standalone

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 86: chart を展開して確認

YAML をプレビューし、サーバーで検証します。

helm template shop-web ./chart --values values-prod.yaml > rendered.yaml
kubectl apply --dry-run=server -f rendered.yaml

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 87: Helm の values ファイル

1 つの環境のための設定です。

replicaCount: 3
image:
  repository: registry.example.com/team/web
  tag: "1.4.2"
resources:
  limits:
    memory: 512Mi
ingress:
  enabled: true
  host: shop.example.com

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 88: Helm でアップグレードとロールバック

失敗したら自動で元に戻る atomic アップグレードです。

helm upgrade shop-web ./chart -n shop -f values-prod.yaml --atomic --wait
helm history shop-web -n shop
helm rollback shop-web 1 -n shop

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 89: Kustomize のオーバーレイ

テンプレートなしで環境ごとの変更を行います。

# overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ../../base
replicas:
  - name: web
    count: 5
images:
  - name: web
    newTag: "1.4.2"

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 90: Kustomize のオーバーレイを適用

まず diff を確認してから適用します。

kubectl kustomize overlays/prod | kubectl diff -f -
kubectl apply -k overlays/prod

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

トピック 14: 可観測性とデバッグ

Pod に問題が起きたとき、答えは状態、イベント、ログ、メトリクスにあります。落ち着いた順序立てた手順で、ほとんどの障害をすばやく見つけられます。同じシグナルがダッシュボードやアラートにも使われます。

状態、イベント、describe

kubectl get pod はフェーズと再起動回数を表示します。kubectl describe は ImagePullBackOff、CrashLoopBackOff、FailedScheduling などのイベントを一覧にし、Pod が止まっている理由を説明します。

複数の Pod にまたがるログ

kubectl logs -l でラベルによって Pod を選び、--prefix を使うと、どの Pod が各行を書いたかが分かります。クラッシュして再起動したコンテナーのログは --previous で読めます。

一時的なデバッグコンテナー

kubectl debug は実行中の Pod に一時的なコンテナーを接続します。アプリケーションのイメージにないツールを、アプリを再起動せずに使えます。

例 91: 複数の Pod のログ

ラベルで Pod を選びます。

kubectl logs -l app=web --all-containers --prefix --tail=20
kubectl logs -l app=web -f --max-log-requests=10

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 92: Pod が起動しない理由

describe の出力とコンテナーの状態を読みます。

kubectl describe pod web-6d4cf56db6-abcde
kubectl get pod web-6d4cf56db6-abcde -o jsonpath="{.status.containerStatuses[0].state}"

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 93: 一時的なデバッグコンテナー

実行中の Pod を再起動せずにデバッグします。

kubectl debug -it web-6d4cf56db6-abcde --image=busybox:1.36 --target=web -- sh

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 94: イベントとノードの負荷

容量やエビクションの問題を見つけます。

kubectl get events -A --sort-by=.lastTimestamp | tail -20
kubectl describe node worker-1 | grep -A5 "Conditions"

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

トピック 15: 本番運用と GitOps

本番運用とは、繰り返し可能なリリース、安全なメンテナンス、復旧を意味します。GitOps はクラスターを Git リポジトリと一致させ続け、バックアップは再構築できないオブジェクトやデータを守ります。

GitOps

Argo CD のような GitOps ツールはリポジトリを監視し、そのマニフェストをクラスターに適用します。すべての変更はプルリクエストを通り、selfHeal は Git からずれた手動の変更を元に戻します。

ダウンタイムなしのメンテナンス

kubectl drain は、アップグレードの前にノードから Pod を移動させます。PodDisruptionBudget は十分なレプリカを稼働させ続けます。ノードの準備ができたら kubectl uncordon を実行します。

バックアップと復旧

Velero はクラスターのオブジェクトと永続ボリュームをオブジェクトストレージにバックアップします。一度も復元していないバックアップは証明されていないので、復元は別のクラスターでテストしてください。

例 95: Argo CD による GitOps

クラスターを Git リポジトリと同期させ続けます。

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: shop
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/example/shop-deploy
    targetRevision: main
    path: overlays/prod
  destination:
    server: https://kubernetes.default.svc
    namespace: shop
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 96: 本番向けの Deployment

probe、制限、安全なロールアウトを一緒に設定します。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
  labels:
    app: api
spec:
  replicas: 3
  revisionHistoryLimit: 5
  selector:
    matchLabels:
      app: api
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
        - name: api
          image: registry.example.com/team/api:1.4.2
          ports:
            - containerPort: 8000
          resources:
            requests: {cpu: 250m, memory: 256Mi}
            limits: {memory: 512Mi}
          readinessProbe:
            httpGet: {path: /ready, port: 8000}
          livenessProbe:
            httpGet: {path: /healthz, port: 8000}

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 97: Velero でバックアップ

クラスターのオブジェクトとボリュームをストレージに保存します。

velero install --provider azure --plugins velero/velero-plugin-for-microsoft-azure:v1.10.0 --bucket k8s-backups
velero backup create shop-daily --include-namespaces shop
velero backup describe shop-daily

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 98: ノードの drain と uncordon

Pod を止めずにメンテナンスします。

kubectl cordon worker-1
kubectl drain worker-1 --ignore-daemonsets --delete-emptydir-data
kubectl uncordon worker-1

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 99: Python からレプリカを確認

公式の Kubernetes クライアントを使います。

from kubernetes import client, config

config.load_kube_config()
apps = client.AppsV1Api()
for d in apps.list_namespaced_deployment("shop").items:
    ready = d.status.ready_replicas or 0
    print(d.metadata.name, f"{ready}/{d.spec.replicas}")

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

例 100: リリース前チェックのスクリプト

チェックが失敗したらリリースを止めます。

#!/usr/bin/env bash
set -euo pipefail
NS="$1"
kubectl -n "$NS" rollout status deployment --timeout=180s
kubectl -n "$NS" get pdb
kubectl -n "$NS" auth can-i --list --as=system:serviceaccount:"$NS":default | grep -q "\*" && { echo "default account has wildcard rights"; exit 1; }
kubectl -n "$NS" get pods -o jsonpath="{range .items[*]}{.metadata.name}{' runAsRoot='}{.spec.securityContext.runAsNonRoot}{'\n'}{end}"
echo "release checks passed for namespace $NS"

*この例は、この記事を準備したコンピューターに Kubernetes クラスターも kubectl もインストールされていなかったため、ここでは実行していません。*

ラボ: 小さな Web アプリをデプロイ、公開、リリースする

記事の内容を 1 つの練習用クラスターにまとめます。プローブと制限を持つ Deployment、Service、Ingress、ConfigMap と Secret、ロールバック付きのローリングアップデート、そしてリリースチェックです。

1. kind で練習用クラスターを作り、shop という名前空間を作成して既定に設定します。

2. 3 つのレプリカ、リソースの requests と limits、readiness probe、liveness probe を持つ Deployment を適用します。

3. ClusterIP の Service を追加してエンドポイントを確認し、一時的な Pod から呼び出します。

4. 設定用の ConfigMap と、プレースホルダーのパスワードを入れた Secret を作り、両方を Pod にマウントします。

5. 新しいイメージのバージョンをロールアウトし、ロールアウトを見守ってから kubectl rollout undo で元に戻します。

6. shop.example.com 用の Ingress を追加し、NetworkPolicy で API を制限します。

7. 例 100 のリリースチェックリストを名前空間に対して実行し、どれか失敗したら停止します。

次のステップ

ラボの後は、サンドボックスのサブスクリプションでマネージドクラスターを使って練習し、自分のアプリケーション用の Helm chart を学び、すべての変更が Git でレビューされるように GitOps を設定してください。例はリポジトリに保存しておき、クラスターをアップグレードするたびに再実行してください。Kubernetes の API はバージョンによって変わるからです。

まとめ

Kubernetes はコンテナーを稼働させ続け、ノードに分散させ、ダウンタイムなしで更新し、需要に応じてスケールします。kubectl とコンテキスト、Pod、Deployment とロールバック、Service と Ingress、設定とシークレット、ストレージ、Job と CronJob、StatefulSet と DaemonSet、オートスケーリング、リソース要求とプローブ、RBAC と Pod のセキュリティ、Helm と Kustomize、デバッグ、そして本番向けの GitOps とバックアップを学びました。