跳转至

服务暴露编排变更 SOP(内网 / 公网双模式:traefik + frp + nginx)

交付物目录:ingress_tunnel_deploy/(相对项目根目录 k8s_ins/) 工程定位:服务暴露编排——把内网(含 k8s 集群内)的服务,按声明的暴露面投放到公网或内网,不改动业务服务自身配置 最近执行:2026-09-18 04:40(CST) 执行结果:成功(公网 5 个站点 + 内网 MariaDB 均已收敛) 关联工程:traefik_deploy(内网入口代理)、gitlab-deploy-v2、ldap-deploy、prometheus_deploy

配套文档:日常使用与排障见 RUNBOOK.md;备份清单与可删时机见 backup/README.md。 本文只回答"这次改了什么、怎么改、怎么验、怎么退"。

目录

1. 背景

内网 Kubernetes 集群(control-plane 192.168.31.10)以 Traefik v3.7.13 作为唯一入口代理,香港机 129.226.138.204 具备公网 IP 并运行 frps + nginx。两侧原本各自可用,但服务对外投放这件事没有任何统一入口,具体表现为:

  • 公网侧是坏账:香港机 nginx.conf 全是旧环境残留——harbor 已下线,其余 server_name 块(gitlab / prometheus / grafana / ldapadmin / traefik / alertmanager)仍 proxy_pass 到 8083 / 8084 / 3000 / 9090 / 9093 等无人监听的端口,实测公网访问一律 502;残留的 harbor.rainchen.top 还占着 443,另有一个拼写错误的 harbor.rianchen.top 块。
  • 隧道是空转的:OpenWrt 192.168.31.2 上的 frpc 能登录 frps,但 frpc.toml 中没有任何 [[proxies]] 定义,隧道零转发。
  • 集群内服务出不了集群:新建的 MariaDB(mariadb-operator 部署)只有 ClusterIP mariadb:3306,内网其他机器无法访问——而 frpc 位于集群外,够不到 ClusterIP。
  • 需求是两类、不是一类:
  • 公网暴露:5 个 Web 服务(gitlab / prometheus / grafana / traefik / ldapadmin)需要公网 HTTPS 访问;
  • 仅内网暴露:MariaDB 只需要内网可达(mysql -h 192.168.31.10),明确不要上公网。
  • 硬性约束:暴露面必须由配置显式可控(内网 / 公网),且以后新增服务只改一处配置、重跑脚本即自动收敛。

2. 目标

变更完成后应达到的可验证状态:

项 期望状态
公网 HTTPS https://{gitlab,prometheus,grafana,traefik,ldapadmin}.rainchen.top 可用,Let's Encrypt 正式证书且客户端校验通过
公网 HTTP 上述 5 个域名 80 端口 301 跳转到同名 HTTPS
公网链路 香港机 8082 / 8443 经 frp 隧道映射到内网 192.168.31.10:80 / :443,由 nginx 按域名分流
内网 TCP 暴露 192.168.31.10:3306 可连到集群内 MariaDB(exposure: internal),不经香港机、不对公网开放
内网 HTTP 路由 traefik 中存在 server/gitlab IngressRoute(websecure → Envoy Gateway Service),原有 4 条路由不受影响
nginx 治理 业务 server 块由本工程全量生成于 conf.d/k8s-ingress.conf;旧环境残留已清理,fps(frps 面板)保留
暴露面可控 每个 TCP 服务用 exposure: internal / public / both 声明投放范围,组合非法时前置校验直接拦下
可复用 新增 HTTP(S) / TCP 服务只需编辑 config/ingress.yaml 并重跑 run.sh
幂等 连续执行两次,第二次零变更、无报错、三模块全部 skipped
证书续期 执行机 crontab 每日执行本工程,剩余有效期 <30 天时自动续期

3. 前置条件

通用

项目 要求 本次实测
执行机 Python ≥3.9;对目标主机 SSH 免密;可操作 k8s 集群 Python 3.12.3;root@129.226.138.204、root@192.168.31.2 免密;kubectl 可用
共享库 /home/rain/py_public_modules 存在(base_module 等 4 个模块) 存在,经 .venv 内 .pth 挂载
内网入口 k8s traefik 已就绪,traefik.io CRD(IngressRoute / IngressRouteTCP)存在 release traefik deployed(chart 41.6.0 / v3.7.13)
环境变量 主机口令(如需)经 password_env 传入,不落盘 本次全部密钥认证,未使用口令

公网 Web 暴露的额外前置

项目 要求 本次实测
香港机 nginx / certbot / openssl 可用 nginx 1.20.1、certbot 1.11.0、openssl 可用
域名解析 各站点域名 A 记录指向香港机公网 IP 5 个域名已解析
80 端口 公网可达(Let's Encrypt HTTP-01 必需) 公网 80 返回 200
frpc 版本 ≥ 0.52.0(需支持 TOML includes) 0.70.1
端口占用 香港机隧道落地端口空闲(8082、8443);443 不被占用 空闲;harbor 已下线

内网 TCP 暴露的额外前置

项目 要求 本次实测
traefik entryPoint 必须已在 traefik_deploy/values.yaml 的 ports 下声明(traefik 入口是静态配置,本工程只能校验、不能动态添加) 已声明 mariadb(容器 3306 → 宿主 3306)
宿主端口 traefik 所在节点 192.168.31.10 对应端口空闲 3306 空闲

执行前检查:

kubectl get crd ingressroutes.traefik.io ingressroutetcps.traefik.io -o name   # 预期:两个 CRD 都在
kubectl -n server get pod -l app.kubernetes.io/name=traefik \
  -o jsonpath='{range .items[0].spec.containers[0].ports[*]}{.name}:{.hostPort}{" "}{end}'   # 预期含 mariadb:3306
timeout 20 ssh root@129.226.138.204 "nginx -t"                        # 预期:syntax is ok
timeout 20 ssh root@129.226.138.204 "ss -lnt | grep -E ':(8082|8443) '"   # 变更前预期:无输出
timeout 20 ssh root@192.168.31.2 "/usr/local/bin/frpc --version"      # 预期:≥ 0.52

4. 影响范围

涉及主机与组件

主机 角色 本工程对它的改动
129.226.138.204(香港,CentOS 7) 公网入口:nginx + frps + certbot ① 清理 nginx.conf 中 7 个旧 server 块(保留默认 server 与 fps);② 生成并维护 conf.d/k8s-ingress.conf;③ 维护 /var/www/letsencrypt 与 5 张 LE 证书;④ nginx -t 通过后 reload
192.168.31.2(OpenWrt 24.10) 隧道客户端 frpc ① 生成 /etc/frp/conf.d/k8s-ingress.toml(公网暴露类的 tcp 代理);② 在 frpc.toml 追加 1 行 includes;③ 内容变化时重启 frpc-tunnel
k8s 192.168.31.8(API)/ 192.168.31.10(traefik) 内网入口 ① server/gitlab IngressRoute(HTTP);② server/mariadb IngressRouteTCP(内网 TCP);③ traefik ports 增加 mariadb entryPoint(宿主 3306),需 helm upgrade 一次

是否停服:不需要。业务服务自身配置零改动,本工程只做「新增路由/配置」与「公网侧治理」。

窗口期与业务影响

  • 香港 nginx reload 是平滑重载,连接不中断。
  • OpenWrt frpc-tunnel 重启会瞬时中断既有 frp 隧道(含 OpenVPN 相关用途),实测 5 秒内恢复;仅在公网暴露类条目有变化时才触发。
  • traefik helm upgrade 会中断公网入口约数秒:单副本 + hostPort 只能"先停旧再起新"。仅在新增内网 TCP 端口时需要,须避开业务高峰。
  • k8s 侧 apply 路由不影响既有 4 条(ldapadmin / grafana / prometheus / traefik-dashboard)。
  • 无在跑批作业依赖上述主机;未触碰 Slurm/LSF 节点。

5. 分步操作

对应实现文件:ingress_tunnel_deploy/main.py、modules/{k8s_route_module,frpc_module,nginx_module}.py、utils/{render,service_spec,file_utils}.py、config/ingress.yaml 以下代码块以实现体为准,仅供阅读、复核与手工复现;幂等、备份与错误处理均由实现体保证。 三个模块的执行顺序固定为 k8s 路由 → frpc 隧道 → 香港 nginx,任一模块失败即终止后续模块。

BASE="/home/rain/Documents/k8s_ins/ingress_tunnel_deploy"

# ============ 一、工程初始化 ============

# —— 步骤 1:独立 venv 与共享库挂载(仅首次)——
cd "$BASE"
python3 -m venv .venv                                                   # 工程内独立环境,已被 .gitignore 排除
.venv/bin/python -c "import sysconfig, pathlib; p = pathlib.Path(sysconfig.get_paths()['purelib']) / 'py_public_modules.pth'; p.write_text('/home/rain/py_public_modules', encoding='utf-8')"
.venv/bin/python -m pip install -r requirements.txt                     # paramiko / PyYAML
.venv/bin/python -c "import sys, importlib.util as u; print('/home/rain/py_public_modules' in sys.path, u.find_spec('base_module').origin)"   # 预期:True + 真实路径

# ============ 二、声明要暴露什么 ============

# —— 步骤 2:编辑服务清单(本工程唯一日常改动点)——
#   config/ingress.yaml 结构:
#     hosts      目标主机(凭据只写环境变量名)
#     frp        frpc 主配置路径、片段目录、服务名
#     nginx      香港机配置路径、ACME 目录、待清理的旧 server_name
#     tls        LE 邮箱、证书目录、续期阈值
#     tunnels    内网后端 → 香港落地端口(scheme 决定 nginx 回源协议)
#     sites      公网 HTTP(S) 站点(按 Host 分流)
#     tcp_services  TCP 服务,暴露面由 exposure 控制(internal / public / both)
#     k8s        traefik 位置 + IngressRoute 声明
#   新增服务只改这一个文件,具体写法见附录 A
grep -nE "^\s+- name:|^\s+host:|^\s+exposure:" "$BASE/config/ingress.yaml"    # 核对清单

# ============ 三、收敛 ============

# —— 步骤 3:预演 ——
./run.sh --dry-run                                       # 只探测与输出,不改任何系统状态

# —— 步骤 4:正式执行 ——
./run.sh                                                 # 等价 .venv/bin/python main.py --config config/ingress.yaml

# ============ 四、以下为脚本内部的等价手工操作,按模块展开 ============

# —— 步骤 5(k8s 路由):IngressRoute 与 IngressRouteTCP ——

# 5.0 仅"内网 TCP 暴露"需要此前置:先在 traefik 侧声明 entryPoint
#     在 traefik_deploy/values.yaml 的 ports 下增加(与 tcp_services[].entrypoint 同名):
#       mariadb:
#         port: 3306
#         hostPort: 3306
#         expose: { default: true }
#         exposedPort: 3306
#         protocol: TCP
cd /home/rain/Documents/k8s_ins/traefik_deploy
cp -a values.yaml "values.yaml.bak.$(date +%Y%m%d%H%M%S)"               # 【高危】改前必备份
helm upgrade --install traefik ./chart/traefik-41.6.0.tgz -n server \
  -f ./values.yaml --skip-crds --wait --timeout 5m                     # 【高危】单副本+hostPort,公网入口中断约数秒
kubectl -n server get pod -l app.kubernetes.io/name=traefik \
  -o jsonpath='{range .items[0].spec.containers[0].ports[*]}{.name}: host={.hostPort}{"\n"}{end}'
# 预期:列表中新增 mariadb: host=3306

# 5.1 HTTP 路由(域名 → 后端 Service)
kubectl -n server get svc -l gateway.envoyproxy.io/owning-gateway-name=gitlab-gw \
  -o jsonpath='{.items[0].metadata.name}'                               # 预期:envoy-server-gitlab-gw-80b44f81(名带 chart 哈希,故用标签发现)
kubectl apply -f "$BASE/manifests/server-gitlab-ingressroute.yaml"      # server/gitlab → 上述 svc:80,entryPoint websecure
kubectl -n server get ingressroute gitlab -o jsonpath='{.spec.routes[0].match}'   # 预期:Host(`gitlab.rainchen.top`)

# 5.2 内网 TCP 路由(TCP 透传,不经过 nginx 与 frp)
#     该 entryPoint 独占使用,用 HostSNI(`*`) 匹配全部连接;一个 entryPoint 只允许一条这样的路由
kubectl apply -f "$BASE/manifests/server-mariadb-ingressroutetcp.yaml"
kubectl -n server get ingressroutetcp mariadb \
  -o jsonpath='{.spec.entryPoints[0]}|{.spec.routes[0].services[0].name}:{.spec.routes[0].services[0].port}'
# 预期:mariadb|mariadb:3306

# —— 步骤 6(frpc 隧道):仅处理 exposure 含 public 的 TCP 条目 ——
timeout 20 ssh root@192.168.31.2 "mkdir -p /etc/frp/conf.d"
# 下发 /etc/frp/conf.d/k8s-ingress.toml(当前 2 条:8082→192.168.31.10:80、8443→192.168.31.10:443)
timeout 20 ssh root@192.168.31.2 "cp -a /etc/frp/frpc.toml /etc/frp/frpc.toml.bak.$(date +%Y%m%d-%H%M%S)"   # 【高危】改主配置前必备份
# 在 frpc.toml 中任何 TOML 表之前追加一行:
#   includes = ["/etc/frp/conf.d/*.toml"]
timeout 20 ssh root@192.168.31.2 "/etc/init.d/frpc-tunnel restart"      # 重启隧道客户端
timeout 20 ssh root@129.226.138.204 "ss -lnt | grep -E ':(8082|8443) '"   # 预期:frps 侧两端口 LISTEN

# —— 步骤 7(香港 nginx):清理旧残留 + 下发业务配置 ——
timeout 20 ssh root@129.226.138.204 "cp -a /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak.$(date +%Y%m%d-%H%M%S)"   # 【高危】改主配置前必备份
# 删除 nginx.conf 中 server_name 命中以下清单的 server 块(保留默认 server 与 fps.rainchen.top):
#   harbor.rainchen.top / harbor.rianchen.top / gitlab / prometheus / grafana / traefik / ldapadmin / alertmanager.rainchen.top
timeout 20 ssh root@129.226.138.204 "nginx -t"                          # 预期:syntax is ok(不通过则从备份恢复,绝不 reload)
timeout 20 ssh root@129.226.138.204 "mkdir -p /var/www/letsencrypt && chmod 755 /var/www/letsencrypt"
# 下发 conf.d/k8s-ingress.conf:各站点 80 端口(ACME 校验路径 + 301 跳转)与 443 端口(按域分流)
timeout 20 ssh root@129.226.138.204 "nginx -t && systemctl reload nginx"   # 首轮配置必须先生效,否则 ACME 校验会命中旧配置

# —— 步骤 8(证书):逐域名签发/续期 ——
# 每域名一张独立证书;仅在证书缺失或剩余有效期 < 30 天时执行
timeout 200 ssh root@129.226.138.204 "certbot certonly --webroot -w /var/www/letsencrypt \
  -d gitlab.rainchen.top --email saier_cy@163.com --agree-tos --no-eff-email --non-interactive"
# (prometheus / grafana / traefik / ldapadmin 同上,逐一签发)

# —— 步骤 9:证书就绪后补齐 443 站点并 reload ——
# 重新渲染 conf.d/k8s-ingress.conf(此时包含 443 server 块),再 reload
timeout 20 ssh root@129.226.138.204 "nginx -t && systemctl reload nginx"

关键实现要点(详见源码注释):

要点 说明
幂等判定 远端文件 SHA256 与期望内容比对,一致即跳过;渲染结果不含时间戳,否则指纹永远不同、幂等失效
原子写入 上传临时文件 → 校验指纹 → mv -f 替换;变更前一律 cp -a 带时间戳备份
失败即回滚 nginx -t 不通过立即从备份恢复并终止,坏配置不会留在生产
首轮/二轮渲染 首次执行存在「先有 HTTP 才能签证书、先有证书才能开 443」的依赖,故分两轮渲染;第二轮仅在有新签发证书时触发
回源 TLS nginx → 127.0.0.1:8443 走 HTTPS,proxy_ssl_verify off(内网 traefik 用自签证书)
暴露面分流 frpc 模块只取 exposure 含 public 的条目;k8s 模块只取含 internal 的条目;不匹配的组合由 pre_check 拦下
应用层 http URL 修正 rewrite_scheme: true 的站点关闭上游压缩并启用 sub_filter,把响应体的 http://<当前域名> 改写为 https://

6. 验证

# —— 6.1 公网 Web(在任意公网机器执行)——
for d in gitlab prometheus grafana traefik ldapadmin; do
  curl -s -o /dev/null -w "$d.rainchen.top  %{http_code}  tls:%{ssl_verify_result}\n" "https://$d.rainchen.top/"
done
# 预期:gitlab/prometheus/grafana/ldapadmin = 200(跟随跳转后),traefik = 401(Dashboard BasicAuth);tls:0 表示证书链校验通过

curl -s -o /dev/null -w '%{http_code} -> %{redirect_url}\n' http://gitlab.rainchen.top/    # 预期:301 -> https://gitlab.rainchen.top/

openssl s_client -connect gitlab.rainchen.top:443 -servername gitlab.rainchen.top </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates                        # 预期:subject CN=gitlab.rainchen.top,issuer Let's Encrypt

# —— 6.2 页面内不残留 http 绝对引用(混合内容检查)——
curl -sL https://ldapadmin.rainchen.top/login | grep -c 'http://ldapadmin'    # 预期:0
curl -sL https://gitlab.rainchen.top/ | grep -c 'http://gitlab'               # 预期:0
curl -s -o /dev/null -w '%{http_code}\n' https://ldapadmin.rainchen.top/css/app.css   # 预期:200

# —— 6.3 公网链路的公网侧不可见项(内网/香港侧)——
timeout 20 ssh root@129.226.138.204 "ss -lnt | grep -E ':(8082|8443) '"   # 预期:两端口 LISTEN(frps)
timeout 20 ssh root@192.168.31.2 "/etc/init.d/frpc-tunnel status"        # 预期:running

# —— 6.4 内网 TCP 服务(MariaDB,仅内网)——
mysql -h 192.168.31.10 -P 3306 -u <账号> -p
# 本机若无 mysql 客户端,可用 socket 直接读握手包确认链路通到真实库:
.venv/bin/python - <<'EOF'
import socket
with socket.create_connection(("192.168.31.10", 3306), timeout=5) as s:
    data = s.recv(256)
print("protocol=", data[4], "server=", data[5:data.find(b"\x00", 5)].decode())
EOF
# 预期:protocol= 10 server= 12.3.3-MariaDB-ubu2404

# —— 6.5 资源与路由 ——
kubectl -n server get ingressroute gitlab -o wide                        # 预期:存在
kubectl -n server get ingressroutetcp mariadb -o wide                    # 预期:存在

# —— 6.6 幂等复验 ——
./run.sh
# 预期:三模块状态全部 skipped,日志出现「状态一致,无需变更,跳过备份与执行」

本次实测结果(2026-09-18 04:42)

验证项 实测
公网 HTTPS gitlab / prometheus / grafana / ldapadmin = 200(落地 /users/sign_in、/query、/login、/login),traefik = 401;5 个域名 tls_verify_result = 0
公网 HTTP 5 个域名全部 301 跳转到 HTTPS
证书 issuer = C=US, O=Let's Encrypt, CN=YR2,有效期 2026-09-17 → 2026-12-16,续期验算均通过
隧道 香港机 8082、8443 由 frps 监听,回源成功
内网路由 server/gitlab IngressRoute 校验通过
内网 TCP 暴露 server/mariadb IngressRouteTCP 已应用(entryPoint mariadb → mariadb:3306);192.168.31.10:3306 完成 MySQL 握手(12.3.3-MariaDB-ubu2404);对照端口 30306 拒绝连接,公网侧无该端口
nginx 治理 nginx.conf 181 → 127 行(删除 7 个旧 server 块),fps 保留;业务配置由脚本生成
混合内容 ldapadmin / gitlab 页面内 http:// 引用计数为 0,css/app.css 返回 200
幂等复验 连续执行:k8s-route / frpc / nginx 三模块全部 skipped,退出码 0
定时任务 精简环境(env -i HOME=... PATH=/usr/local/bin:/usr/bin:/bin)复跑通过,三模块 skipped

7. 回滚

备份物(均由实现体在变更前自动生成;逐条台账见 backup/README.md,含每份备份的来历、用途与可删时机)

位置 备份路径 说明
香港机 /etc/nginx/nginx.conf.bak.<时间戳>、/etc/nginx/conf.d/k8s-ingress.conf.bak.<时间戳> 与目标文件同目录,--rollback 依赖此位置
OpenWrt /etc/frp/frpc.toml.bak.<时间戳>、/etc/frp/conf.d/k8s-ingress.toml.bak.<时间戳> 同上
traefik traefik_deploy/values.yaml.bak.<时间戳> 归 traefik 主题,见 traefik_deploy/backup/README.md
集群 manifests/*-ingressroute.before.yaml 变更前现状导出;首次创建该资源时无此文件
本地留档 backup/etc/nginx/nginx.conf.bak.20260918-025352、backup/etc/frp/frpc.toml.bak.20260918-025336 变更前基线,已取回本仓库并校验 MD5 一致
# —— 方案 A:一键回滚(逆序:先撤公网入口,再撤隧道,最后撤内网路由)——
cd /home/rain/Documents/k8s_ins/ingress_tunnel_deploy && ./run.sh --rollback
# 自动从最近备份恢复 nginx.conf / conf.d 配置并 nginx -t + reload;同时删除本工程创建的 traefik 路由

# —— 方案 B:按暴露面分别回滚(手工)——
# 只撤公网 Web:还原香港机两份配置
timeout 30 ssh root@129.226.138.204 'cp -a $(ls -1t /etc/nginx/nginx.conf.bak.* | head -1) /etc/nginx/nginx.conf'
timeout 30 ssh root@129.226.138.204 'cp -a $(ls -1t /etc/nginx/conf.d/k8s-ingress.conf.bak.* | head -1) /etc/nginx/conf.d/k8s-ingress.conf'
timeout 30 ssh root@129.226.138.204 'nginx -t && systemctl reload nginx'

# 只撤隧道:还原 frpc 主配置并重启(【高危】会短暂中断既有 frp 隧道)
timeout 30 ssh root@192.168.31.2 'cp -a $(ls -1t /etc/frp/frpc.toml.bak.* | head -1) /etc/frp/frpc.toml'
timeout 30 ssh root@192.168.31.2 '/etc/init.d/frpc-tunnel restart'

# 只撤内网 TCP 暴露:【高危】删除路由后 192.168.31.10:3306 立即不可用
kubectl -n server delete ingressroutetcp mariadb
# 若同时在 traefik 侧加了端口,还需回退 values 并 helm upgrade:
# cd /home/rain/Documents/k8s_ins/traefik_deploy && \
#   cp -a $(ls -1t values.yaml.bak.* | head -1) values.yaml && \
#   helm upgrade --install traefik ./chart/traefik-41.6.0.tgz -n server -f ./values.yaml --skip-crds --wait --timeout 5m

# —— 回滚后验证 ——
curl -s -o /dev/null -w '%{http_code}\n' https://gitlab.rainchen.top/   # 预期:连接失败或 5xx
kubectl -n server get ingressroute                                       # 预期:不再有 gitlab
kubectl -n server get ingressroutetcp                                    # 预期:不再有 mariadb
timeout 20 ssh root@192.168.31.2 "grep -c includes /etc/frp/frpc.toml"   # 预期:0

说明:回滚只撤销本工程引入的对象(nginx 两份配置、frpc 片段与 includes 行、traefik 路由),不删除 LE 证书(/etc/letsencrypt/live/<域名> 保留以便重试),也不影响内网既有的 4 条 IngressRoute。

8. 风险说明

风险 说明 缓解措施
证书到期 LE 证书 90 天有效(本次至 2026-12-16) 已配置:执行机 crontab 每日 03:30 跑本工程,剩余 <30 天自动续期并 reload(详见附录 C-1)
certbot 版本偏旧 香港机 certbot 1.11.0(2021 年)、CentOS 7 已 EOL;本次签发与 renew --dry-run 均正常,但长期存在兼容风险 续期失败时回退 acme.sh(纯 shell,支持 HTTP-01),或在其他机器签发后同步证书
香港机磁盘偏紧 根分区已用 83%(25G 中剩约 4G) 定期清理 /var/log 与累积的 nginx 备份;持续增长时扩容
应用层生成 http 绝对 URL GitLab chart 设了 global.hosts.https: false;phpLDAPAdmin 2.x 未启用 trustProxies,两者都不认 X-Forwarded-Proto,页面内出现 http:// 绝对地址(HTTPS 下被浏览器按混合内容拦截,ldapadmin 会丢样式) 已在 nginx 层用 sub_filter 修正(rewrite_scheme: true)。根治办法见附录 C-3
frpc 主配置被外部工程覆盖 frpc.toml 由外部工程 frp_openvpn_tunnel 生成,重跑它会丢掉本工程追加的 includes 行,隧道随之消失 本工程幂等,重跑 run.sh 自动补回;建议把「跑完 frp_openvpn_tunnel 后跑一次本工程」写入日常流程
内网 TCP 暴露需改 traefik traefik 的 entryPoint 是静态配置,本工程只能校验不能动态添加;每新增一个内网 TCP 端口都要改 traefik_deploy/values.yaml 并 helm upgrade(公网入口抖数秒) 已写入配置注释与本 SOP;pre_check 在 entryPoint 缺失时直接报错并给出操作提示,不会静默失败
frps 未注册开机自启 香港机 /etc/rc.d/rc3.d/ 下无 frps 软链,systemctl is-enabled frps 为 disabled;进程是手工拉起的。香港机一旦重启,frps 不会自动恢复,公网 5 个站点全断且不会自愈 已写入 RUNBOOK.md 场景 2(含手工拉起命令)与速查表;建议在宿主机加开机自启或由外部探活自动拉起
香港机处于监控盲区 主机虽有 node_exporter(:9100),但内网 Prometheus 的 ServiceMonitor 中没有对应条目,未被采集;frps/nginx 亦无指标暴露 已记入 RUNBOOK.md 第 7 章;建议补一个抓取香港机 :9100 的采集配置,并对证书剩余天数、8082/8443 监听加告警
隧道单点 香港 frps、OpenWrt frpc、内网 traefik 均为单点,任一故障即公网不可达 监控香港机 8082/8443 监听与 frps 日志;frpc 已由 procd respawn 自动拉起
nginx 配置漂移 手工修改 conf.d/k8s-ingress.conf 或 nginx.conf 会被下次执行覆盖(nginx.conf 只清理旧块,不动其他内容) 文件内已标注「请勿手工修改」;长期变更请改 config/ingress.yaml
公网入口单点 traefik 单副本固定在 k8s-master-01,且 helm upgrade 会中断数秒 变更避开业务高峰;如需零中断,需改多副本 + 反亲和 + 非 hostPort 方案(见 traefik_deploy/SOP.md 风险章)
回源不校验证书 nginx → 内网 traefik 的 TLS 校验被关闭(proxy_ssl_verify off) 内网为受控二层环境;如需加固可导入内部 CA 并开启校验
公共暴露面被扫描 香港机安全组对公网全放行,任何监听端口都会被扫描 对外只开必要端口;数据库类服务坚持 exposure: internal,确需公网时用非标端口 + 来源白名单 + 强口令
日志冗长 远端 cat 大文件会被完整打印(共享库行为) 排障需要时用 --log-level WARN 抑制

附录 A:新增一个服务的复用操作

只需改 config/ingress.yaml,无需改代码。

A.1 公网 Web —— 复用既有隧道(后端在内网 traefik 后面)

k8s:
  routes:
    - name: newapp
      namespace: <命名空间>
      host: newapp.rainchen.top
      entrypoint: websecure
      service:
        namespace: <命名空间>
        selector: app.kubernetes.io/name=<标签>     # 按标签动态发现 Service
        port: 80

sites:
  - name: newapp
    host: newapp.rainchen.top                       # 需先做好公网解析
    tunnel: traefik-websecure                       # 复用已有隧道,不新增 frpc 条目
    tls: true
    websocket: false

A.2 公网 Web —— 后端在内网某台独立主机(新增隧道)

tunnels:
  - name: newapp
    net_addr: 192.168.31.20
    net_port: 8080
    remote_port: 8085                               # 香港机落地端口,需空闲
    scheme: http                                    # 后端是 TLS 端口就填 https

sites:
  - name: newapp
    host: newapp.rainchen.top
    tunnel: newapp
    tls: true
    websocket: false

A.3 TCP 服务 —— 暴露面由 exposure 决定

仅内网(后端是集群内 Service,当前 MariaDB 即此形态):

tcp_services:
  - name: newdb
    exposure: internal                              # 不上公网
    backend: k8s
    namespace: <命名空间>
    service: <Service 名>
    service_port: 3306
    entrypoint: newdb                               # traefik entryPoint 名

公网(后端是内网主机端口):

tcp_services:
  - name: newdb
    exposure: public
    backend: host
    net_addr: 192.168.31.20
    net_port: 3306
    remote_port: 18306                              # = 香港机对公网监听端口

exposure: both 表示两种暴露面同时具备(此时 remote_port 与 entrypoint 都要给)。

三条约束,违反时 pre_check 会直接拦下并说明原因:

  1. exposure: internal 必须配 backend: k8s(内网主机端口本就内网可达,无需"暴露");
  2. exposure: public 不支持 backend: k8s(集群内 Service 要先有 NodePort 才能被 frpc 触达);
  3. 内网暴露所依赖的 traefik entryPoint 必须已在 traefik_deploy/values.yaml 声明(新增端口需 helm upgrade 一次)。

改完后:

cd /home/rain/Documents/k8s_ins/ingress_tunnel_deploy
./run.sh --dry-run       # 先看将要发生的变更
./run.sh                 # 执行:自动签发新证书、渲染 nginx、下发 frp、建路由、端到端验证

附录 B:当前生效清单

暴露面 服务 入口地址 后端 承载
公网 gitlab.rainchen.top 129.226.138.204:443 traefik websecure → Envoy Gateway frp 8443 → 192.168.31.10:443
公网 prometheus.rainchen.top 同上 traefik → kube-prometheus-stack-prometheus:9090 同上
公网 grafana.rainchen.top 同上 traefik → kube-prometheus-stack-grafana:80 同上
公网 traefik.rainchen.top 同上 traefik Dashboard(BasicAuth) 同上
公网 ldapadmin.rainchen.top 同上 traefik web → phpldapadmin-web:80 frp 8082 → 192.168.31.10:80
内网 MariaDB 192.168.31.10:3306 traefik TCP entryPoint → mariadb:3306 内网直连,不经隧道
公网(保留) fps.rainchen.top 129.226.138.204:80 frps Dashboard 127.0.0.1:6443 旧配置,未纳入本工程管理

附录 C:遗留项与后续可选项

  1. 证书自动续期(已配置)——执行机 crontab,保留原有条目:

bash # 30 3 * * * cd /home/rain/Documents/k8s_ins/ingress_tunnel_deploy && PATH=/usr/local/bin:/usr/bin:/bin ./run.sh --log-level WARN >> logs/cron.log 2>&1

依赖执行机常开、两台主机 SSH 免密有效、~/.kube/config 有效(kubectl 在 /usr/local/bin,已显式写入 PATH)。该任务同时承担「证书续期」与「配置漂移收敛」,日志落在 logs/cron.log 与 logs/ingress_tunnel.log。若执行机不能常开,可在香港机改用 17 3 * * * certbot renew --webroot -w /var/www/letsencrypt --quiet && systemctl reload nginx。

  1. 失效证书(已清理)——v2ray.rainchen.top(2025-06-03 过期)已于 2026-09-18 用 certbot delete 删除,renew 不再有失败项。

  2. 根治应用层 http URL(可选,属内网应用变更)

  3. GitLab:gitlab-deploy-v2/values-gitlab.yaml 设 global.hosts.https: true 后 helm upgrade;
  4. phpLDAPAdmin:为其容器挂载一份启用 trustProxies 的 bootstrap/app.php(第三方镜像无对应环境变量)。 两者完成后即可去掉 sites 中对应的 rewrite_scheme: true。

  5. 暂未纳入本工程:registry.rainchen.top、kas.rainchen.top(GitLab 附属域名)、alertmanager.rainchen.top。需要时按附录 A 登记即可。

附录 D:交付物清单

全部位于 ingress_tunnel_deploy/(相对项目根目录 k8s_ins/):

交付物 用途
SOP.md 本文:本次变更的背景、目标、操作、验证、回滚、风险
RUNBOOK.md 运行手册:日常使用、排障场景、监控、安全、容量
backup/README.md 备份台账:每份备份的位置、来历、可删时机
backup/etc/** 变更前基线留档(nginx.conf、frpc.toml,已校验 MD5)
config/ingress.yaml 服务清单,唯一日常改动点
main.py、modules/、utils/ 实现体(Python,幂等 + 自动备份 + 回滚)
run.sh 便捷入口(--dry-run / --force / --rollback / --log-level)
manifests/ 渲染出的 k8s 路由清单,含 *.before.yaml 变更前留档
logs/ 执行日志(ingress_tunnel.log)与定时任务日志(cron.log)
requirements.txt、.gitignore 依赖清单与忽略规则(.venv/ 不入库)
docs/ 预留,存放本主题的参考文档

备份文件按约定仍留在各主机原目录(--rollback 依赖),不在本目录;其位置与用途见 backup/README.md。


来源:/home/rain/Documents/k8s_ins/ingress_tunnel_deploy/SOP.md | 最后更新:2026-09-19 16:26(由 build/sync.py 同步,请勿在站点侧修改)