container_opts_unix.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. // +build !windows
  2. package containerd
  3. import (
  4. "context"
  5. "encoding/json"
  6. "fmt"
  7. "io/ioutil"
  8. "os"
  9. "path/filepath"
  10. "syscall"
  11. "github.com/containerd/containerd/api/types"
  12. "github.com/containerd/containerd/containers"
  13. "github.com/containerd/containerd/content"
  14. "github.com/containerd/containerd/errdefs"
  15. "github.com/containerd/containerd/images"
  16. "github.com/containerd/containerd/mount"
  17. "github.com/containerd/containerd/platforms"
  18. "github.com/gogo/protobuf/proto"
  19. protobuf "github.com/gogo/protobuf/types"
  20. digest "github.com/opencontainers/go-digest"
  21. "github.com/opencontainers/image-spec/identity"
  22. "github.com/opencontainers/image-spec/specs-go/v1"
  23. "github.com/pkg/errors"
  24. )
  25. // WithCheckpoint allows a container to be created from the checkpointed information
  26. // provided by the descriptor. The image, snapshot, and runtime specifications are
  27. // restored on the container
  28. func WithCheckpoint(im Image, snapshotKey string) NewContainerOpts {
  29. // set image and rw, and spec
  30. return func(ctx context.Context, client *Client, c *containers.Container) error {
  31. var (
  32. desc = im.Target()
  33. id = desc.Digest
  34. store = client.ContentStore()
  35. )
  36. index, err := decodeIndex(ctx, store, id)
  37. if err != nil {
  38. return err
  39. }
  40. var rw *v1.Descriptor
  41. for _, m := range index.Manifests {
  42. switch m.MediaType {
  43. case v1.MediaTypeImageLayer:
  44. fk := m
  45. rw = &fk
  46. case images.MediaTypeDockerSchema2Manifest, images.MediaTypeDockerSchema2ManifestList:
  47. config, err := images.Config(ctx, store, m, platforms.Default())
  48. if err != nil {
  49. return errors.Wrap(err, "unable to resolve image config")
  50. }
  51. diffIDs, err := images.RootFS(ctx, store, config)
  52. if err != nil {
  53. return errors.Wrap(err, "unable to get rootfs")
  54. }
  55. setSnapshotterIfEmpty(c)
  56. if _, err := client.SnapshotService(c.Snapshotter).Prepare(ctx, snapshotKey, identity.ChainID(diffIDs).String()); err != nil {
  57. if !errdefs.IsAlreadyExists(err) {
  58. return err
  59. }
  60. }
  61. c.Image = index.Annotations["image.name"]
  62. case images.MediaTypeContainerd1CheckpointConfig:
  63. data, err := content.ReadBlob(ctx, store, m.Digest)
  64. if err != nil {
  65. return errors.Wrap(err, "unable to read checkpoint config")
  66. }
  67. var any protobuf.Any
  68. if err := proto.Unmarshal(data, &any); err != nil {
  69. return err
  70. }
  71. c.Spec = &any
  72. }
  73. }
  74. if rw != nil {
  75. // apply the rw snapshot to the new rw layer
  76. mounts, err := client.SnapshotService(c.Snapshotter).Mounts(ctx, snapshotKey)
  77. if err != nil {
  78. return errors.Wrapf(err, "unable to get mounts for %s", snapshotKey)
  79. }
  80. if _, err := client.DiffService().Apply(ctx, *rw, mounts); err != nil {
  81. return errors.Wrap(err, "unable to apply rw diff")
  82. }
  83. }
  84. c.SnapshotKey = snapshotKey
  85. return nil
  86. }
  87. }
  88. // WithTaskCheckpoint allows a task to be created with live runtime and memory data from a
  89. // previous checkpoint. Additional software such as CRIU may be required to
  90. // restore a task from a checkpoint
  91. func WithTaskCheckpoint(im Image) NewTaskOpts {
  92. return func(ctx context.Context, c *Client, info *TaskInfo) error {
  93. desc := im.Target()
  94. id := desc.Digest
  95. index, err := decodeIndex(ctx, c.ContentStore(), id)
  96. if err != nil {
  97. return err
  98. }
  99. for _, m := range index.Manifests {
  100. if m.MediaType == images.MediaTypeContainerd1Checkpoint {
  101. info.Checkpoint = &types.Descriptor{
  102. MediaType: m.MediaType,
  103. Size_: m.Size,
  104. Digest: m.Digest,
  105. }
  106. return nil
  107. }
  108. }
  109. return fmt.Errorf("checkpoint not found in index %s", id)
  110. }
  111. }
  112. func decodeIndex(ctx context.Context, store content.Store, id digest.Digest) (*v1.Index, error) {
  113. var index v1.Index
  114. p, err := content.ReadBlob(ctx, store, id)
  115. if err != nil {
  116. return nil, err
  117. }
  118. if err := json.Unmarshal(p, &index); err != nil {
  119. return nil, err
  120. }
  121. return &index, nil
  122. }
  123. // WithRemappedSnapshot creates a new snapshot and remaps the uid/gid for the
  124. // filesystem to be used by a container with user namespaces
  125. func WithRemappedSnapshot(id string, i Image, uid, gid uint32) NewContainerOpts {
  126. return withRemappedSnapshotBase(id, i, uid, gid, false)
  127. }
  128. // WithRemappedSnapshotView is similar to WithRemappedSnapshot but rootfs is mounted as read-only.
  129. func WithRemappedSnapshotView(id string, i Image, uid, gid uint32) NewContainerOpts {
  130. return withRemappedSnapshotBase(id, i, uid, gid, true)
  131. }
  132. func withRemappedSnapshotBase(id string, i Image, uid, gid uint32, readonly bool) NewContainerOpts {
  133. return func(ctx context.Context, client *Client, c *containers.Container) error {
  134. diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore(), platforms.Default())
  135. if err != nil {
  136. return err
  137. }
  138. setSnapshotterIfEmpty(c)
  139. var (
  140. snapshotter = client.SnapshotService(c.Snapshotter)
  141. parent = identity.ChainID(diffIDs).String()
  142. usernsID = fmt.Sprintf("%s-%d-%d", parent, uid, gid)
  143. )
  144. if _, err := snapshotter.Stat(ctx, usernsID); err == nil {
  145. if _, err := snapshotter.Prepare(ctx, id, usernsID); err == nil {
  146. c.SnapshotKey = id
  147. c.Image = i.Name()
  148. return nil
  149. } else if !errdefs.IsNotFound(err) {
  150. return err
  151. }
  152. }
  153. mounts, err := snapshotter.Prepare(ctx, usernsID+"-remap", parent)
  154. if err != nil {
  155. return err
  156. }
  157. if err := remapRootFS(mounts, uid, gid); err != nil {
  158. snapshotter.Remove(ctx, usernsID)
  159. return err
  160. }
  161. if err := snapshotter.Commit(ctx, usernsID, usernsID+"-remap"); err != nil {
  162. return err
  163. }
  164. if readonly {
  165. _, err = snapshotter.View(ctx, id, usernsID)
  166. } else {
  167. _, err = snapshotter.Prepare(ctx, id, usernsID)
  168. }
  169. if err != nil {
  170. return err
  171. }
  172. c.SnapshotKey = id
  173. c.Image = i.Name()
  174. return nil
  175. }
  176. }
  177. func remapRootFS(mounts []mount.Mount, uid, gid uint32) error {
  178. root, err := ioutil.TempDir("", "ctd-remap")
  179. if err != nil {
  180. return err
  181. }
  182. defer os.Remove(root)
  183. for _, m := range mounts {
  184. if err := m.Mount(root); err != nil {
  185. return err
  186. }
  187. }
  188. err = filepath.Walk(root, incrementFS(root, uid, gid))
  189. if uerr := mount.Unmount(root, 0); err == nil {
  190. err = uerr
  191. }
  192. return err
  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. }