diff options
author | Brent Baude <bbaude@redhat.com> | 2021-07-14 16:03:55 -0500 |
---|---|---|
committer | Brent Baude <bbaude@redhat.com> | 2021-08-04 14:14:36 -0500 |
commit | 3c3fa6fac4d0f8e89181ea2d4e1fe0318d24b6f4 (patch) | |
tree | f087d0a772797a9028df514d8d0369835724b3a2 /libpod | |
parent | e93661f5e765d84893e2ad5a488682c0a67412d0 (diff) | |
download | podman-3c3fa6fac4d0f8e89181ea2d4e1fe0318d24b6f4.tar.gz podman-3c3fa6fac4d0f8e89181ea2d4e1fe0318d24b6f4.tar.bz2 podman-3c3fa6fac4d0f8e89181ea2d4e1fe0318d24b6f4.zip |
implement init containers in podman
this is the first pass at implementing init containers for podman pods.
init containersare made popular by k8s as a way to run setup for pods
before the pods standard containers run.
unlike k8s, we support two styles of init containers: always and
oneshot. always means the container stays in the pod and starts
whenever a pod is started. this does not apply to pods restarting.
oneshot means the container runs onetime when the pod starts and then is
removed.
Signed-off-by: Brent Baude <bbaude@redhat.com>
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/container_config.go | 3 | ||||
-rw-r--r-- | libpod/container_graph.go | 2 | ||||
-rw-r--r-- | libpod/container_validate.go | 6 | ||||
-rw-r--r-- | libpod/define/container.go | 10 | ||||
-rw-r--r-- | libpod/options.go | 15 | ||||
-rw-r--r-- | libpod/pod.go | 37 | ||||
-rw-r--r-- | libpod/pod_api.go | 67 |
7 files changed, 124 insertions, 16 deletions
diff --git a/libpod/container_config.go b/libpod/container_config.go index 0de79fde3..72a969fe6 100644 --- a/libpod/container_config.go +++ b/libpod/container_config.go @@ -375,4 +375,7 @@ type ContainerMiscConfig struct { CDIDevices []string `json:"cdiDevices,omitempty"` // EnvSecrets are secrets that are set as environment variables EnvSecrets map[string]*secrets.Secret `json:"secret_env,omitempty"` + // InitContainerType specifies if the container is an initcontainer + // and if so, what type: always or oneshot are possible non-nil entries + InitContainerType string `json:"init_container_type,omitempty"` } diff --git a/libpod/container_graph.go b/libpod/container_graph.go index 3ae7cfbc7..32fb264f1 100644 --- a/libpod/container_graph.go +++ b/libpod/container_graph.go @@ -259,7 +259,7 @@ func startNode(ctx context.Context, node *containerNode, setError bool, ctrError } // Start the container (only if it is not running) - if !ctrErrored { + if !ctrErrored && len(node.container.config.InitContainerType) < 1 { if !restart && node.container.state.State != define.ContainerStateRunning { if err := node.container.initAndStart(ctx); err != nil { ctrErrored = true diff --git a/libpod/container_validate.go b/libpod/container_validate.go index 6ff46f1b1..91ebe93fb 100644 --- a/libpod/container_validate.go +++ b/libpod/container_validate.go @@ -131,5 +131,11 @@ func (c *Container) validate() error { if c.config.User == "" && (c.config.Spec.Process.User.UID != 0 || c.config.Spec.Process.User.GID != 0) { return errors.Wrapf(define.ErrInvalidArg, "please set User explicitly via WithUser() instead of in OCI spec directly") } + + // Init-ctrs must be used inside a Pod. Check if a init container type is + // passed and if no pod is passed + if len(c.config.InitContainerType) > 0 && len(c.config.Pod) < 1 { + return errors.Wrap(define.ErrInvalidArg, "init containers must be created in a pod") + } return nil } diff --git a/libpod/define/container.go b/libpod/define/container.go index f3125afa9..f0aca92aa 100644 --- a/libpod/define/container.go +++ b/libpod/define/container.go @@ -26,3 +26,13 @@ var RestartPolicyMap = map[string]string{ RestartPolicyOnFailure: RestartPolicyOnFailure, RestartPolicyUnlessStopped: RestartPolicyUnlessStopped, } + +// InitContainerTypes +const ( + // AlwaysInitContainer is an init container than runs on each + // pod start (including restart) + AlwaysInitContainer = "always" + // OneShotInitContainer is a container that only runs as init once + // and is then deleted. + OneShotInitContainer = "oneshot" +) diff --git a/libpod/options.go b/libpod/options.go index 17a36008d..553206a8a 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1768,6 +1768,21 @@ func WithPidFile(pidFile string) CtrCreateOption { } } +// WithInitCtrType indicates the container is a initcontainer +func WithInitCtrType(containerType string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + // Make sure the type is valid + if containerType == define.OneShotInitContainer || containerType == define.AlwaysInitContainer { + ctr.config.InitContainerType = containerType + return nil + } + return errors.Errorf("%s is invalid init container type", containerType) + } +} + // Pod Creation Options // WithInfraImage sets the infra image for libpod. diff --git a/libpod/pod.go b/libpod/pod.go index 62f5c9e5b..0fef7f6f3 100644 --- a/libpod/pod.go +++ b/libpod/pod.go @@ -3,6 +3,7 @@ package libpod import ( "context" "net" + "sort" "time" "github.com/containers/podman/v3/libpod/define" @@ -332,17 +333,20 @@ func (p *Pod) SharesNamespaces() bool { return p.SharesPID() || p.SharesIPC() || p.SharesNet() || p.SharesMount() || p.SharesUser() || p.SharesUTS() } +// infraContainerID returns the infra ID without a lock +func (p *Pod) infraContainerID() (string, error) { + if err := p.updatePod(); err != nil { + return "", err + } + return p.state.InfraContainerID, nil +} + // InfraContainerID returns the infra container ID for a pod. // If the container returned is "", the pod has no infra container. func (p *Pod) InfraContainerID() (string, error) { p.lock.Lock() defer p.lock.Unlock() - - if err := p.updatePod(); err != nil { - return "", err - } - - return p.state.InfraContainerID, nil + return p.infraContainerID() } // InfraContainer returns the infra container. @@ -350,7 +354,6 @@ func (p *Pod) InfraContainer() (*Container, error) { if !p.HasInfraContainer() { return nil, errors.Wrap(define.ErrNoSuchCtr, "pod has no infra container") } - id, err := p.InfraContainerID() if err != nil { return nil, err @@ -420,3 +423,23 @@ func (p *Pod) ProcessLabel() (string, error) { } return ctr.ProcessLabel(), nil } + +// initContainers returns the list of initcontainers +// in a pod sorted by create time +func (p *Pod) initContainers() ([]*Container, error) { + initCons := make([]*Container, 0) + // the pod is already locked when this is called + cons, err := p.allContainers() + if err != nil { + return nil, err + } + // Sort the pod containers by created time + sort.Slice(cons, func(i, j int) bool { return cons[i].CreatedTime().Before(cons[j].CreatedTime()) }) + // Iterate sorted containers and add ids for any init containers + for _, c := range cons { + if len(c.config.InitContainerType) > 0 { + initCons = append(initCons, c) + } + } + return initCons, nil +} diff --git a/libpod/pod_api.go b/libpod/pod_api.go index 1ab012a8b..90d67dbb0 100644 --- a/libpod/pod_api.go +++ b/libpod/pod_api.go @@ -12,6 +12,45 @@ import ( "github.com/sirupsen/logrus" ) +// startInitContainers starts a pod's init containers. +func (p *Pod) startInitContainers(ctx context.Context) error { + initCtrs, err := p.initContainers() + if err != nil { + return err + } + // Now iterate init containers + for _, initCon := range initCtrs { + if err := initCon.Start(ctx, true); err != nil { + return err + } + // Check that the init container waited correctly and the exit + // code is good + rc, err := initCon.Wait(ctx) + if err != nil { + return err + } + if rc != 0 { + return errors.Errorf("init container %s exited with code %d", initCon.ID(), rc) + } + // If the container is an oneshot init container, we need to remove it + // after it runs + if initCon.Config().InitContainerType == define.OneShotInitContainer { + icLock := initCon.lock + icLock.Lock() + if err := p.runtime.removeContainer(ctx, initCon, false, false, true); err != nil { + icLock.Unlock() + return errors.Wrapf(err, "failed to remove oneshot init container %s", initCon.ID()) + } + // Removing a container this way requires an explicit call to clean up the db + if err := p.runtime.state.RemoveContainerFromPod(p, initCon); err != nil { + logrus.Errorf("Error removing container %s from database: %v", initCon.ID(), err) + } + icLock.Unlock() + } + } + return nil +} + // Start starts all containers within a pod. // It combines the effects of Init() and Start() on a container. // If a container has already been initialized it will be started, @@ -34,26 +73,29 @@ func (p *Pod) Start(ctx context.Context) (map[string]error, error) { return nil, define.ErrPodRemoved } + // Before "regular" containers start in the pod, all init containers + // must have run and exited successfully. + if err := p.startInitContainers(ctx); err != nil { + return nil, err + } allCtrs, err := p.runtime.state.PodContainers(p) if err != nil { return nil, err } - // Build a dependency graph of containers in the pod graph, err := BuildContainerGraph(allCtrs) if err != nil { return nil, errors.Wrapf(err, "error generating dependency graph for pod %s", p.ID()) } - - ctrErrors := make(map[string]error) - ctrsVisited := make(map[string]bool) - // If there are no containers without dependencies, we can't start // Error out if len(graph.noDepNodes) == 0 { return nil, errors.Wrapf(define.ErrNoSuchCtr, "no containers in pod %s have no dependencies, cannot start pod", p.ID()) } + ctrErrors := make(map[string]error) + ctrsVisited := make(map[string]bool) + // Traverse the graph beginning at nodes with no dependencies for _, node := range graph.noDepNodes { startNode(ctx, node, false, ctrErrors, ctrsVisited, false) @@ -449,12 +491,18 @@ func (p *Pod) Status() (map[string]define.ContainerStatus, error) { if !p.valid { return nil, define.ErrPodRemoved } - allCtrs, err := p.runtime.state.PodContainers(p) if err != nil { return nil, err } - return containerStatusFromContainers(allCtrs) + noInitCtrs := make([]*Container, 0) + // Do not add init containers into status + for _, ctr := range allCtrs { + if ctrType := ctr.config.InitContainerType; len(ctrType) < 1 { + noInitCtrs = append(noInitCtrs, ctr) + } + } + return containerStatusFromContainers(noInitCtrs) } func containerStatusFromContainers(allCtrs []*Container) (map[string]define.ContainerStatus, error) { @@ -504,7 +552,10 @@ func (p *Pod) Inspect() (*define.InspectPodData, error) { Name: c.Name(), State: containerStatus, }) - ctrStatuses[c.ID()] = c.state.State + // Do not add init containers fdr status + if len(c.config.InitContainerType) < 1 { + ctrStatuses[c.ID()] = c.state.State + } } podState, err := createPodStatusResults(ctrStatuses) if err != nil { |