跳转到内容

前后端分离架构

业务场景

云咖啡公司的订单系统已经上线,但目前的架构是单体应用,难以扩展。为了提升系统的可维护性和扩展性,我们需要将系统重构为前后端分离的微服务架构。

需求:

  • 部署独立的前端应用
  • 部署独立的后端 API 服务
  • 配置 Ingress 实现路由
  • 实现前后端通信

学习目标

完成本课程后,你将掌握:

  • Ingress 的概念和使用
  • 多服务之间的通信
  • 环境变量的配置和使用
  • 服务发现机制
  • 前后端分离架构的设计

前置准备

1. 确认环境

bash
# 检查命名空间
kubectl get namespace cloud-cafe

# 检查现有资源
kubectl get all -n cloud-cafe

# 检查 Ingress Controller
kubectl get pods -n ingress-nginx

2. 安装 Ingress Controller(如果未安装)

bash
# 检查 Ingress Controller 是否已安装
kubectl get pods -n ingress-nginx

# 如果没有安装,执行以下命令安装
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.9.4/deploy/static/provider/cloud/deploy.yaml

# 等待 Ingress Controller 就绪
kubectl wait --namespace ingress-nginx \
  --for=condition=ready pod \
  --selector=app.kubernetes.io/component=controller \
  --timeout=120s

# 查看 Ingress Controller
kubectl get pods -n ingress-nginx

📌 关于 kubectl wait 命令的解释

kubectl wait 用于阻塞等待资源达到指定状态,常用于脚本中确保资源就绪后再继续。

常用参数

参数说明示例
--for=condition=ready等待资源就绪Pod、Deployment 等
--for=delete等待资源删除完成清理资源时使用
--selector按标签选择资源-l
--timeout设置超时时间超时后命令失败

使用场景

bash
# 等待 Deployment 就绪
kubectl wait deployment/nginx --for=condition=available --timeout=60s

# 等待 Pod 删除完成(常用于清理)
kubectl wait pod/nginx --for=delete --timeout=30s

提示:超时时间格式可以是 30s2m1h


实战步骤

Step 1: 理解 Ingress

概念: Ingress 是 Kubernetes 的 API 对象,用于管理外部访问集群内服务的规则,提供 HTTP 和 HTTPS 路由。

1.1 查看 Ingress Controller

bash
# 查看 Ingress Controller Pod
kubectl get pods -n ingress-nginx

# 查看 Ingress Controller Service
kubectl get svc -n ingress-nginx

# 查看 Ingress Controller 日志
kubectl logs -n ingress-nginx -l app.kubernetes.io/component=controller

观察要点:

  • Ingress Controller 运行在 ingress-nginx 命名空间
  • Ingress Controller Service 的类型是 LoadBalancer 或 NodePort

Step 2: 部署前端应用

我们将部署一个简单的前端应用,使用 Nginx 提供静态文件服务。

2.1 创建前端页面

bash
# 创建前端页面 ConfigMap
kubectl create configmap frontend-html \
  --from-literal=index.html='
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>云咖啡公司 - 在线点单</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    body {
      font-family: "Microsoft YaHei", Arial, sans-serif;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      min-height: 100vh;
      padding: 20px;
    }
    .container {
      max-width: 1200px;
      margin: 0 auto;
    }
    header {
      text-align: center;
      color: white;
      margin-bottom: 40px;
    }
    h1 {
      font-size: 48px;
      margin-bottom: 10px;
    }
    .subtitle {
      font-size: 24px;
      opacity: 0.9;
    }
    .coffee-icon {
      font-size: 80px;
      margin: 20px 0;
    }
    .content {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 30px;
    }
    .card {
      background: white;
      border-radius: 15px;
      padding: 30px;
      box-shadow: 0 10px 30px rgba(0,0,0,0.2);
    }
    .card h2 {
      color: #667eea;
      margin-bottom: 20px;
      font-size: 28px;
    }
    .menu-item {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 15px;
      border-bottom: 1px solid #eee;
      margin-bottom: 10px;
    }
    .menu-item:last-child {
      border-bottom: none;
    }
    .menu-name {
      font-size: 18px;
      font-weight: bold;
    }
    .menu-price {
      color: #f5576c;
      font-size: 20px;
      font-weight: bold;
    }
    .order-form {
      display: flex;
      flex-direction: column;
      gap: 15px;
    }
    .form-group {
      display: flex;
      flex-direction: column;
      gap: 5px;
    }
    label {
      font-weight: bold;
      color: #333;
    }
    input, select {
      padding: 12px;
      border: 2px solid #ddd;
      border-radius: 8px;
      font-size: 16px;
    }
    input:focus, select:focus {
      outline: none;
      border-color: #667eea;
    }
    button {
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      padding: 15px;
      border: none;
      border-radius: 8px;
      font-size: 18px;
      font-weight: bold;
      cursor: pointer;
      transition: transform 0.2s;
    }
    button:hover {
      transform: scale(1.02);
    }
    .orders-list {
      margin-top: 20px;
    }
    .order-item {
      background: #f8f9fa;
      padding: 15px;
      border-radius: 8px;
      margin-bottom: 10px;
    }
    .order-item h4 {
      color: #667eea;
      margin-bottom: 5px;
    }
    .order-item p {
      color: #666;
      font-size: 14px;
    }
    .loading {
      text-align: center;
      padding: 20px;
      color: #666;
    }
    .error {
      background: #fee;
      color: #c33;
      padding: 15px;
      border-radius: 8px;
      margin-bottom: 15px;
    }
    .success {
      background: #efe;
      color: #3c3;
      padding: 15px;
      border-radius: 8px;
      margin-bottom: 15px;
    }
    @media (max-width: 768px) {
      .content {
        grid-template-columns: 1fr;
      }
    }
  </style>
</head>
<body>
  <div class="container">
    <header>
      <div class="coffee-icon">☕</div>
      <h1>云咖啡公司</h1>
      <p class="subtitle">在线点单系统</p>
    </header>

    <div class="content">
      <div class="card">
        <h2>咖啡菜单</h2>
        <div class="menu-item">
          <span class="menu-name">焦糖玛奇朵</span>
          <span class="menu-price">¥28</span>
        </div>
        <div class="menu-item">
          <span class="menu-name">拿铁</span>
          <span class="menu-price">¥25</span>
        </div>
        <div class="menu-item">
          <span class="menu-name">美式咖啡</span>
          <span class="menu-price">¥18</span>
        </div>
        <div class="menu-item">
          <span class="menu-name">卡布奇诺</span>
          <span class="menu-price">¥26</span>
        </div>
        <div class="menu-item">
          <span class="menu-name">摩卡</span>
          <span class="menu-price">¥30</span>
        </div>
      </div>

      <div class="card">
        <h2>在线点单</h2>
        <div id="message"></div>
        <form class="order-form" id="orderForm">
          <div class="form-group">
            <label for="customerName">客户姓名</label>
            <input type="text" id="customerName" required placeholder="请输入您的姓名">
          </div>
          <div class="form-group">
            <label for="coffeeType">咖啡类型</label>
            <select id="coffeeType" required>
              <option value="">请选择咖啡</option>
              <option value="焦糖玛奇朵">焦糖玛奇朵 - ¥28</option>
              <option value="拿铁">拿铁 - ¥25</option>
              <option value="美式咖啡">美式咖啡 - ¥18</option>
              <option value="卡布奇诺">卡布奇诺 - ¥26</option>
              <option value="摩卡">摩卡 - ¥30</option>
            </select>
          </div>
          <div class="form-group">
            <label for="quantity">数量</label>
            <input type="number" id="quantity" min="1" max="10" value="1" required>
          </div>
          <button type="submit">提交订单</button>
        </form>

        <div class="orders-list">
          <h3>最近订单</h3>
          <div id="ordersList">
            <div class="loading">加载中...</div>
          </div>
        </div>
      </div>
    </div>
  </div>

  <script>
    const API_BASE_URL = "/api";

    // 显示消息
    function showMessage(message, type = "success") {
      const messageDiv = document.getElementById("message");
      messageDiv.innerHTML = `<div class="${type}">${message}</div>`;
      setTimeout(() => {
        messageDiv.innerHTML = "";
      }, 3000);
    }

    // 获取订单列表
    async function loadOrders() {
      try {
        const response = await fetch(`${API_BASE_URL}/orders`);
        const orders = await response.json();

        const ordersList = document.getElementById("ordersList");
        if (orders.length === 0) {
          ordersList.innerHTML = "<p>暂无订单</p>";
        } else {
          ordersList.innerHTML = orders
            .slice(-5)
            .reverse()
            .map(
              (order) => `
            <div class="order-item">
              <h4>${order.customer_name}</h4>
              <p>${order.coffee_type} x ${order.quantity} - ¥${order.total_price}</p>
              <p>${new Date(order.order_time).toLocaleString("zh-CN")}</p>
            </div>
          `
            )
            .join("");
        }
      } catch (error) {
        console.error("加载订单失败:", error);
        document.getElementById("ordersList").innerHTML =
          '<div class="error">加载订单失败</div>';
      }
    }

    // 提交订单
    document
      .getElementById("orderForm")
      .addEventListener("submit", async (e) => {
        e.preventDefault();

        const customerName = document.getElementById("customerName").value;
        const coffeeType = document.getElementById("coffeeType").value;
        const quantity = parseInt(document.getElementById("quantity").value);

        // 计算价格
        const prices = {
          "焦糖玛奇朵": 28,
          拿铁: 25,
          美式咖啡: 18,
          卡布奇诺: 26,
          摩卡: 30,
        };
        const totalPrice = prices[coffeeType] * quantity;

        try {
          const response = await fetch(`${API_BASE_URL}/orders`, {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify({
              customer_name: customerName,
              coffee_type: coffeeType,
              quantity: quantity,
              total_price: totalPrice,
            }),
          });

          const result = await response.json();

          if (response.ok) {
            showMessage(`订单提交成功!订单号: ${result.order_id}`);
            document.getElementById("orderForm").reset();
            loadOrders();
          } else {
            showMessage(`订单提交失败: ${result.error}`, "error");
          }
        } catch (error) {
          console.error("提交订单失败:", error);
          showMessage("提交订单失败,请稍后重试", "error");
        }
      });

    // 页面加载时获取订单列表
    loadOrders();

    // 每 5 秒刷新订单列表
    setInterval(loadOrders, 5000);
  </script>
</body>
</html>
' \
  -n cloud-cafe

# 查看 ConfigMap
kubectl get configmap frontend-html -n cloud-cafe

2.2 部署前端应用

bash
# 创建前端应用 Deployment YAML 文件
cat > frontend-deployment.yaml << 'EOF'
# 前端应用 Deployment
# 用途:部署云咖啡官网前端(独立前端应用)
# 技术栈:Nginx 静态文件服务
# 特点:
#   - 从 ConfigMap 加载前端页面
#   - 包含健康检查配置
# 前置依赖:需要 frontend-html ConfigMap 已创建
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
  namespace: cloud-cafe
  labels:
    app: frontend
spec:
  replicas: 2                   # 2 个副本实现高可用
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: nginx
        image: nginx:latest     # Nginx 官方镜像
        ports:
        - containerPort: 80     # HTTP 端口
        # 挂载前端页面
        volumeMounts:
        - name: html-content
          mountPath: /usr/share/nginx/html    # Nginx 默认文档根目录
        # 资源限制
        resources:
          requests:
            memory: "64Mi"      # 最低内存要求
            cpu: "100m"         # 最低 CPU(0.1核)
          limits:
            memory: "128Mi"     # 最大内存限制
            cpu: "200m"         # 最大 CPU(0.2核)
        # 健康检查
        livenessProbe:
          httpGet:
            path: /             # 检查根路径
            port: 80
          initialDelaySeconds: 10    # 首次检查延迟
          periodSeconds: 10          # 检查间隔
        readinessProbe:
          httpGet:
            path: /             # 就绪检查路径
            port: 80
          initialDelaySeconds: 5     # 首次检查延迟
          periodSeconds: 5           # 检查间隔
      # 使用 ConfigMap 作为卷
      volumes:
      - name: html-content
        configMap:
          name: frontend-html           # 引用前端页面 ConfigMap
          items:
          - key: index.html             # ConfigMap 中的键
            path: index.html            # 挂载后的文件名
EOF

# 应用 Deployment
kubectl apply -f frontend-deployment.yaml

# 等待前端应用就绪
kubectl rollout status deployment/frontend -n cloud-cafe

# 查看 Pod
kubectl get pods -n cloud-cafe

2.3 创建前端应用 Service

bash
# 创建前端应用 Service
kubectl expose deployment frontend \
  --port=80 \
  --target-port=80 \
  --name=frontend-svc \
  -n cloud-cafe

# 查看 Service
kubectl get svc frontend-svc -n cloud-cafe

Step 3: 更新后端服务

我们需要更新后端服务,添加 CORS 支持,以便前端可以调用后端 API。

首先,创建 order-backend-deployment.yaml 文件:

bash
# 创建/编辑后端服务 Deployment 文件
vim order-backend-deployment.yaml

order-backend-deployment.yaml 内容(添加 CORS 支持):

点击查看 order-backend-deployment.yaml 内容
yaml
# 订单后端服务 Deployment
# 本次修改:添加 CORS 支持,启用跨域访问
# 修改位置:
#   1. pip install 添加 flask-cors 依赖
#   2. Python 代码中导入并启用 CORS(app)
# 前置依赖:需要先创建 mysql-config、mysql-secret、app-config、order-backend-config
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-backend
  namespace: cloud-cafe
  labels:
    app: order-backend
spec:
  replicas: 2
  selector:
    matchLabels:
      app: order-backend
  template:
    metadata:
      labels:
        app: order-backend
    spec:
      containers:
      - name: order-backend
        image: python:3.9-slim
        command: ["/bin/sh", "-c"]
        args:
          - |
            # [修改开始] 添加 flask-cors 依赖,启用跨域支持
            # CORS (Cross-Origin Resource Sharing) 允许前端从不同域名调用API
            pip install flask pymysql flask-cors
            # [修改结束]
            cat > /app/app.py << 'PYEOF'
            from flask import Flask, request, jsonify
            # [新增开始] 导入 CORS 模块
            from flask_cors import CORS
            # [新增结束]
            import pymysql
            import os

            app = Flask(__name__)
            # [新增开始] 启用 CORS,允许所有来源访问(生产环境应限制特定域名)
            CORS(app)
            # [新增结束]

            # 数据库配置
            db_config = {
                'host': os.getenv('DB_HOST', 'mysql-service'),
                'port': int(os.getenv('DB_PORT', 3306)),
                'user': os.getenv('DB_USER', 'cafeadmin'),
                'password': os.getenv('DB_PASSWORD', 'userpassword123'),
                'database': os.getenv('DB_NAME', 'cloudcafe')
            }

            def get_db_connection():
                return pymysql.connect(**db_config)

            @app.route('/health')
            def health():
                return jsonify({'status': 'healthy'})

            @app.route('/orders', methods=['GET'])
            def get_orders():
                try:
                    conn = get_db_connection()
                    cursor = conn.cursor(pymysql.cursors.DictCursor)
                    cursor.execute('SELECT * FROM orders ORDER BY order_time DESC LIMIT 20')
                    orders = cursor.fetchall()
                    conn.close()
                    return jsonify(orders)
                except Exception as e:
                    return jsonify({'error': str(e)}), 500

            @app.route('/orders', methods=['POST'])
            def create_order():
                try:
                    data = request.json
                    conn = get_db_connection()
                    cursor = conn.cursor()
                    cursor.execute(
                        'INSERT INTO orders (customer_name, coffee_type, quantity, total_price) VALUES (%s, %s, %s, %s)',
                        (data['customer_name'], data['coffee_type'], data['quantity'], data['total_price'])
                    )
                    conn.commit()
                    order_id = cursor.lastrowid
                    conn.close()
                    return jsonify({'order_id': order_id, 'message': 'Order created successfully'}), 201
                except Exception as e:
                    return jsonify({'error': str(e)}), 500

            if __name__ == '__main__':
                app.run(host='0.0.0.0', port=5000)
            PYEOF
            python /app/app.py
        ports:
        - containerPort: 5000
        env:
        - name: DB_HOST
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: DB_HOST
        - name: DB_PORT
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: DB_PORT
        - name: DB_USER
          valueFrom:
            configMapKeyRef:
              name: mysql-config
              key: MYSQL_USER
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: MYSQL_PASSWORD
        - name: DB_NAME
          valueFrom:
            configMapKeyRef:
              name: mysql-config
              key: MYSQL_DATABASE
        - name: FLASK_ENV
          valueFrom:
            configMapKeyRef:
              name: order-backend-config
              key: FLASK_ENV
        - name: FLASK_DEBUG
          valueFrom:
            configMapKeyRef:
              name: order-backend-config
              key: FLASK_DEBUG
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "200m"
        livenessProbe:
          httpGet:
            path: /health
            port: 5000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /health
            port: 5000
          initialDelaySeconds: 10
          periodSeconds: 5
        volumeMounts:
        - name: app-logs
          mountPath: /app/logs
      volumes:
      - name: app-logs
        persistentVolumeClaim:
          claimName: app-log-pvc

保存文件后,应用配置:

bash
# 应用后端服务 Deployment
kubectl apply -f order-backend-deployment.yaml

# 等待后端服务更新完成
kubectl rollout status deployment/order-backend -n cloud-cafe

# 查看 Pod
kubectl get pods -n cloud-cafe

Step 4: 配置 Ingress 路由

现在我们配置 Ingress,将外部请求路由到前端和后端服务。

bash
# 创建 Ingress YAML 文件
cat > cloud-cafe-ingress.yaml << 'EOF'
# 云咖啡应用 Ingress 配置
# 用途:配置 HTTP 路由规则,将外部请求转发到内部服务
# 路由规则:
#   - /         -> 前端服务 (frontend-svc:80)
#   - /api      -> 后端服务 (order-backend-svc:5000)
# 前置依赖:需要 Ingress Controller 已安装,前端和后端 Service 已创建
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: cloud-cafe-ingress
  namespace: cloud-cafe
  labels:
    app: cloud-cafe
  annotations:
    # URL 重写规则(将请求路径重写为根路径)
    nginx.ingress.kubernetes.io/rewrite-target: /
    # 禁用 SSL 重定向(本示例使用 HTTP)
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
  ingressClassName: nginx    # 使用 nginx Ingress Controller
  rules:
  - host: cloudcafe.local    # 虚拟主机名(需要配置 hosts 文件)
    http:
      paths:
      # 前端路由规则
      - path: /
        pathType: Prefix     # 前缀匹配
        backend:
          service:
            name: frontend-svc      # 前端 Service 名称
            port:
              number: 80            # 前端 Service 端口
      # 后端 API 路由规则
      - path: /api
        pathType: Prefix     # 前缀匹配
        backend:
          service:
            name: order-backend-svc    # 后端 Service 名称
            port:
              number: 5000             # 后端 Service 端口
EOF

# 应用 Ingress
kubectl apply -f cloud-cafe-ingress.yaml

# 查看 Ingress
kubectl get ingress -n cloud-cafe

# 查看 Ingress 详细信息
kubectl describe ingress cloud-cafe-ingress -n cloud-cafe

观察要点:

  • Ingress 创建了两个路由规则
  • / 路由到前端服务
  • /api 路由到后端服务

Step 5: 测试前后端分离架构

5.1 获取 Ingress 访问地址

bash
# 获取 Ingress Controller 的 Service
kubectl get svc -n ingress-nginx

# 获取 Ingress Controller 的 NodePort
INGRESS_PORT=$(kubectl get svc -n ingress-nginx ingress-nginx-controller -o jsonpath='{.spec.ports[0].nodePort}')

# 获取节点 IP
NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')

echo "访问地址: http://cloudcafe.local:$INGRESS_PORT"

5.2 配置本地 hosts 文件

在宿主机上编辑 /etc/hosts(Linux/Mac)或 C:\Windows\System32\drivers\etc\hosts(Windows),添加以下内容:

192.168.56.10 cloudcafe.local

注意: 将 192.168.56.10 替换为你的虚拟机 IP 地址。

5.3 测试前端访问

bash
# 测试前端访问
curl -H "Host: cloudcafe.local" http://$NODE_IP:$INGRESS_PORT

预期输出: 应该看到前端页面的 HTML 内容

5.4 测试后端 API

bash
# 测试后端 API 健康检查
curl -H "Host: cloudcafe.local" http://$NODE_IP:$INGRESS_PORT/api/health

# 测试获取订单
curl -H "Host: cloudcafe.local" http://$NODE_IP:$INGRESS_PORT/api/orders

# 测试创建订单
curl -X POST -H "Host: cloudcafe.local" -H "Content-Type: application/json" \
  -d '{"customer_name":"测试用户","coffee_type":"拿铁","quantity":2,"total_price":50.00}' \
  http://$NODE_IP:$INGRESS_PORT/api/orders

5.5 在浏览器中访问

在浏览器中访问:http://cloudcafe.local:xxxxx

你应该看到:

  • 云咖啡公司的在线点单页面
  • 左侧显示咖啡菜单
  • 右侧显示点单表单和最近订单
  • 可以提交订单,订单会实时显示在列表中

Step 6: 验证服务发现

Kubernetes 提供了内置的服务发现机制,让我们验证一下。

6.1 测试 DNS 解析

bash
# 创建测试 Pod
kubectl run test-dns --image=busybox --rm -it -n cloud-cafe -- nslookup frontend-svc

# 测试后端服务 DNS
kubectl run test-dns --image=busybox --rm -it -n cloud-cafe -- nslookup order-backend-svc

# 测试 MySQL 服务 DNS
kubectl run test-dns --image=busybox --rm -it -n cloud-cafe -- nslookup mysql-service

预期输出: 应该能够解析到服务的 ClusterIP

6.2 测试服务间通信

bash
# 从前端 Pod 访问后端服务
kubectl exec -it $(kubectl get pod -l app=frontend -n cloud-cafe -o jsonpath='{.items[0].metadata.name}') -n cloud-cafe -- \
  curl http://order-backend-svc:5000/health

# 从后端 Pod 访问 MySQL 服务
kubectl exec -it $(kubectl get pod -l app=order-backend -n cloud-cafe -o jsonpath='{.items[0].metadata.name}') -n cloud-cafe -- \
  nc -zv mysql-service 3306

预期输出:

  • 前端 Pod 应该能够访问后端服务
  • 后端 Pod 应该能够连接到 MySQL 服务

验证和测试

1. 检查所有资源状态

bash
# 查看 Deployment
kubectl get deployment -n cloud-cafe

# 查看 StatefulSet
kubectl get statefulset -n cloud-cafe

# 查看 Pod
kubectl get pods -n cloud-cafe

# 查看 Service
kubectl get svc -n cloud-cafe

# 查看 Ingress
kubectl get ingress -n cloud-cafe

# 查看 PVC
kubectl get pvc -n cloud-cafe

2. 测试完整流程

bash
# 1. 访问前端页面
echo "访问前端页面: http://cloudcafe.local:$INGRESS_PORT"

# 2. 提交订单
curl -X POST -H "Host: cloudcafe.local" -H "Content-Type: application/json" \
  -d '{"customer_name":"集成测试","coffee_type":"摩卡","quantity":1,"total_price":30.00}' \
  http://$NODE_IP:$INGRESS_PORT/api/orders

# 3. 查询订单
curl -H "Host: cloudcafe.local" http://$NODE_IP:$INGRESS_PORT/api/orders

# 4. 验证数据库
MYSQL_POD=$(kubectl get pod -l app=mysql -n cloud-cafe -o jsonpath='{.items[0].metadata.name}')
kubectl exec -it $MYSQL_POD -n cloud-cafe -- mysql -ucafeadmin -puserpassword123 cloudcafe -e "SELECT * FROM orders ORDER BY order_time DESC LIMIT 5;"

3. 测试高可用性

bash
# 删除一个前端 Pod
kubectl delete pod -l app=frontend -n cloud-cafe

# 删除一个后端 Pod
kubectl delete pod -l app=order-backend -n cloud-cafe

# 观察自动恢复
kubectl get pods -n cloud-cafe -w

# 测试服务是否仍然可用
curl -H "Host: cloudcafe.local" http://$NODE_IP:$INGRESS_PORT/api/health

4. 查看 Ingress 日志

bash
# 查看 Ingress Controller 日志
kubectl logs -n ingress-nginx -l app.kubernetes.io/component=controller --tail=50

# 实时查看日志
kubectl logs -n ingress-nginx -l app.kubernetes.io/component=controller -f

📝 总结和思考

本课程学到的知识点

  1. Ingress: 管理外部访问集群内服务的规则
  2. 前后端分离: 将前端和后端部署为独立的服务
  3. 服务发现: Kubernetes 内置的 DNS 服务发现机制
  4. CORS: 跨域资源共享,允许前端调用后端 API
  5. 路由配置: 使用 Ingress 配置 HTTP 路由规则

关键概念

  • 微服务架构: 将应用拆分为多个独立的服务
  • 服务间通信: 通过 Service 实现服务之间的通信
  • 外部访问: 使用 Ingress 暴露服务到外部
  • DNS 解析: Kubernetes 自动为 Service 创建 DNS 记录

思考题

  1. Ingress 和 Service 有什么区别?分别在什么场景下使用?
  2. 为什么需要配置 CORS?不配置会有什么问题?
  3. Kubernetes 的服务发现是如何工作的?
  4. 如何实现 HTTPS 访问?(提示:TLS 证书)
  5. 如何实现蓝绿部署或金丝雀发布?

最佳实践

  1. 使用 Ingress 统一管理路由: 避免使用多个 NodePort
  2. 配置健康检查: 确保服务可用性
  3. 合理设置副本数: 根据负载调整副本数
  4. 监控服务间通信: 使用日志和监控工具

下一步

本课程学习了如何实现前后端分离,构建微服务架构。

下一课程将学习如何添加缓存和负载均衡,提升系统性能和可用性。

下一课程: 05-高可用架构.md


清理环境

如果你想清理本课程创建的资源:

bash
# 删除 Ingress
kubectl delete ingress cloud-cafe-ingress -n cloud-cafe

# 删除前端应用
kubectl delete deployment frontend -n cloud-cafe
kubectl delete svc frontend-svc -n cloud-cafe
kubectl delete configmap frontend-html -n cloud-cafe

# 删除后端服务
kubectl delete deployment order-backend -n cloud-cafe
kubectl delete svc order-backend-svc -n cloud-cafe

# 删除数据库
kubectl delete statefulset mysql -n cloud-cafe
kubectl delete svc mysql-service -n cloud-cafe

# 删除 PVC
kubectl delete pvc mysql-pvc app-log-pvc -n cloud-cafe

# 删除 ConfigMap
kubectl delete configmap mysql-config app-config order-backend-config -n cloud-cafe

# 删除 Secret
kubectl delete secret mysql-secret mysql-secret-manual -n cloud-cafe

# 删除命名空间
kubectl delete namespace cloud-cafe

提示: 如果你要继续学习下一个课程,建议保留这些资源,因为下一个课程会在此基础上进行。

评论区

专业的Linux技术学习平台,从入门到精通的完整学习路径