【docker 17 源码分析】 Docker 镜像源码分析

桃扇骨 2022-03-11 08:24 444阅读 0赞
  1. Dockergraph driver主要用于管理和维护[镜像][Link 1],包括把镜像从仓库下载下来,到运行时把镜像挂载起来可以被容器访问等

目前docker支持的graph driver有:

  • Overlay
  • Aufs
  • Devicemapper
  • Btrfs
  • Zfs
  • Vfs

Docker镜像概念

  • rootfs: 容器进程可见的文件系统、工具、容器文件等
  • Union mount:多种文件系统内容合并后的目录,较为常见的有UnionFS、AUFS、OverlayFS等
  • layer: Docker容器中的每一层只读的image,以及最上层可读写的文件系统,均被称为layer

镜像基于内容寻址

  1. docker1.10推翻了之前的镜像管理方式,重新开发了基于内容寻址的策略。该策略至少有3个好处:
  • 提高了安全性
  • 避免了ID冲突
  • 确保数据完整性

    基于内容寻址的实现,使用了两个目录:/var/lib/docker/image和/var/lib/docker/overlay, 后面的这个根据存储驱动的名称不同,而目录名不同。image目录保存了image的内容(sha256)数据。overlay目录保持了image的真实数据。

写时复制策略

  1. 每个container都有自己的读写layer,对镜像文件的修改和删除操作都会先执行镜像文件拷贝到读写layer的操作,然后对读写layer的文件进行修改和删除。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pob25nbGluemhhbmc_size_16_color_FFFFFF_t_70

  1. Docker镜像的内容主要包含两个部分:第一,镜像层文件内容;第二,镜像json文件
  2. 静态的镜像不包含的
  • 1./proc以及/sys等虚拟文件系统的内容
  • 2.容器的hosts文件,hostname文件以及resolv.conf文件,这些事具体环境的信息,原则上的确不应该被打入镜像。
  • 3.容器的Volume路径,这部分的视角来源于从宿主机上挂载到容器内部的路径
  • 4.部分的设备文件

image存放路径解析

  1. 对于overlay2存储驱动路径为/var/lib/docker/image/overlay2

1. repositories.json:

  1. 存储imageimage-id信息,主要是nameimage id的对应关系

2. imagedb目录

  1. content/sha256: 每一个镜像的配置信息,其id都是image-id,文件内容的sha256

3. distribution目录

  1. diffid-by-digest: digestdiffid的对应关系

# cat 85199fa09ec1510ee053da666286c385d07b8fab9fa61ae73a5b8c454e1b3397
sha256:919f29a58bd0ef96e5ed82bc7da07cafaea5e712acfd4a4b4558e06d57d7c2fc

  1. v2metadata-by-diffiddiffiddigest的对应关系

# cat 919f29a58bd0ef96e5ed82bc7da07cafaea5e712acfd4a4b4558e06d57d7c2fc | python -m json.tool
[
{
“Digest”: “sha256:85199fa09ec1510ee053da666286c385d07b8fab9fa61ae73a5b8c454e1b3397”,
“HMAC”: “”,
“SourceRepository”: “docker.io/library/buildpack-deps”
},
{
“Digest”: “sha256:85199fa09ec1510ee053da666286c385d07b8fab9fa61ae73a5b8c454e1b3397”,
“HMAC”: “3ba1c2a458861768fa3153832272d291557d1d08951b29b40b1e47d95a91c09f”,
“SourceRepository”: “docker.io/zhangzhonglin/docker-dev”
},
{
“Digest”: “sha256:85199fa09ec1510ee053da666286c385d07b8fab9fa61ae73a5b8c454e1b3397”,
“HMAC”: “”,
“SourceRepository”: “docker.io/zhangzhonglin/docker-dev”
},
{
“Digest”: “sha256:85199fa09ec1510ee053da666286c385d07b8fab9fa61ae73a5b8c454e1b3397”,
“HMAC”: “”,
“SourceRepository”: “docker.io/library/debian”
}
]

4. layerdb目录-元数据属性信息

  1. 目录名称是layerchainid,由于最底层的layerchainiddiffid相同,比如centos:7.2.1511就是一层,chainid用到了所有祖先layer的信息,从而能保证根据chainid得到的rootfs是唯一的
  2. cache-id: 每一个层生成的uuid,神马鬼值,干啥用?????????????????
  3. diff: 最上层的layerid,也就是为这个chain附加上去的层,是一个layer-id
  4. size: 单位字节
  5. tar-split.json.gz: 压缩该层的压缩包

20190308150247447.png

目录 /var/lib/docker/overlay2

  1. 存放的是镜像的每一层layer解压后的,以及基于每一个镜像生成容器后,对镜像合并挂载后的目录和对应的init目录
  • /var/lib/docker/overlay2//merged: 所有镜像层合并后的,就是容器中进程看到的
  • /var/lib/docker/overlay2//upper: 只读的上层
  • /var/lib/docker/overlay2//work:用来做cow相关操作的
  • /var/lib/docker/overlay2//diff: 容器层的读写层

    “GraphDriver”: {

    1. "Data": {
    2. "LowerDir": "/var/lib/docker/overlay2/713057c5c2f360df298bed473d78999515d9939d77d2e2ecb21afc09ceff3530-init/diff:/var/lib/docker/overlay2/8aff7f83b84a179e0d0685f6898ad6c969e9e5c8160c5118548c61bb296c1001/diff",
    3. "MergedDir": "/var/lib/docker/overlay2/713057c5c2f360df298bed473d78999515d9939d77d2e2ecb21afc09ceff3530/merged",
    4. "UpperDir": "/var/lib/docker/overlay2/713057c5c2f360df298bed473d78999515d9939d77d2e2ecb21afc09ceff3530/diff",
    5. "WorkDir": "/var/lib/docker/overlay2/713057c5c2f360df298bed473d78999515d9939d77d2e2ecb21afc09ceff3530/work"
    6. },
    7. "Name": "overlay2"
    8. }

为什么digest?

  1. 镜像的内容变了,但镜像的名称和tag没有变,所以会造成前后两次通过同样的名称和tag从服务器得到不同的两个镜像的问题,于是docker引入了镜像的digest的概念。
  2. 一个镜像的digest就是镜像的manifes文件的sha256码,当镜像的内容发生变化的时候,即镜像的layer发生变化,从而layersha256发生变化,而manifest里面包含了每一个layersha256,所以manifestsha256也会发生变化,即镜像的digest发生变化,这样就保证了digest能唯一的对应一个镜像。

初始化注册

  1. 全局变量drivers,所有存储驱动注册
  2. // All registered drivers
  3. drivers map[string]InitFunc
  4. 所有存储驱动文件都有该init函数,注册到全局变量drivers
  5. func init() {
  6. graphdriver.Register(driverName, Init)
  7. }

一. NewDaemon函数

路径: daemon/daemon.go

1.1 调用NewManager实例化

  1. root路径默认为:/var/lib/dockerexecRoot路径为: /run/docker/plugins
  2. d.pluginManager, err = plugin.NewManager(plugin.ManagerConfig{
  3. Root: filepath.Join(config.Root, "plugins"),
  4. ExecRoot: getPluginExecRoot(config.Root),
  5. Store: d.PluginStore,
  6. Executor: containerdRemote,
  7. RegistryService: registryService,
  8. LiveRestoreEnabled: config.LiveRestoreEnabled,
  9. LogPluginEvent: d.LogPluginEvent, // todo: make private
  10. AuthzMiddleware: config.AuthzMiddleware,
  11. })

1.2 实例化layerStore

  1. 路径为
  2. d.layerStore, err = layer.NewStoreFromOptions(layer.StoreOptions{
  3. StorePath: config.Root,
  4. MetadataStorePathTemplate: filepath.Join(config.Root, "image", "%s", "layerdb"),
  5. GraphDriver: driverName,
  6. GraphDriverOptions: config.GraphOptions,
  7. UIDMaps: uidMaps,
  8. GIDMaps: gidMaps,
  9. PluginGetter: d.PluginStore,
  10. ExperimentalEnabled: config.Experimental,
  11. })

1.2.1 NewStoreFromOptions函数

  1. 调用daemon/graphdriver/driver.go函数中的New方法,结构体store实现了Store接口
  2. // NewStoreFromOptions creates a new Store instance
  3. func NewStoreFromOptions(options StoreOptions) (Store, error) {
  4. driver, err := graphdriver.New(options.GraphDriver, options.PluginGetter, graphdriver.Options{
  5. Root: options.StorePath,
  6. DriverOptions: options.GraphDriverOptions,
  7. UIDMaps: options.UIDMaps,
  8. GIDMaps: options.GIDMaps,
  9. ExperimentalEnabled: options.ExperimentalEnabled,
  10. })
  11. if err != nil {
  12. return nil, fmt.Errorf("error initializing graphdriver: %v", err)
  13. }
  14. logrus.Debugf("Using graph driver %s", driver)
  15. fms, err := NewFSMetadataStore(fmt.Sprintf(options.MetadataStorePathTemplate, driver))
  16. if err != nil {
  17. return nil, err
  18. }
  19. return NewStoreFromGraphDriver(fms, driver)
  20. }

1.2.1.1 New函数

  1. 路径daemon/graphdriver/driver.go,一点点继续分析
  2. // New creates the driver and initializes it at the specified root.
  3. func New(name string, pg plugingetter.PluginGetter, config Options) (Driver, error) {
  4. if name != "" {
  5. logrus.Debugf("[graphdriver] trying provided driver: %s", name) // so the logs show specified driver
  6. return GetDriver(name, pg, config)
  7. }
  8. 遍历一个slice这个是定义顺序的存储驱动,getBuiltinDriver函数如果有存储驱动直接调用initFunc初始化,就是各个存储驱动注册的Init函数,讲解overlay2的存储驱动
  9. for _, name := range priority {
  10. if name == "vfs" {
  11. // don't use vfs even if there is state present.
  12. continue
  13. }
  14. if _, prior := driversMap[name]; prior {
  15. // of the state found from prior drivers, check in order of our priority
  16. // which we would prefer
  17. driver, err := getBuiltinDriver(name, config.Root, config.DriverOptions, config.UIDMaps, config.GIDMaps)
  18. // abort starting when there are other prior configured drivers
  19. // to ensure the user explicitly selects the driver to load
  20. if len(driversMap)-1 > 0 {
  21. var driversSlice []string
  22. for name := range driversMap {
  23. driversSlice = append(driversSlice, name)
  24. }
  25. return nil, fmt.Errorf("%s contains several valid graphdrivers: %s; Please cleanup or explicitly choose storage driver (-s <DRIVER>)", config.Root, strings.Join(driversSlice, ", "))
  26. }
  27. logrus.Infof("[graphdriver] using prior storage driver: %s", name)
  28. return driver, nil
  29. }
  30. }

二. overlay2存储驱动

2.1 Init函数

  1. 路径: daemon/graphdriver/overlay2/overlay.go
  2. // Init returns the a native diff driver for overlay filesystem.
  3. // If overlay filesystem is not supported on the host, graphdriver.ErrNotSupported is returned as error.
  4. // If an overlay filesystem is not supported over an existing filesystem then error graphdriver.ErrIncompatibleFS is returned.
  5. func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
  6. opts, err := parseOptions(options)
  7. if err != nil {
  8. return nil, err
  9. }

2.1.1 supportsOverlay函数

  1. 验证是否支持overlay,验证方式为读取/proc/filesystems,比如我的环境:

# cat /proc/filesystems
………………
nodev overlay
fuseblk
nodev fuse
nodev fusectl

  1. func supportsOverlay() error {
  2. // We can try to modprobe overlay first before looking at
  3. // proc/filesystems for when overlay is supported
  4. exec.Command("modprobe", "overlay").Run()
  5. f, err := os.Open("/proc/filesystems")
  6. if err != nil {
  7. return err
  8. }
  9. defer f.Close()
  10. s := bufio.NewScanner(f)
  11. for s.Scan() {
  12. if s.Text() == "nodev\toverlay" {
  13. return nil
  14. }
  15. }
  16. logrus.Error("'overlay' not found as a supported filesystem on this host. Please ensure kernel is new enough and has overlay support loaded.")
  17. return graphdriver.ErrNotSupported
  18. }

d.naiveDiff = graphdriver.NewNaiveDiffDriver(d, uidMaps, gidMaps)这块就是包裹了一下,实现了四个主要接口:

  1. // Diff(id, parent string) (archive.Archive, error)
  2. // Changes(id, parent string) ([]archive.Change, error)
  3. // ApplyDiff(id, parent string, diff archive.Reader) (size int64, err error)
  4. // DiffSize(id, parent string) (size int64, err error)
  1. // NewNaiveDiffDriver returns a fully functional driver that wraps the
  2. // given ProtoDriver and adds the capability of the following methods which
  3. // it may or may not support on its own:
  4. // Diff(id, parent string) (archive.Archive, error)
  5. // Changes(id, parent string) ([]archive.Change, error)
  6. // ApplyDiff(id, parent string, diff archive.Reader) (size int64, err error)
  7. // DiffSize(id, parent string) (size int64, err error)
  8. func NewNaiveDiffDriver(driver ProtoDriver, uidMaps, gidMaps []idtools.IDMap) Driver {
  9. return &NaiveDiffDriver{ProtoDriver: driver,
  10. uidMaps: uidMaps,
  11. gidMaps: gidMaps}
  12. }

2.1.2 NewStoreFromGraphDriver函数

  1. // NewStoreFromGraphDriver creates a new Store instance using the provided
  2. // metadata store and graph driver. The metadata store will be used to restore
  3. // the Store.
  4. func NewStoreFromGraphDriver(store MetadataStore, driver graphdriver.Driver) (Store, error) {
  5. caps := graphdriver.Capabilities{}
  6. if capDriver, ok := driver.(graphdriver.CapabilityDriver); ok {
  7. caps = capDriver.Capabilities()
  8. }

NewFSStoreBackend创建的是文件系统存储,路径为/var/lib/docker/image/overlay2/imagedb,该文件下创建两个目录分别为content, metadata,这俩文件都含有shan256目录

  1. // NewFSStoreBackend returns new filesystem based backend for image.Store
  2. func NewFSStoreBackend(root string) (StoreBackend, error) {
  3. return newFSStore(root)
  4. }
  5. func newFSStore(root string) (*fs, error) {
  6. s := &fs{
  7. root: root,
  8. }
  9. if err := os.MkdirAll(filepath.Join(root, contentDirName, string(digest.Canonical)), 0700); err != nil {
  10. return nil, errors.Wrap(err, "failed to create storage backend")
  11. }
  12. if err := os.MkdirAll(filepath.Join(root, metadataDirName, string(digest.Canonical)), 0700); err != nil {
  13. return nil, errors.Wrap(err, "failed to create storage backend")
  14. }
  15. return s, nil
  16. }
  17. NewImageStore函数用来缓存当前镜像以及层信息
  18. // NewImageStore returns new store object for given layer store
  19. func NewImageStore(fs StoreBackend, ls LayerGetReleaser) (Store, error) {
  20. is := &store{
  21. ls: ls,
  22. images: make(map[ID]*imageMeta),
  23. fs: fs,
  24. digestSet: digestset.NewSet(),
  25. }
  26. // load all current images and retain layers
  27. if err := is.restore(); err != nil {
  28. return nil, err
  29. }
  30. return is, nil
  31. }

总结一下:

  1. NewDaemon函数中,定义并创建了一大堆目录,存储后端,将层结构信息存储images

发表评论

表情:
评论列表 (有 0 条评论,444人围观)

还没有评论,来说两句吧...

相关阅读