Kubernetes 学習ログ

Cloud Pratica で公開されている内容を参考にしつつ、 Kubernetes の学習を進めます。

kubectl
kubectlはKubernetesクラスターを操作するためのコマンドラインツールです。
インストール
brew install kubectl
基本コマンド
# クラスター情報を確認
kubectl cluster-info
# ノード一覧を表示
kubectl get nodes
# Pod一覧を表示
kubectl get pods
# 詳細情報を表示
kubectl describe pod <pod-name>

Minikube
MinikubeはローカルでKubernetesクラスターを簡単に構築できるツールです。
インストールと起動
# macOS
brew install minikube
# クラスター開始
minikube start
# 状態確認
minikube status
# ダッシュボード起動
minikube dashboard
brew install minikube
$ brew install minikube
==> Downloading https://formulae.brew.sh/api/formula.jws.json
==> Downloading https://formulae.brew.sh/api/cask.jws.json
==> Downloading https://ghcr.io/v2/homebrew/core/minikube/manifests/1.36.0
################################################################################################################################################################################################### 100.0%
==> Fetching minikube
==> Downloading https://ghcr.io/v2/homebrew/core/minikube/blobs/sha256:e9a0f9e6e9218387985c3dd006b85fbfd8a83f76578902da2bc15a3753bd2839
################################################################################################################################################################################################### 100.0%
==> Pouring minikube--1.36.0.arm64_sequoia.bottle.tar.gz
🍺 /opt/homebrew/Cellar/minikube/1.36.0: 10 files, 124.6MB
==> Running `brew cleanup minikube`...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
==> Caveats
zsh completions have been installed to:
/opt/homebrew/share/zsh/site-functions
minikube start
$ minikube start
😄 Darwin 15.3.2 (arm64) 上の minikube v1.36.0
✨ docker ドライバーが自動的に選択されました。他の選択肢 : virtualbox, ssh
📌 root 権限を持つ Docker Desktop ドライバーを使用
👍 Starting "minikube" primary control-plane node in "minikube" cluster
🚜 Pulling base image v0.0.47 ...
💾 ロード済み Kubernetes v1.33.1 をダウンロードしています ...
> preloaded-images-k8s-v18-v1...: 327.15 MiB / 327.15 MiB 100.00% 35.29 M
> gcr.io/k8s-minikube/kicbase...: 463.69 MiB / 463.69 MiB 100.00% 13.74 M
🔥 Creating docker container (CPUs=2, Memory=7889MB) ...|
🐳 Docker 28.1.1 で Kubernetes v1.33.1 を準備しています...
▪ 証明書と鍵を作成しています...
▪ コントロールプレーンを起動しています...
▪ RBAC のルールを設定中です...
🔗 bridge CNI (コンテナーネットワークインターフェース) を設定中です ...
🔎 Kubernetes コンポーネントを検証しています ...
▪ gcr.io/k8s-minikube/storage-provisioner:v5 イメージを使用しています
🌟 有効なアドオン : storage-provisioner, default-storageclass
🏄 終了しました!kubectl がデフォルトで「minikube」クラスターと「default」ネームスペースを使用するよう設定されました
minikube status
$ minikube status
minikube
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured
minikube dashboard
$ minikube dashboard
🔌 ダッシュボードを有効化しています...
▪ docker.io/kubernetesui/metrics-scraper:v1.0.8 イメージを使用しています
▪ docker.io/kubernetesui/dashboard:v2.7.0 イメージを使用しています
💡 Some dashboard features require the metrics-server addon. To enable all features please run:
minikube addons enable metrics-server
🤔 ダッシュボードの状態を検証しています...
🚀 プロキシーを起動しています...
🤔 プロキシーの状態を検証しています...
🎉 デフォルトブラウザーで http://127.0.0.1:55379/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/ を開いています...
Pod と Node の理解
Node(ノード)
Kubernetesクラスターの物理的または仮想的なマシンです。複数のPodを実行できます。
Pod(ポッド)
Kubernetesの最小デプロイ単位で、1つ以上のコンテナを含みます。同じPod内のコンテナはネットワークとストレージを共有します。
Deployment 作成
DeploymentはPodの宣言的な管理を行い、スケーリングやローリングアップデートを簡単に実現できます。
# Deployment 作成
# Webサーバーを含むテストコンテナイメージを実行する
kubectl create deployment hello-node --image=registry.k8s.io/e2e-test-images/agnhost:2.39 -- /agnhost netexec --http-port=8080
# Deployment 確認
kubectl get deployments
# Pod 確認
kubectl get pods
# クラスターイベント確認
kubectl get events
# kubectl設定確認
kubectl config view
# Pod で実行されているコンテナのアプリケーションログ確認
kubectl logs <pod-name>
registry.k8s.io/e2e-test-images/agnhost:2.39 -- /agnhost netexec --http-port=8080
kubectl create deployment hello-node --image=$ kubectl create deployment hello-node --image=registry.k8s.io/e2e-test-images/agnhost:2.39 -- /agnhost netexec --http-port=8080
deployment.apps/hello-node created
kubectl get deployments
$ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
hello-node 1/1 1 1 17m
kubectl get pods
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
hello-node-c74958b5d-9r2rb 1/1 Running 0 20m
kubectl get events
$ kubectl get events
LAST SEEN TYPE REASON OBJECT MESSAGE
21m Normal Scheduled pod/hello-node-c74958b5d-9r2rb Successfully assigned default/hello-node-c74958b5d-9r2rb to minikube
21m Normal Pulling pod/hello-node-c74958b5d-9r2rb Pulling image "registry.k8s.io/e2e-test-images/agnhost:2.39"
21m Normal Pulled pod/hello-node-c74958b5d-9r2rb Successfully pulled image "registry.k8s.io/e2e-test-images/agnhost:2.39" in 3.401s (3.401s including waiting). Image size: 127408057 bytes.
21m Normal Created pod/hello-node-c74958b5d-9r2rb Created container: agnhost
21m Normal Started pod/hello-node-c74958b5d-9r2rb Started container agnhost
21m Normal SuccessfulCreate replicaset/hello-node-c74958b5d Created pod: hello-node-c74958b5d-9r2rb
21m Normal ScalingReplicaSet deployment/hello-node Scaled up replica set hello-node-c74958b5d from 0 to 1
29m Normal Starting node/minikube Starting kubelet.
29m Normal NodeHasSufficientMemory node/minikube Node minikube status is now: NodeHasSufficientMemory
29m Normal NodeHasNoDiskPressure node/minikube Node minikube status is now: NodeHasNoDiskPressure
29m Normal NodeHasSufficientPID node/minikube Node minikube status is now: NodeHasSufficientPID
29m Normal NodeAllocatableEnforced node/minikube Updated Node Allocatable limit across pods
29m Normal Starting node/minikube Starting kubelet.
29m Normal NodeAllocatableEnforced node/minikube Updated Node Allocatable limit across pods
29m Normal NodeHasSufficientMemory node/minikube Node minikube status is now: NodeHasSufficientMemory
29m Normal NodeHasNoDiskPressure node/minikube Node minikube status is now: NodeHasNoDiskPressure
29m Normal NodeHasSufficientPID node/minikube Node minikube status is now: NodeHasSufficientPID
29m Normal RegisteredNode node/minikube Node minikube event: Registered Node minikube in Controller
kubectl config view
$ kubectl config view
apiVersion: v1
clusters:
- cluster:
certificate-authority: /Users/glaciermelt/.minikube/ca.crt
extensions:
- extension:
last-update: Sat, 14 Jun 2025 09:07:06 JST
provider: minikube.sigs.k8s.io
version: v1.36.0
name: cluster_info
server: https://127.0.0.1:55306
name: minikube
contexts:
- context:
cluster: minikube
extensions:
- extension:
last-update: Sat, 14 Jun 2025 09:07:06 JST
provider: minikube.sigs.k8s.io
version: v1.36.0
name: context_info
namespace: default
user: minikube
name: minikube
current-context: minikube
kind: Config
preferences: {}
users:
- name: minikube
user:
client-certificate: /Users/glaciermelt/.minikube/profiles/minikube/client.crt
client-key: /Users/glaciermelt/.minikube/profiles/minikube/client.key
$ kubectl logs <pod-name>
$ kubectl logs hello-node-c74958b5d-9r2rb
I0614 00:15:37.195655 1 log.go:195] Started HTTP server on port 8080
I0614 00:15:37.196060 1 log.go:195] Started UDP server on port 8081

Service 作成
Podへの安定したアクセスを提供するためにServiceを作成します。
# Pod をインターネットに公開
# --type=LoadBalancerフラグはServiceをクラスター外部に公開したいことを示しています。
kubectl expose deployment hello-node --type=LoadBalancer --port=8080
# Service 確認
kubectl get services
# Minikube でサービスにアクセス
minikube service hello-node
kubectl expose deployment hello-node --type=LoadBalancer --port=8080
$ kubectl expose deployment hello-node --type=LoadBalancer --port=8080
service/hello-node exposed
kubectl get services
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello-node LoadBalancer 10.105.28.56 <pending> 8080:30119/TCP 112s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 37m
minikube service hello-node
$ minikube service hello-node
|-----------|------------|-------------|---------------------------|
| NAMESPACE | NAME | TARGET PORT | URL |
|-----------|------------|-------------|---------------------------|
| default | hello-node | 8080 | http://192.168.49.2:30119 |
|-----------|------------|-------------|---------------------------|
🏃 hello-node サービス用のトンネルを起動しています。
|-----------|------------|-------------|------------------------|
| NAMESPACE | NAME | TARGET PORT | URL |
|-----------|------------|-------------|------------------------|
| default | hello-node | | http://127.0.0.1:56373 |
|-----------|------------|-------------|------------------------|
🎉 デフォルトブラウザーで default/hello-node サービスを開いています ...
❗ Docker ドライバーを darwin 上で使用しているため、実行するにはターミナルを開く必要があります。

クリーンアップ
クラスターに作成したリソースをクリーンアップします。
# クラスターに作成したリソースをクリーンアップ
kubectl delete service hello-node
kubectl delete deployment hello-node
# minikube クラスターを停止
minikube stop
kubectl delete service hello-node
$ kubectl delete service hello-node
service "hello-node" deleted
kubectl delete deployment hello-node
$ kubectl delete deployment hello-node
deployment.apps "hello-node" deleted
minikube stop
$ minikube stop
✋ 「minikube」ノードを停止しています ...
🛑 SSH 経由で「minikube」の電源をオフにしています ...
🛑 1 台のノードが停止しました。

構築する環境の全体像
1. インフラ構成
- Minikubeを使用したローカルKubernetes環境
- Dockerコンテナランタイム
- MacBook上で動作(192.168.49.2:80でアクセス可能)
2. アプリケーション構成
- Node.jsベースのWebアプリケーション
- /etc/hostsによる名前解決(http://sm-api.local)
- Ingressによるルーティング制御
3. Kubernetesリソース構成
ネットワーク層
- Ingress: 外部からのHTTPアクセスを管理
- Service (NodePort): クラスター外部からのアクセスを可能に
- Service (ClusterIP): クラスター内部での通信
- Service (Headless): StatefulSet用のサービス
ワークロード層
- Deployment: ステートレスなアプリケーションのデプロイ
- StatefulSet: ステートフルなアプリケーション(データベース等)
- Job: バッチ処理の実行
- CronJob: 定期的なタスクの実行
- slack-metrics API: アプリケーション内のAPIエンドポイント
- DBマイグレーション: アプリケーション内のマイグレーション機能
ストレージ層
- Pod: 基本的なコンテナ実行単位(複数種類)
- PVC (Persistent Volume Claim): 永続ストレージの要求
- PV (Persistent Volume): 実際の永続ストレージ
- postgres DB: アプリケーションが接続するデータベース
設定管理
- ConfigMap: 環境変数や設定ファイルの管理
- Secret: 機密情報の管理

StatefulSet、PersistentVolume、PersistentVolumeClaimの関係と使い方
重要なポイントのまとめ
- StatefulSet = 状態を持つアプリ用のコントローラー
- 順序付きPod名(app-0, app-1)
- 安定したネットワークID
- Pod毎の専用ストレージ
- PersistentVolume (PV) = 実際のストレージリソース
- クラスター管理者が作成
- 物理ストレージの抽象化
- PersistentVolumeClaim (PVC) = ストレージの要求書
- 開発者が作成
- 必要な容量・性能を指定
- 関係性:
StatefulSet → Pod → PVC → PV → 物理ストレージ
PostgreSQLの例で言えば、StatefulSetがデータベースPodを管理し、各PodはPVCを通じて専用のストレージ(PV)を確保することで、データの永続化を実現しています。
StatefulSet、PersistentVolume、PersistentVolumeClaim 完全ガイド
1. StatefulSet とは
1.1 概要
StatefulSetは、ステートフル(状態を持つ)アプリケーションを管理するためのKubernetesリソースです。データベース、メッセージキュー、分散システムなど、永続的なデータや一意な識別子が必要なアプリケーションに適しています。
1.2 Deployment との違い
特徴 | StatefulSet | Deployment |
---|---|---|
Pod名 | 順序付き・固定(app-0, app-1) | ランダム(app-7b9f8d-x2z) |
起動順序 | 順番に起動(0→1→2) | 並列起動 |
削除順序 | 逆順に削除(2→1→0) | 順不同 |
ストレージ | Pod毎に専用PVC | 共有可能 |
ネットワークID | 安定したDNS名 | 変動する |
用途 | DB、キャッシュ等 | Webアプリ等 |
1.3 StatefulSet の特徴
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx" # Headless Service名(必須)
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates: # PVCテンプレート
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
作成されるリソース:
- Pod: web-0, web-1, web-2(順序付き)
- PVC: www-web-0, www-web-1, www-web-2(Pod毎)
- DNS: web-0.nginx, web-1.nginx, web-2.nginx
2. PersistentVolume (PV) とは
2.1 概要
PersistentVolume(PV)は、クラスター管理者が提供する物理的なストレージリソースの抽象化です。実際のストレージ(NFS、iSCSI、クラウドディスク等)をKubernetesで使用可能にします。
2.2 PV の例
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-example
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
hostPath: # ローカルストレージ(テスト用)
path: /mnt/data
2.3 アクセスモード
モード | 略称 | 説明 |
---|---|---|
ReadWriteOnce | RWO | 単一ノードで読み書き可能 |
ReadOnlyMany | ROX | 複数ノードで読み取り専用 |
ReadWriteMany | RWX | 複数ノードで読み書き可能 |
2.4 回収ポリシー
persistentVolumeReclaimPolicy: Retain # 選択肢
- Retain: PVC削除後もPVとデータを保持
- Delete: PVC削除時にPVと物理ストレージも削除
- Recycle: データを削除してPVを再利用(非推奨)
3. PersistentVolumeClaim (PVC) とは
3.1 概要
PersistentVolumeClaim(PVC)は、ユーザーがストレージを要求するためのリソースです。必要な容量やアクセスモードを指定して、適合するPVを要求します。
3.2 PVC の例
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-example
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: manual
selector: # 特定のPVを選択(オプション)
matchLabels:
type: local
3.3 PVC のライフサイクル
[Pending] → [Bound] → [Released]
↓ ↓ ↓
待機中 使用中 解放済み
4. ストレージクラス(StorageClass)
4.1 動的プロビジョニング
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: kubernetes.io/aws-ebs # プロビジョナー
parameters:
type: gp3
iopsPerGB: "10"
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
4.2 Minikube のデフォルト StorageClass
# 確認コマンド
kubectl get storageclass
# 出力例
NAME PROVISIONER RECLAIMPOLICY
standard (default) k8s.io/minikube-hostpath Delete
5. StatefulSet + PV/PVC の連携
5.1 完全な例:MongoDB StatefulSet
# mongodb-statefulset.yaml
apiVersion: v1
kind: Service
metadata:
name: mongodb-headless
spec:
ports:
- port: 27017
clusterIP: None
selector:
app: mongodb
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mongodb
spec:
serviceName: mongodb-headless
replicas: 3
selector:
matchLabels:
app: mongodb
template:
metadata:
labels:
app: mongodb
spec:
containers:
- name: mongodb
image: mongo:5.0
ports:
- containerPort: 27017
volumeMounts:
- name: mongodb-data
mountPath: /data/db
env:
- name: MONGO_INITDB_ROOT_USERNAME
value: admin
- name: MONGO_INITDB_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mongodb-secret
key: password
volumeClaimTemplates:
- metadata:
name: mongodb-data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: standard
resources:
requests:
storage: 10Gi
5.2 作成されるリソースの関係
StatefulSet (mongodb)
├── Pod: mongodb-0
│ └── PVC: mongodb-data-mongodb-0 → PV (自動作成)
├── Pod: mongodb-1
│ └── PVC: mongodb-data-mongodb-1 → PV (自動作成)
└── Pod: mongodb-2
└── PVC: mongodb-data-mongodb-2 → PV (自動作成)
6. 実践的な使用例
6.1 データの永続性テスト
# データ作成
kubectl exec -it mongodb-0 -- mongo -u admin -p password --eval "
use testdb;
db.users.insert({name: 'John', age: 30});
"
# Pod削除
kubectl delete pod mongodb-0
# Pod再作成後、データ確認
kubectl exec -it mongodb-0 -- mongo -u admin -p password --eval "
use testdb;
db.users.find();
"
6.2 スケーリング時の動作
# スケールアップ
kubectl scale statefulset mongodb --replicas=5
# 新しいPVCが作成される
kubectl get pvc | grep mongodb-data
# mongodb-data-mongodb-0 Bound pvc-xxx 10Gi
# mongodb-data-mongodb-1 Bound pvc-yyy 10Gi
# mongodb-data-mongodb-2 Bound pvc-zzz 10Gi
# mongodb-data-mongodb-3 Bound pvc-aaa 10Gi # 新規
# mongodb-data-mongodb-4 Bound pvc-bbb 10Gi # 新規
7. トラブルシューティング
7.1 PVC が Pending のまま
# 原因調査
kubectl describe pvc mongodb-data-mongodb-0
# よくある原因:
# 1. StorageClassが存在しない
# 2. 要求サイズが大きすぎる
# 3. アクセスモードが未対応
7.2 Pod が起動しない
# イベント確認
kubectl describe pod mongodb-0
# ログ確認
kubectl logs mongodb-0
# PVCのマウント状態確認
kubectl get pvc -o wide
8. ベストプラクティス
8.1 本番環境での推奨設定
# 1. リソース制限の設定
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "1000m"
# 2. Pod Disruption Budget
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: mongodb-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: mongodb
# 3. アンチアフィニティ
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- mongodb
topologyKey: kubernetes.io/hostname
8.2 バックアップ戦略
# CronJobでバックアップ
apiVersion: batch/v1
kind: CronJob
metadata:
name: mongodb-backup
spec:
schedule: "0 2 * * *" # 毎日2時
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: mongo:5.0
command:
- sh
- -c
- |
mongodump --host=mongodb-0.mongodb-headless \
--username=admin \
--password=$MONGO_PASSWORD \
--out=/backup/$(date +%Y%m%d)
volumeMounts:
- name: backup
mountPath: /backup
volumes:
- name: backup
persistentVolumeClaim:
claimName: backup-pvc
9. まとめ
使い分けガイドライン
ユースケース | 推奨リソース | 理由 |
---|---|---|
ステートレスWebアプリ | Deployment + emptyDir | データ永続化不要 |
データベース | StatefulSet + PVC | 順序保証とデータ永続化 |
共有ストレージ | Deployment + PVC (RWX) | 複数Podで共有 |
キャッシュサーバー | StatefulSet + PVC | 各インスタンスが独立 |
ログ収集 | DaemonSet + hostPath | 各ノードのログアクセス |
チェックリスト
- アプリケーションの状態管理要件を理解
- 適切なアクセスモードを選択
- ストレージ容量を適切に見積もり
- バックアップ・リストア手順を準備
- 監視・アラートを設定
- 災害復旧計画を策定

StatefulSet により Postgres DB を構築
事前準備
- Secret: DB 認証情報(環境変数から生成)
- ConfigMap: PostgreSQL 設定ファイル
- Headless Service: StatefulSet 用の Pod 間通信
- StatefulSet: PostgreSQL 15 Alpine 版、1 レプリカ
- PersistentVolumeClaim: 10Gi のデータ永続化
Minikube の起動
# Minikube クラスタを起動(メモリとCPUを多めに割り当て)
$ minikube start --cpus=2 --memory=4096 --driver=docker
😄 Darwin 15.3.2 (arm64) 上の minikube v1.36.0
✨ 既存のプロファイルを元に、docker ドライバーを使用します
❗ 既存の minikube クラスターに対して、メモリサイズを変更できません。最初にクラスターを削除してください。
👍 Starting "minikube" primary control-plane node in "minikube" cluster
🚜 Pulling base image v0.0.47 ...
🔄 「minikube」のために既存の docker container を再起動しています...
🐳 Docker 28.1.1 で Kubernetes v1.33.1 を準備しています...
🔎 Kubernetes コンポーネントを検証しています...
▪ docker.io/kubernetesui/dashboard:v2.7.0 イメージを使用しています
▪ gcr.io/k8s-minikube/storage-provisioner:v5 イメージを使用しています
▪ docker.io/kubernetesui/metrics-scraper:v1.0.8 イメージを使用しています
💡 Some dashboard features require the metrics-server addon. To enable all features please run:
minikube addons enable metrics-server
🌟 有効なアドオン: default-storageclass, storage-provisioner, dashboard
🏄 終了しました!kubectl がデフォルトで「minikube」クラスターと「default」ネームスペースを使用するよう設定されました
# クラスタの状態を確認
$ minikube status
minikube
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured
2. ストレージの準備
StatefulSet では PersistentVolume が必要です。Minikube では自動的に hostPath
タイプの PV が作成されます。
# デフォルトの StorageClass を確認
$ kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
standard (default) k8s.io/minikube-hostpath Delete Immediate false 13h
3. PostgreSQL StatefulSet のデプロイ
環境変数の設定
# プロジェクトルートに移動
cd /path/to/project/root
# 環境変数を設定
cp .envrc.example .envrc
vim .envrc
# direnv を使用している場合
direnv allow
Secret の生成
# Secret マニフェストを生成
./postgres/create-secret.sh
リソースのデプロイ
# すべてのリソースを適用
$ kubectl apply -f postgres/
configmap/postgres-config created
secret/postgres-secret created
service/postgres-headless created
statefulset.apps/postgres created
# デプロイの確認
$ kubectl get all -l app=postgres
NAME READY STATUS RESTARTS AGE
pod/postgres-0 0/1 Running 0 18s
$ kubectl get all -l app=postgres
NAME READY STATUS RESTARTS AGE
pod/postgres-0 1/1 Running 0 2m41s
4. 動作確認
Pod の状態確認
# Pod の詳細情報
$ kubectl describe pod postgres-0
Name: postgres-0
Namespace: default
Priority: 0
Service Account: default
Node: minikube/192.168.49.2
Start Time: Sat, 14 Jun 2025 22:39:06 +0900
Labels: app=postgres
apps.kubernetes.io/pod-index=0
controller-revision-hash=postgres-76d5fd8888
statefulset.kubernetes.io/pod-name=postgres-0
Annotations: <none>
Status: Running
IP: 10.244.0.9
IPs:
IP: 10.244.0.9
Controlled By: StatefulSet/postgres
Containers:
postgres:
Container ID: docker://c8f9c8e9ca64230008a5c529610d106f4a9efc18488bd4c94c49f0a7d5d62ecc
Image: postgres:15-alpine
Image ID: docker-pullable://postgres@sha256:2985f77749c75e90d340b8538dbf55d4e5b2c5396b2f05b7add61a7d8cd50a99
Port: 5432/TCP
Host Port: 0/TCP
State: Running
Started: Sat, 14 Jun 2025 22:39:17 +0900
Ready: True
Restart Count: 0
Limits:
cpu: 500m
memory: 1Gi
Requests:
cpu: 250m
memory: 512Mi
Liveness: exec [pg_isready -U postgres] delay=30s timeout=1s period=10s #success=1 #failure=3
Readiness: exec [pg_isready -U postgres] delay=5s timeout=1s period=5s #success=1 #failure=3
Environment:
POSTGRES_USER: <set to the key 'POSTGRES_USER' in secret 'postgres-secret'> Optional: false
POSTGRES_PASSWORD: <set to the key 'POSTGRES_PASSWORD' in secret 'postgres-secret'> Optional: false
POSTGRES_DB: <set to the key 'POSTGRES_DB' in secret 'postgres-secret'> Optional: false
PGDATA: /var/lib/postgresql/data/pgdata
Mounts:
/etc/postgresql/postgresql.conf from postgres-config (rw,path="postgresql.conf")
/var/lib/postgresql/data from postgres-data (rw)
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-xnkjd (ro)
Conditions:
Type Status
PodReadyToStartContainers True
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
postgres-data:
Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
ClaimName: postgres-data-postgres-0
ReadOnly: false
postgres-config:
Type: ConfigMap (a volume populated by a ConfigMap)
Name: postgres-config
Optional: false
kube-api-access-xnkjd:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
Optional: false
DownwardAPI: true
QoS Class: Burstable
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 4m12s (x2 over 4m12s) default-scheduler 0/1 nodes are available: pod has unbound immediate PersistentVolumeClaims. preemption: 0/1 nodes are available: 1 Preemption is not helpful for scheduling.
Warning FailedScheduling 4m12s default-scheduler 0/1 nodes are available: pod has unbound immediate PersistentVolumeClaims. preemption: 0/1 nodes are available: 1 Preemption is not helpful for scheduling.
Normal Scheduled 4m12s default-scheduler Successfully assigned default/postgres-0 to minikube
Normal Pulling 4m12s kubelet Pulling image "postgres:15-alpine"
Normal Pulled 4m2s kubelet Successfully pulled image "postgres:15-alpine" in 9.823s (9.823s including waiting). Image size: 265525513 bytes.
Normal Created 4m2s kubelet Created container: postgres
Normal Started 4m2s kubelet Started container postgres
# ログの確認
$ kubectl logs postgres-0
The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.
The database cluster will be initialized with locale "en_US.utf8".
The default database encoding has accordingly been set to "UTF8".
The default text search configuration will be set to "english".
Data page checksums are disabled.
fixing permissions on existing directory /var/lib/postgresql/data/pgdata ... ok
creating subdirectories ... ok
selecting dynamic shared memory implementation ... posix
selecting default max_connections ... 100
selecting default shared_buffers ... 128MB
selecting default time zone ... UTC
creating configuration files ... ok
running bootstrap script ... ok
sh: locale: not found
2025-06-14 13:39:17.984 UTC [36] WARNING: no usable system locales were found
performing post-bootstrap initialization ... ok
initdb: warning: enabling "trust" authentication for local connections
initdb: hint: You can change this by editing pg_hba.conf or using the option -A, or --auth-local and --auth-host, the next time you run initdb.
syncing data to disk ... ok
Success. You can now start the database server using:
pg_ctl -D /var/lib/postgresql/data/pgdata -l logfile start
waiting for server to start....2025-06-14 13:39:18.577 UTC [42] LOG: starting PostgreSQL 15.13 on aarch64-unknown-linux-musl, compiled by gcc (Alpine 14.2.0) 14.2.0, 64-bit
2025-06-14 13:39:18.578 UTC [42] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2025-06-14 13:39:18.580 UTC [45] LOG: database system was shut down at 2025-06-14 13:39:18 UTC
2025-06-14 13:39:18.583 UTC [42] LOG: database system is ready to accept connections
done
server started
CREATE DATABASE
/usr/local/bin/docker-entrypoint.sh: ignoring /docker-entrypoint-initdb.d/*
waiting for server to shut down....2025-06-14 13:39:18.709 UTC [42] LOG: received fast shutdown request
2025-06-14 13:39:18.711 UTC [42] LOG: aborting any active transactions
2025-06-14 13:39:18.712 UTC [42] LOG: background worker "logical replication launcher" (PID 48) exited with exit code 1
2025-06-14 13:39:18.743 UTC [43] LOG: shutting down
2025-06-14 13:39:18.744 UTC [43] LOG: checkpoint starting: shutdown immediate
2025-06-14 13:39:18.775 UTC [43] LOG: checkpoint complete: wrote 921 buffers (5.6%); 0 WAL file(s) added, 0 removed, 0 recycled; write=0.011 s, sync=0.018 s, total=0.032 s; sync files=301, longest=0.005 s, average=0.001 s; distance=4238 kB, estimate=4238 kB
2025-06-14 13:39:18.778 UTC [42] LOG: database system is shut down
done
server stopped
PostgreSQL init process complete; ready for start up.
2025-06-14 13:39:18.827 UTC [1] LOG: starting PostgreSQL 15.13 on aarch64-unknown-linux-musl, compiled by gcc (Alpine 14.2.0) 14.2.0, 64-bit
2025-06-14 13:39:18.827 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432
2025-06-14 13:39:18.827 UTC [1] LOG: listening on IPv6 address "::", port 5432
2025-06-14 13:39:18.828 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2025-06-14 13:39:18.830 UTC [58] LOG: database system was shut down at 2025-06-14 13:39:18 UTC
2025-06-14 13:39:18.833 UTC [1] LOG: database system is ready to accept connections
データベースへの接続
# Pod 内から接続
$ kubectl exec -it postgres-0 -- psql -U postgres -d postgresdb
psql (15.13)
Type "help" for help.
postgresdb=#
# ポートフォワード経由で接続
$ kubectl port-forward postgres-0 5432:5432 &
[1] 27845
Forwarding from 127.0.0.1:5432 -> 5432
Forwarding from [::1]:5432 -> 5432
$ psql -h localhost -U postgres -d postgresdb
Handling connection for 5432
psql (17.2, server 15.13)
Type "help" for help.
postgresdb=# exit
5. 永続性の確認
# データを作成
$ kubectl exec -it postgres-0 -- psql -U postgres -d postgresdb -c "CREATE TABLE test (id int);"
CREATE TABLE
$ kubectl exec -it postgres-0 -- psql -U postgres -d postgresdb -c "INSERT INTO test VALUES (1);"
INSERT 0 1
# Pod を削除
$ kubectl delete pod postgres-0
pod "postgres-0" deleted
# Pod が再作成されるのを待つ
$ kubectl wait --for=condition=ready pod/postgres-0 --timeout=60s
pod/postgres-0 condition met
# データが残っていることを確認
kubectl exec -it postgres-0 -- psql -U postgres -d postgresdb -c "SELECT * FROM test;"

Headless Service 経由でのアクセス
StatefulSet の Pod は Headless Service を通じて DNS 名でアクセスできます。
DNS 名の形式
<pod-name>.<service-name>.<namespace>.svc.cluster.local
PostgreSQL の場合:
postgres-0.postgres-headless.default.svc.cluster.local
テスト用クライアント Pod の作成
# クライアント Pod を作成
$ kubectl apply -f postgres/test-pod.yaml
pod/postgres-client created
# クライアント Pod から Headless Service 経由で接続
$ kubectl exec -it postgres-client -- psql -h postgres-0.postgres-headless.default.svc.cluster.local -U postgres -d postgresdb
psql (15.13)
Type "help" for help.
postgresdb=#
# または短縮形で接続
$ kubectl exec -it postgres-client -- psql -h postgres-0.postgres-headless -U postgres -d postgresdb
psql (15.13)
Type "help" for help.
postgresdb=#
# Service 名だけでも接続可能(ラウンドロビンではなく、StatefulSet の場合は postgres-0 に接続)
kubectl exec -it postgres-client -- psql -h postgres-headless -U postgres -d postgresdb
# DNS 解決の確認
$ kubectl exec -it postgres-client -- nslookup postgres-headless
Server: 10.96.0.10
Address: 10.96.0.10:53
** server can't find postgres-headless.svc.cluster.local: NXDOMAIN
** server can't find postgres-headless.cluster.local: NXDOMAIN
** server can't find postgres-headless.svc.cluster.local: NXDOMAIN
Name: postgres-headless.default.svc.cluster.local
Address: 10.244.0.10
** server can't find postgres-headless.cluster.local: NXDOMAIN
command terminated with exit code 1
$ kubectl exec -it postgres-client -- nslookup postgres-0.postgres-headless
Server: 10.96.0.10
Address: 10.96.0.10:53
** server can't find postgres-0.postgres-headless: NXDOMAIN
** server can't find postgres-0.postgres-headless: NXDOMAIN
command terminated with exit code 1

Job とは
1.1 概要
Jobは、一度だけ実行して完了するタスク(バッチ処理)を管理するKubernetesリソースです。Podが正常に終了するまで実行を保証し、失敗した場合は再試行します。
1.2 Job の特徴
特徴 | 説明 |
---|---|
一度きりの実行 | タスクが完了したら終了 |
完了保証 | 成功するまで再試行 |
並列実行 | 複数のPodで並列処理可能 |
履歴保持 | 完了後もJobとPodの情報を保持 |
1.3 基本的な Job の例
apiVersion: batch/v1
kind: Job
metadata:
name: hello-job
spec:
template:
metadata:
name: hello-job
spec:
containers:
- name: hello
image: busybox
command: ["sh", "-c", "echo 'Hello, Job!' && sleep 30"]
restartPolicy: Never # JobではNeverまたはOnFailure
2. Job の設定オプション
2.1 重要なフィールド
apiVersion: batch/v1
kind: Job
metadata:
name: batch-job
spec:
# 並列実行するPod数
parallelism: 3
# 成功が必要なPod数
completions: 10
# バックオフ制限(再試行回数)
backoffLimit: 4
# アクティブな期限(秒)
activeDeadlineSeconds: 3600
# 完了後のTTL(秒)
ttlSecondsAfterFinished: 86400
template:
spec:
containers:
- name: worker
image: myapp:latest
command: ["./process.sh"]
restartPolicy: OnFailure
2.2 restartPolicy の選択
Policy | 動作 | 使用場面 |
---|---|---|
Never | Podを再作成して再試行 | 環境をクリーンに保ちたい場合 |
OnFailure | 同じPod内でコンテナを再起動 | 高速な再試行が必要な場合 |
3. 実践的な Job の例
3.1 データベースマイグレーション
apiVersion: batch/v1
kind: Job
metadata:
name: db-migration-v2
spec:
template:
metadata:
labels:
app: db-migration
version: v2
spec:
containers:
- name: migration
image: myapp:latest
command: ["npm", "run", "migrate"]
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-secret
key: url
- name: MIGRATION_VERSION
value: "v2"
restartPolicy: Never
3.2 データ処理ジョブ(並列実行)
apiVersion: batch/v1
kind: Job
metadata:
name: data-processing
spec:
parallelism: 5 # 同時に5つのPodを実行
completions: 20 # 合計20個のタスクを完了
backoffLimit: 3
template:
spec:
containers:
- name: processor
image: data-processor:latest
env:
- name: TASK_INDEX
valueFrom:
fieldRef:
fieldPath: metadata.annotations['batch.kubernetes.io/job-completion-index']
command:
- sh
- -c
- |
echo "Processing task $TASK_INDEX"
# タスクインデックスに基づいて処理を分割
./process-data.sh --partition=$TASK_INDEX --total=20
restartPolicy: Never
3.3 バックアップジョブ
apiVersion: batch/v1
kind: Job
metadata:
name: postgres-backup
labels:
app: postgres-backup
spec:
ttlSecondsAfterFinished: 604800 # 1週間後に自動削除
template:
spec:
containers:
- name: backup
image: postgres:15
command:
- sh
- -c
- |
set -e
echo "Starting backup at $(date)"
# バックアップ実行
PGPASSWORD=$POSTGRES_PASSWORD pg_dump \
-h postgres-service \
-U postgres \
-d myapp \
-f /backup/myapp-$(date +%Y%m%d-%H%M%S).sql
# S3にアップロード(例)
aws s3 cp /backup/myapp-*.sql s3://my-backup-bucket/postgres/
echo "Backup completed at $(date)"
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: aws-secret
key: access-key
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: aws-secret
key: secret-key
volumeMounts:
- name: backup
mountPath: /backup
volumes:
- name: backup
emptyDir: {}
restartPolicy: OnFailure
4. Job パターン
4.1 Work Queue パターン
# メッセージキューから取得して処理
apiVersion: batch/v1
kind: Job
metadata:
name: work-queue-consumer
spec:
parallelism: 10
completions: 100
template:
spec:
containers:
- name: worker
image: worker:latest
env:
- name: QUEUE_URL
value: "redis://redis-service:6379"
command:
- python
- -c
- |
import redis
import json
import time
r = redis.from_url(os.environ['QUEUE_URL'])
while True:
# キューからタスクを取得
task = r.blpop('task_queue', timeout=30)
if not task:
print("No more tasks")
break
task_data = json.loads(task[1])
print(f"Processing task: {task_data['id']}")
# タスク処理
process_task(task_data)
# 完了マーク
r.sadd('completed_tasks', task_data['id'])
restartPolicy: Never
4.2 Indexed Job パターン(K8s 1.24+)
apiVersion: batch/v1
kind: Job
metadata:
name: indexed-job
spec:
completions: 5
parallelism: 3
completionMode: Indexed # インデックス付きモード
template:
spec:
containers:
- name: worker
image: busybox
command:
- sh
- -c
- |
echo "My index is: $JOB_COMPLETION_INDEX"
# インデックスに基づいて異なる処理を実行
case $JOB_COMPLETION_INDEX in
0) echo "Processing customer data..." ;;
1) echo "Processing order data..." ;;
2) echo "Processing inventory data..." ;;
3) echo "Processing shipping data..." ;;
4) echo "Generating reports..." ;;
esac
sleep 10
restartPolicy: Never
5. Job の監視と管理
5.1 Job の状態確認
# Job一覧
kubectl get jobs
# 詳細情報
kubectl describe job hello-job
# Job のログ確認
kubectl logs -l job-name=hello-job
# 完了したPodも含めて表示
kubectl get pods -a -l job-name=hello-job
# Job の実行状況を監視
kubectl get job hello-job -w
5.2 Job の状態遷移
作成 → アクティブ → 成功/失敗
↓
再試行(失敗時)
5.3 プログラムでの状態確認
# Python Kubernetes Client での例
from kubernetes import client, config
# Kubernetesクラスタに接続
config.load_incluster_config() # Pod内から
# または
# config.load_kube_config() # ローカルから
batch_v1 = client.BatchV1Api()
# Job の状態を確認
job = batch_v1.read_namespaced_job(
name="my-job",
namespace="default"
)
print(f"Active: {job.status.active}")
print(f"Succeeded: {job.status.succeeded}")
print(f"Failed: {job.status.failed}")
# 完了待機
import time
while True:
job = batch_v1.read_namespaced_job(
name="my-job",
namespace="default"
)
if job.status.succeeded:
print("Job completed successfully!")
break
elif job.status.failed:
print("Job failed!")
break
time.sleep(10)
6. エラーハンドリング
6.1 再試行戦略
apiVersion: batch/v1
kind: Job
metadata:
name: retry-job
spec:
backoffLimit: 5 # 最大5回再試行
template:
spec:
containers:
- name: app
image: myapp:latest
command:
- sh
- -c
- |
# 再試行回数を確認
RETRY_COUNT=${RETRY_COUNT:-0}
echo "Attempt: $((RETRY_COUNT + 1))"
# エクスポネンシャルバックオフ
if [ $RETRY_COUNT -gt 0 ]; then
SLEEP_TIME=$((2 ** RETRY_COUNT))
echo "Waiting ${SLEEP_TIME}s before retry..."
sleep $SLEEP_TIME
fi
# メイン処理
./main-task.sh || exit 1
env:
- name: RETRY_COUNT
value: "0" # Jobコントローラが管理
restartPolicy: Never
6.2 タイムアウト設定
apiVersion: batch/v1
kind: Job
metadata:
name: timeout-job
spec:
activeDeadlineSeconds: 300 # 5分でタイムアウト
template:
spec:
containers:
- name: task
image: busybox
command: ["sleep", "600"] # 10分スリープ(タイムアウトする)
restartPolicy: Never
7. Job と CronJob の連携
7.1 手動で CronJob から Job を作成
# CronJob から Job を手動実行
kubectl create job manual-backup --from=cronjob/backup-cronjob
7.2 Job テンプレートの再利用
# job-template.yaml
apiVersion: batch/v1
kind: Job
metadata:
generateName: templated-job- # 名前を自動生成
spec:
template:
spec:
containers:
- name: task
image: myapp:latest
args: ["--date", "$(DATE)"]
restartPolicy: Never
# 実行時に環境変数を注入
$ DATE=$(date +%Y%m%d) envsubst < job-template.yaml | kubectl apply -f -
8. ベストプラクティス
8.1 イディオムパターン
apiVersion: batch/v1
kind: Job
metadata:
name: idempotent-job
spec:
template:
spec:
containers:
- name: app
image: myapp:latest
command:
- sh
- -c
- |
# イディオムポテント(冪等)な処理
JOB_ID=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 8)
# 既に処理済みかチェック
if exists_in_database "$JOB_ID"; then
echo "Job $JOB_ID already processed"
exit 0
fi
# メイン処理
process_data
# 完了マーク
mark_as_completed "$JOB_ID"
restartPolicy: OnFailure
8.2 リソース管理
apiVersion: batch/v1
kind: Job
metadata:
name: resource-aware-job
spec:
parallelism: 5
template:
spec:
containers:
- name: processor
image: processor:latest
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "1000m"
# ノードセレクタで特定のノードで実行
nodeSelector:
workload-type: batch
# Tolerationでtaintedノードでも実行可能に
tolerations:
- key: "batch-only"
operator: "Equal"
value: "true"
effect: "NoSchedule"
restartPolicy: Never
8.3 クリーンアップ戦略
# 自動クリーンアップ
apiVersion: batch/v1
kind: Job
metadata:
name: auto-cleanup-job
spec:
ttlSecondsAfterFinished: 3600 # 完了後1時間で自動削除
successfulJobsHistoryLimit: 3 # 成功したJobを3つまで保持
failedJobsHistoryLimit: 1 # 失敗したJobを1つまで保持
template:
spec:
containers:
- name: task
image: busybox
command: ["echo", "Done"]
restartPolicy: Never
9. Job のデバッグ
9.1 失敗の調査
# 失敗したJobの詳細確認
kubectl describe job failed-job
# Podのイベント確認
kubectl get events --field-selector involvedObject.name=failed-job-xxxxx
# 失敗したPodのログ確認
kubectl logs job/failed-job --all-containers=true
# 前回の実行ログ(コンテナが再起動した場合)
kubectl logs job/failed-job --previous
9.2 デバッグ用の設定
apiVersion: batch/v1
kind: Job
metadata:
name: debug-job
spec:
backoffLimit: 0 # 再試行しない(デバッグ用)
template:
spec:
containers:
- name: debug
image: myapp:debug # デバッグツール入りイメージ
command: ["sh", "-c"]
args:
- |
set -x # コマンドを表示
echo "Starting debug job..."
# 環境変数を出力
env | sort
# ファイルシステムを確認
ls -la /app/
# メイン処理(エラー時は詳細を表示)
./main.sh || {
echo "Failed with exit code: $?"
# デバッグ情報を収集
cat /tmp/debug.log
exit 1
}
restartPolicy: Never
10. まとめ
Job を使うべき場合
ユースケース | 例 |
---|---|
データ処理 | ETL、バッチ集計、レポート生成 |
メンテナンス | DB移行、バックアップ、クリーンアップ |
初期化処理 | データロード、環境セットアップ |
定期タスク | CronJobと組み合わせた定期実行 |
チェックリスト
-
適切な
restartPolicy
を選択(Never or OnFailure) -
backoffLimit
で再試行回数を制限 -
activeDeadlineSeconds
でタイムアウトを設定 -
ttlSecondsAfterFinished
で自動クリーンアップ - リソース制限を適切に設定
- ログとモニタリングの仕組みを準備
- イディオムポテントな処理設計
- エラーハンドリングとリトライ戦略

minikubeでDockerイメージを参照する方法
1. minikubeの内部Dockerデーモンを使用する方法
これが最も一般的な方法です。
# minikubeのDockerデーモンを使うように環境変数を設定
eval $(minikube docker-env)
# この状態でDockerイメージをビルド
docker build -t my-app:latest .
# 確認
docker images
eval $(minikube docker-env)
を実行すると、ローカルのDockerクライアントがminikube内のDockerデーモンを使うようになります。
2. イメージをminikubeにロードする方法
既存のDockerイメージをminikubeに転送する場合:
# ローカルでイメージをビルド
docker build -t my-app:latest .
# minikubeにイメージをロード
minikube image load my-app:latest
3. Podで使用する際の注意点
minikube内のイメージを使う場合、imagePullPolicy
をNever
に設定する必要があります:
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: my-container
image: my-app:latest
imagePullPolicy: Never # 重要:外部レジストリから取得しない
4. 環境を元に戻す
作業が終わったら、環境変数を元に戻します:
eval $(minikube docker-env -u)