主题
添加订单系统
业务场景
云咖啡公司的官网已经上线,现在客户希望能够在线下单。我们需要开发一个订单系统,包括:
- 数据库存储订单信息
- 后端 API 处理订单请求
- 数据持久化确保数据不丢失
需求:
- 部署 MySQL 数据库
- 配置数据库连接信息
- 实现数据持久化
- 部署订单后端服务
学习目标
完成本课程后,你将掌握:
- ConfigMap 的使用场景和最佳实践
- Secret 的创建和使用
- PVC 和 PV 的概念和使用
- StatefulSet 部署有状态应用
- 数据持久化的实现方法
前置准备
1. 确认环境
bash
# 检查命名空间
kubectl get namespace cloud-cafe
# 检查现有资源
kubectl get all -n cloud-cafe2. 准备存储
K3s 默认使用 hostPath 作为存储类,让我们先检查存储类:
bash
# 查看存储类
kubectl get storageclass
# 查看默认存储类
kubectl get storageclass -o jsonpath='{.items[?(@.metadata.annotations.storageclass\.kubernetes\.io/is-default-class=="true")].metadata.name}'实战步骤
Step 1: 使用 ConfigMap 管理配置
概念: ConfigMap 用于存储非敏感的配置数据,可以将配置与镜像分离。
1.1 创建数据库配置
bash
# 创建 MySQL 配置文件
kubectl create configmap mysql-config \
--from-literal=MYSQL_DATABASE=cloudcafe \
--from-literal=MYSQL_USER=cafeadmin \
-n cloud-cafe
# 查看 ConfigMap
kubectl get configmap mysql-config -n cloud-cafe
# 查看 ConfigMap 详细信息
kubectl describe configmap mysql-config -n cloud-cafe
# 查看 ConfigMap 内容
kubectl get configmap mysql-config -n cloud-cafe -o yaml📌 关于
--from-literal参数的解释
--from-literal用于从键值对字面量创建 ConfigMap 或 Secret。语法:
--from-literal=KEY=VALUE使用场景:
- 快速创建简单的配置项
- 在命令行直接指定配置值
- 脚本中动态生成配置
对比其他创建方式:
方式 命令 适用场景 --from-literalkubectl create configmap ... --from-literal=KEY=VALUE简单键值对 --from-filekubectl create configmap ... --from-file=config.properties从文件创建 --from-env-filekubectl create configmap ... --from-env-file=.env从环境变量文件创建 YAML 文件 kubectl apply -f configmap.yaml复杂配置、版本控制 示例:
bash# 从文件创建(适合配置文件) kubectl create configmap nginx-config --from-file=nginx.conf # 从目录创建(包含多个配置文件) kubectl create configmap app-config --from-file=./config/
1.2 创建应用配置
bash
# 创建应用配置文件
kubectl create configmap app-config \
--from-literal=DB_HOST=mysql-service \
--from-literal=DB_PORT=3306 \
--from-literal=LOG_LEVEL=info \
-n cloud-cafe
# 查看 ConfigMap
kubectl get configmap -n cloud-cafeStep 2: 使用 Secret 管理敏感信息
概念: Secret 用于存储敏感信息(如密码、密钥等),数据会进行 base64 编码。
2.1 创建数据库密码 Secret
bash
# 方法 1: 使用 kubectl create secret 命令
kubectl create secret generic mysql-secret \
--from-literal=MYSQL_ROOT_PASSWORD=rootpassword123 \
--from-literal=MYSQL_PASSWORD=userpassword123 \
-n cloud-cafe
# 查看 Secret
kubectl get secret mysql-secret -n cloud-cafe
# 查看 Secret 详细信息(密码会被隐藏)
kubectl describe secret mysql-secret -n cloud-cafe
# 查看 Secret 内容(base64 编码)
kubectl get secret mysql-secret -n cloud-cafe -o yaml2.2 手动创建 Secret(了解 base64 编码)
bash
# 方法 2: 手动创建 Secret(了解 base64 编码)
# 对密码进行 base64 编码
echo -n "rootpassword123" | base64
echo -n "userpassword123" | base64
# 创建 Secret YAML 文件
cat > mysql-secret-manual.yaml << 'EOF'
# MySQL Secret 手动创建示例
# 用途:演示如何通过 base64 编码创建 Secret
# 注意:生产环境建议使用 kubectl create secret 命令,更安全
apiVersion: v1
kind: Secret
metadata:
name: mysql-secret-manual
namespace: cloud-cafe
type: Opaque
data:
# [base64编码] MYSQL_ROOT_PASSWORD=rootpassword123
MYSQL_ROOT_PASSWORD: cm9vdHBhc3N3b3JkMTIz
# [base64编码] MYSQL_PASSWORD=userpassword123
MYSQL_PASSWORD: dXNlcnBhc3N3b3JkMTIz
EOF
# 应用 Secret
kubectl apply -f mysql-secret-manual.yaml
# 查看 Secret
kubectl get secret mysql-secret-manual -n cloud-cafeStep 3: 创建 PVC 实现数据持久化
概念: PVC(PersistentVolumeClaim)用于声明存储需求,PV(PersistentVolume)是实际的存储资源。
3.1 创建 PVC
bash
# 创建 MySQL 数据 PVC YAML 文件
cat > mysql-pvc.yaml << 'EOF'
# MySQL 数据持久化卷声明
# 用途:为 MySQL 数据库提供持久化存储
# 存储类:使用 K3s 默认的 local-path
# 容量:1Gi
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
namespace: cloud-cafe
spec:
accessModes:
- ReadWriteOnce # 单节点读写,适合数据库
resources:
requests:
storage: 1Gi # 申请 1GB 存储空间
storageClassName: local-path # K3s 默认存储类
EOF
# 应用 PVC
kubectl apply -f mysql-pvc.yaml
# 查看 PVC
kubectl get pvc -n cloud-cafe
# 查看 PVC 详细信息
kubectl describe pvc mysql-pvc -n cloud-cafe
# 查看 PV(K3s 会自动创建)
kubectl get pv观察要点:
- PVC 的状态是 Bound
- 自动创建了对应的 PV
- PV 的容量是 1Gi
3.2 创建日志存储 PVC
bash
# 创建应用日志 PVC YAML 文件
cat > app-log-pvc.yaml << 'EOF'
# 应用日志持久化卷声明
# 用途:为后端应用提供日志存储
# 容量:500Mi
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-log-pvc
namespace: cloud-cafe
spec:
accessModes:
- ReadWriteOnce # 单节点读写
resources:
requests:
storage: 500Mi # 申请 500MB 存储空间
storageClassName: local-path # K3s 默认存储类
EOF
# 应用 PVC
kubectl apply -f app-log-pvc.yaml
# 查看 PVC
kubectl get pvc -n cloud-cafeStep 4: 使用 StatefulSet 部署 MySQL
概念: StatefulSet 用于管理有状态应用,提供稳定的网络标识和持久化存储。
4.1 部署 MySQL
bash
# 创建 MySQL StatefulSet YAML 文件
cat > mysql-statefulset.yaml << 'EOF'
# MySQL StatefulSet 配置
# 用途:部署有状态的 MySQL 数据库
# 特点:
# - 稳定的网络标识(Pod 名称固定为 mysql-0)
# - 持久化存储(通过 PVC)
# - 健康检查(livenessProbe 和 readinessProbe)
# 前置依赖:需要 mysql-secret、mysql-config、mysql-pvc 已创建
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
namespace: cloud-cafe
labels:
app: mysql
spec:
serviceName: mysql-service # 关联的 Headless Service 名称
replicas: 1 # 单节点 MySQL(生产环境建议主从复制)
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0 # MySQL 8.0 官方镜像
ports:
- containerPort: 3306
name: mysql # 端口名称,Service 会引用
# 环境变量:从 Secret 和 ConfigMap 读取配置
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: MYSQL_ROOT_PASSWORD
- name: MYSQL_DATABASE
valueFrom:
configMapKeyRef:
name: mysql-config
key: MYSQL_DATABASE
- name: MYSQL_USER
valueFrom:
configMapKeyRef:
name: mysql-config
key: MYSQL_USER
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: MYSQL_PASSWORD
# 存储挂载
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql # MySQL 数据目录
# 资源限制
resources:
requests:
memory: "256Mi" # 最低内存要求
cpu: "250m" # 最低 CPU 要求(0.25核)
limits:
memory: "512Mi" # 最大内存限制
cpu: "500m" # 最大 CPU 限制(0.5核)
# 健康检查配置
livenessProbe:
exec:
# 使用 mysqladmin ping 检查 MySQL 是否存活
command: ["mysqladmin", "ping", "-h", "localhost"]
initialDelaySeconds: 30 # 首次检查延迟(MySQL 启动较慢)
periodSeconds: 10 # 检查间隔
timeoutSeconds: 5 # 超时时间
failureThreshold: 3 # 失败阈值
readinessProbe:
exec:
# 执行简单 SQL 检查 MySQL 是否就绪
command: ["mysql", "-h", "127.0.0.1", "-e", "SELECT 1"]
initialDelaySeconds: 5 # 首次检查延迟
periodSeconds: 5 # 检查间隔
timeoutSeconds: 3 # 超时时间
failureThreshold: 3 # 失败阈值
# 持久化卷声明
volumes:
- name: mysql-data
persistentVolumeClaim:
claimName: mysql-pvc # 引用之前创建的 PVC
EOF
# 应用 StatefulSet
kubectl apply -f mysql-statefulset.yaml
# 等待 MySQL 就绪
kubectl rollout status statefulset/mysql -n cloud-cafe
# 查看 StatefulSet
kubectl get statefulset -n cloud-cafe
# 查看 Pod
kubectl get pods -n cloud-cafe观察要点:
- StatefulSet 创建的 Pod 名称是稳定的(mysql-0)
- Pod 的状态是 Running 且 Ready
4.2 创建 MySQL Service
bash
# 创建 MySQL Service YAML 文件
cat > mysql-service.yaml << 'EOF'
# MySQL Headless Service 配置
# 用途:为 StatefulSet 提供稳定的网络标识
# 特点:
# - clusterIP: None 表示这是 Headless Service
# - Pod 可以通过 mysql-0.mysql-service 直接访问
# - 用于 StatefulSet 内部通信
apiVersion: v1
kind: Service
metadata:
name: mysql-service
namespace: cloud-cafe
labels:
app: mysql
spec:
selector:
app: mysql # 选择标签为 app=mysql 的 Pod
ports:
- port: 3306 # Service 暴露的端口
targetPort: 3306 # 容器内的端口
clusterIP: None # Headless Service,为 StatefulSet 提供 DNS 记录
EOF
# 应用 Service
kubectl apply -f mysql-service.yaml
# 查看 Service
kubectl get svc mysql-service -n cloud-cafe
# 查看 Service 详细信息
kubectl describe svc mysql-service -n cloud-cafe注意: 这里使用 clusterIP: None 创建 Headless Service,为 StatefulSet 提供稳定的网络标识。
Step 5: 验证 MySQL 部署
5.1 测试数据库连接
bash
# 获取 MySQL Pod 名称
MYSQL_POD=$(kubectl get pod -l app=mysql -n cloud-cafe -o jsonpath='{.items[0].metadata.name}')
# 进入 MySQL Pod
kubectl exec -it $MYSQL_POD -n cloud-cafe -- mysql -ucafeadmin -puserpassword123 cloudcafe
# 在 MySQL 中执行以下命令
SHOW DATABASES;
SHOW TABLES;
CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_name VARCHAR(100),
coffee_type VARCHAR(50),
quantity INT,
total_price DECIMAL(10,2),
order_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
SHOW TABLES;
DESCRIBE orders;
# 插入一些测试数据
INSERT INTO orders (customer_name, coffee_type, quantity, total_price) VALUES
('张三', '焦糖玛奇朵', 2, 56.00),
('李四', '拿铁', 1, 28.00),
('王五', '美式咖啡', 3, 45.00);
# 查询数据
SELECT * FROM orders;
# 退出 MySQL
EXIT;5.2 验证数据持久化
bash
# 删除 MySQL Pod
kubectl delete pod $MYSQL_POD -n cloud-cafe
# 等待 Pod 重新创建
kubectl get pods -n cloud-cafe -w
# 等待 Pod 就绪后,再次进入 MySQL
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
# 查询数据,验证数据是否持久化
SELECT * FROM orders;
# 退出 MySQL
EXIT;预期结果: 数据应该仍然存在,证明数据持久化成功。
Step 6: 部署订单后端服务
现在我们部署一个简单的订单后端服务,使用 Python Flask 框架。
6.1 创建后端服务配置
bash
# 创建后端服务 ConfigMap
kubectl create configmap order-backend-config \
--from-literal=FLASK_ENV=production \
--from-literal=FLASK_DEBUG=0 \
-n cloud-cafe6.2 部署后端服务
bash
# 创建后端服务 Deployment YAML 文件
cat > order-backend-deployment.yaml << 'EOF'
# 订单后端服务 Deployment
# 用途:部署订单系统的后端 API 服务
# 技术栈:Python Flask + PyMySQL
# 功能:提供订单的增删改查 RESTful API
# 前置依赖:需要 MySQL、相关 ConfigMap 和 Secret 已创建
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-backend
namespace: cloud-cafe
labels:
app: order-backend
spec:
replicas: 2 # 运行 2 个副本,实现高可用
selector:
matchLabels:
app: order-backend
template:
metadata:
labels:
app: order-backend
spec:
containers:
- name: order-backend
image: python:3.9-slim # Python 3.9 精简版镜像
command: ["/bin/sh", "-c"]
args:
- |
# 安装依赖
pip install flask pymysql
# 创建 Flask 应用
cat > /app/app.py << 'PYEOF'
from flask import Flask, request, jsonify
import pymysql
import os
app = Flask(__name__)
# 数据库配置:从环境变量读取,环境变量由 ConfigMap/Secret 提供
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')
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 # Flask 应用端口
# 环境变量配置
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" # 最低 CPU(0.1核)
limits:
memory: "256Mi" # 最大内存限制
cpu: "200m" # 最大 CPU(0.2核)
# 健康检查
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 # 引用之前创建的日志 PVC
EOF
# 应用 Deployment
kubectl apply -f order-backend-deployment.yaml
# 等待后端服务就绪
kubectl rollout status deployment/order-backend -n cloud-cafe
# 查看 Pod
kubectl get pods -n cloud-cafe6.3 创建后端服务 Service
bash
# 创建后端服务 Service
kubectl expose deployment order-backend \
--port=5000 \
--target-port=5000 \
--name=order-backend-svc \
-n cloud-cafe
# 查看 Service
kubectl get svc order-backend-svc -n cloud-cafeStep 7: 测试订单系统
7.1 测试健康检查
bash
# 测试健康检查端点
kubectl run test-pod --image=curlimages/curl --rm -it -n cloud-cafe -- \
curl http://order-backend-svc:5000/health📌 关于
--参数的解释
--(双横线)是参数分隔符,表示后面的内容不作为 kubectl 的参数解析,而是传递给容器作为命令。为什么需要
--?
- 避免容器命令中的参数被 kubectl 误解
- 明确区分 kubectl 参数和容器命令
对比:
bash# ❌ 错误:-c 会被 kubectl 解析 kubectl run test --image=busybox --rm -it -n cloud-cafe echo "hello" # ✅ 正确:-- 后的内容都传递给容器 kubectl run test --image=busybox --rm -it -n cloud-cafe -- echo "hello"使用建议:当容器命令包含参数时,养成使用
--的习惯
预期输出: {"status":"healthy"}
7.2 测试获取订单
bash
# 获取所有订单
kubectl run test-pod --image=curlimages/curl --rm -it -n cloud-cafe -- \
curl http://order-backend-svc:5000/orders预期输出: 应该看到之前插入的订单数据
7.3 测试创建订单
bash
# 创建新订单
kubectl run test-pod --image=curlimages/curl --rm -it -n cloud-cafe -- \
curl -X POST http://order-backend-svc:5000/orders \
-H "Content-Type: application/json" \
-d '{"customer_name":"赵六","coffee_type":"卡布奇诺","quantity":1,"total_price":32.00}'预期输出: {"order_id": 4, "message": "Order created successfully"}
7.4 验证数据持久化
bash
# 再次获取订单,验证新订单已保存
kubectl run test-pod --image=curlimages/curl --rm -it -n cloud-cafe -- \
curl http://order-backend-svc:5000/orders
# 在 MySQL 中验证
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;"验证和测试
1. 检查所有资源状态
bash
# 查看 StatefulSet
kubectl get statefulset -n cloud-cafe
# 查看 Deployment
kubectl get deployment -n cloud-cafe
# 查看 Pod
kubectl get pods -n cloud-cafe
# 查看 Service
kubectl get svc -n cloud-cafe
# 查看 PVC
kubectl get pvc -n cloud-cafe
# 查看 PV
kubectl get pv
# 查看 ConfigMap
kubectl get configmap -n cloud-cafe
# 查看 Secret
kubectl get secret -n cloud-cafe2. 测试数据持久化
bash
# 删除 MySQL Pod
kubectl delete pod -l app=mysql -n cloud-cafe
# 等待 Pod 重新创建
kubectl get pods -n cloud-cafe -w
# 验证数据是否仍然存在
kubectl run test-pod --image=curlimages/curl --rm -it -n cloud-cafe -- \
curl http://order-backend-svc:5000/orders3. 测试高可用性
bash
# 删除一个后端服务 Pod
kubectl delete pod -l app=order-backend -n cloud-cafe
# 观察 Deployment 是否自动创建新的 Pod
kubectl get pods -n cloud-cafe -w
# 测试服务是否仍然可用
kubectl run test-pod --image=curlimages/curl --rm -it -n cloud-cafe -- \
curl http://order-backend-svc:5000/health4. 查看日志
bash
# 查看 MySQL 日志
kubectl logs -l app=mysql -n cloud-cafe
# 查看后端服务日志
kubectl logs -l app=order-backend -n cloud-cafe
# 查看持久化存储的日志
kubectl exec -it $(kubectl get pod -l app=order-backend -n cloud-cafe -o jsonpath='{.items[0].metadata.name}') -n cloud-cafe -- ls -la /app/logs📝 总结和思考
本课程学到的知识点
- ConfigMap: 管理非敏感配置数据
- Secret: 管理敏感信息(密码、密钥等)
- PVC/PV: 实现数据持久化
- StatefulSet: 部署有状态应用
- Headless Service: 为 StatefulSet 提供稳定的网络标识
关键概念
- 配置分离: ConfigMap 和 Secret 将配置与镜像分离
- 数据持久化: PVC 确保数据在 Pod 重启后仍然存在
- 有状态应用: StatefulSet 提供稳定的网络标识和存储
- 安全最佳实践: 使用 Secret 存储敏感信息
思考题
- ConfigMap 和 Secret 有什么区别?分别在什么场景下使用?
- PVC 的 accessModes 有哪些类型?分别代表什么含义?
- StatefulSet 和 Deployment 有什么区别?分别在什么场景下使用?
- 为什么 MySQL 使用 StatefulSet 而不是 Deployment?
- 如何实现 MySQL 的高可用?(提示:主从复制、集群)
最佳实践
- 敏感信息使用 Secret: 不要在 ConfigMap 中存储密码
- 合理设置存储大小: 根据实际需求设置 PVC 的存储大小
- 配置健康检查: 确保服务可用性
- 备份数据: 定期备份重要数据
下一步
本课程学习了如何部署数据库,实现数据持久化,并构建了一个完整的订单系统。
下一课程将学习如何实现前后端分离,构建更复杂的微服务架构。
下一课程: 04-前后端分离架构.md
清理环境
如果你想清理本课程创建的资源:
bash
# 删除 StatefulSet
kubectl delete statefulset mysql -n cloud-cafe
# 删除 Deployment
kubectl delete deployment order-backend -n cloud-cafe
# 删除 Service
kubectl delete svc mysql-service order-backend-svc -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提示: 如果你要继续学习下一个课程,建议保留这些资源,因为下一个课程会在此基础上进行。