Use CNI to configure container networks

Use CNI to configure networks for containers for which we create new
network namespaces.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>

Closes: #700
Approved by: rhatdan
This commit is contained in:
Nalin Dahyabhai 2018-04-13 18:20:25 -04:00 committed by Atomic Bot
parent aa5cf3115e
commit 00fafcf9cb
18 changed files with 447 additions and 66 deletions

View File

@ -8,11 +8,12 @@ GO := go
GIT_COMMIT := $(if $(shell git rev-parse --short HEAD),$(shell git rev-parse --short HEAD),$(error "git failed"))
BUILD_INFO := $(if $(shell date +%s),$(shell date +%s),$(error "date failed"))
CNI_COMMIT := $(if $(shell sed -e '\,github.com/containernetworking/cni, !d' -e 's,.* ,,g' vendor.conf),$(shell sed -e '\,github.com/containernetworking/cni, !d' -e 's,.* ,,g' vendor.conf),$(error "sed failed"))
RUNC_COMMIT := c5ec25487693612aed95673800863e134785f946
LIBSECCOMP_COMMIT := release-2.3
LDFLAGS := -ldflags '-X main.gitCommit=${GIT_COMMIT} -X main.buildInfo=${BUILD_INFO}'
LDFLAGS := -ldflags '-X main.gitCommit=${GIT_COMMIT} -X main.buildInfo=${BUILD_INFO} -X main.cniVersion=${CNI_COMMIT}'
all: buildah imgtype docs

View File

@ -67,6 +67,37 @@ func (p PullPolicy) String() string {
return fmt.Sprintf("unrecognized policy %d", p)
}
// NetworkConfigurationPolicy takes the value NetworkDefault, NetworkDisabled,
// or NetworkEnabled.
type NetworkConfigurationPolicy int
const (
// NetworkDefault is one of the values that BuilderOptions.ConfigureNetwork
// can take, signalling that the default behavior should be used.
NetworkDefault NetworkConfigurationPolicy = iota
// NetworkDisabled is one of the values that BuilderOptions.ConfigureNetwork
// can take, signalling that network interfaces should NOT be configured for
// newly-created network namespaces.
NetworkDisabled
// NetworkEnabled is one of the values that BuilderOptions.ConfigureNetwork
// can take, signalling that network interfaces should be configured for
// newly-created network namespaces.
NetworkEnabled
)
// String formats a NetworkConfigurationPolicy as a string.
func (p NetworkConfigurationPolicy) String() string {
switch p {
case NetworkDefault:
return "NetworkDefault"
case NetworkDisabled:
return "NetworkDisabled"
case NetworkEnabled:
return "NetworkEnabled"
}
return fmt.Sprintf("unknown NetworkConfigurationPolicy %d", p)
}
// Builder objects are used to represent containers which are being used to
// build images. They also carry potential updates which will be applied to
// the image's configuration when the container's contents are used to build an
@ -118,6 +149,18 @@ type Builder struct {
// NamespaceOptions controls how we set up the namespaces for processes that we run in the container.
NamespaceOptions NamespaceOptions
// ConfigureNetwork controls whether or not network interfaces and
// routing are configured for a new network namespace (i.e., when not
// joining another's namespace and not just using the host's
// namespace), effectively deciding whether or not the process has a
// usable network.
ConfigureNetwork NetworkConfigurationPolicy
// CNIPluginPath is the location of CNI plugin helpers, if they should be
// run from a location other than the default location.
CNIPluginPath string
// CNIConfigDir is the location of CNI configuration files, if the files in
// the default configuration directory shouldn't be used.
CNIConfigDir string
// ID mapping options to use when running processes in the container with non-host user namespaces.
IDMappingOptions IDMappingOptions
@ -142,6 +185,9 @@ type BuilderInfo struct {
Docker docker.V2Image
DefaultMountsFilePath string
NamespaceOptions NamespaceOptions
ConfigureNetwork string
CNIPluginPath string
CNIConfigDir string
IDMappingOptions IDMappingOptions
}
@ -164,6 +210,9 @@ func GetBuildInfo(b *Builder) BuilderInfo {
Docker: b.Docker,
DefaultMountsFilePath: b.DefaultMountsFilePath,
NamespaceOptions: b.NamespaceOptions,
ConfigureNetwork: fmt.Sprintf("%v", b.ConfigureNetwork),
CNIPluginPath: b.CNIPluginPath,
CNIConfigDir: b.CNIConfigDir,
IDMappingOptions: b.IDMappingOptions,
}
}
@ -262,6 +311,18 @@ type BuilderOptions struct {
// NamespaceOptions controls how we set up namespaces for processes that
// we might need to run using the container's root filesystem.
NamespaceOptions NamespaceOptions
// ConfigureNetwork controls whether or not network interfaces and
// routing are configured for a new network namespace (i.e., when not
// joining another's namespace and not just using the host's
// namespace), effectively deciding whether or not the process has a
// usable network.
ConfigureNetwork NetworkConfigurationPolicy
// CNIPluginPath is the location of CNI plugin helpers, if they should be
// run from a location other than the default location.
CNIPluginPath string
// CNIConfigDir is the location of CNI configuration files, if the files in
// the default configuration directory shouldn't be used.
CNIConfigDir string
// ID mapping options to use if we're setting up our own user namespace.
IDMappingOptions *IDMappingOptions

View File

@ -167,7 +167,7 @@ func budCmd(c *cli.Context) error {
logrus.Debugf("build caching not enabled so --rm flag has no effect")
}
namespaceOptions, err := parseNamespaceOptions(c)
namespaceOptions, networkPolicy, err := parseNamespaceOptions(c)
if err != nil {
return errors.Wrapf(err, "error parsing namespace-related options")
}
@ -191,6 +191,9 @@ func budCmd(c *cli.Context) error {
OutputFormat: format,
SystemContext: systemContext,
NamespaceOptions: namespaceOptions,
ConfigureNetwork: networkPolicy,
CNIPluginPath: c.String("cni-plugin-path"),
CNIConfigDir: c.String("cni-config-dir"),
IDMappingOptions: idmappingOptions,
CommonBuildOpts: commonOpts,
DefaultMountsFilePath: c.GlobalString("default-mounts-file"),

View File

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"time"
@ -137,8 +138,9 @@ func parseUserOptions(c *cli.Context) string {
return c.String("user")
}
func parseNamespaceOptions(c *cli.Context) (namespaceOptions buildah.NamespaceOptions, err error) {
func parseNamespaceOptions(c *cli.Context) (namespaceOptions buildah.NamespaceOptions, networkPolicy buildah.NetworkConfigurationPolicy, err error) {
options := make(buildah.NamespaceOptions, 0, 7)
policy := buildah.NetworkDefault
for _, what := range []string{string(specs.IPCNamespace), "net", string(specs.PIDNamespace), string(specs.UTSNamespace)} {
if c.IsSet(what) {
how := c.String(what)
@ -159,8 +161,27 @@ func parseNamespaceOptions(c *cli.Context) (namespaceOptions buildah.NamespaceOp
Host: true,
})
default:
if what == specs.NetworkNamespace {
if how == "none" {
options.AddOrReplace(buildah.NamespaceOption{
Name: what,
})
policy = buildah.NetworkDisabled
logrus.Debugf("setting network to disabled")
break
}
if !filepath.IsAbs(how) {
options.AddOrReplace(buildah.NamespaceOption{
Name: what,
Path: how,
})
policy = buildah.NetworkEnabled
logrus.Debugf("setting network configuration to %q", how)
break
}
}
if _, err := os.Stat(how); err != nil {
return nil, errors.Wrapf(err, "error checking for %s namespace at %q", what, how)
return nil, buildah.NetworkDefault, errors.Wrapf(err, "error checking for %s namespace at %q", what, how)
}
logrus.Debugf("setting %q namespace to %q", what, how)
options.AddOrReplace(buildah.NamespaceOption{
@ -170,7 +191,7 @@ func parseNamespaceOptions(c *cli.Context) (namespaceOptions buildah.NamespaceOp
}
}
}
return options, nil
return options, policy, nil
}
func parseIDMappingOptions(c *cli.Context) (usernsOptions buildah.NamespaceOptions, idmapOptions *buildah.IDMappingOptions, err error) {

View File

@ -112,7 +112,7 @@ func fromCmd(c *cli.Context) error {
}
}
namespaceOptions, err := parseNamespaceOptions(c)
namespaceOptions, networkPolicy, err := parseNamespaceOptions(c)
if err != nil {
return errors.Wrapf(err, "error parsing namespace-related options")
}
@ -131,6 +131,9 @@ func fromCmd(c *cli.Context) error {
SystemContext: systemContext,
DefaultMountsFilePath: c.GlobalString("default-mounts-file"),
NamespaceOptions: namespaceOptions,
ConfigureNetwork: networkPolicy,
CNIPluginPath: c.String("cni-plugin-path"),
CNIConfigDir: c.String("cni-config-dir"),
IDMappingOptions: idmappingOptions,
CommonBuildOpts: commonOpts,
}

View File

@ -90,7 +90,7 @@ func runCmd(c *cli.Context) error {
}
user := parseUserOptions(c)
namespaceOptions, err := parseNamespaceOptions(c)
namespaceOptions, networkPolicy, err := parseNamespaceOptions(c)
if err != nil {
return errors.Wrapf(err, "error parsing namespace-related options")
}
@ -101,6 +101,9 @@ func runCmd(c *cli.Context) error {
Args: runtimeFlags,
User: user,
NamespaceOptions: namespaceOptions,
ConfigureNetwork: networkPolicy,
CNIPluginPath: c.String("cni-plugin-path"),
CNIConfigDir: c.String("cni-config-dir"),
}
if c.IsSet("tty") {

View File

@ -6,6 +6,7 @@ import (
"strconv"
"time"
cniversion "github.com/containernetworking/cni/pkg/version"
ispecs "github.com/opencontainers/image-spec/specs-go"
rspecs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/projectatomic/buildah"
@ -14,8 +15,9 @@ import (
//Overwritten at build time
var (
gitCommit string
buildInfo string
gitCommit string
buildInfo string
cniVersion string
)
//Function to get and print info for version command
@ -27,15 +29,17 @@ func versionCmd(c *cli.Context) error {
return err
}
fmt.Println("Version: ", buildah.Version)
fmt.Println("Go Version: ", runtime.Version())
fmt.Println("Image Spec: ", ispecs.Version)
fmt.Println("Runtime Spec: ", rspecs.Version)
fmt.Println("Git Commit: ", gitCommit)
fmt.Println("Version: ", buildah.Version)
fmt.Println("Go Version: ", runtime.Version())
fmt.Println("Image Spec: ", ispecs.Version)
fmt.Println("Runtime Spec: ", rspecs.Version)
fmt.Println("CNI Spec: ", cniversion.Current())
fmt.Println("libcni Version: ", cniVersion)
fmt.Println("Git Commit: ", gitCommit)
//Prints out the build time in readable format
fmt.Println("Built: ", time.Unix(buildTime, 0).Format(time.ANSIC))
fmt.Println("OS/Arch: ", runtime.GOOS+"/"+runtime.GOARCH)
fmt.Println("Built: ", time.Unix(buildTime, 0).Format(time.ANSIC))
fmt.Println("OS/Arch: ", runtime.GOOS+"/"+runtime.GOARCH)
return nil
}

View File

@ -371,6 +371,8 @@ return 1
--build-arg
--cert-dir
--cgroup-parent
--cni-config-dir
--cni-plugin-path
--cpu-period
--cpu-quota
--cpu-shares
@ -439,6 +441,8 @@ return 1
"
local options_with_args="
--cni-config-dir
--cni-plugin-path
--hostname
--ipc
--net
@ -704,6 +708,8 @@ return 1
--authfile
--cert-dir
--cgroup-parent
--cni-config-dir
--cni-plugin-path
--cpu-period
--cpu-quota
--cpu-shares

View File

@ -60,9 +60,21 @@ Path to cgroups under which the cgroup for the container will be created. If the
**--compress**
This option is added to be aligned with other containers CLIs.
Buildah doesn't communicate with a daemon or a remote server.
Buildah doesn't send a copy of the context directory to a daemon or a remote server.
Thus, compressing the data before sending it is irrelevant to Buildah.
**--cni-config-dir**=*directory*
Location of CNI configuration files which will dictate which plugins will be
used to configure network interfaces and routing for containers created for
handling `RUN` instructions, if those containers will be run in their own
network namespaces, and networking is not disabled.
**--cni-plugin-path**=*directory[:directory[:directory[...]]]*
List of directories in which the CNI plugins which will be used for configuring
network namespaces can be found.
**--cpu-period**=*0*
Limit the CPU CFS (Completely Fair Scheduler) period

View File

@ -57,6 +57,18 @@ The default certificates directory is _/etc/containers/certs.d_.
Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist.
**--cni-config-dir**=*directory*
Location of CNI configuration files which will dictate which plugins will be
used to configure network interfaces and routing when the container is
subsequently used for `buildah run`, if processes to be started will be run in
their own network namespaces, and networking is not disabled.
**--cni-plugin-path**=*directory[:directory[:directory[...]]]*
List of directories in which the CNI plugins which will be used for configuring
network namespaces can be found.
**--cpu-period**=*0*
Limit the CPU CFS (Completely Fair Scheduler) period

View File

@ -14,6 +14,18 @@ the *buildah config* command. To execute *buildah run* within an
interactive shell, specify the --tty option.
## OPTIONS
**--cni-config-dir**=*directory*
Location of CNI configuration files which will dictate which plugins will be
used to configure network interfaces and routing inside the running container,
if the container will be run in its own network namespace, and networking is
not disabled.
**--cni-plugin-path**=*directory[:directory[:directory[...]]]*
List of directories in which the CNI plugins which will be used for configuring
network namespaces can be found.
**--hostname**
Set the hostname inside of the running container.

View File

@ -111,6 +111,18 @@ type BuildOptions struct {
// NamespaceOptions controls how we set up namespaces processes that we
// might need when handling RUN instructions.
NamespaceOptions []buildah.NamespaceOption
// ConfigureNetwork controls whether or not network interfaces and
// routing are configured for a new network namespace (i.e., when not
// joining another's namespace and not just using the host's
// namespace), effectively deciding whether or not the process has a
// usable network.
ConfigureNetwork buildah.NetworkConfigurationPolicy
// CNIPluginPath is the location of CNI plugin helpers, if they should be
// run from a location other than the default location.
CNIPluginPath string
// CNIConfigDir is the location of CNI configuration files, if the files in
// the default configuration directory shouldn't be used.
CNIConfigDir string
// ID mapping options to use if we're setting up our own user namespace
// when handling RUN instructions.
IDMappingOptions *buildah.IDMappingOptions
@ -161,6 +173,9 @@ type Executor struct {
volumeCacheInfo map[string]os.FileInfo
reportWriter io.Writer
namespaceOptions []buildah.NamespaceOption
configureNetwork buildah.NetworkConfigurationPolicy
cniPluginPath string
cniConfigDir string
idmappingOptions *buildah.IDMappingOptions
commonBuildOptions *buildah.CommonBuildOptions
defaultMountsFilePath string
@ -421,17 +436,21 @@ func (b *Executor) Run(run imagebuilder.Run, config docker.Config) error {
return errors.Errorf("no build container available")
}
options := buildah.RunOptions{
Hostname: config.Hostname,
Runtime: b.runtime,
Args: b.runtimeArgs,
Mounts: convertMounts(b.transientMounts),
Env: config.Env,
User: config.User,
WorkingDir: config.WorkingDir,
Entrypoint: config.Entrypoint,
Cmd: config.Cmd,
NetworkDisabled: config.NetworkDisabled,
Quiet: b.quiet,
Hostname: config.Hostname,
Runtime: b.runtime,
Args: b.runtimeArgs,
Mounts: convertMounts(b.transientMounts),
Env: config.Env,
User: config.User,
WorkingDir: config.WorkingDir,
Entrypoint: config.Entrypoint,
Cmd: config.Cmd,
Quiet: b.quiet,
}
if config.NetworkDisabled {
options.ConfigureNetwork = buildah.NetworkDisabled
} else {
options.ConfigureNetwork = buildah.NetworkEnabled
}
args := run.Args
@ -498,6 +517,9 @@ func NewExecutor(store storage.Store, options BuildOptions) (*Executor, error) {
err: options.Err,
reportWriter: options.ReportWriter,
namespaceOptions: options.NamespaceOptions,
configureNetwork: options.ConfigureNetwork,
cniPluginPath: options.CNIPluginPath,
cniConfigDir: options.CNIConfigDir,
idmappingOptions: options.IDMappingOptions,
commonBuildOptions: options.CommonBuildOpts,
defaultMountsFilePath: options.DefaultMountsFilePath,
@ -548,6 +570,9 @@ func (b *Executor) Prepare(ctx context.Context, ib *imagebuilder.Builder, node *
ReportWriter: b.reportWriter,
SystemContext: b.systemContext,
NamespaceOptions: b.namespaceOptions,
ConfigureNetwork: b.configureNetwork,
CNIPluginPath: b.cniPluginPath,
CNIConfigDir: b.cniConfigDir,
IDMappingOptions: b.idmappingOptions,
CommonBuildOpts: b.commonBuildOptions,
DefaultMountsFilePath: b.defaultMountsFilePath,

View File

@ -17,6 +17,38 @@ encounters a `RUN` instruction, so you'll also need to build and install a compa
[runc](https://github.com/opencontainers/runc) for Buildah to call for those cases. If Buildah is installed
via a package manager such as yum, dnf or apt-get, runc will be installed as part of that process.
### CNI Requirement
When Buildah uses `runc` to run commands, it defaults to running those commands
in the host's network namespace. If the command is being run in a separate
user namespace, though, for example when ID mapping is used, then the command
will also be run in a separate network namespace.
A newly-created network namespace starts with no network interfaces, so
commands which are run in that namespace are effectively disconnected from the
network unless additional setup is done. Buildah relies on the CNI
[library](https://github.com/containernetworking/cni) and
[plugins](https://github.com/containernetworking/plugins) to set up interfaces
and routing for network namespaces.
If Buildah is installed via a package manager such as yum, dnf or apt-get, a
package containing CNI plugins may be available (in Fedora, the package is
named `containernetworking-cni`). If not, they will need to be installed,
for example using:
```
git clone https://github.com/containernetworking/plugins
( cd ./plugins; ./build.sh )
mkdir -p /opt/cni/bin
install -v ./plugins/bin/* /opt/cni/bin
```
The CNI library needs to be configured so that it will know which plugins to
call to set up namespaces. Usually, this configuration takes the form of one
or more configuration files in the `/etc/cni/net.d` directory. A set of example
configuration files is included in the
[`docs/cni-examples`](https://github.com/projectatomic/buildah/tree/master/docs/cni-examples)
directory of this source tree.
## Package Installation
Buildah is available on several software repositories and can be installed via a package manager such
@ -66,7 +98,6 @@ In Fedora, you can use this command:
Then to install Buildah on Fedora follow the steps in this example:
```
mkdir ~/buildah
cd ~/buildah
@ -80,8 +111,8 @@ Then to install Buildah on Fedora follow the steps in this example:
### RHEL, CentOS
In RHEL and CentOS 7, ensure that you are subscribed to `rhel-7-server-rpms`,
`rhel-7-server-extras-rpms`, and `rhel-7-server-optional-rpms`, then
In RHEL and CentOS 7, ensure that you are subscribed to the `rhel-7-server-rpms`,
`rhel-7-server-extras-rpms`, and `rhel-7-server-optional-rpms` repositories, then
run this command:
```
@ -103,7 +134,7 @@ run this command:
skopeo-containers
```
The build steps for Buildah on RHEL or CentOS are the same as Fedora, above.
The build steps for Buildah on RHEL or CentOS are the same as for Fedora, above.
### openSUSE
@ -124,7 +155,7 @@ Currently openSUSE Leap 15 offers `go1.8` , while openSUSE Tumbleweed has `go1.9
go-md2man
```
The build steps for Buildah on SUSE / openSUSE are the same as Fedora, above.
The build steps for Buildah on SUSE / openSUSE are the same as for Fedora, above.
### Ubuntu

3
new.go
View File

@ -315,6 +315,9 @@ func newBuilder(ctx context.Context, store storage.Store, options BuilderOptions
MountLabel: mountLabel,
DefaultMountsFilePath: options.DefaultMountsFilePath,
NamespaceOptions: namespaceOptions,
ConfigureNetwork: options.ConfigureNetwork,
CNIPluginPath: options.CNIPluginPath,
CNIConfigDir: options.CNIConfigDir,
IDMappingOptions: IDMappingOptions{
HostUIDMapping: len(uidmap) == 0,
HostGIDMapping: len(uidmap) == 0,

View File

@ -7,6 +7,7 @@ package cli
import (
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/projectatomic/buildah"
"github.com/projectatomic/buildah/util"
"github.com/urfave/cli"
)
@ -43,6 +44,16 @@ var (
Name: string(specs.NetworkNamespace) + ", net",
Usage: "'container', `path` of network namespace to join, or 'host'",
},
cli.StringFlag{
Name: "cni-config-dir",
Usage: "`directory` of CNI configuration files",
Value: util.DefaultCNIConfigDir,
},
cli.StringFlag{
Name: "cni-plugin-path",
Usage: "`path` of CNI network plugins",
Value: util.DefaultCNIPluginPath,
},
cli.StringFlag{
Name: string(specs.PIDNamespace),
Usage: "'container', `path` of PID namespace to join, or 'host'",

220
run.go
View File

@ -17,6 +17,7 @@ import (
"syscall"
"time"
"github.com/containernetworking/cni/libcni"
"github.com/containers/storage/pkg/ioutils"
"github.com/containers/storage/pkg/reexec"
"github.com/docker/docker/profiles/seccomp"
@ -26,6 +27,7 @@ import (
"github.com/opencontainers/runtime-tools/generate"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
"github.com/projectatomic/buildah/util"
"github.com/projectatomic/libpod/pkg/secrets"
"github.com/sirupsen/logrus"
"golang.org/x/crypto/ssh/terminal"
@ -81,6 +83,10 @@ type NamespaceOption struct {
// Path is the path of the namespace to attach our process to, if Host
// is not set. If Host is not set and Path is also empty, a new
// namespace will be created for the process that we're starting.
// If Name is specs.NetworkNamespace, if Path doesn't look like an
// absolute path, it is treated as a comma-separated list of CNI
// configuration names which will be selected from among all of the CNI
// network configurations which we find.
Path string
}
@ -120,13 +126,20 @@ type RunOptions struct {
Cmd []string
// Entrypoint is an override for the configured entry point.
Entrypoint []string
// NetworkDisabled skips configuration of network interfaces and routing
// when setting up a new network namespace (i.e., when not joining another's
// namespace and not just using the host's namespace), effectively leaving
// the process without a usable network.
NetworkDisabled bool
// NamespaceOptions controls how we set up the namespaces for the process.
NamespaceOptions NamespaceOptions
// ConfigureNetwork controls whether or not network interfaces and
// routing are configured for a new network namespace (i.e., when not
// joining another's namespace and not just using the host's
// namespace), effectively deciding whether or not the process has a
// usable network.
ConfigureNetwork NetworkConfigurationPolicy
// CNIPluginPath is the location of CNI plugin helpers, if they should be
// run from a location other than the default location.
CNIPluginPath string
// CNIConfigDir is the location of CNI configuration files, if the files in
// the default configuration directory shouldn't be used.
CNIConfigDir string
// Terminal provides a way to specify whether or not the command should
// be run with a pseudoterminal. By default (DefaultTerminal), a
// terminal is used if os.Stdout is connected to a terminal, but that
@ -497,9 +510,10 @@ func setupTerminal(g *generate.Generator, terminalPolicy TerminalPolicy) {
}
}
func setupNamespaces(g *generate.Generator, namespaceOptions NamespaceOptions, idmapOptions IDMappingOptions, networkDisabled bool) error {
func setupNamespaces(g *generate.Generator, namespaceOptions NamespaceOptions, idmapOptions IDMappingOptions, policy NetworkConfigurationPolicy) (configureNetwork bool, configureNetworks []string, err error) {
// Set namespace options in the container configuration.
configureUserns := false
specifiedNetwork := false
for _, namespaceOption := range namespaceOptions {
switch namespaceOption.Name {
case string(specs.UserNamespace):
@ -507,22 +521,32 @@ func setupNamespaces(g *generate.Generator, namespaceOptions NamespaceOptions, i
if !namespaceOption.Host && namespaceOption.Path == "" {
configureUserns = true
}
case specs.NetworkNamespace:
specifiedNetwork = true
configureNetwork = false
if !namespaceOption.Host && (namespaceOption.Path == "" || !filepath.IsAbs(namespaceOption.Path)) {
if namespaceOption.Path != "" && !filepath.IsAbs(namespaceOption.Path) {
configureNetworks = strings.Split(namespaceOption.Path, ",")
namespaceOption.Path = ""
}
configureNetwork = (policy != NetworkDisabled)
}
}
if namespaceOption.Host {
if err := g.RemoveLinuxNamespace(namespaceOption.Name); err != nil {
return errors.Wrapf(err, "error removing %q namespace for run", namespaceOption.Name)
return false, nil, errors.Wrapf(err, "error removing %q namespace for run", namespaceOption.Name)
}
} else if err := g.AddOrReplaceLinuxNamespace(namespaceOption.Name, namespaceOption.Path); err != nil {
if namespaceOption.Path == "" {
return errors.Wrapf(err, "error adding new %q namespace for run", namespaceOption.Name)
return false, nil, errors.Wrapf(err, "error adding new %q namespace for run", namespaceOption.Name)
}
return errors.Wrapf(err, "error adding %q namespace %q for run", namespaceOption.Name, namespaceOption.Path)
return false, nil, errors.Wrapf(err, "error adding %q namespace %q for run", namespaceOption.Name, namespaceOption.Path)
}
}
// If we've got mappings, we're going to have to create a user namespace.
if len(idmapOptions.UIDMap) > 0 || len(idmapOptions.GIDMap) > 0 || configureUserns {
if err := g.AddOrReplaceLinuxNamespace(specs.UserNamespace, ""); err != nil {
return errors.Wrapf(err, "error adding new %q namespace for run", string(specs.UserNamespace))
return false, nil, errors.Wrapf(err, "error adding new %q namespace for run", string(specs.UserNamespace))
}
for _, m := range idmapOptions.UIDMap {
g.AddLinuxUIDMapping(m.HostID, m.ContainerID, m.Size)
@ -530,7 +554,7 @@ func setupNamespaces(g *generate.Generator, namespaceOptions NamespaceOptions, i
if len(idmapOptions.UIDMap) == 0 {
mappings, err := getProcIDMappings("/proc/self/uid_map")
if err != nil {
return err
return false, nil, err
}
for _, m := range mappings {
g.AddLinuxUIDMapping(m.ContainerID, m.ContainerID, m.Size)
@ -542,28 +566,29 @@ func setupNamespaces(g *generate.Generator, namespaceOptions NamespaceOptions, i
if len(idmapOptions.GIDMap) == 0 {
mappings, err := getProcIDMappings("/proc/self/gid_map")
if err != nil {
return err
return false, nil, err
}
for _, m := range mappings {
g.AddLinuxGIDMapping(m.ContainerID, m.ContainerID, m.Size)
}
}
if !networkDisabled {
if !specifiedNetwork {
if err := g.AddOrReplaceLinuxNamespace(specs.NetworkNamespace, ""); err != nil {
return errors.Wrapf(err, "error adding new %q namespace for run", string(specs.NetworkNamespace))
return false, nil, errors.Wrapf(err, "error adding new %q namespace for run", string(specs.NetworkNamespace))
}
configureNetwork = (policy != NetworkDisabled)
}
} else {
if err := g.RemoveLinuxNamespace(specs.UserNamespace); err != nil {
return errors.Wrapf(err, "error removing %q namespace for run", string(specs.UserNamespace))
return false, nil, errors.Wrapf(err, "error removing %q namespace for run", string(specs.UserNamespace))
}
if !networkDisabled {
if !specifiedNetwork {
if err := g.RemoveLinuxNamespace(specs.NetworkNamespace); err != nil {
return errors.Wrapf(err, "error removing %q namespace for run", string(specs.NetworkNamespace))
return false, nil, errors.Wrapf(err, "error removing %q namespace for run", string(specs.NetworkNamespace))
}
}
}
return nil
return configureNetwork, configureNetworks, nil
}
// Run runs the specified command in the container's root filesystem.
@ -634,7 +659,14 @@ func (b *Builder) Run(command []string, options RunOptions) error {
namespaceOptions := DefaultNamespaceOptions()
namespaceOptions.AddOrReplace(b.NamespaceOptions...)
namespaceOptions.AddOrReplace(options.NamespaceOptions...)
if err = setupNamespaces(g, namespaceOptions, b.IDMappingOptions, options.NetworkDisabled); err != nil {
networkPolicy := options.ConfigureNetwork
if networkPolicy == NetworkDefault {
networkPolicy = b.ConfigureNetwork
}
configureNetwork, configureNetworks, err := setupNamespaces(g, namespaceOptions, b.IDMappingOptions, networkPolicy)
if err != nil {
return err
}
@ -694,25 +726,43 @@ func (b *Builder) Run(command []string, options RunOptions) error {
if err != nil {
return errors.Wrapf(err, "error resolving mountpoints for container")
}
return b.runUsingRuntimeSubproc(options, spec, mountPoint, path, Package+"-"+filepath.Base(path))
if options.CNIConfigDir == "" {
options.CNIConfigDir = b.CNIConfigDir
if b.CNIConfigDir == "" {
options.CNIConfigDir = util.DefaultCNIConfigDir
}
}
if options.CNIPluginPath == "" {
options.CNIPluginPath = b.CNIPluginPath
if b.CNIPluginPath == "" {
options.CNIPluginPath = util.DefaultCNIPluginPath
}
}
return b.runUsingRuntimeSubproc(options, configureNetwork, configureNetworks, spec, mountPoint, path, Package+"-"+filepath.Base(path))
}
type runUsingRuntimeSubprocOptions struct {
Options RunOptions
Spec *specs.Spec
RootPath string
BundlePath string
ContainerName string
Options RunOptions
Spec *specs.Spec
RootPath string
BundlePath string
ConfigureNetwork bool
ConfigureNetworks []string
ContainerName string
}
func (b *Builder) runUsingRuntimeSubproc(options RunOptions, spec *specs.Spec, rootPath, bundlePath, containerName string) (err error) {
func (b *Builder) runUsingRuntimeSubproc(options RunOptions, configureNetwork bool, configureNetworks []string, spec *specs.Spec, rootPath, bundlePath, containerName string) (err error) {
var confwg sync.WaitGroup
config, conferr := json.Marshal(runUsingRuntimeSubprocOptions{
Options: options,
Spec: spec,
RootPath: rootPath,
BundlePath: bundlePath,
ContainerName: containerName,
Options: options,
Spec: spec,
RootPath: rootPath,
BundlePath: bundlePath,
ConfigureNetwork: configureNetwork,
ConfigureNetworks: configureNetworks,
ContainerName: containerName,
})
if conferr != nil {
return errors.Wrapf(conferr, "error encoding configuration for %q", runUsingRuntimeCommand)
@ -773,7 +823,7 @@ func runUsingRuntimeMain() {
os.Exit(1)
}
// Run the container, start to finish.
status, err := runUsingRuntime(options.Options, options.Spec, options.RootPath, options.BundlePath, options.ContainerName)
status, err := runUsingRuntime(options.Options, options.ConfigureNetwork, options.ConfigureNetworks, options.Spec, options.RootPath, options.BundlePath, options.ContainerName)
if err != nil {
fmt.Fprintf(os.Stderr, "error running container: %v\n", err)
os.Exit(1)
@ -788,7 +838,7 @@ func runUsingRuntimeMain() {
os.Exit(1)
}
func runUsingRuntime(options RunOptions, spec *specs.Spec, rootPath, bundlePath, containerName string) (wstatus unix.WaitStatus, err error) {
func runUsingRuntime(options RunOptions, configureNetwork bool, configureNetworks []string, spec *specs.Spec, rootPath, bundlePath, containerName string) (wstatus unix.WaitStatus, err error) {
// Write the runtime configuration.
specbytes, err := json.Marshal(spec)
if err != nil {
@ -928,6 +978,16 @@ func runUsingRuntime(options RunOptions, spec *specs.Spec, rootPath, bundlePath,
}
}()
if configureNetwork {
teardown, err := runConfigureNetwork(options, configureNetwork, configureNetworks, pid, containerName, spec.Process.Args)
if teardown != nil {
defer teardown()
}
if err != nil {
return 1, err
}
}
if copyStdio {
// We don't need the ends of the pipes that belong to the container.
stdin.Close()
@ -1006,6 +1066,100 @@ func runUsingRuntime(options RunOptions, spec *specs.Spec, rootPath, bundlePath,
return wstatus, nil
}
func runConfigureNetwork(options RunOptions, configureNetwork bool, configureNetworks []string, pid int, containerName string, command []string) (teardown func(), err error) {
var netconf, undo []*libcni.NetworkConfigList
// Scan for CNI configuration files.
confdir := options.CNIConfigDir
files, err := libcni.ConfFiles(confdir, []string{".conf"})
if err != nil {
return nil, errors.Wrapf(err, "error finding CNI networking configuration files named *.conf in directory %q", confdir)
}
lists, err := libcni.ConfFiles(confdir, []string{".conflist"})
if err != nil {
return nil, errors.Wrapf(err, "error finding CNI networking configuration list files named *.conflist in directory %q", confdir)
}
logrus.Debugf("CNI network configuration file list: %#v", append(files, lists...))
// Read the CNI configuration files.
for _, file := range files {
nc, err := libcni.ConfFromFile(file)
if err != nil {
return nil, errors.Wrapf(err, "error loading networking configuration from file %q for %v", file, command)
}
if len(configureNetworks) > 0 && nc.Network != nil && (nc.Network.Name == "" || !stringInSlice(nc.Network.Name, configureNetworks)) {
if nc.Network.Name == "" {
logrus.Debugf("configuration in %q has no name, skipping it", file)
} else {
logrus.Debugf("configuration in %q has name %q, skipping it", file, nc.Network.Name)
}
continue
}
cl, err := libcni.ConfListFromConf(nc)
if err != nil {
return nil, errors.Wrapf(err, "error converting networking configuration from file %q for %v", file, command)
}
logrus.Debugf("using network configuration from %q", file)
netconf = append(netconf, cl)
}
for _, list := range lists {
cl, err := libcni.ConfListFromFile(list)
if err != nil {
return nil, errors.Wrapf(err, "error loading networking configuration list from file %q for %v", list, command)
}
if len(configureNetworks) > 0 && (cl.Name == "" || !stringInSlice(cl.Name, configureNetworks)) {
if cl.Name == "" {
logrus.Debugf("configuration list in %q has no name, skipping it", list)
} else {
logrus.Debugf("configuration list in %q has name %q, skipping it", list, cl.Name)
}
continue
}
logrus.Debugf("using network configuration list from %q", list)
netconf = append(netconf, cl)
}
// Make sure we can access the container's network namespace,
// even after it exits, to successfully tear down the
// interfaces. Ensure this by opening a handle to the network
// namespace, and using our copy to both configure and
// deconfigure it.
netns := fmt.Sprintf("/proc/%d/ns/net", pid)
netFD, err := unix.Open(netns, unix.O_RDONLY, 0)
if err != nil {
return nil, errors.Wrapf(err, "error opening network namespace for %v", command)
}
mynetns := fmt.Sprintf("/proc/%d/fd/%d", unix.Getpid(), netFD)
// Build our search path for the plugins.
pluginPaths := strings.Split(options.CNIPluginPath, string(os.PathListSeparator))
cni := libcni.CNIConfig{Path: pluginPaths}
// Configure the interfaces.
rtconf := make(map[*libcni.NetworkConfigList]*libcni.RuntimeConf)
teardown = func() {
for _, nc := range undo {
if err = cni.DelNetworkList(nc, rtconf[nc]); err != nil {
logrus.Errorf("error cleaning up network %v for %v: %v", rtconf[nc].IfName, command, err)
}
}
unix.Close(netFD)
}
for i, nc := range netconf {
// Build the runtime config for use with this network configuration.
rtconf[nc] = &libcni.RuntimeConf{
ContainerID: containerName,
NetNS: mynetns,
IfName: fmt.Sprintf("if%d", i),
Args: [][2]string{},
CapabilityArgs: map[string]interface{}{},
}
// Bring it up.
_, err := cni.AddNetworkList(nc, rtconf[nc])
if err != nil {
return teardown, errors.Wrapf(err, "error configuring network list %v for %v", rtconf[nc].IfName, command)
}
// Add it to the list of networks to take down when the container process exits.
undo = append([]*libcni.NetworkConfigList{nc}, undo...)
}
return teardown, nil
}
func runCopyStdio(stdio *sync.WaitGroup, copyStdio bool, stdioPipe [][]int, copyConsole bool, consoleListener *net.UnixListener, finishCopy []int, finishedCopy chan struct{}) {
defer func() {
unix.Close(finishCopy[0])

View File

@ -36,6 +36,15 @@ func copyStringSlice(s []string) []string {
return t
}
func stringInSlice(s string, slice []string) bool {
for _, v := range slice {
if v == s {
return true
}
}
return false
}
func convertStorageIDMaps(UIDMap, GIDMap []idtools.IDMap) ([]rspec.LinuxIDMapping, []rspec.LinuxIDMapping) {
uidmap := make([]rspec.LinuxIDMapping, 0, len(UIDMap))
gidmap := make([]rspec.LinuxIDMapping, 0, len(GIDMap))

10
util/types.go Normal file
View File

@ -0,0 +1,10 @@
package util
const (
// DefaultRuntime is the default command to use to run the container.
DefaultRuntime = "runc"
// DefaultCNIPluginPath is the default location of CNI plugin helpers.
DefaultCNIPluginPath = "/usr/libexec/cni:/opt/cni/bin"
// DefaultCNIConfigDir is the default location of CNI configuration files.
DefaultCNIConfigDir = "/etc/cni/net.d"
)