container_opts_unix.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. // +build !windows
  2. /*
  3. Copyright The containerd Authors.
  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. http://www.apache.org/licenses/LICENSE-2.0
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. */
  14. package containerd
  15. import (
  16. "context"
  17. "encoding/json"
  18. "fmt"
  19. "os"
  20. "path/filepath"
  21. "syscall"
  22. "github.com/containerd/containerd/api/types"
  23. "github.com/containerd/containerd/containers"
  24. "github.com/containerd/containerd/content"
  25. "github.com/containerd/containerd/errdefs"
  26. "github.com/containerd/containerd/images"
  27. "github.com/containerd/containerd/linux/runctypes"
  28. "github.com/containerd/containerd/mount"
  29. "github.com/containerd/containerd/platforms"
  30. "github.com/gogo/protobuf/proto"
  31. protobuf "github.com/gogo/protobuf/types"
  32. digest "github.com/opencontainers/go-digest"
  33. "github.com/opencontainers/image-spec/identity"
  34. "github.com/opencontainers/image-spec/specs-go/v1"
  35. "github.com/pkg/errors"
  36. )
  37. // WithCheckpoint allows a container to be created from the checkpointed information
  38. // provided by the descriptor. The image, snapshot, and runtime specifications are
  39. // restored on the container
  40. func WithCheckpoint(im Image, snapshotKey string) NewContainerOpts {
  41. // set image and rw, and spec
  42. return func(ctx context.Context, client *Client, c *containers.Container) error {
  43. var (
  44. desc = im.Target()
  45. id = desc.Digest
  46. store = client.ContentStore()
  47. )
  48. index, err := decodeIndex(ctx, store, id)
  49. if err != nil {
  50. return err
  51. }
  52. var rw *v1.Descriptor
  53. for _, m := range index.Manifests {
  54. switch m.MediaType {
  55. case v1.MediaTypeImageLayer:
  56. fk := m
  57. rw = &fk
  58. case images.MediaTypeDockerSchema2Manifest, images.MediaTypeDockerSchema2ManifestList:
  59. config, err := images.Config(ctx, store, m, platforms.Default())
  60. if err != nil {
  61. return errors.Wrap(err, "unable to resolve image config")
  62. }
  63. diffIDs, err := images.RootFS(ctx, store, config)
  64. if err != nil {
  65. return errors.Wrap(err, "unable to get rootfs")
  66. }
  67. setSnapshotterIfEmpty(c)
  68. if _, err := client.SnapshotService(c.Snapshotter).Prepare(ctx, snapshotKey, identity.ChainID(diffIDs).String()); err != nil {
  69. if !errdefs.IsAlreadyExists(err) {
  70. return err
  71. }
  72. }
  73. c.Image = index.Annotations["image.name"]
  74. case images.MediaTypeContainerd1CheckpointConfig:
  75. data, err := content.ReadBlob(ctx, store, m.Digest)
  76. if err != nil {
  77. return errors.Wrap(err, "unable to read checkpoint config")
  78. }
  79. var any protobuf.Any
  80. if err := proto.Unmarshal(data, &any); err != nil {
  81. return err
  82. }
  83. c.Spec = &any
  84. }
  85. }
  86. if rw != nil {
  87. // apply the rw snapshot to the new rw layer
  88. mounts, err := client.SnapshotService(c.Snapshotter).Mounts(ctx, snapshotKey)
  89. if err != nil {
  90. return errors.Wrapf(err, "unable to get mounts for %s", snapshotKey)
  91. }
  92. if _, err := client.DiffService().Apply(ctx, *rw, mounts); err != nil {
  93. return errors.Wrap(err, "unable to apply rw diff")
  94. }
  95. }
  96. c.SnapshotKey = snapshotKey
  97. return nil
  98. }
  99. }
  100. // WithTaskCheckpoint allows a task to be created with live runtime and memory data from a
  101. // previous checkpoint. Additional software such as CRIU may be required to
  102. // restore a task from a checkpoint
  103. func WithTaskCheckpoint(im Image) NewTaskOpts {
  104. return func(ctx context.Context, c *Client, info *TaskInfo) error {
  105. desc := im.Target()
  106. id := desc.Digest
  107. index, err := decodeIndex(ctx, c.ContentStore(), id)
  108. if err != nil {
  109. return err
  110. }
  111. for _, m := range index.Manifests {
  112. if m.MediaType == images.MediaTypeContainerd1Checkpoint {
  113. info.Checkpoint = &types.Descriptor{
  114. MediaType: m.MediaType,
  115. Size_: m.Size,
  116. Digest: m.Digest,
  117. }
  118. return nil
  119. }
  120. }
  121. return fmt.Errorf("checkpoint not found in index %s", id)
  122. }
  123. }
  124. func decodeIndex(ctx context.Context, store content.Provider, id digest.Digest) (*v1.Index, error) {
  125. var index v1.Index
  126. p, err := content.ReadBlob(ctx, store, id)
  127. if err != nil {
  128. return nil, err
  129. }
  130. if err := json.Unmarshal(p, &index); err != nil {
  131. return nil, err
  132. }
  133. return &index, nil
  134. }
  135. // WithRemappedSnapshot creates a new snapshot and remaps the uid/gid for the
  136. // filesystem to be used by a container with user namespaces
  137. func WithRemappedSnapshot(id string, i Image, uid, gid uint32) NewContainerOpts {
  138. return withRemappedSnapshotBase(id, i, uid, gid, false)
  139. }
  140. // WithRemappedSnapshotView is similar to WithRemappedSnapshot but rootfs is mounted as read-only.
  141. func WithRemappedSnapshotView(id string, i Image, uid, gid uint32) NewContainerOpts {
  142. return withRemappedSnapshotBase(id, i, uid, gid, true)
  143. }
  144. func withRemappedSnapshotBase(id string, i Image, uid, gid uint32, readonly bool) NewContainerOpts {
  145. return func(ctx context.Context, client *Client, c *containers.Container) error {
  146. diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore(), platforms.Default())
  147. if err != nil {
  148. return err
  149. }
  150. setSnapshotterIfEmpty(c)
  151. var (
  152. snapshotter = client.SnapshotService(c.Snapshotter)
  153. parent = identity.ChainID(diffIDs).String()
  154. usernsID = fmt.Sprintf("%s-%d-%d", parent, uid, gid)
  155. )
  156. if _, err := snapshotter.Stat(ctx, usernsID); err == nil {
  157. if _, err := snapshotter.Prepare(ctx, id, usernsID); err == nil {
  158. c.SnapshotKey = id
  159. c.Image = i.Name()
  160. return nil
  161. } else if !errdefs.IsNotFound(err) {
  162. return err
  163. }
  164. }
  165. mounts, err := snapshotter.Prepare(ctx, usernsID+"-remap", parent)
  166. if err != nil {
  167. return err
  168. }
  169. if err := remapRootFS(ctx, mounts, uid, gid); err != nil {
  170. snapshotter.Remove(ctx, usernsID)
  171. return err
  172. }
  173. if err := snapshotter.Commit(ctx, usernsID, usernsID+"-remap"); err != nil {
  174. return err
  175. }
  176. if readonly {
  177. _, err = snapshotter.View(ctx, id, usernsID)
  178. } else {
  179. _, err = snapshotter.Prepare(ctx, id, usernsID)
  180. }
  181. if err != nil {
  182. return err
  183. }
  184. c.SnapshotKey = id
  185. c.Image = i.Name()
  186. return nil
  187. }
  188. }
  189. func remapRootFS(ctx context.Context, mounts []mount.Mount, uid, gid uint32) error {
  190. return mount.WithTempMount(ctx, mounts, func(root string) error {
  191. return filepath.Walk(root, incrementFS(root, uid, gid))
  192. })
  193. }
  194. func incrementFS(root string, uidInc, gidInc uint32) filepath.WalkFunc {
  195. return func(path string, info os.FileInfo, err error) error {
  196. if err != nil {
  197. return err
  198. }
  199. var (
  200. stat = info.Sys().(*syscall.Stat_t)
  201. u, g = int(stat.Uid + uidInc), int(stat.Gid + gidInc)
  202. )
  203. // be sure the lchown the path as to not de-reference the symlink to a host file
  204. return os.Lchown(path, u, g)
  205. }
  206. }
  207. // WithNoPivotRoot instructs the runtime not to you pivot_root
  208. func WithNoPivotRoot(_ context.Context, _ *Client, info *TaskInfo) error {
  209. if info.Options == nil {
  210. info.Options = &runctypes.CreateOptions{
  211. NoPivotRoot: true,
  212. }
  213. return nil
  214. }
  215. copts, ok := info.Options.(*runctypes.CreateOptions)
  216. if !ok {
  217. return errors.New("invalid options type, expected runctypes.CreateOptions")
  218. }
  219. copts.NoPivotRoot = true
  220. return nil
  221. }