【博客618】docker容器重启后读写层数据并不丢失的原理

这篇具有很好参考价值的文章主要介绍了【博客618】docker容器重启后读写层数据并不丢失的原理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

docker容器重启后读写层数据并不丢失的原理

1、场景

当我们对docker容器执行restart后,其实容器中原本读写层里对临时数据还在。只有我们删除了这个容器,重新创建的容器是基于镜像的只读层然后挂载上新的空的读写层,此时临时数据是不在的

2、前置知识

镜像,静态容器,运行时容器之间的区别
  • Image:统一只读文件系统)

  • 静态容器 :未运行的容器,统一可读写文件系统

  • 运行时容器:运行中的容器,进程空间(包括进程)+ 统一可读写文件系统

docker run,create,start之间的区别

docker run相当于执行了两步操作:将镜像放入容器中(docker create),然后将容器启动,使之变成运行时容器(docker start)。而docker start的作用是,重新启动已存在的镜像。也就是说,如果使用这个命令,我们必须事先知道这个容器的ID,或者这个容器的名字,我们可以使用docker ps找到这个容器的信息。
【博客618】docker容器重启后读写层数据并不丢失的原理

docker常见命令的区别:
  • docker create < image-id >

    该命令即为在只读文件系统上添加一层可读写层「Top Layer」,并生成可读写文件系统。该命令状态下容器为静态容器,并没有运行。

  • docker start | restart < container-id >

    该命令即为在可读写文件系统添加一个进程空间和运行的进程,并生成一个动态容器。

  • docker run < image-id >

    docker run = docker create + docker start

  • docker stop < container-id >

    该指令向运行中的容器发一个 SIGTERM 信号,然后停止所有的进程。即为 docker start 的逆过程。

  • docker kill < container-id >

    该指令向容器发送一个不友好的 SIGKILL 信号,相当于快速强制关闭容器。与 docker stop 的区别是 docker stop 是先发 SIGTERM 信号来清理进程,然后再发 SIGKILL 信号退出,整个进程是正常关闭的。

  • docker pause < container-id >

    该指令用作暂停容器中的所有进程,使用 cgroup 的 freezer 顺序暂停容器里的所有进程。

  • docker commit < container-id >

    该指令用作把容器的可读写层转化成只读层,即从容器状态「可读写文件系统」变为镜像状态「只读文件系统」,可理解为固化。

  • docker build

    docker build = docker run 「运行容器 + 进程修改数据」+ docker commit「固化数据」,整个过程不断循环直至生成所需镜像。循环一次便会形成一个新的层(新镜像 = 原镜像层 + 已固化的可读写层)。docker build 过程一般通过 dockerfile 文件来实现。

docker容器生命周期

【博客618】docker容器重启后读写层数据并不丢失的原理

3、docker容器重启后读写层数据并不丢失的原理

容器创建与启动的流程:
【博客618】docker容器重启后读写层数据并不丢失的原理

docker创建容器和运行容器源码剖析:

docker run命令其实是由两部分组成:create和start:

  • 创建容器的逻辑(create):

    • 获取镜像ID GetImage

    • 合并容器配置

    • 合并日志配置

    • 创建容器对象 newContainer

      if container, err = daemon.newContainer(params.Name, params.Config, imgID, managed); err != nil {
      return nil, err
      }

    • 设置安全选项

    • 设置容器读写层

      if err := daemon.setRWLayer(container); err != nil {
      return nil, err
      }

    • 创建文件夹保存容器配置信息

      //创建文件夹,用于保存容器的配置信息,在/var/lib/docker/containers/id下,并赋予容器进程的读写权限
      if err := idtools.MkdirAs(container.Root, 0700, rootUID, rootGID); err != nil {
      return nil, err
      }

      //把配置文件保存到磁盘
      if err := daemon.setHostConfig(container, params.HostConfig); err != nil {
      return nil, err
      }

    • 保存到硬盘

      if err := daemon.updateContainerNetworkSettings(container, endpointsConfigs); err != nil {
      return nil, err
      }

    • 注册到daemon

  • 启动容器的逻辑(start):

    • 找到容器对象实例

      container, err := daemon.GetContainer(name)
      if err != nil {
      return err
      }

    • 判断如果暂停的容器不能启动,先unpause再启动

      if container.IsPaused() {
      return fmt.Errorf(“Cannot start a paused container, try unpause instead.”)
      }

    • 判断如果是运行的容器不用启动

      if container.IsRunning() {
      err := fmt.Errorf(“Container already started”)
      return errors.NewErrorWithStatusCode(err, http.StatusNotModified)
      }

    • 确认hostconfig与当前系统是否一致

      if _, err = daemon.verifyContainerSettings(container.HostConfig, nil, false, validateHostname); err != nil {
      return err
      }

    • 调整旧版容器设置:主要是cpu、内存限制的校验和设置

      if err := daemon.adaptContainerSettings(container.HostConfig, false); err != nil {
      return err
      }

    • 开始启动容器

      • 容器对象加锁

        container.Lock()
        defer container.Unlock()

      • 状态校验,如该已经运行,直接返回

        if container.Running {
        return nil
        }

      • 挂载读写层

        dir, err := container.RWLayer.Mount(container.GetMountLabel())
        if err != nil {
        return err
        }

      • 初始化网络

      • containerd 调用runc

      • 进入runc启动容器

4、创建容器读写层源码

func (daemon *Daemon) setRWLayer(container *container.Container) error {
       var layerID layer.ChainID
       if container.ImageID != "" {
              img, err := daemon.stores[container.Platform].imageStore.Get(container.ImageID)
              layerID = img.RootFS.ChainID()
       }

       rwLayerOpts := &layer.CreateRWLayerOpts{
              MountLabel: container.MountLabel,
              InitFunc:   daemon.getLayerInit(),
              StorageOpt: container.HostConfig.StorageOpt,
       }

       rwLayer, err := daemon.stores[container.Platform].layerStore.CreateRWLayer(container.ID, layerID, rwLayerOpts)
       container.RWLayer = rwLayer

       return nil
}
func (ls *layerStore) CreateRWLayer(name string, parent ChainID, opts *CreateRWLayerOpts) (RWLayer, error) {
       if opts != nil {
              mountLabel = opts.MountLabel
              storageOpt = opts.StorageOpt
              initFunc = opts.InitFunc
       }

       ls.mountL.Lock()
       defer ls.mountL.Unlock()
       m, ok := ls.mounts[name]

       if string(parent) != "" {
              p = ls.get(parent)
              if p == nil {
                     return nil, ErrLayerDoesNotExist
              }
              pid = p.cacheID
       }

       m = &mountedLayer{
              name:       name,
              parent:     p,
              mountID:    ls.mountID(name),
              layerStore: ls,
              references: map[RWLayer]*referencedRWLayer{},
       }

       if initFunc != nil {
              pid, err = ls.initMount(m.mountID, pid, mountLabel, initFunc, storageOpt)
              m.initID = pid
       }

       createOpts := &graphdriver.CreateOpts{
              StorageOpt: storageOpt,
       }

       if err = ls.driver.CreateReadWrite(m.mountID, pid, createOpts); err != nil {
     
       if err = ls.saveMount(m); err != nil {
     
       return m.getReference(), nil
}
func (ls *layerStore) initMount(graphID, parent, mountLabel string, initFunc MountInit, storageOpt map[string]string) (string, error) {
       // Use "<graph-id>-init" to maintain compatibility with graph drivers
       // which are expecting this layer with this special name. If all
       // graph drivers can be updated to not rely on knowing about this layer
       // then the initID should be randomly generated.
       initID := fmt.Sprintf("%s-init", graphID)

       createOpts := &graphdriver.CreateOpts{
              MountLabel: mountLabel,
              StorageOpt: storageOpt,
       }

       if err := ls.driver.CreateReadWrite(initID, parent, createOpts); err != nil {
              return "", err
       }
       p, err := ls.driver.Get(initID, "")
       if err != nil {
              return "", err
       }

       if err := initFunc(p); err != nil {
              ls.driver.Put(initID)
              return "", err
       }

       if err := ls.driver.Put(initID); err != nil {
              return "", err
       }

       return initID, nil
}

CreateReadWrite 函数如下, 在 /var/lib/docker/aufs 目录下创建两个文件 mnt 和 diff,创建 /var/lib/docker/aufs/layers/${id} 文件,获得该层的父层,记录所有父层 id 该文件

func (a *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error {
       return a.Create(id, parent, opts)
}

// Create three folders for each id
// mnt, layers, and diff
func (a *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error{
       if err := a.createDirsFor(id); err != nil {
              return err
       }
       // Write the layers metadata
       f, err := os.Create(path.Join(a.rootPath(), "layers", id))

       if parent != "" {
              ids, err := getParentIDs(a.rootPath(), parent)

              if _, err := fmt.Fprintln(f, parent); err != nil {
         
              for _, i := range ids {
                     if _, err := fmt.Fprintln(f, i); err != nil {
                    
       }

       return nil
}

saveMount 函数是在 /var/lib/image/aufs/layerdb/mounts目录下操作,如下所示:

func (ls *layerStore) saveMount(mount *mountedLayer) error {
       if err := ls.store.SetMountID(mount.name, mount.mountID); err != nil {
   
       if mount.initID != "" {
              if err := ls.store.SetInitID(mount.name, mount.initID); err != nil {

       if mount.parent != nil {
              if err := ls.store.SetMountParent(mount.name, mount.parent.chainID); err != nil {

       ls.mounts[mount.name] = mount

       return nil
}

SetMountID 函数位置 layer/filestore.go,主要是在 /var/lib/docker/image/aufs/layerdb/mounts 目录下创建层,将 ${mount-id} 写入 mount-id 文件

func (fms *fileMetadataStore) SetMountID(mount string, mountID string) error {
       if err := os.MkdirAll(fms.getMountDirectory(mount), 0755); err != nil {
              return err
       }
       return ioutil.WriteFile(fms.getMountFilename(mount, "mount-id"), []byte(mountID), 0644)
}

SetInitID 主要是在 ${mount-id}-init 写入 init-id 文件

func (fms *fileMetadataStore) SetInitID(mount string, init string) error {
       if err := os.MkdirAll(fms.getMountDirectory(mount), 0755); err != nil {
              return err
       }
       return ioutil.WriteFile(fms.getMountFilename(mount, "init-id"), []byte(init), 0644)
}

SetMountParent 将父层 image 记录 parent 文件

func (fms *fileMetadataStore) SetMountParent(mount string, parent ChainID) error {
       if err := os.MkdirAll(fms.getMountDirectory(mount), 0755); err != nil {
              return err
       }
       return ioutil.WriteFile(fms.getMountFilename(mount, "parent"), []byte(digest.Digest(parent).String()), 0644)
}

总结

docker run的时候其实是由create和start来完成的,create创建容器的时候会调用setRWLayer(container)创建读写层,start的时候会调用container.RWLayer.Mount(container.GetMountLabel())挂载读写层。restart的时候,则会使用新的镜像只读层 + 挂载当前容器的读写层,因为容器重启并不会丢失那些临时修改文章来源地址https://www.toymoban.com/news/detail-429551.html

到了这里,关于【博客618】docker容器重启后读写层数据并不丢失的原理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • docker 设置容器总是重启,重启策略(记录)

    在创建 Docker 容器时,我们可以通过添加 --restart always 参数来使容器在出现问题时自动重启。但是有时候我们可能会忘记设置这个参数,这时候可以使用 docker update 命令来修改容器的重启策略。 使用以下命令可以将容器的重启策略设置为 always: docker update --restart always 上述命

    2024年02月16日
    浏览(65)
  • docker容器重启策略

    当使用docker run命令运行一个容器的时候,使用--restart选项可以配置该容器的重启策略。 no 不要自动重启容器(默认) on-failure[:max-retries] 如果容器发生错误导致退出(非0退出状态),则重启该容器。可以使用 :max-retries 选项限制尝试重启的次数。 always 如果容器退出了,总是

    2023年04月18日
    浏览(49)
  • docker重启容器

    2024年02月16日
    浏览(57)
  • docker容器重启故障

    强杀docker进程后,重启docker。docker中的容器无法启动并报错 docker restart XXXXXXX Error response from daemon: Cannot restart container XXXXXXX: container ‘XXXXXXXXXXXXXXXX’: already exists 原因:旧容器未安全退出 解决方式: docker-containerd-ctr --address /run/docker/containerd/docker-containerd.sock --namespace c rm 容器

    2024年04月13日
    浏览(70)
  • Docker 容器设置为 自动重启

    有时Docker服务出现异常,或者服务器出现异常,需要重启Docker服务或者服务器; 如果希望有一部分基础的或者常用的容器,在服务或者服务器重启的时候,可以实现自动启动,仅需使用命令进行简单配置即可实现。 Docker 容器设置为自动重启,分为两种情况:新建容器时设置

    2024年02月07日
    浏览(48)
  • Docker 容器设置为自动重启

     Docker自动重启原因 Docker自动重启通常是由以下几个原因导致的: 程序崩溃 系统内存不足 系统进程使用过多CPU和RAM导致的阻塞 docker容器被杀死或重新启动,导致应用程序中断 网络中断 当这些问题出现时,Docker会自动重启运行中的服务来尝试解决问题。 docker update --restart

    2024年02月03日
    浏览(54)
  • docker—启动、停止、重启容器实例

    先查看已经暂停的容器实例信息 docker ps -a 通过 docker start xxx 启动容器 通过 docker ps 查看当前启动的容器 1、docker stop 此方式常常被翻译为优雅的停止容器 docker stop 容器ID或容器名 参数 -t:关闭容器的限时,如果超时未能关闭则用kill强制关闭,默认值10s,这个时间用于容器的

    2024年02月09日
    浏览(63)
  • Docker 查询、停止、删除和重启容器

    docker 列出所有容器ID docker 查看所有运行容器 docker 查看正在运行容器 docker 停止指定的 container (容器) 温馨提示:删除 container (容器) 的镜像文件,必须先停止 container (容器) 运行 。 知识拓展:停止docker 所有容器(包含正在运行) container (容器) docker 删除指定的

    2024年02月03日
    浏览(63)
  • 无法停止删除容器,Mac重启Docker

    不能停止、杀死、删除容器,使用以下命令得到错误消息: 无法停止、杀死、删除容器,因为没有接收退出事件“receive an exit event”。 没有找到强制停止容器的方法,只能重启容器:

    2024年02月07日
    浏览(56)
  • docker篇---重启策略 + run 容器常用参数

    container runctime 负责的是容器的生命周期管理 OCI :(open container initiative)开源容器协议, 包含两个规范:运行规范(runctime spec)-对容器状态、创建、删除、查看定义 镜像规范(image-spec)-如何创建一个OCI 运行时的系统包 CRI (container run interface) 容器运行接口 隔开了各个

    2024年02月12日
    浏览(63)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包