docker_container.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  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 after the container successfully
  160. // starts or gets removed
  161. ds.containerCleanupInfos[containerID] = cleanupInfo
  162. }
  163. return &runtimeapi.CreateContainerResponse{ContainerId: containerID}, nil
  164. }
  165. // the creation failed, let's clean up right away
  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. ds.performPlatformSpecificContainerForContainer(r.ContainerId)
  243. return &runtimeapi.StartContainerResponse{}, nil
  244. }
  245. // StopContainer stops a running container with a grace period (i.e., timeout).
  246. func (ds *dockerService) StopContainer(_ context.Context, r *runtimeapi.StopContainerRequest) (*runtimeapi.StopContainerResponse, error) {
  247. err := ds.client.StopContainer(r.ContainerId, time.Duration(r.Timeout)*time.Second)
  248. if err != nil {
  249. return nil, err
  250. }
  251. return &runtimeapi.StopContainerResponse{}, nil
  252. }
  253. // RemoveContainer removes the container.
  254. func (ds *dockerService) RemoveContainer(_ context.Context, r *runtimeapi.RemoveContainerRequest) (*runtimeapi.RemoveContainerResponse, error) {
  255. ds.performPlatformSpecificContainerForContainer(r.ContainerId)
  256. // Ideally, log lifecycle should be independent of container lifecycle.
  257. // However, docker will remove container log after container is removed,
  258. // we can't prevent that now, so we also clean up the symlink here.
  259. err := ds.removeContainerLogSymlink(r.ContainerId)
  260. if err != nil {
  261. return nil, err
  262. }
  263. err = ds.client.RemoveContainer(r.ContainerId, dockertypes.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
  264. if err != nil {
  265. return nil, fmt.Errorf("failed to remove container %q: %v", r.ContainerId, err)
  266. }
  267. return &runtimeapi.RemoveContainerResponse{}, nil
  268. }
  269. func getContainerTimestamps(r *dockertypes.ContainerJSON) (time.Time, time.Time, time.Time, error) {
  270. var createdAt, startedAt, finishedAt time.Time
  271. var err error
  272. createdAt, err = libdocker.ParseDockerTimestamp(r.Created)
  273. if err != nil {
  274. return createdAt, startedAt, finishedAt, err
  275. }
  276. startedAt, err = libdocker.ParseDockerTimestamp(r.State.StartedAt)
  277. if err != nil {
  278. return createdAt, startedAt, finishedAt, err
  279. }
  280. finishedAt, err = libdocker.ParseDockerTimestamp(r.State.FinishedAt)
  281. if err != nil {
  282. return createdAt, startedAt, finishedAt, err
  283. }
  284. return createdAt, startedAt, finishedAt, nil
  285. }
  286. // ContainerStatus inspects the docker container and returns the status.
  287. func (ds *dockerService) ContainerStatus(_ context.Context, req *runtimeapi.ContainerStatusRequest) (*runtimeapi.ContainerStatusResponse, error) {
  288. containerID := req.ContainerId
  289. r, err := ds.client.InspectContainer(containerID)
  290. if err != nil {
  291. return nil, err
  292. }
  293. // Parse the timestamps.
  294. createdAt, startedAt, finishedAt, err := getContainerTimestamps(r)
  295. if err != nil {
  296. return nil, fmt.Errorf("failed to parse timestamp for container %q: %v", containerID, err)
  297. }
  298. // Convert the image id to a pullable id.
  299. ir, err := ds.client.InspectImageByID(r.Image)
  300. if err != nil {
  301. return nil, fmt.Errorf("unable to inspect docker image %q while inspecting docker container %q: %v", r.Image, containerID, err)
  302. }
  303. imageID := toPullableImageID(r.Image, ir)
  304. // Convert the mounts.
  305. mounts := make([]*runtimeapi.Mount, 0, len(r.Mounts))
  306. for i := range r.Mounts {
  307. m := r.Mounts[i]
  308. readonly := !m.RW
  309. mounts = append(mounts, &runtimeapi.Mount{
  310. HostPath: m.Source,
  311. ContainerPath: m.Destination,
  312. Readonly: readonly,
  313. // Note: Can't set SeLinuxRelabel
  314. })
  315. }
  316. // Interpret container states.
  317. var state runtimeapi.ContainerState
  318. var reason, message string
  319. if r.State.Running {
  320. // Container is running.
  321. state = runtimeapi.ContainerState_CONTAINER_RUNNING
  322. } else {
  323. // Container is *not* running. We need to get more details.
  324. // * Case 1: container has run and exited with non-zero finishedAt
  325. // time.
  326. // * Case 2: container has failed to start; it has a zero finishedAt
  327. // time, but a non-zero exit code.
  328. // * Case 3: container has been created, but not started (yet).
  329. if !finishedAt.IsZero() { // Case 1
  330. state = runtimeapi.ContainerState_CONTAINER_EXITED
  331. switch {
  332. case r.State.OOMKilled:
  333. // TODO: consider exposing OOMKilled via the runtimeAPI.
  334. // Note: if an application handles OOMKilled gracefully, the
  335. // exit code could be zero.
  336. reason = "OOMKilled"
  337. case r.State.ExitCode == 0:
  338. reason = "Completed"
  339. default:
  340. reason = "Error"
  341. }
  342. } else if r.State.ExitCode != 0 { // Case 2
  343. state = runtimeapi.ContainerState_CONTAINER_EXITED
  344. // Adjust finshedAt and startedAt time to createdAt time to avoid
  345. // the confusion.
  346. finishedAt, startedAt = createdAt, createdAt
  347. reason = "ContainerCannotRun"
  348. } else { // Case 3
  349. state = runtimeapi.ContainerState_CONTAINER_CREATED
  350. }
  351. message = r.State.Error
  352. }
  353. // Convert to unix timestamps.
  354. ct, st, ft := createdAt.UnixNano(), startedAt.UnixNano(), finishedAt.UnixNano()
  355. exitCode := int32(r.State.ExitCode)
  356. metadata, err := parseContainerName(r.Name)
  357. if err != nil {
  358. return nil, err
  359. }
  360. labels, annotations := extractLabels(r.Config.Labels)
  361. imageName := r.Config.Image
  362. if len(ir.RepoTags) > 0 {
  363. imageName = ir.RepoTags[0]
  364. }
  365. status := &runtimeapi.ContainerStatus{
  366. Id: r.ID,
  367. Metadata: metadata,
  368. Image: &runtimeapi.ImageSpec{Image: imageName},
  369. ImageRef: imageID,
  370. Mounts: mounts,
  371. ExitCode: exitCode,
  372. State: state,
  373. CreatedAt: ct,
  374. StartedAt: st,
  375. FinishedAt: ft,
  376. Reason: reason,
  377. Message: message,
  378. Labels: labels,
  379. Annotations: annotations,
  380. LogPath: r.Config.Labels[containerLogPathLabelKey],
  381. }
  382. return &runtimeapi.ContainerStatusResponse{Status: status}, nil
  383. }
  384. func (ds *dockerService) UpdateContainerResources(_ context.Context, r *runtimeapi.UpdateContainerResourcesRequest) (*runtimeapi.UpdateContainerResourcesResponse, error) {
  385. resources := r.Linux
  386. updateConfig := dockercontainer.UpdateConfig{
  387. Resources: dockercontainer.Resources{
  388. CPUPeriod: resources.CpuPeriod,
  389. CPUQuota: resources.CpuQuota,
  390. CPUShares: resources.CpuShares,
  391. Memory: resources.MemoryLimitInBytes,
  392. CpusetCpus: resources.CpusetCpus,
  393. CpusetMems: resources.CpusetMems,
  394. },
  395. }
  396. err := ds.client.UpdateContainerResources(r.ContainerId, updateConfig)
  397. if err != nil {
  398. return nil, fmt.Errorf("failed to update container %q: %v", r.ContainerId, err)
  399. }
  400. return &runtimeapi.UpdateContainerResourcesResponse{}, nil
  401. }
  402. func (ds *dockerService) performPlatformSpecificContainerForContainer(containerID string) {
  403. if cleanupInfo, present := ds.containerCleanupInfos[containerID]; present {
  404. ds.performPlatformSpecificContainerCleanupAndLogErrors(containerID, cleanupInfo)
  405. delete(ds.containerCleanupInfos, containerID)
  406. }
  407. }
  408. func (ds *dockerService) performPlatformSpecificContainerCleanupAndLogErrors(containerNameOrID string, cleanupInfo *containerCleanupInfo) {
  409. if cleanupInfo == nil {
  410. return
  411. }
  412. for _, err := range ds.performPlatformSpecificContainerCleanup(cleanupInfo) {
  413. klog.Warningf("error when cleaning up after container %q: %v", containerNameOrID, err)
  414. }
  415. }