2016-07-22 21:24:05 +00:00
|
|
|
/*
|
|
|
|
Copyright 2016 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 dockershim
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2016-11-12 00:09:23 +00:00
|
|
|
"regexp"
|
2016-07-25 23:25:36 +00:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
2016-07-22 21:24:05 +00:00
|
|
|
|
2017-02-03 02:28:19 +00:00
|
|
|
"github.com/blang/semver"
|
2016-07-25 23:25:36 +00:00
|
|
|
dockertypes "github.com/docker/engine-api/types"
|
|
|
|
dockerfilters "github.com/docker/engine-api/types/filters"
|
|
|
|
dockernat "github.com/docker/go-connections/nat"
|
|
|
|
"github.com/golang/glog"
|
|
|
|
|
2016-11-18 20:50:58 +00:00
|
|
|
"k8s.io/kubernetes/pkg/api/v1"
|
2016-11-30 07:27:27 +00:00
|
|
|
runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
2016-09-26 07:46:29 +00:00
|
|
|
"k8s.io/kubernetes/pkg/kubelet/dockertools"
|
2016-10-01 00:08:32 +00:00
|
|
|
"k8s.io/kubernetes/pkg/kubelet/types"
|
2016-07-22 21:24:05 +00:00
|
|
|
)
|
|
|
|
|
2016-09-15 20:08:51 +00:00
|
|
|
const (
|
|
|
|
annotationPrefix = "annotation."
|
|
|
|
)
|
|
|
|
|
2016-11-12 00:09:23 +00:00
|
|
|
var (
|
2017-01-26 23:17:56 +00:00
|
|
|
conflictRE = regexp.MustCompile(`Conflict. (?:.)+ is already in use by container ([0-9a-z]+)`)
|
2016-07-22 21:24:05 +00:00
|
|
|
|
2017-02-03 02:28:19 +00:00
|
|
|
// Docker changes the security option separator from ':' to '=' in the 1.23
|
|
|
|
// API version.
|
|
|
|
optsSeparatorChangeVersion = semver.MustParse(dockertools.SecurityOptSeparatorChangeVersion)
|
|
|
|
)
|
2016-07-22 21:24:05 +00:00
|
|
|
|
|
|
|
// generateEnvList converts KeyValue list to a list of strings, in the form of
|
|
|
|
// '<key>=<value>', which can be understood by docker.
|
2016-11-30 07:27:27 +00:00
|
|
|
func generateEnvList(envs []*runtimeapi.KeyValue) (result []string) {
|
2016-07-22 21:24:05 +00:00
|
|
|
for _, env := range envs {
|
2017-01-20 01:55:37 +00:00
|
|
|
result = append(result, fmt.Sprintf("%s=%s", env.Key, env.Value))
|
2016-07-22 21:24:05 +00:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-09-15 20:08:51 +00:00
|
|
|
// makeLabels converts annotations to labels and merge them with the given
|
|
|
|
// labels. This is necessary because docker does not support annotations;
|
|
|
|
// we *fake* annotations using labels. Note that docker labels are not
|
|
|
|
// updatable.
|
2016-07-25 23:25:36 +00:00
|
|
|
func makeLabels(labels, annotations map[string]string) map[string]string {
|
|
|
|
merged := make(map[string]string)
|
|
|
|
for k, v := range labels {
|
|
|
|
merged[k] = v
|
|
|
|
}
|
|
|
|
for k, v := range annotations {
|
2016-09-15 20:08:51 +00:00
|
|
|
// Assume there won't be conflict.
|
|
|
|
merged[fmt.Sprintf("%s%s", annotationPrefix, k)] = v
|
2016-07-25 23:25:36 +00:00
|
|
|
}
|
|
|
|
return merged
|
|
|
|
}
|
|
|
|
|
2016-09-15 20:08:51 +00:00
|
|
|
// extractLabels converts raw docker labels to the CRI labels and annotations.
|
|
|
|
// It also filters out internal labels used by this shim.
|
|
|
|
func extractLabels(input map[string]string) (map[string]string, map[string]string) {
|
|
|
|
labels := make(map[string]string)
|
|
|
|
annotations := make(map[string]string)
|
|
|
|
for k, v := range input {
|
|
|
|
// Check if the key is used internally by the shim.
|
2016-09-16 00:31:51 +00:00
|
|
|
internal := false
|
|
|
|
for _, internalKey := range internalLabelKeys {
|
|
|
|
if k == internalKey {
|
|
|
|
internal = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if internal {
|
2016-09-15 20:08:51 +00:00
|
|
|
continue
|
|
|
|
}
|
2016-09-16 00:31:51 +00:00
|
|
|
|
2016-10-01 00:08:32 +00:00
|
|
|
// Delete the container name label for the sandbox. It is added in the shim,
|
|
|
|
// should not be exposed via CRI.
|
|
|
|
if k == types.KubernetesContainerNameLabel &&
|
|
|
|
input[containerTypeLabelKey] == containerTypeLabelSandbox {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2016-09-16 00:31:51 +00:00
|
|
|
// Check if the label should be treated as an annotation.
|
2016-09-15 20:08:51 +00:00
|
|
|
if strings.HasPrefix(k, annotationPrefix) {
|
|
|
|
annotations[strings.TrimPrefix(k, annotationPrefix)] = v
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
labels[k] = v
|
|
|
|
}
|
|
|
|
return labels, annotations
|
|
|
|
}
|
|
|
|
|
2016-07-22 21:24:05 +00:00
|
|
|
// generateMountBindings converts the mount list to a list of strings that
|
|
|
|
// can be understood by docker.
|
|
|
|
// Each element in the string is in the form of:
|
|
|
|
// '<HostPath>:<ContainerPath>', or
|
|
|
|
// '<HostPath>:<ContainerPath>:ro', if the path is read only, or
|
|
|
|
// '<HostPath>:<ContainerPath>:Z', if the volume requires SELinux
|
|
|
|
// relabeling and the pod provides an SELinux label
|
2016-11-30 07:27:27 +00:00
|
|
|
func generateMountBindings(mounts []*runtimeapi.Mount) (result []string) {
|
2016-07-22 21:24:05 +00:00
|
|
|
for _, m := range mounts {
|
2017-01-20 01:55:37 +00:00
|
|
|
bind := fmt.Sprintf("%s:%s", m.HostPath, m.ContainerPath)
|
|
|
|
readOnly := m.Readonly
|
2016-07-22 21:24:05 +00:00
|
|
|
if readOnly {
|
|
|
|
bind += ":ro"
|
|
|
|
}
|
2016-11-04 11:54:07 +00:00
|
|
|
// Only request relabeling if the pod provides an SELinux context. If the pod
|
|
|
|
// does not provide an SELinux context relabeling will label the volume with
|
|
|
|
// the container's randomly allocated MCS label. This would restrict access
|
|
|
|
// to the volume to the container which mounts it first.
|
2017-01-20 01:55:37 +00:00
|
|
|
if m.SelinuxRelabel {
|
2016-07-22 21:24:05 +00:00
|
|
|
if readOnly {
|
|
|
|
bind += ",Z"
|
|
|
|
} else {
|
|
|
|
bind += ":Z"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result = append(result, bind)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
2016-07-25 23:25:36 +00:00
|
|
|
|
2016-11-30 07:27:27 +00:00
|
|
|
func makePortsAndBindings(pm []*runtimeapi.PortMapping) (map[dockernat.Port]struct{}, map[dockernat.Port][]dockernat.PortBinding) {
|
2016-07-25 23:25:36 +00:00
|
|
|
exposedPorts := map[dockernat.Port]struct{}{}
|
|
|
|
portBindings := map[dockernat.Port][]dockernat.PortBinding{}
|
|
|
|
for _, port := range pm {
|
2017-01-20 01:55:37 +00:00
|
|
|
exteriorPort := port.HostPort
|
2016-07-25 23:25:36 +00:00
|
|
|
if exteriorPort == 0 {
|
|
|
|
// No need to do port binding when HostPort is not specified
|
|
|
|
continue
|
|
|
|
}
|
2017-01-20 01:55:37 +00:00
|
|
|
interiorPort := port.ContainerPort
|
2016-07-25 23:25:36 +00:00
|
|
|
// Some of this port stuff is under-documented voodoo.
|
|
|
|
// See http://stackoverflow.com/questions/20428302/binding-a-port-to-a-host-interface-using-the-rest-api
|
|
|
|
var protocol string
|
2017-01-20 01:55:37 +00:00
|
|
|
switch strings.ToUpper(string(port.Protocol)) {
|
2016-07-25 23:25:36 +00:00
|
|
|
case "UDP":
|
|
|
|
protocol = "/udp"
|
|
|
|
case "TCP":
|
|
|
|
protocol = "/tcp"
|
|
|
|
default:
|
|
|
|
glog.Warningf("Unknown protocol %q: defaulting to TCP", port.Protocol)
|
|
|
|
protocol = "/tcp"
|
|
|
|
}
|
|
|
|
|
|
|
|
dockerPort := dockernat.Port(strconv.Itoa(int(interiorPort)) + protocol)
|
|
|
|
exposedPorts[dockerPort] = struct{}{}
|
|
|
|
|
|
|
|
hostBinding := dockernat.PortBinding{
|
|
|
|
HostPort: strconv.Itoa(int(exteriorPort)),
|
2017-01-20 01:55:37 +00:00
|
|
|
HostIP: port.HostIp,
|
2016-07-25 23:25:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Allow multiple host ports bind to same docker port
|
|
|
|
if existedBindings, ok := portBindings[dockerPort]; ok {
|
|
|
|
// If a docker port already map to a host port, just append the host ports
|
|
|
|
portBindings[dockerPort] = append(existedBindings, hostBinding)
|
|
|
|
} else {
|
|
|
|
// Otherwise, it's fresh new port binding
|
|
|
|
portBindings[dockerPort] = []dockernat.PortBinding{
|
|
|
|
hostBinding,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return exposedPorts, portBindings
|
|
|
|
}
|
|
|
|
|
2016-09-26 07:46:29 +00:00
|
|
|
// getContainerSecurityOpt gets container security options from container and sandbox config, currently from sandbox
|
|
|
|
// annotations.
|
|
|
|
// It is an experimental feature and may be promoted to official runtime api in the future.
|
2017-02-03 02:28:19 +00:00
|
|
|
func getContainerSecurityOpts(containerName string, sandboxConfig *runtimeapi.PodSandboxConfig, seccompProfileRoot string, separator rune) ([]string, error) {
|
2016-09-26 07:46:29 +00:00
|
|
|
appArmorOpts, err := dockertools.GetAppArmorOpts(sandboxConfig.GetAnnotations(), containerName)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
seccompOpts, err := dockertools.GetSeccompOpts(sandboxConfig.GetAnnotations(), containerName, seccompProfileRoot)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
securityOpts := append(appArmorOpts, seccompOpts...)
|
2017-02-03 02:28:19 +00:00
|
|
|
fmtOpts := dockertools.FmtDockerOpts(securityOpts, separator)
|
|
|
|
return fmtOpts, nil
|
2016-09-26 07:46:29 +00:00
|
|
|
}
|
|
|
|
|
2017-02-03 02:28:19 +00:00
|
|
|
func getSandboxSecurityOpts(sandboxConfig *runtimeapi.PodSandboxConfig, seccompProfileRoot string, separator rune) ([]string, error) {
|
2016-09-26 07:46:29 +00:00
|
|
|
// sandboxContainerName doesn't exist in the pod, so pod security options will be returned by default.
|
2017-02-03 02:28:19 +00:00
|
|
|
return getContainerSecurityOpts(sandboxContainerName, sandboxConfig, seccompProfileRoot, separator)
|
2016-07-25 23:25:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func getNetworkNamespace(c *dockertypes.ContainerJSON) string {
|
2016-10-20 16:55:28 +00:00
|
|
|
if c.State.Pid == 0 {
|
|
|
|
// Docker reports pid 0 for an exited container. We can't use it to
|
|
|
|
// check the network namespace, so return an empty string instead.
|
|
|
|
glog.V(4).Infof("Cannot find network namespace for the terminated container %q", c.ID)
|
|
|
|
return ""
|
|
|
|
}
|
2016-07-25 23:25:36 +00:00
|
|
|
return fmt.Sprintf(dockerNetNSFmt, c.State.Pid)
|
|
|
|
}
|
|
|
|
|
2016-10-14 15:51:24 +00:00
|
|
|
// getSysctlsFromAnnotations gets sysctls from annotations.
|
|
|
|
func getSysctlsFromAnnotations(annotations map[string]string) (map[string]string, error) {
|
|
|
|
var results map[string]string
|
|
|
|
|
2016-11-18 20:50:58 +00:00
|
|
|
sysctls, unsafeSysctls, err := v1.SysctlsFromPodAnnotations(annotations)
|
2016-10-14 15:51:24 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if len(sysctls)+len(unsafeSysctls) > 0 {
|
|
|
|
results = make(map[string]string, len(sysctls)+len(unsafeSysctls))
|
|
|
|
for _, c := range sysctls {
|
|
|
|
results[c.Name] = c.Value
|
|
|
|
}
|
|
|
|
for _, c := range unsafeSysctls {
|
|
|
|
results[c.Name] = c.Value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return results, nil
|
|
|
|
}
|
|
|
|
|
2016-07-25 23:25:36 +00:00
|
|
|
// dockerFilter wraps around dockerfilters.Args and provides methods to modify
|
|
|
|
// the filter easily.
|
|
|
|
type dockerFilter struct {
|
2016-09-14 23:01:12 +00:00
|
|
|
args *dockerfilters.Args
|
2016-07-25 23:25:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func newDockerFilter(args *dockerfilters.Args) *dockerFilter {
|
2016-09-14 23:01:12 +00:00
|
|
|
return &dockerFilter{args: args}
|
2016-07-25 23:25:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (f *dockerFilter) Add(key, value string) {
|
2016-09-14 23:01:12 +00:00
|
|
|
f.args.Add(key, value)
|
2016-07-25 23:25:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (f *dockerFilter) AddLabel(key, value string) {
|
|
|
|
f.Add("label", fmt.Sprintf("%s=%s", key, value))
|
|
|
|
}
|
2016-11-14 23:33:22 +00:00
|
|
|
|
|
|
|
// getUserFromImageUser gets uid or user name of the image user.
|
|
|
|
// If user is numeric, it will be treated as uid; or else, it is treated as user name.
|
2017-01-20 01:55:37 +00:00
|
|
|
func getUserFromImageUser(imageUser string) (*int64, string) {
|
2016-11-14 23:33:22 +00:00
|
|
|
user := dockertools.GetUserFromImageUser(imageUser)
|
|
|
|
// return both nil if user is not specified in the image.
|
|
|
|
if user == "" {
|
2017-01-20 01:55:37 +00:00
|
|
|
return nil, ""
|
2016-11-14 23:33:22 +00:00
|
|
|
}
|
|
|
|
// user could be either uid or user name. Try to interpret as numeric uid.
|
|
|
|
uid, err := strconv.ParseInt(user, 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
// If user is non numeric, assume it's user name.
|
2017-01-20 01:55:37 +00:00
|
|
|
return nil, user
|
2016-11-14 23:33:22 +00:00
|
|
|
}
|
|
|
|
// If user is a numeric uid.
|
2017-01-20 01:55:37 +00:00
|
|
|
return &uid, ""
|
2016-11-14 23:33:22 +00:00
|
|
|
}
|
2016-11-12 00:09:23 +00:00
|
|
|
|
|
|
|
// See #33189. If the previous attempt to create a sandbox container name FOO
|
|
|
|
// failed due to "device or resource busy", it is possbile that docker did
|
|
|
|
// not clean up properly and has inconsistent internal state. Docker would
|
|
|
|
// not report the existence of FOO, but would complain if user wants to
|
|
|
|
// create a new container named FOO. To work around this, we parse the error
|
|
|
|
// message to identify failure caused by naming conflict, and try to remove
|
|
|
|
// the old container FOO.
|
2017-01-25 19:46:57 +00:00
|
|
|
// See #40443. Sometimes even removal may fail with "no such container" error.
|
|
|
|
// In that case we have to create the container with a randomized name.
|
|
|
|
// TODO(random-liu): Remove this work around after docker 1.11 is deprecated.
|
2016-11-12 00:09:23 +00:00
|
|
|
// TODO(#33189): Monitor the tests to see if the fix is sufficent.
|
2017-01-25 19:46:57 +00:00
|
|
|
func recoverFromCreationConflictIfNeeded(client dockertools.DockerInterface, createConfig dockertypes.ContainerCreateConfig, err error) (*dockertypes.ContainerCreateResponse, error) {
|
2016-11-12 00:09:23 +00:00
|
|
|
matches := conflictRE.FindStringSubmatch(err.Error())
|
|
|
|
if len(matches) != 2 {
|
2017-01-25 19:46:57 +00:00
|
|
|
return nil, err
|
2016-11-12 00:09:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
id := matches[1]
|
|
|
|
glog.Warningf("Unable to create pod sandbox due to conflict. Attempting to remove sandbox %q", id)
|
2017-01-25 19:46:57 +00:00
|
|
|
if rmErr := client.RemoveContainer(id, dockertypes.ContainerRemoveOptions{RemoveVolumes: true}); rmErr == nil {
|
|
|
|
glog.V(2).Infof("Successfully removed conflicting container %q", id)
|
|
|
|
return nil, err
|
2016-11-12 00:09:23 +00:00
|
|
|
} else {
|
2017-01-25 19:46:57 +00:00
|
|
|
glog.Errorf("Failed to remove the conflicting container %q: %v", id, rmErr)
|
2017-01-26 23:17:56 +00:00
|
|
|
// Return if the error is not container not found error.
|
|
|
|
if !dockertools.IsContainerNotFoundError(rmErr) {
|
2017-01-25 19:46:57 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2016-11-12 00:09:23 +00:00
|
|
|
}
|
2017-01-25 19:46:57 +00:00
|
|
|
|
|
|
|
// randomize the name to avoid conflict.
|
|
|
|
createConfig.Name = randomizeName(createConfig.Name)
|
|
|
|
glog.V(2).Infof("Create the container with randomized name %s", createConfig.Name)
|
|
|
|
return client.CreateContainer(createConfig)
|
2016-11-12 00:09:23 +00:00
|
|
|
}
|
2017-02-03 02:28:19 +00:00
|
|
|
|
|
|
|
// getSecurityOptSeparator returns the security option separator based on the
|
|
|
|
// docker API version.
|
|
|
|
// TODO: Remove this function along with the relevant code when we no longer
|
|
|
|
// need to support docker 1.10.
|
|
|
|
func getSecurityOptSeparator(v *semver.Version) rune {
|
|
|
|
switch v.Compare(optsSeparatorChangeVersion) {
|
|
|
|
case -1:
|
|
|
|
// Current version is less than the API change version; use the old
|
|
|
|
// separator.
|
|
|
|
return dockertools.SecurityOptSeparatorOld
|
|
|
|
default:
|
|
|
|
return dockertools.SecurityOptSeparatorNew
|
|
|
|
}
|
|
|
|
}
|