mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-03 19:58:17 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			411 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			411 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
Copyright 2014 The Kubernetes 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 dockertools
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"math/rand"
 | 
						|
	"net/http"
 | 
						|
	"path"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	dockerdigest "github.com/docker/distribution/digest"
 | 
						|
	dockerref "github.com/docker/distribution/reference"
 | 
						|
	"github.com/docker/docker/pkg/jsonmessage"
 | 
						|
	dockerapi "github.com/docker/engine-api/client"
 | 
						|
	dockertypes "github.com/docker/engine-api/types"
 | 
						|
	"github.com/golang/glog"
 | 
						|
	"k8s.io/apimachinery/pkg/types"
 | 
						|
	utilerrors "k8s.io/apimachinery/pkg/util/errors"
 | 
						|
	"k8s.io/kubernetes/pkg/api/v1"
 | 
						|
	"k8s.io/kubernetes/pkg/credentialprovider"
 | 
						|
	kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
 | 
						|
	"k8s.io/kubernetes/pkg/kubelet/images"
 | 
						|
	"k8s.io/kubernetes/pkg/kubelet/leaky"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	PodInfraContainerName = leaky.PodInfraContainerName
 | 
						|
	DockerPrefix          = "docker://"
 | 
						|
	DockerPullablePrefix  = "docker-pullable://"
 | 
						|
	LogSuffix             = "log"
 | 
						|
	ext4MaxFileNameLen    = 255
 | 
						|
)
 | 
						|
 | 
						|
// DockerInterface is an abstract interface for testability.  It abstracts the interface of docker client.
 | 
						|
type DockerInterface interface {
 | 
						|
	ListContainers(options dockertypes.ContainerListOptions) ([]dockertypes.Container, error)
 | 
						|
	InspectContainer(id string) (*dockertypes.ContainerJSON, error)
 | 
						|
	CreateContainer(dockertypes.ContainerCreateConfig) (*dockertypes.ContainerCreateResponse, error)
 | 
						|
	StartContainer(id string) error
 | 
						|
	StopContainer(id string, timeout int) error
 | 
						|
	RemoveContainer(id string, opts dockertypes.ContainerRemoveOptions) error
 | 
						|
	InspectImageByRef(imageRef string) (*dockertypes.ImageInspect, error)
 | 
						|
	InspectImageByID(imageID string) (*dockertypes.ImageInspect, error)
 | 
						|
	ListImages(opts dockertypes.ImageListOptions) ([]dockertypes.Image, error)
 | 
						|
	PullImage(image string, auth dockertypes.AuthConfig, opts dockertypes.ImagePullOptions) error
 | 
						|
	RemoveImage(image string, opts dockertypes.ImageRemoveOptions) ([]dockertypes.ImageDelete, error)
 | 
						|
	ImageHistory(id string) ([]dockertypes.ImageHistory, error)
 | 
						|
	Logs(string, dockertypes.ContainerLogsOptions, StreamOptions) error
 | 
						|
	Version() (*dockertypes.Version, error)
 | 
						|
	Info() (*dockertypes.Info, error)
 | 
						|
	CreateExec(string, dockertypes.ExecConfig) (*dockertypes.ContainerExecCreateResponse, error)
 | 
						|
	StartExec(string, dockertypes.ExecStartCheck, StreamOptions) error
 | 
						|
	InspectExec(id string) (*dockertypes.ContainerExecInspect, error)
 | 
						|
	AttachToContainer(string, dockertypes.ContainerAttachOptions, StreamOptions) error
 | 
						|
	ResizeContainerTTY(id string, height, width int) error
 | 
						|
	ResizeExecTTY(id string, height, width int) error
 | 
						|
}
 | 
						|
 | 
						|
// KubeletContainerName encapsulates a pod name and a Kubernetes container name.
 | 
						|
type KubeletContainerName struct {
 | 
						|
	PodFullName   string
 | 
						|
	PodUID        types.UID
 | 
						|
	ContainerName string
 | 
						|
}
 | 
						|
 | 
						|
// containerNamePrefix is used to identify the containers on the node managed by this
 | 
						|
// process.
 | 
						|
var containerNamePrefix = "k8s"
 | 
						|
 | 
						|
// SetContainerNamePrefix allows the container prefix name for this process to be changed.
 | 
						|
// This is intended to support testing and bootstrapping experimentation. It cannot be
 | 
						|
// changed once the Kubelet starts.
 | 
						|
func SetContainerNamePrefix(prefix string) {
 | 
						|
	containerNamePrefix = prefix
 | 
						|
}
 | 
						|
 | 
						|
// DockerPuller is an abstract interface for testability.  It abstracts image pull operations.
 | 
						|
type DockerPuller interface {
 | 
						|
	Pull(image string, secrets []v1.Secret) error
 | 
						|
	GetImageRef(image string) (string, error)
 | 
						|
}
 | 
						|
 | 
						|
// dockerPuller is the default implementation of DockerPuller.
 | 
						|
type dockerPuller struct {
 | 
						|
	client  DockerInterface
 | 
						|
	keyring credentialprovider.DockerKeyring
 | 
						|
}
 | 
						|
 | 
						|
// newDockerPuller creates a new instance of the default implementation of DockerPuller.
 | 
						|
func newDockerPuller(client DockerInterface) DockerPuller {
 | 
						|
	return &dockerPuller{
 | 
						|
		client:  client,
 | 
						|
		keyring: credentialprovider.NewDockerKeyring(),
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func filterHTTPError(err error, image string) error {
 | 
						|
	// docker/docker/pull/11314 prints detailed error info for docker pull.
 | 
						|
	// When it hits 502, it returns a verbose html output including an inline svg,
 | 
						|
	// which makes the output of kubectl get pods much harder to parse.
 | 
						|
	// Here converts such verbose output to a concise one.
 | 
						|
	jerr, ok := err.(*jsonmessage.JSONError)
 | 
						|
	if ok && (jerr.Code == http.StatusBadGateway ||
 | 
						|
		jerr.Code == http.StatusServiceUnavailable ||
 | 
						|
		jerr.Code == http.StatusGatewayTimeout) {
 | 
						|
		glog.V(2).Infof("Pulling image %q failed: %v", image, err)
 | 
						|
		return images.RegistryUnavailable
 | 
						|
	} else {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// matchImageTagOrSHA checks if the given image specifier is a valid image ref,
 | 
						|
// and that it matches the given image. It should fail on things like image IDs
 | 
						|
// (config digests) and other digest-only references, but succeed on image names
 | 
						|
// (`foo`), tag references (`foo:bar`), and manifest digest references
 | 
						|
// (`foo@sha256:xyz`).
 | 
						|
func matchImageTagOrSHA(inspected dockertypes.ImageInspect, image string) bool {
 | 
						|
	// The image string follows the grammar specified here
 | 
						|
	// https://github.com/docker/distribution/blob/master/reference/reference.go#L4
 | 
						|
	named, err := dockerref.ParseNamed(image)
 | 
						|
	if err != nil {
 | 
						|
		glog.V(4).Infof("couldn't parse image reference %q: %v", image, err)
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	_, isTagged := named.(dockerref.Tagged)
 | 
						|
	digest, isDigested := named.(dockerref.Digested)
 | 
						|
	if !isTagged && !isDigested {
 | 
						|
		// No Tag or SHA specified, so just return what we have
 | 
						|
		return true
 | 
						|
	}
 | 
						|
 | 
						|
	if isTagged {
 | 
						|
		// Check the RepoTags for a match.
 | 
						|
		for _, tag := range inspected.RepoTags {
 | 
						|
			// An image name (without the tag/digest) can be [hostname '/'] component ['/' component]*
 | 
						|
			// Because either the RepoTag or the name *may* contain the
 | 
						|
			// hostname or not, we only check for the suffix match.
 | 
						|
			if strings.HasSuffix(image, tag) || strings.HasSuffix(tag, image) {
 | 
						|
				return true
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if isDigested {
 | 
						|
		for _, repoDigest := range inspected.RepoDigests {
 | 
						|
			named, err := dockerref.ParseNamed(repoDigest)
 | 
						|
			if err != nil {
 | 
						|
				glog.V(4).Infof("couldn't parse image RepoDigest reference %q: %v", repoDigest, err)
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			if d, isDigested := named.(dockerref.Digested); isDigested {
 | 
						|
				if digest.Digest().Algorithm().String() == d.Digest().Algorithm().String() &&
 | 
						|
					digest.Digest().Hex() == d.Digest().Hex() {
 | 
						|
					return true
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// process the ID as a digest
 | 
						|
		id, err := dockerdigest.ParseDigest(inspected.ID)
 | 
						|
		if err != nil {
 | 
						|
			glog.V(4).Infof("couldn't parse image ID reference %q: %v", id, err)
 | 
						|
			return false
 | 
						|
		}
 | 
						|
		if digest.Digest().Algorithm().String() == id.Algorithm().String() && digest.Digest().Hex() == id.Hex() {
 | 
						|
			return true
 | 
						|
		}
 | 
						|
	}
 | 
						|
	glog.V(4).Infof("Inspected image (%q) does not match %s", inspected.ID, image)
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
// matchImageIDOnly checks that the given image specifier is a digest-only
 | 
						|
// reference, and that it matches the given image.
 | 
						|
func matchImageIDOnly(inspected dockertypes.ImageInspect, image string) bool {
 | 
						|
	// If the image ref is literally equal to the inspected image's ID,
 | 
						|
	// just return true here (this might be the case for Docker 1.9,
 | 
						|
	// where we won't have a digest for the ID)
 | 
						|
	if inspected.ID == image {
 | 
						|
		return true
 | 
						|
	}
 | 
						|
 | 
						|
	// Otherwise, we should try actual parsing to be more correct
 | 
						|
	ref, err := dockerref.Parse(image)
 | 
						|
	if err != nil {
 | 
						|
		glog.V(4).Infof("couldn't parse image reference %q: %v", image, err)
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	digest, isDigested := ref.(dockerref.Digested)
 | 
						|
	if !isDigested {
 | 
						|
		glog.V(4).Infof("the image reference %q was not a digest reference")
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	id, err := dockerdigest.ParseDigest(inspected.ID)
 | 
						|
	if err != nil {
 | 
						|
		glog.V(4).Infof("couldn't parse image ID reference %q: %v", id, err)
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	if digest.Digest().Algorithm().String() == id.Algorithm().String() && digest.Digest().Hex() == id.Hex() {
 | 
						|
		return true
 | 
						|
	}
 | 
						|
 | 
						|
	glog.V(4).Infof("The reference %s does not directly refer to the given image's ID (%q)", image, inspected.ID)
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
func (p dockerPuller) Pull(image string, secrets []v1.Secret) error {
 | 
						|
	keyring, err := credentialprovider.MakeDockerKeyring(secrets, p.keyring)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	// The only used image pull option RegistryAuth will be set in kube_docker_client
 | 
						|
	opts := dockertypes.ImagePullOptions{}
 | 
						|
 | 
						|
	creds, haveCredentials := keyring.Lookup(image)
 | 
						|
	if !haveCredentials {
 | 
						|
		glog.V(1).Infof("Pulling image %s without credentials", image)
 | 
						|
 | 
						|
		err := p.client.PullImage(image, dockertypes.AuthConfig{}, opts)
 | 
						|
		if err == nil {
 | 
						|
			// Sometimes PullImage failed with no error returned.
 | 
						|
			imageRef, ierr := p.GetImageRef(image)
 | 
						|
			if ierr != nil {
 | 
						|
				glog.Warningf("Failed to inspect image %s: %v", image, ierr)
 | 
						|
			}
 | 
						|
			if imageRef == "" {
 | 
						|
				return fmt.Errorf("image pull failed for unknown error")
 | 
						|
			}
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		// Image spec: [<registry>/]<repository>/<image>[:<version] so we count '/'
 | 
						|
		explicitRegistry := (strings.Count(image, "/") == 2)
 | 
						|
		// Hack, look for a private registry, and decorate the error with the lack of
 | 
						|
		// credentials.  This is heuristic, and really probably could be done better
 | 
						|
		// by talking to the registry API directly from the kubelet here.
 | 
						|
		if explicitRegistry {
 | 
						|
			return fmt.Errorf("image pull failed for %s, this may be because there are no credentials on this request.  details: (%v)", image, err)
 | 
						|
		}
 | 
						|
 | 
						|
		return filterHTTPError(err, image)
 | 
						|
	}
 | 
						|
 | 
						|
	var pullErrs []error
 | 
						|
	for _, currentCreds := range creds {
 | 
						|
		err = p.client.PullImage(image, credentialprovider.LazyProvide(currentCreds), opts)
 | 
						|
		// If there was no error, return success
 | 
						|
		if err == nil {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		pullErrs = append(pullErrs, filterHTTPError(err, image))
 | 
						|
	}
 | 
						|
 | 
						|
	return utilerrors.NewAggregate(pullErrs)
 | 
						|
}
 | 
						|
 | 
						|
func (p dockerPuller) GetImageRef(image string) (string, error) {
 | 
						|
	resp, err := p.client.InspectImageByRef(image)
 | 
						|
	if err == nil {
 | 
						|
		if resp == nil {
 | 
						|
			return "", nil
 | 
						|
		}
 | 
						|
 | 
						|
		imageRef := resp.ID
 | 
						|
		if len(resp.RepoDigests) > 0 {
 | 
						|
			imageRef = resp.RepoDigests[0]
 | 
						|
		}
 | 
						|
		return imageRef, nil
 | 
						|
	}
 | 
						|
	if IsImageNotFoundError(err) {
 | 
						|
		return "", nil
 | 
						|
	}
 | 
						|
	return "", err
 | 
						|
}
 | 
						|
 | 
						|
// Creates a name which can be reversed to identify both full pod name and container name.
 | 
						|
// This function returns stable name, unique name and a unique id.
 | 
						|
// Although rand.Uint32() is not really unique, but it's enough for us because error will
 | 
						|
// only occur when instances of the same container in the same pod have the same UID. The
 | 
						|
// chance is really slim.
 | 
						|
func BuildDockerName(dockerName KubeletContainerName, container *v1.Container) (string, string, string) {
 | 
						|
	containerName := dockerName.ContainerName + "." + strconv.FormatUint(kubecontainer.HashContainerLegacy(container), 16)
 | 
						|
	stableName := fmt.Sprintf("%s_%s_%s_%s",
 | 
						|
		containerNamePrefix,
 | 
						|
		containerName,
 | 
						|
		dockerName.PodFullName,
 | 
						|
		dockerName.PodUID)
 | 
						|
	UID := fmt.Sprintf("%08x", rand.Uint32())
 | 
						|
	return stableName, fmt.Sprintf("%s_%s", stableName, UID), UID
 | 
						|
}
 | 
						|
 | 
						|
// Unpacks a container name, returning the pod full name and container name we would have used to
 | 
						|
// construct the docker name. If we are unable to parse the name, an error is returned.
 | 
						|
func ParseDockerName(name string) (dockerName *KubeletContainerName, hash uint64, err error) {
 | 
						|
	// For some reason docker appears to be appending '/' to names.
 | 
						|
	// If it's there, strip it.
 | 
						|
	name = strings.TrimPrefix(name, "/")
 | 
						|
	parts := strings.Split(name, "_")
 | 
						|
	if len(parts) == 0 || parts[0] != containerNamePrefix {
 | 
						|
		err = fmt.Errorf("failed to parse Docker container name %q into parts", name)
 | 
						|
		return nil, 0, err
 | 
						|
	}
 | 
						|
	if len(parts) < 6 {
 | 
						|
		// We have at least 5 fields.  We may have more in the future.
 | 
						|
		// Anything with less fields than this is not something we can
 | 
						|
		// manage.
 | 
						|
		glog.Warningf("found a container with the %q prefix, but too few fields (%d): %q", containerNamePrefix, len(parts), name)
 | 
						|
		err = fmt.Errorf("Docker container name %q has less parts than expected %v", name, parts)
 | 
						|
		return nil, 0, err
 | 
						|
	}
 | 
						|
 | 
						|
	nameParts := strings.Split(parts[1], ".")
 | 
						|
	containerName := nameParts[0]
 | 
						|
	if len(nameParts) > 1 {
 | 
						|
		hash, err = strconv.ParseUint(nameParts[1], 16, 32)
 | 
						|
		if err != nil {
 | 
						|
			glog.Warningf("invalid container hash %q in container %q", nameParts[1], name)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	podFullName := parts[2] + "_" + parts[3]
 | 
						|
	podUID := types.UID(parts[4])
 | 
						|
 | 
						|
	return &KubeletContainerName{podFullName, podUID, containerName}, hash, nil
 | 
						|
}
 | 
						|
 | 
						|
func LogSymlink(containerLogsDir, podFullName, containerName, dockerId string) string {
 | 
						|
	suffix := fmt.Sprintf(".%s", LogSuffix)
 | 
						|
	logPath := fmt.Sprintf("%s_%s-%s", podFullName, containerName, dockerId)
 | 
						|
	// Length of a filename cannot exceed 255 characters in ext4 on Linux.
 | 
						|
	if len(logPath) > ext4MaxFileNameLen-len(suffix) {
 | 
						|
		logPath = logPath[:ext4MaxFileNameLen-len(suffix)]
 | 
						|
	}
 | 
						|
	return path.Join(containerLogsDir, logPath+suffix)
 | 
						|
}
 | 
						|
 | 
						|
// Get a *dockerapi.Client, either using the endpoint passed in, or using
 | 
						|
// DOCKER_HOST, DOCKER_TLS_VERIFY, and DOCKER_CERT path per their spec
 | 
						|
func getDockerClient(dockerEndpoint string) (*dockerapi.Client, error) {
 | 
						|
	if len(dockerEndpoint) > 0 {
 | 
						|
		glog.Infof("Connecting to docker on %s", dockerEndpoint)
 | 
						|
		return dockerapi.NewClient(dockerEndpoint, "", nil, nil)
 | 
						|
	}
 | 
						|
	return dockerapi.NewEnvClient()
 | 
						|
}
 | 
						|
 | 
						|
// ConnectToDockerOrDie creates docker client connecting to docker daemon.
 | 
						|
// If the endpoint passed in is "fake://", a fake docker client
 | 
						|
// will be returned. The program exits if error occurs. The requestTimeout
 | 
						|
// is the timeout for docker requests. If timeout is exceeded, the request
 | 
						|
// will be cancelled and throw out an error. If requestTimeout is 0, a default
 | 
						|
// value will be applied.
 | 
						|
func ConnectToDockerOrDie(dockerEndpoint string, requestTimeout, imagePullProgressDeadline time.Duration) DockerInterface {
 | 
						|
	if dockerEndpoint == "fake://" {
 | 
						|
		return NewFakeDockerClient()
 | 
						|
	}
 | 
						|
	client, err := getDockerClient(dockerEndpoint)
 | 
						|
	if err != nil {
 | 
						|
		glog.Fatalf("Couldn't connect to docker: %v", err)
 | 
						|
	}
 | 
						|
	glog.Infof("Start docker client with request timeout=%v", requestTimeout)
 | 
						|
	return newKubeDockerClient(client, requestTimeout, imagePullProgressDeadline)
 | 
						|
}
 | 
						|
 | 
						|
// GetKubeletDockerContainers lists all container or just the running ones.
 | 
						|
// Returns a list of docker containers that we manage
 | 
						|
func GetKubeletDockerContainers(client DockerInterface, allContainers bool) ([]*dockertypes.Container, error) {
 | 
						|
	result := []*dockertypes.Container{}
 | 
						|
	containers, err := client.ListContainers(dockertypes.ContainerListOptions{All: allContainers})
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	for i := range containers {
 | 
						|
		container := &containers[i]
 | 
						|
		if len(container.Names) == 0 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		// Skip containers that we didn't create to allow users to manually
 | 
						|
		// spin up their own containers if they want.
 | 
						|
		if !strings.HasPrefix(container.Names[0], "/"+containerNamePrefix+"_") {
 | 
						|
			glog.V(5).Infof("Docker Container: %s is not managed by kubelet.", container.Names[0])
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		result = append(result, container)
 | 
						|
	}
 | 
						|
	return result, nil
 | 
						|
}
 |