容器化与编排技术总结
概述
本文档是对容器化与编排技术的系统性总结,涵盖 Docker 核心与进阶、Docker Compose 生产实践、Kubernetes 核心概念与运维、Helm 包管理以及 Helmfile/Kustomize 选型对比。旨在帮助开发者建立完整的容器化知识体系,从开发到生产运维形成闭环理解。
一、Docker 核心回顾与进阶
1.1 Docker 生命周期:镜像 -> 容器 -> 仓库
Docker 的核心工作流围绕三个基本要素展开:镜像(Image)、容器(Container) 和 仓库(Registry)。
基本工作流:
# 1. 从 Dockerfile 构建镜像
docker build -t my-app:1.0.0 .
# 2. 将镜像推送到仓库
docker tag my-app:1.0.0 registry.example.com/my-app:1.0.0
docker push registry.example.com/my-app:1.0.0
# 3. 从仓库拉取镜像并运行容器
docker pull registry.example.com/my-app:1.0.0
docker run -d --name my-app -p 8080:8080 registry.example.com/my-app:1.0.0镜像构建原理:
Docker 镜像由多层只读层(Layer)堆叠而成,每一层对应 Dockerfile 中的一条指令。层被 Docker Daemon 缓存,当层内容未变化时直接复用缓存,加速构建。
Dockerfile 指令 镜像层
FROM base --> Layer 0: base OS
RUN apt install --> Layer 1: apt packages
COPY app.jar --> Layer 2: application
CMD ["java"] --> Layer 3: metadata (不占空间)
|
容器运行时在最上层增加可写层(容器层)容器的本质:
容器是运行在宿主机上的隔离进程,通过以下技术实现隔离:
- Namespace:隔离进程、网络、文件系统、用户、主机名等视图
- Cgroups:限制 CPU、内存、磁盘 IO、网络带宽等资源使用
- UnionFS(联合文件系统):将镜像多层叠加为统一视图,容器层记录变更
仓库的交互流程:
docker login --> 认证(生成 ~/.docker/config.json)
docker push --> 镜像分层上传(只推送不存在的层)
docker pull --> 镜像分层下载(仅下载缺失的层)镜像仓库支持 OCI(Open Container Initiative)标准格式,Harbor、Docker Hub、AWS ECR、阿里云 ACR 等均遵循该标准。
1.2 多阶段构建最佳实践
多阶段构建允许在一个 Dockerfile 中使用多个 FROM 语句,每个 FROM 开启一个新的构建阶段。只有最后一个阶段的产物保留在最终镜像中。
Java / Spring Boot 典型 Dockerfile
# 第一阶段:Maven 编译
FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests -B
# 第二阶段:使用 Spring Boot layertools 分离依赖(优化缓存)
FROM eclipse-temurin:21-jre-alpine AS extractor
WORKDIR /app
COPY --from=builder /build/target/*.jar app.jar
RUN java -Djarmode=layertools -jar app.jar extract
# 第三阶段:最终运行镜像
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# 分层复制,利用 Docker 层缓存
COPY --from=extractor /app/dependencies/ ./
COPY --from=extractor /app/spring-boot-loader/ ./
COPY --from=extractor /app/snapshot-dependencies/ ./
COPY --from=extractor /app/application/ ./
USER appuser
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
CMD curl -sf http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["java", "-jar", "app.jar"]
CMD ["--spring.profiles.active=prod"]GraalVM 原生编译 Dockerfile
# 第一阶段:GraalVM 原生编译
FROM ghcr.io/graalvm/graalvm-ce:ol8-java17 AS builder
WORKDIR /build
COPY . .
RUN gu install native-image
RUN ./mvnw -Pnative native:compile -DskipTests
# 第二阶段:scratch 空基础镜像运行(极致精简)
FROM scratch
WORKDIR /app
COPY --from=builder /build/target/application .
EXPOSE 8080
ENTRYPOINT ["/app/application"]前端 Nginx 部署 Dockerfile
# 第一阶段:Node.js 构建
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# 第二阶段:Nginx 运行
FROM nginx:1.25-alpine
# 从 builder 阶段复制构建产物到 Nginx 静态目录
COPY --from=builder /app/dist /usr/share/nginx/html
# 自定义 Nginx 配置
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD wget -qO- http://localhost:80/ || exit 1Node.js 应用 Dockerfile
# 第一阶段:安装依赖并构建
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# 第二阶段:运行(使用更小的基础镜像)
FROM node:20-alpine
WORKDIR /app
# 复制构建产物和 node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json .
# 非 root 运行
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
EXPOSE 3000
CMD ["node", "dist/main.js"]Python 应用 Dockerfile
# 第一阶段:安装依赖
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dirs -r requirements.txt
# 第二阶段:运行
FROM python:3.12-slim
WORKDIR /app
# 从 builder 阶段复制用户安装的包
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]1.3 镜像优化实践
基础镜像选型对比
| 镜像变体 | 基础大小 | libc | 包管理器 | 兼容性 | 适用场景 |
|---|---|---|---|---|---|
alpine | ~5MB | musl | apk | 部分原生库需适配 | 追求极致体积 |
slim (Debian) | ~30MB | glibc | apt | 兼容性好 | 平衡体积与兼容性 |
distroless | ~2MB (static) / ~25MB (java) | glibc | 无 | 仅运行应用 | 安全优先的生产镜像 |
nanoserver (Windows) | ~200MB | - | - | Windows 容器 | Windows 容器化 |
scratch | 0MB | - | - | 静态编译 | Go/C 静态二进制 |
| 标准版 (Debian/Ubuntu) | ~100MB+ | glibc | apt | 最好 | 需要完整工具链 |
Alpine 注意事项:
- 使用 musl libc 而非 glibc,某些原生扩展(如
psycopg2、cffi)需要编译 musl 版本 - DNS 解析行为与 glibc 不同,偶尔出现网络连接问题
- 调试时缺少 shell 工具(bash、curl、ping),需手动安装
Distroless 优势:
- 无 shell、无包管理器、无系统工具,攻击面极小
- 使用
gcr.io/distroless/java21-debian12等基础镜像 - 仅能运行应用,无法 ssh 进入容器,安全性高
- 调试可通过
kubectl debug注入临时容器
# Distroless Java 示例
FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests
FROM gcr.io/distroless/java21-debian12
WORKDIR /app
COPY --from=builder /build/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]镜像优化综合实践
1. 多阶段构建减少最终镜像层数
构建阶段包含完整的 SDK 和构建工具,运行阶段仅包含 JRE 和产物,最终镜像体积可减少 60%-80%。
2. 依赖缓存清理
# apt 缓存清理
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
# apk 缓存清理
RUN apk add --no-cache curl
# pip 缓存清理
RUN pip install --no-cache-dir -r requirements.txt
# npm 缓存清理
RUN npm ci --only=production && npm cache clean --force3. 分离构建产物
# Spring Boot 分层构建(利用 layertools)
# 将依赖层与应用代码层分离,应用代码变化不影响依赖层缓存
COPY --from=extractor /app/dependencies/ ./
COPY --from=extractor /app/spring-boot-loader/ ./
COPY --from=extractor /app/snapshot-dependencies/ ./
COPY --from=extractor /app/application/ ./4. COPY 顺序利用缓存
# 正确的顺序:先复制不常变化的文件
COPY package.json package-lock.json ./ # 依赖定义
RUN npm ci # 安装依赖(仅 package.json 变化时重跑)
COPY . . # 复制源码(常变化)
RUN npm run build # 构建5. HEALTHCHECK 配置
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
CMD curl -sf http://localhost:8080/health || exit 1参数说明:
| 参数 | 默认值 | 说明 |
|---|---|---|
--interval | 30s | 检查间隔 |
--timeout | 30s | 单次检查超时 |
--retries | 3 | 连续失败次数判为不健康 |
--start-period | 0s | 启动宽限期,期间失败不计入重试 |
6. 非 root 用户运行
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser在 Alpine 中使用 adduser/addgroup,在 Debian 中使用 useradd/groupadd。
1.4 Docker 网络进阶
网络驱动对比
| 驱动 | 作用域 | 容器间通信 | 外部访问 | 适用场景 |
|---|---|---|---|---|
| Bridge | 单机 | 同一网络下容器名互访 | 端口映射 -p | 默认模式,开发测试 |
| Host | 单机 | 共享宿主机网络栈 | 直接使用宿主机端口 | 追求网络性能(如 Redis) |
| Overlay | 跨主机 | 集群内容器互访 | 结合 ingress 网络 | Docker Swarm / K8s |
| Macvlan | 单机 | 容器拥有独立 MAC 地址 | 直接接入物理网络 | 遗留应用直接使用物理 IP |
| IPvlan | 单机 | 容器共享 MAC 但独立 IP | 同 Macvlan 但更高效 | 大规模容器部署 |
Bridge 网络工作原理:
宿主机
+----------------------------------------------------+
| docker0 (虚拟网桥, 172.17.0.1/16) |
| | |
| +--- veth0 ---> eth0@container1 (172.17.0.2) |
| +--- veth1 ---> eth0@container2 (172.17.0.3) |
| |
| iptables NAT 规则: |
| 宿主机 8080 -> 172.17.0.2:80 |
+----------------------------------------------------+容器间通信方式:
# 1. 默认 bridge 网络(通过 IP 通信)
docker network create my-network
docker run -d --name app1 --network my-network my-app
docker run -d --name app2 --network my-network my-app
# app1 可通过 app2 容器名访问(Docker 内置 DNS)
# 2. 自定义网络(推荐)
# 自定义 bridge 网络提供了:
# - 自动 DNS 解析(容器名 -> IP)
# - 网络隔离(不同网络无法直接通信)
# - 动态加入/退出
# 3. 容器连接到多个网络
docker network connect frontend-network app1
docker network connect backend-network app1CNM(Container Network Model)与 libnetwork:
Docker 的网络实现基于 CNM(Container Network Model),由 libnetwork 库实现:
- Sandbox:容器的网络栈(命名空间),包含网卡、路由表、DNS 配置
- Endpoint:Sandbox 与 Network 的连接点(veth 对的一端)
- Network:一组可以相互通信的 Endpoint 集合
# 查看容器的网络配置
docker inspect app1 --format '{{json .NetworkSettings}}'
# 查看网络详情
docker network inspect my-network网络隔离原理:
# docker-compose 中网络隔离示例
services:
frontend:
networks:
- frontend
- backend
backend:
networks:
- backend
db:
networks:
- backend # db 不接入 frontend,前端无法直接访问数据库
networks:
frontend:
backend:1.5 数据持久化
三种挂载方式对比
| 特性 | Volume(数据卷) | Bind Mount(绑定挂载) | tmpfs Mount(临时挂载) |
|---|---|---|---|
| 存储位置 | /var/lib/docker/volumes/ | 宿主机任意路径 | 内存(RAM) |
| 生命周期 | Docker 管理,独立于容器 | 依赖宿主机路径存在 | 容器停止后数据消失 |
| 备份迁移 | 通过 docker volume 命令 | 手动处理文件 | 不支持持久化 |
| 跨主机共享 | 需 Volume Driver(如 NFS) | 需共享文件系统 | 不支持 |
| 性能 | 与 Bind Mount 接近 | I/O 直通,性能高 | 极高性能 |
| 适用场景 | 数据库、配置文件持久化 | 开发热重载、日志输出 | 缓存、敏感信息(不落盘) |
Volume 生命周期管理:
# 创建数据卷
docker volume create --name app-data --driver local
docker volume create --name nfs-share --driver local \
--opt type=nfs \
--opt o=addr=192.168.1.100,rw \
--opt device=:/exported/path
# 使用数据卷
docker run -d -v app-data:/app/data --name my-app my-image
# 查看数据卷详情
docker volume inspect app-data
# 清理未使用的数据卷
docker volume prune
# 备份数据卷
docker run --rm -v app-data:/source -v $(pwd):/backup alpine \
tar czf /backup/app-data-backup.tar.gz -C /source .Bind Mount 主机路径依赖:
# 开发环境使用 bind mount 实现热重载
docker run -d \
-v /host/project/src:/app/src \
-v /host/project/config:/app/config:ro \
my-app
# 日志输出到宿主机
docker run -d \
-v /var/log/my-app:/app/logs \
my-appVolume Driver NFS 共享存储:
# 使用 NFS volume driver 实现跨主机共享
docker volume create \
--driver local \
--opt type=nfs \
--opt o=addr=192.168.1.100,rw,nfsvers=4 \
--opt device=:/data/shared \
shared-volume
docker run -d -v shared-volume:/app/data my-app数据容器模式(已过时,但仍在使用):
# 数据容器模式(创建专门的数据容器,通过 --volumes-from 共享)
version: "3.9"
services:
data-container:
image: alpine
volumes:
- /var/lib/mysql
- /app/uploads
command: "echo Data Container"
app:
image: my-app
volumes_from:
- data-container推荐使用命名 Docker Volume 替代数据容器模式,Volume 语义更清晰,独立于容器生命周期。
1.6 Docker 安全
容器沙箱隔离
Docker 容器的安全性基于 Linux 内核的多种安全机制:
Namespace 隔离:
| Namespace 类型 | 隔离内容 | 系统调用参数 |
|---|---|---|
| PID | 进程号 | CLONE_NEWPID |
| Network | 网络栈(网卡、路由、iptables) | CLONE_NEWNET |
| Mount | 文件系统挂载点 | CLONE_NEWNS |
| UTS | 主机名与域名 | CLONE_NEWUTS |
| IPC | System V IPC / POSIX 消息队列 | CLONE_NEWIPC |
| User | 用户与用户组 ID 映射 | CLONE_NEWUSER |
| Cgroup | cgroup 根目录 | CLONE_NEWCGROUP |
Seccomp(安全计算模式):
Seccomp 限制容器可以使用的系统调用,Docker 默认提供一个白名单策略(约 300 个 syscall 允许,其他全部拒绝)。
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": ["SCMP_ARCH_X86_64"],
"syscalls": [
{
"names": ["accept", "bind", "connect", "listen", "read", "write", "open", "close", "exit", "exit_group"],
"action": "SCMP_ACT_ALLOW"
}
]
}# 运行容器时使用自定义 seccomp 策略
docker run --security-opt seccomp=./custom-profile.json my-app
# 禁用 seccomp(不推荐)
docker run --security-opt seccomp=unconfined my-appAppArmor / SELinux:
- AppArmor:使用路径名进行强制访问控制(MAC),Ubuntu 默认使用
- SELinux:使用安全上下文(标签)进行 MAC,CentOS/RHEL 默认使用
# AppArmor
docker run --security-opt apparmor=my-app-profile my-app
# SELinux
docker run --security-opt label:type:container_t my-appReadonly Rootfs:
将容器根文件系统设置为只读,防止恶意写入:
FROM alpine
RUN mkdir /data
VOLUME /datadocker run --read-only --tmpfs /tmp --tmpfs /var/run my-appCapability 最小化:
Docker 默认删除容器的所有特权能力,只保留白名单中的能力。生产环境应进一步裁剪:
# 查看容器默认能力
docker run --rm alpine grep CapPrm /proc/1/status
# 裁剪能力(删除 NET_RAW 防止容器内修改网络)
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE my-app
# 特权模式(仅调试时使用,绝对不要用于生产)
docker run --privileged my-app| 能力 | 说明 | 是否保留 |
|---|---|---|
| CHOWN | 修改文件所有者 | 需保留 |
| DAC_OVERRIDE | 绕过文件权限检查 | 需保留 |
| FOWNER | 绕过文件所有者检查 | 需保留 |
| KILL | 发送 SIGKILL | 需保留 |
| NET_BIND_SERVICE | 绑定 <1024 端口 | 按需保留 |
| NET_RAW | 原始套接字 | 建议删除 |
| SYS_ADMIN | 系统管理 | 建议删除 |
| NET_ADMIN | 网络管理 | 建议删除 |
安全扫描 Trivy:
# 安装 Trivy
# 下载二进制或使用 Docker 镜像
# 扫描镜像漏洞
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy image my-app:1.0.0
# 扫描本地文件系统
trivy filesystem --severity=CRITICAL,HIGH /path/to/project
# 生成 SBOM(软件物料清单)
trivy image --format=spdx --output=my-app.spdx my-app:1.0.0Docker Bench Security:
Docker Bench Security 是一个用于检查 Docker 部署是否符合 CIS Docker Benchmark 安全标准的脚本:
# 运行 Docker Bench Security
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /etc:/host/etc:ro \
docker/docker-bench-security检查项包括:
| 检查类别 | 内容 |
|---|---|
| 主机配置 | 内核安全、Docker daemon 用户 |
| Docker daemon 配置 | TLS 加密、日志级别、仓库证书 |
| 容器运行时 | 权限、能力、只读文件系统 |
| 镜像构建 | 安全扫描、非 root 用户 |
| Docker Swarm | 加密通信、自动锁 |
二、Docker Compose 进阶
2.1 Compose 生产配置
Profiles(按环境启用服务)
Compose 3.8+ 支持 profiles,通过 profile 控制哪些服务在特定环境启动:
version: "3.9"
services:
app:
image: my-app:latest
ports:
- "8080:8080"
# 仅开发环境启用的服务
mailhog:
image: mailhog/mailhog
profiles:
- dev
ports:
- "8025:8025"
# 仅测试环境启用的服务
integration-test:
image: my-integration-test:latest
profiles:
- test
depends_on:
- app
# 生产监控服务
prometheus:
image: prom/prometheus
profiles:
- prod# 启动所有服务(不含 profile 服务)
docker compose up -d
# 启动 dev profile 服务
docker compose --profile dev up -d
# 启动多个 profile
docker compose --profile dev --profile test up -dExtends(服务继承)
extends 支持从其他服务或外部文件继承配置,减少重复:
# common.yml
services:
common-app:
image: my-app:latest
restart: always
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 5s
retries: 3# docker-compose.yml
version: "3.9"
services:
user-service:
extends:
file: common.yml
service: common-app
environment:
- SPRING_PROFILES_ACTIVE=prod
- DB_URL=jdbc:mysql://db:3306/user_db
ports:
- "8081:8080"
order-service:
extends:
file: common.yml
service: common-app
environment:
- SPRING_PROFILES_ACTIVE=prod
- DB_URL=jdbc:mysql://db:3306/order_db
ports:
- "8082:8080"注意:
extends在 Compose V2 中推荐使用include替代(Docker Compose V2.20+),include更灵活且支持合并多个文件。
Deploy Resources(资源限制)
version: "3.9"
services:
app:
image: my-app:latest
deploy:
resources:
limits:
cpus: "1.0"
memory: "512M"
reservations:
cpus: "0.25"
memory: "128M"
# Docker Compose 非 Swarm 模式下,直接使用以下配置
mem_limit: 512m
mem_reservation: 128m
cpus: "1.0"Restart Policy
services:
app:
image: my-app:latest
restart: always # always / unless-stopped / on-failure / no
batch-worker:
image: my-worker:latest
restart: on-failure:3 # 最多重启 3 次
deploy:
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
window: 120sHealthcheck 依赖检查
version: "3.9"
services:
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MYSQL_DATABASE: mydb
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
app:
image: my-app:latest
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
ports:
- "8080:8080"depends_on 的 condition 支持三种值:
| 值 | 说明 |
|---|---|
service_started | 等待服务启动(默认行为) |
service_healthy | 等待健康检查通过 |
service_completed_successfully | 等待服务成功退出(一次性任务) |
2.2 多环境管理
Override 文件机制
Docker Compose 支持多个 -f 文件合并,默认自动加载 docker-compose.override.yml:
# docker-compose.yml(基础配置,所有环境通用)
version: "3.9"
services:
app:
image: my-app:latest
environment:
- SPRING_PROFILES_ACTIVE=prod
volumes:
- app-logs:/app/logs# docker-compose.override.yml(开发环境覆盖,自动加载)
version: "3.9"
services:
app:
# 开发环境使用 build 替代 image
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "8080:8080"
- "5005:5005" # JVM 远程调试
environment:
- SPRING_PROFILES_ACTIVE=dev
volumes:
- ./src:/app/src:ro # 源码热重载
- ./config:/app/config:ro# docker-compose.prod.yml(生产环境配置)
version: "3.9"
services:
app:
image: registry.example.com/my-app:${APP_VERSION:-latest}
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- DB_URL=${DB_URL}
- DB_USERNAME=${DB_USERNAME}
- DB_PASSWORD=${DB_PASSWORD}
deploy:
resources:
limits:
memory: "1G"
reservations:
memory: "512M"
restart: always
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"多环境启动命令:
# 开发环境(override.yml 自动生效)
docker compose up -d
# 生产环境(显式指定 -f 文件)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# 测试环境
docker compose -f docker-compose.yml -f docker-compose.test.yml up -d
# 构建并启动
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build.env 环境变量文件
# .env 文件(Compose 自动加载)
APP_VERSION=1.2.0
DB_URL=jdbc:mysql://db:3306/mydb
DB_USERNAME=app_user
DB_PASSWORD=${DB_PASSWORD}
LOG_LEVEL=INFO# docker-compose.yml 中使用
services:
app:
image: my-app:${APP_VERSION:-latest}
environment:
- DB_URL=${DB_URL}
- DB_USERNAME=${DB_USERNAME}
- DB_PASSWORD=${DB_PASSWORD:-default_pass}
- PROFILE=${PROFILE:-dev}变量替换语法:
| 语法 | 说明 |
|---|---|
${VAR} | 使用 VAR 值,未定义则警告 |
${VAR:-default} | 未定义时使用 default |
${VAR:?error} | 未定义时报错 "error" |
${VAR:+replacement} | 定义时使用 replacement,否则空 |
2.3 Compose 与服务发现
自动 DNS 解析
Compose 为自定义网络中的每个容器创建 DNS 记录,容器名作为主机名可互相解析:
version: "3.9"
services:
web:
image: nginx:alpine
depends_on:
- api
api:
image: my-api:latest
environment:
- DB_HOST=db # 自动解析到 db 容器 IP
- REDIS_HOST=redis
db:
image: mysql:8.0
redis:
image: redis:7-alpine容器名访问: 在 api 容器中,ping db 会自动解析为 db 容器的 IP 地址。Compose 使用 Docker 内置 DNS resolver,监听在 127.0.0.11:53。
Links vs Network
# 旧方式(links,已过时)
services:
web:
image: nginx
links:
- db:database # 创建别名,web 可通过 hostname "database" 访问 db
# 新方式(自定义 network,推荐)
services:
web:
image: nginx
networks:
- app-network
db:
image: mysql:8.0
networks:
- app-network
networks:
app-network:
driver: bridgelinks 的限制:
- 仅在单机 Compose 项目内有效
- 创建了环境变量(如
DB_PORT_3306_TCP_ADDR),增加安全隐患 - 不支持跨主机
network 的优势:
- 支持跨主机(使用 overlay 驱动)
- 自动 DNS 解析,无需别名声明
- 更好的网络隔离控制
- 支持网络级别的安全策略
Scale 扩展与负载均衡
version: "3.9"
services:
web:
image: nginx:alpine
ports:
- "8080"
networks:
- app-network
app:
image: my-app:latest
networks:
- app-network
networks:
app-network:# 扩展 web 服务到 3 个实例
docker compose up -d --scale web=3
# 扩展多个服务
docker compose up -d --scale app=5 --scale web=2Nginx 反向代理负载均衡配置(手动实现):
# nginx.conf
upstream app_backend {
server app:8080;
# Docker DNS 会自动轮询到多个 app 容器
}
server {
listen 80;
location / {
proxy_pass http://app_backend;
}
}注意:
docker compose up --scale不提供内置负载均衡,通常需要前面加一层 Nginx/Traefik 反向代理,或使用 Docker Swarm 模式的内置负载均衡。
使用 Traefik 做自动负载均衡:
version: "3.9"
services:
traefik:
image: traefik:v3.0
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
ports:
- "80:80"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
app:
image: my-app:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`api.example.com`)"
- "traefik.http.services.app.loadbalancer.server.port=8080"三、Kubernetes 核心回顾
3.1 K8s 资源对象层次
Kubernetes 的资源对象体系构成了声明式基础设施的核心,以下是资源对象的分层全景:
Namespace(逻辑隔离空间)
|
+-- Workloads(工作负载)
| +-- Pod(最小调度单元)
| +-- ReplicaSet(Pod 副本管理)
| +-- Deployment(声明式更新,管理 ReplicaSet)
| +-- StatefulSet(有状态应用,稳定的网络标识和存储)
| +-- DaemonSet(每个节点运行一个 Pod)
| +-- Job(一次性任务)
| +-- CronJob(定时任务)
|
+-- Service & Networking(服务发现与网络)
| +-- Service(抽象 Pod 访问入口)
| | +-- ClusterIP(集群内访问)
| | +-- NodePort(节点端口暴露)
| | +-- LoadBalancer(云负载均衡器)
| | +-- ExternalName(外部服务别名)
| +-- Ingress(七层路由,HTTP/HTTPS 进入集群)
| +-- IngressClass(Ingress 控制器分类)
| +-- EndpointSlice(Service 后端 Pod 端点)
| +-- NetworkPolicy(网络访问策略)
|
+-- Configuration & Storage(配置与存储)
| +-- ConfigMap(非敏感配置)
| +-- Secret(敏感配置,Base64 编码 / 加密)
| +-- PersistentVolume(持久化存储资源)
| +-- PersistentVolumeClaim(存储资源请求)
| +-- StorageClass(动态存储供给模板)
| +-- CSI Driver(容器存储接口驱动)
|
+-- Security(安全)
| +-- ServiceAccount(Pod 身份标识)
| +-- Role / ClusterRole(权限集合)
| +-- RoleBinding / ClusterRoleBinding(权限绑定)
| +-- PodSecurityPolicy(已弃用,替代为 Pod Security Standards)
| +-- PodSecurityAdmission(PSA,内置准入控制器)
|
+-- Governance & Limits(治理与限制)
| +-- ResourceQuota(命名空间资源配额)
| +-- LimitRange(默认资源限制范围)
| +-- HorizontalPodAutoscaler(HPA,Pod 水平自动扩缩容)
| +-- VerticalPodAutoscaler(VPA,Pod 垂直自动扩缩容)
| +-- PodDisruptionBudget(PDB,Pod disruptions 预算)
| +-- PriorityClass(调度优先级)
|
+-- Cluster Infrastructure(集群基础设施)
| +-- Node(集群工作节点)
| +-- Namespace(命名空间)
| +-- APIService(API 聚合)
|
+-- Extensibility(扩展)
+-- CustomResourceDefinition(CRD,自定义资源类型)
+-- Operator(CRD + Controller)
+-- MutatingWebhookConfiguration(可变准入 Webhook)
+-- ValidatingWebhookConfiguration(校验准入 Webhook)核心工作负载对比:
| 资源 | Pod 标识 | 存储 | 更新策略 | 适用场景 |
|---|---|---|---|---|
| Deployment | 随机名称 | 无状态(临时) | 滚动更新/回滚 | 无状态应用(API、Web) |
| StatefulSet | 有序名称(-0, -1, -2) | 每 Pod 独立 PVC | 顺序滚动更新 | 数据库、消息队列 |
| DaemonSet | 每个节点一个 | 可选共享存储 | 节点感知更新 | 日志采集、监控 Agent |
| Job | 任务完成即退出 | 无需持久化 | 重试/并行 | 批处理、数据迁移 |
| CronJob | 定时触发的 Job | 无需持久化 | 历史限制 | 定时备份、清理 |
3.2 调度器
Kubernetes 调度器(kube-scheduler)负责将 Pod 绑定到合适的 Node 上,调度过程分为 过滤(Predicates) 和 打分(Priorities) 两个阶段。
调度两阶段
第一阶段:Predicates(预选)
过滤不满足 Pod 需求的节点。常见的预选策略:
| 预选策略 | 说明 |
|---|---|
| PodFitsResources | 节点剩余资源 >= Pod 请求资源 |
| PodFitsHostPorts | 节点上请求的端口未被占用 |
| PodMatchNodeSelector | 节点标签匹配 Pod 的 nodeSelector |
| PodToleratesNodeTaints | Pod 容忍节点的所有 Taints |
| CheckNodeMemoryPressure | 节点内存压力时,不调度 QoS 类为 BestEffort 的 Pod |
| CheckNodeDiskPressure | 节点磁盘压力时,不调度新 Pod |
| CheckNodePIDPressure | 节点 PID 压力时,不调度新 Pod |
第二阶段:Priorities(优选)
对通过预选的节点进行打分排序,选择最优节点。常见的优选策略:
| 优选策略 | 权重 | 说明 |
|---|---|---|
| LeastRequestedPriority | 1 | 优先调度到资源利用率低的节点(分散负载) |
| MostRequestedPriority | 1 | 优先调度到资源利用率高的节点(集中负载) |
| BalancedResourceAllocation | 1 | 优先调度到 CPU/内存利用率最均衡的节点 |
| NodeAffinityPriority | 2 | 节点亲和性匹配度 |
| TaintTolerationPriority | 1 | 根据 Pod 对节点 Taint 的容忍程度打分 |
| ImageLocalityPriority | 1 | 已有 Pod 所需镜像的节点优先 |
节点亲和性(nodeAffinity)
apiVersion: v1
kind: Pod
metadata:
name: with-node-affinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role.kubernetes.io/gpu
operator: Exists
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
preference:
matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- us-east-1a
- us-east-1b
- weight: 20
preference:
matchExpressions:
- key: disk-type
operator: In
values:
- ssdrequiredDuringSchedulingIgnoredDuringExecution:硬性要求,必须满足才能调度。IgnoredDuringExecution 表示运行时如果节点标签变化,已运行的 Pod 不会受到影响。
preferredDuringSchedulingIgnoredDuringExecution:软性偏好,调度器会尽量满足但不强制。
Pod 亲和性与反亲和性
spec:
affinity:
podAffinity:
# Pod 亲和性:与满足条件的 Pod 运行在同一拓扑域
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: cache
topologyKey: topology.kubernetes.io/hostname
podAntiAffinity:
# Pod 反亲和性:避免与满足条件的 Pod 在同一拓扑域
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: web
topologyKey: topology.kubernetes.io/hostnametopologyKey 常见取值:
| 值 | 含义 |
|---|---|
kubernetes.io/hostname | 节点级别(同一节点) |
topology.kubernetes.io/zone | 可用区级别 |
topology.kubernetes.io/region | 地域级别 |
Taints and Tolerations
Taint(污点) 标记节点的特殊属性,阻止普通 Pod 调度到该节点:
# 给节点添加污点
kubectl taint nodes node1 key=value:NoSchedule
kubectl taint nodes node1 key=value:PreferNoSchedule
kubectl taint nodes node1 key=value:NoExecute
# 查看节点污点
kubectl describe node node1 | grep TaintsToleration(容忍) 允许 Pod 调度到有特定污点的节点:
apiVersion: v1
kind: Pod
metadata:
name: with-toleration
spec:
tolerations:
- key: "key"
operator: "Equal"
value: "value"
effect: "NoSchedule"
- key: "key"
operator: "Exists"
effect: "NoExecute"
tolerationSeconds: 3600 # 容忍 NoExecute 3600 秒后驱逐Taint 效果说明:
| 效果 | 说明 |
|---|---|
NoSchedule | 不调度新 Pod 到该节点(已有 Pod 不受影响) |
PreferNoSchedule | 尽量不调度,但不保证 |
NoExecute | 不调度新 Pod,并驱逐已有非容忍 Pod |
自定义调度器
# 部署自定义调度器(以 CRD 方式)
# 1. 编写调度器扩展(使用 scheduling.k8s.io/v1 API)
# 2. 部署为 Deployment
# 3. 在 Pod 中指定 schedulerNameapiVersion: v1
kind: Pod
metadata:
name: with-custom-scheduler
spec:
schedulerName: my-custom-scheduler # 指定自定义调度器
containers:
- name: app
image: my-app:latest调度器扩展机制:
- Extender:HTTP 扩展,在调度器预选/优选阶段调用外部服务
- Scheduler Framework:Kubernetes 1.19+ 推荐的插件化调度框架,通过实现
ScorePlugin、FilterPlugin等接口扩展 - 调度器 Webhook:通过 Mutating/Validating Webhook 修改 Pod 调度相关字段
3.3 存储体系
PV / PVC 生命周期
PV(PersistentVolume) 是集群中的存储资源,PVC(PersistentVolumeClaim) 是用户的存储资源请求。
PV 周期:
Available(可用)--> Bound(已绑定)--> Released(已释放)--> Available(回收后可用)
| |
+--> Failed(绑定失败/回收失败)<---------+PVC 与 PV 绑定规则:
- PVC 的
storageClassName必须与 PV 匹配(或为空时匹配默认 StorageClass) - PVC 的
accessModes必须包含 PV 的至少一种模式 - PVC 的
resources.requests.storage必须小于等于 PV 的容量 - 若
selector指定了标签,则 PV 必须匹配标签
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs-1
spec:
capacity:
storage: 100Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
- ReadOnlyMany
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs-storage
nfs:
server: 192.168.1.100
path: "/data/k8s-volumes"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-app-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 50Gi
storageClassName: nfs-storageAccessModes:
| 模式 | 说明 |
|---|---|
| ReadWriteOnce(RWO) | 单节点读写(块存储) |
| ReadOnlyMany(ROX) | 多节点只读 |
| ReadWriteMany(RWX) | 多节点读写(文件/对象存储) |
| ReadWriteOncePod(RWOP) | 单 Pod 读写(1.22+) |
PV 回收策略:
| 策略 | 说明 |
|---|---|
| Retain | PVC 删除后 PV 保留,需手动清理 |
| Recycle | 已弃用,自动删除数据并恢复 Available |
| Delete | 自动删除底层存储资源(如 AWS EBS) |
StorageClass 动态供给
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp3
fsType: ext4
encrypted: "true"
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
storageClassName: fast-ssdvolumeBindingMode:
| 模式 | 说明 |
|---|---|
| Immediate | 立即创建 PV(默认),可能导致 PV 与 Pod 不在同一可用区 |
| WaitForFirstConsumer | 等待 Pod 调度后再创建 PV,确保 PV 与 Pod 在同一可用区 |
CSI 驱动与存储方案对比
CSI(Container Storage Interface)是 Kubernetes 存储的标准扩展接口,支持第三方存储系统以插件方式接入。
apiVersion: storage.k8s.io/v1
kind: CSIDriver
metadata:
name: csi-hostpath
spec:
attachRequired: true
podInfoOnMount: true
volumeLifecycleModes:
- Persistent
- EphemeralNFS vs Longhorn vs Rook Ceph 对比:
| 特性 | NFS CSI Driver | Longhorn | Rook Ceph |
|---|---|---|---|
| 架构 | 中心化 NFS 服务 | 分布式,每节点存储 | 分布式 Ceph 集群 |
| 数据高可用 | 依赖 NFS 服务端 HA | 自动副本(2-3 副本) | 多副本 + EC |
| 性能 | 一般,NFS 协议开销 | 较好,本地优先写入 | 高,Ceph 成熟稳定 |
| 快照/备份 | 需外部工具 | 内置快照/备份 | Ceph RBD 快照 |
| 扩容 | 手动扩展 NFS | 在线扩容 | 在线扩容 |
| 运维复杂度 | 低 | 中等 | 高 |
| 适用场景 | 开发测试、简单文件共享 | 中小规模生产、边缘 | 大规模生产、高性能 |
# 安装 Longhorn
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/master/deploy/longhorn.yaml
# 安装 Rook Ceph
kubectl apply -f https://raw.githubusercontent.com/rook/rook/master/deploy/examples/crds.yaml
kubectl apply -f https://raw.githubusercontent.com/rook/rook/master/deploy/examples/common.yaml
kubectl apply -f https://raw.githubusercontent.com/rook/rook/master/deploy/examples/operator.yaml3.4 安全体系
RBAC(基于角色的访问控制)
RBAC 是 Kubernetes 的默认授权模式,通过四个资源对象实现权限管理:
# Role:定义命名空间级别的权限
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods", "pods/log", "pods/status"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create"]
---
# ClusterRole:定义集群级别的权限(或跨命名空间资源)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cluster-reader
rules:
- apiGroups: [""]
resources: ["nodes", "namespaces", "persistentvolumes"]
verbs: ["get", "list", "watch"]
---
# RoleBinding:将 Role 绑定到用户/ServiceAccount(命名空间级别)
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: production
name: read-pods
subjects:
- kind: ServiceAccount
name: jenkins
namespace: cicd
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
---
# ClusterRoleBinding:将 ClusterRole 绑定到用户/ServiceAccount(集群级别)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: cluster-reader-binding
subjects:
- kind: User
name: "ops-team@example.com"
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: cluster-reader
apiGroup: rbac.authorization.k8s.ioRBAC 核心规则:
| 资源 | 作用域 | 说明 |
|---|---|---|
| Role | 命名空间 | 只对指定命名空间内的资源生效 |
| ClusterRole | 集群 | 对集群级资源(Nodes、PV)和所有命名空间资源生效 |
| RoleBinding | 命名空间 | 将 Role/ClusterRole 绑定到主体 |
| ClusterRoleBinding | 集群 | 将 ClusterRole 绑定到主体,范围覆盖整个集群 |
常见 verbs 说明:
| Verb | 对应 HTTP 方法 | 说明 |
|---|---|---|
| get | GET | 获取单个资源 |
| list | GET | 获取资源列表 |
| watch | GET | 监听资源变化 |
| create | POST | 创建资源 |
| update | PUT | 更新资源 |
| patch | PATCH | 部分更新资源 |
| delete | DELETE | 删除资源 |
| deletecollection | DELETE | 删除集合 |
ServiceAccount
ServiceAccount 为 Pod 提供身份标识,用于访问 Kubernetes API:
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-service-account
namespace: production
---
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
serviceAccountName: app-service-account
automountServiceAccountToken: true # 默认 true
containers:
- name: app
image: my-app:latest注意事项:
- 未指定
serviceAccountName的 Pod 使用命名空间的defaultServiceAccount - 建议每个应用创建独立的 ServiceAccount,避免权限过大
- 如果 Pod 不需要访问 API,设置
automountServiceAccountToken: false
Pod Security Standards(Pod 安全标准)
Kubernetes 1.23+ 弃用 PodSecurityPolicy(PSP),推荐使用内置的 Pod Security Admission(PSA):
# 命名空间级别设置 Pod 安全标准
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted # 强制 restricted 级别
pod-security.kubernetes.io/audit: baseline # 审计 baseline 违规
pod-security.kubernetes.io/warn: baseline # 警告 baseline 违规三个安全级别:
| 级别 | 说明 | 典型限制 |
|---|---|---|
| privileged | 无限制,完全信任 | 无额外限制 |
| baseline | 最低限制,POD 标准配置 | 禁止 privileged 容器、禁止 hostNetwork/hostPID/hostIPC、限制 SELinux/AppArmor/Seccomp |
| restricted | 严格限制,推荐生产 | 在 baseline 基础上:必须 non-root、只读 rootfs、禁止 ALL capabilities、限制 seccomp 为 RuntimeDefault |
# 满足 restricted 级别的 Pod 配置示例
apiVersion: v1
kind: Pod
metadata:
name: restricted-pod
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: my-app:latest
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
readOnlyRootFilesystem: true
runAsUser: 1000
runAsGroup: 1000NetworkPolicy 隔离规则
NetworkPolicy 通过标签选择器定义 Pod 的网络访问规则:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-network-policy
namespace: production
spec:
# 选择目标 Pod(应用于哪些 Pod)
podSelector:
matchLabels:
app: api-service
policyTypes:
- Ingress # 入站规则
- Egress # 出站规则
ingress:
# 允许来自 frontend Pod 的流量
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
# 允许来自 monitoring 命名空间的流量
- from:
- namespaceSelector:
matchLabels:
ns: monitoring
ports:
- protocol: TCP
port: 8080
egress:
# 允许访问数据库 Pod
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 3306
# 允许访问 kube-dns(需显式允许)
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53NetworkPolicy 注意事项:
- 如果 Namespace 中存在任何 NetworkPolicy,未被显式允许的流量将被拒绝(默认拒绝)
- 如果不创建 NetworkPolicy,默认允许所有流量
- NetworkPolicy 由 CNI 插件实现(Calico、Cilium、Weave Net 等),需确认集群 CNI 支持
podSelector: {}匹配命名空间中的所有 PodnamespaceSelector: {}匹配集群中的所有 Namespace
四、K8s 集群运维
4.1 集群安装
kubeadm 生产部署
kubeadm 是 Kubernetes 官方推荐的集群部署工具,支持生产级高可用部署。
环境准备:
# 所有节点执行
# 1. 关闭 swap
swapoff -a
sed -i '/ swap / s/^\(.*\)$/#\1/' /etc/fstab
# 2. 设置内核参数
cat <<EOF | tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
modprobe overlay
modprobe br_netfilter
cat <<EOF | tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sysctl --system
# 3. 安装 containerd(CRI 运行时)
# containerd 配置
mkdir -p /etc/containerd
containerd config default | tee /etc/containerd/config.toml
# 修改 SystemdCgroup 为 true
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
systemctl restart containerd
systemctl enable containerd
# 4. 安装 kubeadm / kubelet / kubectl
apt-get update
apt-get install -y apt-transport-https ca-certificates curl gpg
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /' | tee /etc/apt/sources.list.d/kubernetes.list
apt-get update
apt-get install -y kubelet=1.28.0-1.1 kubeadm=1.28.0-1.1 kubectl=1.28.0-1.1
apt-mark hold kubelet kubeadm kubectl高可用 etcd 集群:
# 在 etcd 节点执行
# 使用 kubeadm 创建 etcd 集群或手动部署
# 方法一:kubeadm 自动部署(推荐)
# 方法二:手动部署二进制 etcd 集群
# 每个 etcd 节点配置
etcd --name etcd-1 \
--initial-advertise-peer-urls https://192.168.1.10:2380 \
--listen-peer-urls https://0.0.0.0:2380 \
--listen-client-urls https://0.0.0.0:2379 \
--advertise-client-urls https://192.168.1.10:2379 \
--initial-cluster-token etcd-cluster-1 \
--initial-cluster etcd-1=https://192.168.1.10:2380,etcd-2=https://192.168.1.11:2380,etcd-3=https://192.168.1.12:2380 \
--initial-cluster-state new \
--cert-file=/etc/etcd/pki/server.crt \
--key-file=/etc/etcd/pki/server.key \
--peer-cert-file=/etc/etcd/pki/peer.crt \
--peer-key-file=/etc/etcd/pki/peer.key \
--trusted-ca-file=/etc/etcd/pki/ca.crt \
--peer-trusted-ca-file=/etc/etcd/pki/ca.crt负载均衡器 API Server:
在多个控制平面节点前部署负载均衡器,确保 API Server 的高可用:
# Nginx 负载均衡配置
stream {
upstream kube-apiserver {
server 192.168.1.10:6443 max_fails=3 fail_timeout=30s;
server 192.168.1.11:6443 max_fails=3 fail_timeout=30s;
server 192.168.1.12:6443 max_fails=3 fail_timeout=30s;
}
server {
listen 6443;
proxy_pass kube-apiserver;
proxy_connect_timeout 5s;
}
}# 初始化控制平面(通过负载均衡器地址)
kubeadm init \
--control-plane-endpoint "lb.example.com:6443" \
--upload-certs \
--pod-network-cidr=10.244.0.0/16 \
--service-cidr=10.96.0.0/12 \
--kubernetes-version v1.28.0
# 加入其他控制平面节点
kubeadm join lb.example.com:6443 --token <token> \
--discovery-token-ca-cert-hash sha256:<hash> \
--control-plane --certificate-key <cert-key>
# 加入工作节点
kubeadm join lb.example.com:6443 --token <token> \
--discovery-token-ca-cert-hash sha256:<hash>安装网络插件(CNI):
# Flannel
kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml
# Calico(生产推荐)
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.27/manifests/tigera-operator.yaml
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.27/manifests/custom-resources.yaml安装附加组件:
# CoreDNS(kubeadm 默认安装)
kubectl get pods -n kube-system -l k8s-app=kube-dns
# Dashboard
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml
# Metrics Server
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# 创建 Dashboard 管理员
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: admin-user
namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: admin-user
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: admin-user
namespace: kubernetes-dashboard
EOF
# 获取登录 token
kubectl -n kubernetes-dashboard create token admin-userK8s 版本升级策略
# 依次升级各组件:
# 1. 升级 kubeadm
apt-get update
apt-get install -y kubeadm=1.29.0-1.1
# 2. 验证升级计划并升级控制平面
kubeadm upgrade plan
kubeadm upgrade apply v1.29.0
# 3. 升级 kubelet 和 kubectl
apt-get install -y kubelet=1.29.0-1.1 kubectl=1.29.0-1.1
systemctl restart kubelet
# 4. 升级工作节点
kubeadm upgrade node升级原则:
- 遵循 N-2 升级策略(最多跨两个次要版本升级)
- 分批升级节点,确保集群可用
- 升级前备份 etcd 快照
- 使用 PDB 保护关键应用
4.2 监控
Metrics Server
Metrics Server 是 Kubernetes 内置的资源指标收集器,提供 CPU/内存的实时使用数据:
# 安装 Metrics Server
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# 查看节点资源使用
kubectl top nodes
# 查看 Pod 资源使用
kubectl top pods -n production
# 查看容器级别的资源使用
kubectl top pods --containersMetrics Server 仅提供实时快照数据,不存储历史数据,适用于 HPA 自动扩缩容和 kubectl top 命令行查询。
Prometheus Operator + Grafana
Prometheus Operator 使用 CRD 定义监控目标,简化 Prometheus 集群的管理:
# ServiceMonitor 定义监控目标
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: app-monitor
namespace: monitoring
spec:
selector:
matchLabels:
app: my-app
endpoints:
- port: metrics
interval: 15s
path: /actuator/prometheus
namespaceSelector:
any: true# PrometheusRule 定义告警规则
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: app-alerts
namespace: monitoring
spec:
groups:
- name: app.rules
rules:
- alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.05
for: 5m
labels:
severity: critical
annotations:
summary: "High error rate on {{ $labels.instance }}"
description: "Error rate is {{ $value | humanizePercentage }}"
- alert: PodCrashLooping
expr: kube_pod_status_phase{phase="Pending"} > 0
for: 5m
labels:
severity: warning监控架构:
Prometheus Operator
|
+-- Prometheus(监控服务器)
| +-- ServiceMonitor(自动发现应用 metrics 端点)
| +-- PodMonitor(自动发现 Pod metrics 端点)
| +-- PrometheusRule(告警规则)
|
+-- Alertmanager(告警管理)
| +-- 告警分组/抑制/静默
| +-- 通知渠道(邮件/Slack/钉钉/飞书/PagerDuty)
|
+-- Grafana(可视化仪表盘)
+-- 数据源:Prometheus
+-- 预置 Dashboard:
- Kubernetes Cluster (ID: 315)
- Node Exporter (ID: 1860)
- Kube State Metrics (ID: 13332)关键导出器:
| 组件 | 作用 | 默认端口 |
|---|---|---|
| node_exporter | 收集节点级别指标(CPU、内存、磁盘、网络) | 9100 |
| kube-state-metrics | 收集 K8s 资源对象状态指标 | 8080 |
| kubelet / cAdvisor | 收集容器级别指标 | 10250 |
| blackbox_exporter | 黑盒探测(HTTP/HTTPS/TCP/ICMP) | 9115 |
# 使用 Helm 安装 Prometheus Stack(包含所有组件)
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--create-namespace \
--set grafana.adminPassword=admin123 \
--set prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues=false4.3 日志
日志采集架构
Kubernetes 集群日志采集有三种主流方案:
方案一:Fluentd / Filebeat + Elasticsearch + Kibana(EFK/ELK)
Pod (stdout) --> Container Log File (/var/log/pods/) --> Fluentd DaemonSet
|
Elasticsearch
|
Kibana# Fluentd DaemonSet 采集配置
apiVersion: v1
kind: ConfigMap
metadata:
name: fluentd-config
namespace: logging
data:
fluent.conf: |
<source>
@type tail
path /var/log/containers/*.log
pos_file /var/log/fluentd-containers.log.pos
tag kubernetes.*
<parse>
@type json
time_format %Y-%m-%dT%H:%M:%S.%NZ
</parse>
</source>
<filter kubernetes.**>
@type kubernetes_metadata
</filter>
<match kubernetes.**>
@type elasticsearch
host elasticsearch-service.logging.svc.cluster.local
port 9200
logstash_format true
logstash_prefix k8s-logs
<buffer>
@type file
path /var/log/fluentd-buffer
flush_mode interval
retry_type exponential_backoff
flush_interval 5s
</buffer>
</match>方案二:Loki + Promtail(轻量化)
Pod (stdout) --> Promtail DaemonSet --> Loki --> Grafana# Promtail DaemonSet 配置
apiVersion: v1
kind: ConfigMap
metadata:
name: promtail-config
namespace: logging
data:
promtail.yaml: |
scrape_configs:
- job_name: kubernetes-pods
pipeline_stages:
- cri: {}
- regex:
expression: "^(?s)(?P<time>\\S+?) (?P<stream>stdout|stderr) (?P<flags>\\S+?) (?P<content>.*)$"
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_label_app]
target_label: app
- source_labels: [__meta_kubernetes_namespace]
target_label: namespace
- source_labels: [__meta_kubernetes_pod_name]
target_label: pod
clients:
- url: http://loki-service.logging.svc.cluster.local:3100/loki/api/v1/push方案三:Filebeat + Elasticsearch
# Filebeat DaemonSet 配置
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: filebeat
namespace: logging
spec:
selector:
matchLabels:
app: filebeat
template:
metadata:
labels:
app: filebeat
spec:
containers:
- name: filebeat
image: docker.elastic.co/beats/filebeat:8.11.0
args: ["-e", "-strict.perms=false"]
volumeMounts:
- name: varlog
mountPath: /var/log
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
- name: config
mountPath: /usr/share/filebeat/filebeat.yml
subPath: filebeat.yml
volumes:
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
- name: config
configMap:
name: filebeat-config多行日志解析
K8s 中容器日志默认按行分割,多行日志(如 Java 异常堆栈)需要特殊处理:
<!-- Logstash 多行合并配置 -->
<match kubernetes.**>
@type multiline
format_firstline /^\d{4}-\d{2}-\d{2}/
format1 /^(?<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3}) (?<level>[A-Z]+) (?<message>[\s\S]*)$/
</match># Promtail 多行合并配置
pipeline_stages:
- multiline:
firstline: '^\d{4}-\d{2}-\d{2}'
max_lines: 50
max_wait_time: 3s
- regex:
expression: '^(?P<timestamp>\S+) (?P<level>\S+) (?P<logger>\S+) --- \[(?P<thread>.*?)\] (?P<class>\S+?) : (?P<message>[\s\S]*)$'K8s 日志标准输出 vs 文件日志
| 方式 | 说明 | 优缺点 |
|---|---|---|
| 标准输出 stdout/stderr | 容器内应用写入 stdout/stderr | 天然被 kubelet 采集,无需额外配置;无法按文件分割、轮转策略 |
| 文件日志 | 写入容器内的文件系统 | 需 sidecar 或 hostPath 采集;支持日志轮转、按文件分割 |
最佳实践:应用日志同时输出到 stdout 和文件
- JSON/结构化的业务日志写入 stdout(
consoleappender),便于标准采集 - 访问日志、慢查询日志写入文件(
fileappender),通过 sidecar 采集 - 使用 logrotate 或 Docker logging driver 控制日志轮转
# Docker logging driver 配置日志轮转
docker run -d \
--log-driver json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
my-app4.4 故障排查
Pod 排障命令
# 1. 查看 Pod 状态和事件
kubectl get pods -n <namespace>
kubectl describe pod <pod-name> -n <namespace>
# 2. 查看 Pod 日志
kubectl logs <pod-name> -n <namespace>
kubectl logs --previous <pod-name> -n <namespace> # 查看上一次容器日志(CrashLoopBackOff)
# 3. 进入容器调试
kubectl exec -it <pod-name> -n <namespace> -- sh
kubectl exec -it <pod-name> -n <namespace> -c <container-name> -- sh
# 4. 查看集群事件
kubectl get events -n <namespace> --sort-by='.lastTimestamp'
kubectl get events --all-namespaces --sort-by='.lastTimestamp' | tail -20常见问题排查步骤
Node NotReady:
# 1. 查看节点状态
kubectl describe node <node-name>
# 2. SSH 到节点查看 kubelet 状态
systemctl status kubelet
journalctl -u kubelet -f --no-pager | tail -50
# 3. 检查节点资源
free -h
df -h
top
# 4. 检查容器运行时
crictl ps
crictl info
# 5. 检查 CNI 网络插件
ls /opt/cni/bin/
cat /etc/cni/net.d/*.confCoreDNS CrashLoopBackOff:
# 1. 查看 CoreDNS Pod 状态
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl logs -n kube-system -l k8s-app=kube-dns
# 2. 检查 CoreDNS ConfigMap
kubectl get configmap -n kube-system coredns -o yaml
# 3. 常见原因及修复
# - 节点 DNS 配置问题:检查 /etc/resolv.conf
# - 网络插件未安装:确认 CNI 插件已部署
# - 资源不足:增加资源限制
kubectl edit deployment -n kube-system coredns
# 添加资源限制
# resources:
# limits:
# memory: 170Mi
# requests:
# cpu: 100m
# memory: 70Mi
# 4. 重启 CoreDNS
kubectl rollout restart -n kube-system deployment/corednsOOMKilled:
# Pod 状态:OOMKilled
# 原因:容器内存使用超过 limit 限制
# 解决方案:
# 1. 增加内存限制
resources:
limits:
memory: "1Gi"
requests:
memory: "512Mi"
# 2. 优化应用内存使用(JVM)
# -XX:MaxRAMPercentage=75.0 # 限制 JVM 堆最大占用容器内存的 75%
# 3. 使用 VPA 自动调整资源ImagePullBackOff:
# 1. 查看具体错误
kubectl describe pod <pod-name>
# 常见错误:
# - ImagePullBackOff: 镜像拉取失败
# - ErrImagePull: 镜像拉取出错
# - ImagePullBackOff: 重试拉取失败
# 2. 排查步骤
# - 检查镜像名称和标签是否正确
kubectl get pod <pod-name> -o jsonpath='{.spec.containers[0].image}'
# - 检查镜像仓库认证
kubectl get secret <image-pull-secret> -o yaml
# - 检查镜像仓库地址是否可达
# - 检查镜像架构是否匹配节点架构(如 arm64 镜像部署到 amd64 节点)CrashLoopBackOff:
# 1. 查看容器退出原因
kubectl describe pod <pod-name>
# 关注 Last State: Terminated 的 Reason 和 Exit Code
# 2. 查看应用日志(包括上一次容器的日志)
kubectl logs --previous <pod-name>
# 3. 常见退出码
# Exit Code 0: 正常退出(可能是 Job 完成或应用主动退出)
# Exit Code 1: 应用错误(配置错误、端口冲突等)
# Exit Code 137: SIGKILL(OOMKilled)
# Exit Code 139: SIGSEGV(段错误,常见于原生应用)
# Exit Code 143: SIGTERM(优雅关闭超时)Pending(Pod 一直处于 Pending 状态):
# 查看 Pending 原因
kubectl describe pod <pod-name>
# 常见原因:
# - 0/1 nodes are available: 1 Insufficient cpu/memory
# 解决方案:增加节点或调整资源请求
# - 0/1 nodes are available: 1 node(s) had taint
# 解决方案:添加 tolerations
# - 0/1 nodes are available: 1 node(s) didn't match pod affinity/anti-affinity
# 解决方案:调整亲和性规则
# - 0/1 nodes are available: 1 node(s) didn't match node selector
# 解决方案:调整 nodeSelector 或节点标签
# - 没有 StorageClass 对应的 PV
# 解决方案:检查 PVC 绑定状态和 StorageClass
# 检查 PVC 状态
kubectl get pvc
kubectl describe pvc <pvc-name>Evicted(Pod 被驱逐):
# 查看 Pod 状态
kubectl get pods | grep Evicted
kubectl describe pod <evicted-pod-name>
# 原因:节点资源不足(磁盘/内存/PID压力),kubelet 驱逐低优先级 Pod
# 解决方案:
# 1. 清理节点上不需要的镜像和容器
docker system prune -f
# 2. 检查节点资源使用
kubectl top nodes
# 3. 设置 Pod 优先级(PriorityClass)
# 4. 配置 PDB(PodDisruptionBudget)保护关键服务
# 5. 使用 HPA/PCA 自动扩缩容节点状态排查总结:
# 节点全面检查脚本
echo "=== Node Status ==="
kubectl get nodes -o wide
echo "=== Node Conditions ==="
kubectl describe node <node-name> | grep -A 10 Conditions
echo "=== Node Resources ==="
kubectl top node <node-name>
echo "=== Pods on Node ==="
kubectl get pods --all-namespaces --field-selector spec.nodeName=<node-name>
echo "=== Kubelet Logs ==="
journalctl -u kubelet --since "5 min ago" --no-pager | tail -30五、Helm 回顾与进阶
5.1 Chart 开发
Chart.yaml 格式
Chart 的元数据定义在 Chart.yaml 中:
apiVersion: v2 # Helm 3 使用 v2
name: my-app
description: A production-ready microservice application
version: 1.2.0 # Chart 版本
appVersion: "2.5.0" # 应用版本
type: application # application 或 library
home: https://github.com/myorg/my-app
sources:
- https://github.com/myorg/my-app
maintainers:
- name: DevOps Team
email: devops@example.com
url: https://devops.example.com
icon: https://example.com/icon.png
keywords:
- microservice
- spring-boot
dependencies:
- name: redis
version: "~17.0.0"
repository: "https://charts.bitnami.com/bitnami"
condition: redis.enabled # 通过 values 控制是否启用
tags:
- cache
- name: mysql
version: "~9.0.0"
repository: "https://charts.bitnami.com/bitnami"
condition: mysql.enabled
tags:
- databasevalues.yaml 层级设计
# values.yaml -- 清晰的层级结构
# 全局配置
global:
environment: production
imageRegistry: registry.example.com
# 应用镜像配置
image:
repository: my-app
tag: latest
pullPolicy: Always
pullSecrets:
- name: regcred
# 副本数
replicas: 3
# 服务配置
service:
type: ClusterIP
port: 8080
annotations: {}
# 资源限制
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
# 探针配置
probes:
liveness:
enabled: true
path: /actuator/health/liveness
initialDelaySeconds: 30
periodSeconds: 10
readiness:
enabled: true
path: /actuator/health/readiness
initialDelaySeconds: 20
periodSeconds: 5
# 配置注入
configmap:
enabled: true
mountPath: /app/config
data:
application.yml: |
server:
port: 8080
spring:
profiles:
active: ${SPRING_PROFILES_ACTIVE}
# 环境变量
env:
- name: SPRING_PROFILES_ACTIVE
value: "prod"
- name: JAVA_OPTS
value: "-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
# 数据持久化
persistence:
enabled: true
storageClass: ""
accessMode: ReadWriteOnce
size: 10Gi
mountPath: /app/data
# 自动扩缩容
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 70
targetMemoryUtilizationPercentage: 80
# 网络策略
networkPolicy:
enabled: true
ingressRules:
- from:
- namespaceSelector:
matchLabels:
name: monitoring
ports:
- port: 8080模板与辅助函数
# templates/_helpers.tpl
{{- define "my-app.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "my-app.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{- define "my-app.labels" -}}
helm.sh/chart: {{ include "my-app.name" . }}-{{ .Chart.Version }}
app.kubernetes.io/name: {{ include "my-app.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{- define "my-app.selectorLabels" -}}
app.kubernetes.io/name: {{ include "my-app.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{- define "my-app.image" -}}
{{- $registry := .Values.global.imageRegistry | default .Values.image.repository -}}
{{- $tag := .Values.image.tag | default .Chart.AppVersion -}}
{{- printf "%s:%s" $registry $tag }}
{{- end }}模板中使用辅助函数:
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "my-app.fullname" . }}
labels:
{{- include "my-app.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicas }}
selector:
matchLabels:
{{- include "my-app.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "my-app.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ include "my-app.image" . }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: {{ .Values.service.port }}依赖管理
子 Chart 管理:
# Chart.yaml
dependencies:
- name: redis
version: "~17.0.0"
repository: "https://charts.bitnami.com/bitnami"
condition: redis.enabled
tags:
- cache
- name: mysql
version: "~9.0.0"
repository: "https://charts.bitnami.com/bitnami"
condition: mysql.enabled
tags:
- database# 管理依赖
helm dependency list # 查看依赖
helm dependency update # 根据 Chart.yaml 更新依赖
helm dependency build # 从 lock 文件重建依赖# values.yaml 控制子 Chart
redis:
enabled: true
architecture: standalone
auth:
enabled: true
password: "redis123"
master:
resources:
requests:
memory: 256Mi
cpu: 100m
mysql:
enabled: true
architecture: standalone
auth:
rootPassword: "root123"
database: mydb
primary:
persistence:
size: 20GiChartMuseum / Harbor 仓库:
# 启动 ChartMuseum
docker run -d -p 8090:8080 \
-v /data/charts:/charts \
-e STORAGE=local \
-e DEBUG=true \
chartmuseum/chartmuseum:latest
# 将 Chart 推送到 ChartMuseum
helm package my-app/
helm push my-app-1.2.0.tgz http://localhost:8090
# 使用 Harbor 作为 Chart 仓库
helm repo add my-harbor https://harbor.example.com/chartrepo/library \
--username admin --password <password>
helm push my-app-1.2.0.tgz oci://harbor.example.com/library5.2 Helm 生命周期
核心命令
# 安装
helm install my-release ./my-chart --namespace production --create-namespace
helm install my-release ./my-chart -f values-prod.yaml --set image.tag=v2.5.0
# 升级
helm upgrade my-release ./my-chart -f values-prod.yaml
helm upgrade my-release ./my-chart --reuse-values # 复用之前的 values
helm upgrade my-release ./my-chart --reset-values # 重置 values 为 Chart 默认值
# 回滚
helm rollback my-release 1 # 回滚到版本 1
helm rollback my-release 2 --wait --timeout 5m # 等待回滚完成
# 删除
helm uninstall my-release --namespace production
# 查看
helm list -n production # 列出 release
helm status my-release -n production # 查看 release 状态
helm get values my-release -n production # 查看当前的 values
helm get manifest my-release -n production # 查看渲染后的 K8s 清单
helm get notes my-release -n production # 查看安装提示
helm history my-release -n production # 查看 release 版本历史Hooks(生命周期钩子)
Helm Hooks 允许在 release 生命周期的特定时刻执行资源:
# templates/migration-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "my-app.fullname" . }}-db-migration
annotations:
"helm.sh/hook": pre-upgrade # 钩子触发时机
"helm.sh/hook-weight": "5" # 执行权重(数字越小越先执行)
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migration
image: "{{ include "my-app.image" . }}"
command: ["java", "-jar", "migration.jar"]Hooks 触发时机与权重:
| Hook 名称 | 触发时机 | 典型用途 |
|---|---|---|
pre-install | 安装时,所有资源渲染后执行 | 数据库初始化、资源校验 |
post-install | 安装成功后执行 | 通知、注册服务 |
pre-upgrade | 升级时,但旧版本仍在运行 | 数据库迁移、数据备份 |
post-upgrade | 升级成功后执行 | 更新通知、缓存预热 |
pre-delete | 删除时执行 | 清理外部资源、数据备份 |
post-delete | 删除成功后执行 | 清理通知 |
pre-rollback | 回滚时执行 | 回滚前数据备份 |
post-rollback | 回滚成功后执行 | 回滚通知 |
test | helm test 命令执行 | 集成测试、健康检查 |
Hook 权重控制:
# 执行顺序:权重小的先执行
annotations:
"helm.sh/hook-weight": "0" # 最先执行
"helm.sh/hook-weight": "5" # 中间
"helm.sh/hook-weight": "10" # 最后执行Hook 删除策略:
annotations:
"helm.sh/hook-delete-policy": before-hook-creation # 创建新 hook 前删除旧的
"helm.sh/hook-delete-policy": hook-succeeded # hook 成功后删除
"helm.sh/hook-delete-policy": hook-failed # hook 失败后删除
# 可以组合
"helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded"测试 Chart
# templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "my-app.fullname" . }}-test-connection"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command:
- wget
- "--timeout=5"
- "{{ include "my-app.fullname" . }}:{{ .Values.service.port }}"
restartPolicy: Never# 运行 Chart 测试
helm test my-release -n production
# 验证 values
helm template ./my-chart -f values-prod.yaml # 渲染模板但不安装
helm lint ./my-chart # 语法校验
helm lint ./my-chart -f values-prod.yaml # 校验 values 与模板的兼容性5.3 Helmfile / Kustomize 对比
Helmfile:声明式 Helm 管理
Helmfile 通过声明式文件管理多个 Helm Chart 的安装和升级:
# helmfile.yaml
repositories:
- name: bitnami
url: https://charts.bitnami.com/bitnami
- name: stable
url: https://charts.helm.sh/stable
releases:
# 基础设施层
- name: cert-manager
namespace: cert-manager
chart: jetstack/cert-manager
version: v1.12.0
values:
- installCRDs: true
set:
- name: global.leaderElection.namespace
value: cert-manager
# 中间件层
- name: redis
namespace: middleware
chart: bitnami/redis
version: 17.x
values:
- values/redis/{{ .Environment.Name }}.yaml
needs:
- cert-manager
- name: mysql
namespace: middleware
chart: bitnami/mysql
version: 9.x
values:
- values/mysql/{{ .Environment.Name }}.yaml
# 应用层
- name: my-app
namespace: production
chart: ./charts/my-app
version: 1.2.0
values:
- values/my-app/{{ .Environment.Name }}.yaml
needs:
- redis
- mysql
set:
- name: image.tag
value: {{ .Values.appVersion }}# environments.yaml
environments:
dev:
values:
- env/dev.yaml
staging:
values:
- env/staging.yaml
prod:
values:
- env/prod.yaml# Helmfile 命令
helmfile sync # 同步所有 release(安装或升级)
helmfile diff # 预览差异
helmfile apply # 仅 apply 有变更的 release
helmfile destroy # 删除所有 release
helmfile -e prod sync # 指定环境Kustomize:原生 K8s 配置管理
Kustomize 是 Kubernetes 原生的配置管理工具(1.14+ 内置在 kubectl 中),通过补丁和覆盖层实现配置定制:
# kustomization.yaml(基础配置)
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
- configmap.yaml
commonLabels:
app.kubernetes.io/managed-by: kustomize
namePrefix: prod-
namespace: production
images:
- name: my-app
newName: registry.example.com/my-app
newTag: v2.5.0
configMapGenerator:
- name: app-config
files:
- configs/application.yaml# overlays/prod/kustomization.yaml(生产环境覆盖层)
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
patches:
# 修改 Deployment 的资源限制
- patch: |-
- op: replace
path: /spec/replicas
value: 5
- op: add
path: /spec/template/spec/containers/0/resources
value:
limits:
cpu: "1"
memory: 1Gi
requests:
cpu: 500m
memory: 512Mi
target:
kind: Deployment
name: my-app
# 添加环境变量
- patch: |-
- op: add
path: /spec/template/spec/containers/0/env/-
value:
name: SPRING_PROFILES_ACTIVE
value: prod
target:
kind: Deployment
name: my-app
replicas:
- name: my-app
count: 5
configMapGenerator:
- name: app-config
behavior: merge
literals:
- log.level=INFO# Kustomize 命令
kubectl kustomize overlays/prod/ # 渲染最终 YAML
kubectl apply -k overlays/prod/ # 部署到集群Helmfile / Kustomize / Helm 选型对比
| 对比维度 | Helm | Kustomize | Helmfile |
|---|---|---|---|
| 本质 | 包管理 + 模板引擎 | 配置补丁/覆盖层 | Chart 编排管理 |
| 模板语法 | Go Template + Sprig | 无模板语法,纯 YAML 合并 | 同 Helm |
| 学习曲线 | 中(需学 Go Template) | 低(纯 YAML Patch) | 中高 |
| 多环境管理 | 多个 values 文件 + -f 选择 | overlay 目录分层 | 声明式环境定义 |
| 共享与复用 | Helm Chart 包可共享 | 通过 base 复用 | 编排多个 Chart |
| 版本管理 | 语义化版本 | Git 版本 | 语义化版本 |
| 配置复杂度 | 高(模板函数丰富) | 中(Patch 操作有限) | 高 |
| 原生支持 | 外部工具 | kubectl 内置 | 外部工具 |
| CI/CD 集成 | helm upgrade --install | kubectl apply -k | helmfile sync |
| 依赖管理 | dependency + subchart | 无内置依赖,需手动 | needs + 执行顺序控制 |
| Secret 管理 | 需 Sealed Secrets / External Secrets | 同 Helm | 同 Helm |
| 适用场景 | 需要高度可配置、可共享的包 | 简单的配置覆盖、团队内使用 | 管理复杂多 Chart 部署 |
选型建议:
- 如果发布 Chart 到仓库供他人使用(如 Bitnami、开源项目),选择 Helm
- 如果团队内部管理少量应用,配置变化不复杂,选择 Kustomize
- 如果需要编排多个 Helm Chart、控制部署顺序、管理多环境差异,选择 Helmfile
- 组合使用:Helmfile 编排多个 Helm Chart,其中某些 Chart 内部使用 Kustomize 管理 overlay