核心业务 Kubernetes 容器化交付平台建设部署笔记
项目时间:2025.03—2026.04。
0. 部署线路图
这条线路只做一件事:代码合并后,经 Jenkins 构建并推送带唯一标签的 Harbor 镜像,再滚动发布到 Kubernetes;发布后以 HTTPS、Pod 状态和监控指标验收,异常只回滚已经开始发布的 Deployment revision。
0.1 飞书课程真实截图:CI/CD 流程

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

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

截图展示 Jenkins Job 的 GitLab Push 触发器。生产中该 URL 必须使用参数表中的 HTTPS 地址,Push 事件只允许受保护的发布分支触发;课程截图中的 HTTP 域名仅用于说明页面位置,不作为生产访问地址。
| 层级 | 本项目使用的组件 | 完成标准 |
|---|---|---|
| 集群入口 | LB/VIP、3 台 Control Plane | kubectl get nodes 中控制面和 Worker 均为 Ready |
| 容器运行 | containerd、kubeadm、Calico | Pod 网络跨节点可通信 |
| 镜像交付 | GitLab、Jenkins、Harbor | 每次发布都有镜像标签、构建记录和回滚版本 |
| 业务入口 | Ingress Nginx、TLS、Service | 域名 HTTPS 返回业务健康页 |
| 可观测性 | Prometheus、Grafana、Alertmanager | 节点、Pod、接口和发布结果均可查看/告警 |
1. 开工前只填这一张参数表
下面是一套中型内部业务平台的示例值。部署前把这一张表改成公司资产表的真实值;后面代码出现相同值时,不要临时改成另一套。
| 参数 | 本笔记示例值 | 生产中填什么 |
|---|---|---|
| API Server 统一地址 | 10.10.0.10:6443 | LB 或 VIP 地址和端口 |
| 控制面节点 | k8s-cp-01=10.10.0.11、k8s-cp-02=10.10.0.12、k8s-cp-03=10.10.0.13 | 三台不同主机名/IP |
| Worker 节点 | k8s-wk-01=10.10.0.21、k8s-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.33 | Jenkins 控制器与受控构建 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.internal | DNS 已解析到 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.git | Job 的唯一 SCM 仓库地址 |
| GitLab 只读凭据 | gitlab-readonly-business-prod | Jenkins Credentials 中的 GitLab 项目只读访问令牌名称 |
| Jenkinsfile 路径 | Jenkinsfile | 仓库根目录中的唯一流水线文件 |
| Webhook 地址 | https://jenkins.ops.internal/project/business-prod-knowledge-api/ | Jenkins Job 保存后显示的 GitLab webhook URL |
| Webhook 凭据 | gitlab-webhook-business-prod | GitLab 与 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 节一致;新机器上 kubelet 为 inactive/未安装;没有需保留的容器。 通过标准:每台机器的主机名、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 -h 的 Swap: 行为 0B;sysctl net.ipv4.ip_forward 输出 1;chronyc 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 输出 active;kubeadm version 和 kubelet --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-02 与 k8s-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
预期结果:节点为 Ready;coredns、calico-node、kube-proxy 为 Running;最后一条能解析 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-01、ops-harbor-01、ops-jenkins-01;Kubernetes 只承载业务与发布目标。三个域名均使用可信 HTTPS 证书,数据盘与备份盘分开挂载。
9.0 初始化三个运维平台主机
执行机器:ops-gitlab-01、ops-harbor-01、ops-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 登录页验收;截图原课程 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 项目页面验收。本项目创建 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-prod、harbor-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 均 Running 且 READY 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。 不通过先处理:CrashLoopBackOff 用 kubectl -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 200 或 HTTP/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。

1. 在 Jenkins 新建名称为 business-prod-knowledge-api 的 Pipeline 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 控制台依次显示 Checkout、Branch Gate、Unit Test、Build and Push、Deploy、Verify;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
预期结果:可用副本等于期望副本;没有持续新增的 Unhealthy、BackOff、FailedScheduling 事件;Grafana 中节点、Pod 和接口指标无异常尖刺。 不通过先处理:先根据事件分类处理:FailedScheduling 查 requests/limits 与节点资源;OOMKilled 查内存 limit 和应用泄漏;Readiness probe failed 查健康接口、依赖服务及启动耗时。
14. 生产故障定位顺序
| 现象 | 第一条命令 | 判断方向 | 常见修复 |
|---|---|---|---|
| Pod Pending | kubectl -n business-prod describe pod <pod名> | 调度、PVC、污点、资源 | 扩容节点/调整 requests/修复 StorageClass |
| CrashLoopBackOff | kubectl -n business-prod logs <pod名> --previous | 应用启动、配置、依赖 | 修正 ConfigMap/Secret/启动参数后重新发布 |
| ImagePullBackOff | kubectl -n business-prod describe pod <pod名> | Harbor 地址、tag、认证、证书 | 修正镜像路径或 harbor-pull 凭据 |
| Service 不通 | kubectl -n business-prod get svc,endpoints | selector、Ready Pod、端口 | 保证标签一致,Service 80 → targetPort 8080 |
| Ingress 502/504 | kubectl -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-01、ops-harbor-01、ops-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. 最终验收清单
- [ ] API 统一通过
10.10.0.10:6443访问,不依赖某一台控制面 IP。
- [ ] 3 台控制面和所有 Worker 都是
Ready。
- [ ] Calico、CoreDNS、Ingress Nginx 均正常运行。
- [ ]
https://gitlab.ops.internal、https://harbor.ops.internal、https://jenkins.ops.internal均为有效 HTTPS 页面,三台运维平台主机的数据与备份目录均可用。
- [ ] GitLab 的
main为受保护分支;Jenkins 从business/knowledge-api.git的main/Jenkinsfile读取流水线;Webhook 测试投递为200。
- [ ] Harbor 镜像使用固定 tag,业务命名空间只使用最小权限
imagePullSecret。
- [ ]
knowledge-api有 2 个 Ready Pod、2 个 Service Endpoints,PVC 为Bound。
- [ ]
https://knowledge-api.ops.internal/healthz返回200。
- [ ] Jenkins 有构建记录、Harbor 有镜像 tag、Deployment 有 revision,回滚命令已演练。
- [ ] Grafana 能看到节点/Pod/业务指标;Alertmanager 路由已在非生产告警中验证。
- [ ] GitLab、Harbor、Jenkins 的当日备份文件已上传至受控备份库,并在隔离环境完成至少一次恢复演练。
项目交付物应归档:资产表、网络与端口放行单、Kubernetes/Calico/Ingress 版本、Harbor 项目及机器人账号权限、全部 YAML 所在 Git 仓库、Jenkins 构建记录、发布与回滚记录、监控仪表盘和告警规则链接。