From 4c456015b6ebb573cd0471f1673ded2f06980888 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Thu, 25 Sep 2014 21:53:17 -0700 Subject: [PATCH] Add the ability to turn off image pulling. --- pkg/api/types.go | 36 +++++++++++++++++++ pkg/api/v1beta1/types.go | 14 ++++++++ pkg/api/v1beta2/types.go | 14 ++++++++ pkg/api/v1beta3/types.go | 14 ++++++++ pkg/kubelet/dockertools/docker.go | 20 +++++++++++ pkg/kubelet/dockertools/fake_docker_client.go | 8 +++++ pkg/kubelet/kubelet.go | 15 ++++++-- 7 files changed, 118 insertions(+), 3 deletions(-) diff --git a/pkg/api/types.go b/pkg/api/types.go index 1d9a41e8051..616400e84e0 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -17,6 +17,8 @@ limitations under the License. package api import ( + "strings" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/fsouza/go-dockerclient" ) @@ -173,6 +175,38 @@ type LivenessProbe struct { InitialDelaySeconds int64 `yaml:"initialDelaySeconds,omitempty" json:"initialDelaySeconds,omitempty"` } +// PullPolicy describes a policy for if/when to pull a container image +type PullPolicy string + +const ( + // Always attempt to pull the latest image. Container will fail If the pull fails. + PullAlways PullPolicy = "PullAlways" + // Never pull an image, only use a local image. Container will fail if the image isn't present + PullNever PullPolicy = "PullNever" + // Pull if the image isn't present on disk. Container will fail if the image isn't present and the pull fails. + PullIfNotPresent PullPolicy = "PullIfNotPresent" +) + +func IsPullAlways(p PullPolicy) bool { + // Default to pull always + if len(p) == 0 { + return true + } + return pullPoliciesEqual(p, PullAlways) +} + +func IsPullNever(p PullPolicy) bool { + return pullPoliciesEqual(p, PullNever) +} + +func IsPullIfNotPresent(p PullPolicy) bool { + return pullPoliciesEqual(p, PullIfNotPresent) +} + +func pullPoliciesEqual(p1, p2 PullPolicy) bool { + return strings.ToLower(string(p1)) == strings.ToLower(string(p2)) +} + // Container represents a single container that is expected to be run on the host. type Container struct { // Required: This must be a DNS_LABEL. Each container in a pod must @@ -195,6 +229,8 @@ type Container struct { Lifecycle *Lifecycle `yaml:"lifecycle,omitempty" json:"lifecycle,omitempty"` // Optional: Default to false. Privileged bool `json:"privileged,omitempty" yaml:"privileged,omitempty"` + // Optional: Policy for pulling images for this container + ImagePullPolicy PullPolicy `json:"imagePullPolicy" yaml:"imagePullPolicy"` } // Handler defines a specific action that should be taken diff --git a/pkg/api/v1beta1/types.go b/pkg/api/v1beta1/types.go index 61277c3e801..9bd94917d8a 100644 --- a/pkg/api/v1beta1/types.go +++ b/pkg/api/v1beta1/types.go @@ -183,6 +183,18 @@ type LivenessProbe struct { InitialDelaySeconds int64 `yaml:"initialDelaySeconds,omitempty" json:"initialDelaySeconds,omitempty"` } +// PullPolicy describes a policy for if/when to pull a container image +type PullPolicy string + +const ( + // Always attempt to pull the latest image. Container will fail If the pull fails. + PullAlways PullPolicy = "PullAlways" + // Never pull an image, only use a local image. Container will fail if the image isn't present + PullNever PullPolicy = "PullNever" + // Pull if the image isn't present on disk. Container will fail if the image isn't present and the pull fails. + PullIfNotPresent PullPolicy = "PullIfNotPresent" +) + // Container represents a single container that is expected to be run on the host. type Container struct { // Required: This must be a DNS_LABEL. Each container in a pod must @@ -205,6 +217,8 @@ type Container struct { Lifecycle *Lifecycle `yaml:"lifecycle,omitempty" json:"lifecycle,omitempty"` // Optional: Default to false. Privileged bool `json:"privileged,omitempty" yaml:"privileged,omitempty"` + // Optional: Policy for pulling images for this container + ImagePullPolicy PullPolicy `json:"imagePullPolicy" yaml:"imagePullPolicy"` } // Handler defines a specific action that should be taken diff --git a/pkg/api/v1beta2/types.go b/pkg/api/v1beta2/types.go index 61d066dd4f7..92dd6be708b 100644 --- a/pkg/api/v1beta2/types.go +++ b/pkg/api/v1beta2/types.go @@ -182,6 +182,18 @@ type LivenessProbe struct { InitialDelaySeconds int64 `yaml:"initialDelaySeconds,omitempty" json:"initialDelaySeconds,omitempty"` } +// PullPolicy describes a policy for if/when to pull a container image +type PullPolicy string + +const ( + // Always attempt to pull the latest image. Container will fail If the pull fails. + PullAlways PullPolicy = "PullAlways" + // Never pull an image, only use a local image. Container will fail if the image isn't present + PullNever PullPolicy = "PullNever" + // Pull if the image isn't present on disk. Container will fail if the image isn't present and the pull fails. + PullIfNotPresent PullPolicy = "PullIfNotPresent" +) + // Container represents a single container that is expected to be run on the host. type Container struct { // Required: This must be a DNS_LABEL. Each container in a pod must @@ -204,6 +216,8 @@ type Container struct { Lifecycle *Lifecycle `yaml:"lifecycle,omitempty" json:"lifecycle,omitempty"` // Optional: Default to false. Privileged bool `json:"privileged,omitempty" yaml:"privileged,omitempty"` + // Optional: Policy for pulling images for this container + ImagePullPolicy PullPolicy `json:"imagePullPolicy" yaml:"imagePullPolicy"` } // Handler defines a specific action that should be taken diff --git a/pkg/api/v1beta3/types.go b/pkg/api/v1beta3/types.go index 661bc855749..d25263cc2ef 100644 --- a/pkg/api/v1beta3/types.go +++ b/pkg/api/v1beta3/types.go @@ -173,6 +173,18 @@ type LivenessProbe struct { InitialDelaySeconds int64 `yaml:"initialDelaySeconds,omitempty" json:"initialDelaySeconds,omitempty"` } +// PullPolicy describes a policy for if/when to pull a container image +type PullPolicy string + +const ( + // Always attempt to pull the latest image. Container will fail If the pull fails. + PullAlways PullPolicy = "PullAlways" + // Never pull an image, only use a local image. Container will fail if the image isn't present + PullNever PullPolicy = "PullNever" + // Pull if the image isn't present on disk. Container will fail if the image isn't present and the pull fails. + PullIfNotPresent PullPolicy = "PullIfNotPresent" +) + // Container represents a single container that is expected to be run on the host. type Container struct { // Required: This must be a DNS_LABEL. Each container in a pod must @@ -195,6 +207,8 @@ type Container struct { Lifecycle *Lifecycle `yaml:"lifecycle,omitempty" json:"lifecycle,omitempty"` // Optional: Default to false. Privileged bool `json:"privileged,omitempty" yaml:"privileged,omitempty"` + // Optional: Policy for pulling images for this container + ImagePullPolicy PullPolicy `json:"imagePullPolicy" yaml:"imagePullPolicy"` } // Handler defines a specific action that should be taken diff --git a/pkg/kubelet/dockertools/docker.go b/pkg/kubelet/dockertools/docker.go index 2f09929d71e..ba521b30041 100644 --- a/pkg/kubelet/dockertools/docker.go +++ b/pkg/kubelet/dockertools/docker.go @@ -47,6 +47,7 @@ type DockerInterface interface { CreateContainer(docker.CreateContainerOptions) (*docker.Container, error) StartContainer(id string, hostConfig *docker.HostConfig) error StopContainer(id string, timeout uint) error + InspectImage(image string) (*docker.Image, error) PullImage(opts docker.PullImageOptions, auth docker.AuthConfiguration) error Logs(opts docker.LogsOptions) error } @@ -57,6 +58,7 @@ type DockerID string // DockerPuller is an abstract interface for testability. It abstracts image pull operations. type DockerPuller interface { Pull(image string) error + IsImagePresent(image string) (bool, error) } // dockerPuller is the default implementation of DockerPuller. @@ -148,6 +150,24 @@ func (p throttledDockerPuller) Pull(image string) error { return fmt.Errorf("pull QPS exceeded.") } +func (p dockerPuller) IsImagePresent(name string) (bool, error) { + image, _ := parseImageName(name) + _, err := p.client.InspectImage(image) + if err == nil { + return true, nil + } + // This is super brittle, but its the best we got. + // TODO: Land code in the docker client to use docker.Error here instead. + if err.Error() == "no such image" { + return false, nil + } + return false, err +} + +func (p throttledDockerPuller) IsImagePresent(name string) (bool, error) { + return p.puller.IsImagePresent(name) +} + // DockerContainers is a map of containers type DockerContainers map[DockerID]*docker.APIContainers diff --git a/pkg/kubelet/dockertools/fake_docker_client.go b/pkg/kubelet/dockertools/fake_docker_client.go index 2c222001c9b..df636010010 100644 --- a/pkg/kubelet/dockertools/fake_docker_client.go +++ b/pkg/kubelet/dockertools/fake_docker_client.go @@ -130,6 +130,10 @@ func (f *FakeDockerClient) PullImage(opts docker.PullImageOptions, auth docker.A return f.Err } +func (f *FakeDockerClient) InspectImage(name string) (*docker.Image, error) { + return nil, f.Err +} + // FakeDockerPuller is a stub implementation of DockerPuller. type FakeDockerPuller struct { sync.Mutex @@ -153,3 +157,7 @@ func (f *FakeDockerPuller) Pull(image string) (err error) { } return err } + +func (f *FakeDockerPuller) IsImagePresent(name string) (bool, error) { + return true, nil +} diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index bee489f3d55..7b64a473e4a 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -528,9 +528,18 @@ func (kl *Kubelet) syncPod(pod *Pod, dockerContainers dockertools.DockerContaine } glog.V(3).Infof("Container with name %s--%s--%s doesn't exist, creating %#v", podFullName, uuid, container.Name, container) - if err := kl.dockerPuller.Pull(container.Image); err != nil { - glog.Errorf("Failed to pull image %s: %v skipping pod %s container %s.", container.Image, err, podFullName, container.Name) - continue + if !api.IsPullNever(container.ImagePullPolicy) { + present, err := kl.dockerPuller.IsImagePresent(container.Image) + if err != nil { + glog.Errorf("Failed to inspect image: %s: %#v skipping pod %s container %s", container.Image, err, podFullName, container.Name) + continue + } + if api.IsPullAlways(container.ImagePullPolicy) || !present { + if err := kl.dockerPuller.Pull(container.Image); err != nil { + glog.Errorf("Failed to pull image %s: %v skipping pod %s container %s.", container.Image, err, podFullName, container.Name) + continue + } + } } // TODO(dawnchen): Check RestartPolicy.DelaySeconds before restart a container containerID, err := kl.runContainer(pod, &container, podVolumes, "container:"+string(netID))