Skip to content

Commit 2eb63bd

Browse files
committed
init
0 parents  commit 2eb63bd

File tree

8 files changed

+443
-0
lines changed

8 files changed

+443
-0
lines changed

.cnb.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"**":
2+
push:
3+
- services:
4+
- docker
5+
stages:
6+
- name: docker login
7+
script:
8+
- docker login -u ${CNB_TOKEN_USER_NAME} -p "${CNB_TOKEN}" ${CNB_DOCKER_REGISTRY}
9+
- name: docker build
10+
script:
11+
- docker build -t ${CNB_DOCKER_REGISTRY}/${CNB_REPO_SLUG_LOWERCASE}:${CNB_COMMIT} -t ${CNB_DOCKER_REGISTRY}/${CNB_REPO_SLUG_LOWERCASE}:latest .
12+
- name: docker push
13+
script:
14+
- docker push ${CNB_DOCKER_REGISTRY}/${CNB_REPO_SLUG_LOWERCASE}:${CNB_COMMIT}
15+
- docker push ${CNB_DOCKER_REGISTRY}/${CNB_REPO_SLUG_LOWERCASE}:latest

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.idea

Dockerfile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
FROM golang:1.23.5-bookworm
2+
3+
WORKDIR /app
4+
COPY . .
5+
RUN go build -o nginx-reloader .
6+
7+
CMD /app/nginx-reloader
8+
9+

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# K8S Nginx Hot Reload
2+
3+
## Know-Why
4+
5+
* [share-process-namespace](https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/)
6+
* Processes are visible to other containers in the pod.
7+
* Container filesystems are visible to other containers in the pod through the `/proc/$pid/root` link.
8+
* SideCar Container
9+
* Go program
10+
* kill -HUP pid.
11+
* Watch nginx config dirs.
12+
* Event debounce.
13+
* [configmap updated automatically](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#mounted-configmaps-are-updated-automatically)
14+
15+
## How-Use
16+
17+
* example [nginx-reloader.yml](https://github.com/cheerego/nginx-reloader/blob/master/nginx-reloader.yml)
18+
* enable shareProcessNamespace
19+
* TIME_AFTER_SECONDS, Event debounce default second.
20+
* WATCH_DIRS, split by comma, like `/etc/nginx,/etc/nginx/conf.d`

go.mod

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
module nginx-reloader
2+
3+
go 1.23.2
4+
5+
require (
6+
github.com/fsnotify/fsnotify v1.8.0
7+
github.com/pkg/errors v0.9.1
8+
github.com/shirou/gopsutil/v4 v4.24.12
9+
golang.org/x/sync v0.10.0
10+
)
11+
12+
require (
13+
github.com/ebitengine/purego v0.8.1 // indirect
14+
github.com/go-ole/go-ole v1.2.6 // indirect
15+
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
16+
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
17+
github.com/tklauser/go-sysconf v0.3.12 // indirect
18+
github.com/tklauser/numcpus v0.6.1 // indirect
19+
github.com/yusufpapurcu/wmi v1.2.4 // indirect
20+
golang.org/x/sys v0.28.0 // indirect
21+
)

go.sum

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE=
4+
github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
5+
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
6+
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
7+
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
8+
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
9+
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
10+
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
11+
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
12+
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
13+
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
14+
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
15+
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
16+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
17+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
18+
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
19+
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
20+
github.com/shirou/gopsutil/v4 v4.24.12 h1:qvePBOk20e0IKA1QXrIIU+jmk+zEiYVVx06WjBRlZo4=
21+
github.com/shirou/gopsutil/v4 v4.24.12/go.mod h1:DCtMPAad2XceTeIAbGyVfycbYQNBGk2P8cvDi7/VN9o=
22+
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
23+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
24+
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
25+
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
26+
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
27+
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
28+
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
29+
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
30+
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
31+
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
32+
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
33+
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
34+
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
35+
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
36+
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
37+
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
38+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
39+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
40+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

main.go

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/fsnotify/fsnotify"
7+
"github.com/pkg/errors"
8+
"github.com/shirou/gopsutil/v4/process"
9+
"golang.org/x/sync/errgroup"
10+
"io/fs"
11+
"log"
12+
"os"
13+
"os/exec"
14+
"os/signal"
15+
"path/filepath"
16+
"strconv"
17+
"strings"
18+
"sync"
19+
"syscall"
20+
"time"
21+
)
22+
23+
// https://kubernetes.io/zh-cn/docs/tasks/configure-pod-container/share-process-namespace/
24+
// https://cctoctofx.netlify.app/post/cloud-computing/k8s-config-update/
25+
26+
func main() {
27+
time.Sleep(10 * time.Second)
28+
err := NewNginxReloadWatcher().Start()
29+
if err != nil {
30+
log.Println("[ERROR] start watcher err")
31+
}
32+
}
33+
34+
type NginxReloadWatcher struct {
35+
sync.Mutex
36+
nginxMasterPid int32
37+
timer *time.Timer
38+
timeAfterSeconds time.Duration
39+
40+
// file watch 的目录,用逗号分割
41+
// 如 /etc/nginx,/etc/nginx/conf.d
42+
watchDirs []string
43+
}
44+
45+
func NewNginxReloadWatcher() *NginxReloadWatcher {
46+
47+
timeAfterSecondsEnv := os.Getenv("TIME_AFTER_SECONDS")
48+
var timeAfter = 30
49+
atoi, err := strconv.Atoi(timeAfterSecondsEnv)
50+
if err == nil {
51+
timeAfter = atoi
52+
}
53+
log.Println("TIME_AFTER_SECONDS: ", timeAfter)
54+
55+
watchDirsEnv := os.Getenv("WATCH_DIRS")
56+
57+
log.Println("WATCH_DIRS: ", watchDirsEnv)
58+
watchDirs := strings.Split(watchDirsEnv, ",")
59+
log.Println("WATCH_DIRS after split: ", watchDirs)
60+
61+
return &NginxReloadWatcher{
62+
timeAfterSeconds: time.Duration(timeAfter) * time.Second,
63+
watchDirs: watchDirs,
64+
}
65+
}
66+
67+
func (n *NginxReloadWatcher) findNginxMaterPid() (int32, error) {
68+
processes, err := process.Processes()
69+
if err != nil {
70+
return 0, err
71+
}
72+
73+
for _, p := range processes {
74+
name, err := p.Name()
75+
if err != nil {
76+
return 0, errors.Wrap(err, "process Name err")
77+
}
78+
if name != "nginx" {
79+
continue
80+
}
81+
ppid, err := p.Ppid()
82+
if err != nil {
83+
return 0, errors.Wrap(err, "process ppid err")
84+
}
85+
if ppid == 0 {
86+
return p.Pid, nil
87+
}
88+
}
89+
return 0, errors.New("not found nginx master")
90+
}
91+
92+
func (n *NginxReloadWatcher) Start() error {
93+
pid, err := n.findNginxMaterPid()
94+
if err != nil {
95+
return err
96+
}
97+
log.Println(fmt.Sprintf("nginx master id %d", pid))
98+
n.nginxMasterPid = pid
99+
100+
g, gctx := errgroup.WithContext(context.TODO())
101+
g.Go(func() error {
102+
killSignal := make(chan os.Signal, 1)
103+
signal.Notify(killSignal, os.Interrupt, syscall.SIGTERM, os.Kill)
104+
<-killSignal
105+
return errors.New("kill signal")
106+
})
107+
108+
g.Go(func() error {
109+
watcher, err := fsnotify.NewWatcher()
110+
if err != nil {
111+
return err
112+
}
113+
defer watcher.Close()
114+
115+
// Add a path.
116+
for _, parentDir := range n.watchDirs {
117+
parentDir = fmt.Sprintf("/proc/%d/root%s", pid, parentDir)
118+
err = watcher.Add(parentDir)
119+
if err != nil {
120+
return err
121+
}
122+
err = filepath.WalkDir(parentDir, func(path string, d fs.DirEntry, err error) error {
123+
if err != nil {
124+
fmt.Printf("无法访问路径 %q: %v\n", path, err)
125+
return err // 继续遍历其他文件/目录
126+
}
127+
if d.IsDir() {
128+
watcher.Add(path)
129+
}
130+
return nil
131+
})
132+
if err != nil {
133+
return errors.Wrap(err, "遍历目录时出错")
134+
}
135+
}
136+
137+
watchList := watcher.WatchList()
138+
139+
for _, item := range watchList {
140+
log.Println(item)
141+
}
142+
143+
for {
144+
select {
145+
case <-gctx.Done():
146+
return errors.New("gctx done")
147+
case event, ok := <-watcher.Events:
148+
if !ok {
149+
continue
150+
}
151+
log.Println("event:", event)
152+
if event.Has(fsnotify.Create) || event.Has(fsnotify.Write) || event.Has(fsnotify.Remove) || event.Has(fsnotify.Rename) || event.Has(fsnotify.Chmod) {
153+
n.timerHubSignal(event.String())
154+
}
155+
156+
case err, ok := <-watcher.Errors:
157+
if !ok {
158+
continue
159+
}
160+
log.Println("error:", err)
161+
}
162+
}
163+
164+
})
165+
return g.Wait()
166+
}
167+
168+
func (n *NginxReloadWatcher) timerHubSignal(event string) {
169+
170+
n.Lock()
171+
defer n.Unlock()
172+
173+
if n.timer != nil {
174+
log.Println("[INFO] stop preview timer")
175+
n.timer.Stop()
176+
}
177+
//
178+
log.Println("[INFO] set new timer ", event)
179+
n.timer = time.AfterFunc(n.timeAfterSeconds, func() {
180+
n.Lock()
181+
defer n.Unlock()
182+
n.timer = nil
183+
184+
command := exec.Command("/bin/bash", "-c", fmt.Sprintf("kill -HUP %d", n.nginxMasterPid))
185+
output, err := command.CombinedOutput()
186+
s := string(output)
187+
if err != nil {
188+
log.Println(fmt.Sprintf("kill -HUP %d err, err %v, output %s, event %s", n.nginxMasterPid, err, s, event))
189+
return
190+
}
191+
log.Println(fmt.Sprintf("kill -HUP %d success, output %s, event %s", n.nginxMasterPid, s, event))
192+
})
193+
}

0 commit comments

Comments
 (0)