docker-compose から k8s へ
付録A — Docker / docker-compose まで触れてきた読者が K8s へ移っていくときに詰まる7つの違いを整理します。docker-compose.yml の各キーが K8s のどのリソースに対応するかをマッピング表で示し、小さな web + db の docker-compose.yml を K8s マニフェストへ移す流れをたどり、kompose 自動変換ツールの限界とその次の段階を挙げます。本書の最後の章ですが、Docker まで触れてきた読者には出発点になります。
付録A です。本書を第1章〜第31章までたどってきた読者には付録ですが、Docker / docker-compose まで触れてきて本書を初めて開いた読者には出発点です。1章 Kubernetes とは何か の終わりで「本文が進む中で何度も開き直すことになる」と案内したそのマッピング表が、この付録の中心です。
この付録の目標は2つです。
docker-compose.ymlの各キーが K8s のどのリソースに対応するか — 一表で大きな絵。- そのマッピングが単純な 1 : 1 ではない7つの論点 — 同じ意図でも現れ方が異なります。
この2つが手に入れば、K8s 本文の第1章〜第6章が自然に馴染んできます。
メンタルモデルの違い — 単一ホスト、単一ネットワーク vs 複数ノード、複数コントローラ #
docker-compose up の一コマンドで起こることを一行で整理すると次のようになります。
1つのホストの上に:
- 1つのネットワークが作られ (compose-default)
- その中にコンテナ N 個が立ち上がり
- 同じネットワークのサービス同士は service name で呼び出し可能
- ホストのディスクにデータをマウント
- restart: always がダウン時に自動再起動K8s はこのモデルを 複数のノード、複数のコントローラ、複数のネットワーク層 へ分解した仕組みです。同じ一行を K8s へ移すと次のようになります。
複数のノードの上に:
- 複数のネームスペース (隔離単位)
- その中の Pod たちが複数のノードに散らばっている
- Service が Pod の仮想 IP + DNS 名を提供
- PersistentVolume がノードと分離された永続ディスク
- Deployment コントローラがダウン時に自動再起動 + ローリングアップデート
- コントロールプレーン (API Server, scheduler, controller-manager, etcd) が上のすべてを調整単一ホストのシンプルさ vs 複数ノードの分散 の違いがメンタルモデルの核心です。compose ではすべてのコンテナが同じホストの stdout へログを落とし、同じネットワークにありますが、K8s では Pod がどのノードに立ち上がるかがマニフェストの決定によって異なり、ログもノード別に分散されます。
この違いが7つの論点の出発点です。
リソースマッピング — 大きな絵 #
docker-compose.yml のよく使うキーが K8s のどのリソースへ移されるかを一表に整理します。
| docker-compose | Kubernetes | 本書の章 |
|---|---|---|
services | Deployment + Service | 4章, 5章 |
image | Pod spec の containers[].image | 3章 |
command / entrypoint | Pod spec の command / args | 3章 |
ports: "8080:8080" | Service (ClusterIP / NodePort / LoadBalancer) + Ingress | 5章, 10章 |
volumes (named) | PersistentVolumeClaim + StorageClass | 9章 |
volumes (bind mount) | hostPath (開発用) または ConfigMap (設定ファイル) | 9章, 6章 |
environment / env_file | ConfigMap + Secret + envFrom | 6章 |
restart: always | Deployment のデフォルト (ReplicaSet) | 4章 |
depends_on | initContainer / Job / Helm hook (正確な 1 : 1 なし) | 8章, 23章 |
healthcheck | readinessProbe + livenessProbe + startupProbe | 12章 |
networks | NetworkPolicy + Service DNS + CNI | 5章, 14章, 15章 |
deploy.replicas | Deployment の replicas | 4章 |
deploy.resources | Pod spec の resources.requests/limits | 11章 |
deploy.update_config | Deployment の strategy.rollingUpdate | 4章 |
deploy.placement.constraints | nodeSelector / affinity / taints | 22章 |
secrets: (Compose) | Secret + (sealed-secrets / external-secrets) | 6章, 29章 |
configs: (Compose) | ConfigMap (volumeMount または envFrom) | 6章 |
表だけでも見えてくるポイントがあります。services の一行が K8s では2つのオブジェクト (Deployment + Service) へ分かれます。 ワークロード自体 (Deployment) とそのワークロードの入り口 (Service) が分離された責任だというのが K8s の考え方です。compose の services が2つの仕事を1つの概念で束ねていたのに対し、K8s は2つのオブジェクトに分けてそれぞれのライフサイクルを独立して運用します。
もう1つ — depends_on の正確な K8s 対応はありません。 最も近いパターンが initContainer (Pod 起動前の事前作業) ですが、「db が別のサービスとして立ち上がってから web が起動する」という意味は K8s の非同期モデルと相性が異なります。K8s は2つのワークロードの起動順序を強制せず、readiness と retry でこの問題を解決します — 詳しい内容は §「よく詰まる7つ」の5番目で扱います。
よく詰まる7つの違い #
1. ネットワーキング — service name DNS vs ClusterIP + Service #
compose の経験:
services:
web:
image: nginx
db:
image: postgresweb コンテナの中で psql -h db ... がそのまま動きます。compose が作った基本ネットワークの中で service name が DNS として自動解決されます。
K8s の対応:
apiVersion: v1
kind: Service
metadata:
name: db
namespace: default
spec:
selector:
app: db
ports:
- port: 5432K8s では Service オブジェクトが明示的に作られてこそ web Pod の中から db (または db.default.svc.cluster.local) へ呼び出せます。Pod 同士の直接呼び出しではなく Service を経由することが標準 です。
理由は 5章 Service にあります — Pod は死んで立ち上がり直すたびに IP が変わりますが、Service は安定した仮想 IP と DNS 名を保証します。compose の service name の自動解決は、Docker の embedded DNS が単一ホスト内の単一ネットワークを追跡してくれる仕組みであり、K8s は複数のノードをまたぐ安定した入り口を Service という別のオブジェクトとして分離しています。
2. サービスディスカバリ — 同じネットワークの自動発見 vs Service + endpoints #
上と論点は同じですが、見る角度が異なります。compose では 暗黙的に 自動発見されます。K8s では 明示的な Service の定義があってこそ発見されます。
kubectl get endpoints dbEndpoints が Service の selector と一致する Pod の IP リストです。この2つが空だったりずれていると呼び出しができません — 27章 kubectl デバッグパターン §「Service / Ingress に届かないとき」の selector → endpoints → port の3段チェーンそのままです。
compose の自動発見はシンプルですが単一ホストの中でしか動作しません。K8s の明示的なモデルは手がよりかかりますが クラスタ全体で一貫して動作する という違いがあります。
3. 永続ボリューム — ホストパスの自動マウント vs PVC 要求 + StorageClass #
compose の経験:
services:
db:
image: postgres
volumes:
- db-data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
volumes:
db-data:named volume は自動生成され、bind mount はホストのファイルパスをそのままマウントします。1つのホストのディスクがそのまま見えるシンプルなモデルです。
K8s の対応:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: db-data
spec:
storageClassName: gp3
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 10Gi
---
# Pod の volumeMount + volumes
spec:
containers:
- name: db
image: postgres
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumes:
- name: data
persistentVolumeClaim:
claimName: db-data9章 PV / PVC / StorageClass のモデルです。PVC が要求、PV が実際のディスク、StorageClass が自動プロビジョニングのポリシー の3つのオブジェクトへ分かれます。
理由は K8s の前提にあります — Pod がどのノードに立ち上がるかをあらかじめ知ることができないので、ノードのホストディスクに依存してはいけません。 EBS / EFS / GCP PD のようなノードと独立した永続ディスクを PVC で要求し、StorageClass が自動的にプロビジョニングして Pod へマウントします。
compose の bind mount に対応するパターンは本書の標準経路にはありません — 運用環境のホストパスのマウントは、セキュリティと移植性の面で推奨されません。開発環境の hostPath は可能ですが本文では扱いません。
4. シークレット — env_file vs Secret + 運用ライフサイクル #
compose の経験:
services:
api:
image: myapi
env_file:
- .env.env の1ファイルにパスワードと API キーを書いておいて、.gitignore に追加すれば終わりです。シンプルです。
K8s の対応:
apiVersion: v1
kind: Secret
metadata:
name: api-secrets
type: Opaque
stringData:
DATABASE_PASSWORD: "..."
---
# Pod の envFrom
spec:
containers:
- name: api
envFrom:
- secretRef:
name: api-secrets6章 ConfigMap と Secret のモデルです。マニフェストの data は base64 エンコーディングであるだけで暗号化ではありません — だから本マニフェストをそのまま git にコミットすると秘密が露出します。
この問題を解く本格的な道具が 29章 シークレット運用 の3つのオプション (sealed-secrets / external-secrets / SOPS) です。compose の env_file + .gitignore モデルは K8s ではより精緻な運用ライフサイクルへ分かれます — 保存 · 回転 · 注入 · 監査の4軸が本格的な論点であり、シンプルさの代償です。
5. ヘルスチェック — 1段階 vs 3段階 #
compose の経験:
services:
api:
image: myapi
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s1つの healthcheck でコンテナの「健康さ」を判断します。unhealthy が一定回数累積されると (restart: on-failure と連動すると) 再起動です。
K8s の対応:
spec:
containers:
- name: api
readinessProbe:
httpGet: { path: /health/ready, port: http }
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet: { path: /health/live, port: http }
initialDelaySeconds: 30
periodSeconds: 10
startupProbe:
httpGet: { path: /health/live, port: http }
failureThreshold: 30
periodSeconds: 1012章 ヘルスチェック で扱ったモデルそのままです — readiness / liveness / startup の3つの役割に分かれます。
- readiness — トラフィックを受ける準備ができているか。fail なら Service endpoints から除外。
- liveness — コンテナが生きているか。fail が累積されると kubelet がコンテナを再起動。
- startup — 初期化中か。初期化時間が長いワークロードの猶予。
compose の1つの healthcheck が K8s で3つの役割へ分かれた理由 — 「トラフィックを受けられる」と「生きている」と「初期化中だ」が異なる意図 だという点です。同じ endpoint を fail しても意味が異なります。1段階に束ねると、初期化中に liveness が fail してコンテナが永遠に再起動される事故が発生します。
depends_on の対応もここで解けます。compose の「db が healthy になった後に api 起動」は K8s では api の readinessProbe が db の応答を確認するパターン で扱います — 起動順序を強制せず、「db が応答しなければ readiness が false → Service endpoints に載せない → トラフィックが来ない」という流れで自然に解決されます。より明示的さが必要なら initContainer で db の応答を待つパターンです。
6. スケーリング — --scale の単一ホストの限界 vs HPA + Cluster Autoscaler
#
compose の経験:
docker-compose up --scale api=31つのホストの限度までは増やせますが、そのホストの CPU / メモリの限界に到達すると終わりです。
K8s の対応:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api
spec:
scaleTargetRef:
kind: Deployment
name: api
minReplicas: 2
maxReplicas: 50
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 6013章 オートスケーリング の HPA と Cluster Autoscaler / Karpenter の2段階の連携が K8s の答えです。Pod が増えるとノードが自動的に追加される 形がコンテナオーケストレーションの本質の1つであり、compose にはなかった仕組みです。
7. ログ — 単一ホストの stdout vs ノード別の分散 + エージェント #
compose の経験:
docker-compose logs -f api単一ホストのすべてのコンテナの stdout が1か所に集まります。シンプルです。
K8s の対応:
kubectl logs <pod-name>
kubectl logs -l app=api --tail=100 # ラベル単位Pod が複数のノードに散らばっているので、各ノードの kubelet がそのノードのコンテナのログを持っています。kubectl logs が API Server を経由して各ノードのログを取ってきます。
運用環境ではノード別のログエージェント (19章 可観測性 §「Loki — 軽量なログスタック」の Promtail、または 25章 モニタリング · アラート の Fluent Bit) がすべてのログを中央ストレージ (Loki / CloudWatch) へ集める仕組みが追加されます。compose の一行のコマンドが K8s では 収集エージェント + 中央ストレージ + 検索インターフェース の3つのコンポーネントへつながります。
移行の一連の流れ — 小さな web + db の例 #
最もよくある docker-compose のセットを K8s マニフェストへ移してみます。
version: "3"
services:
web:
image: nginx:1.27-alpine
ports:
- "80:80"
depends_on:
- api
environment:
API_URL: http://api:8000
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/"]
interval: 30s
api:
image: myapi:1.0
ports:
- "8000:8000"
environment:
DATABASE_URL: postgresql://app:secret@db:5432/myapp
depends_on:
- db
db:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
POSTGRES_DB: myapp
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:この1ファイルが K8s の約 200 行のマニフェストへつながります。核心のオブジェクトだけを挙げていきます。
1. Namespace #
apiVersion: v1
kind: Namespace
metadata:
name: myappcompose にはない考え方です。K8s の隔離単位 — 7章 Namespace とラベル の最初のオブジェクトです。
2. Secret (DB パスワード) #
apiVersion: v1
kind: Secret
metadata:
name: db
namespace: myapp
type: Opaque
stringData:
POSTGRES_PASSWORD: secret
POSTGRES_USER: app
POSTGRES_DB: myappcompose の environment の平文が K8s の Secret へ分離されます。運用では 29章 の3つのオプションのうち1つで封印しますが、学習用マニフェストではいったん平文の stringData から始めます。
3. PVC (DB データ) #
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: db-data
namespace: myapp
spec:
storageClassName: gp3 # または minikube の standard
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 10Gicompose の named volume が PVC + StorageClass へ解かれます。
4. DB — Deployment + Service #
apiVersion: apps/v1
kind: Deployment
metadata:
name: db
namespace: myapp
spec:
replicas: 1
selector:
matchLabels:
app: db
template:
metadata:
labels:
app: db
spec:
containers:
- name: postgres
image: postgres:16
envFrom:
- secretRef:
name: db
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
readinessProbe:
exec:
command: ["pg_isready", "-U", "app"]
periodSeconds: 5
volumes:
- name: data
persistentVolumeClaim:
claimName: db-data
---
apiVersion: v1
kind: Service
metadata:
name: db
namespace: myapp
spec:
selector:
app: db
ports:
- port: 5432
targetPort: 5432運用 PostgreSQL は通常 8章 StatefulSet · DaemonSet · Job の StatefulSet へ行きますが、単一インスタンスの学習シナリオでは Deployment + 単一 PVC で十分です。運用の標準は 23章 DB 連携 のマネージド RDS です — K8s の中に DB を直接立ち上げません。
5. API — Deployment + Service #
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
namespace: myapp
spec:
replicas: 2
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: myapi:1.0
env:
- name: DATABASE_URL
value: "postgresql://app:$(POSTGRES_PASSWORD)@db.myapp.svc.cluster.local:5432/myapp"
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: db
key: POSTGRES_PASSWORD
readinessProbe:
httpGet: { path: /health, port: 8000 }
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet: { path: /health, port: 8000 }
initialDelaySeconds: 30
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: api
namespace: myapp
spec:
selector:
app: api
ports:
- port: 8000
targetPort: 8000compose の depends_on: [db] は K8s では readinessProbe の流れで解けます — api の readiness が false の間は Service endpoints から除外されて web が api を見られません。db が立ち上がって api が db に接続できるようになると readiness が true になりトラフィックが流れます。
6. Web — Deployment + Service + Ingress #
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: myapp
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.27-alpine
env:
- name: API_URL
value: "http://api.myapp.svc.cluster.local:8000"
readinessProbe:
httpGet: { path: /, port: 80 }
---
apiVersion: v1
kind: Service
metadata:
name: web
namespace: myapp
spec:
selector:
app: web
ports:
- port: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
namespace: myapp
spec:
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80compose の ports: "80:80" の一行が K8s では Service (クラスタ内部の仮想 IP) + Ingress (外部の入り口) へ分かれます。外部公開の決定は Service ではなく Ingress で — 5章 Service + 10章 Ingress の分離モデルです。
適用 #
kubectl apply -f namespace.yaml
kubectl apply -f secret.yaml
kubectl apply -f pvc.yaml
kubectl apply -f db.yaml
kubectl apply -f api.yaml
kubectl apply -f web.yamlこの6段階が終わると、compose の一コマンド (docker-compose up) と同じシステムが K8s の上に立ち上がっています。違いは 各オブジェクトが明示的でライフサイクルが分離されている という点です。1つのオブジェクトだけを更新でき、1つのオブジェクトだけをデバッグでき、1つのオブジェクトだけ権限を分離できます。
マニフェストの総行数は約 200 行 — compose の 30 行に比べれば6倍以上です。この違いが K8s の学習曲線の出発点ですが、その分の表現力と運用のための本格的な道具を受け取れます。
kompose ツールの限界 #
kompose が docker-compose.yml を K8s マニフェストへ自動変換してくれる道具です。
kompose convert -f docker-compose.yml -o k8s-manifests/このコマンドが上の6段階の K8s マニフェストを一度に作ってくれます — 出発点として有用です。しかし自動変換には限界があります。
1. depends_on -> initContainer マッピングはシンプルだが、意図された readiness パターンではない
2. healthcheck -> probe 変換は1段階 (liveness) のみ — 3つの役割の分離は人の手
3. bind mount -> hostPath へ変換 — 運用では不適合
4. networks の隔離 -> NetworkPolicy へ変換されない
5. secrets / configs の封印オプション -> マニフェストの平文 Secret へのみ変換
6. Ingress マニフェスト -> kompose が作ってくれない。常に人が追加
7. PersistentVolume の StorageClass の決定 -> クラスタごとに異なるので人が選択kompose は出発点であってゴールではありません。 自動変換されたマニフェストをそのまま運用に載せると、本書の7つの違いをほとんどすべて無視した結果になります。運用マニフェストへ進むには、本書の第1章〜第14章の要点を適用して手で整える必要があります。
代替として Helm チャートで最初から書く 流れのほうが自然です。compose のまとまりを helm create myapp で始めて、本書のパターンを1マニフェストずつ適用するのが運用 K8s の標準の入り口経路です。
付録A の次の段階 — どこへ進むべきか #
本付録を終えた時点で、Docker / docker-compose まで来ていた読者に推奨する本書の入り口経路は次のとおりです。
[付録A 終了]
|
v
[1章 Kubernetes とは何か] -- 本付録を終えた時点では大きな絵がすでに掴めている
[2章 ローカル環境] -- kind または minikube を立ち上げる
[3章 kubectl と最初の Pod] -- 上の移行の一連の流れ の最初のマニフェスト
[4章 Deployment] -- Deployment モデルの本格
[5章 Service] -- ClusterIP / NodePort / LoadBalancer + DNS
[6章 ConfigMap · Secret] -- envFrom の本格化
[7章 Namespace とラベル] -- 隔離と分類
|
v
1部完了 -- compose のすべての要点が K8s のセットに入っている状態1章 Kubernetes とは何か が本格的な入り口の最初の章であり、本付録のマッピング表をそばに置いて本文をたどると、章ごとに「これは compose のどのキーだったか」が自然につながります。
本書の 1 部 (1 ~ 7 章) までたどると、本付録の移行マニフェストが手に馴染んだ結に見えます。その時点から 2 部 (8 ~ 14 章) へ進んで StatefulSet · PV · Ingress · リソース管理 · ヘルスチェック · オートスケーリング · RBAC まで扱い終えると、小さなクラスタでさまざまなワークロードを運用できる視野が掴めます。3 部 (15 ~ 20 章) の深さ、4 部 (21 ~ 26 章) の EKS 実戦、5 部 (27 ~ 30 章) の運用 · デバッグ · コスト、6 部 (31章) のキャップストーンまでが本書全体の大きな絵です。
練習問題 #
- 自分が運用していたり学習中の小さな
docker-compose.ymlの1ファイルを選んで、本付録 §「移行の一連の流れ」の6段階に従って K8s マニフェストへ移してみます。変換前 / 後のマニフェストの行数を比較し、増えた行がどんな要点の追加なのか (隔離 · ライフサイクルの分離 · 明示的なオブジェクト) を一段落で整理します。 - 同じ
docker-compose.ymlをkompose convertで自動変換した結果と、自分で手で移した結果を比較します。2つの結果の違いを §「kompose の限界」の7つの項目のうちどれに該当するかで分類し、自動変換が埋められなかった点を本文のどの章で補完すべきかをマッピングします。 - 本付録の §「よく詰まる7つの違い」のうち、自分の経験で最も腑に落ちる違い1つを選んで、自分のワークロードの docker-compose モデルが K8s でどんな形へ変わるかを1ページにあらかじめ描いてみます。このメモを本書の本文を読んでいる間そばに置いて、該当する章に到達したときその対応が実際にどう解けるかを検証します。
一行まとめ: docker-compose の単一ホスト / 単一ネットワーク / 単純な service name DNS モデルが、K8s では複数ノード / 複数ネームスペース / Service + endpoints の明示的なモデルへ移ります。
servicesの一行が Deployment + Service の2オブジェクトへ、volumesが PVC + StorageClass へ、env_fileが Secret + 運用ライフサイクルへ、healthcheckの1段階が readiness + liveness + startup の3段階へ、--scaleの単一ホストの限界が HPA + Cluster Autoscaler の2段階の自動反応へ分かれる。depends_onの正確な対応はなく readiness probe の自然な流れで解けます。kompose は出発点を自動化するがゴールではない。第1章〜第14章の要点を手で適用してこそ運用マニフェストになります。本付録を終えた時点で 1 章へ入ると、マッピング表が章ごとに自然に解けていきます。
本の終わり #
本付録で、本書の 32 章 (1 ~ 31 章 + 付録A) がすべて手に入りました。本書の本文の最後の章である 31章 フルスタックアプリの EKS 配備 の事後の振り返り表が本書の一行まとめであり、本付録のマッピング表が本書の出発点です。出発点とゴールが一冊の本の中に束ねられた形 が本書の構造です。
1章 Kubernetes とは何か から本格的な本文を始め、付録A が本文の進行中にマッピングが曖昧になったときに再び戻ってくる reference になってくれることを期待します。