diff --git a/pkg/kubelet/dockertools/docker.go b/pkg/kubelet/dockertools/docker.go index f3bc5ebacd5..095b90f6723 100644 --- a/pkg/kubelet/dockertools/docker.go +++ b/pkg/kubelet/dockertools/docker.go @@ -94,7 +94,7 @@ func SetContainerNamePrefix(prefix string) { // DockerPuller is an abstract interface for testability. It abstracts image pull operations. type DockerPuller interface { Pull(image string, secrets []v1.Secret) error - IsImagePresent(image string) (bool, error) + IsImagePresent(image string) (string, error) } // dockerPuller is the default implementation of DockerPuller. @@ -241,11 +241,11 @@ func (p dockerPuller) Pull(image string, secrets []v1.Secret) error { err := p.client.PullImage(image, dockertypes.AuthConfig{}, opts) if err == nil { // Sometimes PullImage failed with no error returned. - exist, ierr := p.IsImagePresent(image) + imageRef, ierr := p.IsImagePresent(image) if ierr != nil { glog.Warningf("Failed to inspect image %s: %v", image, ierr) } - if !exist { + if imageRef == "" { return fmt.Errorf("image pull failed for unknown error") } return nil @@ -277,15 +277,23 @@ func (p dockerPuller) Pull(image string, secrets []v1.Secret) error { return utilerrors.NewAggregate(pullErrs) } -func (p dockerPuller) IsImagePresent(image string) (bool, error) { - _, err := p.client.InspectImageByRef(image) +func (p dockerPuller) IsImagePresent(image string) (string, error) { + resp, err := p.client.InspectImageByRef(image) if err == nil { - return true, nil + if resp == nil { + return "", nil + } + + imageRef := resp.ID + if len(resp.RepoDigests) > 0 { + imageRef = resp.RepoDigests[0] + } + return imageRef, nil } if _, ok := err.(imageNotFoundError); ok { - return false, nil + return "", nil } - return false, err + return "", err } // Creates a name which can be reversed to identify both full pod name and container name. diff --git a/pkg/kubelet/dockertools/docker_manager.go b/pkg/kubelet/dockertools/docker_manager.go index f515db814fc..1f386cdf448 100644 --- a/pkg/kubelet/dockertools/docker_manager.go +++ b/pkg/kubelet/dockertools/docker_manager.go @@ -590,6 +590,7 @@ func (dm *DockerManager) runContainer( container *v1.Container, opts *kubecontainer.RunContainerOptions, ref *v1.ObjectReference, + imageRef string, netMode string, ipcMode string, utsMode string, @@ -765,7 +766,7 @@ func (dm *DockerManager) runContainer( Name: containerName, Config: &dockercontainer.Config{ Env: makeEnvList(opts.Envs), - Image: container.Image, + Image: imageRef, WorkingDir: container.WorkingDir, Labels: labels, // Interactive containers: @@ -958,13 +959,37 @@ func (dm *DockerManager) ListImages() ([]kubecontainer.Image, error) { return images, nil } +// GetImageRef returns the image digest if exists, or else returns the image ID. +// It is exported for reusing in dockershim. +func GetImageRef(client DockerInterface, image string) (string, error) { + img, err := client.InspectImageByRef(image) + if err != nil { + return "", err + } + if img == nil { + return "", fmt.Errorf("unable to inspect image %s", image) + } + + // Returns the digest if it exist. + if len(img.RepoDigests) > 0 { + return img.RepoDigests[0], nil + } + + return img.ID, nil +} + // PullImage pulls an image from network to local storage. -func (dm *DockerManager) PullImage(image kubecontainer.ImageSpec, secrets []v1.Secret) error { - return dm.dockerPuller.Pull(image.Image, secrets) +func (dm *DockerManager) PullImage(image kubecontainer.ImageSpec, secrets []v1.Secret) (string, error) { + err := dm.dockerPuller.Pull(image.Image, secrets) + if err != nil { + return "", err + } + + return GetImageRef(dm.client, image.Image) } // IsImagePresent checks whether the container image is already in the local storage. -func (dm *DockerManager) IsImagePresent(image kubecontainer.ImageSpec) (bool, error) { +func (dm *DockerManager) IsImagePresent(image kubecontainer.ImageSpec) (string, error) { return dm.dockerPuller.IsImagePresent(image.Image) } @@ -1683,7 +1708,7 @@ func (dm *DockerManager) applyOOMScoreAdj(pod *v1.Pod, container *v1.Container, // Run a single container from a pod. Returns the docker container ID // If do not need to pass labels, just pass nil. -func (dm *DockerManager) runContainerInPod(pod *v1.Pod, container *v1.Container, netMode, ipcMode, pidMode, podIP string, restartCount int) (kubecontainer.ContainerID, error) { +func (dm *DockerManager) runContainerInPod(pod *v1.Pod, container *v1.Container, netMode, ipcMode, pidMode, podIP, imageRef string, restartCount int) (kubecontainer.ContainerID, error) { start := time.Now() defer func() { metrics.ContainerManagerLatency.WithLabelValues("runContainerInPod").Observe(metrics.SinceInMicroseconds(start)) @@ -1708,7 +1733,7 @@ func (dm *DockerManager) runContainerInPod(pod *v1.Pod, container *v1.Container, oomScoreAdj := dm.calculateOomScoreAdj(pod, container) - id, err := dm.runContainer(pod, container, opts, ref, netMode, ipcMode, utsMode, pidMode, restartCount, oomScoreAdj) + id, err := dm.runContainer(pod, container, opts, ref, imageRef, netMode, ipcMode, utsMode, pidMode, restartCount, oomScoreAdj) if err != nil { return kubecontainer.ContainerID{}, fmt.Errorf("runContainer: %v", err) } @@ -1888,12 +1913,13 @@ func (dm *DockerManager) createPodInfraContainer(pod *v1.Pod) (kubecontainer.Doc // No pod secrets for the infra container. // The message isn't needed for the Infra container - if err, msg := dm.imagePuller.EnsureImageExists(pod, container, nil); err != nil { + imageRef, msg, err := dm.imagePuller.EnsureImageExists(pod, container, nil) + if err != nil { return "", err, msg } // Currently we don't care about restart count of infra container, just set it to 0. - id, err := dm.runContainerInPod(pod, container, netNamespace, getIPCMode(pod), getPidMode(pod), "", 0) + id, err := dm.runContainerInPod(pod, container, netNamespace, getIPCMode(pod), getPidMode(pod), "", imageRef, 0) if err != nil { return "", kubecontainer.ErrRunContainer, err.Error() } @@ -2305,7 +2331,7 @@ func (dm *DockerManager) SyncPod(pod *v1.Pod, _ v1.PodStatus, podStatus *kubecon // tryContainerStart attempts to pull and start the container, returning an error and a reason string if the start // was not successful. func (dm *DockerManager) tryContainerStart(container *v1.Container, pod *v1.Pod, podStatus *kubecontainer.PodStatus, pullSecrets []v1.Secret, namespaceMode, pidMode, podIP string) (err error, reason string) { - err, msg := dm.imagePuller.EnsureImageExists(pod, container, pullSecrets) + imageRef, msg, err := dm.imagePuller.EnsureImageExists(pod, container, pullSecrets) if err != nil { return err, msg } @@ -2331,7 +2357,7 @@ func (dm *DockerManager) tryContainerStart(container *v1.Container, pod *v1.Pod, netMode = namespaceMode } - _, err = dm.runContainerInPod(pod, container, netMode, namespaceMode, pidMode, podIP, restartCount) + _, err = dm.runContainerInPod(pod, container, netMode, namespaceMode, pidMode, podIP, imageRef, restartCount) if err != nil { // TODO(bburns) : Perhaps blacklist a container after N failures? return kubecontainer.ErrRunContainer, err.Error() diff --git a/pkg/kubelet/dockertools/docker_manager_test.go b/pkg/kubelet/dockertools/docker_manager_test.go index eee5f102348..2f22a76bc34 100644 --- a/pkg/kubelet/dockertools/docker_manager_test.go +++ b/pkg/kubelet/dockertools/docker_manager_test.go @@ -112,8 +112,8 @@ func newFakeImageManager() images.ImageManager { return &fakeImageManager{} } -func (m *fakeImageManager) EnsureImageExists(pod *v1.Pod, container *v1.Container, pullSecrets []v1.Secret) (error, string) { - return nil, "" +func (m *fakeImageManager) EnsureImageExists(pod *v1.Pod, container *v1.Container, pullSecrets []v1.Secret) (string, string, error) { + return container.Image, "", nil } func createTestDockerManager(fakeHTTPClient *fakeHTTP, fakeDocker *FakeDockerClient) (*DockerManager, *FakeDockerClient) { @@ -647,9 +647,9 @@ func TestSyncPodCreatesNetAndContainerPullsImage(t *testing.T) { verifyCalls(t, fakeDocker, []string{ // Create pod infra container. - "create", "start", "inspect_container", "inspect_container", + "inspect_image", "create", "start", "inspect_container", "inspect_container", // Create container. - "create", "start", "inspect_container", + "inspect_image", "create", "start", "inspect_container", }) fakeDocker.Lock() diff --git a/pkg/kubelet/dockertools/fake_docker_client.go b/pkg/kubelet/dockertools/fake_docker_client.go index e26b4c2a084..af4c51a9219 100644 --- a/pkg/kubelet/dockertools/fake_docker_client.go +++ b/pkg/kubelet/dockertools/fake_docker_client.go @@ -592,18 +592,18 @@ func (f *FakeDockerPuller) Pull(image string, secrets []v1.Secret) (err error) { return err } -func (f *FakeDockerPuller) IsImagePresent(name string) (bool, error) { +func (f *FakeDockerPuller) IsImagePresent(name string) (string, error) { f.Lock() defer f.Unlock() if f.HasImages == nil { - return true, nil + return name, nil } for _, s := range f.HasImages { if s == name { - return true, nil + return s, nil } } - return false, nil + return "", nil } func (f *FakeDockerClient) ImageHistory(id string) ([]dockertypes.ImageHistory, error) { f.Lock()