昨天我們學會了用 Commands 和 Hooks 打造自動化的資料庫環境,就像學會了開一家自動化餐廳的廚房!
但現實中的應用系統不只有資料庫,還需要:
🗄️ 資料庫服務 (PostgreSQL)
🚀 後端 API (Go 應用程式)
🌐 前端界面 (可選)
📊 監控系統 (可選)
這就像經營一家完整的餐廳,需要廚房、服務生、收銀台... 各部門協調運作!
今天我們要學習 DevSpace 的 Dependencies,讓微服務像交響樂團一樣和諧演奏!
✅ Dependencies - 管理服務間的依賴關係
✅ Go 應用整合 - 連接資料庫的實戰應用
✅ 服務編排 - 確保啟動順序和依賴關係
✅ 完整工作流 - 從資料庫到 API 的端到端自動化
微服務的現實挑戰
想像你要開一家現代化餐廳:
如果沒有好的編排機制:
🔴 順序混亂:廚師還沒到,服務生就開始接單
🔴 資源浪費:所有部門同時啟動,但廚房還沒準備好
🔴 錯誤連鎖:一個環節出問題,整個系統崩潰
🔴 調試困難:不知道是哪個服務出了問題
開發環境的類似困擾
# 傳統混亂流程 😫
kubectl apply -f postgres.yaml
kubectl apply -f go-app.yaml # 💥 連不到資料庫!
kubectl apply -f frontend.yaml # 💥 後端還沒準備好!
# 手動等待和重試...
kubectl get pods -w
kubectl logs go-app-xxx # 查看錯誤
kubectl delete pod go-app-xxx # 重啟應用
# 重複以上步驟... 😫
概念理解:像餐廳的開業流程
DevSpace Dependencies 就像餐廳的標準開業流程:
# devspace.yaml
version: v2beta1
name: my-microservices
dependencies:
- name: database # 依賴項目名稱
source:
path: ./database # 本地路徑
# 或者
# source:
# git: https://github.com/user/database-config
# branch: main
- name: backend
source:
path: ./backend
dependencies: # 這個服務依賴於 database
- database
deployments:
frontend:
helm:
chart:
name: ./frontend
dependencies: # 前端依賴於後端
- backend
讓我們建立一個完整的 Go 微服務系統!
📈 系統架構圖
microservices-demo/
├── devspace.yaml # 主要編排配置
├── database/ # 資料庫依賴
│ ├── devspace.yaml
│ ├── manifest/
│ │ ├── postgres-deployment.yaml
│ │ ├── postgres-pvc.yaml
│ │ └── postgres-service.yaml
│ └── sql/
│ ├── V1__Create_users_table.sql
│ └── V2__Add_init_data.sql
├── backend/ # Go 後端服務
│ ├── devspace.yaml
│ ├── Dockerfile
│ ├── main.go
│ ├── go.mod
│ ├── go.sum
│ └── manifest/
│ ├── deployment.yaml
│ └── service.yaml
└── README.md
這部份能直接用昨天分享的內容,一字不改搬過來用
package main
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"time"
"github.com/gorilla/mux"
_ "github.com/lib/pq"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Department string `json:"department"`
CreatedAt time.Time `json:"created_at"`
}
type App struct {
DB *sql.DB
}
func main() {
app := &App{}
// 連接資料庫
if err := app.connectDB(); err != nil {
log.Fatal("Failed to connect to database:", err)
}
defer app.DB.Close()
// 設定路由
router := mux.NewRouter()
router.HandleFunc("/health", app.healthHandler).Methods("GET")
router.HandleFunc("/users", app.getUsersHandler).Methods("GET")
router.HandleFunc("/users", app.createUserHandler).Methods("POST")
// 啟動服務器
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
log.Printf("🚀 Server starting on port %s", port)
log.Fatal(http.ListenAndServe(":"+port, router))
}
func (app *App) connectDB() error {
// 從環境變數獲取資料庫連接資訊
host := os.Getenv("DB_HOST")
if host == "" {
host = "postgresql-service" // Kubernetes service name
}
port := os.Getenv("DB_PORT")
if port == "" {
port = "5432"
}
user := os.Getenv("DB_USER")
if user == "" {
user = "postgres"
}
password := os.Getenv("DB_PASSWORD")
if password == "" {
password = "secret"
}
dbname := os.Getenv("DB_NAME")
if dbname == "" {
dbname = "myapp"
}
// 建立conn string
connStr := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
host, port, user, password, dbname)
log.Printf("🔗 Connecting to database: %s:%s/%s", host, port, dbname)
// 重試連接邏輯
var db *sql.DB
var err error
for i := 0; i < 10; i++ {
db, err = sql.Open("postgres", connStr)
if err != nil {
log.Printf("❌ Failed to open database connection (attempt %d): %v", i+1, err)
time.Sleep(5 * time.Second)
continue
}
err = db.Ping()
if err != nil {
log.Printf("❌ Failed to ping database (attempt %d): %v", i+1, err)
time.Sleep(5 * time.Second)
continue
}
log.Println("✅ Successfully connected to database")
app.DB = db
return nil
}
return fmt.Errorf("failed to connect to database after 10 attempts: %v", err)
}
func (app *App) healthHandler(w http.ResponseWriter, r *http.Request) {
// 檢查資料庫連接
err := app.DB.Ping()
if err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
json.NewEncoder(w).Encode(map[string]string{
"status": "unhealthy",
"error": err.Error(),
})
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"status": "healthy",
"database": "connected",
"time": time.Now().Format(time.RFC3339),
})
}
func (app *App) getUsersHandler(w http.ResponseWriter, r *http.Request) {
rows, err := app.DB.Query("SELECT id, name, department, created_at FROM users ORDER BY id")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer rows.Close()
var users []User
for rows.Next() {
var user User
err := rows.Scan(&user.ID, &user.Name, &user.Department, &user.CreatedAt)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
users = append(users, user)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
}
func (app *App) createUserHandler(w http.ResponseWriter, r *http.Request) {
var user User
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
query := "INSERT INTO users (name, department) VALUES ($1, $2) RETURNING id, created_at"
err := app.DB.QueryRow(query, user.Name, user.Department).Scan(&user.ID, &user.CreatedAt)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(user)
}
backend/manifest/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: go-backend
spec:
replicas: 1
selector:
matchLabels:
app: go-backend
template:
metadata:
labels:
app: go-backend
spec:
containers:
- name: go-backend
image: go-backend:latest
ports:
- containerPort: 8080
env:
- name: DB_HOST
value: "postgresql-service"
- name: DB_PORT
value: "5432"
- name: DB_USER
value: "postgres"
- name: DB_PASSWORD
value: "password123"
- name: DB_NAME
value: "myapp"
- name: PORT
value: "8080"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
backend/devspace.yaml
version: v2beta1
name: backend
images:
go-backend:
image: go-backend
dockerfile: ./Dockerfile
deployments:
go-backend:
kubectl:
manifests:
- manifest/deployment.yaml
- manifest/service.yaml
hooks:
# 部署前檢查 - 修正服務名稱和增加重試機制
- name: pre-deploy-check
events: ["before:deploy"]
command: |
echo "🚀 Starting Go backend deployment..."
echo "🔍 Checking if database is ready..."
# 等待資料庫服務可用 (重試機制)
MAX_ATTEMPTS=12
ATTEMPT=1
while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do
echo "⏳ Attempt $ATTEMPT/$MAX_ATTEMPTS: Checking for database service..."
# 檢查正確的服務名稱: postgresql-service
if kubectl get svc postgresql-service -n devspace-demo >/dev/null 2>&1; then
echo "✅ Database service 'postgresql-service' found!"
# 額外檢查:確認資料庫 pod 是否就緒
echo "🔍 Checking database pod status..."
if kubectl get pods -l app=postgresql -n devspace-demo >/dev/null 2>&1; then
POD_STATUS=$(kubectl get pods -l app=postgresql -n devspace-demo -o jsonpath='{.items[0].status.phase}' 2>/dev/null)
if [ "$POD_STATUS" = "Running" ]; then
echo "✅ Database pod is running!"
# 測試資料庫連線
DB_POD=$(kubectl get pods -l app=postgresql -n devspace-demo -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
if [ ! -z "$DB_POD" ]; then
echo "🔍 Testing database connectivity..."
if kubectl exec $DB_POD -n devspace-demo -- pg_isready -U postgres >/dev/null 2>&1; then
echo "✅ Database is ready and accepting connections!"
break
else
echo "⏳ Database pod exists but not ready yet..."
fi
fi
else
echo "⏳ Database pod status: $POD_STATUS"
fi
else
echo "⏳ Database pod not found yet..."
fi
else
echo "⏳ Database service not found yet..."
fi
if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then
echo "❌ Database service not ready after $MAX_ATTEMPTS attempts"
echo "🔍 Available services:"
kubectl get svc -n devspace-demo
echo "🔍 Available pods:"
kubectl get pods -n devspace-demo
exit 1
fi
echo "⏳ Waiting 10 seconds before retry..."
sleep 10
ATTEMPT=$((ATTEMPT + 1))
done
echo "✅ Database is ready! Proceeding with backend deployment..."
# 部署後檢查
- name: post-deploy-check
events: ["after:deploy"]
command: |
echo "⏳ Waiting for Go backend to be ready..."
kubectl wait --for=condition=ready pod -l app=go-backend -n devspace-demo --timeout=120s
echo "🔍 Testing backend health..."
sleep 10
POD_NAME=$(kubectl get pods -l app=go-backend -n devspace-demo -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
if [ ! -z "$POD_NAME" ]; then
echo "✅ Backend pod found: $POD_NAME"
# 測試健康檢查端點
echo "🏥 Testing health endpoint..."
kubectl exec $POD_NAME -n devspace-demo -- wget -q -O- http://localhost:8080/health 2>/dev/null && echo "✅ Health endpoint working!" || echo "⏳ Health endpoint will be available soon..."
# 測試資料庫連線
echo "🔍 Testing database connection from backend..."
kubectl exec $POD_NAME -n devspace-demo -- wget -q -O- http://localhost:8080/users 2>/dev/null && echo "✅ Database connection working!" || echo "⏳ Database connection will be established soon..."
else
echo "❌ Backend pod not found"
fi
echo "✅ Backend deployment completed!"
# 開發環境配置
dev:
go-backend:
labelSelector:
app: go-backend
sync:
- path: .:/app
excludePaths:
- '*.tmp'
- '.git/'
- 'vendor/'
- 'go.sum'
logs:
enabled: true
ports:
- port: 8080:8080
# 實用命令
commands:
test:
description: "Test backend API endpoints"
command: |
echo "🧪 Testing Go Backend API..."
POD_NAME=$(kubectl get pods -l app=go-backend -n devspace-demo -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
if [ ! -z "$POD_NAME" ]; then
echo "✅ Backend pod found: $POD_NAME"
echo ""
echo "🏥 Testing /health endpoint:"
kubectl exec $POD_NAME -n devspace-demo -- curl -s http://localhost:8080/health || echo "❌ Health endpoint failed"
echo ""
echo "👥 Testing /users endpoint:"
kubectl exec $POD_NAME -n devspace-demo -- curl -s http://localhost:8080/users || echo "❌ Users endpoint failed"
echo ""
echo "📊 Backend logs (last 20 lines):"
kubectl logs $POD_NAME -n devspace-demo --tail=20
else
echo "❌ Backend pod not found"
fi
logs:
description: "Show backend logs"
command: |
echo "📊 Go Backend Logs:"
POD_NAME=$(kubectl get pods -l app=go-backend -n devspace-demo -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
if [ ! -z "$POD_NAME" ]; then
kubectl logs $POD_NAME -n devspace-demo -f
else
echo "❌ Backend pod not found"
fi
restart:
description: "Restart backend pod"
command: |
echo "🔄 Restarting backend pod..."
kubectl delete pod -l app=go-backend -n devspace-demo
echo "✅ Pod deleted, Kubernetes will create a new one"
根目錄的 devspace.yaml
version: v2beta1
name: microservices-demo
# 🎯 Dependencies (simplified structure)
dependencies:
database:
path: ./database
backend:
path: ./backend
# 📊 Commands only
commands:
status:
description: "Check status of all services"
command: |
echo "📊 Microservices Status Report"
echo "================================"
echo ""
echo "🗄️ Database Status:"
kubectl get pods -l app=postgresql
echo ""
echo "🚀 Backend Status:"
kubectl get pods -l app=go-backend
echo ""
echo "🌐 Services:"
kubectl get svc
test-full:
description: "Run full integration test"
command: |
echo "🧪 Running full integration test..."
echo "1️⃣ Testing database..."
kubectl get pods -l app=postgresql
echo ""
echo "2️⃣ Testing backend API..."
kubectl get pods -l app=go-backend
echo ""
echo "3️⃣ Testing end-to-end flow..."
# Port forward and test API
kubectl port-forward svc/go-backend 8080:8080 &
PF_PID=$!
sleep 5
echo "📊 Testing health endpoint:"
curl -s http://localhost:8080/health || echo "❌ Health endpoint test failed"
echo ""
echo "👥 Testing users endpoint:"
curl -s http://localhost:8080/users || echo "❌ Users endpoint test failed"
# Cleanup
kill $PF_PID 2>/dev/null || true
echo ""
echo "✅ Integration test completed!"
clean:
description: "Clean up all resources"
command: |
echo "🧹 Cleaning up microservices..."
devspace purge
kubectl delete pvc postgres-pvc 2>/dev/null || true
echo "✅ Cleanup completed!"
# 🔧 Hooks
hooks:
- name: startup-message
events: ["before:deploy"]
command: |
echo "🎭 Starting Microservices Orchestra..."
echo "📋 Services to deploy:"
echo " 1. 🗄️ PostgreSQL Database"
echo " 2. 🚀 Go Backend API"
echo ""
echo "⏳ This may take a few minutes for first-time setup..."
- name: completion-message
events: ["after:deploy"]
command: |
echo ""
echo "🎉 Microservices deployment completed!"
echo "================================"
echo ""
echo "🔗 Available endpoints:"
echo " • Database: localhost:5432 (manual port-forward needed)"
echo " • Backend API: localhost:8080 (manual port-forward needed)"
echo ""
echo "💡 Quick commands:"
echo " • devspace run status - Check all services"
echo " • devspace run test-full - Run integration tests"
echo " • devspace run clean - Clean up everything"
echo ""
echo "🚀 Happy coding!"
# 在根目錄執行
devspace dev --debug
此時能觀察佈署流程的log
🔄 詳細執行步驟分析
階段 1: 初始化 (0-10秒)
🚀 devspace dev 啟動
├── 📦 檢查依賴關係 (database + backend)
├── 📂 檢查 SQL 文件 (3個文件確認)
└── 🏗️ 開始並行構建
階段 2: 資料庫部署 (10-30秒)
🗄️ Database Module:
├── 📦 部署 PVC + Deployment + Service
├── ⏳ 等待 Pod 調度完成
├── ⏳ 等待 Pod 就緒
├── 🔍 測試 PostgreSQL 連接
├── 📊 創建 myapp 資料庫
├── 📝 執行 3個 SQL 腳本
│ ├── V1__Create_users_table.sql ✅
│ ├── V2_Add_init_data.sql ✅
│ └── V3_add_more_user.sql ✅
└── 📊 顯示資料庫資訊
階段 3: 後端部署 (並行進行)
🔧 Backend Module:
├── 🏗️ 構建 Go Docker image
├── 📋 預部署檢查 (等待資料庫就緒)
├── 🚀 部署 Backend Deployment + Service
├── ⏳ 等待 Pod 就緒
├── 🏥 健康檢查 ✅
└── 🔍 測試資料庫連接 ✅
驗證一下 API,完美!
> curl http://localhost:20018/users
[{"id":1,"name":"雷N","department":"ithome","created_at":"2025-08-30T17:05:42.139438Z"},{"id":2,"name":"雷NN","department":"ithome","created_at":"2025-08-30T17:05:42.139438Z"},{"id":3,"name":"雷NNN","department":"ithome","created_at":"2025-08-30T17:05:42.139438Z"}]
今天掌握了多個服務之間如果有依賴關係,需要按需啟動,則可以依賴 DevSpace 的 dependencies
。
且 dependencies 不只能指定相對路徑來調用服務,也能通過 Git 來取得,這是 Docker compose 所沒有的能力。
dependencies:
api-server:
git: https://github.com/my-api-server
branch: stable
pipeline: dev
database-server:
git: https://github.com/my-database-server
tag: v3.0.1
subPath: /configuration
vars:
ROOT_PASSWORD: ${ROOT_PASSWORD}
這給了新人同事或是團隊同事都很方便的統一方式,展示完整的微服務架構和組件關係。