部署微服务应用

部署微服务应用

本章将通过一个完整的微服务应用,实践 Kubernetes 的各种特性。

项目架构

我们将部署一个电商微服务系统:

┌─────────────┐
│   Ingress   │
└──────┬──────┘
       │
   ┌───┴────────────────┬──────────────┬──────────────┐
   │                    │              │              │
┌──▼──────┐    ┌───────▼────┐  ┌─────▼─────┐  ┌────▼─────┐
│ Frontend│    │  API       │  │  Product  │  │  Order   │
│ (React) │    │  Gateway   │  │  Service  │  │  Service │
└─────────┘    └────────────┘  └───────────┘  └──────────┘
                      │              │              │
                  ┌───┴──────────────┴──────────────┴───┐
                  │                                      │
           ┌──────▼──────┐                      ┌──────▼──────┐
           │   MongoDB   │                      │    Redis    │
           └─────────────┘                      └─────────────┘

准备工作

创建 Namespace

apiVersion: v1
kind: Namespace
metadata:
  name: ecommerce
  labels:
    name: ecommerce
    env: production
kubectl apply -f namespace.yaml
kubectl config set-context --current --namespace=ecommerce

配置 Secret

apiVersion: v1
kind: Secret
metadata:
  name: db-secret
  namespace: ecommerce
type: Opaque
stringData:
  mongodb-root-password: "SecurePassword123"
  redis-password: "RedisPass456"
---
apiVersion: v1
kind: Secret
metadata:
  name: jwt-secret
  namespace: ecommerce
type: Opaque
stringData:
  jwt-key: "your-secret-jwt-key-here"

部署数据库

MongoDB

apiVersion: v1
kind: ConfigMap
metadata:
  name: mongodb-config
  namespace: ecommerce
data:
  mongo.conf: |
    storage:
      dbPath: /data/db
    systemLog:
      destination: file
      path: /var/log/mongodb/mongod.log
      logAppend: true
    net:
      port: 27017
      bindIp: 0.0.0.0
---
apiVersion: v1
kind: Service
metadata:
  name: mongodb
  namespace: ecommerce
spec:
  clusterIP: None
  ports:
  - port: 27017
    name: mongodb
  selector:
    app: mongodb
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mongodb
  namespace: ecommerce
spec:
  serviceName: mongodb
  replicas: 3
  selector:
    matchLabels:
      app: mongodb
  template:
    metadata:
      labels:
        app: mongodb
    spec:
      containers:
      - name: mongodb
        image: mongo:6.0
        ports:
        - containerPort: 27017
          name: mongodb
        env:
        - name: MONGO_INITDB_ROOT_USERNAME
          value: "admin"
        - name: MONGO_INITDB_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: mongodb-root-password
        volumeMounts:
        - name: data
          mountPath: /data/db
        - name: config
          mountPath: /etc/mongo
        resources:
          requests:
            cpu: 500m
            memory: 1Gi
          limits:
            cpu: 1
            memory: 2Gi
        livenessProbe:
          exec:
            command:
            - mongo
            - --eval
            - "db.adminCommand('ping')"
          initialDelaySeconds: 30
          periodSeconds: 10
      volumes:
      - name: config
        configMap:
          name: mongodb-config
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 20Gi

Redis

apiVersion: v1
kind: ConfigMap
metadata:
  name: redis-config
  namespace: ecommerce
data:
  redis.conf: |
    maxmemory 256mb
    maxmemory-policy allkeys-lru
    save ""
---
apiVersion: v1
kind: Service
metadata:
  name: redis
  namespace: ecommerce
spec:
  ports:
  - port: 6379
    name: redis
  selector:
    app: redis
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis
  namespace: ecommerce
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:7.0-alpine
        ports:
        - containerPort: 6379
        command:
        - redis-server
        - /usr/local/etc/redis/redis.conf
        - --requirepass
        - $(REDIS_PASSWORD)
        env:
        - name: REDIS_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: redis-password
        volumeMounts:
        - name: config
          mountPath: /usr/local/etc/redis
        resources:
          requests:
            cpu: 100m
            memory: 256Mi
          limits:
            cpu: 200m
            memory: 512Mi
      volumes:
      - name: config
        configMap:
          name: redis-config

部署微服务

Product Service

apiVersion: v1
kind: ConfigMap
metadata:
  name: product-config
  namespace: ecommerce
data:
  APP_ENV: "production"
  LOG_LEVEL: "info"
  DB_HOST: "mongodb-0.mongodb.ecommerce.svc.cluster.local"
  DB_PORT: "27017"
  DB_NAME: "products"
  CACHE_HOST: "redis.ecommerce.svc.cluster.local"
  CACHE_PORT: "6379"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: product-service
  namespace: ecommerce
spec:
  replicas: 3
  selector:
    matchLabels:
      app: product-service
  template:
    metadata:
      labels:
        app: product-service
        version: v1
    spec:
      containers:
      - name: product-service
        image: ecommerce/product-service:v1.0.0
        ports:
        - containerPort: 8080
          name: http
        envFrom:
        - configMapRef:
            name: product-config
        env:
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: mongodb-root-password
        - name: CACHE_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: redis-password
        resources:
          requests:
            cpu: 200m
            memory: 256Mi
          limits:
            cpu: 500m
            memory: 512Mi
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: product-service
  namespace: ecommerce
spec:
  selector:
    app: product-service
  ports:
  - port: 8080
    targetPort: 8080
    name: http
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: product-service-hpa
  namespace: ecommerce
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: product-service
  minReplicas: 3
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70

Order Service

apiVersion: v1
kind: ConfigMap
metadata:
  name: order-config
  namespace: ecommerce
data:
  APP_ENV: "production"
  LOG_LEVEL: "info"
  DB_HOST: "mongodb-0.mongodb.ecommerce.svc.cluster.local"
  DB_PORT: "27017"
  DB_NAME: "orders"
  PRODUCT_SERVICE_URL: "http://product-service:8080"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  namespace: ecommerce
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
        version: v1
    spec:
      containers:
      - name: order-service
        image: ecommerce/order-service:v1.0.0
        ports:
        - containerPort: 8080
          name: http
        envFrom:
        - configMapRef:
            name: order-config
        env:
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: mongodb-root-password
        resources:
          requests:
            cpu: 200m
            memory: 256Mi
          limits:
            cpu: 500m
            memory: 512Mi
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 30
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: order-service
  namespace: ecommerce
spec:
  selector:
    app: order-service
  ports:
  - port: 8080
    targetPort: 8080

API Gateway

apiVersion: v1
kind: ConfigMap
metadata:
  name: gateway-config
  namespace: ecommerce
data:
  nginx.conf: |
    events {
      worker_connections 1024;
    }
    http {
      upstream product_service {
        server product-service:8080;
      }
      upstream order_service {
        server order-service:8080;
      }
      server {
        listen 80;
        location /api/products {
          proxy_pass http://product_service;
          proxy_set_header Host $host;
          proxy_set_header X-Real-IP $remote_addr;
        }
        location /api/orders {
          proxy_pass http://order_service;
          proxy_set_header Host $host;
          proxy_set_header X-Real-IP $remote_addr;
        }
        location /health {
          return 200 "OK";
        }
      }
    }
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-gateway
  namespace: ecommerce
spec:
  replicas: 2
  selector:
    matchLabels:
      app: api-gateway
  template:
    metadata:
      labels:
        app: api-gateway
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
        volumeMounts:
        - name: config
          mountPath: /etc/nginx/nginx.conf
          subPath: nginx.conf
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 200m
            memory: 256Mi
      volumes:
      - name: config
        configMap:
          name: gateway-config
---
apiVersion: v1
kind: Service
metadata:
  name: api-gateway
  namespace: ecommerce
spec:
  selector:
    app: api-gateway
  ports:
  - port: 80
    targetPort: 80

Frontend

apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
  namespace: ecommerce
spec:
  replicas: 2
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: frontend
        image: ecommerce/frontend:v1.0.0
        ports:
        - containerPort: 80
        env:
        - name: API_BASE_URL
          value: "http://api-gateway"
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 200m
            memory: 256Mi
---
apiVersion: v1
kind: Service
metadata:
  name: frontend
  namespace: ecommerce
spec:
  selector:
    app: frontend
  ports:
  - port: 80
    targetPort: 80

配置 Ingress

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ecommerce-ingress
  namespace: ecommerce
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/rate-limit: "100"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - ecommerce.example.com
    - api.ecommerce.example.com
    secretName: ecommerce-tls
  rules:
  - host: ecommerce.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: frontend
            port:
              number: 80
  - host: api.ecommerce.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: api-gateway
            port:
              number: 80

部署流程

1. 按顺序部署

# 1. 创建 Namespace 和 Secrets
kubectl apply -f namespace.yaml
kubectl apply -f secrets.yaml

# 2. 部署数据库
kubectl apply -f mongodb.yaml
kubectl apply -f redis.yaml

# 等待数据库就绪
kubectl wait --for=condition=ready pod -l app=mongodb --timeout=300s
kubectl wait --for=condition=ready pod -l app=redis --timeout=300s

# 3. 部署微服务
kubectl apply -f product-service.yaml
kubectl apply -f order-service.yaml

# 等待服务就绪
kubectl wait --for=condition=ready pod -l app=product-service --timeout=180s
kubectl wait --for=condition=ready pod -l app=order-service --timeout=180s

# 4. 部署网关和前端
kubectl apply -f api-gateway.yaml
kubectl apply -f frontend.yaml

# 5. 配置 Ingress
kubectl apply -f ingress.yaml

2. 验证部署

# 查看所有资源
kubectl get all -n ecommerce

# 查看 Pod 状态
kubectl get pods -n ecommerce

# 查看服务
kubectl get svc -n ecommerce

# 查看 Ingress
kubectl get ingress -n ecommerce

3. 测试服务

# 测试 API Gateway
kubectl port-forward -n ecommerce svc/api-gateway 8080:80
curl http://localhost:8080/health

# 测试 Product Service
kubectl port-forward -n ecommerce svc/product-service 8081:8080
curl http://localhost:8081/health

# 测试前端
kubectl port-forward -n ecommerce svc/frontend 8082:80

监控和日志

配置 Service Monitor

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: ecommerce-services
  namespace: ecommerce
spec:
  selector:
    matchLabels:
      monitoring: enabled
  endpoints:
  - port: http
    path: /metrics
    interval: 30s

查看日志

# 查看特定服务日志
kubectl logs -n ecommerce -l app=product-service --tail=100 -f

# 查看所有服务日志
kubectl logs -n ecommerce --all-containers=true --tail=100

# 查看特定 Pod 日志
kubectl logs -n ecommerce product-service-abc123 -f

更新和回滚

滚动更新

# 更新镜像
kubectl set image deployment/product-service \
  product-service=ecommerce/product-service:v1.1.0 \
  -n ecommerce

# 查看更新状态
kubectl rollout status deployment/product-service -n ecommerce

# 查看更新历史
kubectl rollout history deployment/product-service -n ecommerce

回滚

# 回滚到上一版本
kubectl rollout undo deployment/product-service -n ecommerce

# 回滚到指定版本
kubectl rollout undo deployment/product-service --to-revision=2 -n ecommerce

最佳实践总结

1. 资源组织

  • 使用 Namespace 隔离环境
  • 使用 Labels 标记资源
  • 使用 ConfigMap 管理配置
  • 使用 Secret 管理敏感信息

2. 高可用

  • 多副本部署(至少 3 个)
  • 配置 Pod 反亲和性
  • 使用 HPA 自动扩缩容
  • 配置健康检查

3. 安全

  • 使用 RBAC 控制权限
  • Secret 加密存储
  • 网络策略隔离
  • 定期更新镜像

4. 可观测性

  • 配置日志收集
  • 添加监控指标
  • 配置告警规则
  • 分布式追踪

5. 性能优化

  • 合理设置资源限制
  • 使用缓存(Redis)
  • 配置连接池
  • 优化数据库查询

小结

本章通过完整的微服务应用实践了:

  • 架构设计:微服务拆分
  • 数据库部署:StatefulSet
  • 服务部署:Deployment + Service
  • 网关配置:API Gateway
  • 流量管理:Ingress
  • 弹性伸缩:HPA
  • 配置管理:ConfigMap + Secret

下一章我们将学习 CI/CD 集成,实现自动化部署。