Kubernetes入門
第4回:宣言的定義(YAML)と「あるべき姿」
前回までで、私たちは kubectl create や kubectl expose といったコマンドを使い、Podを作り、サービスを公開してきました。手を動かすたびに、K8sが確かに動いている実感が湧いてきたのではないでしょうか。
しかし、ふと立ち止まって考えてみてください。
「この環境、もう一度ゼロから作れと言われたら、どうする?」
VMの世界なら、あなたはきっとこう答えるでしょう。「手順書を見ながら、同じコマンドを同じ順番で打てばいい」と。でも、その手順書が100行あったら?途中で打ち間違えたら?前任者が退職して、手順書の「なぜ」が誰にもわからなくなっていたら?
今回は、そんな「手順書地獄」から私たちを解放してくれる、K8sの真骨頂——宣言的定義(Declarative Configuration) の世界に足を踏み入れます。
4.1 手順書(Imperative) vs 設計図(Declarative)
4.1.1 Shellスクリプトによる構築の限界と、YAMLによる宣言の利点
VMエンジニアとして、あなたは何度「構築手順書」を書いてきたでしょうか。
# 典型的なVM構築手順書の一部(イメージ)
# 1. パッケージをインストール
yum install -y httpd
# 2. 設定ファイルを編集
vi /etc/httpd/conf/httpd.conf # ← ここで何を変えるかは別紙参照
# 3. サービスを起動
systemctl start httpd
systemctl enable httpd
# 4. ファイアウォールを開放
firewall-cmd --add-service=http --permanent
firewall-cmd --reload
この手順書には、いくつかの根本的な問題があります。
| 問題点 | 具体例 |
|---|---|
| 順序依存 | 3番を先に実行するとエラー。順番を間違えると詰む |
| 状態依存 | すでにhttpdがインストール済みだと、1番が想定外の動作をすることも |
| 暗黙知の塊 | 「別紙参照」の中身は、書いた人の頭の中にしかない |
| 冪等性がない | 2回実行すると、2回目で何が起きるか保証されない |
これが 命令的(Imperative) アプローチの限界です。「Aをしろ、次にBをしろ、それからCだ」という手順(How) を記述しているため、途中で失敗したら、どこまで進んだかを人間が判断し、手動でリカバリーするしかありません。
一方、K8sの 宣言的(Declarative) アプローチは、まったく異なる哲学に基づいています。
# K8sのマニフェスト(設計図)
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-server
spec:
replicas: 1
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
このYAMLは「手順」ではありません。「あるべき姿(What)」 を宣言しているのです。
「nginxコンテナが1台動いていて、ポート80で待ち受けている状態にしてくれ」
K8sに対して私たちが伝えるのは、これだけです。どうやってその状態を実現するかは、K8sが考えます。
VMとK8sの思考モデルの違い
| 観点 | VM(命令的) | K8s(宣言的) |
|---|---|---|
| 伝えること | 「何をするか(How)」 | 「どうあるべきか(What)」 |
| 責任の所在 | 人間が手順の正しさを保証 | K8sが状態の一致を保証 |
| 失敗時の対応 | 手順書のどこで止まったか調査 | 再度applyすれば、差分だけ適用 |
| ドキュメント | 手順書+設定値+暗黙知 | YAMLファイルがすべてを語る |
4.1.2 コントロールプレーンの役割:理想(YAML)と現実(現状)の差を埋める「ループ」
第2回で、K8sには「コントロールプレーン」と「ワーカーノード」があることを学びました。ここで、コントロールプレーンの核心的な動作原理を理解しましょう。
K8sのコントロールプレーンは、常に以下のループを回し続けています。
┌────────────────────────────┐
│ │
│ ① 理想状態を読む(etcdに保存されたマニフェスト) │
│ ↓ │
│ ② 現実状態を観測する(実際に動いているPod等) │
│ ↓ │
│ ③ 差分を検出する(理想 − 現実 = やるべきこと) │
│ ↓ │
│ ④ 差分を埋めるアクションを実行 │
│ ↓ │
│ ⑤ ①に戻る(無限ループ) │
│ │
└────────────────────────────┘
これを Reconciliation Loop(調停ループ) と呼びます。
VMの世界で喩えるなら、「24時間365日、設計図と現場を見比べて、ズレがあれば自動で直してくれる現場監督」が常駐しているようなものです。しかも、この監督は疲れを知らず、休憩も取らず、ミスもしません。
具体例:Podが死んだとき
- あなたが
replicas: 3と宣言した(理想状態) - 現在、Podは3つ動いている(現実状態)
- 突然、1つのPodがクラッシュした
- コントロールプレーンが検知:「理想は3、現実は2。差分は1」
- 自動的に新しいPodを1つ起動
- 再び理想と現実が一致
あなたは何もしていません。深夜に電話が鳴ることもありません。
VMの世界では、この「Podが死んだ」に相当する事態——たとえばApacheプロセスのクラッシュ——が起きたとき、監視ツールがアラートを飛ばし、誰かがSSHでログインし、ログを確認し、プロセスを再起動していたはずです。
K8sは、その「誰か」の役割を、自律的に、何度でも、正確に果たしてくれるのです。
4.1.3 冪等性(べきとうせい):何度実行しても同じ結果になる安心感
冪等性(Idempotency) という言葉を聞いたことがあるでしょうか。
数学では「何度演算しても結果が変わらない性質」を指しますが、インフラの文脈では、こう言い換えられます。
「同じ操作を何度実行しても、システムは常に同じ状態になる」
VMの構築スクリプトで、これを実現するのがいかに難しいか、経験者ならご存知でしょう。
# 冪等でないスクリプトの例
echo "ServerName localhost" >> /etc/httpd/conf/httpd.conf
このコマンドを2回実行すると、ServerName localhost が2行追記されてしまいます。3回実行すれば3行。これが本番環境で起きたら?想像するだけで冷や汗が出ます。
一方、K8sの kubectl apply は、設計上、冪等 です。
# 何度実行しても、結果は同じ
kubectl apply -f deployment.yaml
kubectl apply -f deployment.yaml
kubectl apply -f deployment.yaml
1回目:Deploymentが作成される
2回目:「すでに理想状態です」→ 何も変わらない
3回目:同上
これが意味することは重大です。
VMのバックアップ・スナップショットからの解放
VMの世界では、「何かあったら戻せるように」と、こまめにスナップショットを取っていたはずです。なぜなら、手順書ベースの構築では「今の状態を再現できる保証がない」からです。
K8sでは、YAMLファイル自体が「あるべき姿」の完全な記録 です。
- 環境がおかしくなった? →
kubectl apply -fで理想状態に戻る - 新しいクラスタを作りたい? → 同じYAMLを適用すれば、同じ環境ができる
- 半年前の状態に戻したい? → Gitから半年前のYAMLをcheckoutして適用
スナップショットの容量を気にする必要も、リストアに何時間もかかることもありません。テキストファイル数個で、インフラ全体が再現できるのです。
4.2 実践:マニフェストによるインフラのコード管理
理論はここまでにして、実際に手を動かしてみましょう。第3回で作成したWebサーバ環境を、YAMLファイルで「設計図化」します。
4.2.1 Webサーバ(Deployment/Service)の構成をYAMLファイルに書き出す
まず、作業用のディレクトリを作成します。
[Execution User: developer]
mkdir -p ~/k8s-practice/04-declarative
cd ~/k8s-practice/04-declarative
次に、Deploymentのマニフェストを作成します。
[Execution User: developer]
cat << 'EOF' > web-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-server
labels:
app: web
environment: practice
spec:
replicas: 1
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "200m"
EOF
続いて、Serviceのマニフェストを作成します。
[Execution User: developer]
cat << 'EOF' > web-service.yaml
apiVersion: v1
kind: Service
metadata:
name: web-service
labels:
app: web
spec:
type: NodePort
selector:
app: web
ports:
- port: 80
targetPort: 80
nodePort: 30080
protocol: TCP
EOF
YAMLの構造を理解する
作成したマニフェストの各フィールドが何を意味しているか、確認しておきましょう。
| フィールド | 説明 |
|---|---|
apiVersion | 使用するK8s APIのバージョン。リソースの種類によって異なる |
kind | 作成するリソースの種類(Deployment, Service, Pod等) |
metadata | リソースの識別情報(名前、ラベル等) |
spec | リソースの「あるべき姿」の詳細定義 |
特に spec セクションが、宣言的定義の核心です。ここに書かれた内容が「理想状態」として etcd に保存され、コントロールプレーンが常に監視し続けます。
4.2.2 kubectl apply -f の実行:設定を変更して「差分だけ」が適用される様子を観察
いよいよ、マニフェストを適用します。
[Execution User: developer]
# Deploymentを適用
kubectl apply -f web-deployment.yaml
# Serviceを適用
kubectl apply -f web-service.yaml
出力を確認しましょう。
deployment.apps/web-server created
service/web-service created
created と表示されています。リソースが新規作成されたことを意味します。
状態を確認します。
[Execution User: developer]
kubectl get deployments,pods,services -l app=web
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/web-server 1/1 1 1 30s
NAME READY STATUS RESTARTS AGE
pod/web-server-6d4f8b7c9d-x2k8m 1/1 Running 0 30s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/web-service NodePort 10.96.123.45 <none> 80:30080/TCP 25s
素晴らしい。YAMLに書いた通りの状態が実現されています。
冪等性を体感する
では、同じコマンドをもう一度実行してみましょう。
[Execution User: developer]
kubectl apply -f web-deployment.yaml
kubectl apply -f web-service.yaml
deployment.apps/web-server unchanged
service/web-service unchanged
今度は unchanged と表示されました。「すでに理想状態なので、何も変更しませんでした」という意味です。
これが冪等性です。 何度実行しても、システムは同じ状態を維持します。
差分適用を体感する
次に、設定を変更してみましょう。レプリカ数を1から2に増やします。
[Execution User: developer]
# replicas を 1 から 2 に変更
sed -i 's/replicas: 1/replicas: 2/' web-deployment.yaml
# 変更を確認
grep replicas web-deployment.yaml
replicas: 2
変更したマニフェストを適用します。
[Execution User: developer]
kubectl apply -f web-deployment.yaml
deployment.apps/web-server configured
今度は configured と表示されました。「設定が変更されました」という意味です。
状態を確認します。
[Execution User: developer]
kubectl get pods -l app=web
NAME READY STATUS RESTARTS AGE
web-server-6d4f8b7c9d-x2k8m 1/1 Running 0 5m
web-server-6d4f8b7c9d-p9q3r 1/1 Running 0 10s
Podが1つから2つに増えています。K8sは「理想状態(replicas: 2)」と「現実状態(Pod 1つ)」の差分を検出し、必要な分だけ 新しいPodを作成しました。
VMの世界で同じことをしようとしたら、どうなるでしょうか。
- 新しいVMをプロビジョニング
- OSをインストール
- ミドルウェアをセットアップ
- 設定ファイルを既存サーバからコピー
- ロードバランサに追加
- 動作確認
この手順を、K8sは kubectl apply 一発で、数秒で完了させました。
4.2.3 ドライラン(–dry-run=client)の活用:本番適用前の安全確認フロー
「YAMLを書き換えたけど、適用したらどうなるか不安…」
その気持ち、よくわかります。VMの世界で、本番サーバに設定変更を入れる前、何度もテスト環境で確認してきたはずです。
K8sには、ドライラン(Dry Run) という機能があります。これは「実際には何も変更せず、変更した場合の結果だけを表示する」モードです。
[Execution User: developer]
# ドライランで差分を確認(実際には適用されない)
kubectl apply -f web-deployment.yaml --dry-run=client -o yaml
さらに、現在の状態との差分を視覚的に確認したい場合は、diff オプションが使えます。
[Execution User: developer]
# 現在の状態との差分を表示
kubectl diff -f web-deployment.yaml
このコマンドは、まさにLinuxの diff コマンドのように、変更点をハイライトして表示してくれます。
本番適用前のセーフティネット
インフラエンジニアにとって、「本番環境への変更」は常に緊張の瞬間です。手順書を三重にチェックし、ロールバック手順を用意し、それでも何かが起きないかヒヤヒヤする——。
K8sのドライランと差分確認は、その緊張を大きく和らげてくれます。
# 推奨する本番適用フロー
# 1. まず差分を確認
kubectl diff -f production-deployment.yaml
# 2. 問題なければドライランで最終確認
kubectl apply -f production-deployment.yaml --dry-run=server
# 3. 確信を持って適用
kubectl apply -f production-deployment.yaml
--dry-run=server は、クライアント側ではなくAPIサーバ側でバリデーションを行うモードです。より本番に近い検証ができます。
4.3 理論:コントロールプレーンが「裏側」で行っていること
4.3.1 「1台動かして」と願うだけ。K8sが自律的に帳尻を合わせる仕組み
ここまでの実践で、kubectl apply の便利さを体感していただけたと思います。しかし、なぜこんなことが可能なのでしょうか?その裏側を少し覗いてみましょう。
コントローラという「自律的な番人」
K8sには、コントローラ(Controller) と呼ばれるコンポーネントが複数存在します。それぞれが特定のリソースを監視し、理想状態との差分を埋める責任を持っています。
┌──────────────────────────────┐
│ Control Plane │
├──────────────────────────────┤
│ │
│ ┌────────┐ ┌────────┐ │
│ │ Deployment │ │ ReplicaSet │ │
│ │ Controller │ │ Controller │ │
│ │ │ │ │ │
│ │ "Deploymentの │ │ "ReplicaSetの │ │
│ │ 理想を監視" │ │ 理想を監視" │ │
│ └───┬────┘ └───┬────┘ │
│ │ │ │
│ └────┬─────┘ │
│ ▼ │
│ ┌────────┐ │
│ │ etcd │ │
│ │ (理想状態DB) │ │
│ └────────┘ │
│ │
└──────────────────────────────┘
例えば、Deploymentを適用したとき、裏側では以下のような連鎖が起きています。
- Deployment Controller が「Deploymentの理想状態」を監視
- 理想状態に対応する ReplicaSet を作成・更新
- ReplicaSet Controller が「ReplicaSetの理想状態」を監視
- 理想のレプリカ数に達するまで Pod を作成
- Scheduler がPodを適切なノードに配置
- kubelet がノード上でコンテナを起動
あなたが書いたYAMLは、この連鎖の「起点」に過ぎません。あとは、各コントローラが自律的に動き、システム全体が理想状態に収束していきます。
VMとの決定的な違い
VMの世界では、「サーバAのApacheが落ちた」というイベントが発生したら、それを検知し、対処するのは人間(またはそのための自動化スクリプト)の責任でした。
K8sでは、この「検知 → 対処」のループがシステムに内蔵されています。
- 人間:「Podが1台動いていてほしい」と宣言
- K8s:「了解。1台の状態を維持し続けます」
Podが死のうが、ノードが落ちようが、K8sは粛々と「1台の状態」を維持しようとします。人間に相談することなく、自律的に。
4.3.2 手順書がいらなくなる日:インフラの「再現性」がもたらす恩恵
宣言的定義がもたらす最大の恩恵は、インフラの完全な再現性 です。
GitOpsという発想
YAMLファイルをGitで管理すると、インフラに以下の恩恵がもたらされます。
| Gitの機能 | インフラへの恩恵 |
|---|---|
| バージョン管理 | いつ、誰が、何を変更したか完全に追跡可能 |
| ブランチ | 本番/ステージング/開発環境を分離管理 |
| プルリクエスト | 変更を本番適用前にレビュー |
| ロールバック | git revert → kubectl apply で即座に前の状態へ |
| 差分表示 | インフラの変更履歴が git diff で一目瞭然 |
VMの世界で「3ヶ月前の本番環境の状態」を再現しようとしたら、どれほどの労力がかかるでしょうか。スナップショットがあればまだマシですが、なければ絶望的です。
K8sとGitの組み合わせなら、こうなります。
# 3ヶ月前のインフラ状態を再現
git checkout HEAD~90 -- manifests/
kubectl apply -f manifests/
# やっぱり最新に戻す
git checkout main -- manifests/
kubectl apply -f manifests/
これが「Infrastructure as Code(IaC)」の真髄であり、K8sが自然にそれを可能にしてくれる理由です。
手順書の終焉
従来の手順書は、「インフラの状態を再現するための記録」でした。しかし、以下の問題を常に抱えていました。
- 手順書と実際の環境が乖離する(ドキュメントの陳腐化)
- 手順書通りに実行しても、環境の微妙な違いで失敗する
- 手順書を書いた人が退職すると、暗黙知が失われる
K8sのマニフェストは、それ自体が「生きた手順書」 です。
- マニフェストと環境は常に一致している(ズレたらK8sが直す)
- マニフェストを適用すれば、確実に同じ環境ができる
- マニフェストを読めば、システムの全容がわかる
手順書を書く時間を、YAMLを書く時間に置き換える。それだけで、インフラ運用の信頼性は劇的に向上します。
4.3.3 【次回予告】コマンド一つでサーバーを10台に増やす「レプリケーション」
今回、replicas: 1 を replicas: 2 に変更して、Podが自動的に増える様子を見ました。
次回は、この「レプリケーション」の仕組みをさらに深掘りします。
- トラフィックの急増に応じて、Podを10台、100台に増やす
- 逆に、深夜の閑散期には1台に減らしてリソースを節約する
- そして、それを「手動」ではなく「自動」で行う方法
VMの世界で「サーバを10台増やす」と言われたら、何日かかりますか?K8sなら、コマンド一発、数秒です。
次回:「スケーリングの魔法——需要に応じてPodを増減させる」
4.4 トラブルシューティングのTips
YAMLの落とし穴と、それを回避する方法
YAMLは人間にとって読みやすいフォーマットですが、いくつかの「落とし穴」があります。ここでは、よくあるエラーとその対処法を紹介します。
落とし穴1:インデントミス(タブ文字混入)
YAMLでは、インデントにスペースのみが許可されています。タブ文字を使うと、パースエラーになります。
# エラー例
error: error parsing web-deployment.yaml: error converting YAML to JSON:
yaml: line 8: found character that cannot start any token
対処法:
[Execution User: developer]
# タブ文字をスペースに変換
sed -i 's/\t/ /g' web-deployment.yaml
# タブ文字の存在を確認(何も出力されなければOK)
grep -P '\t' web-deployment.yaml
落とし穴2:apiVersionの記述間違い
K8sのリソースは、種類(kind)によって使用する apiVersion が異なります。間違えると、リソースが見つからないエラーになります。
# エラー例
error: resource mapping not found for name: "web-server" namespace: ""
from "web-deployment.yaml": no matches for kind "Deployment" in version "v1"
対処法: kubectl explain コマンドで、正しいAPIバージョンを確認できます。
[Execution User: developer]
# Deploymentの正しいAPIバージョンを確認
kubectl explain deployment | head -5
GROUP: apps
KIND: Deployment
VERSION: v1
DESCRIPTION:
...
GROUP と VERSION を組み合わせて、apiVersion: apps/v1 が正しいことがわかります。
落とし穴3:ラベルのミスマッチ
Deployment の spec.selector.matchLabels と spec.template.metadata.labels が一致していないと、Podが作成されません。
[Execution User: developer]
# Deploymentの構造を確認
kubectl explain deployment.spec.selector
ベストプラクティス: 以下のように、ラベルを明示的に一致させます。
spec:
selector:
matchLabels:
app: web # ← ここと
template:
metadata:
labels:
app: web # ← ここが一致している必要がある
便利コマンド:kubectl explain
kubectl explain は、K8sリソースの「生きたリファレンス」です。Webで検索するより早く、正確な情報を得られます。
[Execution User: developer]
# Deploymentの全フィールドを確認
kubectl explain deployment --recursive | less
# 特定のフィールドの説明を確認
kubectl explain deployment.spec.replicas
KIND: Deployment
VERSION: apps/v1
FIELD: replicas <integer>
DESCRIPTION:
Number of desired pods. This is a pointer to distinguish between explicit
zero and not specified. Defaults to 1.
困ったときは、まず kubectl explain を叩く。これをクセにしておくと、トラブルシューティングの速度が格段に上がります。
まとめ:今回学んだこと
- 命令的 vs 宣言的: VMの「手順書」はHowを記述し、K8sの「マニフェスト」はWhatを宣言する
- Reconciliation Loop: K8sは常に「理想」と「現実」の差分を監視し、自律的に埋め続ける
- 冪等性:
kubectl applyは何度実行しても同じ結果になり、差分だけが適用される - ドライラン:
--dry-runとdiffで、本番適用前に安全確認ができる - Infrastructure as Code: YAMLとGitの組み合わせで、インフラの完全な再現性が手に入る
次回予告: 「スケーリングの魔法」——replicas の値を変えるだけで、Podが増減する仕組みを体験します。そして、トラフィックの変化に応じて自動的にスケールする「オートスケーリング」の世界へ。
VMの時代、「サーバ増設」は大仕事でした。K8sでは、それが日常の些事になります。
