第一阶段:配置存储入口

第一步:安装本地存储插件 (StorageClass)

这是必须要做的,否则后续部署 MySQL、Redis 和 Prometheus 时,PVC 会一直卡在 Pending 状态。

  1. 安装 Rancher Local Path Provisioner

kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.24/deploy/local-path-storage.yaml
2. 将其设为默认存储类 (关键) 这样以后不必每次都指定 StorageClass 的名字。

kubectl patch storageclass local-path -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
3. 验证

kubectl get sc

第二步:安装 Ingress 控制器 (流量入口)

需要 Ingress Nginx 来管理外部访问。由于是本地环境,使用兼容性最好的 baremetal 版本。

  1. 安装 Ingress-Nginx Controller

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.2/deploy/static/provider/baremetal/deploy.yaml
2. 验证 Pod 启动

kubectl get pods -n ingress-nginx

第三步:为项目创建专属空间

为了不污染系统环境,我们把电商项目都放在独立的 Namespace 里。

kubectl create namespace ecommerce
✅ 阶段性检查
预期输出:应看到 local-path (default)。
预期输出:需要等待一会儿,直到 ingress-nginx-controller 变为 Running。
一旦确认无误,就可以直接部署数据库了!

kubectl get sc
kubectl get pods -n ingress-nginx

Image

第二阶段:数据层部署

任何电商系统都离不开数据。我们将部署 MySQL(存储订单和商品)和 Redis(做缓存)。使用 ConfigMap 自动初始化数据库表结构,实现“启动即用”。

1. 创建数据层部署文件

请直接在终端创建一个名为 db-layer.yaml 的文件:

vim db-layer.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: ecommerce
---
# ===========================
# 1. MySQL 配置 (自动建库建表)
# ===========================
apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-init-script
  namespace: ecommerce
data:
  init.sql: |
    CREATE DATABASE IF NOT EXISTS shop;
    USE shop;
    
    CREATE TABLE IF NOT EXISTS product (
      id INT AUTO_INCREMENT PRIMARY KEY,
      name VARCHAR(100),
      price DECIMAL(10,2),
      stock INT
    );
    
    CREATE TABLE IF NOT EXISTS orders (
      id INT AUTO_INCREMENT PRIMARY KEY,
      product_id INT,
      user_id INT,
      amount DECIMAL(10,2),
      status VARCHAR(20) DEFAULT 'PENDING'
    );
    
    -- 初始化一些测试数据
    INSERT INTO product (name, price, stock) VALUES ('iPhone 15', 5999.00, 100);
    INSERT INTO product (name, price, stock) VALUES ('MacBook Pro', 12999.00, 50);
---
# ===========================
# 2. MySQL 部署 (有状态应用)
# ===========================
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
  namespace: ecommerce
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:5.7  # 使用 5.7 版本,资源占用少
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: "123456" # 模拟项目为了方便直接写明文,生产环境请用 Secret
        ports:
        - containerPort: 3306
        volumeMounts:
        - name: mysql-storage
          mountPath: /var/lib/mysql
        - name: init-script
          mountPath: /docker-entrypoint-initdb.d # 这里是 MySQL 自动初始化的关键目录
      volumes:
      - name: mysql-storage
        persistentVolumeClaim:
          claimName: mysql-pvc
      - name: init-script
        configMap:
          name: mysql-init-script
---
# ===========================
# 3. MySQL 存储声明 (PVC)
# ===========================
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pvc
  namespace: ecommerce
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
---
# ===========================
# 4. MySQL 服务暴露
# ===========================
apiVersion: v1
kind: Service
metadata:
  name: mysql
  namespace: ecommerce
spec:
  ports:
  - port: 3306
  selector:
    app: mysql
  clusterIP: None # Headless Service,适合数据库内部通信
---
# ===========================
# 5. 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:alpine
        ports:
        - containerPort: 6379
---
# ===========================
# 6. Redis 服务暴露
# ===========================
apiVersion: v1
kind: Service
metadata:
  name: redis
  namespace: ecommerce
spec:
  ports:
  - port: 6379
  selector:
    app: redis

2. 执行部署

运行以下命令应用配置:

kubectl apply -f db-layer.yaml

3. 验证部署状态

需要确认三件事:

PVC 绑定成功:看 MySQL 是否成功申请到了存储。
Pod 运行正常:看数据库是否启动。
数据初始化成功:看表是不是建好了。

查看 PVC 状态 (STATUS 必须是 Bound)
kubectl get pvc -n ecommerce
查看 Pod 状态 (必须是 Running)
kubectl get pods -n ecommerce

(进阶验证) 进入 MySQL 容器查看表是否存在
等 Pod Running 后执行:
kubectl exec -it -n ecommerce $(kubectl get pod -n ecommerce -l app=mysql -o jsonp

第三阶段:微服务应用层(Application Layer)

使用 ConfigMap-as-Code 把 Python 业务代码直接写在 Kubernetes 的 ConfigMap 里,然后挂载到容器中运行。

1. 部署微服务群 (应用层)

创建一个新文件 app-layer.yaml:

vim app-layer.yaml

逻辑说明:
Product Service: 提供 /products 接口,模拟从数据库查商品。
Pay Service: 提供 /pay 接口,模拟 20% 的支付失败率(用于测试重试机制)。
Order Service: 调用上面两个服务,模拟下单全流程。
部署策略: 每个服务 2 个副本,演示负载均衡。

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-code
  namespace: ecommerce
data:
  # ===========================
  # 1. 商品服务代码 (模拟)
  # ===========================
  product.py: |
    from flask import Flask, jsonify
    import os
    app = Flask(__name__)
    
    @app.route('/product/<id>')
    def get_product(id):
        # 模拟从数据库查询 (为了演示简化,直接返回静态 JSON)
        return jsonify({
            "id": id,
            "name": "iPhone 15 Pro",
            "price": 8999,
            "stock": 100,
            "pod": os.getenv("HOSTNAME") # 返回 Pod 名字,证明负载均衡在工作
        })

    if __name__ == '__main__':
        app.run(host='0.0.0.0', port=8080)

  # ===========================
  # 2. 支付服务代码 (模拟高可用故障)
  # ===========================
  pay.py: |
    from flask import Flask, jsonify
    import random, time, os
    app = Flask(__name__)

    @app.route('/pay', methods=['POST'])
    def pay():
        # 模拟 20% 的随机支付失败
        if random.random() < 0.2:
            return jsonify({"error": "Payment Gateway Timeout"}), 500
        
        # 模拟网络延迟
        time.sleep(0.1) 
        return jsonify({"status": "success", "pod": os.getenv("HOSTNAME")})

    if __name__ == '__main__':
        app.run(host='0.0.0.0', port=8080)

  # ===========================
  # 3. 订单服务代码 (核心聚合逻辑)
  # ===========================
  order.py: |
    from flask import Flask, jsonify
    import requests, os
    app = Flask(__name__)

    @app.route('/create_order')
    def create_order():
        # 1. 调用商品服务
        try:
            prod = requests.get('http://product-service:8080/product/1').json()
        except:
            return jsonify({"error": "Product Service Down"}), 503

        # 2. 调用支付服务
        try:
            pay = requests.post('http://pay-service:8080/pay').json()
        except:
            return jsonify({"error": "Payment Failed"}), 500

        # 3. 生成订单
        return jsonify({
            "status": "Order Created",
            "product": prod['name'],
            "price": prod['price'],
            "payment": pay,
            "served_by": os.getenv("HOSTNAME")
        })

    @app.route('/')
    def home():
        return "Welcome to K8s Shop! Try /create_order"

    if __name__ == '__main__':
        app.run(host='0.0.0.0', port=8080)

---
# ===========================
# 通用部署模板 (3个微服务共用配置)
# ===========================
apiVersion: apps/v1
kind: Deployment
metadata:
  name: product-service
  namespace: ecommerce
spec:
  replicas: 2
  selector:
    matchLabels:
      app: product
  template:
    metadata:
      labels:
        app: product
    spec:
      containers:
      - name: app
        image: python:3.9-slim
        command: ["/bin/sh", "-c"]
        # 启动时现场安装 Flask 并运行代码
        args: ["pip install flask requests && python /app/product.py"]
        ports:
        - containerPort: 8080
        volumeMounts:
        - name: code-volume
          mountPath: /app
      volumes:
      - name: code-volume
        configMap:
          name: app-code
---
apiVersion: v1
kind: Service
metadata:
  name: product-service
  namespace: ecommerce
spec:
  selector:
    app: product
  ports:
  - port: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pay-service
  namespace: ecommerce
spec:
  replicas: 2
  selector:
    matchLabels:
      app: pay
  template:
    metadata:
      labels:
        app: pay
    spec:
      containers:
      - name: app
        image: python:3.9-slim
        command: ["/bin/sh", "-c"]
        args: ["pip install flask requests && python /app/pay.py"]
        ports:
        - containerPort: 8080
        volumeMounts:
        - name: code-volume
          mountPath: /app
      volumes:
      - name: code-volume
        configMap:
          name: app-code
---
apiVersion: v1
kind: Service
metadata:
  name: pay-service
  namespace: ecommerce
spec:
  selector:
    app: pay
  ports:
  - port: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  namespace: ecommerce
spec:
  replicas: 2
  selector:
    matchLabels:
      app: order
  template:
    metadata:
      labels:
        app: order
    spec:
      containers:
      - name: app
        image: python:3.9-slim
        command: ["/bin/sh", "-c"]
        args: ["pip install flask requests && python /app/order.py"]
        ports:
        - containerPort: 8080
        volumeMounts:
        - name: code-volume
          mountPath: /app
      volumes:
      - name: code-volume
        configMap:
          name: app-code
---
apiVersion: v1
kind: Service
metadata:
  name: order-service
  namespace: ecommerce
spec:
  selector:
    app: order
  ports:
  - port: 8080
---
# ===========================
# Ingress 入口配置 (对外暴露)
# ===========================
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: shop-ingress
  namespace: ecommerce
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
  - host: order.shop.local  # 虚拟域名
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: order-service
            port:
              number: 8080

2. 执行部署

kubectl apply -f app-layer.yaml
这些 Pod 启动可能需要 1-2 分钟,因为它们需要在容器启动时运行 pip install 下载 Flask 库。

使用以下命令观察:

加上 -w 参数,实时观察状态变化
kubectl get pods -n ecommerce -w
等到 product-service, pay-service, order-service 全部变成 Running。

3. 最终验证

现在整个系统已经跑起来了,我们需要访问它。

快速验证 (在虚拟机内部) 直接用 curl 访问 Ingress Controller,强制指定 Host 头。
模拟访问下单接口
curl -H "Host: order.shop.local" http://127.0.0.1:<端口号>/create_order

Image

成功说明:
流量入口:curl 请求通过 Ingress (Nginx) 进入集群。
总指挥:流量被路由到 Order Service (served_by 显示的那个 Pod)。
服务发现与调用:
Order Service 通过 K8s DNS 找到了 product-service,获取了 iPhone 信息。
Order Service 找到了 pay-service,完成扣款(由 pay-service-8b65... 这个 Pod 处理)。
结果聚合:Order Service 把所有结果打包成 JSON 返回。

第四阶段:连接真实数据库

需要修改 app-layer.yaml 中的 Product Service 部分:
安装驱动:在启动命令里加上 pymysql 库。
修改代码:把返回死数据改成连接 MySQL 查询。
执行 vim app-layer.yaml,找到 app-code ConfigMap 里的 product.py 和下面的 Deployment,按以下内容修改:

  1. 修改 ConfigMap 里的 product.py
    把原来的 product.py 内容替换为:
  product.py: |
    from flask import Flask, jsonify
    import os, pymysql
    app = Flask(__name__)
    
    def get_db_connection():
        return pymysql.connect(
            host='mysql',        # K8s Service 名字,自动解析 IP
            user='root',
            password='123456',   # 之前设置的密码
            db='shop',
            charset='utf8mb4',
            cursorclass=pymysql.cursors.DictCursor
        )
    
    @app.route('/product/<id>')
    def get_product(id):
        try:
            connection = get_db_connection()
            with connection.cursor() as cursor:
                # 真的去查数据库了!
                sql = "SELECT * FROM product WHERE id=%s"
                cursor.execute(sql, (id,))
                result = cursor.fetchone()
            connection.close()
            
            if result:
                # 把 Pod 名字加上,方便调试
                result['pod'] = os.getenv("HOSTNAME")
                # 转换 Decimal 类型为 float 避免 JSON 报错
                result['price'] = float(result['price'])
                return jsonify(result)
            else:
                return jsonify({"error": "Not Found"}), 404
        except Exception as e:
            return jsonify({"error": str(e)}), 500

    if __name__ == '__main__':
        app.run(host='0.0.0.0', port=8080)
  1. 修改 Deployment 的启动命令
    找到 product-service 的 Deployment 部分,修改 args 这一行,加上 pymysql:
    containers:
     - name: app
       # ... 镜像和其他配置不变 ...
       # 注意:加上了 pymysql
       args: ["pip install -i https://mirrors.aliyun.com/pypi/simple/ flask requests pymysql && python /app/product.py"] 

执行更新

1. 应用新配置

kubectl apply -f app-layer.yaml

2. 重启 Product 服务 (让它加载新代码)

kubectl delete pods -n ecommerce -l app=product
等新的 Product Pod 跑起来之后,再次执行 curl:

curl -H "Host: order.shop.local" http://127.0.0.1:30628/create_order
观察变化: 如果你看到的价格变成了 5999.0(在数据库初始化脚本里写的价格),而不是代码里写死的 8999,那就证明已经成功构建了一个包含:流量入口(Ingress)、服务发现(Service)、负载均衡、故障模拟、以及持久化数据库(MySQL)的完整 Kubernetes 微服务电商系统!