diff --git a/pkg/api/types.go b/pkg/api/types.go index fc3637f1c31..47513640f80 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" ) @@ -181,6 +183,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 @@ -203,6 +237,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 cc6addfdc49..50654307ab6 100644 --- a/pkg/api/v1beta1/types.go +++ b/pkg/api/v1beta1/types.go @@ -191,6 +191,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 @@ -213,6 +225,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 359921f1ed0..837dfcd585a 100644 --- a/pkg/api/v1beta2/types.go +++ b/pkg/api/v1beta2/types.go @@ -190,6 +190,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 @@ -212,6 +224,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 9dd609499fa..26a0f1f5874 100644 --- a/pkg/api/v1beta3/types.go +++ b/pkg/api/v1beta3/types.go @@ -226,6 +226,18 @@ type LivenessProbe struct { InitialDelaySeconds int64 `json:"initialDelaySeconds,omitempty" yaml:"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 @@ -248,6 +260,8 @@ type Container struct { Lifecycle *Lifecycle `json:"lifecycle,omitempty" yaml:"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 5814c0e3206..13c4f3d12eb 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 e6d1ea54d04..c5ed18ac84d 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))