docker映象管理
目錄
docker映象概念
Docker的三大核心概念分別是映象、容器、倉庫。Docker執行容器前需要本地存在對應的映象,如果本地沒有對應的映象,Docker會嘗試從預設的映象倉庫下載。當然使用者也可以通過配置,使用自定義的映象倉庫。也就是說,映象是執行容器的前提,倉庫是存放映象的場所,可見映象更是Docker的核心。
docker映象原理
Docker映象本質就是一個檔案,底層依賴於聯合檔案系統(UnionFS)。
什麼是UnionFS?
UnionFS是一種分層、輕量級、高效能的檔案系統,支援對檔案系統的修改作為一次提交來層層疊加。這很類似於我們生活中的千層餅或雞蛋,由蛋黃、蛋清、蛋殼一層一層的最終構成了一個雞蛋。
UnionFS的特性
聯合檔案系統最大特點就是分層和聯合載入。
在載入時可以一次同時載入多層檔案系統,通過聯合載入把各層檔案系統疊加起來,從外部看只能看到一個檔案系統,最終的檔案系統保護所有底層的檔案和目錄。
Docker映象分層結構及載入原理
由於docker映象底層採用聯合檔案系統,自然而然docker映象也是分層的。docker採用的聯合檔案系統為aufs (advanced multi layered unification filesystem),是一種可堆疊的聯合檔案系統。
按照docker官網的說法,docker檔案系統分為兩層:bootfs
和rootfs
。最底層為bootfs,其上為rootfs
- bootfs:用於系統引導的檔案系統,包括bootloader和kernel,容器啟動完成後會被解除安裝以節約記憶體資源
- rootfs:位於bootfs之上,表現為docker容器的根檔案系統
- 傳統模式中,系統啟動之時,核心掛載rootfs會首先將其掛載為“只讀”模式,完整性自檢完成後將其重新掛載為讀寫模式
- docker中,rootfs由核心掛載為“只讀”模式,而後通過“聯合掛載”技術額外掛載一個“可寫”層
當刪除容器時,這個容器自有的“可寫”層會一起被刪除
bootfs層:
bootfs包含了bootloader和linux核心,使用者是不能對這層作任何修改的,在核心啟動之後,bootfs實際上會unmount掉。
rootfs則包含了一般系統上的常見目錄結構,類似於/dev, /proc, /bin等等以及一些基本的檔案和命令。
rootfs層:
對於linux上不同版本的問題,docker可以同時執行多個rootfs。
Docker的檔案系統是分層的,它的rootfs在mount之後會轉為只讀模式, Docker在它上面新增一個新的檔案系統,來達成它的只讀。由於共享一個核心,這也是為什麼docker比虛擬機器消耗資源更少的根本原因。
images層:
從下圖中,我們能看到多個只讀的檔案系統,Docker中把他們稱為層。image是隻讀的,container部分則是可寫的。如果使用者想要修改底層只讀層上的檔案,這個檔案就會被先拷貝到上層,修改後駐留在上層,並遮蔽原有的下層檔案。
container層:
最後一部分是容器(container), 容器是可讀寫的。
在系統啟動時,Docker會首先一層一層的載入image,直到最先面的base image,這些image都是隻讀的。最後,在最上層新增可讀寫的一個容器, 這裡存放著諸如unique id,網路配置之類的資訊。
docker映象為什麼要分層
docker映象採用基於聯合檔案系統的分層結構主要是為了共享公用檔案,除了節約儲存空間,還能實現對檔案的高效管理。
映象可以通過分層來進行繼承,基於父映象可以製作各種具體的應用映象。如果你有面向物件程式語言的開發經驗,這就很好理解,它類似於繼承與多型,子類可以繼承父類功能並能派上新功能。
關於映象的分層,在使用後續的docker pull命令時可以很好的進行驗證。比如使用docker pull命令下載一個mysql映象,將會看到是一層一層的下載。另外如果不同映象包含相同的公用部分(層),假設之前已經下載過包含了公用層的映象的話,再下載包含公用層的其他映象,會提示這些公用層已經存在於本地了,這個映象的下載過程就不在去下載公用層,整個過程就會明顯快很多。
docker儲存驅動
docker提供了多種儲存驅動來實現不同的方式儲存映象,下面是常用的幾種儲存驅動:
- AUFS
- OverlayFS
- Devicemapper
- Btrfs
- VFS
AUFS
AUFS(AnotherUnionFS)是一種Union FS,是檔案級的儲存驅動。AUFS是一個能透明覆蓋一個或多個現有檔案系統的層狀檔案系統,把多層合併成檔案系統的單層表示。簡單來說就是支援將不同目錄掛載到同一個虛擬檔案系統下的檔案系統。這種檔案系統可以一層一層地疊加修改檔案。無論底下有多少層都是隻讀的,只有最上層的檔案系統是可寫的。當需要修改一個檔案時,AUFS建立該檔案的一個副本,使用CoW將檔案從只讀層複製到可寫層進行修改,結果也儲存在可寫層。在Docker中,底下的只讀層就是image,可寫層就是Container。
AUFS檔案系統據說有3W行程式碼,而ext4檔案系統卻只有4000-5000行左右程式碼,這些程式碼是要被整合進核心的,後來AUFS申請要被合併進核心程式碼的時候,linuz覺得它這程式碼太過臃腫,於是拒絕了。因此AUFS這個檔案系統一直以來就不是linux核心中自有的檔案系統,想用AUFS這個檔案系統的話,必須自己向核心打補丁並去編譯使用它,但redhat系列的作業系統一向以穩定著稱,不會幹這種出格的事,所以在redhat系列作業系統中使用AUFS並無可能。而ubuntu上的docker預設使用的就是AUFS。
OverlayFS
Overlay是Linux核心3.18後支援的,也是一種Union FS,和AUFS的多層不同的是Overlay只有兩層:一個upper檔案系統和一個lower檔案系統,分別代表Docker的映象層和容器層。當需要修改一個檔案時,使用CoW將檔案從只讀的lower複製到可寫的upper進行修改,結果也儲存在upper層。在Docker中,底下的只讀層就是image,可寫層就是Container。目前最新的OverlayFS為Overlay2。
AUFS和Overlay都是聯合檔案系統,但AUFS有多層,而Overlay只有兩層,所以在做寫時複製操作時,如果檔案比較大且存在比較低的層,則AUSF會慢一些。而且Overlay併入了linux kernel mainline,AUFS沒有。目前AUFS已基本被淘汰。
DeviceMapper
Device mapper是Linux核心2.6.9後支援的,提供的一種從邏輯裝置到物理裝置的對映框架機制,在該機制下,使用者可以很方便的根據自己的需要制定實現儲存資源的管理策略。AUFS和OverlayFS都是檔案級儲存,而Device mapper是塊級儲存,所有的操作都是直接對塊進行操作,而不是檔案。Device mapper驅動會先在塊裝置上建立一個資源池,然後在資源池上建立一個帶有檔案系統的基本裝置,所有映象都是這個基本裝置的快照,而容器則是映象的快照。所以在容器裡看到檔案系統是資源池上基本裝置的檔案系統的快照,並沒有為容器分配空間。當要寫入一個新檔案時,在容器的映象內為其分配新的塊並寫入資料,這個叫用時分配。當要修改已有檔案時,再使用CoW為容器快照分配塊空間,將要修改的資料複製到在容器快照中新的塊裡再進行修改。
OverlayFS是檔案級儲存,Device mapper是塊級儲存,當檔案特別大而修改的內容很小,Overlay不管修改的內容大小都會複製整個檔案,對大檔案進行修改顯然要比小檔案要消耗更多的時間,而塊級無論是大檔案還是小檔案都只複製需要修改的塊,並不是整個檔案,在這種場景下,顯然device mapper要快一些。因為塊級的是直接訪問邏輯盤,適合IO密集的場景。而對於程式內部複雜,大併發但少IO的場景,Overlay的效能相對要強一些。
docker registry
啟動容器時,docker daemon會試圖從本地獲取相關的映象,本地映象不存在時,其將從Registry中下載該映象並儲存到本地。
Registry用於儲存docker映象,包括映象的層次結構和元資料。使用者可以自建Registry,亦可使用官方的Docker Hub。
docker registry的分類:
- Sponsor Registry:第三方的Registry,供客戶和Docker社群使用
- Mirror Registry:第三方的Registry,只讓客戶使用
- Vendor Registry:由釋出docker映象的供應商提供的registry
- Private Registry:通過設有防火牆和額外的安全層的私有實體提供的registry
docker registry的組成:
- Repository
- 由某特定的docker映象的所有迭代版本組成的映象倉庫
- 一個Registry中可以存在多個Repository
- Repository可分為“頂層倉庫”和“使用者倉庫”
- 使用者倉庫名稱格式為“使用者名稱/倉庫名”
- 每個倉庫可包含多個Tag(標籤),每個標籤對應一個映象
- Index
- 維護使用者帳戶、映象的檢驗以及公共名稱空間的資訊
- 相當於為Registry提供了一個完成使用者認證等功能的檢索介面
Docker Registry中的映象通常由開發人員製作,而後推送至“公共”或“私有”Registry上儲存,供其他人員使用,例如“部署”到生產環境。
docker映象的製作
Docker Image 的製作兩種方法
方法 1:docker commit #儲存 container 的當前狀態到 image 後,然後生成對應的 image
方法 2:docker build #使用 Dockerfile 檔案自動化製作 image
方法 3:Docker Hub 自動構建
基於容器來製作映象
docker commit : 從容器建立一個新的映象。
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
OPTIONS 說明:
- -a : 提交的映象作者;
- -c : 使用 Dockerfile 指令來建立映象;
- -m : 提交時的說明文字;
- -p : 在 commit 時,將容器暫停。
[root@localhost ~]# docker pull busybox
Using default tag: latest
latest: Pulling from library/busybox
61c5ed1cbdf8: Pull complete
Digest: sha256:4f47c01fa91355af2865ac10fef5bf6ec9c7f42ad2321377c21e844427972977
Status: Downloaded newer image for busybox:latest
docker.io/library/busybox:latest
[root@localhost ~]# docker run --name bi -it busybox
/ # ls
bin dev etc home proc root sys tmp usr var
/ # mkdir data
/ # echo 'busybox test page' > data/index.html
/ # cat data/index.html
busybox test page
在建立映象時,我們不能關閉容器,必須使其處於執行狀態,所以我們必須要另起一個終端,然後執行
[root@localhost ~]# docker commit -p bi
sha256:adb6814ee79ed4bc761601d89b427b0f0762ca7ba4418fb5b180533b7d091929
[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> adb6814ee79e 3 seconds ago 1.22MB
busybox latest 018c9d7b792b 4 weeks ago 1.22MB
[root@localhost ~]# docker tag adb6814ee79e evereternity/bi:v0.1
[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
evereternity/bi v0.1 adb6814ee79e 46 seconds ago 1.22MB
busybox latest 018c9d7b792b 4 weeks ago 1.22MB
[root@localhost ~]# docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: evereternity
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
[root@localhost ~]#
[root@localhost ~]# docker push evereternity/bi:v0.1
The push refers to repository [docker.io/evereternity/bi]
d7e08212e4fc: Pushed
514c3a3e64d4: Mounted from library/busybox
v0.1: digest: sha256:7a65f0e0d46fb7b71e0381128355b91f5ef071ac155503f6dc706ead8012d99e size: 734
使用新生成的映象建立容器
[root@localhost ~]# docker pull evereternity/bi:v0.1
v0.1: Pulling from evereternity/bi
Digest: sha256:7a65f0e0d46fb7b71e0381128355b91f5ef071ac155503f6dc706ead8012d99e
Status: Image is up to date for evereternity/bi:v0.1
docker.io/evereternity/bi:v0.1
[root@localhost ~]# docker run --name t1 -it evereternity/bi:v0.1
/ # ls
bin data dev etc home proc root sys tmp usr var
/ # ls data/
index.html
/ # cat data/index.html
busybox test page
新生成的映象中是包含了新增的內容的,但容器預設要啟動的程序sh程序,但我們是要啟動一個http站點,所以我們要在建立映象時將容器預設啟動的程序設為httpd,這樣一來我們就可以通過新生成的映象來快速構建一個簡單的http站點了。
使用docker inspect
命令檢視b1容器啟動的預設程序是什麼
[root@localhost ~]# docker inspect t1
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"sh"
],
"Image": "evereternity/bi:v0.1",
重新生成映象並上傳
[root@localhost ~]# docker commit -a 'evereternity' -c 'CMD ["/bin/httpd","-f","-h","/data"]' -p t1 evereternity/bi:v0.2
sha256:3e06e84549155f943da9472741379f39ff8acf716a1c1b306c1e6114e8b2ce27
[root@localhost ~]# docker push evereternity/bi:v0.2
The push refers to repository [docker.io/evereternity/bi]
99025c58a57d: Pushed
d7e08212e4fc: Layer already exists
514c3a3e64d4: Layer already exists
v0.2: digest: sha256:95fc7c9a5b69c7cd282dfd86caaefc6d51138d242a20652ce20d03f63db8f454 size: 941
[root@localhost ~]# docker run --name t2 -d evereternity/bi:v0.2
bef1dce3a48cec71f827f2667501892de05b88576c7efca8c8a0be3f306b0768
[root@localhost ~]# docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
bef1dce3a48c evereternity/bi:v0.2 "/bin/httpd -f -h /d…" 13 seconds ago Up 11 seconds t2
f59bd30e08b0 evereternity/bi:v0.1 "sh" 12 minutes ago Up 12 minutes t1
使用docker inspect
命令檢視t2容器啟動的預設程序是什麼,以及其IP地址,然後用curl命令訪問該IP,看是否能訪問到網頁
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "2aa6262694d84c71a2781260ae81343b188b66a169247fa3e533867048b4f265",
"EndpointID": "0a2232af8b2a20c5e5597d35261c268f289971618a48eed251c6797adc11807b",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.3",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
#訪問
[root@localhost ~]# curl 172.17.0.3
busybox test page
映象的匯入和匯出
docker中我們使用docker save
進行匯出,使用docker load
進行匯入。
save 和 export
save 儲存的是映象,export 儲存的是容器
load 和 import
load 載入映象包,import 載入容器包,但兩者都會恢復為映象
load 不能對載入的進行進行命名,import 可以
save
docker save : 將指定映象儲存成 tar 歸檔檔案。
docker save [OPTIONS] IMAGE [IMAGE...]
OPTIONS 說明:
- -o : 輸出到的檔案。
將映象 runoob/ubuntu:v3 生成 my_ubuntu_v3.tar 文件
[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
evereternity/bi v0.2 3e06e8454915 14 minutes ago 1.22MB
evereternity/bi v0.1 adb6814ee79e 33 minutes ago 1.22MB
nginx latest 4bb46517cac3 2 weeks ago 133MB
busybox latest 018c9d7b792b 4 weeks ago 1.22MB
[root@localhost ~]# docker save -o busybox_v0.2.gz evereternity/bi:v0.2
[root@localhost ~]# ls
anaconda-ks.cfg busybox_v0.2.gz
load
docker load : 匯入使用 docker save 命令匯出的映象。
docker load [OPTIONS]
OPTIONS 說明:
- --input , -i : 指定匯入的檔案,代替 STDIN。
- --quiet , -q : 精簡輸出資訊。
[root@localhost ~]# ls
anaconda-ks.cfg busybox_v0.2.gz
#匯入映象
[root@localhost ~]# docker load -i busybox_v0.2.gz
Loaded image: evereternity/bi:v0.2