From 75578aad61c1e9fae021223ece70cb83e3e2bcf2 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Sat, 22 Dec 2018 14:59:43 +0100 Subject: add container-init support Add support for executing an init binary as PID 1 in a container to forward signals and reap processes. When the `--init` flag is set for podman-create or podman-run, the init binary is bind-mounted to `/dev/init` in the container and "/dev/init --" is prepended to the container's command. The default base path of the container-init binary is `/usr/libexec/podman` while the default binary is catatonit [1]. This default can be changed permanently via the `init_path` field in the `libpod.conf` configuration file (which is recommended for packaging) or temporarily via the `--init-path` flag of podman-create and podman-run. [1] https://github.com/openSUSE/catatonit Fixes: #1670 Signed-off-by: Valentin Rothberg --- API.md | 4 ++++ Makefile | 9 ++++++--- cmd/podman/common.go | 9 +++++++++ cmd/podman/create.go | 10 ++++++++++ cmd/podman/varlink/io.podman.varlink | 2 ++ docs/libpod.conf.5.md | 3 +++ docs/podman-create.1.md | 8 ++++++++ docs/podman-run.1.md | 8 ++++++++ hack/install_catatonit.sh | 15 +++++++++++++++ libpod.conf | 3 +++ libpod/runtime.go | 6 ++++++ pkg/spec/createconfig.go | 31 +++++++++++++++++++++++++++++++ test/e2e/run_test.go | 12 ++++++++++++ 13 files changed, 117 insertions(+), 3 deletions(-) create mode 100755 hack/install_catatonit.sh diff --git a/API.md b/API.md index 51787496c..3722c2864 100755 --- a/API.md +++ b/API.md @@ -1169,6 +1169,10 @@ image [string](https://godoc.org/builtin#string) image_id [string](https://godoc.org/builtin#string) +init [bool](https://godoc.org/builtin#bool) + +init_path [string](https://godoc.org/builtin#string) + builtin_imgvolumes [[]string](#[]string) id_mappings [IDMappingOptions](#IDMappingOptions) diff --git a/Makefile b/Makefile index c423d82f2..32f21227f 100644 --- a/Makefile +++ b/Makefile @@ -154,10 +154,10 @@ dbuild: libpodimage ${CONTAINER_RUNTIME} run --name=${LIBPOD_INSTANCE} --privileged -v ${PWD}:/go/src/${PROJECT} --rm ${LIBPOD_IMAGE} make all test: libpodimage - ${CONTAINER_RUNTIME} run -e STORAGE_OPTIONS="--storage-driver=vfs" -e TESTFLAGS -e CGROUP_MANAGER=cgroupfs -e TRAVIS -t --privileged --rm -v ${CURDIR}:/go/src/${PROJECT} ${LIBPOD_IMAGE} make clean all localunit localintegration + ${CONTAINER_RUNTIME} run -e STORAGE_OPTIONS="--storage-driver=vfs" -e TESTFLAGS -e CGROUP_MANAGER=cgroupfs -e TRAVIS -t --privileged --rm -v ${CURDIR}:/go/src/${PROJECT} ${LIBPOD_IMAGE} make clean all localunit install.catatonit localintegration integration: libpodimage - ${CONTAINER_RUNTIME} run -e STORAGE_OPTIONS="--storage-driver=vfs" -e TESTFLAGS -e CGROUP_MANAGER=cgroupfs -e TRAVIS -t --privileged --rm -v ${CURDIR}:/go/src/${PROJECT} ${LIBPOD_IMAGE} make clean all localintegration + ${CONTAINER_RUNTIME} run -e STORAGE_OPTIONS="--storage-driver=vfs" -e TESTFLAGS -e CGROUP_MANAGER=cgroupfs -e TRAVIS -t --privileged --rm -v ${CURDIR}:/go/src/${PROJECT} ${LIBPOD_IMAGE} make clean all install.catatonit localintegration integration.fedora: DIST=Fedora sh .papr_prepare.sh @@ -201,7 +201,10 @@ vagrant-check: binaries: varlink_generate easyjson_generate podman -test-binaries: test/bin2img/bin2img test/copyimg/copyimg test/checkseccomp/checkseccomp test/goecho/goecho +install.catatonit: + ./hack/install_catatonit.sh + +test-binaries: test/bin2img/bin2img test/copyimg/copyimg test/checkseccomp/checkseccomp test/goecho/goecho install.catatonit MANPAGES_MD ?= $(wildcard docs/*.md pkg/*/docs/*.md) MANPAGES ?= $(MANPAGES_MD:%.md=%) diff --git a/cmd/podman/common.go b/cmd/podman/common.go index 8404a29b8..0fc9a6acc 100644 --- a/cmd/podman/common.go +++ b/cmd/podman/common.go @@ -320,6 +320,15 @@ var createFlags = []cli.Flag{ Usage: "Tells podman how to handle the builtin image volumes. The options are: 'bind', 'tmpfs', or 'ignore' (default 'bind')", Value: "bind", }, + cli.BoolFlag{ + Name: "init", + Usage: "Run an init binary inside the container that forwards signals and reaps processes", + }, + cli.StringFlag{ + Name: "init-path", + // Do not use the Value field for setting the default value to determine user input (i.e., non-empty string) + Usage: fmt.Sprintf("Path to the container-init binary (default: %q)", libpod.DefaultInitPath), + }, cli.BoolFlag{ Name: "interactive, i", Usage: "Keep STDIN open even if not attached", diff --git a/cmd/podman/create.go b/cmd/podman/create.go index dae429047..395a64b3b 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -809,6 +809,16 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim Syslog: c.GlobalBool("syslog"), } + if c.Bool("init") { + initPath := c.String("init-path") + if initPath == "" { + initPath = runtime.GetConfig().InitPath + } + if err := config.AddContainerInitBinary(initPath); err != nil { + return nil, err + } + } + if config.Privileged { config.LabelOpts = label.DisableSecOpt() } else { diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index c1b7c703a..4e8b69faf 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -211,6 +211,8 @@ type Create ( hostname: string, image: string, image_id: string, + init: bool, + init_path: string, builtin_imgvolumes: []string, id_mappings: IDMappingOptions, image_volume_type: string, diff --git a/docs/libpod.conf.5.md b/docs/libpod.conf.5.md index d63baeb88..c02d247fb 100644 --- a/docs/libpod.conf.5.md +++ b/docs/libpod.conf.5.md @@ -24,6 +24,9 @@ libpod to manage containers. **cgroup_manager**="" Specify the CGroup Manager to use; valid values are "systemd" and "cgroupfs" +**init_path**="" + Path to the container-init binary, which forwards signals and reaps processes within containers. Note that the container-init binary will only be used when the `--init` for podman-create and podman-run is set. + **hooks_dir**=["*path*", ...] Each `*.json` file in the path configures a hook for Podman containers. For more details on the syntax of the JSON files and the semantics of hook injection, see `oci-hooks(5)`. Podman and libpod currently support both the 1.0.0 and 0.1.0 hook schemas, although the 0.1.0 schema is deprecated. diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md index 97d6e77b1..3a75a4b00 100644 --- a/docs/podman-create.1.md +++ b/docs/podman-create.1.md @@ -276,6 +276,14 @@ tmpfs: The volume is mounted onto the container as a tmpfs, which allows the use content that disappears when the container is stopped. ignore: All volumes are just ignored and no action is taken. +**--init** + +Run an init inside the container that forwards signals and reaps processes. + +**--init-path**="" + +Path to the container-init binary. + **-i**, **--interactive**=*true*|*false* Keep STDIN open even if not attached. The default is *false*. diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md index c0a466a9c..971b8829a 100644 --- a/docs/podman-run.1.md +++ b/docs/podman-run.1.md @@ -285,6 +285,14 @@ the container for the volumes. content that disappears when the container is stopped. - `ignore`: All volumes are just ignored and no action is taken. +**--init** + +Run an init inside the container that forwards signals and reaps processes. + +**--init-path**="" + +Path to the container-init binary. + **-i**, **--interactive**=*true*|*false* Keep STDIN open even if not attached. The default is *false*. diff --git a/hack/install_catatonit.sh b/hack/install_catatonit.sh new file mode 100755 index 000000000..e5532a200 --- /dev/null +++ b/hack/install_catatonit.sh @@ -0,0 +1,15 @@ +#!/bin/bash -e +BASE_PATH="/usr/libexec/podman" +CATATONIT_PATH="${BASE_PATH}/catatonit" +CATATONIT_VERSION="v0.1.3" + +if [ -f $CATATONIT_PATH ]; then + echo "skipping ... catatonit is already installed" +else + echo "downloading catatonit to $CATATONIT_PATH" + curl -o catatonit -L https://github.com/openSUSE/catatonit/releases/download/$CATATONIT_VERSION/catatonit.x86_64 + chmod +x catatonit + install ${SELINUXOPT} -d -m 755 $BASE_PATH + install ${SELINUXOPT} -m 755 catatonit $CATATONIT_PATH + rm catatonit +fi diff --git a/libpod.conf b/libpod.conf index d7469af68..cfdf83775 100644 --- a/libpod.conf +++ b/libpod.conf @@ -35,6 +35,9 @@ conmon_env_vars = [ # CGroup Manager - valid values are "systemd" and "cgroupfs" cgroup_manager = "systemd" +# Container init binary +#init_path = "/usr/libexec/podman/catatonit" + # Directory for persistent libpod files (database, etc) # By default, this will be configured relative to where containers/storage # stores containers diff --git a/libpod/runtime.go b/libpod/runtime.go index 2dfebf565..facbe5d66 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -61,6 +61,9 @@ const ( DefaultInfraImage = "k8s.gcr.io/pause:3.1" // DefaultInfraCommand to be run in an infra container DefaultInfraCommand = "/pause" + + // DefaultInitPath is the default path to the container-init binary + DefaultInitPath = "/usr/libexec/podman/catatonit" ) // A RuntimeOption is a functional option which alters the Runtime created by @@ -122,6 +125,8 @@ type RuntimeConfig struct { // CGroupManager is the CGroup Manager to use // Valid values are "cgroupfs" and "systemd" CgroupManager string `toml:"cgroup_manager"` + // InitPath is the path to the container-init binary. + InitPath string `toml:"init_path"` // StaticDir is the path to a persistent directory to store container // files StaticDir string `toml:"static_dir"` @@ -217,6 +222,7 @@ var ( ConmonEnvVars: []string{ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", }, + InitPath: DefaultInitPath, CgroupManager: SystemdCgroupsManager, StaticDir: filepath.Join(storage.DefaultStoreOptions.GraphRoot, "libpod"), TmpDir: "", diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 25f8cd7a1..ffc98e307 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -2,6 +2,7 @@ package createconfig import ( "encoding/json" + "fmt" "net" "os" "strconv" @@ -145,6 +146,36 @@ func (c *CreateConfig) CreateBlockIO() (*spec.LinuxBlockIO, error) { return c.createBlockIO() } +// AddContainerInitBinary adds the init binary specified by path iff the +// container will run in a private PID namespace that is not shared with the +// host or another pre-existing container, where an init-like process is +// already running. +// +// Note that AddContainerInitBinary prepends "/dev/init" "--" to the command +// to execute the bind-mounted binary as PID 1. +func (c *CreateConfig) AddContainerInitBinary(path string) error { + if path == "" { + return fmt.Errorf("please specify a path to the container-init binary") + } + if !c.PidMode.IsPrivate() { + return fmt.Errorf("cannot add init binary as PID 1 (PID namespace isn't private)") + } + if c.Systemd { + return fmt.Errorf("cannot use container-init binary with systemd") + } + if _, err := os.Stat(path); os.IsNotExist(err) { + return errors.Wrap(err, "container-init binary not found on the host") + } + c.Command = append([]string{"/dev/init", "--"}, c.Command...) + c.Mounts = append(c.Mounts, spec.Mount{ + Destination: "/dev/init", + Type: "bind", + Source: path, + Options: []string{"bind", "ro"}, + }) + return nil +} + func processOptions(options []string) []string { var ( foundrw, foundro bool diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index cf366b197..a0b16a254 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -87,6 +87,18 @@ var _ = Describe("Podman run", func() { Expect(session.ExitCode()).To(Equal(0)) }) + It("podman run a container with --init", func() { + session := podmanTest.Podman([]string{"run", "--init", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + + It("podman run a container with --init and --init-path", func() { + session := podmanTest.Podman([]string{"run", "--init", "--init-path", "/usr/libexec/podman/catatonit", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman run seccomp test", func() { jsonFile := filepath.Join(podmanTest.TempDir, "seccomp.json") in := []byte(`{"defaultAction":"SCMP_ACT_ALLOW","syscalls":[{"name":"getcwd","action":"SCMP_ACT_ERRNO"}]}`) -- cgit v1.2.3-54-g00ecf