docker容器重啟后讀寫層數(shù)據(jù)并不丟失的原理
1、場(chǎng)景
當(dāng)我們對(duì)docker容器執(zhí)行restart后,其實(shí)容器中原本讀寫層里對(duì)臨時(shí)數(shù)據(jù)還在。只有我們刪除了這個(gè)容器,重新創(chuàng)建的容器是基于鏡像的只讀層然后掛載上新的空的讀寫層,此時(shí)臨時(shí)數(shù)據(jù)是不在的
2、前置知識(shí)
鏡像,靜態(tài)容器,運(yùn)行時(shí)容器之間的區(qū)別
-
Image:統(tǒng)一只讀文件系統(tǒng))
-
靜態(tài)容器 :未運(yùn)行的容器,統(tǒng)一可讀寫文件系統(tǒng)
-
運(yùn)行時(shí)容器:運(yùn)行中的容器,進(jìn)程空間(包括進(jìn)程)+ 統(tǒng)一可讀寫文件系統(tǒng)
docker run,create,start之間的區(qū)別
docker run相當(dāng)于執(zhí)行了兩步操作:將鏡像放入容器中(docker create),然后將容器啟動(dòng),使之變成運(yùn)行時(shí)容器(docker start)。而docker start的作用是,重新啟動(dòng)已存在的鏡像。也就是說(shuō),如果使用這個(gè)命令,我們必須事先知道這個(gè)容器的ID,或者這個(gè)容器的名字,我們可以使用docker ps找到這個(gè)容器的信息。
docker常見命令的區(qū)別:
-
docker create < image-id >
該命令即為在只讀文件系統(tǒng)上添加一層可讀寫層「Top Layer」,并生成可讀寫文件系統(tǒng)。該命令狀態(tài)下容器為靜態(tài)容器,并沒有運(yùn)行。
-
docker start | restart < container-id >
該命令即為在可讀寫文件系統(tǒng)添加一個(gè)進(jìn)程空間和運(yùn)行的進(jìn)程,并生成一個(gè)動(dòng)態(tài)容器。
-
docker run < image-id >
docker run = docker create + docker start
-
docker stop < container-id >
該指令向運(yùn)行中的容器發(fā)一個(gè) SIGTERM 信號(hào),然后停止所有的進(jìn)程。即為 docker start 的逆過程。
-
docker kill < container-id >
該指令向容器發(fā)送一個(gè)不友好的 SIGKILL 信號(hào),相當(dāng)于快速?gòu)?qiáng)制關(guān)閉容器。與 docker stop 的區(qū)別是 docker stop 是先發(fā) SIGTERM 信號(hào)來(lái)清理進(jìn)程,然后再發(fā) SIGKILL 信號(hào)退出,整個(gè)進(jìn)程是正常關(guān)閉的。
-
docker pause < container-id >
該指令用作暫停容器中的所有進(jìn)程,使用 cgroup 的 freezer 順序暫停容器里的所有進(jìn)程。
-
docker commit < container-id >
該指令用作把容器的可讀寫層轉(zhuǎn)化成只讀層,即從容器狀態(tài)「可讀寫文件系統(tǒng)」變?yōu)殓R像狀態(tài)「只讀文件系統(tǒng)」,可理解為固化。
-
docker build
docker build = docker run 「運(yùn)行容器 + 進(jìn)程修改數(shù)據(jù)」+ docker commit「固化數(shù)據(jù)」,整個(gè)過程不斷循環(huán)直至生成所需鏡像。循環(huán)一次便會(huì)形成一個(gè)新的層(新鏡像 = 原鏡像層 + 已固化的可讀寫層)。docker build 過程一般通過 dockerfile 文件來(lái)實(shí)現(xiàn)。
docker容器生命周期
3、docker容器重啟后讀寫層數(shù)據(jù)并不丟失的原理
容器創(chuàng)建與啟動(dòng)的流程:
docker創(chuàng)建容器和運(yùn)行容器源碼剖析:
docker run命令其實(shí)是由兩部分組成:create和start:
-
創(chuàng)建容器的邏輯(create):
-
獲取鏡像ID GetImage
-
合并容器配置
-
合并日志配置
-
創(chuàng)建容器對(duì)象 newContainer
if container, err = daemon.newContainer(params.Name, params.Config, imgID, managed); err != nil {
return nil, err
} -
設(shè)置安全選項(xiàng)
-
設(shè)置容器讀寫層
if err := daemon.setRWLayer(container); err != nil {
return nil, err
} -
創(chuàng)建文件夾保存容器配置信息
//創(chuàng)建文件夾,用于保存容器的配置信息,在/var/lib/docker/containers/id下,并賦予容器進(jìn)程的讀寫權(quán)限
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
} -
注冊(cè)到daemon
-
-
啟動(dòng)容器的邏輯(start):
-
找到容器對(duì)象實(shí)例
container, err := daemon.GetContainer(name)
if err != nil {
return err
} -
判斷如果暫停的容器不能啟動(dòng),先unpause再啟動(dòng)
if container.IsPaused() {
return fmt.Errorf(“Cannot start a paused container, try unpause instead.”)
} -
判斷如果是運(yùn)行的容器不用啟動(dòng)
if container.IsRunning() {
err := fmt.Errorf(“Container already started”)
return errors.NewErrorWithStatusCode(err, http.StatusNotModified)
} -
確認(rèn)hostconfig與當(dāng)前系統(tǒng)是否一致
if _, err = daemon.verifyContainerSettings(container.HostConfig, nil, false, validateHostname); err != nil {
return err
} -
調(diào)整舊版容器設(shè)置:主要是cpu、內(nèi)存限制的校驗(yàn)和設(shè)置
if err := daemon.adaptContainerSettings(container.HostConfig, false); err != nil {
return err
} -
開始啟動(dòng)容器
-
容器對(duì)象加鎖
container.Lock()
defer container.Unlock() -
狀態(tài)校驗(yàn),如該已經(jīng)運(yùn)行,直接返回
if container.Running {
return nil
} -
掛載讀寫層
dir, err := container.RWLayer.Mount(container.GetMountLabel())
if err != nil {
return err
} -
初始化網(wǎng)絡(luò)
-
containerd 調(diào)用runc
-
進(jìn)入runc啟動(dòng)容器
-
-
4、創(chuàng)建容器讀寫層源碼
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 函數(shù)如下, 在 /var/lib/docker/aufs 目錄下創(chuàng)建兩個(gè)文件 mnt 和 diff,創(chuàng)建 /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 函數(shù)是在 /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 函數(shù)位置 layer/filestore.go,主要是在 /var/lib/docker/image/aufs/layerdb/mounts 目錄下創(chuàng)建層,將 ${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 文件文章來(lái)源:http://www.zghlxwxcb.cn/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)
}
總結(jié)
docker run的時(shí)候其實(shí)是由create和start來(lái)完成的,create創(chuàng)建容器的時(shí)候會(huì)調(diào)用setRWLayer(container)創(chuàng)建讀寫層,start的時(shí)候會(huì)調(diào)用container.RWLayer.Mount(container.GetMountLabel())掛載讀寫層。restart的時(shí)候,則會(huì)使用新的鏡像只讀層 + 掛載當(dāng)前容器的讀寫層,因?yàn)槿萜髦貑⒉⒉粫?huì)丟失那些臨時(shí)修改文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-429551.html
到了這里,關(guān)于【博客618】docker容器重啟后讀寫層數(shù)據(jù)并不丟失的原理的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!