1/* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15*/ 16 17package server 18 19import ( 20 "fmt" 21 "path" 22 "path/filepath" 23 "strconv" 24 "strings" 25 26 "github.com/BurntSushi/toml" 27 runhcsoptions "github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/options" 28 "github.com/containerd/containerd" 29 "github.com/containerd/containerd/containers" 30 "github.com/containerd/containerd/plugin" 31 "github.com/containerd/containerd/reference/docker" 32 "github.com/containerd/containerd/runtime/linux/runctypes" 33 runcoptions "github.com/containerd/containerd/runtime/v2/runc/options" 34 "github.com/containerd/typeurl" 35 imagedigest "github.com/opencontainers/go-digest" 36 "github.com/pkg/errors" 37 "golang.org/x/net/context" 38 runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" 39 40 criconfig "github.com/containerd/containerd/pkg/cri/config" 41 "github.com/containerd/containerd/pkg/cri/store" 42 containerstore "github.com/containerd/containerd/pkg/cri/store/container" 43 imagestore "github.com/containerd/containerd/pkg/cri/store/image" 44 sandboxstore "github.com/containerd/containerd/pkg/cri/store/sandbox" 45 runtimeoptions "github.com/containerd/containerd/pkg/runtimeoptions/v1" 46) 47 48const ( 49 // errorStartReason is the exit reason when fails to start container. 50 errorStartReason = "StartError" 51 // errorStartExitCode is the exit code when fails to start container. 52 // 128 is the same with Docker's behavior. 53 // TODO(windows): Figure out what should be used for windows. 54 errorStartExitCode = 128 55 // completeExitReason is the exit reason when container exits with code 0. 56 completeExitReason = "Completed" 57 // errorExitReason is the exit reason when container exits with code non-zero. 58 errorExitReason = "Error" 59 // oomExitReason is the exit reason when process in container is oom killed. 60 oomExitReason = "OOMKilled" 61 62 // sandboxesDir contains all sandbox root. A sandbox root is the running 63 // directory of the sandbox, all files created for the sandbox will be 64 // placed under this directory. 65 sandboxesDir = "sandboxes" 66 // containersDir contains all container root. 67 containersDir = "containers" 68 // Delimiter used to construct container/sandbox names. 69 nameDelimiter = "_" 70 71 // criContainerdPrefix is common prefix for cri-containerd 72 criContainerdPrefix = "io.cri-containerd" 73 // containerKindLabel is a label key indicating container is sandbox container or application container 74 containerKindLabel = criContainerdPrefix + ".kind" 75 // containerKindSandbox is a label value indicating container is sandbox container 76 containerKindSandbox = "sandbox" 77 // containerKindContainer is a label value indicating container is application container 78 containerKindContainer = "container" 79 // imageLabelKey is the label key indicating the image is managed by cri plugin. 80 imageLabelKey = criContainerdPrefix + ".image" 81 // imageLabelValue is the label value indicating the image is managed by cri plugin. 82 imageLabelValue = "managed" 83 // sandboxMetadataExtension is an extension name that identify metadata of sandbox in CreateContainerRequest 84 sandboxMetadataExtension = criContainerdPrefix + ".sandbox.metadata" 85 // containerMetadataExtension is an extension name that identify metadata of container in CreateContainerRequest 86 containerMetadataExtension = criContainerdPrefix + ".container.metadata" 87 88 // defaultIfName is the default network interface for the pods 89 defaultIfName = "eth0" 90 91 // runtimeRunhcsV1 is the runtime type for runhcs. 92 runtimeRunhcsV1 = "io.containerd.runhcs.v1" 93) 94 95// makeSandboxName generates sandbox name from sandbox metadata. The name 96// generated is unique as long as sandbox metadata is unique. 97func makeSandboxName(s *runtime.PodSandboxMetadata) string { 98 return strings.Join([]string{ 99 s.Name, // 0 100 s.Namespace, // 1 101 s.Uid, // 2 102 fmt.Sprintf("%d", s.Attempt), // 3 103 }, nameDelimiter) 104} 105 106// makeContainerName generates container name from sandbox and container metadata. 107// The name generated is unique as long as the sandbox container combination is 108// unique. 109func makeContainerName(c *runtime.ContainerMetadata, s *runtime.PodSandboxMetadata) string { 110 return strings.Join([]string{ 111 c.Name, // 0 112 s.Name, // 1: pod name 113 s.Namespace, // 2: pod namespace 114 s.Uid, // 3: pod uid 115 fmt.Sprintf("%d", c.Attempt), // 4 116 }, nameDelimiter) 117} 118 119// getSandboxRootDir returns the root directory for managing sandbox files, 120// e.g. hosts files. 121func (c *criService) getSandboxRootDir(id string) string { 122 return filepath.Join(c.config.RootDir, sandboxesDir, id) 123} 124 125// getVolatileSandboxRootDir returns the root directory for managing volatile sandbox files, 126// e.g. named pipes. 127func (c *criService) getVolatileSandboxRootDir(id string) string { 128 return filepath.Join(c.config.StateDir, sandboxesDir, id) 129} 130 131// getContainerRootDir returns the root directory for managing container files, 132// e.g. state checkpoint. 133func (c *criService) getContainerRootDir(id string) string { 134 return filepath.Join(c.config.RootDir, containersDir, id) 135} 136 137// getVolatileContainerRootDir returns the root directory for managing volatile container files, 138// e.g. named pipes. 139func (c *criService) getVolatileContainerRootDir(id string) string { 140 return filepath.Join(c.config.StateDir, containersDir, id) 141} 142 143// criContainerStateToString formats CRI container state to string. 144func criContainerStateToString(state runtime.ContainerState) string { 145 return runtime.ContainerState_name[int32(state)] 146} 147 148// getRepoDigestAngTag returns image repoDigest and repoTag of the named image reference. 149func getRepoDigestAndTag(namedRef docker.Named, digest imagedigest.Digest, schema1 bool) (string, string) { 150 var repoTag, repoDigest string 151 if _, ok := namedRef.(docker.NamedTagged); ok { 152 repoTag = namedRef.String() 153 } 154 if _, ok := namedRef.(docker.Canonical); ok { 155 repoDigest = namedRef.String() 156 } else if !schema1 { 157 // digest is not actual repo digest for schema1 image. 158 repoDigest = namedRef.Name() + "@" + digest.String() 159 } 160 return repoDigest, repoTag 161} 162 163// localResolve resolves image reference locally and returns corresponding image metadata. It 164// returns store.ErrNotExist if the reference doesn't exist. 165func (c *criService) localResolve(refOrID string) (imagestore.Image, error) { 166 getImageID := func(refOrId string) string { 167 if _, err := imagedigest.Parse(refOrID); err == nil { 168 return refOrID 169 } 170 return func(ref string) string { 171 // ref is not image id, try to resolve it locally. 172 // TODO(random-liu): Handle this error better for debugging. 173 normalized, err := docker.ParseDockerRef(ref) 174 if err != nil { 175 return "" 176 } 177 id, err := c.imageStore.Resolve(normalized.String()) 178 if err != nil { 179 return "" 180 } 181 return id 182 }(refOrID) 183 } 184 185 imageID := getImageID(refOrID) 186 if imageID == "" { 187 // Try to treat ref as imageID 188 imageID = refOrID 189 } 190 return c.imageStore.Get(imageID) 191} 192 193// toContainerdImage converts an image object in image store to containerd image handler. 194func (c *criService) toContainerdImage(ctx context.Context, image imagestore.Image) (containerd.Image, error) { 195 // image should always have at least one reference. 196 if len(image.References) == 0 { 197 return nil, errors.Errorf("invalid image with no reference %q", image.ID) 198 } 199 return c.client.GetImage(ctx, image.References[0]) 200} 201 202// getUserFromImage gets uid or user name of the image user. 203// If user is numeric, it will be treated as uid; or else, it is treated as user name. 204func getUserFromImage(user string) (*int64, string) { 205 // return both empty if user is not specified in the image. 206 if user == "" { 207 return nil, "" 208 } 209 // split instances where the id may contain user:group 210 user = strings.Split(user, ":")[0] 211 // user could be either uid or user name. Try to interpret as numeric uid. 212 uid, err := strconv.ParseInt(user, 10, 64) 213 if err != nil { 214 // If user is non numeric, assume it's user name. 215 return nil, user 216 } 217 // If user is a numeric uid. 218 return &uid, "" 219} 220 221// ensureImageExists returns corresponding metadata of the image reference, if image is not 222// pulled yet, the function will pull the image. 223func (c *criService) ensureImageExists(ctx context.Context, ref string, config *runtime.PodSandboxConfig) (*imagestore.Image, error) { 224 image, err := c.localResolve(ref) 225 if err != nil && err != store.ErrNotExist { 226 return nil, errors.Wrapf(err, "failed to get image %q", ref) 227 } 228 if err == nil { 229 return &image, nil 230 } 231 // Pull image to ensure the image exists 232 resp, err := c.PullImage(ctx, &runtime.PullImageRequest{Image: &runtime.ImageSpec{Image: ref}, SandboxConfig: config}) 233 if err != nil { 234 return nil, errors.Wrapf(err, "failed to pull image %q", ref) 235 } 236 imageID := resp.GetImageRef() 237 newImage, err := c.imageStore.Get(imageID) 238 if err != nil { 239 // It's still possible that someone removed the image right after it is pulled. 240 return nil, errors.Wrapf(err, "failed to get image %q after pulling", imageID) 241 } 242 return &newImage, nil 243} 244 245// isInCRIMounts checks whether a destination is in CRI mount list. 246func isInCRIMounts(dst string, mounts []*runtime.Mount) bool { 247 for _, m := range mounts { 248 if filepath.Clean(m.ContainerPath) == filepath.Clean(dst) { 249 return true 250 } 251 } 252 return false 253} 254 255// filterLabel returns a label filter. Use `%q` here because containerd 256// filter needs extra quote to work properly. 257func filterLabel(k, v string) string { 258 return fmt.Sprintf("labels.%q==%q", k, v) 259} 260 261// buildLabel builds the labels from config to be passed to containerd 262func buildLabels(configLabels map[string]string, containerType string) map[string]string { 263 labels := make(map[string]string) 264 for k, v := range configLabels { 265 labels[k] = v 266 } 267 labels[containerKindLabel] = containerType 268 return labels 269} 270 271// toRuntimeAuthConfig converts cri plugin auth config to runtime auth config. 272func toRuntimeAuthConfig(a criconfig.AuthConfig) *runtime.AuthConfig { 273 return &runtime.AuthConfig{ 274 Username: a.Username, 275 Password: a.Password, 276 Auth: a.Auth, 277 IdentityToken: a.IdentityToken, 278 } 279} 280 281// parseImageReferences parses a list of arbitrary image references and returns 282// the repotags and repodigests 283func parseImageReferences(refs []string) ([]string, []string) { 284 var tags, digests []string 285 for _, ref := range refs { 286 parsed, err := docker.ParseAnyReference(ref) 287 if err != nil { 288 continue 289 } 290 if _, ok := parsed.(docker.Canonical); ok { 291 digests = append(digests, parsed.String()) 292 } else if _, ok := parsed.(docker.Tagged); ok { 293 tags = append(tags, parsed.String()) 294 } 295 } 296 return tags, digests 297} 298 299// generateRuntimeOptions generates runtime options from cri plugin config. 300func generateRuntimeOptions(r criconfig.Runtime, c criconfig.Config) (interface{}, error) { 301 if r.Options == nil { 302 if r.Type != plugin.RuntimeLinuxV1 { 303 return nil, nil 304 } 305 // This is a legacy config, generate runctypes.RuncOptions. 306 return &runctypes.RuncOptions{ 307 Runtime: r.Engine, 308 RuntimeRoot: r.Root, 309 SystemdCgroup: c.SystemdCgroup, 310 }, nil 311 } 312 options := getRuntimeOptionsType(r.Type) 313 if err := toml.PrimitiveDecode(*r.Options, options); err != nil { 314 return nil, err 315 } 316 return options, nil 317} 318 319// getRuntimeOptionsType gets empty runtime options by the runtime type name. 320func getRuntimeOptionsType(t string) interface{} { 321 switch t { 322 case plugin.RuntimeRuncV1: 323 fallthrough 324 case plugin.RuntimeRuncV2: 325 return &runcoptions.Options{} 326 case plugin.RuntimeLinuxV1: 327 return &runctypes.RuncOptions{} 328 case runtimeRunhcsV1: 329 return &runhcsoptions.Options{} 330 default: 331 return &runtimeoptions.Options{} 332 } 333} 334 335// getRuntimeOptions get runtime options from container metadata. 336func getRuntimeOptions(c containers.Container) (interface{}, error) { 337 if c.Runtime.Options == nil { 338 return nil, nil 339 } 340 opts, err := typeurl.UnmarshalAny(c.Runtime.Options) 341 if err != nil { 342 return nil, err 343 } 344 return opts, nil 345} 346 347const ( 348 // unknownExitCode is the exit code when exit reason is unknown. 349 unknownExitCode = 255 350 // unknownExitReason is the exit reason when exit reason is unknown. 351 unknownExitReason = "Unknown" 352) 353 354// unknownContainerStatus returns the default container status when its status is unknown. 355func unknownContainerStatus() containerstore.Status { 356 return containerstore.Status{ 357 CreatedAt: 0, 358 StartedAt: 0, 359 FinishedAt: 0, 360 ExitCode: unknownExitCode, 361 Reason: unknownExitReason, 362 Unknown: true, 363 } 364} 365 366// unknownSandboxStatus returns the default sandbox status when its status is unknown. 367func unknownSandboxStatus() sandboxstore.Status { 368 return sandboxstore.Status{ 369 State: sandboxstore.StateUnknown, 370 } 371} 372 373// getPassthroughAnnotations filters requested pod annotations by comparing 374// against permitted annotations for the given runtime. 375func getPassthroughAnnotations(podAnnotations map[string]string, 376 runtimePodAnnotations []string) (passthroughAnnotations map[string]string) { 377 passthroughAnnotations = make(map[string]string) 378 379 for podAnnotationKey, podAnnotationValue := range podAnnotations { 380 for _, pattern := range runtimePodAnnotations { 381 // Use path.Match instead of filepath.Match here. 382 // filepath.Match treated `\\` as path separator 383 // on windows, which is not what we want. 384 if ok, _ := path.Match(pattern, podAnnotationKey); ok { 385 passthroughAnnotations[podAnnotationKey] = podAnnotationValue 386 } 387 } 388 } 389 return passthroughAnnotations 390} 391