Merge pull request #991 from Random-Liu/remove-container-lifecycle-image-dependency
Remove container lifecycle image dependency
This commit is contained in:
commit
ec6a1eab11
75
integration/container_without_image_ref_test.go
Normal file
75
integration/container_without_image_ref_test.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test container lifecycle can work without image references.
|
||||||
|
func TestContainerLifecycleWithoutImageRef(t *testing.T) {
|
||||||
|
t.Log("Create a sandbox")
|
||||||
|
sbConfig := PodSandboxConfig("sandbox", "container-lifecycle-without-image-ref")
|
||||||
|
sb, err := runtimeService.RunPodSandbox(sbConfig, *runtimeHandler)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer func() {
|
||||||
|
assert.NoError(t, runtimeService.StopPodSandbox(sb))
|
||||||
|
assert.NoError(t, runtimeService.RemovePodSandbox(sb))
|
||||||
|
}()
|
||||||
|
|
||||||
|
const (
|
||||||
|
testImage = "busybox"
|
||||||
|
containerName = "test-container"
|
||||||
|
)
|
||||||
|
t.Log("Pull test image")
|
||||||
|
img, err := imageService.PullImage(&runtime.ImageSpec{Image: testImage}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer func() {
|
||||||
|
assert.NoError(t, imageService.RemoveImage(&runtime.ImageSpec{Image: img}))
|
||||||
|
}()
|
||||||
|
|
||||||
|
t.Log("Create test container")
|
||||||
|
cnConfig := ContainerConfig(
|
||||||
|
containerName,
|
||||||
|
testImage,
|
||||||
|
WithCommand("sleep", "1000"),
|
||||||
|
)
|
||||||
|
cn, err := runtimeService.CreateContainer(sb, cnConfig, sbConfig)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, runtimeService.StartContainer(cn))
|
||||||
|
|
||||||
|
t.Log("Remove test image")
|
||||||
|
assert.NoError(t, imageService.RemoveImage(&runtime.ImageSpec{Image: img}))
|
||||||
|
|
||||||
|
t.Log("Container status should be running")
|
||||||
|
status, err := runtimeService.ContainerStatus(cn)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, status.GetState(), runtime.ContainerState_CONTAINER_RUNNING)
|
||||||
|
|
||||||
|
t.Logf("Stop container")
|
||||||
|
err = runtimeService.StopContainer(cn, 1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
t.Log("Container status should be exited")
|
||||||
|
status, err = runtimeService.ContainerStatus(cn)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, status.GetState(), runtime.ContainerState_CONTAINER_EXITED)
|
||||||
|
}
|
@ -187,6 +187,7 @@ func (c *criService) CreateContainer(ctx context.Context, r *runtime.CreateConta
|
|||||||
opts = append(opts, customopts.WithVolumes(mountMap))
|
opts = append(opts, customopts.WithVolumes(mountMap))
|
||||||
}
|
}
|
||||||
meta.ImageRef = image.ID
|
meta.ImageRef = image.ID
|
||||||
|
meta.StopSignal = image.ImageSpec.Config.StopSignal
|
||||||
|
|
||||||
// Get container log path.
|
// Get container log path.
|
||||||
if config.GetLogPath() != "" {
|
if config.GetLogPath() != "" {
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"
|
runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"
|
||||||
|
|
||||||
|
"github.com/containerd/cri/pkg/store"
|
||||||
containerstore "github.com/containerd/cri/pkg/store/container"
|
containerstore "github.com/containerd/cri/pkg/store/container"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -43,8 +44,10 @@ func (c *criService) ContainerStatus(ctx context.Context, r *runtime.ContainerSt
|
|||||||
imageRef := container.ImageRef
|
imageRef := container.ImageRef
|
||||||
image, err := c.imageStore.Get(imageRef)
|
image, err := c.imageStore.Get(imageRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if err != store.ErrNotExist {
|
||||||
return nil, errors.Wrapf(err, "failed to get image %q", imageRef)
|
return nil, errors.Wrapf(err, "failed to get image %q", imageRef)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
repoTags, repoDigests := parseImageReferences(image.References)
|
repoTags, repoDigests := parseImageReferences(image.References)
|
||||||
if len(repoTags) > 0 {
|
if len(repoTags) > 0 {
|
||||||
// Based on current behavior of dockershim, this field should be
|
// Based on current behavior of dockershim, this field should be
|
||||||
@ -55,6 +58,7 @@ func (c *criService) ContainerStatus(ctx context.Context, r *runtime.ContainerSt
|
|||||||
// Based on the CRI definition, this field will be consumed by user.
|
// Based on the CRI definition, this field will be consumed by user.
|
||||||
imageRef = repoDigests[0]
|
imageRef = repoDigests[0]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
status := toCRIContainerStatus(container, spec, imageRef)
|
status := toCRIContainerStatus(container, spec, imageRef)
|
||||||
info, err := toCRIContainerInfo(ctx, container, r.GetVerbose())
|
info, err := toCRIContainerInfo(ctx, container, r.GetVerbose())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"
|
runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"
|
||||||
|
|
||||||
|
"github.com/containerd/cri/pkg/store"
|
||||||
containerstore "github.com/containerd/cri/pkg/store/container"
|
containerstore "github.com/containerd/cri/pkg/store/container"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -76,24 +77,36 @@ func (c *criService) stopContainer(ctx context.Context, container containerstore
|
|||||||
// We only need to kill the task. The event handler will Delete the
|
// We only need to kill the task. The event handler will Delete the
|
||||||
// task from containerd after it handles the Exited event.
|
// task from containerd after it handles the Exited event.
|
||||||
if timeout > 0 {
|
if timeout > 0 {
|
||||||
stopSignal := unix.SIGTERM
|
stopSignal := "SIGTERM"
|
||||||
|
if container.StopSignal != "" {
|
||||||
|
stopSignal = container.StopSignal
|
||||||
|
} else {
|
||||||
|
// The image may have been deleted, and the `StopSignal` field is
|
||||||
|
// just introduced to handle that.
|
||||||
|
// However, for containers created before the `StopSignal` field is
|
||||||
|
// introduced, still try to get the stop signal from the image config.
|
||||||
|
// If the image has been deleted, logging an error and using the
|
||||||
|
// default SIGTERM is still better than returning error and leaving
|
||||||
|
// the container unstoppable. (See issue #990)
|
||||||
|
// TODO(random-liu): Remove this logic when containerd 1.2 is deprecated.
|
||||||
image, err := c.imageStore.Get(container.ImageRef)
|
image, err := c.imageStore.Get(container.ImageRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// NOTE(random-liu): It's possible that the container is stopped,
|
if err != store.ErrNotExist {
|
||||||
// deleted and image is garbage collected before this point. However,
|
return errors.Wrapf(err, "failed to get image %q", container.ImageRef)
|
||||||
// the chance is really slim, even it happens, it's still fine to return
|
|
||||||
// an error here.
|
|
||||||
return errors.Wrapf(err, "failed to get image metadata %q", container.ImageRef)
|
|
||||||
}
|
}
|
||||||
|
logrus.Warningf("Image %q not found, stop container with signal %q", container.ImageRef, stopSignal)
|
||||||
|
} else {
|
||||||
if image.ImageSpec.Config.StopSignal != "" {
|
if image.ImageSpec.Config.StopSignal != "" {
|
||||||
stopSignal, err = signal.ParseSignal(image.ImageSpec.Config.StopSignal)
|
stopSignal = image.ImageSpec.Config.StopSignal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sig, err := signal.ParseSignal(stopSignal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "failed to parse stop signal %q",
|
return errors.Wrapf(err, "failed to parse stop signal %q", stopSignal)
|
||||||
image.ImageSpec.Config.StopSignal)
|
|
||||||
}
|
}
|
||||||
}
|
logrus.Infof("Stop container %q with signal %v", id, sig)
|
||||||
logrus.Infof("Stop container %q with signal %v", id, stopSignal)
|
if err = task.Kill(ctx, sig); err != nil && !errdefs.IsNotFound(err) {
|
||||||
if err = task.Kill(ctx, stopSignal); err != nil && !errdefs.IsNotFound(err) {
|
|
||||||
return errors.Wrapf(err, "failed to stop container %q", id)
|
return errors.Wrapf(err, "failed to stop container %q", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,6 +40,7 @@ func TestContainerStore(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
ImageRef: "TestImage-1",
|
ImageRef: "TestImage-1",
|
||||||
|
StopSignal: "SIGTERM",
|
||||||
LogPath: "/test/log/path/1",
|
LogPath: "/test/log/path/1",
|
||||||
},
|
},
|
||||||
"2abcd": {
|
"2abcd": {
|
||||||
@ -52,6 +53,7 @@ func TestContainerStore(t *testing.T) {
|
|||||||
Attempt: 2,
|
Attempt: 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
StopSignal: "SIGTERM",
|
||||||
ImageRef: "TestImage-2",
|
ImageRef: "TestImage-2",
|
||||||
LogPath: "/test/log/path/2",
|
LogPath: "/test/log/path/2",
|
||||||
},
|
},
|
||||||
@ -65,6 +67,7 @@ func TestContainerStore(t *testing.T) {
|
|||||||
Attempt: 3,
|
Attempt: 3,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
StopSignal: "SIGTERM",
|
||||||
ImageRef: "TestImage-3",
|
ImageRef: "TestImage-3",
|
||||||
LogPath: "/test/log/path/3",
|
LogPath: "/test/log/path/3",
|
||||||
},
|
},
|
||||||
@ -78,6 +81,7 @@ func TestContainerStore(t *testing.T) {
|
|||||||
Attempt: 1,
|
Attempt: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
StopSignal: "SIGTERM",
|
||||||
ImageRef: "TestImage-4abcd",
|
ImageRef: "TestImage-4abcd",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -183,6 +187,7 @@ func TestWithContainerIO(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
ImageRef: "TestImage-1",
|
ImageRef: "TestImage-1",
|
||||||
|
StopSignal: "SIGTERM",
|
||||||
LogPath: "/test/log/path",
|
LogPath: "/test/log/path",
|
||||||
}
|
}
|
||||||
status := Status{
|
status := Status{
|
||||||
|
@ -58,6 +58,9 @@ type Metadata struct {
|
|||||||
ImageRef string
|
ImageRef string
|
||||||
// LogPath is the container log path.
|
// LogPath is the container log path.
|
||||||
LogPath string
|
LogPath string
|
||||||
|
// StopSignal is the system call signal that will be sent to the container to exit.
|
||||||
|
// TODO(random-liu): Add integration test for stop signal.
|
||||||
|
StopSignal string
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON encodes Metadata into bytes in json format.
|
// MarshalJSON encodes Metadata into bytes in json format.
|
||||||
|
Loading…
Reference in New Issue
Block a user