Ubuntu 系统系列 | 第 25 天(重访):容器编排进阶——Docker Compose 生产实践与 Kubernetes 单节点集群实战

第 25/30 天(重访进阶版)

前言

在首发文章中,我们用 Docker Compose 搭建了 LEMP 应用栈,并用 Minikube 体验了 Kubernetes 的基本流程。但在生产环境中,”能跑起来”和”可靠地跑”之间隔着无数细节:数据库还没就绪应用就启动了怎么办?容器日志把磁盘写满怎么办?Minikube 真的能上生产吗?单节点 K8s 集群又有哪些必须绕开的坑?

本文是容器编排进阶实战:上半部分讲 Docker Compose 的生产化改造(健康检查、依赖编排、多环境管理、日志治理),下半部分用 kubeadm 搭建一个真实的生产级单节点 Kubernetes 集群(而不是玩具级的 Minikube),并给出备份、升级、排查的完整方案。每一节都配有可以直接复制的命令和真实踩坑经验。


一、Docker Compose 生产化:从「能跑」到「可靠」

1.1 健康检查与启动依赖:根治「数据库未就绪」竞态

入门教程里的 depends_on 只保证启动顺序,不保证可用状态。MySQL 容器刚启动时端口还没就绪,应用容器先跑起来连接数据库必然失败。生产级写法是 healthcheck + depends_on.condition: service_healthy

# docker-compose.prod.yml
services:
  db:
    image: mysql:8.4
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:?请设置环境变量}
      MYSQL_DATABASE: ${DB_NAME}
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
      interval: 5s
      timeout: 3s
      retries: 12
      start_period: 30s

  app:
    image: myapp:latest
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped
# 检查各服务健康状态(HEALTHY 才是真就绪)
docker compose -f docker-compose.prod.yml ps

# 输出示例:
# NAME        IMAGE          STATUS
# db          mysql:8.4      Up 40 seconds (healthy)
# app         myapp:latest   Up 5 seconds

⚠️ 关键坑${VAR:?错误提示} 语法让 Compose 在变量缺失时直接报错退出,防止把空密码带到生产环境。这是 CI 中非常实用的防护。

1.2 资源限制与优雅停机

不加资源限制的容器会吃光宿主机内存引发 OOM;stop_grace_period 太短则应用来不及完成收尾。

services:
  app:
    image: myapp:latest
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512M
        reservations:
          memory: 256M
    stop_grace_period: 30s
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
# 查看容器实际资源占用,确认限制生效
docker stats --no-stream

# 输出示例:
# CONTAINER ID   NAME   CPU %     MEM USAGE / LIMIT
# a1b2c3d4e5f6   app    12.50%    268MiB / 512MiB

⚠️ 关键坑logging.max-size 必须显式配置——不配置的话 json-file 日志无限增长,/var/lib/docker/containers 目录会被撑爆,这是生产环境最常见的磁盘故障之一。

1.3 多环境管理:一份 Compose 文件走天下

.env 分离环境差异,用 profiles 控制可选服务(如调试工具只在开发环境启动):

# .env.production 示例
DB_NAME=myapp
DB_ROOT_PASSWORD=SuperSecret123!
NGINX_PORT=8080
# docker-compose.yml(基础文件,不写死任何密码)
version: "3.8"
services:
  nginx:
    image: nginx:1.26-alpine
    ports:
      - "${NGINX_PORT:-8080}:80"
    profiles: ["web"]

  # 仅开发环境启用
  phpmyadmin:
    image: phpmyadmin:latest
    profiles: ["dev"]
# 生产启动(只起 nginx,不起 phpmyadmin)
docker compose --env-file .env.production --profile web up -d

# 开发启动(附加上调试工具)
docker compose --env-file .env.development --profile web --profile dev up -d

# 校验配置是否正确(CI 中用于静态检查,不会启动任何容器)
docker compose config --quiet && echo "COMPOSE FILE VALID"

1.4 Compose 生产常见坑速查

现象 原因 解决
容器时间比宿主机晚 8 小时 镜像默认 UTC 时区 挂载 /etc/localtime 或设置 TZ=Asia/Shanghai
docker compose down 后数据丢失 未用命名卷,数据在容器可写层 数据目录必须用 volumes: 命名的卷或 bind mount
升级镜像后容器没变 镜像 tag 用了 latest 且本地有缓存 用固定版本号 tag;docker compose up -d --pull always
端口冲突 多个项目都映射 3306 容器间走 Compose 网络用服务名互访,仅网关端口映射到宿主机

二、Kubernetes 单节点:用 kubeadm 搭生产级集群

2.1 为什么生产不用 Minikube

Minikube 是本地开发工具:单二进制、资源受限、kubelet 是独立进程管理而非 systemd,无法平滑升级到多节点。生产级单节点集群应该用 kubeadm——它生成的是与云厂商托管集群同构的标准集群,以后加节点只需一条 kubeadm join

2.2 前置准备:内核模块、sysctl 与 containerd

# 1. 关闭 swap(kubelet 强校验,不关则无法启动)
sudo swapoff -a
sudo sed -i '/ swap / s/^/#/' /etc/fstab

# 2. 加载内核模块
cat <<'EOF' | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay && sudo modprobe br_netfilter

# 3. 设置网络转发参数
cat <<'EOF' | sudo 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
sudo sysctl --system

containerd 配置是最容易踩的坑:Kubernetes 1.28+ 中 kubelet 默认使用 systemd cgroup driver,containerd 必须保持一致,否则 kubelet 报 failed to run Kubelet: failed to validate kubelet flags ... cgroup driver is "cgroupfs"

# 查看当前 containerd 版本
containerd --version

# 生成默认配置并启用 SystemdCgroup
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml > /dev/null
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
sudo systemctl restart containerd

2.3 安装 kubeadm、kubelet、kubectl 并初始化

# 添加 Kubernetes apt 源(以 1.30 为例,版本号可按需调整)
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gpg
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key |
  sudo 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.30/deb/ /' |
  sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl

# 初始化单节点集群(Calico 需要的网段)
sudo kubeadm init 
  --pod-network-cidr=192.168.0.0/16 
  --apiserver-advertise-address=192.168.1.10

# 配置 kubectl 访问权限(普通用户)
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

2.4 安装 Calico CNI 并让单节点参与调度

# 安装 Calico 网络插件
curl -L https://raw.githubusercontent.com/projectcalico/calico/v3.28/manifests/calico.yaml -O
kubectl apply -f calico.yaml

# 等待所有系统 Pod 就绪
kubectl get pods -n kube-system

# 单节点集群默认不调度业务 Pod(控制面有 taint),需要移除:
kubectl taint nodes --all node-role.kubernetes.io/control-plane-

# 输出示例:node/ubuntu-node untainted

# 部署测试应用验证集群可用
kubectl create deployment nginx-demo --image=nginx:alpine --replicas=2
kubectl get pods -o wide

⚠️ 关键坑:kubeadm 版本号必须与 kubelet/kubectl 完全一致;apt-mark hold 防止 apt upgrade 悄悄升级 kubeadm 造成版本错配。


三、生产必备:备份、升级与故障排查

3.1 etcd 备份:救命的最后一根稻草

集群的唯一持久状态在 etcd 里,必须定期快照:

# etcd 备份(kubeadm 集群中 etcd 以 static pod 运行)
sudo ETCDCTL_API=3 etcdctl 
  --endpoints=https://127.0.0.1:2379 
  --cacert=/etc/kubernetes/pki/etcd/ca.crt 
  --cert=/etc/kubernetes/pki/etcd/server.crt 
  --key=/etc/kubernetes/pki/etcd/server.key 
  snapshot save /backup/etcd-snapshot-$(date +%F).db

# 验证快照完整性
sudo ETCDCTL_API=3 etcdctl snapshot status /backup/etcd-snapshot-2026-08-13.db
# 输出示例:{"hash":..., "revision":12345, "totalKey":678, "totalSize":1048576}

# 配合 cron 每日自动备份 + rsync 异地保存(参考本系列第 13 天)

3.2 故障排查三板斧

# 1. 看 Pod 状态与事件
kubectl describe pod <pod-name> -n <namespace>

# 2. 看容器启动日志(-p 看上一次崩溃前的日志)
kubectl logs <pod-name> -n <namespace> --previous

# 3. containerd 环境没有 docker 命令,用 crictl 直接调试
sudo crictl ps -a                      # 列出所有容器
sudo crictl logs <container-id>        # 查看容器日志
sudo crictl images                     # 查看本地镜像

⚠️ 关键坑:kubeadm 集群默认 CRI 是 containerd,没有 Docker 命令可用。别再敲 docker ps 排查 K8s 容器,crictl 才是正解。另外国内服务器拉镜像慢时,编辑 /etc/containerd/config.tomlregistry.mirrors 配置镜像加速器后 systemctl restart containerd

3.3 升级策略

# 查看可升级版本与计划
sudo kubeadm upgrade plan

# 逐版本升级(先升级 kubeadm,再 kubelet/kubectl,最后部署的组件)
sudo kubeadm upgrade apply v1.30.2
sudo apt-get install -y kubelet=1.30.2-* kubectl=1.30.2-*
sudo systemctl restart kubelet

规则:不要跨大版本升级(如 1.28 → 1.30),必须 1.28 → 1.29 → 1.30 逐级升;升级前务必先做 etcd 快照。


总结

场景 推荐方案 理由
单机多容器应用 Docker Compose 轻量、声明式、学习成本低
需要自愈/扩缩容/滚动更新 Kubernetes 生产级调度与声明式管理
长期单节点生产 kubeadm 单节点 + 去 taint 与多节点集群同构,可平滑扩容
数据与备份 命名卷 + etcd 快照 容器可销毁,数据不可丢

容器编排的进阶之路,核心不是会敲多少命令,而是理解声明式状态、健康检查、数据生命周期这三个概念。生产环境里每一次「玄学故障」,追根溯源几乎都能落到这三个维度上。

下一期我们将进入第 26 天(重访):高可用与负载均衡——Keepalived + HAProxy 集群方案的生产级实践,看看如何让单点变成高可用、让流量分配更加智能,敬请期待!

系列目录

天数 主题 状态
第 1 天 Ubuntu 简介与版本选择
第 2 天 手把手安装 Ubuntu
第 3 天 Ubuntu 桌面环境初探
第 4 天 Ubuntu 终端基础
第 5 天 用户与权限管理
第 6 天 软件包管理
第 7 天 文件与文本操作
第 8 天 系统服务管理
第 9 天 磁盘与文件系统管理
第 10 天 网络配置与管理
第 11 天 进程管理与监控
第 12 天 计划任务与自动化
第 13 天 备份与恢复策略
第 14 天 系统更新与升级管理
第 15 天 SSH 远程管理与安全加固
第 16 天 Web 服务器搭建
第 17 天 数据库服务器部署
第 18 天 Docker 安装与容器化管理
第 19 天 文件共享服务
第 20 天 监控与告警系统
第 21 天 邮件服务器基础
第 22 天 系统安全加固
第 23 天 性能调优与内核参数
第 24 天 KVM 虚拟化高级实战(重访)
第 25 天 容器编排进阶(重访) 🟢 今日
第 26 天 高可用与负载均衡
第 27 天 Ubuntu 自动部署
第 28 天 故障排查实战
第 29 天 Ubuntu 社区与文档
第 30 天 30 天回顾总结
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片快捷回复

    暂无评论内容