Skip to content

Commit fb60d2b

Browse files
committed
initial commit
0 parents  commit fb60d2b

File tree

3 files changed

+271
-0
lines changed

3 files changed

+271
-0
lines changed

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2017 Pablo RUTH
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# go-init
2+
3+
**go-init** is a minimal init system with simple *lifecycle management* heavily inspired by [dumb-init](https://github.com/Yelp/dumb-init).
4+
5+
It is designed to run as the first process (PID 1) inside a container.
6+
7+
It is lightweight (less than 500KB after UPX compression) and statically linked so you don't need to install any dependency.
8+
9+
## Why you need an init system
10+
11+
I can't explain it better than Yelp in *dumb-init* repo, so please [read their explanation](https://github.com/Yelp/dumb-init/blob/v1.2.0/README.md#why-you-need-an-init-system)
12+
13+
Summary:
14+
- Proper signal forwarding
15+
- Orphaned zombies reaping
16+
17+
## Why another minimal init system
18+
19+
In addition to *init* problematic, **go-init** tries to solve another Docker flaw by adding *hooks* on start and stop of the main process.
20+
21+
If you want to launch a command before the main process of your container and another one after the main process exit, you can't with Docker, see [issue 6982](https://github.com/moby/moby/issues/6982)
22+
23+
With **go-init** you can do that with "pre" and "post" hooks.
24+
25+
## Usage
26+
27+
### one command
28+
29+
```
30+
$ go-init -main "my_command param1 param2"
31+
```
32+
33+
### pre-start and post-stop hooks
34+
35+
```
36+
$ go-init -pre "my_pre_command param1" -main "my_command param1 param2" -post "my_post_command param1"
37+
```
38+
39+
## docker
40+
41+
Example of Dockerfile using *go-init*:
42+
```
43+
FROM alpine:latest
44+
45+
COPY go-init /bin/go-init
46+
47+
RUN chmod +x /bin/go-init
48+
49+
ENTRYPOINT ["go-init"]
50+
51+
CMD ["-pre", "echo hello world", "-main", "sleep 5", "-post", "echo I finished my sleep bye"]
52+
```
53+
54+
Build it:
55+
```
56+
docker build -t go-init-example
57+
```
58+
59+
Run it:
60+
```
61+
docker run go-init-example
62+
```

main.go

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"flag"
6+
"fmt"
7+
"log"
8+
"os"
9+
"os/exec"
10+
"os/signal"
11+
"strings"
12+
"sync"
13+
"syscall"
14+
"time"
15+
)
16+
17+
var (
18+
versionString = "undefined"
19+
)
20+
21+
func main() {
22+
var preStartCmd string
23+
var mainCmd string
24+
var postStopCmd string
25+
var version bool
26+
27+
flag.StringVar(&preStartCmd, "pre", "", "Pre-start command")
28+
flag.StringVar(&mainCmd, "main", "", "Main command")
29+
flag.StringVar(&postStopCmd, "post", "", "Post-stop command")
30+
flag.BoolVar(&version, "version", false, "Display go-init version")
31+
flag.Parse()
32+
33+
if version {
34+
fmt.Println(versionString)
35+
os.Exit(0)
36+
}
37+
38+
if mainCmd == "" {
39+
log.Fatal("[go-init] No main command defined, exiting")
40+
}
41+
42+
// Routine to reap zombies (it's the job of init)
43+
ctx, cancel := context.WithCancel(context.Background())
44+
var wg sync.WaitGroup
45+
wg.Add(1)
46+
go removeZombies(ctx, &wg)
47+
48+
// Launch pre-start command
49+
if preStartCmd == "" {
50+
log.Println("[go-init] No pre-start command defined, skip")
51+
} else {
52+
log.Printf("[go-init] Pre-start command launched : %s\n", preStartCmd)
53+
err := run(preStartCmd)
54+
if err != nil {
55+
log.Println("[go-init] Pre-start command failed")
56+
log.Printf("[go-init] %s\n", err)
57+
cleanQuit(cancel, &wg, 1)
58+
} else {
59+
log.Printf("[go-init] Pre-start command exited")
60+
}
61+
}
62+
63+
// Launch main command
64+
var mainRC int
65+
log.Printf("[go-init] Main command launched : %s\n", mainCmd)
66+
err := run(mainCmd)
67+
if err != nil {
68+
log.Println("[go-init] Main command failed")
69+
log.Printf("[go-init] %s\n", err)
70+
mainRC = 1
71+
} else {
72+
log.Printf("[go-init] Main command exited")
73+
}
74+
75+
// Launch post-stop command
76+
if postStopCmd == "" {
77+
log.Println("[go-init] No post-stop command defined, skip")
78+
} else {
79+
log.Printf("[go-init] Post-stop command launched : %s\n", postStopCmd)
80+
err := run(postStopCmd)
81+
if err != nil {
82+
log.Println("[go-init] Post-stop command failed")
83+
log.Printf("[go-init] %s\n", err)
84+
cleanQuit(cancel, &wg, 1)
85+
} else {
86+
log.Printf("[go-init] Post-stop command exited")
87+
}
88+
}
89+
90+
// Wait removeZombies goroutine
91+
cleanQuit(cancel, &wg, mainRC)
92+
}
93+
94+
func removeZombies(ctx context.Context, wg *sync.WaitGroup) {
95+
for {
96+
var status syscall.WaitStatus
97+
98+
// Wait for orphaned zombie process
99+
pid, _ := syscall.Wait4(-1, &status, syscall.WNOHANG, nil)
100+
101+
if pid <= 0 {
102+
// PID is 0 or -1 if no child waiting
103+
// so we wait for 1 second for next check
104+
time.Sleep(1 * time.Second)
105+
} else {
106+
// PID is > 0 if a child was reaped
107+
// we immediately check if another one
108+
// is waiting
109+
continue
110+
}
111+
112+
// Non-blocking test
113+
// if context is done
114+
select {
115+
case <-ctx.Done():
116+
// Context is done
117+
// so we stop goroutine
118+
wg.Done()
119+
return
120+
default:
121+
}
122+
}
123+
}
124+
125+
func run(command string) error {
126+
127+
var commandStr string
128+
var argsSlice []string
129+
130+
// Split cmd and args
131+
commandSlice := strings.Fields(command)
132+
commandStr = commandSlice[0]
133+
// if there is args
134+
if len(commandSlice) > 1 {
135+
argsSlice = commandSlice[1:]
136+
}
137+
138+
// Register chan to receive system signals
139+
sigs := make(chan os.Signal, 1)
140+
defer close(sigs)
141+
signal.Notify(sigs)
142+
defer signal.Reset()
143+
144+
// Define command and rebind
145+
// stdout and stdin
146+
cmd := exec.Command(commandStr, argsSlice...)
147+
cmd.Stdout = os.Stdout
148+
cmd.Stderr = os.Stderr
149+
// Create a dedicated pidgroup
150+
// used to forward signals to
151+
// main process and all children
152+
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
153+
154+
// Goroutine for signals forwarding
155+
go func() {
156+
for sig := range sigs {
157+
// Ignore SIGCHLD signals since
158+
// thez are only usefull for go-init
159+
if sig != syscall.SIGCHLD {
160+
// Forward signal to main process and all children
161+
syscall.Kill(-cmd.Process.Pid, sig.(syscall.Signal))
162+
}
163+
}
164+
}()
165+
166+
// Start defined command
167+
err := cmd.Start()
168+
if err != nil {
169+
return err
170+
}
171+
172+
// Wait for command to exit
173+
err = cmd.Wait()
174+
if err != nil {
175+
return err
176+
}
177+
178+
return nil
179+
}
180+
181+
func cleanQuit(cancel context.CancelFunc, wg *sync.WaitGroup, code int) {
182+
// Signal zombie goroutine to stop
183+
// and wait for it to release waitgroup
184+
cancel()
185+
wg.Wait()
186+
187+
os.Exit(code)
188+
}

0 commit comments

Comments
 (0)