mirror of https://github.com/containers/podman.git
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>
This commit is contained in:
parent
e93661f5e7
commit
3c3fa6fac4
|
@ -61,6 +61,7 @@ type ContainerCLIOpts struct {
|
||||||
HTTPProxy bool
|
HTTPProxy bool
|
||||||
ImageVolume string
|
ImageVolume string
|
||||||
Init bool
|
Init bool
|
||||||
|
InitContainerType string
|
||||||
InitPath string
|
InitPath string
|
||||||
Interactive bool
|
Interactive bool
|
||||||
IPC string
|
IPC string
|
||||||
|
|
|
@ -659,6 +659,8 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
|
||||||
s.PidFile = c.PidFile
|
s.PidFile = c.PidFile
|
||||||
s.Volatile = c.Rm
|
s.Volatile = c.Rm
|
||||||
|
|
||||||
|
// Initcontainers
|
||||||
|
s.InitContainerType = c.InitContainerType
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containers/common/pkg/completion"
|
||||||
"github.com/containers/common/pkg/config"
|
"github.com/containers/common/pkg/config"
|
||||||
"github.com/containers/image/v5/transports/alltransports"
|
"github.com/containers/image/v5/transports/alltransports"
|
||||||
"github.com/containers/podman/v3/cmd/podman/common"
|
"github.com/containers/podman/v3/cmd/podman/common"
|
||||||
|
@ -49,12 +50,20 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cliVals common.ContainerCLIOpts
|
cliVals common.ContainerCLIOpts
|
||||||
|
InitContainerType string
|
||||||
)
|
)
|
||||||
|
|
||||||
func createFlags(cmd *cobra.Command) {
|
func createFlags(cmd *cobra.Command) {
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|
||||||
|
initContainerFlagName := "init-ctr"
|
||||||
|
flags.StringVar(
|
||||||
|
&InitContainerType,
|
||||||
|
initContainerFlagName, "",
|
||||||
|
"Make this a pod init container.",
|
||||||
|
)
|
||||||
|
|
||||||
flags.SetInterspersed(false)
|
flags.SetInterspersed(false)
|
||||||
common.DefineCreateFlags(cmd, &cliVals)
|
common.DefineCreateFlags(cmd, &cliVals)
|
||||||
common.DefineNetFlags(cmd)
|
common.DefineNetFlags(cmd)
|
||||||
|
@ -65,6 +74,8 @@ func createFlags(cmd *cobra.Command) {
|
||||||
_ = flags.MarkHidden("conmon-pidfile")
|
_ = flags.MarkHidden("conmon-pidfile")
|
||||||
_ = flags.MarkHidden("pidfile")
|
_ = flags.MarkHidden("pidfile")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc(initContainerFlagName, completion.AutocompleteDefault)
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -89,6 +100,17 @@ func create(cmd *cobra.Command, args []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if initctr is used with --pod and the value is correct
|
||||||
|
if initctr := InitContainerType; cmd.Flags().Changed("init-ctr") {
|
||||||
|
if !cmd.Flags().Changed("pod") {
|
||||||
|
return errors.New("must specify pod value with init-ctr")
|
||||||
|
}
|
||||||
|
if !util.StringInSlice(initctr, []string{"always", "oneshot"}) {
|
||||||
|
return errors.New("init-ctr value must be 'always' or 'oneshot'")
|
||||||
|
}
|
||||||
|
cliVals.InitContainerType = initctr
|
||||||
|
}
|
||||||
|
|
||||||
if err := createInit(cmd); err != nil {
|
if err := createInit(cmd); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -447,6 +447,21 @@ content that disappears when the container is stopped.
|
||||||
|
|
||||||
Run an init inside the container that forwards signals and reaps processes.
|
Run an init inside the container that forwards signals and reaps processes.
|
||||||
|
|
||||||
|
#### **--init-ctr**=*type* (pods only)
|
||||||
|
|
||||||
|
When using pods, create an init style container, which is run after the infra container is started
|
||||||
|
but before regular pod containers are started. Init containers are useful for running
|
||||||
|
setup operations for the pod's applications.
|
||||||
|
|
||||||
|
Valid values for `init-ctr` type are *always* or *oneshot*. The *always* value
|
||||||
|
means the container will run with each and every `pod start`, whereas the *oneshot*
|
||||||
|
value means is will ony run once when the pod is started and then the container is
|
||||||
|
removed.
|
||||||
|
|
||||||
|
Init containers are only run on pod `start`. Restarting a pod will not execute any init
|
||||||
|
containers should they be present. Furthermore, init containers can only be created in a
|
||||||
|
pod when that pod is not running.
|
||||||
|
|
||||||
#### **--init-path**=*path*
|
#### **--init-path**=*path*
|
||||||
|
|
||||||
Path to the container-init binary.
|
Path to the container-init binary.
|
||||||
|
|
|
@ -375,4 +375,7 @@ type ContainerMiscConfig struct {
|
||||||
CDIDevices []string `json:"cdiDevices,omitempty"`
|
CDIDevices []string `json:"cdiDevices,omitempty"`
|
||||||
// EnvSecrets are secrets that are set as environment variables
|
// EnvSecrets are secrets that are set as environment variables
|
||||||
EnvSecrets map[string]*secrets.Secret `json:"secret_env,omitempty"`
|
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"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -259,7 +259,7 @@ func startNode(ctx context.Context, node *containerNode, setError bool, ctrError
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the container (only if it is not running)
|
// 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 !restart && node.container.state.State != define.ContainerStateRunning {
|
||||||
if err := node.container.initAndStart(ctx); err != nil {
|
if err := node.container.initAndStart(ctx); err != nil {
|
||||||
ctrErrored = true
|
ctrErrored = true
|
||||||
|
|
|
@ -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) {
|
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")
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,3 +26,13 @@ var RestartPolicyMap = map[string]string{
|
||||||
RestartPolicyOnFailure: RestartPolicyOnFailure,
|
RestartPolicyOnFailure: RestartPolicyOnFailure,
|
||||||
RestartPolicyUnlessStopped: RestartPolicyUnlessStopped,
|
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"
|
||||||
|
)
|
||||||
|
|
|
@ -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
|
// Pod Creation Options
|
||||||
|
|
||||||
// WithInfraImage sets the infra image for libpod.
|
// WithInfraImage sets the infra image for libpod.
|
||||||
|
|
|
@ -3,6 +3,7 @@ package libpod
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containers/podman/v3/libpod/define"
|
"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()
|
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.
|
// InfraContainerID returns the infra container ID for a pod.
|
||||||
// If the container returned is "", the pod has no infra container.
|
// If the container returned is "", the pod has no infra container.
|
||||||
func (p *Pod) InfraContainerID() (string, error) {
|
func (p *Pod) InfraContainerID() (string, error) {
|
||||||
p.lock.Lock()
|
p.lock.Lock()
|
||||||
defer p.lock.Unlock()
|
defer p.lock.Unlock()
|
||||||
|
return p.infraContainerID()
|
||||||
if err := p.updatePod(); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.state.InfraContainerID, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// InfraContainer returns the infra container.
|
// InfraContainer returns the infra container.
|
||||||
|
@ -350,7 +354,6 @@ func (p *Pod) InfraContainer() (*Container, error) {
|
||||||
if !p.HasInfraContainer() {
|
if !p.HasInfraContainer() {
|
||||||
return nil, errors.Wrap(define.ErrNoSuchCtr, "pod has no infra container")
|
return nil, errors.Wrap(define.ErrNoSuchCtr, "pod has no infra container")
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := p.InfraContainerID()
|
id, err := p.InfraContainerID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -420,3 +423,23 @@ func (p *Pod) ProcessLabel() (string, error) {
|
||||||
}
|
}
|
||||||
return ctr.ProcessLabel(), nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,45 @@ import (
|
||||||
"github.com/sirupsen/logrus"
|
"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.
|
// Start starts all containers within a pod.
|
||||||
// It combines the effects of Init() and Start() on a container.
|
// It combines the effects of Init() and Start() on a container.
|
||||||
// If a container has already been initialized it will be started,
|
// 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
|
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)
|
allCtrs, err := p.runtime.state.PodContainers(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build a dependency graph of containers in the pod
|
// Build a dependency graph of containers in the pod
|
||||||
graph, err := BuildContainerGraph(allCtrs)
|
graph, err := BuildContainerGraph(allCtrs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "error generating dependency graph for pod %s", p.ID())
|
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
|
// If there are no containers without dependencies, we can't start
|
||||||
// Error out
|
// Error out
|
||||||
if len(graph.noDepNodes) == 0 {
|
if len(graph.noDepNodes) == 0 {
|
||||||
return nil, errors.Wrapf(define.ErrNoSuchCtr, "no containers in pod %s have no dependencies, cannot start pod", p.ID())
|
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
|
// Traverse the graph beginning at nodes with no dependencies
|
||||||
for _, node := range graph.noDepNodes {
|
for _, node := range graph.noDepNodes {
|
||||||
startNode(ctx, node, false, ctrErrors, ctrsVisited, false)
|
startNode(ctx, node, false, ctrErrors, ctrsVisited, false)
|
||||||
|
@ -449,12 +491,18 @@ func (p *Pod) Status() (map[string]define.ContainerStatus, error) {
|
||||||
if !p.valid {
|
if !p.valid {
|
||||||
return nil, define.ErrPodRemoved
|
return nil, define.ErrPodRemoved
|
||||||
}
|
}
|
||||||
|
|
||||||
allCtrs, err := p.runtime.state.PodContainers(p)
|
allCtrs, err := p.runtime.state.PodContainers(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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) {
|
func containerStatusFromContainers(allCtrs []*Container) (map[string]define.ContainerStatus, error) {
|
||||||
|
@ -504,7 +552,10 @@ func (p *Pod) Inspect() (*define.InspectPodData, error) {
|
||||||
Name: c.Name(),
|
Name: c.Name(),
|
||||||
State: containerStatus,
|
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)
|
podState, err := createPodStatusResults(ctrStatuses)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -250,7 +250,9 @@ func (ic *ContainerEngine) prunePodHelper(ctx context.Context) ([]*entities.PodP
|
||||||
|
|
||||||
func (ic *ContainerEngine) PodCreate(ctx context.Context, opts entities.PodCreateOptions) (*entities.PodCreateReport, error) {
|
func (ic *ContainerEngine) PodCreate(ctx context.Context, opts entities.PodCreateOptions) (*entities.PodCreateReport, error) {
|
||||||
podSpec := specgen.NewPodSpecGenerator()
|
podSpec := specgen.NewPodSpecGenerator()
|
||||||
opts.ToPodSpecGen(podSpec)
|
if err := opts.ToPodSpecGen(podSpec); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
pod, err := generate.MakePod(podSpec, ic.Libpod)
|
pod, err := generate.MakePod(podSpec, ic.Libpod)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -140,10 +140,29 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat
|
||||||
// VM, which is the default behavior
|
// VM, which is the default behavior
|
||||||
// - "container" denotes the container should join the VM of the SandboxID
|
// - "container" denotes the container should join the VM of the SandboxID
|
||||||
// (the infra container)
|
// (the infra container)
|
||||||
|
|
||||||
if len(s.Pod) > 0 {
|
if len(s.Pod) > 0 {
|
||||||
annotations[ann.SandboxID] = s.Pod
|
annotations[ann.SandboxID] = s.Pod
|
||||||
annotations[ann.ContainerType] = ann.ContainerTypeContainer
|
annotations[ann.ContainerType] = ann.ContainerTypeContainer
|
||||||
|
// Check if this is an init-ctr and if so, check if
|
||||||
|
// the pod is running. we do not want to add init-ctrs to
|
||||||
|
// a running pod because it creates confusion for us.
|
||||||
|
if len(s.InitContainerType) > 0 {
|
||||||
|
p, err := r.LookupPod(s.Pod)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
containerStatuses, err := p.Status()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// If any one of the containers is running, the pod is considered to be
|
||||||
|
// running
|
||||||
|
for _, con := range containerStatuses {
|
||||||
|
if con == define.ContainerStateRunning {
|
||||||
|
return nil, errors.New("cannot add init-ctr to a running pod")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range rtc.Containers.Annotations {
|
for _, v := range rtc.Containers.Annotations {
|
||||||
|
|
|
@ -144,11 +144,14 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
|
||||||
options = append(options, libpod.WithNetworkAliases(s.Aliases))
|
options = append(options, libpod.WithNetworkAliases(s.Aliases))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if containerType := s.InitContainerType; len(containerType) > 0 {
|
||||||
|
options = append(options, libpod.WithInitCtrType(containerType))
|
||||||
|
}
|
||||||
|
|
||||||
if len(s.Devices) > 0 {
|
if len(s.Devices) > 0 {
|
||||||
opts = extractCDIDevices(s)
|
opts = extractCDIDevices(s)
|
||||||
options = append(options, opts...)
|
options = append(options, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
runtimeSpec, err := SpecGenToOCI(ctx, s, rt, rtc, newImage, finalMounts, pod, command)
|
runtimeSpec, err := SpecGenToOCI(ctx, s, rt, rtc, newImage, finalMounts, pod, command)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -183,6 +183,9 @@ type ContainerBasicConfig struct {
|
||||||
// EnvSecrets are secrets that will be set as environment variables
|
// EnvSecrets are secrets that will be set as environment variables
|
||||||
// Optional.
|
// Optional.
|
||||||
EnvSecrets map[string]string `json:"secret_env,omitempty"`
|
EnvSecrets map[string]string `json:"secret_env,omitempty"`
|
||||||
|
// InitContainerType describes if this container is an init container
|
||||||
|
// and if so, what type: always or oneshot
|
||||||
|
InitContainerType string `json:"init_container_type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerStorageConfig contains information on the storage configuration of a
|
// ContainerStorageConfig contains information on the storage configuration of a
|
||||||
|
|
|
@ -0,0 +1,171 @@
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/containers/podman/v3/libpod/define"
|
||||||
|
. "github.com/containers/podman/v3/test/utils"
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
. "github.com/onsi/gomega/gexec"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Podman init containers", func() {
|
||||||
|
var (
|
||||||
|
tempdir string
|
||||||
|
err error
|
||||||
|
podmanTest *PodmanTestIntegration
|
||||||
|
)
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
tempdir, err = CreateTempDirInTempDir()
|
||||||
|
if err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
podmanTest = PodmanTestCreate(tempdir)
|
||||||
|
podmanTest.Setup()
|
||||||
|
podmanTest.SeedImages()
|
||||||
|
})
|
||||||
|
|
||||||
|
AfterEach(func() {
|
||||||
|
podmanTest.Cleanup()
|
||||||
|
f := CurrentGinkgoTestDescription()
|
||||||
|
processTestResult(f)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
It("podman create init container without --pod should fail", func() {
|
||||||
|
session := podmanTest.Podman([]string{"create", "--init-ctr", "always", ALPINE, "top"})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session).Should(Exit(125))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("podman create init container with bad init type should fail", func() {
|
||||||
|
session := podmanTest.Podman([]string{"create", "--init-ctr", "unknown", "--pod", "new:foobar", ALPINE, "top"})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session).Should(Exit(125))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("podman init containers should not degrade pod status", func() {
|
||||||
|
// create a pod
|
||||||
|
topPod := podmanTest.Podman([]string{"create", "-t", "--pod", "new:foobar", ALPINE, "top"})
|
||||||
|
topPod.WaitWithDefaultTimeout()
|
||||||
|
Expect(topPod).Should(Exit(0))
|
||||||
|
// add an init container
|
||||||
|
session := podmanTest.Podman([]string{"create", "--init-ctr", "always", "--pod", "foobar", ALPINE, "date"})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session).Should(Exit(0))
|
||||||
|
// start a pod
|
||||||
|
start := podmanTest.Podman([]string{"pod", "start", "foobar"})
|
||||||
|
start.WaitWithDefaultTimeout()
|
||||||
|
Expect(start).Should(Exit(0))
|
||||||
|
|
||||||
|
inspect := podmanTest.Podman([]string{"pod", "inspect", "foobar"})
|
||||||
|
inspect.WaitWithDefaultTimeout()
|
||||||
|
Expect(inspect).Should(Exit(0))
|
||||||
|
data := inspect.InspectPodToJSON()
|
||||||
|
Expect(data.State).To(Equal(define.PodStateRunning))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("podman create init container should fail in running pod", func() {
|
||||||
|
// create a running pod
|
||||||
|
topPod := podmanTest.Podman([]string{"run", "-dt", "--pod", "new:foobar", ALPINE, "top"})
|
||||||
|
topPod.WaitWithDefaultTimeout()
|
||||||
|
Expect(topPod).Should(Exit(0))
|
||||||
|
// adding init-ctr to running pod should fail
|
||||||
|
session := podmanTest.Podman([]string{"create", "--init-ctr", "always", "--pod", "foobar", ALPINE, "date"})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session).Should(Exit(125))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("podman make sure init container runs before pod containers", func() {
|
||||||
|
filename := filepath.Join("/dev/shm", RandomString(12))
|
||||||
|
content := RandomString(16)
|
||||||
|
session := podmanTest.Podman([]string{"create", "--init-ctr", "always", "--pod", "new:foobar", ALPINE, "bin/sh", "-c", fmt.Sprintf("echo %s > %s", content, filename)})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session).Should(Exit(0))
|
||||||
|
verify := podmanTest.Podman([]string{"create", "--pod", "foobar", "-t", ALPINE, "top"})
|
||||||
|
verify.WaitWithDefaultTimeout()
|
||||||
|
Expect(verify).Should(Exit(0))
|
||||||
|
start := podmanTest.Podman([]string{"pod", "start", "foobar"})
|
||||||
|
start.WaitWithDefaultTimeout()
|
||||||
|
Expect(start).Should(Exit(0))
|
||||||
|
checkLog := podmanTest.Podman([]string{"exec", "-it", verify.OutputToString(), "cat", filename})
|
||||||
|
checkLog.WaitWithDefaultTimeout()
|
||||||
|
Expect(checkLog).Should(Exit(0))
|
||||||
|
Expect(checkLog.OutputToString()).To(Equal(content))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("podman make sure oneshot container is removed", func() {
|
||||||
|
filename := filepath.Join("/dev/shm", RandomString(12))
|
||||||
|
content := RandomString(16)
|
||||||
|
session := podmanTest.Podman([]string{"create", "--init-ctr", "oneshot", "--pod", "new:foobar", ALPINE, "bin/sh", "-c", fmt.Sprintf("echo %s > %s", content, filename)})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
initContainerID := session.OutputToString()
|
||||||
|
Expect(session).Should(Exit(0))
|
||||||
|
verify := podmanTest.Podman([]string{"create", "--pod", "foobar", "-t", ALPINE, "top"})
|
||||||
|
verify.WaitWithDefaultTimeout()
|
||||||
|
Expect(verify).Should(Exit(0))
|
||||||
|
start := podmanTest.Podman([]string{"pod", "start", "foobar"})
|
||||||
|
start.WaitWithDefaultTimeout()
|
||||||
|
Expect(start).Should(Exit(0))
|
||||||
|
check := podmanTest.Podman([]string{"container", "exists", initContainerID})
|
||||||
|
check.WaitWithDefaultTimeout()
|
||||||
|
// Container was rm'd
|
||||||
|
//Expect(check).Should(Exit(1))
|
||||||
|
Expect(check.ExitCode()).To(Equal(1), "I dont understand why the other way does not work")
|
||||||
|
// Lets double check with a stop and start
|
||||||
|
stopPod := podmanTest.Podman([]string{"pod", "stop", "foobar"})
|
||||||
|
stopPod.WaitWithDefaultTimeout()
|
||||||
|
Expect(stopPod).Should(Exit(0))
|
||||||
|
startPod := podmanTest.Podman([]string{"pod", "start", "foobar"})
|
||||||
|
startPod.WaitWithDefaultTimeout()
|
||||||
|
Expect(startPod).Should(Exit(0))
|
||||||
|
|
||||||
|
// Because no init was run, the file should not even exist
|
||||||
|
doubleCheck := podmanTest.Podman([]string{"exec", "-it", verify.OutputToString(), "cat", filename})
|
||||||
|
doubleCheck.WaitWithDefaultTimeout()
|
||||||
|
Expect(doubleCheck).Should(Exit(1))
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
It("podman ensure always init containers always run", func() {
|
||||||
|
filename := filepath.Join("/dev/shm", RandomString(12))
|
||||||
|
|
||||||
|
// Write the date to a file
|
||||||
|
session := podmanTest.Podman([]string{"create", "--init-ctr", "always", "--pod", "new:foobar", ALPINE, "bin/sh", "-c", fmt.Sprintf("date > %s", filename)})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session).Should(Exit(0))
|
||||||
|
verify := podmanTest.Podman([]string{"create", "--pod", "foobar", "-t", ALPINE, "top"})
|
||||||
|
verify.WaitWithDefaultTimeout()
|
||||||
|
Expect(verify).Should(Exit(0))
|
||||||
|
start := podmanTest.Podman([]string{"pod", "start", "foobar"})
|
||||||
|
start.WaitWithDefaultTimeout()
|
||||||
|
Expect(start).Should(Exit(0))
|
||||||
|
|
||||||
|
// capture the date written
|
||||||
|
checkLog := podmanTest.Podman([]string{"exec", "-it", verify.OutputToString(), "cat", filename})
|
||||||
|
checkLog.WaitWithDefaultTimeout()
|
||||||
|
firstResult := checkLog.OutputToString()
|
||||||
|
Expect(checkLog).Should(Exit(0))
|
||||||
|
|
||||||
|
// Stop and start the pod
|
||||||
|
stopPod := podmanTest.Podman([]string{"pod", "stop", "foobar"})
|
||||||
|
stopPod.WaitWithDefaultTimeout()
|
||||||
|
Expect(stopPod).Should(Exit(0))
|
||||||
|
startPod := podmanTest.Podman([]string{"pod", "start", "foobar"})
|
||||||
|
startPod.WaitWithDefaultTimeout()
|
||||||
|
Expect(startPod).Should(Exit(0))
|
||||||
|
|
||||||
|
// Check the file again with exec
|
||||||
|
secondCheckLog := podmanTest.Podman([]string{"exec", "-it", verify.OutputToString(), "cat", filename})
|
||||||
|
secondCheckLog.WaitWithDefaultTimeout()
|
||||||
|
Expect(secondCheckLog).Should(Exit(0))
|
||||||
|
|
||||||
|
// Dates should not match
|
||||||
|
Expect(firstResult).ToNot(Equal(secondCheckLog.OutputToString()))
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
Loading…
Reference in New Issue