Skip to content

Commit cb44958

Browse files
authored
Merge pull request #3385 from kolyshkin/init-logger-setup
init simplification
2 parents 33ce0dc + 883aef7 commit cb44958

File tree

5 files changed

+63
-79
lines changed

5 files changed

+63
-79
lines changed

init.go

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,15 @@ package main
22

33
import (
44
"os"
5-
"runtime"
6-
"strconv"
75

86
"github.com/opencontainers/runc/libcontainer"
97
_ "github.com/opencontainers/runc/libcontainer/nsenter"
10-
"github.com/sirupsen/logrus"
118
)
129

1310
func init() {
1411
if len(os.Args) > 1 && os.Args[1] == "init" {
1512
// This is the golang entry point for runc init, executed
1613
// before main() but after libcontainer/nsenter's nsexec().
17-
runtime.GOMAXPROCS(1)
18-
runtime.LockOSThread()
19-
20-
level, err := strconv.Atoi(os.Getenv("_LIBCONTAINER_LOGLEVEL"))
21-
if err != nil {
22-
panic(err)
23-
}
24-
25-
logPipeFd, err := strconv.Atoi(os.Getenv("_LIBCONTAINER_LOGPIPE"))
26-
if err != nil {
27-
panic(err)
28-
}
29-
30-
logrus.SetLevel(logrus.Level(level))
31-
logrus.SetOutput(os.NewFile(uintptr(logPipeFd), "logpipe"))
32-
logrus.SetFormatter(new(logrus.JSONFormatter))
33-
logrus.Debug("child process in init()")
34-
35-
if err := libcontainer.StartInitialization(); err != nil {
36-
// as the error is sent back to the parent there is no need to log
37-
// or write it to stderr because the parent process will handle this
38-
os.Exit(1)
39-
}
40-
panic("libcontainer: container init failed to exec")
14+
libcontainer.Init()
4115
}
4216
}

libcontainer/README.md

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,9 @@ function as the entry of "bootstrap".
2323
In addition to the go init function the early stage bootstrap is handled by importing
2424
[nsenter](https://github.com/opencontainers/runc/blob/master/libcontainer/nsenter/README.md).
2525

26-
```go
27-
import (
28-
_ "github.com/opencontainers/runc/libcontainer/nsenter"
29-
)
30-
31-
func init() {
32-
if len(os.Args) > 1 && os.Args[1] == "init" {
33-
runtime.GOMAXPROCS(1)
34-
runtime.LockOSThread()
35-
if err := libcontainer.StartInitialization(); err != nil {
36-
logrus.Fatal(err)
37-
}
38-
panic("--this line should have never been executed, congratulations--")
39-
}
40-
}
41-
```
26+
For details on how runc implements such "init", see
27+
[init.go](https://github.com/opencontainers/runc/blob/master/init.go)
28+
and [libcontainer/init_linux.go](https://github.com/opencontainers/runc/blob/master/libcontainer/init_linux.go).
4229

4330
Then to create a container you first have to create a configuration
4431
struct describing how the container is to be created. A sample would look similar to this:

libcontainer/container_linux.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -496,9 +496,10 @@ func (c *Container) commandTemplate(p *Process, childInitPipe *os.File, childLog
496496

497497
cmd.ExtraFiles = append(cmd.ExtraFiles, childLogPipe)
498498
cmd.Env = append(cmd.Env,
499-
"_LIBCONTAINER_LOGPIPE="+strconv.Itoa(stdioFdCount+len(cmd.ExtraFiles)-1),
500-
"_LIBCONTAINER_LOGLEVEL="+p.LogLevel,
501-
)
499+
"_LIBCONTAINER_LOGPIPE="+strconv.Itoa(stdioFdCount+len(cmd.ExtraFiles)-1))
500+
if p.LogLevel != "" {
501+
cmd.Env = append(cmd.Env, "_LIBCONTAINER_LOGLEVEL="+p.LogLevel)
502+
}
502503

503504
// NOTE: when running a container with no PID namespace and the parent process spawning the container is
504505
// PID1 the pdeathsig is being delivered to the container's init process by the kernel for some reason

libcontainer/init_linux.go

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"io"
99
"net"
1010
"os"
11+
"runtime"
1112
"runtime/debug"
1213
"strconv"
1314
"strings"
@@ -84,34 +85,74 @@ type initConfig struct {
8485
Cgroup2Path string `json:"cgroup2_path,omitempty"`
8586
}
8687

87-
// StartInitialization loads a container by opening the pipe fd from the parent
88-
// to read the configuration and state. This is a low level implementation
89-
// detail of the reexec and should not be consumed externally.
90-
func StartInitialization() (retErr error) {
88+
// Init is part of "runc init" implementation.
89+
func Init() {
90+
runtime.GOMAXPROCS(1)
91+
runtime.LockOSThread()
92+
93+
if err := startInitialization(); err != nil {
94+
// If the error is returned, it was not communicated
95+
// back to the parent (which is not a common case),
96+
// so print it to stderr here as a last resort.
97+
//
98+
// Do not use logrus as we are not sure if it has been
99+
// set up yet, but most important, if the parent is
100+
// alive (and its log forwarding is working).
101+
fmt.Fprintln(os.Stderr, err)
102+
}
103+
// Normally, StartInitialization() never returns, meaning
104+
// if we are here, it had failed.
105+
os.Exit(1)
106+
}
107+
108+
// Normally, this function does not return. If it returns, with or without an
109+
// error, it means the initialization has failed. If the error is returned,
110+
// it means the error can not be communicated back to the parent.
111+
func startInitialization() (retErr error) {
91112
// Get the INITPIPE.
92113
envInitPipe := os.Getenv("_LIBCONTAINER_INITPIPE")
93114
pipefd, err := strconv.Atoi(envInitPipe)
94115
if err != nil {
95-
err = fmt.Errorf("unable to convert _LIBCONTAINER_INITPIPE: %w", err)
96-
logrus.Error(err)
97-
return err
116+
return fmt.Errorf("unable to convert _LIBCONTAINER_INITPIPE: %w", err)
98117
}
99118
pipe := os.NewFile(uintptr(pipefd), "pipe")
100119
defer pipe.Close()
101120

102121
defer func() {
103-
// We have an error during the initialization of the container's init,
104-
// send it back to the parent process in the form of an initError.
122+
// If this defer is ever called, this means initialization has failed.
123+
// Send the error back to the parent process in the form of an initError.
105124
if err := writeSync(pipe, procError); err != nil {
106-
fmt.Fprintln(os.Stderr, retErr)
125+
fmt.Fprintln(os.Stderr, err)
107126
return
108127
}
109128
if err := utils.WriteJSON(pipe, &initError{Message: retErr.Error()}); err != nil {
110-
fmt.Fprintln(os.Stderr, retErr)
129+
fmt.Fprintln(os.Stderr, err)
111130
return
112131
}
132+
// The error is sent, no need to also return it (or it will be reported twice).
133+
retErr = nil
113134
}()
114135

136+
// Set up logging. This is used rarely, and mostly for init debugging.
137+
138+
// Passing log level is optional; currently libcontainer/integration does not do it.
139+
if levelStr := os.Getenv("_LIBCONTAINER_LOGLEVEL"); levelStr != "" {
140+
logLevel, err := strconv.Atoi(levelStr)
141+
if err != nil {
142+
return fmt.Errorf("unable to convert _LIBCONTAINER_LOGLEVEL: %w", err)
143+
}
144+
logrus.SetLevel(logrus.Level(logLevel))
145+
}
146+
147+
logFD, err := strconv.Atoi(os.Getenv("_LIBCONTAINER_LOGPIPE"))
148+
if err != nil {
149+
return fmt.Errorf("unable to convert _LIBCONTAINER_LOGPIPE: %w", err)
150+
}
151+
152+
logrus.SetOutput(os.NewFile(uintptr(logFD), "logpipe"))
153+
logrus.SetFormatter(new(logrus.JSONFormatter))
154+
logrus.Debug("child process in init()")
155+
115156
// Only init processes have FIFOFD.
116157
fifofd := -1
117158
envInitType := os.Getenv("_LIBCONTAINER_INITTYPE")
@@ -133,12 +174,6 @@ func StartInitialization() (retErr error) {
133174
defer consoleSocket.Close()
134175
}
135176

136-
logPipeFdStr := os.Getenv("_LIBCONTAINER_LOGPIPE")
137-
logPipeFd, err := strconv.Atoi(logPipeFdStr)
138-
if err != nil {
139-
return fmt.Errorf("unable to convert _LIBCONTAINER_LOGPIPE: %w", err)
140-
}
141-
142177
// Get mount files (O_PATH).
143178
mountSrcFds, err := parseFdsFromEnv("_LIBCONTAINER_MOUNT_FDS")
144179
if err != nil {
@@ -166,7 +201,7 @@ func StartInitialization() (retErr error) {
166201
}()
167202

168203
// If init succeeds, it will not return, hence none of the defers will be called.
169-
return containerInit(it, pipe, consoleSocket, fifofd, logPipeFd, mountFds{sourceFds: mountSrcFds, idmapFds: idmapFds})
204+
return containerInit(it, pipe, consoleSocket, fifofd, logFD, mountFds{sourceFds: mountSrcFds, idmapFds: idmapFds})
170205
}
171206

172207
func containerInit(t initType, pipe *os.File, consoleSocket *os.File, fifoFd, logFd int, mountFds mountFds) error {

libcontainer/integration/init_test.go

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package integration
22

33
import (
4-
"fmt"
54
"os"
6-
"runtime"
75
"testing"
86

97
"github.com/opencontainers/runc/libcontainer"
@@ -14,20 +12,9 @@ import (
1412

1513
// Same as ../../init.go but for libcontainer/integration.
1614
func init() {
17-
if len(os.Args) < 2 || os.Args[1] != "init" {
18-
return
15+
if len(os.Args) > 1 && os.Args[1] == "init" {
16+
libcontainer.Init()
1917
}
20-
// This is the golang entry point for runc init, executed
21-
// before TestMain() but after libcontainer/nsenter's nsexec().
22-
runtime.GOMAXPROCS(1)
23-
runtime.LockOSThread()
24-
if err := libcontainer.StartInitialization(); err != nil {
25-
// logrus is not initialized
26-
fmt.Fprintln(os.Stderr, err)
27-
}
28-
// Normally, StartInitialization() never returns, meaning
29-
// if we are here, it had failed.
30-
os.Exit(1)
3118
}
3219

3320
func TestMain(m *testing.M) {

0 commit comments

Comments
 (0)