1. 概要
私が担当するプロジェクトって気づいたら長いことここ数年、EKS(AWS), GKE(GCP)などを利用しているため、k8s環境にかなり携わっていたんですよねぇ。
色々インフラも触ることは多いのですが、なんとなく理解している知識だけで業務を遂行していたので、この際ちゃんと理解したいなと思い、ローカルでk8sシミュレーションとしてkindを利用してみました。
本プロジェクトの目的は、「Podがクラッシュしても自動でアクセスが復旧し、かつ実行ログがホストPCに永続化される」、モダンで堅牢なローカル開発環境をKind(Kubernetes in Docker)上に構築することです。
Goアプリケーションを核とし、Ingress Controller、二段ボリュームマウント、およびKubernetesの自己修復機能を組み合わせて実現しました。
結論:
Kind(Kubernetes in Docker)を用いたローカル環境構築は、Kubernetes(K8s)のコア概念である「自己修復機能(Self-healing)」と「インフラの抽象化(ネットワーク・ストレージ)」を深く理解するための最適なアプローチです。
理由:
クラウド環境に依存せず、意図的な障害(Podのクラッシュ)に対するK8sの自律的な振る舞いや、コンテナ・ノード・ホスト間の複雑なデータの受け渡しを、ローカルで安全かつ手軽に実験できるためです。本件では、自動復旧とログ永続化を備えたGoアプリの開発環境構築を通じて、K8sのアーキテクチャを実践的に学習しました。
2. 解決したい課題
ローカル開発において、以下のような不便さを解消することを目指しました。
- Podを再起動するたびにコンテナ内のログが消えてしまう。
- アプリがクラッシュすると接続が切れ、手動で復旧作業が必要になる。
3. 実装の詳細
① アプリケーション層 (main.go)
kind install setup
go install sigs.k8s.io/kind@v0.31.0
まず、ログを標準出力とファイルの両方に出力し、かつ「意図的に壊せる」機能を備えたGoアプリを用意しました。
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
)
func main() {
logPath := "/var/log/app/app.log"
// ログファイルを開く(追記モード)
f, _ := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if f != nil {
defer f.Close()
}
// 標準出力とファイルの両方に書き出す
multiWriter := io.MultiWriter(os.Stdout, f)
log.SetOutput(multiWriter)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
log.Println("Access: /")
fmt.Fprintf(w, "Log recorded on host PC!\n")
})
// 自爆エンドポイント:K8sの自動復旧を試すためのもの
http.HandleFunc("/kill", func(w http.ResponseWriter, r *http.Request) {
log.Println("CRITICAL: Self-destructing...")
os.Exit(1)
})
log.Println("Server starting on :8080...")
http.ListenAndServe(":8080", nil)
}
② インフラ層:Kindの設定 (kind-config.yaml)
Kindは「Dockerの中でNodeを動かす」構造のため、ホストPCのディレクトリをまずNodeにマウントする必要があります。
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraMounts:
# [1段目] ホストPCの ./logs を Nodeの /var/local/kind-logs に繋ぐ
- hostPath: ./logs
containerPath: /var/local/kind-logs
extraPortMappings:
# Ingress用のポートマッピング(localhost:80 でアクセス可能にする)
- containerPort: 80
hostPort: 80
protocol: TCP
③ デプロイ層:K8sマニフェスト (deployment.yaml)
Nodeにマウントされたディレクトリを、さらにPod(コンテナ)へマウントします。
apiVersion: apps/v1
kind: Deployment
metadata:
name: go-app-deployment
spec:
replicas: 1
template:
spec:
containers:
- name: go-app
image: my-go-app:v3
volumeMounts:
- name: log-volume
mountPath: /var/log/app # アプリが書き出すパス
volumes:
- name: log-volume
hostPath:
path: /var/local/kind-logs # [2段目] Node上のパスを指定
type: DirectoryOrCreate
4. この構成の「魔法」
この環境のポイントは、**「二段マウント」と「Ingress」**の組み合わせです。
-
ログの永続化ルート:
Pod (/var/log/app)⇄Node (/var/local/kind-logs)⇄Host PC (./logs)
このルートにより、Podが削除されても、Nodeが再起動されても、ログはホストPC의 ファイルとして残り続けます。 -
自動復旧の体験:
curl http://localhost/killを実行するとアプリは死にますが、Kubernetesが数秒で新しいPodを立ち上げます。Ingress Controllerがトラフィックを自動で新しいPodへ流すため、開発者はブラウザをリロードするだけで作業を再開できます。
自動復旧フロー
- アプリが
/killで停止。 - Kubernetesの
liveness管理によりPodが即座に再起動。 - Ingress経由のため、IPやポートの変化を意識せずブラウザ更新だけで接続が再開。
5. 基礎的なkubectlコマンド
構築した環境の動作確認やトラブルシューティングで頻繁に使用するコマンドです。
① Podの状態を確認する
Podが正しく起動しているか、再起動が発生していないかを確認します。
# Podの一覧を表示(RESTARTS列で再起動回数がわかる)
kubectl get pods
# より詳細な情報を出力(IPアドレスやノード名など)
kubectl get pods -o wide
② ログを確認する
アプリの挙動やエラーを追跡します。
# 最新のログを表示
kubectl logs <pod-name>
# ログをリアルタイムで流し読みする(動作確認に最適)
kubectl logs -f <pod-name>
③ Podの詳細・イベントを確認する
Podが起動しない原因(イメージ取得失敗など)や、再起動がいつ起きたかの履歴を調査します。
# Podの設定とイベント履歴を表示
kubectl describe pod <pod-name>
④ コンテナ内部に入る
ファイルが正しくマウントされているかなど、内部の状態を直接確認します。
# シェルを起動してコンテナ内に潜入
kubectl exec -it <pod-name> -- /bin/sh
⑤ 意図的にPodを削除して自律復旧を試す
K8sが自動で新しいPodを立ち上げる様子を観察できます。
# Podを削除(すぐに新しいPodが作成される)
kubectl delete pod <pod-name>
6. docker compose (restart) と kind (Kubernetes) の比較
「落ちても自動で立ち上がる」という点では、docker composeのrestart: alwaysも似ていますが、その本質的な違いを整理しました。
| 項目 | Docker Compose (restart) | Kind (Kubernetes) |
|---|---|---|
| 管理単位 | コンテナ単体 | Pod / ReplicaSet (宣言的な状態管理) |
| 復旧のトリガー | プロセスの終了 (Exit) | Liveness Probe等の高度なヘルスチェック |
| 拡張性 | 単一ホスト内での再起動 | 複数ノードへのスケジューリング、負荷分散 |
| 抽象化 | ホストに依存したポート管理 | Ingress / Service によるネットワークの抽象化 |
| 用途・メリット | 軽量、シンプルで個人開発に最適 | 本番環境(K8s)を見据えた高度な設計・学習 |
実務における選択(結論):
アーキテクチャの学習や、高度なオーケストレーションの実験としてはKind(Kubernetes)は最高の環境でした。
しかし、今回の私の**「個人開発の本番環境」としては、最終的に docker compose (restart: always) を採用**しました。
理由は以下の通りです。
- 軽量さ: 単一ホストでの運用であれば、K8sのコントロールプレーン等のオーバーヘッドがなく、リソース消費が圧倒的に少ない。
- 運用コスト: 個人開発の規模では、宣言的な状態管理や高度なネットワーク機能よりも、シンプルに「プロセスが死んだら再起動する」だけの機能で十分要件を満たせたためです。
7. まとめ
本プロジェクトでは、あえてローカル環境でKindを構築し、Podの破壊やログの永続化を実験することで、Kubernetesの「あるべき状態を維持する」という強力な自己修復機能(Self-healing)を深く体感することができました。
結果として本番環境には軽量なDocker Composeを採用しましたが、**「アプリが死ぬことを前提としたインフラ設計」**の思想をK8sを通じて学べたことは、設計の大きな糧となりました。
大規模なシステムを見据えた学習環境として、ローカルでのKind運用は非常におすすめです。ぜひ「自爆エンドポイント」を作って、K8sの魔法を体験してみてください!
備考
k9sというpodの管理ツールも結構使いやすかったです!