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 文件文章来源:https://www.toymoban.com/news/detail-429551.html
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模板网!