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

news2024/9/29 19:25:21

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/338961.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

详解指针(2)(初阶版)

前言&#xff1a;内容包括&#xff1a;指针运算&#xff0c;指针和数组&#xff0c;二级指针&#xff0c;指针数组 详解指针&#xff08;1&#xff09;&#xff08;点击即跳转&#xff09; part 1&#xff1a;指针运算 1 指针-整数 以如下代码为例&#xff1a;初始化数组内容…

实验名称:经典同步问题:生成者与消费者问题

实验名称&#xff1a;经典同步问题&#xff1a;生成者与消费者问题 相关知识 信号量 信号量是用来协调不同进程间的数据对象&#xff0c;可用来保护共享资源&#xff0c;也能用来实现进程间及同一进程不同线程间的进程同步。分为二值信号灯和计算信号灯两种类型。 进程与线…

VOC数据增强与调整大小

数据增强是针对数据集图像数量太少所采取的一种方法。 博主在实验过程中&#xff0c;使用自己的数据集时发现其数据量过少&#xff0c;只有280张&#xff0c;因此便想到使用数据增强的方式来获取更多的图像信息。对于图像数据&#xff0c;我们可以采用旋转等操作来获取更多的图…

Android Dalvik虚拟机 堆初始化流程

前言 上篇文章介绍了dalvik虚拟机启动流程&#xff0c;在dalvik虚拟机启动时调用了dvmGcStartup来启动堆。 本文介绍我们在日常开发使用Java时的堆创建流程。 Dalvik堆介绍 Dalvik虚拟机中&#xff0c;堆是由heap[0] Active堆和heap[1] Zygote堆两部分组成的。其中&#xff…

13 Day:实现内核线程

前言&#xff1a;我们昨天完成了内核的内存池以及内存管理程序&#xff0c;今天我们要揭开操作系统多任务执行的神秘面纱&#xff0c;来了解并实现一个多任务的操作系统。 一&#xff0c;实现内核线程 在聊线程之间我们先聊聊处理器吧&#xff0c;众所周之现在我们的CPU动不动…

心跳太快对身体带来影响?4种方法来减速!

心脏每时每刻都在跳动&#xff0c;跳动时遵循一定的节律。正常情况下成年人每分钟心跳达到60~120下&#xff0c;若心跳每分钟大于120下&#xff0c;被判断为心动过速&#xff1b;若心跳每分钟不足50下&#xff0c;被判断为心动过缓&#xff0c;无论是哪种因素均会影响身体健康。…

详解Redisson分布式限流的实现原理

我们目前在工作中遇到一个性能问题&#xff0c;我们有个定时任务需要处理大量的数据&#xff0c;为了提升吞吐量&#xff0c;所以部署了很多台机器&#xff0c;但这个任务在运行前需要从别的服务那拉取大量的数据&#xff0c;随着数据量的增大&#xff0c;如果同时多台机器并发…

如何用Python打包好exe文件,并替换图标

前言 Python打包&#xff1f;打包exe文件&#xff1f;怎么操作&#xff1f; ok&#xff0c;今天我来分享分享&#xff0c;教你们如何打包号文件&#xff0c;顺便还来展示一下&#xff0c;如何替换好图标 首先把你的代码准备好&#xff0c;尽量不要中文路径&#xff0c;容易报…

flex 布局

设为 Flex 布局以后&#xff0c;子元素的float、clear和vertical-align属性将失效。 flex 和 inline-flexflex&#xff1a; 将对象作为弹性伸缩盒显示inline-flex&#xff1a;将对象作为内联块级弹性伸缩盒显示<style>.main {background-color: #0f0;display: flex; /*父…

【VictoriaMetrics】VictoriaMetrics启停脚本

先看结果,启动VictoriaMetrics UI界面可访问

有趣的Hack-A-Sat黑掉卫星挑战赛——定位卫星Jackson

国家太空安全是国家安全在空间领域的表现。随着太空技术在政治、经济、军事、文化等各个领域的应用不断增加&#xff0c;太空已经成为国家赖以生存与发展的命脉之一&#xff0c;凝聚着巨大的国家利益&#xff0c;太空安全的重要性日益凸显[1]。而在信息化时代&#xff0c;太空安…

图解LeetCode——剑指 Offer 53 - I. 在排序数组中查找数字 I

一、题目 统计一个数字在排序数组中出现的次数。 二、示例 示例 1 【输入】nums [5,7,7,8,8,10], target 8 【输出】2 示例 2: 【输入】nums [5,7,7,8,8,10], target 6 【输出】0 提示&#xff1a; 0 < nums.length < 10^5-10^9 < nums[i] < 10^9nums 是一…

基于Java+SpringBoot+SpringCloud+Vue前后端分离医院管理系统设计与实现

博主介绍&#xff1a;✌全网粉丝3W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建、毕业项目实战、项目定制✌ 博主作品&#xff1a;《微服务实战》专栏是本人的实战经验总结&#xff0c;《S…

一文总结 JUC 并发编程

文章目录一、JUC 并发编程二、协调锁1. Synchronized2. Synchronized 锁下线程通信3. Lock 锁4. Lock 锁实例 - ReentrantLock5. 读写锁 - ReadWriteLock三、CAS & 原子类1. CAS2. 原子类2.1 基础原子类2.2 数组类型原子类2.3 引用型原子类四、线程1. Callable 和 Future2.…

Filter防火墙(8)

实验目的 1、了解个人防火墙的基本工作原理&#xff1b; 2、掌握Filter防火墙的配置。 预备知识防火墙 防火墙&#xff08;Firewall&#xff09;是一项协助确保信息安全的设备&#xff0c;会依照特定的规则&#xff0c;允许或是限制传输的数据通过。防火墙可以是一台专属的硬…

Linux防火墙(7)

实验目的 通过该实验了解Linux防火墙iptables实现原理&#xff0c;掌握iptables基本使用方法&#xff0c;能够利用iptables对操作系统进行加固。预备知识基本原理 netfilter/iptables&#xff08;简称为iptables&#xff09;组成Linux平台下的包过滤防火墙&#xff0c;具有完成…

拉普拉斯矩阵的定义,常见的几种形式以及代码实现?

拉普拉斯矩阵 拉普拉斯矩阵(Laplacian matrix) 也叫做导纳矩阵、基尔霍夫矩阵或离散拉普拉斯算子,主要应用在图论中,作为一个图的矩阵表示。对于图 G=(V,E),其Laplacian 矩阵的定义为 L=D-A,其中 L 是Laplacian 矩阵, D=diag(d)是顶点的度矩阵(对角矩阵),d=rowSum(A),…

【Java 面试合集】简述下Java的三个特性 以及项目中的应用

简述下Java的特征 以及项目中的应用 1. 概述 上述截图中就是Java的三大特性&#xff0c;以及特性的实现方案。接下来就每个点展开来说说 2. 封装 满足&#xff1a;隐藏实现细节&#xff0c;公开使用方法 的都可以理解为是封装 而实现封装的有利手段就是权限修饰符了。可以根据…

【MT7628】开发环境搭建-安装Fedora12

1.下载Fedora安装镜像 1.1链接地址 http://dl.fedoraproject.org/pub/archive/fedora/linux/releases/12/Fedora/i386/iso/ 1.2选择如下截图的软件包下载 1.3下载完成的软件包名称

06分支限界法

文章目录八数码难题普通BFS算法全局择优算法&#xff08;A算法&#xff0c;启发式搜索算法&#xff09;单源最短路径问题装载问题算法思想&#xff1a;队列式分支限界法优先队列式分支限界法布线问题最大团问题批处理作业调度问题分支限界法与回溯法的区别&#xff1a; &#x…