- Published on
Docker & Kubernetes 入門完全ガイド — コンテナからオーケストレーションまで
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 1. コンテナとは何か
- 2. Dockerの基礎
- 3. Dockerfileベストプラクティス
- 4. Docker Compose
- 5. Kubernetesアーキテクチャ
- 6. Kubernetesコアオブジェクト
- 7. Kubernetes設定管理
- 8. Helm - Kubernetesパッケージマネージャー
- 9. 実践デプロイ例 - Webアプリ + DB + Redis
- 10. トラブルシューティング
- まとめ
1. コンテナとは何か
仮想マシンとコンテナの違い
従来の仮想マシン(VM)は、ハイパーバイザー上でゲストOS全体を実行します。各VMにはカーネル、ライブラリ、バイナリがすべて含まれるため、数GBのディスクと数十秒の起動時間が必要です。
コンテナはホストOSのカーネルを共有し、プロセスレベルで分離を実現します。イメージサイズは数十MB程度で、起動時間はミリ秒単位です。
| 項目 | 仮想マシン | コンテナ |
|---|---|---|
| 分離レベル | OS全体 | プロセス |
| イメージサイズ | 数GB | 数十~数百MB |
| 起動時間 | 数十秒 | ミリ秒 |
| リソースオーバーヘッド | 高い | 低い |
| ポータビリティ | ハイパーバイザー依存 | カーネルが一致すればどこでも |
Linuxネームスペースとcgroups
コンテナの分離は、2つのLinuxカーネル機能で実現されています。
ネームスペース(Namespace) は、プロセスが見えるシステムリソースの範囲を制限します。
- PIDネームスペース: コンテナ内ではPID 1から始まる独立したプロセスツリーが見えます
- NETネームスペース: 独立したネットワークインターフェース、IPアドレス、ルーティングテーブルを持ちます
- MNTネームスペース: 独立したファイルシステムマウントポイントを持ちます
- UTSネームスペース: 独立したホスト名を持ちます
- IPCネームスペース: 独立したIPCリソースを持ちます
- USERネームスペース: 独立したUID/GIDマッピングを持ちます
cgroups(Control Groups) は、CPU、メモリ、ディスクI/Oなどのハードウェアリソース使用量を制限します。
# cgroupでメモリ制限を確認する
cat /sys/fs/cgroup/memory/docker/CONTAINER_ID/memory.limit_in_bytes
この2つの機能により、コンテナは独立したマシンのように動作しながらもカーネルを共有し、軽量な状態を維持できます。
2. Dockerの基礎
3つのコア概念
イメージ(Image) は読み取り専用のテンプレートです。アプリケーションコード、ランタイム、システムライブラリ、設定ファイルがレイヤー構造で格納されています。
コンテナ(Container) はイメージの実行インスタンスです。イメージの上に書き込み可能なレイヤーが追加されます。
レジストリ(Registry) はイメージを保存・配布するサービスです。Docker Hubが代表的で、AWS ECRやGitHub Container Registryなどのプライベートレジストリもあります。
必須コマンド
# イメージのpull
docker pull nginx:1.25
# コンテナの実行
docker run -d --name my-nginx -p 8080:80 nginx:1.25
# 実行中のコンテナを確認
docker ps
# コンテナのログを確認
docker logs my-nginx
# コンテナ内部にアクセス
docker exec -it my-nginx /bin/bash
# コンテナの停止と削除
docker stop my-nginx
docker rm my-nginx
# イメージのビルド
docker build -t my-app:1.0 .
# イメージをレジストリにプッシュ
docker tag my-app:1.0 registry.example.com/my-app:1.0
docker push registry.example.com/my-app:1.0
イメージレイヤー構造の理解
Dockerイメージは複数の読み取り専用レイヤーで構成されています。Dockerfileの各命令が1つのレイヤーを作成します。
# イメージレイヤーの確認
docker history my-app:1.0
レイヤーはキャッシュされます。変更されていないレイヤーは再ビルドされないため、Dockerfileで変更頻度の低い命令を上部に配置することがビルド速度最適化の鍵です。
3. Dockerfileベストプラクティス
基本的なDockerfile構造
# ベースイメージの指定
FROM node:20-alpine
# 作業ディレクトリの設定
WORKDIR /app
# 依存関係ファイルを先にコピー(キャッシュ最適化)
COPY package.json package-lock.json ./
RUN npm ci --only=production
# ソースコードをコピー
COPY . .
# ポートの公開
EXPOSE 3000
# 実行コマンド
CMD ["node", "server.js"]
マルチステージビルド
ビルドツールとランタイム環境を分離し、最終イメージのサイズを大幅に削減できます。
# ステージ1: ビルド
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
# ステージ2: プロダクション
FROM node:20-alpine AS production
WORKDIR /app
COPY /app/dist ./dist
COPY /app/node_modules ./node_modules
COPY /app/package.json ./
EXPOSE 3000
USER node
CMD ["node", "dist/server.js"]
ビルド段階でのみ必要なdevDependencies、ソースコード、ビルドツールが最終イメージに含まれません。
レイヤーキャッシュの最適化
# 悪い例: ソースが変更されるたびにnpm installが再実行される
COPY . .
RUN npm ci
# 良い例: package.jsonが変更されていなければキャッシュを活用
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
.dockerignoreファイル
不要なファイルがビルドコンテキストに含まれないようにします。
node_modules
.git
.env
*.md
dist
.DS_Store
coverage
セキュリティベストプラクティス
# 1. rootでないユーザーで実行
FROM node:20-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
# 2. 特定バージョンのベースイメージを使用(latestタグは使用禁止)
FROM node:20.11.1-alpine3.19
# 3. ADDではなくCOPYを使用
COPY ./config /app/config
# 4. 機密情報をイメージに含めない
# ビルド時のARGを使用する場合は最終イメージに残らないよう注意
4. Docker Compose
マルチコンテナオーケストレーション
Docker Composeは、複数のコンテナを1つのYAMLファイルで定義・管理します。
version: "3.9"
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://user:pass@db:5432/mydb
- REDIS_URL=redis://cache:6379
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
volumes:
- ./src:/app/src
networks:
- backend
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user"]
interval: 10s
timeout: 5s
retries: 5
networks:
- backend
cache:
image: redis:7-alpine
ports:
- "6379:6379"
networks:
- backend
volumes:
postgres_data:
networks:
backend:
driver: bridge
Composeの主要コマンド
# 全サービスの起動
docker compose up -d
# ログの確認
docker compose logs -f app
# 特定サービスを再ビルドして起動
docker compose up -d --build app
# サービスの状態確認
docker compose ps
# 全サービスの停止とリソースのクリーンアップ
docker compose down -v
ネットワークとボリューム
ネットワーク: 同じComposeファイル内のサービスは、サービス名で互いに通信できます。上記の例では、appサービスは db:5432 でデータベースにアクセスします。
ボリューム: コンテナが削除されてもデータが保持されます。postgres_data という名前付きボリュームがデータベースファイルを保管します。
開発環境 vs プロダクション環境
# docker-compose.override.yml(開発環境で自動適用)
services:
app:
build:
target: development
volumes:
- ./src:/app/src
environment:
- NODE_ENV=development
command: npm run dev
# プロダクションデプロイ時はoverrideを除外
docker compose -f docker-compose.yml up -d
5. Kubernetesアーキテクチャ
なぜKubernetesなのか
Docker Composeは単一ホストで複数コンテナを管理するのに適していますが、実際のプロダクション環境では以下が必要です。
- 複数サーバーにまたがるコンテナデプロイ
- オートスケーリング
- サービスディスカバリとロードバランシング
- ローリングアップデートとロールバック
- 自己修復(self-healing)
Kubernetes(K8s)はこれらすべてを提供するコンテナオーケストレーションプラットフォームです。
Control Planeコンポーネント
API Server(kube-apiserver): クラスタのすべてのリクエストを処理する中央ゲートウェイです。kubectlコマンド、内部コンポーネント、外部クライアントのすべてがAPI Serverを経由します。
etcd: クラスタの全状態を保存する分散キーバリューストアです。どのPodがどこで実行されているか、どのServiceが存在するかなどの情報が格納されています。
Scheduler(kube-scheduler): 新しく作成されたPodをどのノードに配置するかを決定します。リソース要件、ノードの状態、アフィニティルールなどを考慮します。
Controller Manager(kube-controller-manager): クラスタの現在の状態を希望する状態(desired state)に合わせる役割を担います。ReplicaSet Controller、Node Controller、Job Controllerなどが含まれます。
Worker Nodeコンポーネント
kubelet: 各ノードで実行され、API Serverの指示に従ってコンテナを実行し、状態を報告します。
kube-proxy: 各ノードでネットワークルールを管理し、Serviceのロードバランシングを処理します。
Container Runtime: 実際にコンテナを実行するソフトウェアです。containerdが最も広く使用されています。
Control Plane
+------------------+
| API Server |<--- kubectl、クライアント
| etcd |
| Scheduler |
| Controller Mgr |
+------------------+
|
Worker Node 1 Worker Node 2
+-----------------+ +-----------------+
| kubelet | | kubelet |
| kube-proxy | | kube-proxy |
| containerd | | containerd |
| [Pod] [Pod] | | [Pod] [Pod] |
+-----------------+ +-----------------+
6. Kubernetesコアオブジェクト
Pod
PodはK8sでデプロイ可能な最小単位です。1つ以上のコンテナを含み、同じネットワークとストレージを共有します。
apiVersion: v1
kind: Pod
metadata:
name: my-app
labels:
app: my-app
spec:
containers:
- name: app
image: my-app:1.0
ports:
- containerPort: 3000
resources:
requests:
memory: "128Mi"
cpu: "250m"
limits:
memory: "256Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /healthz
port: 3000
initialDelaySeconds: 10
periodSeconds: 5
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 3
ReplicaSet
指定された数のPodレプリカが常に実行されることを保証します。通常は直接使用せず、Deploymentを通じて管理します。
Deployment
宣言的にPodとReplicaSetを管理します。ローリングアップデート、ロールバック、スケーリングをサポートします。
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: app
image: my-app:1.0
ports:
- containerPort: 3000
resources:
requests:
memory: "128Mi"
cpu: "250m"
limits:
memory: "256Mi"
cpu: "500m"
# デプロイの状態確認
kubectl rollout status deployment/my-app
# イメージの更新(ローリングアップデートのトリガー)
kubectl set image deployment/my-app app=my-app:2.0
# ロールバック
kubectl rollout undo deployment/my-app
# スケーリング
kubectl scale deployment/my-app --replicas=5
Service
Podに安定したネットワークエンドポイントを提供します。Podは一時的ですが、ServiceのIPとDNSは固定です。
ClusterIP(デフォルト): クラスタ内部からのみアクセス可能です。
apiVersion: v1
kind: Service
metadata:
name: my-app-svc
spec:
type: ClusterIP
selector:
app: my-app
ports:
- port: 80
targetPort: 3000
NodePort: 各ノードの特定ポートを通じて外部からアクセスします。
apiVersion: v1
kind: Service
metadata:
name: my-app-nodeport
spec:
type: NodePort
selector:
app: my-app
ports:
- port: 80
targetPort: 3000
nodePort: 30080
LoadBalancer: クラウドプロバイダーのロードバランサーを自動的にプロビジョニングします。
apiVersion: v1
kind: Service
metadata:
name: my-app-lb
spec:
type: LoadBalancer
selector:
app: my-app
ports:
- port: 80
targetPort: 3000
Ingress
HTTP/HTTPSトラフィックをクラスタ内部のServiceにルーティングします。ホスト名とパスに基づくルーティングルールを定義できます。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app-svc
port:
number: 80
- path: /api
pathType: Prefix
backend:
service:
name: api-svc
port:
number: 80
tls:
- hosts:
- app.example.com
secretName: tls-secret
7. Kubernetes設定管理
ConfigMap
環境設定をコンテナイメージから分離して管理します。
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
DATABASE_HOST: "db-service"
DATABASE_PORT: "5432"
LOG_LEVEL: "info"
app.properties: |
server.port=3000
cache.ttl=300
# PodでConfigMapを使用
spec:
containers:
- name: app
image: my-app:1.0
envFrom:
- configMapRef:
name: app-config
volumeMounts:
- name: config-volume
mountPath: /app/config
volumes:
- name: config-volume
configMap:
name: app-config
items:
- key: app.properties
path: app.properties
Secret
パスワード、APIキーなどの機密データを管理します。base64エンコードされて保存されます。
# Secretの作成
kubectl create secret generic db-secret \
--from-literal=username=admin \
--from-literal=password=s3cret
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
data:
username: YWRtaW4=
password: czNjcmV0
# PodでSecretを使用
spec:
containers:
- name: app
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: db-secret
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
PersistentVolume(PV)とPersistentVolumeClaim(PVC)
Podのライフサイクルとは独立してデータを保持します。
# PersistentVolumeClaim
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: standard
# DeploymentでPVCを使用
spec:
containers:
- name: postgres
image: postgres:16-alpine
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
volumes:
- name: postgres-storage
persistentVolumeClaim:
claimName: postgres-pvc
8. Helm - Kubernetesパッケージマネージャー
Helmとは
HelmはK8sマニフェストをパッケージ(チャート)にまとめて管理するツールです。複数のYAMLファイルを1つの単位としてインストール、アップグレード、ロールバックできます。
チャート構造
my-app-chart/
Chart.yaml # チャートメタデータ
values.yaml # デフォルト設定値
templates/ # K8sマニフェストテンプレート
deployment.yaml
service.yaml
ingress.yaml
configmap.yaml
_helpers.tpl # テンプレートヘルパー関数
charts/ # 依存関係チャート
values.yaml
# values.yaml
replicaCount: 3
image:
repository: my-app
tag: "1.0"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
ingress:
enabled: true
hostname: app.example.com
resources:
requests:
cpu: 250m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilization: 70
Helmの主要コマンド
# チャートのインストール
helm install my-release ./my-app-chart
# カスタムvaluesでインストール
helm install my-release ./my-app-chart -f production-values.yaml
# リリースのアップグレード
helm upgrade my-release ./my-app-chart --set image.tag=2.0
# リリース一覧の確認
helm list
# リリースの状態確認
helm status my-release
# リリース履歴
helm history my-release
# ロールバック
helm rollback my-release 1
# リリースの削除
helm uninstall my-release
環境別valuesファイル管理
# 環境別ファイル構造
values.yaml # 共通デフォルト値
values-dev.yaml # 開発環境
values-staging.yaml # ステージング環境
values-prod.yaml # プロダクション環境
# ステージングデプロイ
helm upgrade --install my-app ./my-app-chart \
-f values.yaml \
-f values-staging.yaml \
--namespace staging
# プロダクションデプロイ
helm upgrade --install my-app ./my-app-chart \
-f values.yaml \
-f values-prod.yaml \
--namespace production
9. 実践デプロイ例 - Webアプリ + DB + Redis
全体構成
Webアプリケーション、PostgreSQLデータベース、RedisキャッシュをKubernetesにデプロイする完全な例です。
Namespaceの作成
kubectl create namespace my-app
PostgreSQLのデプロイ
# postgres-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
namespace: my-app
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16-alpine
ports:
- containerPort: 5432
env:
- name: POSTGRES_DB
value: mydb
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
volumes:
- name: postgres-data
persistentVolumeClaim:
claimName: postgres-pvc
---
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: my-app
spec:
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432
Redisのデプロイ
# redis-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: my-app
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "250m"
---
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: my-app
spec:
selector:
app: redis
ports:
- port: 6379
targetPort: 6379
Webアプリケーションのデプロイ
# app-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
namespace: my-app
spec:
replicas: 3
selector:
matchLabels:
app: web-app
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: web-app
image: my-web-app:1.0
ports:
- containerPort: 3000
env:
- name: DATABASE_URL
value: "postgres://$(DB_USER):$(DB_PASS)@postgres:5432/mydb"
- name: REDIS_URL
value: "redis://redis:6379"
- name: DB_USER
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: DB_PASS
valueFrom:
secretKeyRef:
name: db-credentials
key: password
livenessProbe:
httpGet:
path: /healthz
port: 3000
initialDelaySeconds: 15
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
memory: "128Mi"
cpu: "250m"
limits:
memory: "256Mi"
cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
name: web-app
namespace: my-app
spec:
type: ClusterIP
selector:
app: web-app
ports:
- port: 80
targetPort: 3000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-app-ingress
namespace: my-app
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-app
port:
number: 80
デプロイ手順
# 1. Secretの作成
kubectl create secret generic db-credentials \
--from-literal=username=admin \
--from-literal=password=secure-password-here \
-n my-app
# 2. PVCの作成
kubectl apply -f postgres-pvc.yaml
# 3. データベースのデプロイ
kubectl apply -f postgres-deployment.yaml
# 4. Redisのデプロイ
kubectl apply -f redis-deployment.yaml
# 5. Webアプリのデプロイ
kubectl apply -f app-deployment.yaml
# 6. 状態の確認
kubectl get all -n my-app
10. トラブルシューティング
CrashLoopBackOff
Podが繰り返し起動とクラッシュを繰り返している状態です。
# 原因の確認
kubectl describe pod POD_NAME -n my-app
kubectl logs POD_NAME -n my-app --previous
# よくある原因:
# - アプリケーション起動時のエラー
# - 不正な環境変数
# - 依存サービスへの接続失敗
# - livenessProbeの失敗
解決方法: ログを確認してエラーを特定します。livenessProbeのinitialDelaySecondsを増やすか、環境変数が正しいか確認します。
ImagePullBackOff
コンテナイメージを取得できない状態です。
# 原因の確認
kubectl describe pod POD_NAME -n my-app
# よくある原因:
# - イメージ名やタグのタイプミス
# - プライベートレジストリ認証の未設定
# - イメージが存在しない
# プライベートレジストリ認証の設定
kubectl create secret docker-registry regcred \
--docker-server=registry.example.com \
--docker-username=user \
--docker-password=pass \
-n my-app
OOMKilled
コンテナがメモリ制限を超過して強制終了された状態です。
# 原因の確認
kubectl describe pod POD_NAME -n my-app
# リアルタイムのリソース使用量を確認
kubectl top pod -n my-app
# 解決: メモリlimitsの値を増やす
resources:
limits:
memory: "512Mi" # 既存の256Miから増加
一般的なデバッグコマンド
# Pod一覧と状態の確認
kubectl get pods -n my-app -o wide
# Podの詳細情報(イベント含む)
kubectl describe pod POD_NAME -n my-app
# リアルタイムログの確認
kubectl logs -f POD_NAME -n my-app
# Pod内部へのアクセス
kubectl exec -it POD_NAME -n my-app -- /bin/sh
# Serviceエンドポイントの確認
kubectl get endpoints -n my-app
# クラスタ内部でのDNS確認
kubectl run debug --rm -it --image=busybox -- nslookup web-app.my-app.svc.cluster.local
# ノードリソースの状況
kubectl top nodes
まとめ
この記事で取り上げた内容を整理します。
- コンテナの基礎: ネームスペースとcgroupsによる軽量な分離の実現
- Docker: イメージビルド、マルチステージビルド、セキュリティプラクティス
- Docker Compose: 開発環境でのマルチコンテナ管理
- K8sアーキテクチャ: Control PlaneとWorker Nodeの役割
- K8sオブジェクト: Pod、Deployment、Service、Ingressの活用
- 設定管理: ConfigMap、Secret、PV/PVC
- Helm: チャートを利用したリリース管理
- 実践デプロイ: Webアプリ + DB + Redisの3層アーキテクチャ
- トラブルシューティング: CrashLoopBackOff、ImagePullBackOff、OOMKilledへの対応
DockerとKubernetesは現代のインフラの基盤です。この記事の例をローカル環境(minikubeまたはkind)で実際に試してみてください。Podをデプロイし、Serviceを構成し、トラブルシューティングを体験することが最も早い学習方法です。