Kubernetes 中实现 Nginx 配置信息自动热加载

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
# 定义 ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
data:
default.conf: |
server {
listen 80;
location / {
return 200 "Hello from ConfigMap.\n";
}
}
---
# 定义 Service
apiVersion: v1
kind: Service
metadata:
name: nginx-hotreload
spec:
type: NodePort # Service 类型为 NodePort,可通过节点 IP 访问
selector:
app: nginx-hotreload
ports:
- port: 80 # Service 对外暴露的端口,集群内部访问时也可以使用该端口
targetPort: 80 # Pod 内容器实际监听的端口
nodePort: 30080 # 映射到物理机的端口号,默认范围 30000 - 32767
---
# 定义 Deployment
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:
# 主容器:用于运行 Nginx
- name: nginx
image: nginx:1.15
ports:
- containerPort: 80
volumeMounts:
- name: nginx-config-cm
mountPath: /etc/nginx/conf.d
# Sidecar 容器:用于监控文件变更,并 Reload Nginx
- 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
# 定义卷(Volume)
volumes:
- name: nginx-config-cm # 指定卷的名称
configMap: # 指定卷的类型为 ConfigMap
name: nginx-config # 指定引用的 ConfigMap 名称(需事先创建)
  • 创建或更新 YAML 文件中定义的资源对象
1
2
# 创建 ConfigMap 和 Deployment
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 平滑更新配置文件(不中断请求)。

验证步骤

  • 验证 Nginx 配置信息自动热加载的步骤
1
2
# 查看 Service 列表
kubectl get svc
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
# 查看 Pod 的运行状态
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
# 查看 Sidecar 容器的日志
kubectl logs nginx-hotreload-6646755fcf-bq26b -c reloader
1
2025-10-05 10:03:24 [INFO] Starting config reloader ...
1
2
3
4
5
# 进入 Sidecar 容器内部
kubectl exec -it nginx-hotreload-6646755fcf-bq26b -c reloader sh

# 查看 Nginx 的进程列表
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
# 在集群外部通过节点 IP 访问 Nginx
wget -qO- http://192.168.2.112:30080
1
Hello from ConfigMap.
1
2
# 更改 ConfigMap
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
# 等待约 60 秒后,再次查看 Sidecar 容器的日志
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
# 在集群外部再次通过节点 IP 访问 Nginx
wget -qO- http://192.168.2.112:30080
1
Updated ConfigMap.

实现方案二

方案介绍

使用 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
# 定义 ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
data:
default.conf: |
server {
listen 80;
location / {
return 200 "Hello from ConfigMap.\n";
}
}
---
# 定义 Service
apiVersion: v1
kind: Service
metadata:
name: nginx-hotreload
spec:
type: NodePort # Service 类型为 NodePort,可通过节点 IP 访问
selector:
app: nginx-hotreload
ports:
- port: 80 # Service 对外暴露的端口,集群内部访问时也可以使用该端口
targetPort: 80 # Pod 内容器实际监听的端口
nodePort: 30080 # 映射到物理机的端口号,默认范围 30000 - 32767
---
# 定义 Deployment
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:
# 主容器:用于运行 Nginx + Reload Agent
- 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
# Sidecar 容器:用于监控文件变更,并发送 Reload 请求
- 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
# 定义卷(Volume)
volumes:
- name: nginx-config-cm # 指定卷的名称
configMap: # 指定卷的类型为 ConfigMap
name: nginx-config # 指定引用的 ConfigMap 名称(需事先创建)
- name: nginx-run # 指定卷的名称
emptyDir: {} # 共享运行目录(用于存放 nginx-reload.sock 文件)
  • 创建或更新 YAML 文件中定义的资源对象
1
2
# 创建 ConfigMap 和 Deployment
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 平滑更新配置文件(不中断请求)。

验证步骤

  • 验证 Nginx 配置信息自动热加载的步骤
1
2
# 查看 Service 列表
kubectl get svc
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
# 查看 Pod 的运行状态
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
# 查看 Nginx 容器的日志
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
# 查看 Sidecar 容器的日志
kubectl logs nginx-hotreload-9d56b494b-vbkhv -c reloader
1
2025-10-05 09:42:50 [INFO] Starting config reloader ...
1
2
# 在集群外部通过节点 IP 访问 Nginx
wget -qO- http://192.168.2.131:30080
1
Hello from ConfigMap.
1
2
# 更改 ConfigMap
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
# 等待约 60 秒后,再次查看 Sidecar 容器的日志
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
# 等待约 60 秒后,再次查看 Nginx 容器的日志
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
# 在集群外部再次通过节点 IP 访问 Nginx
wget -qO- http://192.168.2.131:30080
1
Updated ConfigMap.

方案补充说明

  • 如果更新 ConfigMap 后不要求 Nginx 进行热加载,且可以接受 Pod 重启或者 Nginx 中断现有请求,那么可以手动触发 Deployment 的滚动重启,例如执行命令 kubectl rollout restart deployment <name>。Pod 重启后,Nginx 会自动加载最新的配置。
  • 更推荐使用自动检测 ConfigMap 变更的方案(更高级),例如借助 Stakater Reloader 等第三方工具监控 ConfigMap 的变化。一旦检测到更新,就会自动触发相关 Pod 的滚动更新,从而确保配置自动生效。

参考资料