1

I'm trying to understand how runc works and stuck here:

When the container is about to be started (using "runc run ") and in the init phase, can anybody help me understand where does the code logic go after command /proc/self/exe init gets executed?

Does it result in the command "runc init"? I tried to execute runc init from the shell and it failed due to missing _LIBCONTAINER_LOGLEVEL variable. (Error: panic: strconv.Atoi: parsing "": invalid syntax)

Is there a function called init() somewhere that gets invoked (which later invokes the "sh" command in a container)?

I have been cracking my head behind this and unable to trace the steps. Any ideas/help appreciated!!

From Libcontainer Docs:

Because containers are spawned in a two step process you will need a binary that will be executed as the init process for the container. In libcontainer, we use the current binary (/proc/self/exe) to be executed as the init process, and use arg "init", we call the first step process "bootstrap", so you always need a "init" function as the entry of "bootstrap".

Tried to check all the init functions within the codebase but could not find the one where the execution will proceed to?

1 Answers1

0

Without that piece of information I'm assuming the @opencontainers/runc code base on Github for your question and my answer.

As you already found out, runc re-executes its own binary with the init CLI argument. Now, looking at the aforementioned runc code base, there's a Go file init.go in the module's root directory that immediately appeals to the untrained eye (talk of hiding in plain sight!). It seems to be aptly named as it consists of only a single init function; it probably suffices to just show the first few lines to answer your quest for code:

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()

        // ...
    }
}

One thing I want you to become aware of, as this is something easily overlooked and I only happen to know its significance because of my work on a – pardon blowing my own horn here – Linux namespace discovery engine/service, is the following suspiciously unobtrusive unnamed import:

import (
    _ "github.com/opencontainers/runc/libcontainer/nsenter"
)

The nsenter package "peppers" the runc binary with a before-Go initializer that must be run before the Go runtime spins up and usually goes multi-threaded. nsexec does some heavy dance in order to correctly switch especially certain namespaces, such as user and mount namespaces. There are really good comments in the source that give important background information on why things are sometimes really complicated.

Please note that nsexec runs before the Go runtime, and thus before the aforementioned init() function (or any other init() function).

May the runc source be with you!

TheDiveO
  • 2,183
  • 2
  • 19
  • 38