[TOC]
# <span style="font-size:15px">**Docker鏡像的核心原理一:鏡像的存儲和管理方式**</span>
## <span style="font-size:15px">**鏡像的設計**</span>
一個鏡像都由多個鏡像層組成。
為了區別鏡像層,Docker為每個鏡像層都計算了UUID,根據鏡像層中的數據使用加密哈希算法生成UUID。
所有鏡像層和容器層都保存在宿主機的文件系統/var/lib/docker/中,由存儲驅動進行管理。
在下載鏡像時,Docker Daemon會檢查鏡像中的鏡像層與宿主機文件系統中的鏡像層進行對比,如果存在則不下載,只下載不存在的鏡像層。

## <span style="font-size:15px">**棧層式管理鏡像層**</span>
docker 中存儲驅動用于管理鏡像層和容器層。不同的存儲驅動使用不同的算法和管理方式。在容器和鏡像管理中,使用的兩大技術是**棧式層管理和寫時復制。**
Dockerfile中的每一條指令都會對應于Docker鏡像中的一層,因此在docker build完畢之后,鏡像的總大小將等于每一層鏡像的大小總和。
每個鏡像都由多個鏡像層組成,從下往上以棧的方式組合在一起形成容器的根文件系統,Docker的存儲驅動用于管理這些鏡像層,對外提供單一的文件系統。Docker 的鏡像實際上由一層一層的文件系統組成,這種層級的文件系統叫 UnionFS(聯合文件系統)。
> **由于聯合文件系統的存在,容器文件系統內容的大小不等于Docker鏡像大小。**

由于聯合文件系統的存在,如果鏡像層是相同的,則不同的鏡像會共享該層。
如上圖中,鏡像A與鏡像B就共享第二層鏡像,使得A+B鏡像文件大小并不等于A+B鏡像占用宿主機存儲空間容量的大小。
`doker images`命令列出的鏡像體積總和并不能代表實際使用的磁盤空間,需要使用`docker system df`命令來代替。
<br>
### <span style="font-size:15px">**UnionFS (聯合文件系統)**</span>
聯合文件系統(UnionFS)是一種**分層**、輕量級并且高性能的文件系統,它支持對文件系統的修改作為一次提交來一層層的疊加,同時可以將不同目錄掛載到同一個虛擬文件系統下。
>**分層的原因:**
>1. 分層最大的一個好處就是共享資源
>2. 有多個鏡像都從相同的base鏡像構建而來,那么宿主機只需在磁盤上保存一份base鏡像;
>3. 同時內存中也只需加載一份base鏡像,就可以為所有容器服務了,而且鏡像的每一層都可以被共享。
聯合文件系統是 Docker 鏡像的基礎。鏡像可以通過分層來進行繼承,基于基礎鏡像(沒有父鏡像),可以制作各種具體的應用鏡像。
特性:一次同時加載多個文件系統,但從外面看起來,只能看到一個文件系統,聯合加載會把各層文件系統疊加起來,這樣最終的文件系統會包含所有底層的文件和目錄。
> 如圖,在下載鏡像時,也是一層一層下載的

# <span style="font-size:15px">**Docker鏡像的核心原理二:鏡像結構**</span>
要了解docker的鏡像結構,需要先對linux的文件系統結構有所了解。
## <span style="font-size:15px">**linux文件系統結構**</span>
Linux 文件系統由 `bootfs`和 `rootfs` 兩部分組成。
bootfs(boot file system) 主要包含 bootloader 和 kernel,bootloader 主要是引導加載 kernel(內核),當 kernel 被加載到內存中后 bootfs 就被 umount (卸載)了。
rootfs (root file system) 包含的就是典型 Linux 系統中的 /dev,/proc,/bin,/etc 等標準目錄和文件。rootfs就是各種Linux發行版。比如redcat、centOS。

## <span style="font-size:15px">**docker鏡像結構**</span>

docker的分層鏡像結構如圖所示,鏡像的最底層必須是一個啟動文件系統(`bootfs`)的鏡像層。`bootfs`的上層鏡像稱為根鏡像(`rootfs`)或者基礎鏡像(`Base Image`),它一般是操作系統,比如centos、debian或者Ubuntu。
用戶的鏡像必須構建在基礎鏡像之上。如圖所示, emacs鏡像層就是在基礎鏡像上安裝emacs創建出來的鏡像,在此基礎上安裝apache又創建了新的鏡像層。利用這個新的鏡像層啟動的容器里運行的是一個已經安裝好emacs和apache的Debian系統。
## <span style="font-size:15px">**docker鏡像分層的理解**</span>
查看鏡像分層方式可以通過`docker image inspect [IMAGE]`命令。其中RootFS部分則是表示了分層信息。
```
[root@iZbp1bum6107bp8mgzkeunZ ~]# docker image inspect redis
[
{
"Id": "sha256:53aa81e8adfa939348cd4c846c0ab682b16dc7641714e36bfc57b764f0b947dc",
...
...
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:ad6562704f3759fb50f0d3de5f80a38f65a85e709b77fd24491253990f30b6be",
"sha256:49cba0f0997b2bb3a24bcfe71c7cbd6e9f6968ef7934e3ad56b0f1f9361b6b91",
"sha256:309498e524b3e2da1f036d00cd5155e0b74cf9e1d964a3636c8ed63ca4a00d43",
"sha256:f7c9b429437f7ada2d3d455ac4ea90ff38e0cb7ef2551b08d152264b74116309",
"sha256:4dabdd56bbf16307e2328cb6ed1d42b0bb9b8f40551421271c0b38dc9a685dcc",
"sha256:ea450ad6ef893e998f88a35dc9cc22f952c62b88d58f948344cf4eda1a6264fc"
]
},
}
]
```
所有的Docker鏡像都起始于一個基礎鏡像層,當鏡像修改或者新增新的內容時,就會在當前鏡像層之上,創建新的鏡像層。**即在添加額外的鏡像層的同時,鏡像始終保持是當前所有鏡像的組合**。docker通過存儲引擎(新版本采用快照機制)的方式實現鏡像層堆棧,并保證多個鏡像層對外展示為統一的文件系統。示例:

這個鏡像中包含了三個鏡像層,第一層有三個文件,第二層也有三個文件,第三層鏡像中僅有一個文件,且這個文件是對第二層鏡像中的文件5的一個更新版本。在這種情況下,上層鏡像層中的文件會覆蓋底層鏡像層的文件,這樣就使得文件的更新版本作為一個新的鏡像層添加到鏡像當中。
最后docker通過存儲引擎將所有鏡像層堆疊并合并,對外提供統一的視圖。

Dockerfile中的操作對于鏡像分層的影響:**在鏡像構建過程中需要向鏡像寫入數據的時候會產生分層,一個寫操作指令產生一個分層**。
```
// 實例,驗證Dockerfile中的操作對于鏡像分層的影響
// 第一步,簡單編寫一個Dockerfile,復制宿主機的文件到容器中,并且RUN 執行相關命令
[root@iZbp1bum6107bp8mgzkeunZ test]# cat Dockerfile
FROM centos:7
# 寫指令-COPY、RUN
COPY * /root/test/
RUN touch /etc/pidstst.log \
&& yum -y install sysstat
CMD /root/test/process.sh
// 第二步,創建鏡像
[root@iZbp1bum6107bp8mgzkeunZ test]# docker build -f Dockerfile -t test .
Sending build context to Docker daemon 4.096kB
Step 1/4 : FROM centos:7
---> eeb6ee3f44bd
Step 2/4 : COPY * /root/test/
---> 897bd9a5ead0
Step 3/4 : RUN touch /etc/pidstst.log && yum -y install sysstat
---> Running in 4b90b3c273b0
Step 4/4 : CMD /root/test/test.sh
---> Running in dd115ea2e7f7
Removing intermediate container dd115ea2e7f7
---> 205226fedbc6
Successfully built 205226fedbc6
Successfully tagged test:latest
// 查看創建的鏡像
[root@iZbp1bum6107bp8mgzkeunZ docker]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
test latest 205226fedbc6 12 minutes ago 407MB
// 查看該鏡像的分層結構信息
// 所有的Docker鏡像都起始于一個基礎鏡像層,因此第一個layer為dockerfile中指定的基礎鏡像層
// 然后dockerfile中有多少個寫指令,就會有多少個lay。 這里是三個layer,跟dockerfile中剛好對應上。
[root@iZbp1bum6107bp8mgzkeunZ docker]# docker image inspect 205226fedbc6
...
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:174f5685490326fc0a1c0f5570b8663732189b327007e47ff13d2ca59673db02",
"sha256:114bd86a861ccc7b8fcc9c5702529d3e2df0fa7ad3e753e0ad072362db417e7c",
"sha256:9759cbc1af5e6651027f437b3e902ab08725eda7fcf16cdeb5acc45cefb90a5b"
]
}
...
```
# <span style="font-size:15px">**Docker鏡像的核心原理三:寫時復制策略(Copy On Write)**</span>
>當某個容器修改了基礎鏡像的內容,比如 /bin文件夾下的文件,這時其他容器的/bin文件夾是否會發生變化呢?
答案是不會的。根據容器鏡像的寫時復制(Copy-on-Write)技術,某個容器對基礎鏡像的修改會被限制在單個容器內。
寫時復制策略采用了共享和復制技術,**針對相同的數據系統只保留一份,所有操作都訪問這一份數據**。當有操作需要修改或添加數據時,操作系統會把這部分數據復制到新的地方再進行修改或添加,而其他操作仍然訪問原數據區數據,這項技術節約了鏡像的存儲空間,加快了系統啟動時間。

如圖所示,當需要對鏡像中的文件進行修改時,會將文件復制到容器層進行修改,上層文件會覆蓋原始鏡像文件。**注意:該文件存在于容器層,容器重啟之后容器層重新建立,上一次容器運行時對于文件的修改全部丟失!**
只有當需要修改時才復制一份數據,這種特性被稱作`Copy-on-Write`。可見,容器層保存的是鏡像變化的部分,不會對鏡像本身進行任何修改。
# <span style="font-size:15px">**Docker鏡像的核心原理四:內容尋址原理**</span>
在docker中,內容尋址就是根據文件內容來索引對應的鏡像和鏡像層,實際上就是對于鏡像層的內容計算和校驗后生成一個內容哈希值,并作為這個鏡像層的唯一ID。在構造鏡像時,根據這個ID來索引鏡像層。
- PHP
- PHP基礎
- PHP介紹
- 如何理解PHP是弱類型語言
- 超全局變量
- $_SERVER詳解
- 字符串處理函數
- 常用數組函數
- 文件處理函數
- 常用時間函數
- 日歷函數
- 常用url處理函數
- 易混淆函數區別(面試題常見)
- 時間戳
- PHP進階
- PSR規范
- RESTFUL規范
- 面向對象
- 三大基本特征和五大基本原則
- 訪問權限
- static關鍵字
- static關鍵字
- 靜態變量與普通變量
- 靜態方法與普通方法
- const關鍵字
- final關鍵字
- abstract關鍵字
- self、$this、parent::關鍵字
- 接口(interface)
- trait關鍵字
- instanceof關鍵字
- 魔術方法
- 構造函數和析構函數
- 私有屬性的設置獲取
- __toString()方法
- __clone()方法
- __call()方法
- 類的自動加載
- 設計模式詳解
- 關于設計模式的一些建議
- 工廠模式
- 簡單工廠模式
- 工廠方法模式
- 抽象工廠模式
- 區別和適用范圍
- 策略模式
- 單例模式
- HTTP
- 定義
- 特點
- 工作過程
- request
- response
- HTTP狀態碼
- URL
- GET和POST的區別
- HTTPS
- session與cookie
- 排序算法
- 冒泡排序算法
- 二分查找算法
- 直接插入排序算法
- 希爾排序算法
- 選擇排序算法
- 快速排序算法
- 循環算法
- 遞歸與尾遞歸
- 迭代
- 日期相關的類
- DateTimeInterface接口
- DateTime類
- DateTimeImmutable類
- DateInterval類
- DateTimeZone類
- DatePeriod類
- format參數格式
- DateInterval的format格式化參數
- 預定義接口
- ArrayAccess(數組式訪問)接口
- Serializable (序列化)接口
- Traversable(遍歷)接口
- Closure類
- Iterator(迭代器)接口
- IteratorAggregate(聚合迭代器) 接口
- Generator (生成器)接口
- composer
- composer安裝與使用
- python
- python3執行tarfile解壓文件報錯:tarfile.ReadError:file could not be opened successfully
- golang
- 單元測試
- 單元測試框架
- Golang內置testing包
- GoConvey庫
- testify庫
- 打樁與mock
- GoMock框架
- Gomonkey框架
- HTTP Mock
- httpMock
- mux庫/httptest
- 數據庫
- MYSQL
- SQL語言的分類
- 事務(重點)
- 索引
- 存儲過程
- 觸發器
- 視圖
- 導入導出數據庫
- 優化mysql數據庫的方法
- MyISAM與InnoDB區別
- 外連接、內連接的區別
- 物理文件結構
- PostgreSQL
- 編譯安裝
- pgsql常用命令
- pgsql應用目錄(bin目錄)文件結構解析
- pg_ctl
- initdb
- psql
- clusterdb
- cluster命令
- createdb
- dropdb
- createuser
- dropuser
- pg_config
- pg_controldata
- pg_checksums
- pgbench
- pg_basebackup
- pg_dump
- pg_dumpall
- pg_isready
- pg_receivewal
- pg_recvlogical
- pg_resetwal
- pg_restore
- pg_rewind
- pg_test_fsync
- pg_test_timing
- pg_upgrade
- pg_verifybackup
- pg_archivecleanup
- pg_waldump
- postgres
- reindexdb
- vacuumdb
- ecpg
- pgsql數據目錄文件結構解析
- pgsql數據目錄文件結構解析
- postgresql.conf解析
- pgsql系統配置參數說明
- pgsql索引類型
- 四種索引類型解析
- 索引之ctid解析
- 索引相關操作
- pgsql函數解析
- pgsql系統函數解析
- pgsql窗口函數解析
- pgsql聚合函數解析
- pgsql系統表解析
- pg_stat_all_indexes
- pg_stat_all_tables
- pg_statio_all_indexes
- pg_statio_all_tables
- pg_stat_database
- pg_stat_statements
- pg_extension
- pg_available_extensions
- pg_available_extension_versions
- pgsql基本原理
- 進程和內存結構
- 存儲結構
- 數據文件的內部結構
- 垃圾回收機制VACUUM
- 事務日志WAL
- 并發控制
- 介紹
- 事務ID-txid
- 元組結構-Tuple Structure
- 事務狀態記錄-Commit Log (clog)
- 事務快照-Transaction Snapshot
- 事務快照實例
- 事務隔離
- 事務隔離級別
- 讀已提交-Read committed
- 可重復讀-Repeatable read
- 可序列化-Serializable
- 讀未提交-Read uncommitted
- 鎖機制
- 擴展機制解析
- 擴展的定義
- 擴展的安裝方式
- 自定義創建擴展
- 擴展的管理
- 擴展使用實例
- 在pgsql中使用last、first聚合函數
- pgsql模糊查詢不走索引的解決方案
- pgsql的pg_trgm擴展解析與驗證
- 高可用
- LNMP
- LNMP環境搭建
- 一鍵安裝包
- 搭建方法
- 配置文件目錄
- 服務器管理系統
- 寶塔(Linux)
- 安裝與使用
- 開放API
- 自定義apache日志
- 一鍵安裝包LNMP1.5
- LNMP1.5:添加、刪除站點
- LNMP1.5:php多版本切換
- LNMP1.5 部署 thinkphp項目
- Operation not permitted解決方法
- Nginx
- Nginx的產生
- 正向代理和反向代理
- 負載均衡
- Linux常用命令
- 目錄與文件相關命令
- 目錄操作命令
- 文件編輯命令
- 文件查看命令
- 文件查找命令
- 文件權限命令
- 文件上傳下載命令
- 用戶和群組相關命令
- 用戶與用戶組的關系
- 用戶相關的系統配置文件
- 用戶相關命令
- 用戶組相關命令
- 壓縮與解壓相關命令
- .zip格式
- .tar.gz格式
- .gz格式
- .bz2格式
- 查看系統版本
- cpuinfo詳解
- meminfo詳解
- getconf獲取系統信息
- 磁盤空間相關命令
- 查看系統負載情況
- 系統環境變量
- 網絡相關命令
- ip命令詳解
- ip命令格式詳解
- ip address命令詳解
- ip link命令詳解
- ip rule命令詳解
- ip route命令詳解
- nslookup命令詳解
- traceroute命令詳解
- netstat命令詳解
- route命令詳解
- tcpdump命令詳解
- 系統進程相關命令
- ps命令詳解
- pstree命令詳解
- kill命令詳解
- 守護進程-supervisord
- 性能監控相關命令
- top命令詳解
- iostat命令詳解
- pidstat命令詳解
- iotop命令詳解
- mpstat命令詳解
- vmstat命令詳解
- ifstat命令詳解
- sar命令詳解
- iftop命令詳解
- 定時任務相關命令
- ssh登錄遠程主機
- ssh口令登錄
- ssh公鑰登錄
- ssh帶密碼登錄
- ssh端口映射
- ssh配置文件
- ssh安全設置
- 歷史紀錄
- history命令詳解
- linux開啟操作日志記錄
- 拓展
- git
- git初始化本地倉庫-https
- git初始化倉庫-ssh
- git-查看和設置config配置
- docker
- 概念
- docker原理
- docker鏡像原理
- docker Overlay2 文件系統原理
- docker日志原理
- docker日志驅動
- docker容器日志管理
- 原理論證
- 驗證容器的啟動是作為Docker Daemon的子進程
- 驗證syslog類型日志驅動
- 驗證journald類型日志驅動
- 驗證local類型日志驅動
- 修改容器的hostname
- 修改容器的hosts
- 驗證聯合掛載技術
- 驗證啟動多個容器對于磁盤的占用情況
- 驗證寫時復制原理
- 驗證docker內容尋址原理
- docker存儲目錄
- /var/lib/docker目錄
- image目錄
- overlay2目錄
- 數據卷
- 具名掛載和匿名掛載
- 數據卷容器
- Dockerfile詳解
- dockerfile指令詳解
- 實例:構造centos
- 實例:CMD和ENTRYPOINT的區別
- docker網絡詳解
- docker-compose
- 緩存
- redis
- redis的數據類型和應用場景
- redis持久化
- RDB持久化
- AOF持久化
- redis緩存穿透、緩存擊穿、緩存雪崩
- 常見網絡攻擊類型
- CSRF攻擊
- XSS攻擊
- SQL注入
- Cookie攻擊
- 歷史項目經驗
- 圖片上傳項目實例
- 原生php上傳方法實例
- base64圖片流
- tp5的上傳方法封裝實例
- 多級關系的遞歸查詢
- 數組轉樹結構
- thinkphp5.1+ajax實現導出Excel
- JS 刪除數組的某一項
- 判斷是否為索引數組
- ip操作