docker_container.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package dockershim
  14. import (
  15. "context"
  16. "fmt"
  17. "os"
  18. "path/filepath"
  19. "time"
  20. dockertypes "github.com/docker/docker/api/types"
  21. dockercontainer "github.com/docker/docker/api/types/container"
  22. dockerfilters "github.com/docker/docker/api/types/filters"
  23. dockerstrslice "github.com/docker/docker/api/types/strslice"
  24. "k8s.io/klog"
  25. runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
  26. "k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker"
  27. )
  28. // ListContainers lists all containers matching the filter.
  29. func (ds *dockerService) ListContainers(_ context.Context, r *runtimeapi.ListContainersRequest) (*runtimeapi.ListContainersResponse, error) {
  30. filter := r.GetFilter()
  31. opts := dockertypes.ContainerListOptions{All: true}
  32. opts.Filters = dockerfilters.NewArgs()
  33. f := newDockerFilter(&opts.Filters)
  34. // Add filter to get *only* (non-sandbox) containers.
  35. f.AddLabel(containerTypeLabelKey, containerTypeLabelContainer)
  36. if filter != nil {
  37. if filter.Id != "" {
  38. f.Add("id", filter.Id)
  39. }
  40. if filter.State != nil {
  41. f.Add("status", toDockerContainerStatus(filter.GetState().State))
  42. }
  43. if filter.PodSandboxId != "" {
  44. f.AddLabel(sandboxIDLabelKey, filter.PodSandboxId)
  45. }
  46. if filter.LabelSelector != nil {
  47. for k, v := range filter.LabelSelector {
  48. f.AddLabel(k, v)
  49. }
  50. }
  51. }
  52. containers, err := ds.client.ListContainers(opts)
  53. if err != nil {
  54. return nil, err
  55. }
  56. // Convert docker to runtime api containers.
  57. result := []*runtimeapi.Container{}
  58. for i := range containers {
  59. c := containers[i]
  60. converted, err := toRuntimeAPIContainer(&c)
  61. if err != nil {
  62. klog.V(4).Infof("Unable to convert docker to runtime API container: %v", err)
  63. continue
  64. }
  65. result = append(result, converted)
  66. }
  67. return &runtimeapi.ListContainersResponse{Containers: result}, nil
  68. }
  69. // CreateContainer creates a new container in the given PodSandbox
  70. // Docker cannot store the log to an arbitrary location (yet), so we create an
  71. // symlink at LogPath, linking to the actual path of the log.
  72. // TODO: check if the default values returned by the runtime API are ok.
  73. func (ds *dockerService) CreateContainer(_ context.Context, r *runtimeapi.CreateContainerRequest) (*runtimeapi.CreateContainerResponse, error) {
  74. podSandboxID := r.PodSandboxId
  75. config := r.GetConfig()
  76. sandboxConfig := r.GetSandboxConfig()
  77. if config == nil {
  78. return nil, fmt.Errorf("container config is nil")
  79. }
  80. if sandboxConfig == nil {
  81. return nil, fmt.Errorf("sandbox config is nil for container %q", config.Metadata.Name)
  82. }
  83. labels := makeLabels(config.GetLabels(), config.GetAnnotations())
  84. // Apply a the container type label.
  85. labels[containerTypeLabelKey] = containerTypeLabelContainer
  86. // Write the container log path in the labels.
  87. labels[containerLogPathLabelKey] = filepath.Join(sandboxConfig.LogDirectory, config.LogPath)
  88. // Write the sandbox ID in the labels.
  89. labels[sandboxIDLabelKey] = podSandboxID
  90. apiVersion, err := ds.getDockerAPIVersion()
  91. if err != nil {
  92. return nil, fmt.Errorf("unable to get the docker API version: %v", err)
  93. }
  94. image := ""
  95. if iSpec := config.GetImage(); iSpec != nil {
  96. image = iSpec.Image
  97. }
  98. containerName := makeContainerName(sandboxConfig, config)
  99. createConfig := dockertypes.ContainerCreateConfig{
  100. Name: containerName,
  101. Config: &dockercontainer.Config{
  102. // TODO: set User.
  103. Entrypoint: dockerstrslice.StrSlice(config.Command),
  104. Cmd: dockerstrslice.StrSlice(config.Args),
  105. Env: generateEnvList(config.GetEnvs()),
  106. Image: image,
  107. WorkingDir: config.WorkingDir,
  108. Labels: labels,
  109. // Interactive containers:
  110. OpenStdin: config.Stdin,
  111. StdinOnce: config.StdinOnce,
  112. Tty: config.Tty,
  113. // Disable Docker's health check until we officially support it
  114. // (https://github.com/kubernetes/kubernetes/issues/25829).
  115. Healthcheck: &dockercontainer.HealthConfig{
  116. Test: []string{"NONE"},
  117. },
  118. },
  119. HostConfig: &dockercontainer.HostConfig{
  120. Binds: generateMountBindings(config.GetMounts()),
  121. RestartPolicy: dockercontainer.RestartPolicy{
  122. Name: "no",
  123. },
  124. },
  125. }
  126. hc := createConfig.HostConfig
  127. err = ds.updateCreateConfig(&createConfig, config, sandboxConfig, podSandboxID, securityOptSeparator, apiVersion)
  128. if err != nil {
  129. return nil, fmt.Errorf("failed to update container create config: %v", err)
  130. }
  131. // Set devices for container.
  132. devices := make([]dockercontainer.DeviceMapping, len(config.Devices))
  133. for i, device := range config.Devices {
  134. devices[i] = dockercontainer.DeviceMapping{
  135. PathOnHost: device.HostPath,
  136. PathInContainer: device.ContainerPath,
  137. CgroupPermissions: device.Permissions,
  138. }
  139. }
  140. hc.Resources.Devices = devices
  141. securityOpts, err := ds.getSecurityOpts(config.GetLinux().GetSecurityContext().GetSeccompProfilePath(), securityOptSeparator)
  142. if err != nil {
  143. return nil, fmt.Errorf("failed to generate security options for container %q: %v", config.Metadata.Name, err)
  144. }
  145. hc.SecurityOpt = append(hc.SecurityOpt, securityOpts...)
  146. cleanupInfo, err := ds.applyPlatformSpecificDockerConfig(r, &createConfig)
  147. if err != nil {
  148. return nil, err
  149. }
  150. createResp, createErr := ds.client.CreateContainer(createConfig)
  151. if createErr != nil {
  152. createResp, createErr = recoverFromCreationConflictIfNeeded(ds.client, createConfig, createErr)
  153. }
  154. if createResp != nil {
  155. containerID := createResp.ID
  156. if cleanupInfo != nil {
  157. // we don't perform the clean up just yet at that could destroy information
  158. // needed for the container to start (e.g. Windows credentials stored in
  159. // registry keys); instead, we'll clean up when the container gets removed
  160. ds.containerCleanupInfos[containerID] = cleanupInfo
  161. }
  162. return &runtimeapi.CreateContainerResponse{ContainerId: containerID}, nil
  163. }
  164. // the creation failed, let's clean up right away - we ignore any errors though,
  165. // this is best effort
  166. ds.performPlatformSpecificContainerCleanupAndLogErrors(containerName, cleanupInfo)
  167. return nil, createErr
  168. }
  169. // getContainerLogPath returns the container log path specified by kubelet and the real
  170. // path where docker stores the container log.
  171. func (ds *dockerService) getContainerLogPath(containerID string) (string, string, error) {
  172. info, err := ds.client.InspectContainer(containerID)
  173. if err != nil {
  174. return "", "", fmt.Errorf("failed to inspect container %q: %v", containerID, err)
  175. }
  176. return info.Config.Labels[containerLogPathLabelKey], info.LogPath, nil
  177. }
  178. // createContainerLogSymlink creates the symlink for docker container log.
  179. func (ds *dockerService) createContainerLogSymlink(containerID string) error {
  180. path, realPath, err := ds.getContainerLogPath(containerID)
  181. if err != nil {
  182. return fmt.Errorf("failed to get container %q log path: %v", containerID, err)
  183. }
  184. if path == "" {
  185. klog.V(5).Infof("Container %s log path isn't specified, will not create the symlink", containerID)
  186. return nil
  187. }
  188. if realPath != "" {
  189. // Only create the symlink when container log path is specified and log file exists.
  190. // Delete possibly existing file first
  191. if err = ds.os.Remove(path); err == nil {
  192. klog.Warningf("Deleted previously existing symlink file: %q", path)
  193. }
  194. if err = ds.os.Symlink(realPath, path); err != nil {
  195. return fmt.Errorf("failed to create symbolic link %q to the container log file %q for container %q: %v",
  196. path, realPath, containerID, err)
  197. }
  198. } else {
  199. supported, err := ds.IsCRISupportedLogDriver()
  200. if err != nil {
  201. klog.Warningf("Failed to check supported logging driver by CRI: %v", err)
  202. return nil
  203. }
  204. if supported {
  205. klog.Warningf("Cannot create symbolic link because container log file doesn't exist!")
  206. } else {
  207. klog.V(5).Infof("Unsupported logging driver by CRI")
  208. }
  209. }
  210. return nil
  211. }
  212. // removeContainerLogSymlink removes the symlink for docker container log.
  213. func (ds *dockerService) removeContainerLogSymlink(containerID string) error {
  214. path, _, err := ds.getContainerLogPath(containerID)
  215. if err != nil {
  216. return fmt.Errorf("failed to get container %q log path: %v", containerID, err)
  217. }
  218. if path != "" {
  219. // Only remove the symlink when container log path is specified.
  220. err := ds.os.Remove(path)
  221. if err != nil && !os.IsNotExist(err) {
  222. return fmt.Errorf("failed to remove container %q log symlink %q: %v", containerID, path, err)
  223. }
  224. }
  225. return nil
  226. }
  227. // StartContainer starts the container.
  228. func (ds *dockerService) StartContainer(_ context.Context, r *runtimeapi.StartContainerRequest) (*runtimeapi.StartContainerResponse, error) {
  229. err := ds.client.StartContainer(r.ContainerId)
  230. // Create container log symlink for all containers (including failed ones).
  231. if linkError := ds.createContainerLogSymlink(r.ContainerId); linkError != nil {
  232. // Do not stop the container if we failed to create symlink because:
  233. // 1. This is not a critical failure.
  234. // 2. We don't have enough information to properly stop container here.
  235. // Kubelet will surface this error to user via an event.
  236. return nil, linkError
  237. }
  238. if err != nil {
  239. err = transformStartContainerError(err)
  240. return nil, fmt.Errorf("failed to start container %q: %v", r.ContainerId, err)
  241. }
  242. return &runtimeapi.StartContainerResponse{}, nil
  243. }
  244. // StopContainer stops a running container with a grace period (i.e., timeout).
  245. func (ds *dockerService) StopContainer(_ context.Context, r *runtimeapi.StopContainerRequest) (*runtimeapi.StopContainerResponse, error) {
  246. err := ds.client.StopContainer(r.ContainerId, time.Duration(r.Timeout)*time.Second)
  247. if err != nil {
  248. return nil, err
  249. }
  250. return &runtimeapi.StopContainerResponse{}, nil
  251. }
  252. // RemoveContainer removes the container.
  253. func (ds *dockerService) RemoveContainer(_ context.Context, r *runtimeapi.RemoveContainerRequest) (*runtimeapi.RemoveContainerResponse, error) {
  254. // Ideally, log lifecycle should be independent of container lifecycle.
  255. // However, docker will remove container log after container is removed,
  256. // we can't prevent that now, so we also clean up the symlink here.
  257. err := ds.removeContainerLogSymlink(r.ContainerId)
  258. if err != nil {
  259. return nil, err
  260. }
  261. errors := ds.performPlatformSpecificContainerForContainer(r.ContainerId)
  262. if len(errors) != 0 {
  263. return nil, fmt.Errorf("failed to run platform-specific clean ups for container %q: %v", r.ContainerId, errors)
  264. }
  265. err = ds.client.RemoveContainer(r.ContainerId, dockertypes.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
  266. if err != nil {
  267. return nil, fmt.Errorf("failed to remove container %q: %v", r.ContainerId, err)
  268. }
  269. return &runtimeapi.RemoveContainerResponse{}, nil
  270. }
  271. func getContainerTimestamps(r *dockertypes.ContainerJSON) (time.Time, time.Time, time.Time, error) {
  272. var createdAt, startedAt, finishedAt time.Time
  273. var err error
  274. createdAt, err = libdocker.ParseDockerTimestamp(r.Created)
  275. if err != nil {
  276. return createdAt, startedAt, finishedAt, err
  277. }
  278. startedAt, err = libdocker.ParseDockerTimestamp(r.State.StartedAt)
  279. if err != nil {
  280. return createdAt, startedAt, finishedAt, err
  281. }
  282. finishedAt, err = libdocker.ParseDockerTimestamp(r.State.FinishedAt)
  283. if err != nil {
  284. return createdAt, startedAt, finishedAt, err
  285. }
  286. return createdAt, startedAt, finishedAt, nil
  287. }
  288. // ContainerStatus inspects the docker container and returns the status.
  289. func (ds *dockerService) ContainerStatus(_ context.Context, req *runtimeapi.ContainerStatusRequest) (*runtimeapi.ContainerStatusResponse, error) {
  290. containerID := req.ContainerId
  291. r, err := ds.client.InspectContainer(containerID)
  292. if err != nil {
  293. return nil, err
  294. }
  295. // Parse the timestamps.
  296. createdAt, startedAt, finishedAt, err := getContainerTimestamps(r)
  297. if err != nil {
  298. return nil, fmt.Errorf("failed to parse timestamp for container %q: %v", containerID, err)
  299. }
  300. // Convert the image id to a pullable id.
  301. ir, err := ds.client.InspectImageByID(r.Image)
  302. if err != nil {
  303. if !libdocker.IsImageNotFoundError(err) {
  304. return nil, fmt.Errorf("unable to inspect docker image %q while inspecting docker container %q: %v", r.Image, containerID, err)
  305. }
  306. klog.Warningf("ignore error image %q not found while inspecting docker container %q: %v", r.Image, containerID, err)
  307. }
  308. imageID := toPullableImageID(r.Image, ir)
  309. // Convert the mounts.
  310. mounts := make([]*runtimeapi.Mount, 0, len(r.Mounts))
  311. for i := range r.Mounts {
  312. m := r.Mounts[i]
  313. readonly := !m.RW
  314. mounts = append(mounts, &runtimeapi.Mount{
  315. HostPath: m.Source,
  316. ContainerPath: m.Destination,
  317. Readonly: readonly,
  318. // Note: Can't set SeLinuxRelabel
  319. })
  320. }
  321. // Interpret container states.
  322. var state runtimeapi.ContainerState
  323. var reason, message string
  324. if r.State.Running {
  325. // Container is running.
  326. state = runtimeapi.ContainerState_CONTAINER_RUNNING
  327. } else {
  328. // Container is *not* running. We need to get more details.
  329. // * Case 1: container has run and exited with non-zero finishedAt
  330. // time.
  331. // * Case 2: container has failed to start; it has a zero finishedAt
  332. // time, but a non-zero exit code.
  333. // * Case 3: container has been created, but not started (yet).
  334. if !finishedAt.IsZero() { // Case 1
  335. state = runtimeapi.ContainerState_CONTAINER_EXITED
  336. switch {
  337. case r.State.OOMKilled:
  338. // TODO: consider exposing OOMKilled via the runtimeAPI.
  339. // Note: if an application handles OOMKilled gracefully, the
  340. // exit code could be zero.
  341. reason = "OOMKilled"
  342. case r.State.ExitCode == 0:
  343. reason = "Completed"
  344. default:
  345. reason = "Error"
  346. }
  347. } else if r.State.ExitCode != 0 { // Case 2
  348. state = runtimeapi.ContainerState_CONTAINER_EXITED
  349. // Adjust finshedAt and startedAt time to createdAt time to avoid
  350. // the confusion.
  351. finishedAt, startedAt = createdAt, createdAt
  352. reason = "ContainerCannotRun"
  353. } else { // Case 3
  354. state = runtimeapi.ContainerState_CONTAINER_CREATED
  355. }
  356. message = r.State.Error
  357. }
  358. // Convert to unix timestamps.
  359. ct, st, ft := createdAt.UnixNano(), startedAt.UnixNano(), finishedAt.UnixNano()
  360. exitCode := int32(r.State.ExitCode)
  361. metadata, err := parseContainerName(r.Name)
  362. if err != nil {
  363. return nil, err
  364. }
  365. labels, annotations := extractLabels(r.Config.Labels)
  366. imageName := r.Config.Image
  367. if len(ir.RepoTags) > 0 {
  368. imageName = ir.RepoTags[0]
  369. }
  370. status := &runtimeapi.ContainerStatus{
  371. Id: r.ID,
  372. Metadata: metadata,
  373. Image: &runtimeapi.ImageSpec{Image: imageName},
  374. ImageRef: imageID,
  375. Mounts: mounts,
  376. ExitCode: exitCode,
  377. State: state,
  378. CreatedAt: ct,
  379. StartedAt: st,
  380. FinishedAt: ft,
  381. Reason: reason,
  382. Message: message,
  383. Labels: labels,
  384. Annotations: annotations,
  385. LogPath: r.Config.Labels[containerLogPathLabelKey],
  386. }
  387. return &runtimeapi.ContainerStatusResponse{Status: status}, nil
  388. }
  389. func (ds *dockerService) UpdateContainerResources(_ context.Context, r *runtimeapi.UpdateContainerResourcesRequest) (*runtimeapi.UpdateContainerResourcesResponse, error) {
  390. resources := r.Linux
  391. updateConfig := dockercontainer.UpdateConfig{
  392. Resources: dockercontainer.Resources{
  393. CPUPeriod: resources.CpuPeriod,
  394. CPUQuota: resources.CpuQuota,
  395. CPUShares: resources.CpuShares,
  396. Memory: resources.MemoryLimitInBytes,
  397. CpusetCpus: resources.CpusetCpus,
  398. CpusetMems: resources.CpusetMems,
  399. },
  400. }
  401. err := ds.client.UpdateContainerResources(r.ContainerId, updateConfig)
  402. if err != nil {
  403. return nil, fmt.Errorf("failed to update container %q: %v", r.ContainerId, err)
  404. }
  405. return &runtimeapi.UpdateContainerResourcesResponse{}, nil
  406. }
  407. func (ds *dockerService) performPlatformSpecificContainerForContainer(containerID string) (errors []error) {
  408. if cleanupInfo, present := ds.containerCleanupInfos[containerID]; present {
  409. errors = ds.performPlatformSpecificContainerCleanupAndLogErrors(containerID, cleanupInfo)
  410. if len(errors) == 0 {
  411. delete(ds.containerCleanupInfos, containerID)
  412. }
  413. }
  414. return
  415. }
  416. func (ds *dockerService) performPlatformSpecificContainerCleanupAndLogErrors(containerNameOrID string, cleanupInfo *containerCleanupInfo) []error {
  417. if cleanupInfo == nil {
  418. return nil
  419. }
  420. errors := ds.performPlatformSpecificContainerCleanup(cleanupInfo)
  421. for _, err := range errors {
  422. klog.Warningf("error when cleaning up after container %q: %v", containerNameOrID, err)
  423. }
  424. return errors
  425. }