核心业务 Kubernetes 容器化交付平台建设部署笔记

项目时间:2025.03—2026.04。

0. 部署线路图

核心业务容器化交付部署线路图

这条线路只做一件事:代码合并后,经 Jenkins 构建并推送带唯一标签的 Harbor 镜像,再滚动发布到 Kubernetes;发布后以 HTTPS、Pod 状态和监控指标验收,异常只回滚已经开始发布的 Deployment revision。

0.1 飞书课程真实截图:CI/CD 流程

飞书课程中的 CI/CD 流程截图

截图来自已授权课程《阶段三:Kubernetes 基于 Jenkins+GitLab 实现 CICD》,展示代码仓库、持续集成、镜像仓库、测试/生产环境与版本回滚的关系。本项目实施时以本节线路图的 Kubernetes、Harbor、Jenkins 名称和 HTTPS 验收为准。

0.2 飞书课程真实截图:Jenkins 成功构建

飞书课程中的 Jenkins Pipeline 成功构建截图

通过条件:Jenkins 阶段视图中本次构建为绿色成功;本项目实际流水线还必须继续通过镜像推送、滚动发布和 HTTPS 验收,不能只以“拉取代码成功”作为上线结论。

0.3 飞书课程真实截图:GitLab Webhook 触发器

飞书课程中的 Jenkins GitLab Webhook 触发器截图

截图展示 Jenkins Job 的 GitLab Push 触发器。生产中该 URL 必须使用参数表中的 HTTPS 地址,Push 事件只允许受保护的发布分支触发;课程截图中的 HTTP 域名仅用于说明页面位置,不作为生产访问地址。

层级本项目使用的组件完成标准
集群入口LB/VIP、3 台 Control Planekubectl get nodes 中控制面和 Worker 均为 Ready
容器运行containerd、kubeadm、CalicoPod 网络跨节点可通信
镜像交付GitLab、Jenkins、Harbor每次发布都有镜像标签、构建记录和回滚版本
业务入口Ingress Nginx、TLS、Service域名 HTTPS 返回业务健康页
可观测性Prometheus、Grafana、Alertmanager节点、Pod、接口和发布结果均可查看/告警

1. 开工前只填这一张参数表

下面是一套中型内部业务平台的示例值。部署前把这一张表改成公司资产表的真实值;后面代码出现相同值时,不要临时改成另一套。

参数本笔记示例值生产中填什么
API Server 统一地址10.10.0.10:6443LB 或 VIP 地址和端口
控制面节点k8s-cp-01=10.10.0.11k8s-cp-02=10.10.0.12k8s-cp-03=10.10.0.13三台不同主机名/IP
Worker 节点k8s-wk-01=10.10.0.21k8s-wk-02=10.10.0.22按资源池增加
GitLab 主机ops-gitlab-01=10.10.0.31独立的 GitLab 数据与备份主机
Harbor 主机ops-harbor-01=10.10.0.32独立的镜像仓库数据与备份主机
Jenkins 主机ops-jenkins-01=10.10.0.33Jenkins 控制器与受控构建 Agent 所在主机
Pod 网段10.244.0.0/16不得与公司网段、VPN 网段冲突
Service 网段10.96.0.0/12不得与公司网段、VPN 网段冲突
Kubernetes 版本v1.30公司批准的小版本
Calico 版本v3.28.1与 Kubernetes 兼容且已验证的版本
Harbor 地址harbor.ops.internal内部 Harbor 域名,必须有可信证书
业务存储fast-rwo / csi-rbd-snapclass已验证的 StorageClass / VolumeSnapshotClass
业务命名空间business-prod团队/环境对应的 Namespace
业务域名knowledge-api.ops.internalDNS 已解析到 Ingress 地址
Jenkins 地址https://jenkins.ops.internal解析到 ops-jenkins-01 的 Jenkins URL
GitLab 地址https://gitlab.ops.internal解析到 ops-gitlab-01 的 GitLab URL
GitLab 仓库https://gitlab.ops.internal/business/knowledge-api.gitJob 的唯一 SCM 仓库地址
GitLab 只读凭据gitlab-readonly-business-prodJenkins Credentials 中的 GitLab 项目只读访问令牌名称
Jenkinsfile 路径Jenkinsfile仓库根目录中的唯一流水线文件
Webhook 地址https://jenkins.ops.internal/project/business-prod-knowledge-api/Jenkins Job 保存后显示的 GitLab webhook URL
Webhook 凭据gitlab-webhook-business-prodGitLab 与 Jenkins 中同一受管密钥的名称,不展示密钥值
必看:资源底线。 每台控制面建议至少 4 vCPU / 8 GiB 内存;Worker 至少 4 vCPU / 8 GiB。etcd 需要低延迟磁盘,不能把控制面与大规模业务混在同一台小规格虚拟机上。

2. 先盘点:所有候选节点执行

执行机器:3 台控制面和所有 Worker。 目的:确认机器真的是可初始化的新节点;如果看到运行中的业务、数据库、未知端口,立刻停止并走变更评估,不能 kubeadm reset

# 查看系统发行版和版本。
cat /etc/os-release
# 查看当前主机名;每台节点必须不同。
hostnamectl --static
# 查看网卡、IPv4 地址和状态。
ip -br addr
# 查看默认路由,确认网络出口正确。
ip route
# 查看 CPU、内存与 Swap。
nproc
free -h
# 查看根分区可用空间。
df -h /
# 查看当前是否已有容器或 kubelet。
docker ps -a 2>/dev/null || true
crictl ps -a 2>/dev/null || true
systemctl is-active kubelet 2>/dev/null || true

预期结果:主机名、IP 与第 1 节一致;新机器上 kubeletinactive/未安装;没有需保留的容器。 通过标准:每台机器的主机名、IP、/etc/machine-id 均唯一,资源满足节点角色。 不通过先处理:发现旧业务、已有 Kubernetes 或数据盘时,不继续本手册的初始化段;先备份、确认归属和变更窗口。

3. 节点基础初始化:所有节点执行

执行机器:3 台控制面和所有 Worker。以下以 CentOS Stream / Rocky / RHEL 9 为例。

# 启动时间同步服务并设为开机自启。
systemctl enable --now chronyd
# 加载容器 Overlay 文件系统模块。
modprobe overlay
# 加载 Kubernetes 桥接网络过滤模块。
modprobe br_netfilter
# 写入开机自动加载的内核模块。
cat >/etc/modules-load.d/k8s.conf <<'EOF'
# 容器镜像分层文件系统模块。
overlay
# 让桥接流量进入 iptables/nftables 规则链。
br_netfilter
EOF
# 写入 Kubernetes 所需的内核网络参数。
cat >/etc/sysctl.d/99-kubernetes.conf <<'EOF'
# IPv4 桥接流量经过防火墙规则。
net.bridge.bridge-nf-call-iptables = 1
# IPv6 桥接流量经过防火墙规则。
net.bridge.bridge-nf-call-ip6tables = 1
# 开启 IPv4 转发,Pod 跨节点通信需要它。
net.ipv4.ip_forward = 1
EOF
# 立即加载刚写入的内核参数。
sysctl --system
# 关闭当前 Swap;kubelet 默认要求关闭。
swapoff -a
# 注释 fstab 中的 Swap 挂载,避免重启后恢复。
sed -ri '/\sswap\s/s/^#?/#/' /etc/fstab
# 重新读取 fstab 后验证 Swap 为 0。
free -h

预期结果free -hSwap: 行为 0Bsysctl net.ipv4.ip_forward 输出 1chronyc tracking 可看到时间同步状态。 不通过先处理:如果 swapoff 后仍有 Swap,检查 /etc/fstab 和 zram;如果 ip_forward 不是 1,不要安装 kubeadm,先检查是否有安全基线脚本覆盖该参数。

4. 安装 containerd、kubeadm、kubelet:所有节点执行

执行机器:3 台控制面和所有 Worker。仓库地址必须使用公司批准的镜像源或官方源;下面只给出安装后的关键配置,不建议在生产节点执行来源不明的 curl | bash

# 安装容器运行时与 Kubernetes 节点组件;版本由第 1 节的 v1.30 控制。
dnf install -y containerd.io kubelet-1.30.* kubeadm-1.30.* kubectl-1.30.*
# 生成 containerd 默认配置文件。
containerd config default >/etc/containerd/config.toml
# 将 containerd cgroup 驱动改为 systemd,与 kubelet 保持一致。
sed -ri 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
# 重启 containerd 使配置生效,并设置开机启动。
systemctl enable --now containerd
# 设置 kubelet 开机自启;未加入集群前反复重启是正常现象。
systemctl enable kubelet
# 查看已安装版本。
containerd --version
kubeadm version
kubelet --version
# 检查 containerd 服务状态。
systemctl is-active containerd

预期结果containerd 输出 activekubeadm versionkubelet --version 均为 v1.30.x不通过先处理containerd 未启动时先执行 journalctl -u containerd -n 100 --no-pager;版本不一致时停止安装,统一为第 1 节批准版本。

5. 建立高可用控制面

5.1 在第一台控制面 k8s-cp-01 初始化

执行机器:仅 k8s-cp-01 (10.10.0.11)。API 地址 10.10.0.10 必须先由网络/LB 同事配置好并能转发到控制面 6443;不要把某一台控制面 IP 当作长期入口。

# 预拉取当前 Kubernetes 版本需要的镜像,提前暴露网络/镜像源问题。
kubeadm config images pull --kubernetes-version v1.30.0
# 初始化第一个控制面;control-plane-endpoint 使用统一 LB/VIP 地址。
kubeadm init \
  --kubernetes-version v1.30.0 \
  --control-plane-endpoint 10.10.0.10:6443 \
  --upload-certs \
  --pod-network-cidr 10.244.0.0/16 \
  --service-cidr 10.96.0.0/12 \
  --cri-socket unix:///run/containerd/containerd.sock
# 为 root 配置 kubectl 访问集群的 kubeconfig。
mkdir -p /root/.kube
cp -i /etc/kubernetes/admin.conf /root/.kube/config
chown root:root /root/.kube/config
# 验证 API Server 已可访问。
kubectl get nodes
kubectl get pods -n kube-system

预期结果kubeadm init 最后输出 kubeadm join ... --control-plane 和普通 Worker 的 kubeadm join ... 两类命令;此时 k8s-cp-01 可能暂时是 NotReady,因为还没有安装网络插件。

执行机器k8s-cp-01,root。以下命令重新生成两小时有效的加入脚本;脚本内的 token、CA hash 和证书密钥由当前集群生成,不需要人工填写或复制到文档。

umask 077
worker_join="$(kubeadm token create --ttl 2h --print-join-command)"
control_plane_key="$(kubeadm init phase upload-certs --upload-certs | tail -n 1)"
printf '%s --control-plane --certificate-key %s\n' "$worker_join" "$control_plane_key" > /root/k8s-control-plane-join.sh
printf '%s\n' "$worker_join" > /root/k8s-worker-join.sh
chmod 0700 /root/k8s-control-plane-join.sh /root/k8s-worker-join.sh

通过条件:两个脚本权限均为 -rwx------;脚本内容只保存在 k8s-cp-01 的 root 目录,未写入 Git、知识库或聊天记录。 不通过先处理:如果报 connection refused,检查 VIP/LB 到本机 6443 的转发;如果镜像拉取失败,修复公司镜像仓库或离线镜像包后重试,不能绕过镜像审计。

5.2 在 k8s-cp-02k8s-cp-03 加入控制面

执行机器k8s-cp-01,root。以下命令要求使用已审批的 root SSH 密钥;它把刚生成的一次性脚本传给两台指定控制面并立即执行。

for host in k8s-cp-02 k8s-cp-03; do
  scp -o BatchMode=yes /root/k8s-control-plane-join.sh "root@${host}:/root/k8s-control-plane-join.sh"
  ssh -o BatchMode=yes "root@${host}" 'chmod 0700 /root/k8s-control-plane-join.sh && /root/k8s-control-plane-join.sh && shred -u /root/k8s-control-plane-join.sh'
done

预期结果:两台目标主机分别输出 This node has joined the cluster and a new control plane instance was created不通过先处理:脚本过期、传输失败或加入失败时,不修改脚本内容;回到第 5.1 节重新生成两份脚本后重试。重点检查到 10.10.0.10:6443 的 TCP 连通性和 SSH 主机名解析。

5.3 在全部 Worker 加入集群

执行机器k8s-cp-01,root。以下命令使用同一受控 SSH 通道向两台固定 Worker 发送一次性脚本并立即执行。

for host in k8s-wk-01 k8s-wk-02; do
  scp -o BatchMode=yes /root/k8s-worker-join.sh "root@${host}:/root/k8s-worker-join.sh"
  ssh -o BatchMode=yes "root@${host}" 'chmod 0700 /root/k8s-worker-join.sh && /root/k8s-worker-join.sh && shred -u /root/k8s-worker-join.sh'
done
shred -u /root/k8s-control-plane-join.sh /root/k8s-worker-join.sh

预期结果:每台目标主机末尾出现 This node has joined the cluster;所有加入完成后,k8s-cp-01 不再保留一次性加入脚本。 验收机器:回到 k8s-cp-01

# 查看控制面与 Worker 是否都已注册。
kubectl get nodes -o wide

通过标准:所有节点都能看到;安装 Calico 前状态可能为 NotReady,这是预期状态。 不通过先处理:节点没出现时,在该 Worker 执行 journalctl -u kubelet -n 100 --no-pager,重点检查 6443 连通性、token/hash、DNS 和时间同步。

6. 安装 Calico 网络:在 k8s-cp-01 执行

执行机器:仅 k8s-cp-01。生产中应从内部 Git/制品库获取已审计的 calico-v3.28.1.yaml,下面示例使用明确版本的上游清单;应用前先确认其中的 Pod CIDR 与第 1 节一致。

# 下载固定版本 Calico 清单到当前目录,便于审计与留档。
curl -fL -o calico-v3.28.1.yaml https://raw.githubusercontent.com/projectcalico/calico/v3.28.1/manifests/calico.yaml
# 将 Calico 默认 IPv4 地址池改为本项目规划的 Pod 网段;必须与 kubeadm 的 --pod-network-cidr 一致。
sed -ri 's#192\.168\.0\.0/16#10.244.0.0/16#g' calico-v3.28.1.yaml
# 同时确认 Calico DaemonSet 和改后的 Pod 网段均存在。
grep -nE 'kind: DaemonSet|10\.244\.0\.0/16' calico-v3.28.1.yaml
# 将 Calico 安装到集群。
kubectl apply -f calico-v3.28.1.yaml
# 等待 Calico Pod 启动。
kubectl get pods -n kube-system -l k8s-app=calico-node -w

预期结果:检查输出既有 kind: DaemonSet 又有 10.244.0.0/16;每个节点都有一个 calico-node,状态为 Running;随后 kubectl get nodes 中所有节点转为 Ready不通过先处理calico-node 出现 CrashLoopBackOff 时执行 kubectl -n kube-system describe pod <calico-pod>;重点检查 Pod CIDR 冲突、节点间 UDP/BGP 策略和宿主机防火墙。

7. 集群基础验收:在 k8s-cp-01 执行

# 查看所有节点是否 Ready。
kubectl get nodes
# 查看核心组件状态。
kubectl get pods -n kube-system
# 创建一次临时 DNS 测试 Pod。
kubectl run dns-check --image=busybox:1.36 --restart=Never --rm -i -- nslookup kubernetes.default.svc.cluster.local

预期结果:节点为 Readycorednscalico-nodekube-proxyRunning;最后一条能解析 kubernetes.default.svc.cluster.local不通过先处理:DNS 失败时先看 kubectl get pods -n kube-system -l k8s-app=kube-dns,再看 CoreDNS 日志 kubectl logs -n kube-system deploy/coredns

8. 安装 Ingress Nginx:统一 HTTP/HTTPS 入口

执行机器k8s-cp-01。先从内部 Helm 制品库拿到经过版本审核的 ingress-nginx-4.11.1.tgz;以下示例中的 chart 文件名就是交付物,不在生产环境临时安装未审计的 latest chart。

# 创建 Ingress 控制器专用命名空间。
kubectl create namespace ingress-nginx
# 使用固定 chart 版本安装 Ingress Nginx;controller 副本为 2,避免单点入口故障。
helm upgrade --install ingress-nginx /srv/charts/ingress-nginx-4.11.1.tgz \
  --namespace ingress-nginx \
  --set controller.replicaCount=2 \
  --set controller.service.type=LoadBalancer \
  --set controller.ingressClassResource.name=nginx \
  --set controller.ingressClassResource.default=false \
  --wait --timeout 10m
# 检查控制器 Pod、Service 和 IngressClass。
kubectl -n ingress-nginx get pod,svc
kubectl get ingressclass

预期结果:至少两个 ingress-nginx-controller Pod 为 Running;Service 类型为 LoadBalancer,获得内部/外部入口地址;ingressclass 中存在名称为 nginx 的记录。 不通过先处理:Service 长期 Pending 时,说明集群没有云负载均衡控制器或 MetalLB;不要把 NodePort 当作长期生产入口,先由网络团队提供 LB/VIP 方案。

9. 部署 GitLab、Harbor 与 Jenkins 平台

本节的三个系统是本项目交付对象,不再把它们当成已有前提。GitLab、Harbor 与 Jenkins 分别运行在 ops-gitlab-01ops-harbor-01ops-jenkins-01;Kubernetes 只承载业务与发布目标。三个域名均使用可信 HTTPS 证书,数据盘与备份盘分开挂载。

9.0 初始化三个运维平台主机

执行机器ops-gitlab-01ops-harbor-01ops-jenkins-01范围:三台主机执行第 2、3 节的主机名、时间同步、内核与防火墙盘点;它们不是 Kubernetes 节点,不执行第 4 至第 7 节的 kubeadm、kubelet 或 Calico 操作。

# 安装 Docker Engine 与 Compose 插件;软件源使用已批准的内部镜像源或厂商源。
dnf install -y docker-ce docker-compose-plugin
# 让 Docker 立即启动并随系统启动。
systemctl enable --now docker
# 验证运行时和 Compose 均可用。
docker version --format '{{.Server.Version}}'
docker compose version

执行结果样例

27.5.1
Docker Compose version v2.32.4

通过条件:三台主机的 Docker 服务均为 active,并且只开放 22、80、443;GitLab 另开放受控的 22 用于 Git SSH。 失败排查:docker 未启动先执行 systemctl status docker --no-pager;镜像拉取超时先检查到内部镜像代理或厂商镜像源的出网策略,不通过关闭防火墙绕过。

9.1 部署 GitLab

执行机器ops-gitlab-01固定目录:程序 /opt/gitlab,数据 /data/gitlab,证书 /etc/pki/tls/gitlab,备份 /backup/gitlab

# 创建 GitLab 数据、备份与证书目录;敏感目录只允许 root 访问。
install -d -m 0700 /data/gitlab/{config,logs,data} /backup/gitlab /etc/pki/tls/gitlab
# 将已签发的可信证书和私钥放入固定路径;私钥不复制到 Git 仓库。
install -m 0644 /srv/certificates/gitlab.ops.internal.crt /etc/pki/tls/gitlab/gitlab.ops.internal.crt
install -m 0600 /srv/certificates/gitlab.ops.internal.key /etc/pki/tls/gitlab/gitlab.ops.internal.key
# 写入唯一的 GitLab Compose 配置。
cat >/opt/gitlab/compose.yaml <<'EOF'
services:
  gitlab:
    image: gitlab/gitlab-ce:17.8.1-ce.0
    container_name: gitlab
    restart: unless-stopped
    hostname: gitlab.ops.internal
    shm_size: 256m
    ports:
      - '22:22'
      - '80:80'
      - '443:443'
    environment:
      GITLAB_OMNIBUS_CONFIG: |
        external_url 'https://gitlab.ops.internal'
        nginx['redirect_http_to_https'] = true
        nginx['ssl_certificate'] = '/etc/gitlab/ssl/gitlab.ops.internal.crt'
        nginx['ssl_certificate_key'] = '/etc/gitlab/ssl/gitlab.ops.internal.key'
        gitlab_rails['time_zone'] = 'Asia/Shanghai'
        gitlab_rails['backup_keep_time'] = 604800
    volumes:
      - /data/gitlab/config:/etc/gitlab
      - /data/gitlab/logs:/var/log/gitlab
      - /data/gitlab/data:/var/opt/gitlab
      - /etc/pki/tls/gitlab:/etc/gitlab/ssl:ro
EOF
# 启动 GitLab 并持续观察就绪日志。
docker compose -f /opt/gitlab/compose.yaml up -d
docker logs -f gitlab

执行结果样例

Container gitlab  Started
gitlab Reconfigured!
gitlab Reconfigured!
gitlab: GitLab is now available at https://gitlab.ops.internal
飞书课程中的 GitLab 登录页截图

该截图用于对应 GitLab 登录页验收;截图原课程 URL 为 HTTP,仅说明页面位置,本项目验收必须以 https://gitlab.ops.internal 的有效证书为准。

通过条件:https://gitlab.ops.internal/users/sign_in 使用浏览器返回 HTTPS 登录页;docker compose -f /opt/gitlab/compose.yaml ps 中 GitLab 为 running。 失败排查:容器反复重启先执行 docker logs gitlab --tail 200,重点检查内存、磁盘和证书路径;443 不通先检查云防火墙与主机防火墙;不能在 Webhook 配置中使用 GitLab 容器内部地址。

9.2 部署 Harbor

执行机器ops-harbor-01固定版本:Harbor v2.12.2 离线安装包;程序 /opt/harbor,数据 /data/harbor/data,证书 /etc/pki/tls/harbor,备份 /backup/harbor

# 创建程序、数据、备份与证书目录。
install -d -m 0700 /data/harbor/data /backup/harbor /etc/pki/tls/harbor
install -m 0644 /srv/certificates/harbor.ops.internal.crt /etc/pki/tls/harbor/harbor.ops.internal.crt
install -m 0600 /srv/certificates/harbor.ops.internal.key /etc/pki/tls/harbor/harbor.ops.internal.key
# 解压经校验的离线安装包并生成唯一配置。
tar -xzf /srv/packages/harbor-offline-installer-v2.12.2.tgz -C /opt
mv /opt/harbor /opt/harbor-install
cp /opt/harbor-install/harbor.yml.tmpl /opt/harbor-install/harbor.yml
sed -ri 's|^hostname:.*|hostname: harbor.ops.internal|' /opt/harbor-install/harbor.yml
sed -ri 's|^  certificate:.*|  certificate: /etc/pki/tls/harbor/harbor.ops.internal.crt|' /opt/harbor-install/harbor.yml
sed -ri 's|^  private_key:.*|  private_key: /etc/pki/tls/harbor/harbor.ops.internal.key|' /opt/harbor-install/harbor.yml
sed -ri 's|^data_volume:.*|data_volume: /data/harbor/data|' /opt/harbor-install/harbor.yml
# 从受管文件读入初始管理员密码,写入仅 root 可读的 Harbor 配置后立即清理环境变量。
export HARBOR_ADMIN_PASSWORD="$(< /etc/ops-secrets/harbor-admin-password)"
sed -ri "s|^harbor_admin_password:.*|harbor_admin_password: ${HARBOR_ADMIN_PASSWORD}|" /opt/harbor-install/harbor.yml
chmod 0600 /opt/harbor-install/harbor.yml
unset HARBOR_ADMIN_PASSWORD
# 安装并检查核心容器。
cd /opt/harbor-install && ./install.sh
docker compose ps

执行结果样例

✔ Network harbor_harbor        Created
✔ Container harbor-core        Started
✔ Container nginx              Started
NAME          STATUS
harbor-core   running
registry      running
nginx         running
飞书课程中的 Harbor 项目页截图

该截图对应 Harbor 项目页面验收。本项目创建 business 私有项目时必须使用 https://harbor.ops.internal,不沿用截图中的课程域名。

通过条件:浏览器访问 https://harbor.ops.internal 正常;创建私有项目 business,为 Jenkins 创建仅推送 business/* 的机器人账号,为 Kubernetes 创建仅拉取的机器人账号。 失败排查:x509 错误检查客户端是否信任 Harbor 证书链;镜像推送 denied 检查项目名、机器人账号权限和 tag;不要使用管理员账号写入 Jenkins Credentials。

9.3 部署 Jenkins 控制器

执行机器ops-jenkins-01固定目录:程序 /opt/jenkins,数据 /data/jenkins,证书 /etc/pki/tls/jenkins,备份 /backup/jenkins

# 准备 Jenkins 持久化目录和 HTTPS 反向代理证书。
install -d -m 0700 /data/jenkins /backup/jenkins /etc/pki/tls/jenkins
install -m 0644 /srv/certificates/jenkins.ops.internal.crt /etc/pki/tls/jenkins/jenkins.ops.internal.crt
install -m 0600 /srv/certificates/jenkins.ops.internal.key /etc/pki/tls/jenkins/jenkins.ops.internal.key
# 启动仅监听本机回环地址的 Jenkins 控制器;公网 HTTPS 由主机 Nginx 提供。
cat >/opt/jenkins/compose.yaml <<'EOF'
services:
  jenkins:
    image: jenkins/jenkins:2.479.3-lts-jdk17
    container_name: jenkins
    restart: unless-stopped
    user: '1000:1000'
    ports:
      - '127.0.0.1:8080:8080'
    volumes:
      - /data/jenkins:/var/jenkins_home
EOF
docker compose -f /opt/jenkins/compose.yaml up -d
docker logs -f jenkins
# 安装并配置唯一的 HTTPS 反向代理站点。
dnf install -y nginx
cat >/etc/nginx/conf.d/jenkins.conf <<'EOF'
server {
  listen 80;
  server_name jenkins.ops.internal;
  return 301 https://$host$request_uri;
}
server {
  listen 443 ssl http2;
  server_name jenkins.ops.internal;
  ssl_certificate /etc/pki/tls/jenkins/jenkins.ops.internal.crt;
  ssl_certificate_key /etc/pki/tls/jenkins/jenkins.ops.internal.key;
  client_max_body_size 64m;
  location / {
    proxy_pass http://127.0.0.1:8080;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
  }
}
EOF
nginx -t
systemctl enable --now nginx

执行结果样例

Container jenkins  Started
Jenkins is fully up and running

通过条件:Nginx 已将 https://jenkins.ops.internal 反向代理到 127.0.0.1:8080,浏览器显示 Jenkins 登录页;Jenkins 中创建 gitlab-readonly-business-prodharbor-robot、Kubernetes 最小权限 kubeconfig 三类凭据后,才进入第 12 节。 失败排查:Jenkins 页面 502 先检查 curl -I http://127.0.0.1:8080/login 和 Nginx upstream;启动慢先检查 /data/jenkins 的磁盘空间与属主 1000:1000;构建不能直接把 Docker socket 或集群管理员 kubeconfig 挂给 Jenkins 控制器。

10. 接入 Harbor:镜像可追溯与私有仓库认证

执行机器:Harbor 已由平台团队部署;下面是在 k8s-cp-01 创建业务命名空间和拉取凭据。不要把 Harbor 管理员密码写入 Git 或 YAML。

# 创建生产业务命名空间。
kubectl create namespace business-prod
# 使用最小权限 Harbor 机器人账号创建拉取 Secret;命令会从终端读取密码,不写进历史文件。
kubectl -n business-prod create secret docker-registry harbor-pull \
  --docker-server=harbor.ops.internal \
  --docker-username='robot$business-prod' \
  --docker-password
# 检查 Secret 类型是否正确。
kubectl -n business-prod get secret harbor-pull

预期结果:Secret 类型为 kubernetes.io/dockerconfigjson不通过先处理:如果镜像拉取后报 ImagePullBackOff,执行 kubectl -n business-prod describe pod <pod名>,检查 Harbor 域名证书、机器人账号项目权限、镜像路径及 tag 是否存在。

11. 发布一个真实业务服务:Deployment、Service、ConfigMap、Secret、PVC、Ingress

执行机器k8s-cp-01 或具备受控 kubeconfig 的发布机。此示例用 knowledge-api 代表内部知识服务;业务容器监听 8080,Service 在集群内提供 80

11.1 先创建业务配置、敏感信息和存储声明

# 文件:knowledge-api-base.yaml
# 业务命名空间,所有对象都部署在这里。
apiVersion: v1
kind: ConfigMap
metadata:
  # 应用可读取的非敏感配置名称。
  name: knowledge-api-config
  # 业务生产命名空间。
  namespace: business-prod
data:
  # 应用运行环境标识。
  APP_ENV: production
  # 应用日志等级。
  LOG_LEVEL: info
---
# Kubernetes 核心 API 版本。
apiVersion: v1
# 存放敏感数据的对象;Base64 不是加密,生产中建议外接密钥系统。
kind: Secret
metadata:
  # 数据库访问凭据名称。
  name: knowledge-api-db
  # 业务生产命名空间。
  namespace: business-prod
type: Opaque
stringData:
  # 数据库连接地址;按实际服务替换。
  DB_HOST: mysql-prod.ops.internal
  # 数据库用户名;按实际最小权限账号替换。
  DB_USER: knowledge_api
  # 数据库密码;提交前必须改为密钥系统或受控 Secret 创建流程。
  DB_PASSWORD: ChangeMeBeforeApply
---
# Kubernetes 核心 API 版本。
apiVersion: v1
# 持久化存储申请对象。
kind: PersistentVolumeClaim
metadata:
  # 上传文件或需要持久化的数据卷名称。
  name: knowledge-api-data
  # 业务生产命名空间。
  namespace: business-prod
spec:
  # 单节点读写模式;具体能力取决于 StorageClass。
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      # 申请 20 GiB 存储容量。
      storage: 20Gi
  storageClassName: fast-ssd

配置作用:ConfigMap 放非敏感配置,Secret 放凭据,PVC 申请持久化卷。 生效结果:执行后 ConfigMap/Secret 应存在;PVC 在动态存储正常时变为 Bound注意事项:示例密码 ChangeMeBeforeApply 只为展示字段;不得原样进入生产。fast-ssd 必须替换为 kubectl get storageclass 中真实的生产 StorageClass。

# 先查看集群可用 StorageClass,确认上面 PVC 的 storageClassName 真实存在。
kubectl get storageclass
# 应用基础配置文件。
kubectl apply -f knowledge-api-base.yaml
# 验证三类对象的状态。
kubectl -n business-prod get configmap,secret,pvc

预期结果:PVC 状态为 Bound;若为 Pending,说明没有匹配的 StorageClass/动态供给器,不能继续把它当作已持久化数据。

11.2 创建 Deployment 与 Service

# 文件:knowledge-api-workload.yaml
# Deployment API 版本。
apiVersion: apps/v1
# 声明无状态应用的滚动发布控制器。
kind: Deployment
metadata:
  # 部署名称,也是发布/回滚时使用的名称。
  name: knowledge-api
  # 业务生产命名空间。
  namespace: business-prod
spec:
  # 生产至少运行两个副本,避免单 Pod 故障中断服务。
  replicas: 2
  # 仅保留三次旧版本,支持短期快速回滚。
  revisionHistoryLimit: 3
  selector:
    matchLabels:
      # Deployment 管理同标签的 Pod。
      app.kubernetes.io/name: knowledge-api
  template:
    metadata:
      labels:
        # Pod 标签必须与 selector、Service 完全一致。
        app.kubernetes.io/name: knowledge-api
    spec:
      # 私有 Harbor 镜像拉取认证。
      imagePullSecrets:
        - name: harbor-pull
      containers:
        - name: knowledge-api
          # 不使用 latest,固定镜像版本才可以回滚。
          image: harbor.ops.internal/business/knowledge-api:v1.0.0
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              # 容器内应用实际监听端口。
              containerPort: 8080
          envFrom:
            # 将非敏感配置注入容器环境变量。
            - configMapRef:
                name: knowledge-api-config
            # 将数据库凭据注入容器环境变量。
            - secretRef:
                name: knowledge-api-db
          resources:
            requests:
              # 调度时至少预留 200m CPU。
              cpu: 200m
              # 调度时至少预留 256Mi 内存。
              memory: 256Mi
            limits:
              # 单 Pod 最多使用 1 个 CPU 核心。
              cpu: "1"
              # 单 Pod 最多使用 1Gi 内存,超过会 OOMKilled。
              memory: 1Gi
          readinessProbe:
            httpGet:
              # 就绪检查路径;业务必须实现该接口。
              path: /healthz
              # 访问容器命名端口 http,即 8080。
              port: http
            # 给应用 10 秒启动时间。
            initialDelaySeconds: 10
            # 每 10 秒检查一次。
            periodSeconds: 10
          livenessProbe:
            httpGet:
              # 存活检查路径;失败后 kubelet 会重启容器。
              path: /healthz
              # 访问容器命名端口 http,即 8080。
              port: http
            # 给应用 30 秒启动时间。
            initialDelaySeconds: 30
            # 每 15 秒检查一次。
            periodSeconds: 15
          volumeMounts:
            - name: data
              # 容器内持久化数据挂载路径。
              mountPath: /data
      volumes:
        - name: data
          persistentVolumeClaim:
            # 挂载前一步创建的 PVC。
            claimName: knowledge-api-data
---
# Kubernetes 核心 API 版本。
apiVersion: v1
# 声明集群内部稳定访问入口。
kind: Service
metadata:
  # Service 名称。
  name: knowledge-api
  # 业务生产命名空间。
  namespace: business-prod
spec:
  # 只在集群内部暴露,公网入口由 Ingress 负责。
  type: ClusterIP
  selector:
    # 选择上面 Ready 的同标签 Pod。
    app.kubernetes.io/name: knowledge-api
  ports:
    - name: http
      # Service 提供 80 端口。
      port: 80
      # 真实转发到容器 8080 端口。
      targetPort: 8080

配置作用:Deployment 管理两个可滚动更新的 Pod;Service 用稳定的集群内地址将 80 转到容器 8080生效结果:两个 Pod 均 RunningREADY 1/1,Service 必须有两个 Endpoint。 注意事项requests/limits 要按压测数据调整,不可机械照抄;只要 readinessProbe 未通过,Pod 不会进入 Service Endpoints,这是正常保护。

# 发布工作负载和集群内部 Service。
kubectl apply -f knowledge-api-workload.yaml
# 等待滚动发布完成,超时会直接返回失败。
kubectl -n business-prod rollout status deployment/knowledge-api --timeout=180s
# 查看 Pod、Service 与 Endpoint 是否一一对应。
kubectl -n business-prod get pod,svc,endpoints -l app.kubernetes.io/name=knowledge-api

预期结果rollout status 输出 successfully rolled out;两个 Pod 1/1 Running;Endpoint 中应出现两个 Pod IP 加 8080不通过先处理CrashLoopBackOffkubectl -n business-prod logs deploy/knowledge-api --tail=100;没有 Endpoint 时用 kubectl -n business-prod describe pod <pod名> 查看 Readiness 失败原因。

11.3 创建 Ingress:统一域名、HTTPS 与七层路由

前提:集群已安装 ingress-nginx,DNS 的 knowledge-api.ops.internal 已解析到 Ingress LoadBalancer/VIP,证书 Secret 已由证书系统创建。

# 文件:knowledge-api-ingress.yaml
# Ingress API 版本。
apiVersion: networking.k8s.io/v1
# 声明七层 HTTP/HTTPS 入口。
kind: Ingress
metadata:
  # Ingress 名称。
  name: knowledge-api
  # 业务生产命名空间。
  namespace: business-prod
spec:
  # 使用集群内已经安装的 Nginx IngressClass。
  ingressClassName: nginx
  tls:
    - hosts:
        # 外部用户访问的业务域名。
        - knowledge-api.ops.internal
      # 保存该域名证书和私钥的 Secret。
      secretName: knowledge-api-tls
  rules:
    - host: knowledge-api.ops.internal
      http:
        paths:
          - path: /
            # 匹配根路径及全部子路径。
            pathType: Prefix
            backend:
              service:
                # 后端 Service 名称。
                name: knowledge-api
                port:
                  # 后端使用 Service 的 80 端口。
                  number: 80

配置作用:将 https://knowledge-api.ops.internal/ 的流量送入 knowledge-api Service,再由 Service 转发给 Ready Pod。 生效结果:Ingress 有地址,HTTPS 返回 200,证书域名与访问域名一致。 注意事项:不要将容器 8080 直接写到 Ingress;Ingress 应访问 Service 的 80,再由 targetPort: 8080 到容器。

# 应用 Ingress 配置。
kubectl apply -f knowledge-api-ingress.yaml
# 查看 Ingress 是否已获得地址。
kubectl -n business-prod get ingress knowledge-api
# 从能够解析该域名的客户端验证 HTTPS 响应。
curl -Ik https://knowledge-api.ops.internal/healthz

预期结果curl 第一行是 HTTP/2 200HTTP/1.1 200;Ingress 的 ADDRESS 为入口地址。 不通过先处理502/504 时按顺序检查:Ingress → Service → Endpoints → Pod Readiness → 容器日志。先执行 kubectl -n business-prod get ingress,svc,endpoints,pod,不要直接重启所有 Pod。

12. Jenkins Pipeline:构建、推送、发布、验证、回滚

12.1 GitLab Push 触发 Jenkins

执行机器:Jenkins 控制台与 GitLab 项目维护界面。 前提:Jenkins 使用可信 HTTPS 证书;已安装 GitLab 插件;Job 名称固定为 business-prod-knowledge-api;GitLab 受保护发布分支固定为 main

飞书课程中的 Jenkins 新建 Pipeline 页面截图

1. 在 Jenkins 新建名称为 business-prod-knowledge-apiPipeline Job;Definition 选择 Pipeline script from SCM,SCM 选择 Git。 2. Repository URL 填 https://gitlab.ops.internal/business/knowledge-api.git;Credentials 选择 gitlab-readonly-business-prod;Branch Specifier 填 */main;Script Path 填 Jenkinsfile。保存后不手工构建,后续用 GitLab Webhook 的测试事件完成首次读取验证。 3. 在同一 Job 的“构建触发器”勾选 Build when a change is pushed to GitLab,只启用 Push Events,分支过滤固定为 main;复制页面自动显示的 Webhook 地址。 4. 在 GitLab 项目依次进入“设置 → Webhooks”,填入参数表中的 Webhook 地址,令 Token 关联 gitlab-webhook-business-prod 对应的受管密钥;只勾选 Push events,并开启 SSL verification。 5. 保存后使用 GitLab 的 Test → Push events 发送测试;不得把 Jenkins 内部 Service HTTP 地址或明文 Token 暴露给 GitLab 外部页面。

执行结果样例

Obtained Jenkinsfile from git https://gitlab.ops.internal/business/knowledge-api.git
GitLab Webhook: Push events  200 OK
Jenkins #43: Started by GitLab push by liu.wenqi
Stage View: Checkout → Branch Gate → Unit Test → Build and Push → Deploy → Verify

通过条件:首次构建日志出现 Obtained Jenkinsfile from git;GitLab 最近一次 Webhook 投递为 200;随后 Jenkins 构建原因显示 GitLab push。 失败排查:拉取仓库失败先检查 Repository URL、gitlab-readonly-business-prod 的权限、*/main 与仓库默认分支;404 再核对 Job 名称和 Webhook URL;403 检查两端的受管 Token 与 Jenkins GitLab 插件设置;TLS 失败先检查 Jenkins 证书链和 GitLab 的 SSL verification,不能退回 HTTP。

12.2 Jenkinsfile

执行机器:Jenkins 控制台中创建 Pipeline Job;Jenkins Agent 需要有 Git、JDK、Maven、Docker/BuildKit 和 kubectl,且使用最小权限的 Kubernetes kubeconfig。GitLab、Harbor、Kubernetes 三类凭据必须存到 Jenkins Credentials,不能写进 Jenkinsfile。

// 文件:Jenkinsfile
pipeline {
  // 使用带 Docker 与 kubectl 的受控构建 Agent。
  agent { label 'docker-kubectl' }
  environment {
    // Harbor 镜像地址;BUILD_NUMBER 使每次构建可追溯。
    IMAGE = 'harbor.ops.internal/business/knowledge-api'
    // Kubernetes 命名空间。
    NAMESPACE = 'business-prod'
    // 仅在已提交 Deployment 更新后才允许失败分支执行回滚。
    DEPLOYMENT_STARTED = 'false'
  }
  stages {
    stage('Checkout') {
      steps {
        // 拉取当前 Git 提交代码。
        checkout scm
      }
    }
    stage('Branch Gate') {
      steps {
        script {
          // 只允许 GitLab 的 main 分支进入后续交付阶段。
          if (env.gitlabSourceBranch != 'main') {
            error("只允许 main 发布,当前来源分支:${env.gitlabSourceBranch}")
          }
        }
      }
    }
    stage('Unit Test') {
      steps {
        sh '''
          # 单元测试失败即停止;后续不得构建、推送或发布镜像。
          mvn -B test
        '''
      }
    }
    stage('Build and Push') {
      steps {
        // 使用 Jenkins 保存的 Harbor 凭据登录、构建固定 tag 并推送。
        withCredentials([usernamePassword(credentialsId: 'harbor-robot', usernameVariable: 'HARBOR_USER', passwordVariable: 'HARBOR_PASSWORD')]) {
          sh '''
            # 登录 Harbor,不在日志中输出密码。
            echo "$HARBOR_PASSWORD" | docker login harbor.ops.internal -u "$HARBOR_USER" --password-stdin
            # 构建本次流水线编号的镜像。
            docker build -t "$IMAGE:${BUILD_NUMBER}" .
            # 推送镜像到 Harbor。
            docker push "$IMAGE:${BUILD_NUMBER}"
          '''
        }
      }
    }
    stage('Deploy') {
      steps {
        sh '''
          # 将 Deployment 镜像更新为本次构建版本。
          kubectl -n "$NAMESPACE" set image deployment/knowledge-api knowledge-api="$IMAGE:${BUILD_NUMBER}"
        '''
        script {
          // 仅 set image 成功后才打开回滚开关;构建或推送失败绝不触碰线上 Deployment。
          env.DEPLOYMENT_STARTED = 'true'
        }
        sh '''
          # 等待滚动发布完成,失败时 Pipeline 失败。
          kubectl -n "$NAMESPACE" rollout status deployment/knowledge-api --timeout=180s
        '''
      }
    }
    stage('Verify') {
      steps {
        sh '''
          # 确认 Pod、Service、Endpoint 状态。
          kubectl -n "$NAMESPACE" get pod,svc,endpoints -l app.kubernetes.io/name=knowledge-api
          # 从集群内临时验证健康接口;使用后自动删除测试 Pod。
          kubectl -n "$NAMESPACE" run release-check --image=curlimages/curl:8.8.0 --restart=Never --rm -i -- curl -fsS http://knowledge-api/healthz
        '''
      }
    }
  }
  post {
    failure {
      script {
        // 只有发布已经开始且随后的滚动或验收失败,才回滚上一条 revision。
        if (env.DEPLOYMENT_STARTED == 'true') {
          sh 'kubectl -n business-prod rollout undo deployment/knowledge-api'
          sh 'kubectl -n business-prod rollout status deployment/knowledge-api --timeout=180s'
        }
      }
    }
  }
}

配置作用:流水线先执行单元测试;测试通过后才构建带 BUILD_NUMBER 的镜像、推送 Harbor、更新 Deployment;只有 Deployment 已更新且滚动发布或验收失败,才回滚。 生效结果:Jenkins 控制台依次显示 CheckoutBranch GateUnit TestBuild and PushDeployVerify;Harbor 有对应 tag;Deployment 的镜像版本等于本次构建号。 注意事项:自动回滚只能回到 Kubernetes 仍保留的 revision;数据库结构变更必须采用可前后兼容的迁移方案,不能只依赖 rollout undo

# 在受控发布机查看发布历史与当前镜像。
kubectl -n business-prod rollout history deployment/knowledge-api
kubectl -n business-prod get deployment knowledge-api -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'
# 手动回滚的应急命令;只在发布确认失败后执行。
kubectl -n business-prod rollout undo deployment/knowledge-api

预期结果:发布历史有多个 revision;当前镜像 tag 与 Jenkins 构建号一致;回滚后 rollout status 应重新成功。 不通过先处理:Jenkins 无法发布时检查其 kubeconfig 的 RBAC;若出现 ImagePullBackOff,先确认 Harbor 镜像 tag 和 harbor-pull Secret,不要先改 Deployment 副本数。

13. 发布后监控验收:Prometheus 与 Grafana

执行机器:Prometheus/Grafana 已由监控项目提供;本项目负责确认发布对象可被发现并在发布窗口内观察。

# 查看业务 Pod 的 CPU、内存;Metrics Server 未部署时会报错,需要改从 Prometheus/Grafana 查看。
kubectl -n business-prod top pod -l app.kubernetes.io/name=knowledge-api
# 查看最近发布相关事件,确认没有拉取、探针、调度异常。
kubectl -n business-prod get events --sort-by=.lastTimestamp | tail -n 30
# 查看当前副本和可用副本数。
kubectl -n business-prod get deployment knowledge-api

预期结果:可用副本等于期望副本;没有持续新增的 UnhealthyBackOffFailedScheduling 事件;Grafana 中节点、Pod 和接口指标无异常尖刺。 不通过先处理:先根据事件分类处理:FailedScheduling 查 requests/limits 与节点资源;OOMKilled 查内存 limit 和应用泄漏;Readiness probe failed 查健康接口、依赖服务及启动耗时。

14. 生产故障定位顺序

现象第一条命令判断方向常见修复
Pod Pendingkubectl -n business-prod describe pod <pod名>调度、PVC、污点、资源扩容节点/调整 requests/修复 StorageClass
CrashLoopBackOffkubectl -n business-prod logs <pod名> --previous应用启动、配置、依赖修正 ConfigMap/Secret/启动参数后重新发布
ImagePullBackOffkubectl -n business-prod describe pod <pod名>Harbor 地址、tag、认证、证书修正镜像路径或 harbor-pull 凭据
Service 不通kubectl -n business-prod get svc,endpointsselector、Ready Pod、端口保证标签一致,Service 80 → targetPort 8080
Ingress 502/504kubectl -n business-prod get ingress,svc,endpoints入口、Service、后端按 Ingress → Service → Endpoints → Pod 顺序检查
发布失败kubectl -n business-prod rollout status deploy/knowledge-api新 ReplicaSet、探针、镜像修复后重发;必要时 rollout undo

15. 备份与恢复

15.1 发布前备份

执行机器k8s-cp-01;Jenkins 运行节点;负责业务数据卷的存储管理员。 前提:变更单已记录本次 Git commit、Jenkins build number、Harbor 镜像 tag 和当前 Deployment revision。

# 在 k8s-cp-01 创建当天的本地备份目录;目录权限只给 root。
install -d -m 0700 /var/backups/k8s/$(date +%F)
# 备份 etcd;证书与端点使用 kubeadm 静态 Pod 的本机地址。
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 /var/backups/k8s/$(date +%F)/etcd.db
# 校验快照可读并记录 revision。
ETCDCTL_API=3 etcdctl snapshot status /var/backups/k8s/$(date +%F)/etcd.db --write-out=table
# 导出本项目非敏感对象;Secret 不导出到终端或普通 Git 仓库。
kubectl -n business-prod get deployment,service,ingress,configmap,pvc -o yaml \
  > /var/backups/k8s/$(date +%F)/business-prod-resources.yaml
# 记录当前实际镜像与可回滚 revision。
kubectl -n business-prod get deployment knowledge-api -o yaml \
  > /var/backups/k8s/$(date +%F)/knowledge-api-deployment.yaml
kubectl -n business-prod rollout history deployment/knowledge-api

执行结果样例

Snapshot saved at /var/backups/k8s/2026-08-03/etcd.db
9f68ea, 184352, 7461, 1.30.14
REVISION  CHANGE-CAUSE
41        harbor.ops.internal/business/knowledge-api:20260803.41
42        harbor.ops.internal/business/knowledge-api:20260803.42

通过条件:etcdctl snapshot status 返回一行 revision;两份 YAML 文件存在且非空;Harbor 保留当前镜像和上一稳定镜像;PVC 由存储管理员在受支持的 VolumeSnapshotClass 中创建成功。 失败排查:context deadline exceeded 先检查本机 2379 和证书路径;快照创建失败先检查 /var/backups 可用空间;PVC 快照失败先执行 kubectl get volumesnapshotclass,确认 CSI 驱动支持快照,不能把应用目录直接当作数据库一致性备份。

15.2 恢复顺序

执行机器:受控发布机;etcd 灾难恢复仅由控制面维护人按已审批应急预案在维护窗口执行。 原则:应用发布异常先恢复应用;只有控制面整体不可恢复时才使用 etcd 快照,不能在正常运行的 etcd 上直接覆盖数据目录。

# 业务发布异常:先回到上一条已验证的 Deployment revision。
kubectl -n business-prod rollout undo deployment/knowledge-api
kubectl -n business-prod rollout status deployment/knowledge-api --timeout=180s
# 校验恢复后的 Service Endpoint 和集群内健康接口。
kubectl -n business-prod get pod,svc,endpoints -l app.kubernetes.io/name=knowledge-api
kubectl -n business-prod run recovery-check --image=curlimages/curl:8.8.0 --restart=Never --rm -i -- \
  curl -fsS http://knowledge-api/healthz
# 业务对象误删:从当天导出的清单恢复对象,再等待滚动完成。
kubectl apply -f /var/backups/k8s/$(date +%F)/business-prod-resources.yaml
kubectl -n business-prod rollout status deployment/knowledge-api --timeout=180s

执行结果样例

deployment.apps/knowledge-api rolled back
deployment "knowledge-api" successfully rolled out
NAME            ENDPOINTS
knowledge-api   10.244.3.18:8080,10.244.4.21:8080
{"status":"UP"}

通过条件:回滚后的镜像 tag 等于上一稳定版本,两个 Endpoint 均存在,集群内 /healthz 与外部 HTTPS 检查都成功。 失败排查:rollout undo 无历史 revision 时,用已审批的镜像 tag 执行 kubectl set image;Endpoint 为空时依次检查 Pod Ready、Service selector 和容器端口;etcd 级故障停止本节操作,执行独立灾难恢复预案并由控制面维护人恢复快照。

15.3 GitLab、Harbor 与 Jenkins 备份恢复

执行机器:分别在 ops-gitlab-01ops-harbor-01ops-jenkins-01 执行。 规则:平台备份在发布窗口前完成;备份文件上传至受控备份库,不能只留在应用数据盘。

# ops-gitlab-01:创建 GitLab 应用数据备份,并备份配置和证书。
docker exec gitlab gitlab-backup create
tar -C /data/gitlab -czf /backup/gitlab/gitlab-config-$(date +%F).tgz config
find /data/gitlab/data/backups -type f -name '*_gitlab_backup.tar' -printf '%f\n'
# ops-harbor-01:导出 Harbor PostgreSQL 元数据并归档数据目录和配置。
docker exec harbor-db pg_dump -U postgres registry > /backup/harbor/registry-$(date +%F).sql
tar -C /data/harbor -czf /backup/harbor/harbor-data-$(date +%F).tgz data
tar -C /opt -czf /backup/harbor/harbor-config-$(date +%F).tgz harbor-install/harbor.yml
# ops-jenkins-01:停止控制器后归档 Jenkins Home,保证插件、凭据引用和 Job 配置一致。
docker compose -f /opt/jenkins/compose.yaml stop
tar -C /data -czf /backup/jenkins/jenkins-home-$(date +%F).tgz jenkins
docker compose -f /opt/jenkins/compose.yaml up -d

执行结果样例

2026_08_03_17.8.1_gitlab_backup.tar
registry-2026-08-03.sql
harbor-data-2026-08-03.tgz
jenkins-home-2026-08-03.tgz

恢复顺序:先在隔离环境验证备份,再停止对应平台容器并恢复数据,最后启动容器与网页验证;GitLab 使用同一版本容器的 gitlab-backup restore,Harbor 在 harbor-db 中用 psql -U postgres registry < registry-日期.sql 恢复元数据并恢复数据目录,Jenkins 解压对应 jenkins-home-日期.tgz 后启动控制器。恢复后必须重新检查 GitLab Webhook、Harbor 机器人账号和 Jenkins Credentials 是否仍可用。 失败排查:不要跨 GitLab 大版本恢复;Harbor 数据库与镜像存储必须来自同一备份时点;Jenkins 恢复后凭据解密失败,检查是否同时恢复了原 secrets/identity.key.enc

16. 最终验收清单

项目交付物应归档:资产表、网络与端口放行单、Kubernetes/Calico/Ingress 版本、Harbor 项目及机器人账号权限、全部 YAML 所在 Git 仓库、Jenkins 构建记录、发布与回滚记录、监控仪表盘和告警规则链接。