Nginx 配置信息自动热加载 在生产环境中,Nginx 的配置信息通常是通过 Kubernetes 的 ConfigMap 进行存储和管理。为了在 ConfigMap 更新后,让 Nginx 自动加载最新的配置信息(即热加载,不会重启 Pod,不会中断现有请求),可以采用以下几种方案:
方案序号 方案名称 Nginx 是否可以直接 Reload 优点 缺点 方案一 容器之间共享进程命名空间 可以 简单有效 依赖 shareProcessNamespace(共享进程命名空间),容器间进程可见,安全性较低 方案二 部署 Reload Agent 可以 安全隔离 实现复杂一点
方案选择建议
如果是在开发或测试环境中简单实现 Nginx 配置信息自动热加载,推荐使用方案一(容器之间共享进程命名空间)。 如果是在生产环境中实现 Nginx 配置信息自动热加载,推荐使用方案二(部署 Reload Agent),避免跨容器进程控制,隔离性更好。 Secret 热更新
在 Kubernetes 中,如果使用 Secret 来管理密码、证书、Token 等敏感信息,同样可以使用文中介绍的两种方案来实现 Secret 自动热更新,只需要简单更改对应的 YAML 配置文件(Volume 挂载的配置内容)即可。 这是因为 Secret 与 ConfigMap 的使用方式基本是一致的,都可以使用 Volume(卷)的方式将其挂载到 Pod 容器中。 实现方案一 方案介绍 使用 ConfigMap + 共享进程命名空间实现 Nginx 配置信息自动热加载,其工作机制和特点如下:
方案原理:
主容器(Nginx)负责运行 Nginx; 将 ConfigMap 挂载到 /etc/nginx/conf.d 目录; 不负责监测配置变更,也不额外运行 Reload Agent 服务。 Sidecar 容器(Reloader)与主容器(Nginx)共享同一个进程命名空间(通过 shareProcessNamespace: true 实现); 将 ConfigMap 挂载到 /etc/nginx/conf.d 目录,并监测该目录的文件变更; 一旦监测到文件发生变更,立刻向 Nginx 的 Master 进程发送 kill -HUP <master_pid> 信号,触发配置热加载。 方案特点:
支持通过 ConfigMap 实现 Nginx 配置信息自动热加载,无需重启 Pod; 依赖 shareProcessNamespace 特性,使 Sidecar 容器能直接访问主容器(Nginx)的进程; 实现简单,无需在主容器中暴露 HTTP 接口或额外的 Reload Agent; 进程空间共享带来一定的安全隐患(Sidecar 容器可直接操作主容器的进程); 不易与外部系统(如 CI/CD、Webhook)直接集成,触发方式较固定; 适合轻量级场景或内部环境下使用,不建议在高安全要求的生产环境中采用。 实现步骤 创建 YAML 配置文件(比如 nginx-reload.yaml),由于 Pod 支持多个容器共享同一个进程命名空间(依赖 shareProcessNamespace 特性),因此在这种模式下,Sidecar 容器就能看到并操作主容器(Nginx)的进程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 apiVersion: v1 kind: ConfigMap metadata: name: nginx-config data: default.conf: | server { listen 80; location / { return 200 "Hello from ConfigMap.\n"; } } --- apiVersion: v1 kind: Service metadata: name: nginx-hotreload spec: type: NodePort selector: app: nginx-hotreload ports: - port: 80 targetPort: 80 nodePort: 30080 --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx-hotreload spec: replicas: 1 selector: matchLabels: app: nginx-hotreload template: metadata: labels: app: nginx-hotreload spec: shareProcessNamespace: true containers: - name: nginx image: nginx:1.15 ports: - containerPort: 80 volumeMounts: - name: nginx-config-cm mountPath: /etc/nginx/conf.d - name: reloader image: alpine:3.19 command: ["/bin/sh" , "-c" ] args: - | echo "$(date '+%F %T') [INFO] Starting config reloader ..." # 计算初始配置的 md5sum last_sum=$(find /etc/nginx/conf.d -type f -exec md5sum {} + | sort | md5sum) while true; do new_sum=$(find /etc/nginx/conf.d -type f -exec md5sum {} + | sort | md5sum) # 判断配置是否已更新 if [ "$new_sum" != "$last_sum" ]; then echo "$(date '+%F %T') [INFO] Config change detected, reloading nginx ..." # 获取 Nginx Master 进程的 PID nginx_pid=$(ps | grep "nginx: master process" | grep -v grep | awk '{print $1}') if [ -n "$nginx_pid" ]; then echo "$(date '+%F %T') [INFO] Reload nginx (master pid: $nginx_pid)" kill -HUP $nginx_pid || echo "$(date '+%F %T') [WARN] Failed to send HUP" else echo "$(date '+%F %T') [WARN] Nginx master pid not found" fi last_sum="$new_sum" fi sleep 5 done volumeMounts: - name: nginx-config-cm mountPath: /etc/nginx/conf.d volumes: - name: nginx-config-cm configMap: name: nginx-config
1 2 kubectl apply -f nginx-reload.yaml
关键点
shareProcessNamespace: true:表示整个 Pod 里面的所有容器共享同一个进程命名空间。因此,在 Sidecar 容器中,可以直接看到并控制主容器(Nginx)的进程,比如 PID 为 11 的 Nginx Master 进程。 命令 kill -HUP <master_pid> 与 nginx -s reload 的效果在 Nginx 中是等价的,两者都会触发 Nginx 热加载(Reload)配置文件。 对于 kill -HUP <master_pid> 命令,这里必须使用 Nginx Master 进程的 ID,而不是 Worker 进程的 ID,否则无法实现 Nginx 平滑更新配置文件(不中断请求)。 验证步骤 1 2 3 NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 66d nginx-hotreload NodePort 10.0.0.96 <none> 80:30080/TCP 25s
1 2 kubectl get pods -o wide
1 2 NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx-hotreload-6646755fcf-bq26b 2/2 Running 0 38s 10.244.1.32 k8s-node1 <none> <none>
1 2 kubectl logs nginx-hotreload-6646755fcf-bq26b -c reloader
1 2025-10-05 10:03:24 [INFO] Starting config reloader ...
1 2 3 4 5 kubectl exec -it nginx-hotreload-6646755fcf-bq26b -c reloader sh for pid in /proc/[0-9]*; do name=$(cat $pid /comm 2>/dev/null) || continue ; [ "$name " = "nginx" ] || continue ; type =$(tr '\0' ' ' < $pid /cmdline 2>/dev/null | grep -q "master process" && echo master || echo worker); echo "$name $type $pid " ; done
1 2 nginx master /proc/11 nginx worker /proc/16
1 2 wget -qO - http://192.168.2.112:30080
1 2 kubectl create configmap nginx-config --from -literal=default.conf="server { listen 80; return 200 'Updated ConfigMap.\n'; }" --dry -run=client -o yaml | kubectl apply -f -
1 2 kubectl logs nginx-hotreload-6646755fcf-bq26b -c reloader
1 2 3 2025-10-05 10:16:32 [INFO] Starting config reloader ... 2025-10-05 10:19:02 [INFO] Config change detected, reloading nginx ... 2025-10-05 10:19:02 [INFO] Reload nginx (master pid: 11)
1 2 wget -qO - http://192.168.2.112:30080
实现方案二 方案介绍 使用 ConfigMap + 部署 Reload Agent 实现 Nginx 配置信息自动热加载,其工作机制和特点如下:
方案概述:
有时为了更安全,会在 Nginx 容器中部署一个小的 Reload Agent,例如:在 Nginx 容器里部署一个轻量级 API 服务(比如,基于 Python 开发一个简易的 Web 服务); 当 Sidecar 容器监测到 ConfigMap 挂载的文件发生变更时,通过 curl http://localhost:8080/reload 调用 Reload Agent 的 API 服务; Reload Agent 接收到 Reload 请求后,内部会执行 nginx -s reload 或者 kill -HUP <master_pid> 触发 Nginx 热加载。 方案原理:
主容器(Nginx)负责运行 Nginx; 将 ConfigMap 挂载到 /etc/nginx/conf.d 目录; 额外部署一个轻量级 API 服务(Reload Agent),监听 8080 端口; 当接收到 /reload 请求时,执行 nginx -s reload 或者 kill -HUP <master_pid> 让 Nginx 重新加载配置文件。 Sidecar 容器(Reloader)将 ConfigMap 挂载到 /etc/nginx/conf.d 目录; 监测 /etc/nginx/conf.d 目录中的文件更改; 当监测到有配置更改后,通过 HTTP 协议调用 Reload Agent 的 API 接口 http://127.0.0.1:8080/reload,触发 Nginx 热加载。 方案特点:
支持通过 ConfigMap 实现 Nginx 配置信息自动热加载,无需重启 Pod; 不依赖 shareProcessNamespace,容器进程空间隔离(安全性更高); 结构清晰,便于与 CI/CD 或外部触发器集成; 可扩展为 Webhook 式控制(比如在 GitOps 更新配置后自动触发 Reload); 适合在高安全要求的生产环境下使用。 特别注意
为了方便演示,在下面的 Reloader Agent 实现中,使用 Linux Socket 通信来替代 HTTP 接口。由于需要在容器之间通过 Volume(卷)共享 Socket 文件,因此生产环境推荐使用 HTTP 接口来实现 Reloader Agent,而不是 Linux Socket 通信。
实现步骤 创建 YAML 配置文件(比如 nginx-hotreload.yaml) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 apiVersion: v1 kind: ConfigMap metadata: name: nginx-config data: default.conf: | server { listen 80; location / { return 200 "Hello from ConfigMap.\n"; } } --- apiVersion: v1 kind: Service metadata: name: nginx-hotreload spec: type: NodePort selector: app: nginx-hotreload ports: - port: 80 targetPort: 80 nodePort: 30080 --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx-hotreload spec: replicas: 1 selector: matchLabels: app: nginx-hotreload template: metadata: labels: app: nginx-hotreload spec: containers: - name: nginx image: nginx:1.15 ports: - containerPort: 80 command: ["/bin/sh" , "-c" ] args: - | # 创建共享运行目录 mkdir -p /var/run/nginx # 创建 Socket 文件 SOCKET=/var/run/nginx/nginx-reload.sock rm -f $SOCKET mkfifo $SOCKET # 启动 Nginx echo "$(date '+%F %T') [INFO] Starting Nginx ..." nginx -g 'daemon off;' & echo "$(date '+%F %T') [INFO] Reload agent started, listening on $SOCKET" # 监听 Sidecar 发来的 Reload 请求 while true; do if read line < $SOCKET; then echo "$(date '+%F %T') [INFO] Reload request received from Sidecar" # 先校验配置文件 if nginx -t -q; then echo "$(date '+%F %T') [INFO] Nginx config test passed" # 检查 PID 文件 if [ -f /var/run/nginx.pid ]; then kill -HUP $(cat /var/run/nginx.pid) echo "$(date '+%F %T') [INFO] Nginx reloaded successfully" else echo "$(date '+%F %T') [WARN] file nginx.pid not found" fi else echo "$(date '+%F %T') [ERROR] Invalid nginx config, skipping reload" fi fi done volumeMounts: - name: nginx-config-cm mountPath: /etc/nginx/conf.d - name: nginx-run mountPath: /var/run/nginx - name: reloader image: alpine:3.19 command: ["/bin/sh" , "-c" ] args: - | echo "$(date '+%F %T') [INFO] Starting config reloader ..." last_sum="" while true; do new_sum=$(find /etc/nginx/conf.d -type f -exec md5sum {} + | sort | md5sum) if [ -z "$last_sum" ]; then last_sum="$new_sum" elif [ "$new_sum" != "$last_sum" ]; then echo "$(date '+%F %T') [INFO] Config change detected, reloading nginx ..." if [ -p /var/run/nginx/nginx-reload.sock ]; then echo "reload" > /var/run/nginx/nginx-reload.sock else echo "$(date '+%F %T') [WARN] file nginx-reload.sock not found" fi last_sum="$new_sum" fi sleep 5 done volumeMounts: - name: nginx-config-cm mountPath: /etc/nginx/conf.d - name: nginx-run mountPath: /var/run/nginx volumes: - name: nginx-config-cm configMap: name: nginx-config - name: nginx-run emptyDir: {}
1 2 kubectl apply -f nginx-hotreload.yaml
关键点
Nginx 默认使用的是 /var/run/nginx.pid 文件,该文件的内容就是 Nginx Master 进程的 ID。 命令 kill -HUP <master_pid> 与 nginx -s reload 的效果在 Nginx 中是等价的,两者都会触发 Nginx 热加载(Reload)配置文件。 对于 kill -HUP <master_pid> 命令,这里必须使用 Nginx Master 进程的 ID,而不是 Worker 进程的 ID,否则无法实现 Nginx 平滑更新配置文件(不中断请求)。 验证步骤 1 2 3 NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 66d nginx-hotreload NodePort 10.0.0.126 <none> 80:30080/TCP 59s
1 2 kubectl get pods -o wide
1 2 NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx-hotreload-9d56b494b-vbkhv 2/2 Running 0 74s 10.244.0.39 k8s-node2 <none> <none>
1 2 kubectl logs nginx-hotreload-9d56b494b-vbkhv -c nginx
1 2 2025-10-05 10:26:53 [INFO] Starting Nginx ... 2025-10-05 10:26:53 [INFO] Reload agent started, listening on /var/run/nginx/nginx-reload.sock
1 2 kubectl logs nginx-hotreload-9d56b494b-vbkhv -c reloader
1 2025-10-05 09:42:50 [INFO] Starting config reloader ...
1 2 wget -qO - http://192.168.2.131:30080
1 2 kubectl create configmap nginx-config --from -literal=default.conf="server { listen 80; return 200 'Updated ConfigMap.\n'; }" --dry -run=client -o yaml | kubectl apply -f -
1 2 kubectl logs nginx-hotreload-9d56b494b-vbkhv -c reloader
1 2 2025-10-05 10:26:53 [INFO] Starting config reloader ... 2025-10-05 10:32:09 [INFO] Config change detected, reloading nginx ...
1 2 kubectl logs nginx-hotreload-9d56b494b-vbkhv -c nginx
1 2 3 4 2025-10-05 10:26:53 [INFO] Reload agent started, listening on /var/run/nginx/nginx-reload.sock 2025-10-05 10:32:09 [INFO] Reload request received from Sidecar 2025-10-05 10:32:09 [INFO] Nginx config test passed 2025-10-05 10:32:09 [INFO] Nginx reloaded successfully
1 2 wget -qO - http://192.168.2.131:30080
方案补充说明 如果更新 ConfigMap 后不要求 Nginx 进行热加载,且可以接受 Pod 重启或者 Nginx 中断现有请求,那么可以手动触发 Deployment 的滚动重启,例如执行命令 kubectl rollout restart deployment <name>。Pod 重启后,Nginx 会自动加载最新的配置。 更推荐使用自动检测 ConfigMap 变更的方案(更高级),例如借助 Stakater Reloader 等第三方工具监控 ConfigMap 的变化。一旦检测到更新,就会自动触发相关 Pod 的滚动更新,从而确保配置自动生效。 参考资料