docker容器重启后读写层数据并不丢失的原理
1、场景
当我们对docker容器执行restart后,其实容器中原本读写层里对临时数据还在。只有我们删除了这个容器,重新创建的容器是基于镜像的只读层然后挂载上新的空的读写层,此时临时数据是不在的
2、前置知识
镜像,静态容器,运行时容器之间的区别
-  Image:统一只读文件系统) 
-  静态容器 :未运行的容器,统一可读写文件系统 
-  运行时容器:运行中的容器,进程空间(包括进程)+ 统一可读写文件系统 
docker run,create,start之间的区别
docker run相当于执行了两步操作:将镜像放入容器中(docker create),然后将容器启动,使之变成运行时容器(docker start)。而docker start的作用是,重新启动已存在的镜像。也就是说,如果使用这个命令,我们必须事先知道这个容器的ID,或者这个容器的名字,我们可以使用docker ps找到这个容器的信息。
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容器生命周期

3、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的时候,则会使用新的镜像只读层 + 挂载当前容器的读写层,因为容器重启并不会丢失那些临时修改





















