Warikaアプリケーション デプロイマニュアル
📋 概要
Tailscaleをリレーサーバーとして使用し、Kubernetes上でGoアプリケーションとPostgreSQLを連携させるアーキテクチャのデプロイ手順
🏗️ アーキテクチャ図
[クライアント] → [Tailscale] → [Kubernetes Service] → [GoアプリPod] → [PostgreSQL]
📁 ディレクトリ構造
warika-app/
├── go-app/
│ ├── main.go
│ ├── go.mod
│ ├── go.sum
│ ├── handlers/
│ ├── database/
│ └── models/
├── kubernetes/
│ ├── postgres/
│ │ ├── deployment.yaml
│ │ ├── service.yaml
│ │ └── pvc.yaml
│ ├── app/
│ │ ├── leader-deployment.yaml
│ │ ├── reader-deployment.yaml
│ │ ├── leader-service.yaml
│ │ └── reader-service.yaml
│ └── configmap.yaml
├── docker/
│ └── app/
│ └── Dockerfile
└── scripts/
└── deploy.sh
🚀 デプロイ手順
1. 前提条件の確認
# 必須ツールの確認
docker --version
kubectl version --client
kind version
go version
# Kindクラスターの確認
kind get clusters
2. プロジェクトのセットアップ
# リポジトリのクローンまたはディレクトリ作成
mkdir -p ~/warika-app/{go-app,kubernetes,scripts,docker}
cd ~/warika-app
3. Goアプリケーションの準備
# go-app/main.go
cat > go-app/main.go << 'EOF'
package main
import (
"database/sql"
"fmt"
"log"
"net/http"
"os"
"time"
_ "github.com/lib/pq"
)
func main() {
// データベース接続(リトライ付き)
var db *sql.DB
var err error
connStr := "host=postgres-service user=warika password=warika dbname=warika sslmode=disable"
for i := 0; i < 10; i++ {
log.Printf("Connecting to database (attempt %d): %s", i+1, connStr)
db, err = sql.Open("postgres", connStr)
if err != nil {
log.Printf("Database open failed: %v", err)
time.Sleep(2 * time.Second)
continue
}
err = db.Ping()
if err != nil {
log.Printf("Database ping failed: %v", err)
db.Close()
time.Sleep(2 * time.Second)
continue
}
log.Println("Database connection successful!")
break
}
if err != nil {
log.Fatal("Database connection failed after retries:", err)
}
defer db.Close()
// テーブル存在確認
var exists bool
err = db.QueryRow("SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = $1)", "logs").Scan(&exists)
if err != nil {
log.Fatal("Table check failed:", err)
}
log.Printf("Table 'logs' exists: %v", exists)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello from Warika App!\nPod: %s\n", os.Getenv("POD_NAME"))
})
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Healthy\n")
})
http.HandleFunc("/log", func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
message := "Test log message"
_, err := db.Exec("INSERT INTO logs (message, pod_type, pod_name) VALUES ($1, $2, $3)",
message, "leader", os.Getenv("POD_NAME"))
if err != nil {
log.Printf("Insert failed: %v", err)
http.Error(w, "Database error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
fmt.Fprintf(w, "Log created\n")
})
http.HandleFunc("/logs", func(w http.ResponseWriter, r *http.Request) {
rows, err := db.Query("SELECT id, message, pod_type, pod_name, created_at FROM logs ORDER BY created_at DESC")
if err != nil {
log.Printf("Query failed: %v", err)
http.Error(w, "Database error", http.StatusInternalServerError)
return
}
defer rows.Close()
for rows.Next() {
var id int
var message, podType, podName string
var createdAt time.Time
err := rows.Scan(&id, &message, &podType, &podName, &createdAt)
if err != nil {
log.Printf("Scan failed: %v", err)
continue
}
fmt.Fprintf(w, "ID: %d, Message: %s, Pod: %s/%s, Time: %s\n",
id, message, podType, podName, createdAt.Format("2006-01-02 15:04:05"))
}
})
port := os.Getenv("PORT")
if port == "" {
port = "3000"
}
log.Printf("Server starting on port %s", port)
log.Fatal(http.ListenAndServe(":"+port, nil))
}
EOF
# go.modの設定
cd go-app
go mod init warika-app
go get github.com/lib/pq
go mod tidy
cd ..
4. Dockerfileの作成
# Dockerfile
cat > Dockerfile << 'EOF'
FROM golang:1.21-alpine
WORKDIR /app
# 依存関係を先にコピー
COPY go-app/go.mod go-app/go.sum ./
RUN go mod download
# ソースコードをコピー
COPY go-app/ .
RUN go build -o main .
EXPOSE 3000
CMD ["./main"]
EOF
5. Kubernetesマニフェストの作成
# PostgreSQL Deployment
mkdir -p kubernetes/postgres
cat > kubernetes/postgres/deployment.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres-deployment
labels:
app: postgres
tier: database
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
tier: database
spec:
containers:
- name: postgres
image: postgres:15-alpine
env:
- name: POSTGRES_DB
value: warika
- name: POSTGRES_USER
value: warika
- name: POSTGRES_PASSWORD
value: warika
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
ports:
- containerPort: 5432
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
livenessProbe:
exec:
command:
- sh
- -c
- exec pg_isready -U warika -d warika
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command:
- sh
- -c
- exec pg_isready -U warika -d warika
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: postgres-storage
persistentVolumeClaim:
claimName: postgres-pvc
EOF
# PostgreSQL Service
cat > kubernetes/postgres/service.yaml << 'EOF'
apiVersion: v1
kind: Service
metadata:
name: postgres-service
labels:
app: postgres
tier: database
spec:
selector:
app: postgres
ports:
- protocol: TCP
port: 5432
targetPort: 5432
type: ClusterIP
EOF
# PostgreSQL PVC
cat > kubernetes/postgres/pvc.yaml << 'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
labels:
app: postgres
tier: database
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: standard
EOF
# Leader App Deployment
mkdir -p kubernetes/app
cat > kubernetes/app/leader-deployment.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: leader-app-deployment
labels:
app: warika-app
tier: leader
spec:
replicas: 1
selector:
matchLabels:
app: warika-app
tier: leader
template:
metadata:
labels:
app: warika-app
tier: leader
spec:
containers:
- name: warika-app
image: warika-app:latest
imagePullPolicy: Never
env:
- name: POD_TYPE
value: "leader"
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: PORT
value: "3000"
- name: DB_HOST
value: "postgres-service"
- name: DB_USER
value: "warika"
- name: DB_PASSWORD
value: "warika"
- name: DB_NAME
value: "warika"
ports:
- containerPort: 3000
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
EOF
# Reader App Deployment
cat > kubernetes/app/reader-deployment.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: reader-app-deployment
labels:
app: warika-app
tier: reader
spec:
replicas: 2
selector:
matchLabels:
app: warika-app
tier: reader
template:
metadata:
labels:
app: warika-app
tier: reader
spec:
containers:
- name: warika-app
image: warika-app:latest
imagePullPolicy: Never
env:
- name: POD_TYPE
value: "reader"
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: PORT
value: "3000"
- name: DB_HOST
value: "postgres-service"
- name: DB_USER
value: "warika"
- name: DB_PASSWORD
value: "warika"
- name: DB_NAME
value: "warika"
ports:
- containerPort: 3000
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
EOF
# Leader Service
cat > kubernetes/app/leader-service.yaml << 'EOF'
apiVersion: v1
kind: Service
metadata:
name: leader-app-service
labels:
app: warika-app
tier: leader
spec:
selector:
app: warika-app
tier: leader
ports:
- protocol: TCP
port: 3000
targetPort: 3000
type: ClusterIP
EOF
# Reader Service
cat > kubernetes/app/reader-service.yaml << 'EOF'
apiVersion: v1
kind: Service
metadata:
name: reader-app-service
labels:
app: warika-app
tier: reader
spec:
selector:
app: warika-app
tier: reader
ports:
- protocol: TCP
port: 3000
targetPort: 3000
type: ClusterIP
EOF
6. デプロイスクリプトの作成
# scripts/deploy.sh
cat > scripts/deploy.sh << 'EOF'
#!/bin/bash
set -e
echo "=== Warikaアプリケーション デプロイ開始 ==="
# 1. Kindクラスターの作成または確認
echo "=== Kindクラスターの確認 ==="
if ! kind get clusters | grep -q "kind"; then
echo "Kindクラスターを作成します..."
kind create cluster --name kind
else
echo "既存のKindクラスターを使用します"
fi
kubectl config use-context kind-kind
kubectl get nodes
# 2. Dockerイメージのビルド
echo "=== Dockerイメージのビルド ==="
docker build -t warika-app .
# 3. Kindへのイメージロード
echo "=== Kindへのイメージロード ==="
kind load docker-image warika-app:latest --name kind
# 4. PostgreSQLのデプロイ
echo "=== PostgreSQLのデプロイ ==="
kubectl apply -f kubernetes/postgres/
# 5. データベースの起動待機
echo "=== データベースの起動待機 ==="
kubectl wait --for=condition=ready pod -l app=postgres --timeout=120s
# 6. データベースの初期化
echo "=== データベースの初期化 ==="
kubectl exec -it deployment/postgres-deployment -- psql -U warika -d warika -c "
CREATE TABLE IF NOT EXISTS logs (
id SERIAL PRIMARY KEY,
message TEXT NOT NULL,
pod_type VARCHAR(50) NOT NULL,
pod_name VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_logs_created_at ON logs(created_at);
CREATE INDEX IF NOT EXISTS idx_logs_pod_type ON logs(pod_type);
"
# 7. アプリケーションのデプロイ
echo "=== アプリケーションのデプロイ ==="
kubectl apply -f kubernetes/app/
# 8. アプリケーションの起動待機
echo "=== アプリケーションの起動待機 ==="
kubectl wait --for=condition=ready pod -l app=warika-app --timeout=120s
# 9. デプロイ完了確認
echo "=== デプロイ完了確認 ==="
kubectl get all
echo "=== デプロイ完了 ==="
echo "Leader Service: leader-app-service"
echo "Reader Service: reader-app-service"
echo "PostgreSQL Service: postgres-service"
echo "=== テスト方法 ==="
echo "1. ポートフォワード: kubectl port-forward svc/leader-app-service 3000:3000 &"
echo "2. 接続テスト: curl http://localhost:3000"
echo "3. ヘルスチェック: curl http://localhost:3000/health"
echo "4. ログ追加: curl -X POST http://localhost:3000/log"
echo "5. ログ確認: curl http://localhost:3000/logs"
EOF
chmod +x scripts/deploy.sh
7. デプロイ実行
# デプロイスクリプトの実行
./scripts/deploy.sh
# 手動でポートフォワード開始
kubectl port-forward svc/leader-app-service 3000:3000 &
# アプリケーションテスト
curl http://localhost:3000
curl http://localhost:3000/health
curl -X POST http://localhost:3000/log
curl http://localhost:3000/logs
8. テストスクリプトの作成
# scripts/test.sh
cat > scripts/test.sh << 'EOF'
#!/bin/bash
set -e
echo "=== Warikaアプリケーション テスト開始 ==="
# ポートフォワード開始
echo "=== ポートフォワード開始 ==="
pkill -f "kubectl port-forward" || true
kubectl port-forward svc/leader-app-service 3000:3000 &
sleep 3
# 基本テスト
echo "=== 基本接続テスト ==="
curl http://localhost:3000
curl http://localhost:3000/health
# ログ機能テスト
echo "=== ログ機能テスト ==="
curl -X POST http://localhost:3000/log
curl http://localhost:3000/logs
# データベース確認
echo "=== データベース確認 ==="
kubectl exec -it deployment/postgres-deployment -- psql -U warika -d warika -c "SELECT COUNT(*) as total_logs FROM logs;"
# Readerサービステスト
echo "=== Readerサービステスト ==="
kubectl port-forward svc/reader-app-service 3001:3000 &
sleep 2
curl http://localhost:3001/health
echo "=== テスト完了 ==="
EOF
chmod +x scripts/test.sh
🧹 クリーンアップ手順
# リソースの削除
kubectl delete -f kubernetes/app/
kubectl delete -f kubernetes/postgres/
# Kindクラスターの削除
kind delete cluster --name kind
# ポートの解放
pkill -f "kubectl port-forward"
sudo kill -9 $(sudo lsof -t -i:3000) 2>/dev/null || true
sudo kill -9 $(sudo lsof -t -i:3001) 2>/dev/null || true
📊 モニタリングとトラブルシューティング
ログ確認
# アプリケーションログ
kubectl logs -l tier=leader
kubectl logs -l tier=reader
# データベースログ
kubectl logs -l app=postgres
# 詳細なログ
kubectl describe pods -l app=warika-app
状態確認
# すべてのリソース状態
kubectl get all
# イベント確認
kubectl get events --sort-by=.metadata.creationTimestamp
# サービス詳細
kubectl describe svc postgres-service
kubectl describe svc leader-app-service
🎯 正常性確認ポイント
- ✅ すべてのPodがRunning状態
- ✅ データベース接続が成功
- ✅ HTTPリクエストに正常応答
- ✅ ログの追加と取得が可能
- ✅ サービス間でデータの一貫性が保たれている
これでWarikaアプリケーションのデプロイは完了です!