diff --git a/init.go b/init.go index 79176e2f14f..7933630635b 100644 --- a/init.go +++ b/init.go @@ -2,7 +2,6 @@ package main import ( "os" - "runtime" "github.com/opencontainers/runc/libcontainer" _ "github.com/opencontainers/runc/libcontainer/nsenter" @@ -12,14 +11,6 @@ func init() { if len(os.Args) > 1 && os.Args[1] == "init" { // This is the golang entry point for runc init, executed // before main() but after libcontainer/nsenter's nsexec(). - runtime.GOMAXPROCS(1) - runtime.LockOSThread() - - if err := libcontainer.StartInitialization(); err != nil { - // as the error is sent back to the parent there is no need to log - // or write it to stderr because the parent process will handle this - os.Exit(1) - } - panic("libcontainer: container init failed to exec") + libcontainer.Init() } } diff --git a/libcontainer/README.md b/libcontainer/README.md index 26f7b25c2ee..55cf0b98cca 100644 --- a/libcontainer/README.md +++ b/libcontainer/README.md @@ -23,22 +23,8 @@ function as the entry of "bootstrap". In addition to the go init function the early stage bootstrap is handled by importing [nsenter](https://github.com/opencontainers/runc/blob/master/libcontainer/nsenter/README.md). -```go -import ( - _ "github.com/opencontainers/runc/libcontainer/nsenter" -) - -func init() { - if len(os.Args) > 1 && os.Args[1] == "init" { - runtime.GOMAXPROCS(1) - runtime.LockOSThread() - if err := libcontainer.StartInitialization(); err != nil { - logrus.Fatal(err) - } - panic("--this line should have never been executed, congratulations--") - } -} -``` +For details on how runc implements such "init", see +[init.go]((https://github.com/opencontainers/runc/blob/master/libcontainer/init.go). Then to create a container you first have to create a configuration struct describing how the container is to be created. A sample would look similar to this: diff --git a/libcontainer/init_linux.go b/libcontainer/init_linux.go index e5b2540a21b..a631046703e 100644 --- a/libcontainer/init_linux.go +++ b/libcontainer/init_linux.go @@ -8,6 +8,7 @@ import ( "io" "net" "os" + "runtime" "runtime/debug" "strconv" "strings" @@ -73,10 +74,30 @@ type initConfig struct { Cgroup2Path string `json:"cgroup2_path,omitempty"` } -// StartInitialization loads a container by opening the pipe fd from the parent -// to read the configuration and state. This is a low level implementation -// detail of the reexec and should not be consumed externally. -func StartInitialization() (retErr error) { +// Init is part of "runc init" implementation. +func Init() { + runtime.GOMAXPROCS(1) + runtime.LockOSThread() + + if err := startInitialization(); err != nil { + // If the error is returned, it was not communicated + // back to the parent (which is not a common case), + // so print it to stderr here as a last resort. + // + // Do not use logrus as we are not sure if it has been + // set up yet, but most important, if the parent is + // alive (and its log forwarding is working). + fmt.Fprintln(os.Stderr, err) + } + // Normally, StartInitialization() never returns, meaning + // if we are here, it had failed. + os.Exit(1) +} + +// Normally, this function does not return. If it returns, with or without an +// error, it means the initialization has failed. If the error is returned, +// it means the error can not be communicated back to the parent. +func startInitialization() (retErr error) { // Get the INITPIPE. envInitPipe := os.Getenv("_LIBCONTAINER_INITPIPE") pipefd, err := strconv.Atoi(envInitPipe) @@ -87,16 +108,18 @@ func StartInitialization() (retErr error) { defer pipe.Close() defer func() { - // We have an error during the initialization of the container's init, - // send it back to the parent process in the form of an initError. + // If this defer is ever called, this means initialization has failed. + // Send the error back to the parent process in the form of an initError. if err := writeSync(pipe, procError); err != nil { - fmt.Fprintln(os.Stderr, retErr) + fmt.Fprintln(os.Stderr, err) return } if err := utils.WriteJSON(pipe, &initError{Message: retErr.Error()}); err != nil { - fmt.Fprintln(os.Stderr, retErr) + fmt.Fprintln(os.Stderr, err) return } + // The error is sent, no need to also return it (or it will be reported twice). + retErr = nil }() // Set up logging. This is used rarely, and mostly for init debugging. diff --git a/libcontainer/integration/init_test.go b/libcontainer/integration/init_test.go index efcbe72b0f1..a79ffe5c36f 100644 --- a/libcontainer/integration/init_test.go +++ b/libcontainer/integration/init_test.go @@ -1,9 +1,7 @@ package integration import ( - "fmt" "os" - "runtime" "testing" "github.com/opencontainers/runc/libcontainer" @@ -14,20 +12,9 @@ import ( // Same as ../../init.go but for libcontainer/integration. func init() { - if len(os.Args) < 2 || os.Args[1] != "init" { - return + if len(os.Args) > 1 && os.Args[1] == "init" { + libcontainer.Init() } - // This is the golang entry point for runc init, executed - // before TestMain() but after libcontainer/nsenter's nsexec(). - runtime.GOMAXPROCS(1) - runtime.LockOSThread() - if err := libcontainer.StartInitialization(); err != nil { - // logrus is not initialized - fmt.Fprintln(os.Stderr, err) - } - // Normally, StartInitialization() never returns, meaning - // if we are here, it had failed. - os.Exit(1) } func TestMain(m *testing.M) {