SOP — 修复宿主机重启后 haproxy 被 SELinux 拦截导致 16443 入口不可用¶
变更主题:为 Kubernetes 控制面入口 VIP
192.168.31.8:16443的 haproxy 补齐 SELinux 策略并恢复服务 执行时间:2026-09-29 20:27–20:36(CST)|主机:k8s-master-01(192.168.31.10,唯一控制面)
目录¶
- 背景 2. 目标 3. 前置条件 4. 影响范围 5. 分步操作 6. 验证 7. 回滚 8. 风险说明
附录 A:时间线与证据|附录 B:离线环境替代方案|附录 C:交付物清单
1. 背景¶
2026-09-29 18:45 前后宿主机(dell-R620,192.168.31.249/.250)重启,连带 18 台虚拟机全部重启
(各机 uptime 1h37m–1h44m,与本机一致)。
重启后 K8s 控制面入口不可用,现场现象:
E0929 20:19:58 memcache.go:265] "Unhandled Error" err="couldn't get current server API group list:
Get \"https://192.168.31.8:16443/api?timeout=32s\": dial tcp 192.168.31.8:16443: connect: connection refused"
根因:haproxy.service 启动失败。haproxy 以 haproxy_t 域运行,SELinux(Enforcing)拒绝其
绑定 16443(unreserved_port_t)与连接后端 6443。
该配置自 2026-09-16 起就存在,但当时 SELinux 实际处于 permissive(audit 留有 permissive=1 记录),
故一直被放行;本次重启后 SELinux 以 Enforcing 生效,隐患才显现——这解释了"重启前一直正常、重启后立即故障"。
apiserver(6443)、etcd、kubelet、containerd、keepalived、VIP 本身均正常,故障只在入口代理这一层。
2. 目标¶
变更完成后应达到并可验证的状态:
haproxy服务active,监听0.0.0.0:16443;curl -sk https://192.168.31.8:16443/healthz返回ok;kubectl get nodes返回 4 个节点全部Ready;- SELinux 仍为
Enforcing(不以降低安全等级换取可用性); - 上述修复持久:以后重启主机仍然生效。
3. 前置条件¶
| 项 | 要求 |
|---|---|
| 权限 | 本机 root 免密 ssh 到 k8s-master-01(192.168.31.10) |
| 系统 | Rocky Linux 9.8,targeted 策略,SELinux=Enforcing |
| 工具 | semanage 需 policycoreutils-python-utils(初始缺失,需可访问 dnf 源);semodule / setsebool / ausearch 已具备 |
| 依赖 | 无。apiserver/etcd/kubelet 不动,无需停机 |
| 执行前检查 | systemctl is-active haproxy 为 failed、ss -lntp \| grep 16443 无监听 |
4. 影响范围¶
- 主机:仅
k8s-master-01(192.168.31.10)。本集群已是单控制面。 - 组件:
haproxy.service(VIP 入口代理)、SELinux 策略(一条端口标签类型 + 一个布尔)。 - 是否停服:否。apiserver/etcd/kubelet/containerd 均不重启,业务容器不受影响。
- 窗口:修复动作耗时以秒计;16443 在修复前已中断约 1.7 小时,修复即恢复。
- 业务影响:K8s 数据面(Calico 转发)在期间仍可工作,已在运行的 Pod 未被中断;
控制面 API 不可用导致 kubelet 无法上报、控制器无法调谐(表现为节点上一批 Pod 被标记为
Unknown), API 恢复后由 kubelet 自行重建容器并收敛。
5. 分步操作¶
全部命令在
k8s-master-01上执行;前缀ssh root@192.168.31.10表示从本机远程执行,可省略。 本变更为单机、动作少,未编写实现体,全部为可手工复现的 shell 步骤。
步骤 1:定位根因(只读取证)¶
先确认 haproxy 失败而非 apiserver/etcd 故障:6443 与 2379 在监听、16443 无监听,
即问题只在 haproxy 这一层;再用 audit 日志锁定 SELinux 拒绝。
坑:ss -lntp | grep 16443 无输出时返回码非 0,脚本中需容忍;journalctl 要限时间窗,不要全量拉。
# 1) 控制面组件是否正常
for s in kubelet containerd haproxy keepalived; do printf "%-12s %s\n" "$s" "$(systemctl is-active $s)"; done
ss -lntp | grep -E ":16443|:6443|:2379" || true
# 预期:kubelet/containerd/keepalived=active,haproxy=failed;有 6443、2379 监听,无 16443
# 2) haproxy 失败原因
journalctl -u haproxy --since "-30min" --no-pager -l | tail -n 20
# 预期:cannot bind socket (Permission denied) for [0.0.0.0:16443]
# 3) SELinux 拒绝证据(关键)
getenforce
grep 'type=AVC' /var/log/audit/audit.log | grep -E 'comm="haproxy"|comm="keepalived"' | tail -n 10
失败判断:若 haproxy 的报错不是 Permission denied(例如 Address already in use),
本流程不适用,改按端口占用方向排查;若第 3 步无 AVC 记录,改用 ausearch -m AVC -ts recent。
步骤 2:安装 semanage 工具¶
Rocky 9 的 policycoreutils 不含 semanage,需补 policycoreutils-python-utils。
坑:首次 dnf install 可能因元数据下载耗时而超时(实测 180s 未返回);
加大超时并限制重试次数后成功。若环境离线,可改用 semodule -i 加载等价 CIL 模块(见附录 B)。
dnf -q install -y --setopt=timeout=60 --setopt=retries=1 policycoreutils-python-utils
command -v semanage audit2allow # 预期:/usr/sbin/semanage、/usr/bin/audit2allow
失败判断:命令返回非 0 或 command -v 仍无输出 → 走附录 B 的 CIL 方案。
步骤 3:为 16443 定义 SELinux 端口标签¶
把 16443 归入 http_port_t(haproxy 域被允许绑定的端口类型),这是官方推荐做法。
未用 setenforce 0——那只是临时止血且降低安全等级。
semanage port -l | grep 16443 || echo "(未定义)"
semanage port -a -t http_port_t -p tcp 16443
semanage port -l | grep 16443
# 预期:http_port_t tcp 16443, 80, 81, 443, ...
失败判断:若报 already defined 说明已打过标签,直接进入下一步(幂等)。
步骤 4:开启 haproxy 出站布尔¶
haproxy 需连后端 192.168.31.10:6443,该连接同样被 haproxy 域策略拒绝,
用官方布尔放行(作用域仅限 haproxy_t 域)。-P 表示持久化。
getsebool haproxy_connect_any # 变更前:off
setsebool -P haproxy_connect_any on
getsebool haproxy_connect_any # 预期:on
步骤 5:启动 haproxy¶
systemctl start haproxy
sleep 3
systemctl is-active haproxy # 预期:active
ss -lntp | grep 16443 # 预期:0.0.0.0:16443 LISTEN
journalctl -u haproxy --since "-1min" --no-pager -l | grep -E "UP|DOWN"
# 预期:Server k8s-api-backend/master-1 is UP(master-2/3 指向已关闭的 .11/.12,仍 DOWN,属预期)
失败判断:仍 bind ... Permission denied → 回步骤 3;进程起来但后端全 DOWN → 回步骤 4。
6. 验证¶
# 1) 入口健康
ssh root@192.168.31.10 "curl -sk --max-time 8 https://192.168.31.8:16443/healthz" # 预期:ok
# 2) 集群可见
kubectl get nodes -o wide # 预期:4 节点全部 Ready
# 3) 策略持久性
ssh root@192.168.31.10 "semanage port -l | grep 16443; semanage boolean -l | grep haproxy_connect_any"
# 预期:http_port_t 含 16443;haproxy_connect_any (on, on)
# 4) 幂等复跑(第二次应全部跳过、零变更)
ssh root@192.168.31.10 'semanage port -l | grep -q 16443 && echo "port 已定义 → 跳过"
getsebool haproxy_connect_any | grep -q "on$" && echo "boolean 已 on → 跳过"
systemctl is-active haproxy | grep -q active && echo "haproxy 已运行 → 跳过"'
实测结果:第 1–3 项全部通过,第 4 项三条均为"跳过"(零变更)。
集群侧同步观测到收敛:Running Pod 由 20 → 59,Unknown 由 36 → 0,Terminating 由 6 → 0;
server 命名空间业务(GitLab 各组件、MariaDB、MinIO、OpenLDAP、Traefik)全部 Running。
收尾时仍有 3 个 Pod 处于 ContainerCreating/PodInitializing(snapshot-controller、
kube-prometheus-stack-operator、gitlab-toolbox),事件显示为从外网拉取镜像中
(registry.k8s.io / quay.io / registry.gitlab.com),非故障,等待即完成。
7. 回滚¶
回滚目标状态 = 变更前:haproxy 因 SELinux 拒绝而 failed、16443 无监听。
两步均与步骤一一对应,可独立或同时回退。
# 1) 移除端口标签(回到"未定义")
semanage port -d -t http_port_t -p tcp 16443
# 2) 关闭布尔(回到 off)
setsebool -P haproxy_connect_any off
# 3) 停止 haproxy(回到 failed/未监听)
systemctl stop haproxy
回滚后验证:getenforce 仍为 Enforcing;semanage port -l | grep 16443 无输出;
systemctl is-active haproxy 为 inactive。
备份存放位置:k8s-restart-16443-selinux-20260929/backup/,
变更前状态快照见 backup/pre-change-state-master01.txt,台账见 backup/README.md。
本次为"新增策略"类变更,无被覆盖的原文件,故无 .bak.<时间戳> 文件;回滚依据即上表两步删除动作。
8. 风险说明¶
| 风险 | 说明 | 缓解 |
|---|---|---|
| 布尔放宽出站范围 | haproxy_connect_any=on 允许 haproxy 域连接任意端口,范围大于"仅 6443" |
作用域限 haproxy_t 域;该机唯一职责就是代理 apiserver,实际暴露面未扩大。若需更严,可用 semanage port -a -t http_port_t -p tcp 6443 替代(未采用,因 6443 语义上属 apiserver 端口) |
| 持久性未做实机重启验证 | 未在本次变更多做一次重启 | semanage port、setsebool -P 均写入策略存储(已由 semanage boolean -l 显示 (on, on) 佐证),非临时 setenforce 手段 |
| haproxy 后端残留已关闭主机 | haproxy.cfg 的 backend 仍配置 192.168.31.11/.12:6443(master-02/03 已关闭、禁用 autostart),会持续产生健康检查失败告警 |
本次未改配置(避免混入非必要变更);建议后续单独清理 backend 列表 |
| 同类隐患仍在其他组件 | 本机 SELinux 曾在 permissive 下运行,可能还有别的服务依赖"实际未拦截" |
建议后续做一次全节点 ausearch -m AVC 巡检,集中收口 |
附录 A:时间线与证据¶
| 时间(CST) | 事件 | 证据 |
|---|---|---|
| 18:45:39 | 宿主机重启后 systemd 拉起 haproxy,bind 16443 被 SELinux 拒绝 | audit(1790678740.337:37) denied { name_bind } src=16443 ... permissive=0 |
| 18:45:42 | keepalived 对 check_haproxy.sh 的 setattr 被拒(脚本仍执行成功,非致命) |
audit(1790678742.963:39) denied { setattr } name="check_haproxy.sh" |
| 20:19:58 | 现场 kubectl 报 connection refused(192.168.31.8:16443) |
用户截图 |
| 20:27 | 开始处置,确认 apiserver/etcd/keepalived 正常,仅 haproxy failed | 本 SOP 步骤 1 |
| 20:30:48 | 打端口标签后启动 haproxy,进程 active 但三个后端全 DOWN | journalctl -u haproxy:Permission denied at initial connection step of tcp-check |
| 20:33 | 开启 haproxy_connect_any,master-1 转 UP |
Server k8s-api-backend/master-1 is UP |
| 20:33 | VIP 入口恢复 | curl -sk https://192.168.31.8:16443/healthz → ok |
| 20:36–20:40 | 集群 Pod 收敛(Unknown 36→0),业务全部 Running | kubectl get pods -A |
原始 AVC 记录见 evidence/selinux-avc-master01.txt;变更前状态快照见 backup/pre-change-state-master01.txt。
附录 B:离线环境替代方案(未使用)¶
若无法安装 policycoreutils-python-utils,可用 semodule -i 加载 CIL 端口定义,效果等价:
cat > /root/haproxy_16443.cil <<'EOF'
(portcon tcp 16443 (system_u object_r http_port_t ((s0) (s0))))
EOF
semodule -i /root/haproxy_16443.cil
# 回滚:semodule -r haproxy_16443
出站 6443 若同样不便开布尔,也可用 AUDIT 记录生成最小策略(需已装 audit2allow):
ausearch -m AVC -ts recent | grep haproxy | audit2allow -M haproxy_16443_out
semodule -i haproxy_16443_out.pp
附录 C:交付物清单¶
| 文件 | 内容 |
|---|---|
SOP.md |
本文件:变更实施、验证与回滚 |
evidence/selinux-avc-master01.txt |
master-01 的 SELinux AVC 原始记录(根因证据) |
backup/pre-change-state-master01.txt |
变更前状态快照(SELinux 配置、haproxy 状态、AVC 摘要) |
backup/README.md |
备份台账(含本次无 .bak 文件的说明与回滚依据) |
../../k8s_136_cluster_deploy/docs/RUNBOOK.md |
已同步更新「场景 1」:新增 SELinux 分支诊断 |
来源:
/home/rain/Documents/k8s_ins/k8s-restart-16443-selinux-20260929/SOP.md| 最后更新:2026-09-29 20:48(由build/sync.py同步,请勿在站点侧修改)