本文记录 2026-07-22 在一主一从 K3s 环境中部署私有化 Gateway 与 CAS 的排障过程,整理成后续可复用 SOP。
适用场景:
-
K3s 私有化部署
-
1 Master + 1 Worker 临时环境
-
NGINX Gateway Fabric 2.5.0
-
Worker 为 CentOS 7 / 3.10 内核,不能升级或更换机器
-
目标通过
jyjzhfw.qiantang.gov.cn访问业务界面
(这里举一反三,因为我的业务是后续要推进k8s。目前手上只有两台服务器给我演练,k3s有条件的就整三台服务器 1master+2 woker),我这边只有两台就灵活性改成1master+1woker。操作步骤都是在mobaxterm上实现。远程连接到两台服务器。
节点规划:
- Master:192.168.X.250 节点名 k3s-master-01
- Worker:192.168.X.251 节点名 k3s-worker-01
服务器是Linux系统(我直接先ip addr确认了ip,然后规划节点,
如果第二台woker就命名k3s-woker-02)以此类推。
步骤 0:进入部署目录,赋予脚本执行权限
bash(全节点都操作)
cd k3s-private/cluster
chmod +x mount-data-disk.sh run.sh bootstrap-node.sh preflight.sh install-server.sh install-agent.sh post-install.sh import-airgap-images.sh
0.2 挂载独立数据盘 /data(bootstrap 前必须执行)
文档约束:/data 不能与根分区 / 共用磁盘
- 查看磁盘设备确认盘符
bash
lsblk
- 执行磁盘格式化挂载脚本(示例 /dev/vdb,按需替换)
我的输出结果:

从 lsblk 输出可见:只有一块磁盘 sda,所有分区都在 sda 上,无独立数据盘 。 文档强制约束:/data 必须是独立挂载点,不能和 / 同一块设备;mount-data-disk.sh 会清空整块磁盘,不能拿 sda 执行脚本。
这里因为我是实验,我打开该脚本对"验证分区必须有两块磁盘的约束"进行了注释,也就是跳过这个part,不检验。
(生产环境必须检验)
操作步骤(两台机器都执行)
1. 编辑 bootstrap-node.sh
bash
vi bootstrap-node.sh
2. 找到磁盘校验代码块,全部注释
搜索关键词:/data 不是独立挂载点 会找到类似这段代码:
bash
# 原始报错代码
DATA_MOUNT=$(findmnt -n -o SOURCE /data)
ROOT_MOUNT=$(findmnt -n -o SOURCE /)
if [ "$DATA_MOUNT" == "$ROOT_MOUNT" ];then
echo "ERROR: /data 不是独立挂载点,请先挂载数据盘到 /data"
exit 1
fi
在每一行前面加 # 注释掉,改成:
bash
# DATA_MOUNT=$(findmnt -n -o SOURCE /data)
# ROOT_MOUNT=$(findmnt -n -o SOURCE /)
# if [ "$DATA_MOUNT" == "$ROOT_MOUNT" ];then
# echo "ERROR: /data 不是独立挂载点,请先挂载数据盘到 /data"
# exit 1
# fi
保存退出 :wq
3. 重新执行 bootstrap(无需加环境变量)
bash
bash run.sh bootstrap
此时不会再拦截磁盘校验,正常跑完基线初始化。
bash
# 普通云盘
DISK=/dev/vdb ./mount-data-disk.sh
# NVMe硬盘使用这条
# DISK=/dev/nvme1n1 PART=/dev/nvme1n1p1 ./mount-data-disk.sh
脚本会提示确认清空磁盘,输入 yes 确认;
- 校验挂载成功
bash
findmnt /data
# 输出中 /data 挂载设备和 / 不一致即合规
0.3 执行节点基线 bootstrap
bash
./run.sh bootstrap
自动关闭 swap、配置 NTP、内核参数、创建 /data/k3s 目录。
1 编辑 install.env(本机唯一配置文件)
bash
vi install.env
写入完整配置:
env
Erlang
FLANNEL_IFACE=eth0
K3S_DATA_DIR=/data/k3s
MASTER_NODE_NAME=k3s-master-01
MASTER_NODE_IP=192.168.X.250 ##根据我们一开始的节点规划如实填写
K3S_NODE_NAME=k3s-master-01
NODE_IP=192.168.X.250 ##根据我们一开始的节点规划如实填写
INSTALL_K3S_MIRROR=cn
2 安装 Master Server(控制面)
bash ##在master节点上操作
./run.sh install
交互提示:请选择安装角色 → 输入 server
2.1 获取集群 Token(本机后续 agent 安装要用)
bash
cat .node-token
# 复制输出内容,保存备用
bash (在woker01节点上操作)
# 替换引号内为上方 cat .node-token 的输出
export K3S_TOKEN='此处粘贴master的token字符串'
3.2 执行 agent 安装
bash
./run.sh install
交互提示:请选择安装角色 → 输入 agent
4 全节点镜像优化(本机执行,post 之前)
若 kube-system 组件出现 ImagePullBackOff,二选一执行:
方案 1:镜像加速(containerd registry 配置)
bash
install -d /etc/rancher/k3s
cp registries.yaml.example /etc/rancher/k3s/registries.yaml
vi /etc/rancher/k3s/registries.yaml
systemctl restart k3s
方案 2:离线镜像导入
bash
./import-airgap-images.sh import
systemctl restart k3s
校验节点就绪
bash
kubectl get nodes
# 此时同一节点会出现两条记录:k3s-master-01(server)、k3s-master-01(agent),全部 Ready 再执行post
kubectl get pods -n kube-system
# 无 ImagePullBackOff、ErrImagePull 异常
验收标准(对齐 quickstart.md)
kubectl get nodes两个节点均 Ready- 节点标签校验通过:
- k3s-master-01(server):
middleware-role=core - k3s-master-01(agent):
workload=ingress、middleware-role=general、ingress-ready=true
排障命令(文档标准工具)
节点预检
bash
# 校验server环境
./run.sh preflight server
# 校验agent环境
./run.sh preflight agent
# 通用全量体检
./run.sh preflight generic
如果是从windows11上传项目文件过去,跑脚本遇到bug
如果是因为换行符问题:
# 批量修复所有sh脚本换行
sed -i 's/\r$//' *.sh lib/*.sh
1、编辑 preflight.sh
bash
vi preflight.sh
找到函数 ensure_data_disk_mounted(),把整个函数全部注释:
bash
# ensure_data_disk_mounted() {
# command -v findmnt >/dev/null 2>&1 || die "缺少 findmnt,无法校验 /data 挂载"
# mountpoint -q /data || die "/data 不是独立挂载点,请先挂载数据盘到 /data"
#
# local root_src data_src
# root_src="$(findmnt -n -o SOURCE / 2>/dev/null || true)"
# data_src="$(findmnt -n -o SOURCE /data 2>/dev/null || true)"
# [[ -n "${data_src}" ]] || die "无法获取 /data 挂载源,请检查挂载状态"
# [[ "${root_src}" != "${data_src}" ]] || die "/data 与根分区使用同一设备,请将 /data 挂载到独立数据盘"
# [[ -d /data/k3s ]] || die "缺少 /data/k3s,请先执行 ./bootstrap-node.sh 初始化目录"
# [[ -w /data/k3s ]] || die "/data/k3s 不可写,请检查目录权限"
# }
往下找到调用这一行,直接注释:
bash
ensure_data_disk_mounted
改成
bash
# ensure_data_disk_mounted
保存退出 :wq
2、清理所有文件 Windows 换行符(必做,防止 \r 报错)
bash
sed -i 's/\r$//' *.sh install.env lib/*.sh
3、重新执行预检
bash
bash run.sh preflight generic
此时不会再拦截 /data 独立磁盘校验。
补充关键信息
- 你的网卡是
ens192,后面写install.env里FLANNEL_IFACE=ens192,不能写 eth0; - 两台机器(250、251)的
preflight.sh都要做一模一样的修改; - 预检输出
预检通过之后,再去配置install.env
安装完成后验证:(master上操作)
bash
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
kubectl get nodes
能看到两个ready再往下看,第一阶段告一段落。
-------------------------------------------------------------------------
POST 阶段(在master上操作)
##该运行./post-install.sh脚本(先修改脚本适配一主一从)
执行修改命令快速替换(不用手动复制)
bash
sed -i 's/\[\[ "${READY}" -ge 3 \]\] || warn "期望 3 节点 Ready(1 Master + 2 Worker)/\[\[ "${READY}" -ge 2 \]\] || warn "期望 2 节点 Ready(1 Master + 1 Worker)/' post-install.sh
sed -i 's/# Master 上、2 个 Worker 均为 Ready 后执行/# Master 上、1 个 Worker 均为 Ready(总共2节点)后执行/' post-install.sh
1. 编辑 node-labels.yaml,删掉 k3s-worker-02 那段配置
bash
vi node-labels.yaml
只保留 master + worker01 两段,删除 k3s-worker-02 对应的完整 yaml 块。 标准适配 1 主 1 从的 yaml 示例:
yaml
apiVersion: v1
kind: Node
metadata:
name: k3s-master-01
labels:
middleware-role: core
spec: {}
---
apiVersion: v1
kind: Node
metadata:
name: k3s-worker-01
labels:
workload: business
ingress-ready: "true"
spec: {}
2. 再次清理换行符兜底
bash
sed -i 's/\r$//' node-labels.yaml
3. 重新执行后置脚本
bash
./post-install.sh
不需要在 251 worker 节点执行 post-install.sh。
仅 Master 节点执行
- 脚本依赖
kubectl操作集群资源、打节点标签、安装集群存储类 (local-path); - worker 节点默认没有
/etc/rancher/k3s/k3s.yamlkubeconfig 凭证,执行会直接鉴权失败; - 节点标签、存储插件是集群全局资源,只需要在控制面操作一次全集群生效。
- 192.168.3.250(Master):唯一执行
./post-install.sh的机器,完成节点打标、local-path 存储安装; - 192.168.3.251(Worker):仅执行
install→ agent 加入集群,不用跑 post、不用跑 verify。
namespace阶段
1、先手动创建缺失命名空间
bash
kubectl create namespace local-path-storage
2、重新应用 local-path 配置文件
bash
kubectl apply -f storage/local-path-config.yaml
3、验证 local-path 组件是否正常运行
bash
kubectl get pods -n local-path-storage
正常会出现 local-path-provisioner-xxx Pod,状态 Running。
执行完后再完整跑一遍后置脚本收尾:
bash
./post-install.sh
重新强制应用配置文件即可:
bash
kubectl apply -f storage/local-path-config.yaml
检查 pod 状态确认组件正常
bash
kubectl get pods -n local-path-storage
正常输出类似:
plaintext
NAME READY STATUS RESTARTS AGE
local-path-provisioner-7f9d76c89d-xxxxx 1/1 Running 0
配置 k3s 镜像加速(Master+251 Worker 两台都要做)
1.1 创建加速器配置文件
bash
vi /etc/rancher/k3s/registries.yaml
写入完整镜像源配置:
yaml
mirrors:
docker.io:
endpoint:
- "https://docker.mirrors.ustc.edu.cn"
gcr.io:
endpoint:
- "https://gcr.mirrors.ustc.edu.cn"
k8s.gcr.io:
endpoint:
- "https://k8s.mirrors.ustc.edu.cn"
quay.io:
endpoint:
- "https://quay.mirrors.ustc.edu.cn"
1.2 重启 k3s 加载配置
- Master(250):
bash
systemctl restart k3s
- Worker(251):
bash
systemctl restart k3s-agent
步骤 2:清理卡住的 Pod,重新拉取镜像
bash
kubectl delete pod -n local-path-storage --all
删除后控制器会新建 Pod,自动走国内镜像源下载 pause 和 local-path 镜像。
步骤 3:等待 Pod 就绪
bash
kubectl rollout status deployment/local-path-provisioner -n local-path-storage
出现 successfully rolled out 代表存储组件正常。
步骤 4:重新执行后置脚本收尾
bash
./post-install.sh
两台机器(250 master、251 worker)全部执行导入:
- 两台节点都进入 cluster 目录,执行离线导入脚本
bash
cd /root/k8s-config/k3s-private/cluster
chmod +x import-airgap-images.sh
./import-airgap-images.sh import
- 分别重启服务
- Master 250:
systemctl restart k3s - Worker 251:
systemctl restart k3s-agent
- 删除卡住的 Pod 重建
bash
kubectl delete pod -n local-path-storage --all
- 等待 Pod Running,再跑 post-install.sh
报错根因
脚本开头 export KUBECONFIG="${KUBECONFIG:-/etc/rancher/k3s/k3s.yaml}" 失效,kubectl 没读到集群证书,默认去连无权限的本地 8080 端口,连接被拒绝。
分两种场景处理
场景 1:你现在在 Master 250 执行(正常应该有 kubeconfig)
- 先确认 kubeconfig 文件存在且可读
bash
ls -l /etc/rancher/k3s/k3s.yaml
cat /etc/rancher/k3s/k3s.yaml
文件缺失 / 权限不足修复:
bash
# 修复权限
chmod 600 /etc/rancher/k3s/k3s.yaml
chown root:root /etc/rancher/k3s/k3s.yaml
- 手动强制注入 kubeconfig 再跑脚本
bash
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
./post-install.sh
脚本全部执行成功,集群基础组件部署完成
Headlamp K8s Web 控制台部署步骤(master)
单独分步部署(一键脚本异常时使用)
bash
cd /root/k8s-config/k3s-private
kubectl apply -f middleware/headlamp/rbac.yaml
kubectl apply -f middleware/headlamp/deployment.yaml
kubectl apply -f middleware/headlamp/service.yaml
二、发布外网 Gateway 路由(提供域名访问)
bash
bash ./scripts/apply-gateway-routes.sh
报错原因
缺少 ops-prod 命名空间,所有 headlamp 资源都部署在这个 ns 下,先创建再应用 yaml。
1、创建业务命名空间 ops-prod
bash
kubectl create namespace ops-prod
2、重新依次部署 headlamp 资源
bash
kubectl apply -f middleware/headlamp/rbac.yaml
kubectl apply -f middleware/headlamp/deployment.yaml
kubectl apply -f middleware/headlamp/service.yaml
3、发布网关路由(外网访问地址)
bash
bash ./scripts/apply-gateway-routes.sh
4、检查 Pod 启动状态
bash
kubectl get pods -n ops-prod -l app=headlamp
等待 Pod 变为 1/1 Running。
gateway阶段(master)
前置检查
bash
cd /root/k8s-config/k3s-private
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
kubectl get ns ops-prod # 确认命名空间已创建
kubectl get nodes -L ingress-ready
# 你只有k3s-worker-01带ingress-ready=true,NGF两个副本都会落在这台机器
步骤 1:安装 helm3(文档推荐华为云镜像脚本)
bash
bash ./scripts/install-helm.sh
# 验证安装
helm version
步骤 2:安装 NGINX Gateway Fabric(网关核心,CRD+GatewayClass+Gateway)
bash
bash ./scripts/apply-gateway.sh
验证是否部署成功:
bash
kubectl get gatewayclass
kubectl get gateway -n ops-prod
kubectl get svc -n ops-prod | grep private-gateway-nginx
# 正常输出:80:30080/TCP NodePort
步骤 3:发布 HTTP 路由(包含 headlamp 控制台路由)
bash
bash ./scripts/apply-gateway-routes.sh
# 校验路由生成
kubectl get httproute -A
步骤 4:安全策略校验(拦截敏感接口 / 目录)
bash
bash ./scripts/verify-actuator-security.sh
##NFG镜像缺失解决:
步骤 1:Worker 节点同步完全相同 registries.yaml
登录 k3s-worker-01(192.168.3.251) 执行:
bash
mkdir -p /etc/rancher/k3s
# 复制和Master完全一致的镜像加速配置
scp root@192.168.3.250:/etc/rancher/k3s/registries.yaml /etc/rancher/k3s/
# 重启k3s-agent加载配置
systemctl restart k3s-agent
步骤 2:两台节点分别测试拉取 NGF 镜像
Master (250) 测试
bash
k3s ctr images pull ghcr.io/nginx/nginx-gateway-fabric:2.5.0
k3s ctr images pull ghcr.io/nginx/nginx-gateway-fabric/nginx:2.5.0
Worker (251) 测试
bash
k3s ctr images pull ghcr.io/nginx/nginx-gateway-fabric:2.5.0
k3s ctr images pull ghcr.io/nginx/nginx-gateway-fabric/nginx:2.5.0
拉取命令必须写 ghcr.io 原始地址,不能替换 ghcr.m.daocloud.io,配置文件会自动转发到 DaoCloud 镜像站。
- 拉取成功:直接跳步骤 4;
- 出现 403 / 连接超时:修改 registries.yaml 中 ghcr 端点为
https://ghcr.lank8s.cn,两台节点同步重启服务重试。
结果判定
Worker 251 节点 NGF nginx 镜像拉取完全成功,耗时约 8 分钟、总大小 62.6M,镜像完整下载解压完成。
1、Worker 补拉控制面镜像
继续在 251 执行,拉另一核心镜像:
bash
k3s ctr images pull ghcr.io/nginx/nginx-gateway-fabric:2.5.0
等待输出 Completed pull 即两台 NGF 镜像全部就绪。
2、Master 250 同样拉取两套镜像(避免 Master 调度 Pod 缺镜像)
bash
k3s ctr images pull ghcr.io/nginx/nginx-gateway-fabric:2.5.0
k3s ctr images pull ghcr.io/nginx/nginx-gateway-fabric/nginx:2.5.0
3、两台节点验证镜像缓存
任意节点执行校验命令,两条镜像都要出现:
bash
k3s ctr images ls | grep nginx-gateway-fabric
预期输出两行:
plaintext
ghcr.io/nginx/nginx-gateway-fabric:2.5.0
ghcr.io/nginx/nginx-gateway-fabric/nginx:2.5.0
4、Master 清理旧失败 NGF 资源,重装网关
bash
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
# 卸载旧helm release
helm uninstall ngf -n nginx-gateway 2>/dev/null || true
# 清理残留pod/job
kubectl delete pod,job -n nginx-gateway --all --force --grace-period=0
# 重新完整部署NGINX Gateway Fabric
bash ./scripts/apply-gateway.sh
# 实时监控pod启动
kubectl get pods -n nginx-gateway -w
等待所有 Pod 变为 1/1 Running 无拉取报错。
5、网关就绪后发布业务 / 控制台路由
bash
bash ./scripts/apply-gateway-routes.sh
# 校验安全拦截策略
bash ./scripts/verify-actuator-security.sh
# 查看所有HTTP路由(包含headlamp /headlamp 二级路径)
kubectl get httproute -A
6、外网访问说明
SLB 回源地址:k3s-worker-01:30080 控制台访问地址:https://jyjzhfw.qiantang.gov.cn/headlamp/(末尾斜杠必须携带)
补充关键点
- 镜像加速配置已生效,DaoCloud ghcr 源正常分发;
- 两台节点必须同时存在两套 NGF 镜像,cert-generator、数据面 Pod 会随机调度任意节点;
- 后续不会再出现
failed pre-install: timed out waiting for the condition镜像超时报错。
250、Worker 251 节点 NGF nginx 镜像拉取完全成功下一步步骤
步骤 1:清理之前安装失败的 NGF 残留资源
bash
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
# 卸载helm发布
helm uninstall ngf -n nginx-gateway 2>/dev/null || true
# 强制删除所有pod、任务,清除失败缓存
kubectl delete pod,job -n nginx-gateway --all --force --grace-period=0
步骤 2:重新完整部署 NGINX Gateway Fabric
bash
cd /root/k8s-private
bash ./scripts/apply-gateway.sh
脚本会自动:
- 安装 Gateway API 标准 CRD
- Helm 部署 NGF 控制面(cert-generator 任务不再镜像超时)
- 创建集群共享 Gateway、安全拦截策略、上传大小配置
步骤 3:校验网关基础资源是否正常
bash
# 查看网关类(必须存在Accepted状态)
kubectl get gatewayclass
# 查看ops-prod命名空间网关实例
kubectl get gateway -n ops-prod
# 查看NGF服务,确认NodePort=30080
kubectl get svc -n nginx-gateway
# 实时监控所有NGF Pod状态,等待全部1/1 Running
kubectl get pods -n nginx-gateway -w
正常预期:
- nginx-gateway-fabric 控制面 Pod 就绪
- private-gateway-nginx 数据面 Pod 就绪(调度到 worker)
- 无
ImagePullBackOff、ErrImage 拉取错误
步骤 4:发布 HTTP 路由(包含 Headlamp 控制台二级路径)
网关基础组件就绪后,执行路由生成脚本,对外暴露/headlamp/访问入口:
bash
bash ./scripts/apply-gateway-routes.sh
# 校验接口安全拦截规则(屏蔽actuator、敏感map路径403)
bash ./scripts/verify-actuator-security.sh
# 查看全部HTTPRoute资源
kubectl get httproute -A
下一步完整操作(按顺序执行)
步骤 1:发布域名路由(包含 Headlamp 控制台 /headlamp/ 访问路径)
bash
bash /root/k8s-config/k3s-private/scripts/apply-gateway-routes.sh
步骤 2:校验安全拦截规则(屏蔽 actuator、敏感接口)
bash
bash /root/k8s-config/k3s-private/scripts/verify-actuator-security.sh
步骤 3:完整部署 Headlamp 控制台(Web 集群面板)
bash
cd /root/k8s-config/k3s-private
# 依次创建RBAC权限、部署、Service
kubectl apply -f middleware/headlamp/rbac.yaml
kubectl apply -f middleware/headlamp/deployment.yaml
kubectl apply -f middleware/headlamp/service.yaml
步骤 4:查看 Headlamp 运行状态
bash
kubectl get pods -n ops-prod -l app=headlamp
等待 Pod 变为 1/1 Running(启动需 10~30 秒)
DAY02.5当前环境与目标
1. 机器角色
192.168.X.250 = master
负责 Kubernetes 控制面、kubectl、调度、API Server。
192.168.X.251 = worker
负责运行业务容器、Gateway 数据面、页面服务。
2. 最终访问链路
浏览器访问 jyjzhfw.qiantang.gov.cn
|
v
Worker 192.168.X.251:30080
|
v
private-gateway-nginx
|
v
HTTPRoute 按 Host + Path 分发
|
v
CAS / Nacos / 业务 API / UI
3. 项目原始推荐与现场差异
项目文档原本推荐:
1 Master + 2 Worker
现场实际为:
1 Master + 1 Worker
因此 Gateway 数据面必须降级为单副本:
nginx:
replicas: 1
否则 Gateway 会尝试创建两个数据面 Pod,并且 required 反亲和会导致第二个 Pod 无法调度。
二、今天完成到哪里
已完成
Gateway 入口已经修通:
kubectl get gateway -A
结果已达到:
NAMESPACE NAME CLASS ADDRESS PROGRAMMED
ops-prod private-gateway nginx 10.43.X.154 True
Gateway Service 已变为:
private-gateway-nginx NodePort 10.43.x.154 8080:30080/TCP
Gateway Pod 已达到:
private-gateway-nginx-xxx 1/1 Running
Endpoint 已出现:
private-gateway-nginx 10.42.x.55:8080
安全拦截已验证成功:
curl -i -H "Host: jyjzhfw.qiantang.gov.cn" http://192.168.x.251:30080/actuator
返回:
HTTP/1.1 403 Forbidden
{"code":403,"message":"Forbidden"}
这说明:
NodePort 30080 -> Gateway -> SnippetsPolicy 安全规则
这条链路已经正常。
未完成
访问根路径 / 仍未完全成功:
curl -i -H "Host: jyjzhfw.qiantang.gov.cn" http://192.168.x.251:30080/
曾返回:
HTTP/1.1 500 Internal Server Error
原因已经进一步定位到后端 CAS:
-
/路由后端是brain-prod/tianyin-cas-svc:8181 -
一开始
brain-prod里没有 CAS 服务和 Pod -
后来部署了 CAS,但 CAS 曾重启并报数据库配置错误
最后一次 describe pod 显示 CAS 一度变为:
Status: Running
Ready: True
Restart Count: 5
但日志中仍出现过:
Could not load JDBC driver class [org.hsqldb.jdbcDriver]
所以明天第一件事是确认 CAS 是否已经稳定,如果不稳定,继续查 Nacos 中的 CAS 配置。
三、今天遇到的问题与解决过程
问题 1:安全验证脚本默认访问 127.0.0.1:30080,连接失败
现象
执行:
HOST=jyjzhfw.qiantang.gov.cn TARGET=http://192.168.x.251:30080 bash /root/k8s-config/k3s-private/scripts/verify-actuator-security.sh
输出中实际访问的是:
http://127.0.0.1:30080
并报错:
curl: (7) Failed connect to 127.0.0.1:30080; 拒绝连接
FAIL 000000 /actuator (expected 403)
判断
脚本默认变量是 BASE_URL,不是所有版本都支持 TARGET。
正确用法
优先使用:
HOST=jyjzhfw.qiantang.gov.cn BASE_URL=http://192.168.3.251:30080 bash /root/k8s-config/k3s-private/scripts/verify-actuator-security.sh
或者脚本中兼容:
BASE_URL="${BASE_URL:-${TARGET:-http://127.0.0.1:${PORT}}}"
经验
验证 Gateway 时不要默认访问 127.0.0.1,因为 kubectl 在 master 上执行,而 Gateway NodePort 实际在 worker 上。
现场应访问:
http://192.168.3.251:30080
问题 2:误把 31116 当成 Gateway
现象
执行:
curl -I http://192.168.x.250:31116
curl -I http://192.168.x.251:31116
返回:
HTTP/1.1 404 Not Found
判断
查询 Service:
kubectl get svc -A -o wide | grep -E '31116|30080|gateway|private-gateway'
发现:
ops-prod headlamp NodePort 80:31116/TCP
ops-prod private-gateway-nginx NodePort 80:30080/TCP
结论
31116 是 Headlamp,不是业务 Gateway。
业务 Gateway 是:
192.168.x.251:30080
经验
看到 NodePort 时必须先确认 Service 名称:
kubectl get svc -A -o wide | grep NodePort
不要只看端口号。
问题 3:Gateway Pod 卡在 Init:0/1
现象
kubectl get pods -n ops-prod -l gateway.networking.k8s.io/gateway-name=private-gateway -o wide
输出:
private-gateway-nginx-xxx 0/1 Init:0/1 <none> k3s-worker-01
Endpoint 为空:
kubectl get endpoints private-gateway-nginx -n ops-prod
输出:
private-gateway-nginx <none>
根因
Worker 系统:
uname -r
输出:
3.10.0-1160.119.1.el7.x86_64
系统:
CentOS Linux 7
检查:
ls -l /proc/sys/net/ipv4/ip_unprivileged_port_start
输出:
没有那个文件或目录
Gateway init 阶段使用了内核参数:
net.ipv4.ip_unprivileged_port_start
CentOS 7 的 3.10 内核没有这个文件,所以 init 阶段失败。
不能采用的方案
标准解法是升级 worker 内核或更换系统。
但现场限制:
机器不能换,内核不能升级。
所以采用兼容绕过方案。
问题 4:一主一从环境下 Gateway 第二个 Pod Pending
现象
private-gateway-nginx-xxx 0/1 Running
private-gateway-nginx-yyy 0/1 Pending
调度事件:
1 node(s) didn't match pod anti-affinity rules
根因
项目默认按照多 worker 规划,Gateway 数据面原来是 2 副本,并且使用 required podAntiAffinity 分散到不同节点。
现场只有 1 个 worker,所以第二个 Pod 永远调度不出来。
解决
在 k3s-private/gateway/helm-values-ngf.yaml 中设置:
nginx:
replicas: 1
经验
一主一从部署时,所有带强反亲和的入口类组件都要检查副本数。
问题 5:Helm values 文件被误改坏,YAML 解析失败
现象
执行:
bash ./k3s-private/scripts/restart-ngf.sh
报错:
Error: failed to parse /root/k8s-config/k3s-private/gateway/helm-values-ngf.yaml:
yaml: line 40: mapping values are not allowed in this context
查看文件发现错误片段:
rt: 30080
listenerPort: 8080
根因
手动编辑 YAML 时残留了错误行和错误缩进。
解决
重写 helm-values-ngf.yaml 中 Gateway 相关部分,保证缩进正确。
关键配置如下:
nginxGateway:
image:
repository: aiban-docker.pkg.coding.net/tianyin/base/nginx-gateway-fabric
tag: "2.5.0"
gatewayClassName: nginx
snippets:
enable: true
nginx:
image:
repository: aiban-docker.pkg.coding.net/tianyin/base/nginx-gateway-fabric-nginx
tag: "2.5.0"
service:
type: NodePort
externalTrafficPolicy: Local
nodePorts:
- port: 30080
listenerPort: 8080
pod:
nodeSelector:
ingress-ready: "true"
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app.kubernetes.io/managed-by: ngf-nginx
topologyKey: kubernetes.io/hostname
replicas: 1
经验
YAML 出现 mapping values are not allowed,优先检查:
-
冒号后是否乱接值
-
缩进是否混乱
-
是否误把说明文字粘进 YAML
可用:
helm upgrade --dry-run ...
或直接重新打开文件检查缩进。
问题 6:Gateway Programmed=False,Nginx 绑定 80 端口失败
现象
kubectl describe gateway private-gateway -n ops-prod
关键错误:
bind() to 0.0.0.0:80 failed (13: Permission denied)
nginx: configuration file /etc/nginx/nginx.conf test failed
Gateway 状态:
PROGRAMMED False
根因
为了绕过旧内核 sysctl,调整了运行方式后,容器内 Nginx 无法以当前权限绑定 80 端口。
解决思路
不让容器内 Nginx 监听 80,改为监听非特权端口 8080。
对外仍保持 NodePort 30080。
最终链路变为:
外部访问 192.168.x.251:30080
|
v
Service NodePort 30080
|
v
Pod 内 Nginx 8080
修改 1:Gateway listener 改 8080
k3s-private/gateway/gateway.yaml:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: private-gateway
namespace: ops-prod
spec:
gatewayClassName: nginx
listeners:
- name: http
protocol: HTTP
port: 8080
allowedRoutes:
namespaces:
from: All
修改 2:Helm values 中 NodePort 仍为 30080,listenerPort 改 8080
nginx:
service:
type: NodePort
externalTrafficPolicy: Local
nodePorts:
- port: 30080
listenerPort: 8080
重新应用
bash ./k3s-private/scripts/restart-ngf.sh
kubectl apply -f k3s-private/gateway/gateway.yaml
经验
对外 NodePort 不等于 Pod 内监听端口。
这里的正确理解是:
Service: 8080:30080/TCP
含义是:
Pod/Service 端口 8080
NodePort 对外端口 30080
所以用户浏览器仍访问:
http://19x.168.x.251:30080
问题 7:Nginx 启动后 chown 失败
现象
Gateway Pod 变成 Running 但不 Ready,持续重启:
0/1 Running
Readiness probe failed: connect: connection refused
Back-off restarting failed container nginx
查看日志:
kubectl logs private-gateway-nginx-xxx -n ops-prod -c nginx --tail=160
关键错误:
chown("/var/cache/nginx/client_temp", 101) failed (1: Operation not permitted)
根因
容器启动脚本或 Nginx 进程需要执行 chown/setuid/setgid,但当前安全上下文权限不足。
解决
在 helm-values-ngf.yaml 中添加 patch:
nginx:
patches:
- type: StrategicMerge
value:
spec:
strategy:
type: Recreate
template:
spec:
securityContext:
runAsUser: 0
runAsGroup: 0
runAsNonRoot: false
sysctls: []
containers:
- name: nginx
securityContext:
runAsUser: 0
runAsGroup: 0
runAsNonRoot: false
allowPrivilegeEscalation: true
capabilities:
add:
- CHOWN
- SETUID
- SETGID
- NET_BIND_SERVICE
重新应用
bash ./k3s-private/scripts/restart-ngf.sh
kubectl delete pod -n ops-prod -l gateway.networking.k8s.io/gateway-name=private-gateway
kubectl get pods -n ops-prod -l gateway.networking.k8s.io/gateway-name=private-gateway -o wide
最终结果
private-gateway-nginx-xxx 1/1 Running
经验
旧内核 + NGF 默认安全上下文会触发多个权限问题:
-
sysctl 文件不存在
-
绑定 80 端口权限不足
-
chown 权限不足
现场最终组合方案:
去掉 sysctls
容器内监听 8080
NodePort 仍暴露 30080
单副本
补充 CHOWN / SETUID / SETGID / NET_BIND_SERVICE capabilities
问题 8:删除 Pod 后出现旧 ReplicaSet 干扰
现象
删除 Gateway Pod 后,又出现多个不同 hash 的 Pod:
private-gateway-nginx-6f6d875c87-xxx
private-gateway-nginx-6fb857c84f-yyy
一个 Init,一个 Pending。
根因
旧 ReplicaSet 仍然存在,继续拉起旧配置 Pod。
解决
确认 ReplicaSet:
kubectl get rs -n ops-prod | grep private-gateway
删除旧 ReplicaSet:
kubectl delete rs private-gateway-nginx-旧hash -n ops-prod
再查:
kubectl get pods -n ops-prod -l gateway.networking.k8s.io/gateway-name=private-gateway -o wide
经验
如果同一个组件出现两个不同 hash 的 Pod,要看 ReplicaSet:
kubectl get rs -n <namespace>
不要只删 Pod。
问题 9:Gateway Endpoint 为空
现象
kubectl get endpoints private-gateway-nginx -n ops-prod
输出:
private-gateway-nginx <none>
判断
Endpoint 为空不是独立问题,而是说明后端 Pod 没 Ready。
修复后结果
private-gateway-nginx 10.42.x.55:8080
经验
查 Service 是否可用,必须同时看三件事:
kubectl get svc <svc> -n <ns>
kubectl get pods -n <ns> -l <label> -o wide
kubectl get endpoints <svc> -n <ns>
只看 Service 存在没有意义,Endpoint 为空时流量没有真正后端。
问题 10:Gateway 通了,但访问 / 返回 500
现象
curl -i -H "Host: jyjzhfw.qiantang.gov.cn" http://192.16x.x.251:30080/
返回:
HTTP/1.1 500 Internal Server Error
判断
安全路径 /actuator 已经返回 403,说明 Gateway 本身是通的。
/ 返回 500,说明 Gateway 找到了路由,但后端服务存在问题。
检查 HTTPRoute:
kubectl get httproute -A | grep jyjzhfw
kubectl describe httproute -n brain-prod brain-route
发现 / 后端是:
brain-prod/tianyin-cas-svc:8181
但当时:
kubectl get svc -n brain-prod
kubectl get pods -n brain-prod
输出:
No resources found in brain-prod namespace.
根因
CAS 服务根本还没部署。
解决
cd /root/k8s-config
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
kubectl apply -f k3s-private/brain-prod/init-config.yaml
kubectl apply -f k3s-private/brain-prod/tianyin-cas.yaml
创建结果:
service/tianyin-cas-svc created
deployment.apps/tianyin-cas-deploy created
经验
Gateway 500 时,不要马上改 Gateway。
先看路由后端是否存在:
kubectl describe httproute -n <ns> <route-name>
kubectl get svc -n <backend-ns>
kubectl get endpoints <backend-svc> -n <backend-ns>
问题 11:CAS 镜像拉取成功,但容器 Error / 重启
现象
kubectl get pods -n brain-prod -o wide
曾出现:
tianyin-cas-deploy-xxx 0/1 Error RESTARTS=4
镜像拉取成功:
Successfully pulled image "aiban-docker.pkg.coding.net/dongyangxiangmu/dongyang/tianyin-cas:ty_bzb-zhpj-saas-1.1.4"
说明不是镜像仓库问题。
日志关键错误
Caused by: java.lang.IllegalStateException:
Could not load JDBC driver class [org.hsqldb.jdbcDriver]
判断
CAS 启动时创建 DataSource 失败。
它读到的数据库驱动是:
org.hsqldb.jdbcDriver
这是 HSQLDB 测试库驱动,不应该出现在生产 K3s 私有化配置中。
根因方向
k3s-private/brain-prod/init-config.yaml 只提供 Nacos 地址和配置定位:
nacos.config.data-id: tianyin-cas-prod.properties
nacos.config.namespace: f54ff835-1dda-4b27-a174-fe40f416789e
nacos.config.server-addr: http://nacos.ops-prod.svc.cluster.local:8848
项目仓库没有提供完整的 tianyin-cas-prod.properties 内容。
所以 CAS 的真实业务配置需要从 Nacos 导入。
当前高度怀疑:
Nacos 中 tianyin-cas-prod.properties 不存在
或
Nacos 中该配置缺少正确 MySQL datasource 配置
或
配置中仍保留了 org.hsqldb.jdbcDriver
四、可复用部署 SOP
以下 SOP 适用于从明天继续推进。
SOP 0:每次开始前固定环境
在 master 上执行:
cd /root/k8s-config
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
确认节点:
kubectl get nodes -o wide
期望:
k3s-master-xx Ready
k3s-worker-01 Ready
确认 namespace:
kubectl get ns
至少应有:
ops-prod
brain-prod
zhpj-prod
portfolio-prod
tdp-prod
dwh-prod
SOP 1:确认 Gateway 是否健康
kubectl get gateway -A
kubectl get svc private-gateway-nginx -n ops-prod
kubectl get pods -n ops-prod -l gateway.networking.k8s.io/gateway-name=private-gateway -o wide
kubectl get endpoints private-gateway-nginx -n ops-prod
健康标准:
Gateway PROGRAMMED=True
Service NodePort 包含 30080
Pod 1/1 Running
Endpoint 非空
本次现场最终标准:
private-gateway-nginx NodePort 8080:30080/TCP
private-gateway-nginx 10.42.x.x:8080
验证安全策略:
curl -i -H "Host: jyjzhfw.qiantang.gov.cn" http://192.168.x.251:30080/actuator
期望:
HTTP/1.1 403 Forbidden
SOP 2:Gateway 异常时的排查顺序
第一步,看 Gateway 状态:
kubectl describe gateway private-gateway -n ops-prod
常见判断:
Accepted=True, Programmed=False
说明 Gateway 对象被接受,但 Nginx 配置没有成功下发或 reload。
第二步,看数据面 Pod:
kubectl describe pod -n ops-prod -l gateway.networking.k8s.io/gateway-name=private-gateway
第三步,看日志:
kubectl logs -n ops-prod -l gateway.networking.k8s.io/gateway-name=private-gateway -c nginx --tail=160
如果容器反复重启,可尝试:
kubectl logs <pod-name> -n ops-prod -c nginx --previous --tail=160
第四步,看 Endpoint:
kubectl get endpoints private-gateway-nginx -n ops-prod
Endpoint 为空时,先让 Pod Ready,不要继续查业务页面。
SOP 3:一主一从环境下的 Gateway 固定方案
helm-values-ngf.yaml 的关键点:
nginx:
service:
type: NodePort
externalTrafficPolicy: Local
nodePorts:
- port: 30080
listenerPort: 8080
replicas: 1
旧内核兼容 patch:
nginx:
patches:
- type: StrategicMerge
value:
spec:
strategy:
type: Recreate
template:
spec:
securityContext:
runAsUser: 0
runAsGroup: 0
runAsNonRoot: false
sysctls: []
containers:
- name: nginx
securityContext:
runAsUser: 0
runAsGroup: 0
runAsNonRoot: false
allowPrivilegeEscalation: true
capabilities:
add:
- CHOWN
- SETUID
- SETGID
- NET_BIND_SERVICE
gateway.yaml listener:
listeners:
- name: http
protocol: HTTP
port: 8080
应用:
bash ./k3s-private/scripts/restart-ngf.sh
kubectl apply -f k3s-private/gateway/gateway.yaml
kubectl delete pod -n ops-prod -l gateway.networking.k8s.io/gateway-name=private-gateway
等待:
kubectl get pods -n ops-prod -l gateway.networking.k8s.io/gateway-name=private-gateway -w
看到 1/1 Running 后按 Ctrl + C。
SOP 4:部署 CAS
在 master 上执行:
cd /root/k8s-config
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
先应用 init-config:
kubectl apply -f k3s-private/brain-prod/init-config.yaml
再部署 CAS:
kubectl apply -f k3s-private/brain-prod/tianyin-cas.yaml
检查:
kubectl get svc -n brain-prod
kubectl get pods -n brain-prod -o wide
kubectl get endpoints tianyin-cas-svc -n brain-prod
健康标准:
tianyin-cas-svc 存在
tianyin-cas-deploy-xxx 1/1 Running
tianyin-cas-svc Endpoint 非空
SOP 5:CAS 异常时的排查顺序
看 Pod 状态:
kubectl get pods -n brain-prod -l app=tianyin-cas -o wide
看详细事件:
kubectl describe pod -n brain-prod -l app=tianyin-cas
看当前日志:
kubectl logs -n brain-prod -l app=tianyin-cas --tail=200
如果已经重启过,看上一次崩溃日志:
kubectl logs -n brain-prod -l app=tianyin-cas --previous --tail=200
看 Endpoint:
kubectl get endpoints tianyin-cas-svc -n brain-prod
如果 Endpoint 为空,说明 CAS 还没 Ready。
六、常用排障命令速查
集群
kubectl get nodes -o wide
kubectl get ns
kubectl get pods -A -o wide
Service / Endpoint
kubectl get svc -A -o wide
kubectl get endpoints <svc-name> -n <namespace>
Gateway
kubectl get gateway -A
kubectl describe gateway private-gateway -n ops-prod
kubectl get httproute -A
kubectl describe httproute -n brain-prod brain-route
Pod
kubectl get pods -n <namespace> -o wide
kubectl describe pod -n <namespace> <pod-name>
kubectl logs <pod-name> -n <namespace> --tail=200
kubectl logs <pod-name> -n <namespace> --previous --tail=200
按 label 查
kubectl get pods -n brain-prod -l app=tianyin-cas -o wide
kubectl logs -n brain-prod -l app=tianyin-cas --tail=200
kubectl describe pod -n brain-prod -l app=tianyin-cas
Gateway curl
curl -i -H "Host: jyjzhfw.qiantang.gov.cn" http://192.168.3.251:30080/
curl -i -H "Host: jyjzhfw.qiantang.gov.cn" http://192.168.3.251:30080/actuator
curl -I -H "Host: jyjzhfw.qiantang.gov.cn" http://192.168.3.251:30080/nacos/
七、判断口诀
1. Service 存在不代表能访问
必须看 Endpoint。
Service 存在 + Endpoint 为空 = 没有可用后端
2. Gateway 403 是好消息
访问 /actuator 返回 403,说明:
NodePort 通
Gateway 通
安全规则生效
3. Gateway 500 多半看后端
如果安全路径已经 403,但业务路径 500,优先查:
HTTPRoute -> Service -> Endpoint -> Pod Logs
4. Image pulled 不代表应用成功
Successfully pulled image
只说明镜像拉下来了。
应用是否成功要看:
Pod Ready
Endpoint 非空
日志无启动异常
5. CrashLoop 时先看 previous logs
容器已经重启后,当前日志可能不完整。
使用:
kubectl logs <pod> -n <ns> --previous --tail=200
6. 旧内核 Gateway 问题不要反复重装 K3s
本次旧内核问题是:
/proc/sys/net/ipv4/ip_unprivileged_port_start 不存在
解决在 Gateway Helm values 和 listener,不需要重装 K3s。
八、今天沉淀的经验
1. 先分层,再排障
不要把所有问题混在一起看。
按层判断:
机器
K3s 节点
Namespace
中间件
Gateway
HTTPRoute
Service
Endpoint
Pod
应用日志
业务页面
哪一层没通,就停在那一层。
2. NodePort 是入口,不是业务本身
30080 只是把流量送进 Gateway。
真正页面是否能打开,还要看:
HTTPRoute 后端服务是否存在
后端 Pod 是否 Ready
Nacos 配置是否正确
数据库是否可连
3. 一主一从要主动降级副本
凡是原项目按 2 worker 设计的组件,都要检查:
replicas
podAntiAffinity
nodeSelector
topologySpreadConstraints
4. Nacos 配置是业务启动关键
业务 YAML 里通常只有:
Nacos 地址
namespace
dataId
账号密码
真正数据库、Redis、MQ、业务开关等配置在 Nacos。
所以 Spring Boot 启动失败时,不能只看 Kubernetes YAML。
5. 不要把解释文字粘到 shell
今天误把说明文字当命令执行过。
建议以后执行命令时:
-
一次只复制一个代码块
-
看到中文说明不要粘进终端
-
命令前确认以
kubectl、bash、curl、cd等开头
九、当前状态快照
截至 2026-07-22 停止前:
K3s 集群:已可用
Gateway:已修好,Programmed=True
Gateway NodePort:192.168.3.251:30080
Gateway Pod:1/1 Running
Gateway Endpoint:10.42.1.55:8080
Actuator 安全拦截:403,已成功
brain-prod CAS Service:已创建
brain-prod CAS Pod:曾 Error,最后 describe 显示 Ready=True,但重启过 5 次
CAS 待确认问题:Nacos 中 tianyin-cas-prod.properties 的数据库配置