Skip to content

Commit

Permalink
Replace dumb-init with go init (redhat-developer#35)
Browse files Browse the repository at this point in the history
* Moving to use go-init instead of dumb-init

Signed-off-by: Mohammed Zeeshan Ahmed <mohammed.zee1000@gmail.com>

* Moving go-init out of vendor and updating Dockerfile accordingly

* Commenting out everything that has to do with dumb-init from Dockerfile

* Removing dumb-init and any remaining references

* Updating image to follow /go/src/github.com/pablo-ruth/go-init
  • Loading branch information
mohammedzee1000 authored and openshift-merge-robot committed Sep 18, 2019
1 parent 2db434d commit 49d40b7
Show file tree
Hide file tree
Showing 49 changed files with 269 additions and 2,225 deletions.
22 changes: 7 additions & 15 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# This is an "initContainer" image used by odo to inject required tools for odo to work properly.
#

# Build Go stuff (SupervisorD and getlanguage)
# Build Go stuff (SupervisorD, getlanguage and go-init)

FROM registry.svc.ci.openshift.org/openshift/release:golang-1.11 AS gobuilder

Expand All @@ -16,19 +16,13 @@ ADD get-language /go/src/github.com/openshift/odo-supervisord-image/get-language
WORKDIR /go/src/github.com/openshift/odo-supervisord-image/get-language
RUN go build -o /tmp/getlanguage getlanguage.go


# Build dumb-init
FROM registry.access.redhat.com/ubi7/ubi AS dumbinitbuilder
WORKDIR /tmp/dumb-init-src
RUN yum -y install gcc make binutils
COPY vendor/dumb-init /tmp/dumb-init-src
RUN gcc -std=gnu99 -s -Wall -Werror -O3 -o dumb-init dumb-init.c

RUN mkdir -p /go/src/github.com/pablo-ruth/go-init
ADD go-init/main.go /go/src/github.com/pablo-ruth/go-init/go-init.go
RUN go build -o /tmp/go-init /go/src/github.com/pablo-ruth/go-init/go-init.go

# Final image
FROM registry.access.redhat.com/ubi7/ubi


LABEL com.redhat.component=atomic-openshift-odo-init-image-container \
name=openshift/odo-init-image \
io.k8s.display-name=atomic-openshift-odo-init-image \
Expand All @@ -40,10 +34,6 @@ LABEL version=0.10.0

ENV ODO_TOOLS_DIR /opt/odo-init/

# dumb-init
COPY --from=dumbinitbuilder /tmp/dumb-init-src/dumb-init ${ODO_TOOLS_DIR}/bin/dumb-init
RUN chmod +x ${ODO_TOOLS_DIR}/bin/dumb-init

# SupervisorD
RUN mkdir -p ${ODO_TOOLS_DIR}/conf ${ODO_TOOLS_DIR}/bin
COPY supervisor.conf ${ODO_TOOLS_DIR}/conf/
Expand All @@ -55,9 +45,11 @@ COPY run ${ODO_TOOLS_DIR}/bin
COPY s2i-setup ${ODO_TOOLS_DIR}/bin
COPY setup-and-run ${ODO_TOOLS_DIR}/bin
COPY vendor/fix-permissions /usr/bin/fix-permissions

COPY language-scripts ${ODO_TOOLS_DIR}/language-scripts/

# Get Language and go-init
COPY --from=gobuilder /tmp/getlanguage ${ODO_TOOLS_DIR}/bin/getlanguage
COPY --from=gobuilder /tmp/go-init ${ODO_TOOLS_DIR}/bin/go-init

RUN chgrp -R 0 ${ODO_TOOLS_DIR} && \
chmod -R g+rwX ${ODO_TOOLS_DIR} && \
Expand Down
12 changes: 6 additions & 6 deletions vendor/dumb-init/LICENSE → go-init/LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)
MIT License

Copyright (c) 2015 Yelp, Inc.
Copyright (c) 2017 Pablo RUTH

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand All @@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
68 changes: 68 additions & 0 deletions go-init/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# go-init

[![Build Status](https://travis-ci.org/pablo-ruth/go-init.svg?branch=master)](https://travis-ci.org/pablo-ruth/go-init)

**go-init** is a minimal init system with simple *lifecycle management* heavily inspired by [dumb-init](https://github.com/Yelp/dumb-init).

It is designed to run as the first process (PID 1) inside a container.

It is lightweight (less than 500KB after UPX compression) and statically linked so you don't need to install any dependency.

## Download

You can download the latest version on [releases page](https://github.com/pablo-ruth/go-init/releases)

## Why you need an init system

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)

Summary:
- Proper signal forwarding
- Orphaned zombies reaping

## Why another minimal init system

In addition to *init* problematic, **go-init** tries to solve another Docker flaw by adding *hooks* on start and stop of the main process.

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)

With **go-init** you can do that with "pre" and "post" hooks.

## Usage

### one command

```
$ go-init -main "my_command param1 param2"
```

### pre-start and post-stop hooks

```
$ go-init -pre "my_pre_command param1" -main "my_command param1 param2" -post "my_post_command param1"
```

## docker

Example of Dockerfile using *go-init*:
```
FROM alpine:latest
COPY go-init /bin/go-init
RUN chmod +x /bin/go-init
ENTRYPOINT ["go-init"]
CMD ["-pre", "echo hello world", "-main", "sleep 5", "-post", "echo I finished my sleep bye"]
```

Build it:
```
docker build -t go-init-example
```

Run it:
```
docker run go-init-example
```
188 changes: 188 additions & 0 deletions go-init/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package main

import (
"context"
"flag"
"fmt"
"log"
"os"
"os/exec"
"os/signal"
"strings"
"sync"
"syscall"
"time"
)

var (
versionString = "undefined"
)

func main() {
var preStartCmd string
var mainCmd string
var postStopCmd string
var version bool

flag.StringVar(&preStartCmd, "pre", "", "Pre-start command")
flag.StringVar(&mainCmd, "main", "", "Main command")
flag.StringVar(&postStopCmd, "post", "", "Post-stop command")
flag.BoolVar(&version, "version", false, "Display go-init version")
flag.Parse()

if version {
fmt.Println(versionString)
os.Exit(0)
}

if mainCmd == "" {
log.Fatal("[go-init] No main command defined, exiting")
}

// Routine to reap zombies (it's the job of init)
ctx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup
wg.Add(1)
go removeZombies(ctx, &wg)

// Launch pre-start command
if preStartCmd == "" {
log.Println("[go-init] No pre-start command defined, skip")
} else {
log.Printf("[go-init] Pre-start command launched : %s\n", preStartCmd)
err := run(preStartCmd)
if err != nil {
log.Println("[go-init] Pre-start command failed")
log.Printf("[go-init] %s\n", err)
cleanQuit(cancel, &wg, 1)
} else {
log.Printf("[go-init] Pre-start command exited")
}
}

// Launch main command
var mainRC int
log.Printf("[go-init] Main command launched : %s\n", mainCmd)
err := run(mainCmd)
if err != nil {
log.Println("[go-init] Main command failed")
log.Printf("[go-init] %s\n", err)
mainRC = 1
} else {
log.Printf("[go-init] Main command exited")
}

// Launch post-stop command
if postStopCmd == "" {
log.Println("[go-init] No post-stop command defined, skip")
} else {
log.Printf("[go-init] Post-stop command launched : %s\n", postStopCmd)
err := run(postStopCmd)
if err != nil {
log.Println("[go-init] Post-stop command failed")
log.Printf("[go-init] %s\n", err)
cleanQuit(cancel, &wg, 1)
} else {
log.Printf("[go-init] Post-stop command exited")
}
}

// Wait removeZombies goroutine
cleanQuit(cancel, &wg, mainRC)
}

func removeZombies(ctx context.Context, wg *sync.WaitGroup) {
for {
var status syscall.WaitStatus

// Wait for orphaned zombie process
pid, _ := syscall.Wait4(-1, &status, syscall.WNOHANG, nil)

if pid <= 0 {
// PID is 0 or -1 if no child waiting
// so we wait for 1 second for next check
time.Sleep(1 * time.Second)
} else {
// PID is > 0 if a child was reaped
// we immediately check if another one
// is waiting
continue
}

// Non-blocking test
// if context is done
select {
case <-ctx.Done():
// Context is done
// so we stop goroutine
wg.Done()
return
default:
}
}
}

func run(command string) error {

var commandStr string
var argsSlice []string

// Split cmd and args
commandSlice := strings.Fields(command)
commandStr = commandSlice[0]
// if there is args
if len(commandSlice) > 1 {
argsSlice = commandSlice[1:]
}

// Register chan to receive system signals
sigs := make(chan os.Signal, 1)
defer close(sigs)
signal.Notify(sigs)
defer signal.Reset()

// Define command and rebind
// stdout and stdin
cmd := exec.Command(commandStr, argsSlice...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
// Create a dedicated pidgroup
// used to forward signals to
// main process and all children
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

// Goroutine for signals forwarding
go func() {
for sig := range sigs {
// Ignore SIGCHLD signals since
// thez are only usefull for go-init
if sig != syscall.SIGCHLD {
// Forward signal to main process and all children
syscall.Kill(-cmd.Process.Pid, sig.(syscall.Signal))
}
}
}()

// Start defined command
err := cmd.Start()
if err != nil {
return err
}

// Wait for command to exit
err = cmd.Wait()
if err != nil {
return err
}

return nil
}

func cleanQuit(cancel context.CancelFunc, wg *sync.WaitGroup, code int) {
// Signal zombie goroutine to stop
// and wait for it to release waitgroup
cancel()
wg.Wait()

os.Exit(code)
}
11 changes: 0 additions & 11 deletions scripts/update-vendor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ set +eux

# Variables for version updates
DUMBINIT_VERSION="1.2.2"
SUPERVISORD_VERSION="0.5"
##
VDUMBINIT_VERSION="v${DUMBINIT_VERSION}"
VSUPERVISORD_VERSION="v${SUPERVISORD_VERSION}"
REQUIREMENTS_DIR="$(pwd)/vendor"

update_fix_permissions() {
Expand All @@ -29,14 +27,6 @@ download_tar() {
popd
}

update_dumb_init() {
echo "Downloading dump init src"
DUMBINIT_DOWNLOAD_TARGET="${REQUIREMENTS_DIR}/dumb-init";
DUMBINIT_DOWNLOAD="https://github.com/Yelp/dumb-init/archive/${VDUMBINIT_VERSION}.tar.gz";
DUMBINIT_TARBALL_NAME="dumb-init-${DUMBINIT_VERSION}"
download_tar $DUMBINIT_DOWNLOAD $DUMBINIT_DOWNLOAD_TARGET $DUMBINIT_TARBALL_NAME
}

update_supervisord() {
echo "Downloading supervisord src"
SUPERVISORD_MOD_NAME="github.com/ochinchina/supervisord"
Expand All @@ -56,5 +46,4 @@ update_supervisord() {

# MAIN
update_fix_permissions
update_dumb_init
update_supervisord
Loading

0 comments on commit 49d40b7

Please sign in to comment.