前言
"这个服务在我本地明明跑得好好的!"
这句话大概是 DevOps 工程师听到最多的抱怨。本地能跑,生产必挂------这是没有容器化的项目逃不掉的宿命。
2025年,Docker + Kubernetes(K8s) 已经是企业级部署的事实标准。无论你是:
- 🐳 想从零学会 Docker 的后端工程师
- ☸️ 想进阶 K8s 的 DevOps 从业者
- 🏗️ 想搭建高可用微服务架构的架构师
这篇文章,手把手带你从镜像构建 到集群部署 ,从本地开发 到生产高可用,完成一个完整的 DevOps 实战流程。
目录
- 前置知识:Docker和K8s到底是什么?
- 环境准备:安装Docker和Kubernetes
- 第一章:Docker化改造:把应用装进容器
- [第二章:Docker Compose:本地多容器编排](#第二章:Docker Compose:本地多容器编排)
- 第三章:Kubernetes入门:核心概念详解
- 第四章:从零部署应用到K8s集群
- 第五章:服务治理:Ingress、Service与网络
- 第六章:数据持久化:ConfigMap、Secret与PV/PVC
- 第七章:高可用与弹性扩缩容
- [第八章:CI/CD实战:GitHub Actions自动部署](#第八章:CI/CD实战:GitHub Actions自动部署)
- 实战完整项目代码
- 常见问题与排错指南
1. 前置知识:Docker 和 K8s 到底是什么?
1.1 Docker:应用的"集装箱"
Docker 的核心思想是 Container(容器),类似于集装箱:
传统部署: Docker 部署:
┌─────────────┐ ┌─────────────────────┐
│ 服务器A │ │ 服务器A(Docker宿主机)│
│ app.exe │ │ ┌──────┐ ┌──────┐ │
│ config.ini │ → │ │容器1 │ │容器2 │ │
│ python.exe │ │ │app │ │db │ │
│ mysql.exe │ │ │config│ │mysql │ │
│ nginx.exe │ │ │依赖 │ │依赖 │ │
└─────────────┘ │ └──────┘ └──────┘ │
│ ↑ Docker Engine │
└─────────────────────┘
容器 vs 虚拟机:
| 对比项 | 虚拟机 (VM) | 容器 (Container) |
|---|---|---|
| 启动时间 | 几分钟 | 几秒钟 |
| 资源占用 | 完整操作系统,很重 | 共享宿主机内核,很轻 |
| 隔离性 | 完全隔离 | 进程级隔离 |
| 性能 | 有损耗 | 原生性能 |
| 镜像大小 | GB级别 | MB级别 |
1.2 Kubernetes:容器的"指挥官"
Docker 解决了单容器的问题,但当应用扩展到几十个、几百个容器时:
谁管理这些容器的部署?
如何实现负载均衡?
如何做滚动更新?
如何处理容器故障?
如何扩缩容?
Kubernetes(K8s) 就是来解决这些问题的------它是容器编排平台:
Kubernetes 集群架构:
┌──────────────────────────────────────────┐
│ Control Plane(控制平面) │
│ ┌─────────┐ ┌─────────┐ ┌───────────┐ │
│ │ API │ │ Scheduler│ │ Controller│ │
│ │ Server │ │ │ │ Manager │ │
│ └────┬────┘ └─────────┘ └───────────┘ │
│ │ │
└───────┼─────────────────────────────────────┘
│ 管理指令
┌───────┴─────────────────────────────────────┐
│ Data Plane(数据平面) │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Node 1 │ │ Node 2 │ │
│ │ ┌────┐┌───┐ │ │ ┌────┐┌───┐ │ │
│ │ │Pod1││Pod2│ │ │ │Pod3││Pod4│ │ │
│ │ └────┘└───┘ │ │ └────┘└───┘ │ │
│ └─────────────┘ └─────────────┘ │
│ Worker Nodes │
└─────────────────────────────────────────────┘
2. 环境准备:安装 Docker 和 Kubernetes
2.1 安装 Docker(Windows)
方法一:Docker Desktop(推荐新手)
- 下载:https://www.docker.com/products/docker-desktop/
- 安装 Docker Desktop(约 1GB)
- 启动后,右下角托盘出现 Docker 图标即成功
powershell
# 验证安装
docker --version
# Docker version 27.3.1, build 0f6c8d5
docker-compose --version
# docker-compose version v2.29.2
docker ps
# CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
方法二:WSL2 后端(Windows 专业版/教育版推荐)
powershell
# 1. 启用 WSL2
wsl --install
# 2. 安装 Docker(Linux 方式,WSL2 内)
curl -fsSL https://get.docker.com | sh
# 3. 启动 Docker 服务
sudo service docker start
# 4. 验证
docker run hello-world
⚠️ Windows Home 版:只能使用 WSL2 后端,无法使用 Hyper-V。
2.2 安装 Docker(macOS)
bash
# 方法一:Homebrew
brew install --cask docker
# 方法二:官网下载 dmg 文件
# https://docs.docker.com/desktop/install/mac-install/
2.3 安装 Minikube(本地 K8s 学习环境)
Minikube 是在本地启动单节点 K8s 集群的工具,非常适合学习和开发。
powershell
# Windows(需要先安装 Docker Desktop)
choco install minikube -y
# macOS
brew install minikube
# Linux
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube
powershell
# 启动 Minikube(使用 Docker 驱动)
minikube start --driver=docker --cpus=4 --memory=8g
# 验证
kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# minikube Ready control-plane 2m v1.28.0
2.4 安装 kubectl(K8s 命令行工具)
powershell
# Windows(Chocolatey)
choco install kubernetes-cli -y
# macOS
brew install kubectl
# 验证
kubectl version --client
2.5 Kubernetes Dashboard(可视化界面)
bash
# 部署 Dashboard
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml
# 启动代理
kubectl proxy
# 访问
# http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/
3. 第一章:Docker化改造:把应用装进容器
3.1 目标应用:一 RESTful API 服务
我们用 Python Flask 写一个待办事项 API 作为演示应用:
python
# app.py
from flask import Flask, jsonify, request
import json
import os
app = Flask(__name__)
# 简单的内存存储(生产用数据库)
todos = [
{"id": "1", "title": "学习Docker", "completed": False},
{"id": "2", "title": "部署K8s", "completed": False},
]
DATA_FILE = "/data/todos.json"
def load_todos():
"""从文件加载待办"""
if os.path.exists(DATA_FILE):
with open(DATA_FILE, "r") as f:
return json.load(f)
return todos
def save_todos(data):
"""保存待办到文件"""
with open(DATA_FILE, "w") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
@app.route("/api/todos", methods=["GET"])
def get_todos():
"""获取所有待办"""
return jsonify(load_todos())
@app.route("/api/todos", methods=["POST"])
def create_todo():
"""创建待办"""
data = request.json
todos = load_todos()
new_todo = {
"id": str(len(todos) + 1),
"title": data.get("title", ""),
"completed": False
}
todos.append(new_todo)
save_todos(todos)
return jsonify(new_todo), 201
@app.route("/api/todos/<id>", methods=["PUT"])
def update_todo(id):
"""更新待办"""
data = request.json
todos = load_todos()
for todo in todos:
if todo["id"] == id:
todo["title"] = data.get("title", todo["title"])
todo["completed"] = data.get("completed", todo["completed"])
save_todos(todos)
return jsonify(todo)
return jsonify({"error": "Not found"}), 404
@app.route("/api/todos/<id>", methods=["DELETE"])
def delete_todo(id):
"""删除待办"""
todos = load_todos()
todos = [t for t in todos if t["id"] != id]
save_todos(todos)
return jsonify({"message": "Deleted"})
@app.route("/health", methods=["GET"])
def health():
"""健康检查接口"""
return jsonify({"status": "healthy"})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
txt
# requirements.txt
flask==3.0.0
gunicorn==21.2.0
3.2 编写 Dockerfile
Dockerfile 是 Docker 镜像的"配方",告诉 Docker 如何构建你的应用镜像。
dockerfile
# ====== Dockerfile ======
# 基础镜像(Python 运行环境)
FROM python:3.11-slim
# 设置工作目录
WORKDIR /app
# 设置环境变量
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
# 安装依赖(先复制依赖文件,利用 Docker 缓存层)
COPY requirements.txt .
# 安装 Python 依赖
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY app.py .
# 创建数据目录(用于持久化)
RUN mkdir -p /data
# 暴露端口
EXPOSE 5000
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:5000/health || exit 1
# 启动命令(生产用 Gunicorn)
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "--threads", "2", "app:app"]
3.3 构建镜像
bash
# 构建镜像(-t 指定镜像名和标签)
docker build -t todo-api:latest .
# 查看镜像
docker images | grep todo
# REPOSITORY TAG IMAGE ID SIZE
# todo-api latest abc123def456 150MB
# 给镜像打标签(推送到远程仓库前必须打标签)
docker tag todo-api:latest your-dockerhub-username/todo-api:v1.0.0
3.4 运行容器
bash
# 后台运行容器
docker run -d \
--name todo-api \
-p 5000:5000 \
-v $(pwd)/data:/data \
--restart unless-stopped \
todo-api:latest
# 查看运行状态
docker ps
# CONTAINER ID IMAGE STATUS PORTS NAMES
# xxxxxxxx todo-api Up 2 minutes 0.0.0.0:5000->5000/tcp todo-api
# 测试接口
curl http://localhost:5000/api/todos
# [{"id":"1","title":"学习Docker","completed":false},...]
curl http://localhost:5000/health
# {"status":"healthy"}
# 查看日志
docker logs -f todo-api
# 进入容器内部
docker exec -it todo-api /bin/bash
# 停止/删除
docker stop todo-api
docker rm todo-api
3.5 多阶段构建(生产优化)
dockerfile
# ====== 多阶段构建(优化镜像大小)======
# Stage 1: 构建阶段
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --prefix=/install -r requirements.txt
# Stage 2: 运行阶段(只复制需要的内容)
FROM python:3.11-slim
WORKDIR /app
# 只复制依赖包
COPY --from=builder /install /usr/local
# 只复制应用代码
COPY app.py .
RUN mkdir -p /data
EXPOSE 5000
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "2", "app:app"]
效果对比:
普通构建: ~500MB
多阶段构建:~150MB (减少 70%)
3.6 .dockerignore 文件
和 .gitignore 类似,忽略不需要的文件:
dockerignore
# 忽略以下文件,不打包进镜像
__pycache__/
*.pyc
*.pyo
*.log
.git/
.gitignore
data/
node_modules/
.env
.env.*
*.md
tests/
.pytest_cache/
4. 第二章:Docker Compose:本地多容器编排
4.1 场景:API + MySQL + Redis
生产环境的应用通常由多个服务组成,用 Docker Compose 可以一键启动。
yaml
# docker-compose.yml
version: '3.8'
services:
# ===== API 服务 =====
api:
build:
context: .
dockerfile: Dockerfile
container_name: todo-api
ports:
- "5000:5000"
volumes:
- ./data:/data
- ./app.py:/app/app.py # 热更新:修改代码后容器内自动生效
environment:
- FLASK_ENV=development
- DB_HOST=mysql
- DB_PORT=3306
- DB_USER=root
- DB_PASSWORD=secret123
- REDIS_HOST=redis
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_started
restart: unless-stopped
networks:
- todo-network
# ===== MySQL 数据库 =====
mysql:
image: mysql:8.0
container_name: todo-mysql
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: secret123
MYSQL_DATABASE: todoapp
volumes:
- mysql_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- todo-network
# ===== Redis 缓存 =====
redis:
image: redis:7-alpine
container_name: todo-redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
command: redis-server --appendonly yes
restart: unless-stopped
networks:
- todo-network
# ===== Nginx 反向代理(可选)=====
nginx:
image: nginx:alpine
container_name: todo-nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- api
restart: unless-stopped
networks:
- todo-network
volumes:
mysql_data:
redis_data:
networks:
todo-network:
driver: bridge
4.2 Docker Compose 常用命令
bash
# 启动所有服务(-d 后台运行)
docker-compose up -d
# 启动并重新构建镜像
docker-compose up -d --build
# 查看运行状态
docker-compose ps
# 查看日志(-f 实时跟踪)
docker-compose logs -f api
docker-compose logs -f # 所有服务日志
# 停止并删除容器
docker-compose down
# 停止并删除容器 + 数据卷(慎用,会删除数据)
docker-compose down -v
# 进入容器
docker-compose exec api /bin/bash
# 查看资源使用
docker-compose top
# 扩缩容(启动 3 个 API 实例)
docker-compose up -d --scale api=3
4.3 热更新开发技巧
yaml
# 开发环境使用 volumes 挂载源代码
services:
api:
volumes:
- ./app.py:/app/app.py:ro # 挂载但只读
- ./requirements.txt:/app/requirements.txt:ro
environment:
- FLASK_ENV=development
command: flask run --host=0.0.0.0 --reload
5. 第三章:Kubernetes入门:核心概念详解
5.1 K8s 核心对象一览
Kubernetes 核心对象关系图:
┌─────────────────────────────────────────────────────┐
│ Cluster(集群) │
│ ┌───────────────────────────────────────────────┐ │
│ │ Namespace(命名空间) │ │
│ │ ┌─────────────────────────────────────────┐ │ │
│ │ │ Deployment(部署 + 副本管理) │ │ │
│ │ │ ┌──────────┐ ┌──────────┐ ┌────────┐ │ │ │
│ │ │ │ Pod │ │ Pod │ │ Pod │ │ │ │
│ │ │ │ (nginx) │ │ (nginx) │ │(nginx) │ │ │ │
│ │ │ └────┬─────┘ └────┬─────┘ └───┬────┘ │ │ │
│ │ │ │ │ │ │ │ │
│ │ │ └───────────┼───────────┘ │ │ │
│ │ │ │ │ │ │
│ │ │ Service(服务发现) │ │ │
│ │ │ │ │ │ │
│ │ │ Ingress(外网入口) │ │ │
│ │ └─────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
5.2 核心对象解释
📦 Pod(豆荚)------ K8s 最小调度单位
yaml
# 一个 Pod 包含一个或多个容器
apiVersion: v1
kind: Pod
metadata:
name: todo-api-pod
labels:
app: todo-api
spec:
containers:
- name: api
image: todo-api:latest
ports:
- containerPort: 5000
resources:
limits:
memory: "256Mi"
cpu: "500m"
requests:
memory: "128Mi"
cpu: "250m"
livenessProbe:
httpGet:
path: /health
port: 5000
initialDelaySeconds: 10
periodSeconds: 15
readinessProbe:
httpGet:
path: /health
port: 5000
initialDelaySeconds: 5
periodSeconds: 10
🚀 Deployment(部署)------ 管理 Pod 的生命周期
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: todo-api-deployment
spec:
replicas: 3 # 3个副本,保证高可用
selector:
matchLabels:
app: todo-api
template:
metadata:
labels:
app: todo-api
spec:
containers:
- name: api
image: todo-api:latest
ports:
- containerPort: 5000
resources:
limits:
memory: "256Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 5000
readinessProbe:
httpGet:
path: /health
port: 5000
🔗 Service(服务)------ 负载均衡与服务发现
yaml
# ClusterIP:集群内部访问(默认)
apiVersion: v1
kind: Service
metadata:
name: todo-api-svc
spec:
type: ClusterIP
selector:
app: todo-api
ports:
- port: 80 # Service 端口
targetPort: 5000 # Pod 端口
yaml
# NodePort:通过节点IP:端口访问(测试用)
apiVersion: v1
kind: Service
metadata:
name: todo-api-svc-nodeport
spec:
type: NodePort
selector:
app: todo-api
ports:
- port: 80
targetPort: 5000
nodePort: 30007 # 节点端口 30000-32767
yaml
# LoadBalancer:云厂商负载均衡(生产推荐)
apiVersion: v1
kind: Service
metadata:
name: todo-api-svc-lb
spec:
type: LoadBalancer
selector:
app: todo-api
ports:
- port: 80
targetPort: 5000
6. 第四章:从零部署应用到 K8s 集群
6.1 将镜像推送到远程仓库
bash
# 1. 登录 Docker Hub
docker login
# 2. 打标签
docker tag todo-api:latest your-username/todo-api:v1.0.0
# 3. 推送
docker push your-username/todo-api:v1.0.0
# 或者推送到阿里云(国内更快)
docker login --username=your-username registry.cn-hangzhou.aliyuncs.com
docker tag todo-api:latest registry.cn-hangzhou.aliyuncs.com/your-namespace/todo-api:v1.0.0
docker push registry.cn-hangzhou.aliyuncs.com/your-namespace/todo-api:v1.0.0
6.2 编写完整的 K8s 部署清单
yaml
# k8s-deployment.yaml
---
# Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: todo-api
labels:
app: todo-api
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: todo-api
template:
metadata:
labels:
app: todo-api
spec:
containers:
- name: api
# ⬇️ 替换为你实际的镜像地址
image: your-username/todo-api:v1.0.0
imagePullPolicy: Always
ports:
- containerPort: 5000
name: http
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 5000
initialDelaySeconds: 15
periodSeconds: 20
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: 5000
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
env:
- name: FLASK_ENV
value: "production"
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
volumeMounts:
- name: app-config
mountPath: /app/config
readOnly: true
volumes:
- name: app-config
configMap:
name: todo-api-config
restartPolicy: Always
---
# Service
apiVersion: v1
kind: Service
metadata:
name: todo-api-svc
labels:
app: todo-api
spec:
type: ClusterIP
selector:
app: todo-api
ports:
- name: http
port: 80
targetPort: 5000
sessionAffinity: None
---
# HorizontalPodAutoscaler(自动扩缩容)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: todo-api-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: todo-api
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
6.3 部署到集群
bash
# 方式一:单文件部署
kubectl apply -f k8s-deployment.yaml
# 方式二:分目录部署(推荐)
# k8s/
# ├── namespace.yaml
# ├── configmap.yaml
# ├── deployment.yaml
# ├── service.yaml
# └── ingress.yaml
kubectl apply -f k8s/ -n todo-app
# 查看部署状态
kubectl get deployment -n todo-app
kubectl get pods -n todo-app
kubectl get svc -n todo-app
# 查看 Pod 日志
kubectl logs -f deployment/todo-api -n todo-app
# 查看 Pod 详细信息
kubectl describe pod todo-api-7d9f8b6c5-x2m4n -n todo-app
6.4 验证部署
bash
# 通过 Service 访问(创建临时测试 Pod)
kubectl run -it --rm test-client --image=busybox --restart=Never -- sh
# 在测试 Pod 内访问
wget -qO- http://todo-api-svc/api/todos
# [{"id":"1","title":"学习Docker","completed":false},...]
# 或者用 Port Forward 端口转发(本地调试用)
kubectl port-forward svc/todo-api-svc 8080:80 -n todo-app
# 然后浏览器访问 http://localhost:8080
7. 第五章:服务治理:Ingress、Service 与网络
7.1 Ingress:HTTP/HTTPS 入口
Ingress 是 K8s 的 HTTP/HTTPS 路由控制器:
yaml
# k8s-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: todo-api-ingress
annotations:
# Nginx Ingress Controller 注解
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "false"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
nginx.ingress.kubernetes.io/proxy-connect-timeout: "30"
nginx.ingress.kubernetes.io/proxy-read-timeout: "30"
nginx.ingress.kubernetes.io/proxy-send-timeout: "30"
spec:
ingressClassName: nginx
rules:
- host: todo-api.example.com # 生产环境替换为真实域名
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: todo-api-svc
port:
number: 80
# TLS 配置(生产环境必须启用)
# tls:
# - hosts:
# - todo-api.example.com
# secretName: todo-api-tls
7.2 完整的 K8s 命名空间隔离
yaml
# k8s-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: todo-app
labels:
name: todo-app
env: production
7.3 网络策略(安全隔离)
yaml
# k8s-networkpolicy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-network-policy
namespace: todo-app
spec:
podSelector:
matchLabels:
app: todo-api
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 5000
egress:
- to:
- podSelector:
matchLabels:
app: mysql
ports:
- protocol: TCP
port: 3306
8. 第六章:数据持久化:ConfigMap、Secret 与 PV/PVC
8.1 ConfigMap:应用配置
yaml
# k8s-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: todo-api-config
namespace: todo-app
data:
FLASK_ENV: "production"
LOG_LEVEL: "info"
MAX_TODOS: "1000"
# 配置文件也可以整体挂载
nginx.conf: |
server {
listen 80;
server_name _;
location / {
proxy_pass http://todo-api-svc;
}
}
8.2 Secret:敏感信息
yaml
# k8s-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: todo-api-secret
namespace: todo-app
type: Opaque
data:
# echo -n "your-password" | base64
DB_PASSWORD: eW91ci1wYXNzd29yZA==
API_KEY: eW91ci1hcGkta2V5LWJhc2U2NA==
# TLS 证书
tls.crt: LS0tLS1...
tls.key: LS0tLS1...
yaml
# Deployment 中引用 Secret
spec:
containers:
- name: api
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: todo-api-secret
key: DB_PASSWORD
8.3 PersistentVolumeClaim:持久化存储
yaml
# k8s-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: todo-data-pvc
namespace: todo-app
spec:
accessModes:
- ReadWriteOnce # 单节点读写
# - ReadOnlyMany # 多节点只读
# - ReadWriteMany # 多节点读写(需要 NFS/GlusterFS)
resources:
requests:
storage: 5Gi
storageClassName: standard # AWS: gp3 / Azure: managed-premium
yaml
# Deployment 中使用 PVC
spec:
containers:
- name: api
volumeMounts:
- name: todo-data
mountPath: /data
volumes:
- name: todo-data
persistentVolumeClaim:
claimName: todo-data-pvc
9. 第七章:高可用与弹性扩缩容
9.1 HPA 详细配置
yaml
# 基于 CPU 和内存的自动扩缩容
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: todo-api-hpa
namespace: todo-app
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: todo-api
minReplicas: 2
maxReplicas: 20
metrics:
# CPU 指标
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # CPU > 70% 时扩容
# 内存指标
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80 # 内存 > 80% 时扩容
# 扩容/缩容速率控制
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # 扩容稳定 5 分钟再缩容
policies:
- type: Percent
value: 50
periodSeconds: 60 # 每分钟最多缩容 50%
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 15 # 每 15 秒最多扩容 100%
bash
# 查看 HPA 状态
kubectl get hpa -n todo-app
# NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS
# todo-api-hpa Deployment/todo-api 45%/70% 2 20 8
# 手动触发扩缩容
kubectl scale deployment todo-api --replicas=10 -n todo-app
9.2 PodDisruptionBudget:滚动更新时保证可用
yaml
# 确保滚动更新时至少有 2 个 Pod 可用
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: todo-api-pdb
namespace: todo-app
spec:
minAvailable: 2
# 或者用百分比
# maxUnavailable: 25%
selector:
matchLabels:
app: todo-api
9.3 滚动更新与回滚
bash
# 滚动更新(修改镜像版本后自动触发)
kubectl set image deployment/todo-api api=your-username/todo-api:v1.1.0 -n todo-app
# 查看更新状态
kubectl rollout status deployment/todo-api -n todo-app
# 查看历史版本
kubectl rollout history deployment/todo-api -n todo-app
# 回滚到上一个版本
kubectl rollout undo deployment/todo-api -n todo-app
# 回滚到指定版本
kubectl rollout undo deployment/todo-api --to-revision=2 -n todo-app
10. 第八章:CI/CD实战:GitHub Actions 自动部署
10.1 整体流程
代码提交 → GitHub Actions 自动构建 → 构建镜像 → 推送到仓库
↓
K8s 集群拉取新镜像 → 滚动更新 → 验证 → 完成
10.2 GitHub Actions 工作流
yaml
# .github/workflows/deploy.yml
name: Build and Deploy to Kubernetes
on:
push:
branches:
- main # main 分支 push 时触发
pull_request:
branches:
- main
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# ===== Job 1: 构建和推送镜像 =====
build:
runs-on: ubuntu-latest
outputs:
image: ${{ steps.build.outputs.image }}
tag: ${{ steps.build.outputs.tag }}
steps:
- name: Checkout 代码
uses: actions/checkout@v4
- name: 设置 QEMU(多平台构建)
uses: docker/setup-qemu-action@v3
- name: 设置 Docker Buildx
uses: docker/setup-buildx-action@v3
- name: 登录 Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: 提取元数据(构建标签)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ github.repository }}
tags: |
type=ref,event=branch
type=sha,prefix=
type=raw,value=latest
- name: 构建镜像
id: build
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: 输出镜像信息
id: build
run: |
echo "image=${{ env.REGISTRY }}/${{ github.repository }}" >> $GITHUB_OUTPUT
echo "tag=$(echo ${{ steps.meta.outputs.tags }} | awk '{print $1}')" >> $GITHUB_OUTPUT
# ===== Job 2: 部署到 Kubernetes =====
deploy:
needs: build # 等待 build job 完成
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' # 只在 main 分支部署
steps:
- name: Checkout 代码
uses: actions/checkout@v4
- name: 设置 kubectl
uses: azure/setup-kubectl@v4
with:
version: 'v1.28.0'
- name: 配置 Kubeconfig
uses: azure/k8s-set-context@v3
with:
kubeconfig: ${{ secrets.KUBE_CONFIG }}
context: ${{ secrets.K8S_CLUSTER_NAME }}
- name: 更新镜像版本
run: |
# 替换 Deployment 中的镜像版本
sed -i 's|image: .*todo-api:.*|image: ${{ needs.build.outputs.image }}:${{ needs.build.outputs.tag }}|g' k8s/deployment.yaml
cat k8s/deployment.yaml | grep image
- name: 部署到 Kubernetes
run: |
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/configmap.yaml
kubectl apply -f k8s/secret.yaml
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml
kubectl apply -f k8s/ingress.yaml
kubectl apply -f k8s/hpa.yaml
- name: 等待滚动更新完成
run: |
kubectl rollout status deployment/todo-api -n todo-app --timeout=300s
- name: 验证部署
run: |
kubectl get pods -n todo-app
kubectl get svc -n todo-app
kubectl get hpa -n todo-app
10.3 配置 GitHub Secrets
在 GitHub 仓库 Settings → Secrets and variables → Actions 中配置:
KUBE_CONFIG → kubeconfig 文件内容(base64 编码)
K8S_CLUSTER_NAME → 集群名称,如 production
REGISTRY_PASSWORD → 镜像仓库密码(如需)
11. 实战完整项目代码
todo-microservice/
├── app.py # Flask 应用主文件
├── requirements.txt # Python 依赖
├── Dockerfile # Docker 构建文件
├── Dockerfile.multi # 多阶段构建
├── .dockerignore # Docker 忽略文件
├── docker-compose.yml # Docker Compose 本地编排
├── docker-compose.prod.yml # 生产环境编排
├── nginx.conf # Nginx 反向代理配置
├── k8s/
│ ├── namespace.yaml # 命名空间
│ ├── configmap.yaml # 配置
│ ├── secret.yaml # 密钥
│ ├── deployment.yaml # 部署
│ ├── service.yaml # 服务
│ ├── ingress.yaml # 入口
│ ├── hpa.yaml # 自动扩缩容
│ ├── pdb.yaml # Pod 中断预算
│ └── pvc.yaml # 持久化卷
├── tests/
│ ├── test_api.py # API 测试
│ └── test_docker.py # Docker 测试
└── .github/
└── workflows/
└── deploy.yml # CI/CD 流水线
12. 常见问题与排错指南
❌ Pod 一直处于 Pending 状态
bash
kubectl describe pod <pod-name>
# 常见原因:
# 1. 资源不足(CPU/内存不够)→ 降低 requests
# 2. PVC 未绑定 → 检查 storageclass 是否存在
# 3. 节点选择器不匹配 → 检查 nodeSelector/affinity
❌ ImagePullBackOff(拉取镜像失败)
bash
# 原因1:镜像地址错误
# 解决:检查镜像地址是否正确
# 原因2:未登录私有仓库
# 解决:创建 imagePullSecret
kubectl create secret docker-registry regcred \
--docker-server=https://index.docker.io/v1/ \
--docker-username=your-username \
--docker-password=your-password \
--docker-email=your-email
# Deployment 中引用
spec:
imagePullSecrets:
- name: regcred
❌ CrashLoopBackOff(容器启动后崩溃)
bash
# 查看日志
kubectl logs <pod-name> --previous
# 常见原因:
# 1. 应用启动慢(livenessProbe 太激进)→ 调大 initialDelaySeconds
# 2. 配置错误(环境变量不对)→ 检查 ConfigMap/Secret
# 3. 依赖服务未就绪 → 添加 readinessProbe + depends_on
❌ Service 无法访问 Pod
bash
# 检查标签匹配
kubectl get pods --show-labels
kubectl describe svc <svc-name>
# Endpoints 应该不为空
# 检查网络策略
kubectl get networkpolicy -n todo-app
❌ Ingress 不生效
bash
# 检查 Ingress Controller 是否安装
kubectl get pods -n ingress-nginx
kubectl get ingressclass
# 检查 Ingress 配置
kubectl describe ingress <ingress-name>
# 查看 Events 找错误信息
❌ HPA 不工作
bash
# 检查 metrics-server 是否安装(必需)
kubectl get pods -n kube-system | grep metrics
# 如果没有:
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# 查看 HPA 详细信息
kubectl describe hpa <hpa-name>
总结:Docker + K8s 部署全流程速查
1. 写 Dockerfile → 构建镜像
2. docker build → 本地测试镜像
3. docker-compose → 本地多容器联调
4. 推送镜像 → 远程仓库
5. 写 K8s YAML → Deployment + Service + HPA
6. kubectl apply → 部署到集群
7. GitHub Actions → 自动化 CI/CD
从本地到生产,完整链路打通!