Kubernetes にアプリケーションをデプロイする

KubernetesBeginner
オンラインで実践に進む

はじめに

最初のコースセクションでは、Kubernetes クラスターを変更せずに調査しました。kubectl が API サーバーにリクエストを送信すること、そして Kubernetes のコントローラーが 望ましい状態実際の状態 を継続的に比較していることを学びました。

ここからは、初めてアプリケーションに関するリクエストを実行します。Kubernetes に実行すべき細かな操作を一つずつ指示する代わりに、マニフェスト と呼ばれる YAML ファイルで実現したい結果を記述します。Kubernetes はそのオブジェクト定義を保存し、記述された状態を実現しようとします。

まずは、基本的なマニフェストの構造を確認しやすいように、単一の Pod を作成します。続いて、2 つの Pod を管理する Deployment を定義します。単独で作成した Pod と Deployment が管理する Pod を比較することで、アプリケーションでは通常、より上位のコントローラーが推奨される理由を理解できます。

この実験では、ワークロードの作成に意図的に焦点を当てます。アプリケーションを Service 経由で公開する方法については、Pod、ラベル、Deployment に慣れた後のセクションで学びます。

宣言型 Kubernetes オブジェクトを理解する

環境の起動: この実験では、完全な Kubernetes クラスターが起動します。コントロールプレーン、ノード、ネットワークコンポーネントの構成には通常 2~3 分かかります。環境の読み込みが完了するまで、そのままお待ちください。

このステップでは、前の実験で学んだ望ましい状態の考え方を Kubernetes マニフェストに結び付け、最初のアプリケーション定義を作成するための作業場所を準備します。

コマンドから望ましい状態へ

Kubernetes には、大きく分けて次の 2 つの管理方式があります。

  • 命令型(imperative) コマンドでは、「first-nginx という名前の Pod を作成する」のように、実行したい操作を直接要求します。
  • 宣言型(declarative) マニフェストでは、望ましいオブジェクト設定をファイルに保存し、クラスターがその状態になるよう Kubernetes に依頼します。

宣言型ファイルには、変更前に内容を確認できる、何度でも適用できる、差分をレビューできる、バージョン管理に保存できる、といった利点があります。このコースでは宣言型の方式を中心に扱います。

API から返されるすべての Kubernetes オブジェクトには、次の重要なトップレベルフィールドがあります。

  • apiVersion は、使用する Kubernetes API グループとバージョンを指定します。
  • kind は、PodDeployment などのオブジェクト種別を識別します。
  • metadata は、名前やラベルなど、オブジェクトの識別情報を定義します。
  • spec は、そのオブジェクトの望ましい状態を記述します。
  • status は、観測された状態を報告します。通常は作成後に Kubernetes が設定するため、マニフェストに記述するものではありません。

この仕組みにより、次のような重要なループが形成されます。

manifest spec -> API server stores desired state -> controllers act -> object status reports actual state

マニフェスト用ディレクトリを準備する

この実験用に準備されたディレクトリへ移動します。

cd /home/labex/project/k8s-manifests

作業ディレクトリを表示する(print working directory) pwd で、現在位置を確認します。

pwd
/home/labex/project/k8s-manifests

これから使用する 4 つのマニフェストフィールドを記録した短いメモファイルを作成します。printf コマンドは引用符で囲まれた各文字列を個別の行として出力し、> はその出力をファイルへリダイレクトします。ファイルがすでに存在する場合は上書きされます。

printf '%s\n' apiVersion kind metadata spec > manifest-fields.txt

ファイルを表示して内容を確認します。

cat manifest-fields.txt
apiVersion
kind
metadata
spec

このメモファイルは、学習内容を確認するための小さなチェックポイントです。次に作成する 2 つのマニフェストには、これら 4 つのフィールドがどちらにも登場します。

Pod マニフェストを記述して検証する

このステップでは、1 つの Pod 用の YAML マニフェストを記述し、クラスターへ送信する前に構造を検証します。

Pod について知る

Pod は、Kubernetes でデプロイ可能な最小単位のオブジェクトです。Pod は、密接に関連する 1 つ以上のコンテナに対して、ネットワーク識別情報とストレージコンテキストを共有させます。初心者向けの例では、通常、1 つの Pod に 1 つのコンテナを配置します。

この実験の Pod では、小規模な Web サーバーである NGINX を実行します。イメージには nginx:1.27-alpine を固定指定します。バージョンを固定すると、変化し続ける latest タグを使用する場合よりも実行結果を再現しやすくなります。インターネットからのダウンロードに依存しないよう、このイメージはクラスター内にあらかじめキャッシュされています。

YAML ファイルを作成する

マニフェスト用ディレクトリにいることを確認します。

cd /home/labex/project/k8s-manifests

ヒアドキュメント(here-document) を使ってファイルを作成します。シェルは <<'EOF' から終了側の EOF までの各行を first-pod.yaml にリダイレクトします。最初の EOF を引用符で囲むことで、YAML 内の特殊文字がシェルによって展開されるのを防ぎます。

cat <<'EOF' > first-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: first-nginx
  namespace: default
  labels:
    app: first-nginx
spec:
  containers:
    - name: nginx
      image: nginx:1.27-alpine
      imagePullPolicy: IfNotPresent
      ports:
        - name: http
          containerPort: 80
          protocol: TCP
EOF

YAML では、インデントによって階層を表現します。スペースを一貫して使用してください。タブを使うと YAML が無効になる場合があります。- name: nginx のようなハイフンは、リスト項目の開始を表します。

オブジェクトを上から順に確認します。

  • apiVersion: v1 は、Pod が使用するコア API を選択します。
  • kind: Pod は、リソース種別が Pod であることを宣言します。
  • metadata.name は、Pod に固定名 first-nginx を付けます。
  • metadata.namespace: default は、システム名前空間ではなく、コースで通常使用するアプリケーション用の名前空間に配置することを指定します。
  • metadata.labels は、後からこの Pod を選択できるよう app=first-nginx を付加します。
  • spec.containers は、Pod で実行するコンテナのリストです。
  • imagePullPolicy: IfNotPresent は、利用可能な場合にキャッシュ済みのイメージを使用します。
  • 名前付きポート http は、containerPort: 80protocol: TCP を使用します。これはコンテナ内部で NGINX が待ち受ける場所を示すものであり、クラスター外部へ Pod を公開するものではありません。

作成前に検証する

クライアント側のドライランを使用して、Pod を作成せずにファイルを解析します。-ffile を意味し、--dry-run=client によってリクエストをローカルで処理します。

kubectl apply --dry-run=client -f first-pod.yaml
pod/first-nginx created (dry run)

dry run という表示が重要です。構文は有効ですが、クラスターにはまだ変更が加えられていません。

kubectl に、正規化されたオブジェクトを YAML として出力させます。

出力オプション -ooutput format を意味します。yaml を指定すると、kubectl は解析したオブジェクトを 1 行の結果だけでなく YAML として表示します。

kubectl apply --dry-run=client -f first-pod.yaml -o yaml

入力したフィールドに加えて、クライアントが追加したデフォルト値も表示されます。実際に適用する前に、インデント、フィールド名、型に関する誤りを見つけるのに役立ちます。

最初の Pod を作成して確認する

このステップでは、検証済みのマニフェストを適用し、Kubernetes が Pod を望ましい状態へ移行させる様子を確認して、作成されたオブジェクトを調査します。

マニフェストを適用する

必要に応じてマニフェスト用ディレクトリへ移動します。

cd /home/labex/project/k8s-manifests

ドライランオプションを付けずにファイルを適用します。

kubectl apply -f first-pod.yaml
pod/first-nginx created

kubectl apply はオブジェクトを API サーバーへ送信します。API サーバーは Pod の望ましい仕様を保存し、スケジューラーと kubelet が連携してノード上で Pod を実行します。

Ready になるまで待つ

Pod の作成は非同期です。そのため、コンテナが Ready になる前に kubectl apply が終了することがあります。kubectl wait を使って、Pod の Ready 条件を待ちます。条件が真になると正常終了し、60 秒経過すると失敗します。

kubectl wait --for=condition=Ready pod/first-nginx --timeout=60s
pod/first-nginx condition met

次に Pod を一覧表示します。この実験では、各実験を単独でも利用できるよう、-o wide を繰り返し使用します。-o は出力形式を選択し、wide は Pod の IP アドレスやノード名などのフィールドを追加します。

kubectl get pod first-nginx -o wide
NAME          READY   STATUS    RESTARTS   AGE   IP           NODE
first-nginx   1/1     Running   ...        ...   ...          labex-v135

READY=1/1 は、1 つのコンテナが Ready であることを意味します。一方、STATUS=Running は Pod のフェーズを示します。ワイド表示には、Pod の IP アドレスと割り当てられたノードも表示されます。Pod の IP アドレスや経過時間は自動生成される値なので、実行環境によって異なる場合があります。

ラベルと所有関係を確認する

Pod のラベルを表示します。

kubectl get pod first-nginx --show-labels

app=first-nginx が表示されることを確認してください。ラベルはオブジェクトとともに保存され、今後 Deployment や Service が Pod を選択する際に重要になります。

この Pod を管理している別のオブジェクトがあるか、Kubernetes に確認します。-o jsonpath='...' はオブジェクト全体を表示せず、指定したフィールドだけを抽出します。ここでは metadata.ownerReferences をたどり、{"\n"} によって最後に改行を追加して、シェルプロンプトが次の行に表示されるようにしています。

kubectl get pod first-nginx -o jsonpath='Owner: {.metadata.ownerReferences[0].kind}{"\n"}'
Owner:

所有者が空欄なのは、この 単独の Pod(bare Pod) を直接作成したためです。この Pod が削除されても、それを置き換えるべきだと認識する上位コントローラーは存在しません。次のステップ以降で、管理対象の Pod と比較します。

ステップのチェックポイント

ローカルに保存した望ましい状態のファイルから、実行中の Kubernetes オブジェクトを作成しました。API は Pod を受け付け、スケジューラーがノードを割り当て、kubelet がコンテナを Ready にしました。ただし、この Pod のライフサイクルを管理するコントローラーは存在しません。

Deployment を定義する

このステップでは、NGINX Pod を 2 つ維持するよう Kubernetes に要求する Deployment を定義します。

Deployment を使う理由

単独の Pod は学習には便利ですが、アプリケーションでは通常、コントローラーが必要です。Deployment は、望ましいレプリカ数と Pod テンプレートを提供します。Deployment は ReplicaSet を作成し、ReplicaSet が要求された数の Pod を維持します。

所有関係は次のとおりです。

Deployment -> ReplicaSet -> Pods -> containers

管理対象の Pod が消失すると、ReplicaSet は実際のレプリカ数が望ましい数を下回ったことを検知し、代替 Pod を作成します。後の実験では、Deployment を使ったスケーリングやローリングアップデートを扱います。

Deployment マニフェストを作成する

マニフェスト用ディレクトリへ戻ります。

cd /home/labex/project/k8s-manifests

ヒアドキュメントを使って course-web-deployment.yaml を作成します。

cat <<'EOF' > course-web-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: course-web
  labels:
    app: course-web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: course-web
  template:
    metadata:
      labels:
        app: course-web
    spec:
      containers:
        - name: nginx
          image: nginx:1.27-alpine
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 80
EOF

Deployment は、Deployment 用の安定版 API である apps/v1 を使用します。その spec には、次の 3 つの重要なフィールドがあります。

  • replicas: 2 は、望ましい Pod 数を指定します。
  • selector.matchLabels は、Deployment が管理する Pod を識別します。
  • template は、各 Pod の作成に使用する設計図です。

selectortemplate.metadata.labels は、どちらも app: course-web を使用しています。これらは一致していなければなりません。一致しない場合、Deployment は自身のテンプレートから作成した Pod を識別できません。

Deployment を検証する

クラスターを変更せずにマニフェストを解析します。

kubectl apply --dry-run=client -f course-web-deployment.yaml
deployment.apps/course-web created (dry run)

kubectl diff を使って、マニフェストと実際の状態を比較します。差分がある場合は終了ステータスが 0 以外になりますが、|| true によって、予想される差分を失敗として扱わずにシェルプロンプトへ戻れるようにしています。

kubectl diff -f course-web-deployment.yaml || true

course-web はまだ存在しないため、出力には追加される完全なオブジェクトが表示され、各行は + で始まります。apply とは異なり、diff はクラスターを変更しません。

管理対象アプリケーションをデプロイして確認する

このステップでは、Deployment を適用し、2 つのレプリカが Ready になるまで待ち、共通ラベルで選択された関連リソースを確認します。

Deployment を適用して待機する

まず、マニフェストが置かれているディレクトリへ移動します。その後、保存した望ましい状態を適用します。-f は、指定したファイルから読み取るよう kubectl に指示します。

cd /home/labex/project/k8s-manifests
kubectl apply -f course-web-deployment.yaml
deployment.apps/course-web created

Deployment のロールアウトが完了するまで待ちます。ロールアウトとは、Deployment の Pod を望ましいテンプレートとレプリカ数に一致させる処理です。

kubectl rollout status deployment/course-web --timeout=60s
deployment "course-web" successfully rolled out

Deployment を一覧表示します。

kubectl get deployment course-web
NAME         READY   UP-TO-DATE   AVAILABLE   AGE
course-web   2/2     2            2           ...

READY=2/2 は、望ましい 2 つのレプリカが両方とも Ready であることを意味します。UP-TO-DATE=2 は、両方が現在の Pod テンプレートを使用していることを示し、AVAILABLE=2 は、両方が利用可能であることを示します。

関連リソースを確認する

ラベルセレクター -l app=course-web を使って、関連リソースを一覧表示します。

kubectl get deployment,replicaset,pods -l app=course-web

出力には、1 つの Deployment、1 つの ReplicaSet、2 つの Pod が含まれます。ReplicaSet と Pod に付く自動生成のサフィックスは環境によって異なります。

NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/course-web   2/2     2            2           ...

NAME                                    DESIRED   CURRENT   READY   AGE
replicaset.apps/course-web-...          2         2         2       ...

NAME                              READY   STATUS    RESTARTS   AGE
pod/course-web-...-...            1/1     Running   ...        ...
pod/course-web-...-...            1/1     Running   ...        ...

この表示から、Deployment が望んでいた 2 つのレプリカが、Ready な 2 つの Pod になったことが分かります。次のステップでは、これらのリソース間の所有関係をたどります。

ステップのチェックポイント

Deployment マニフェストを適用し、望ましい状態になるまで待機しました。Kubernetes は ReplicaSet と 2 つの Pod を作成し、共通の app=course-web ラベルによって、それらを 1 つのアプリケーションのグループとして一覧表示できました。

コントローラーの所有関係をたどる

このステップでは、管理対象の Pod から ReplicaSet、さらに Deployment へと Kubernetes の所有者参照をたどります。また、マニフェストを再適用して、宣言型のべき等性を確認します。

Pod の所有者を確認する

自動生成された Pod 名の 1 つをシェル変数に保存します。NAME=$(command) という構文は コマンド置換(command substitution) です。シェルがコマンドを実行し、その出力を NAME に保存します。ここでは、-l app=course-web で一致する Pod を選択し、JSONPath で最初の Pod の自動生成された名前を抽出します。

POD_NAME=$(kubectl get pods -l app=course-web -o jsonpath='{.items[0].metadata.name}')

選択された Pod を確認するため、名前を表示します。

echo "$POD_NAME"

次に、その Pod の直接の所有者を確認します。"$POD_NAME" を引用符で囲むことで、保存された名前を安全に 1 つのコマンド引数として渡せます。

kubectl get pod "$POD_NAME" -o jsonpath='Owner: {.metadata.ownerReferences[0].kind}/{.metadata.ownerReferences[0].name}{"\n"}'
Owner: ReplicaSet/course-web-...

単独の first-nginx Pod とは異なり、Deployment が管理する Pod の所有者は ReplicaSet です。そして、その ReplicaSet 自体は Deployment によって所有されています。

ReplicaSet の所有者を表示します。最初のコマンドでは、先ほどと同じコマンド置換のパターンを使い、今回は ReplicaSet 名を RS_NAME に保存します。

RS_NAME=$(kubectl get replicaset -l app=course-web -o jsonpath='{.items[0].metadata.name}')
kubectl get replicaset "$RS_NAME" -o jsonpath='Owner: {.metadata.ownerReferences[0].kind}/{.metadata.ownerReferences[0].name}{"\n"}'
Owner: Deployment/course-web

望ましい状態を再適用する

同じマニフェストをもう一度適用します。

kubectl apply -f course-web-deployment.yaml
deployment.apps/course-web unchanged

unchanged という表示は、宣言型管理における重要な性質を示しています。同じ望ましい状態を繰り返し適用しても安全です。望ましい設定と実際の設定に差異がある場合にだけ、Kubernetes は変更を行います。

ステップのチェックポイント

これで、単独の Pod と Deployment が管理するアプリケーションの両方を確認できました。どちらもコンテナを実行しますが、Deployment には 2 つのレプリカを維持するコントローラー階層があり、今後のスケーリングやローリングアップデートの基盤となります。

まとめ

読み取り専用のクラスター調査から、宣言型によるアプリケーション管理へ進みました。apiVersionkindmetadataspec の役割を学び、クライアント側のドライランでマニフェストを検証し、単独の Pod を作成して確認し、Deployment によって管理される 2 つのレプリカをデプロイしました。

最も重要なのは、Deployment から ReplicaSet、Pod へ続くコントローラーの所有関係を確認したことです。この望ましい状態に基づく仕組みは、後のコースセクションでアプリケーションを診断し、Service 経由で公開し、レプリカをスケーリングし、ローリングアップデートを実行するための基礎となります。