典型的 Linux 文件系統(tǒng)
bootfs(bootfilesystem)
- Bootloader - 引導(dǎo)加載 kernel
- Kernel - 當(dāng) kernel 被加載到內(nèi)存中后 umount bootfs
rootfs(rootfilesystem)
- /dev沈自,/proc徐勃,/bin,/etc 等標(biāo)準(zhǔn)目錄和文件
對(duì)于不同的 Linux 發(fā)行版刺洒,bootfs 基本是一致的豫尽,但 rootfs 會(huì)有差別
Docker 的文件系統(tǒng)如何啟動(dòng)
Linux
- 在啟動(dòng)后篙梢,首先將 rootfs 置為 readonly,進(jìn)行一系列檢查,然后將其切換為“readwrite”共用戶(hù)使用
Docker
也是將 rootfs 以 readonly 方式加載并檢查,然而接下來(lái)利用 union mount 將一個(gè) readwrite 文件系統(tǒng)掛載在 readonly 的 rootfs 之上
并且允許再次將下層的 file system 設(shè)定為 readonly 并且向上疊加
這樣一組 readonly 和一個(gè) writeable 的結(jié)構(gòu)構(gòu)成一個(gè) container 的運(yùn)行目錄逼友,每一個(gè)被稱(chēng)作一個(gè) Layer
AUFS
AUFS 是一種 Union File System,所謂 UnionFS 就是把不同物理位置的目錄合并 mount 到同一個(gè)目錄中妄呕。UnionFS 的一個(gè)最主要的應(yīng)用是,把一張CD/DVD 和一個(gè)硬盤(pán)目錄給聯(lián)合 mount 在一起嗽测,然后绪励,你就可以對(duì)這個(gè)只讀的 CD/DVD 上的文件進(jìn)行修改(當(dāng)然,修改的文件存于硬盤(pán)上的目錄里)
AUFS 是將多個(gè)目錄合并成一個(gè)虛擬文件系統(tǒng)唠粥,成員目錄稱(chēng)為虛擬文件系統(tǒng)的一個(gè)分支(branch)疏魏,每個(gè) branch 可以指定 readwrite/whiteout-able/readonly 權(quán)限,只讀(ro)晤愧,讀寫(xiě)(rw)大莫,寫(xiě)隱藏(wo)。一般情況下养涮,aufs 只有最上層的 branch 具有讀寫(xiě)權(quán)限葵硕,其余 branch 均為只讀權(quán)限眉抬。只讀 branch 只能邏輯上修改贯吓,AUFS 每層branch 可以動(dòng)態(tài)的增加刪除,每增加一層蜀变,下層默認(rèn)置為 ro悄谐,最上一層為 rw。刪除 branch 是在 aufs 掛載點(diǎn)移除库北,并未刪除掛載目錄
AUFS 的文件讀寫(xiě)與刪除
當(dāng)需要修改一個(gè)文件爬舰,而該文件位于低層 branch時(shí)们陆,頂層 branch 會(huì)直接復(fù)制低層 branch 的文件至頂層再進(jìn)行修改,而低層的文件不變情屹,這種方式即是 CoW 技術(shù)(寫(xiě)復(fù)制)坪仇,AUFS 默認(rèn)支持 Cow 技術(shù),當(dāng)容器刪除一個(gè)低層 branch 文件時(shí)垃你,只是在頂層 branch 對(duì)該文件進(jìn)行重命名并隱藏椅文,實(shí)際并未刪除文件,只是不可見(jiàn)惜颇,這種方式即 AUFS 的whiteout(寫(xiě)隱藏)
- 添加文件:創(chuàng)建文件時(shí)皆刺,新文件被添加到branch中
- 讀取文件 :讀取某個(gè)文件時(shí),會(huì)從上往下依次在各鏡像層中查找此文件凌摄。一旦找到羡蛾,打開(kāi)并讀入內(nèi)存。
- 修改文件 :修改已存在的文件時(shí)锨亏,會(huì)從上往下依次在各鏡像層中查找此文件痴怨。一旦找到,立即將其復(fù)制到容器層屯伞,然后修改之腿箩。
- 刪除文件 :刪除文件時(shí),也是從上往下依次在鏡像層中查找此文件劣摇。找到后珠移,會(huì)在branch中記錄下此刪除操作。
Docker 中的 AUFS
Docker 鏡像(Image)是由一個(gè)或多個(gè) AUFS branch 組成末融,并且所有的 branch 均為只讀權(quán)限钧惧。簡(jiǎn)單來(lái)說(shuō),AUFS 所有 robranch 按照一定順序堆積構(gòu)成 Docker Image 鏡像
在運(yùn)行容器的時(shí)候勾习,創(chuàng)建一個(gè) AUFS branch 位于image 層之上浓瞪,具有 rw 權(quán)限,并把這些 branch 聯(lián)合掛載到一個(gè)掛載點(diǎn)下巧婶。這就是 Docker 能夠一個(gè)鏡像運(yùn)行多個(gè)容器的原理所在
寫(xiě)時(shí)復(fù)制(Copy-on-Write)
- 容器啟動(dòng)時(shí)乾颁,一個(gè)新的可寫(xiě)層被加載到鏡像的頂部,這一層通常被稱(chēng)作“容器層”艺栈,“容器層”之下的都叫“鏡像層”
- 所有對(duì)容器的改動(dòng)英岭,無(wú)論添加、刪除湿右、還是修改文件都只會(huì)發(fā)生在容器層中
- 只有容器層是可寫(xiě)的诅妹,容器層下面的所有鏡像層都是只讀的
- 只有當(dāng)需要修改時(shí)才復(fù)制一份數(shù)據(jù),這種特性被稱(chēng)作 Copy-on-Write】越疲可見(jiàn)尖殃,容器層保存的是鏡像變化的部分, 不會(huì)對(duì)鏡像本身進(jìn)行任何修改
AUFS 的好處
- 節(jié)省存儲(chǔ)空間:多個(gè)容器可以共享基礎(chǔ)鏡像(Base Image)存儲(chǔ)
- 快速部署:如果要部署多個(gè)容器划煮,基礎(chǔ)鏡像(Base Image)可以避免多次拷貝
- 內(nèi)存更仕头帷:因?yàn)槎鄠€(gè)容器共享 Base Image,以及OS 的 Disk 緩存機(jī)制弛秋,多個(gè)容器中的進(jìn)程命中緩存內(nèi)容的幾率大大增加
- 允許在不更改基礎(chǔ)鏡像的同時(shí)修改其目錄中的文件:所有寫(xiě)操作都發(fā)生在最上層的 writeable 層中蚪战,這樣可以大大增加 Base Image 能共享的文件內(nèi)容
鏡像的定義
鏡像(Image)就是一堆只讀層(read-only layer)的統(tǒng)一視角
從左邊我們看到了多個(gè)只讀層,它們重疊在一起铐懊。除了最下面一層邀桑,其它層都會(huì)有一個(gè)指針指向下一層。這些層是 Docker 內(nèi)部的實(shí)現(xiàn)細(xì)節(jié)科乎,并且能夠在主機(jī)(譯者注:運(yùn)行 Docker 的機(jī)器)的文件系統(tǒng)上訪(fǎng)問(wèn)到壁畸。統(tǒng)一文件系統(tǒng)(union file system)技術(shù)能夠?qū)⒉煌膶诱铣梢粋€(gè)文件系統(tǒng),為這些層提供了一個(gè)統(tǒng)一的視角
鏡像與分層
- Docker 鏡像是多個(gè)的茅茂,堆疊的分層的只讀文件系統(tǒng)
- 每一層的變化是基于下一層的
- 相鄰層之間的改動(dòng)是有延續(xù)性的
- 從 docker 1.10 之后捏萍,每個(gè)鏡像層的由一致性的 hash 生成 id,取代了舊版使用隨機(jī)生成的 UUID
FROM ubuntu:15.10
COPY . /app
RUN make /app
CMD python /app/app.py
鏡像命名規(guī)則
registry.abc.net/orgname/imagename:tag
- registry.abc.net:Remote registry 的地址
-(docker.io,gcr.io) - orgname:(optional)組織區(qū)分的鏡像集合
- imagename:鏡像名稱(chēng)
- tag:標(biāo)簽
docker.io/oracle/mysql:v5.7.1
k8s.gcr.io/kube-apiserver-amd64:v1.11.0
小心 latest空闲,千萬(wàn)別被 latest tag 給誤導(dǎo)了令杈。latest 其實(shí)并沒(méi)有什么特殊的含義。當(dāng)沒(méi)指明鏡像
tag 時(shí)碴倾,Docker 會(huì)使用默認(rèn)值 latest逗噩,僅此而已,所以我們?cè)谑褂苗R像時(shí)最好還是避免使用 latest跌榔,明確指定某個(gè) tag异雁,比如 httpd:2.3
容器的定義
- 容器(container)的定義和鏡像(image)幾乎一模一樣,也是一堆層的統(tǒng)一視角僧须,
唯一區(qū)別在于容器的最上面那一層是可讀可寫(xiě)的 - 要點(diǎn):容器 = 鏡像 + 讀寫(xiě)層纲刀,并且容器的定義并沒(méi)有提及是否要運(yùn)行容器
Dockerfile
dockerfile
FROM debian
RUN apt-get install emacs
RUN apt-get install apache2
CMD ["/bin/bash"]
- 新鏡像不再是從 scratch 開(kāi)始,而是直接在 Debian base 鏡像上構(gòu)建
- 安裝 emacs 編輯器
- 安裝 apache2
- 容器啟動(dòng)時(shí)運(yùn)行 bash
構(gòu)建過(guò)程如下圖所示:
Dockerfile 是 docker 構(gòu)建鏡像的基礎(chǔ)担平,也是 docker區(qū)別于其他容器的重要特征示绊,正是有了 Dockerfile,docker 的自動(dòng)化和可移植性才成為可能
編寫(xiě)Dockerfile命令:
FROM:從一個(gè)基礎(chǔ)鏡像構(gòu)建新的鏡像
- FROM ubuntu
MAINTAINER:維護(hù)者信息
- MAINTAINER William <wlj@nicescale.com>
RUN:非交互式運(yùn)行shell命令
- RUN apt-get -y update
- RUN apt-get -y install nginx
ENV:設(shè)置環(huán)境變量
- ENV MYSQL 5.7
WORKDIR /path/to/workdir:設(shè)置工作目錄
- WORKDIR /var/www
USER:設(shè)置用戶(hù)ID
- USER nginx
VOLUME <#dir>:設(shè)置volume
- VOLUME [‘/data’]
EXPOSE:暴露哪些端口
- EXPOSE 80 443
ENTRYPOINT [‘executable’, ‘param1’,’param2’]:執(zhí)行命令
- ENTRYPOINT ["/usr/sbin/nginx"]
CMD [“param1”,”param2”]
- CMD ["start"]
注意:
- ENTRYPOINT 指令和 CMD 指令雖然是在 Dockerfile 中定義暂论,但是在構(gòu)建鏡像的時(shí)候并不會(huì)被執(zhí)行面褐,只有在執(zhí)行 docker run 命令啟動(dòng)容器時(shí)才會(huì)起作用
- 在 Dockerfile 中,只能有一個(gè) ENTRYPOINT 指令空另,如果有多個(gè) ENTRYPOINT 指令則以最后一個(gè)為準(zhǔn)
- 在 Dockerfile 中盆耽,只能有一個(gè) CMD 指令蹋砚,如果有多個(gè) CMD 指令則以最后一個(gè)為準(zhǔn)
- 在 Dockerfile 中扼菠,ENTRYPOINT 指令或 CMD指令摄杂,至少必有其一,如果設(shè)置了 ENTRYPOINT循榆,則 CMD 將作為參數(shù)
Dockerfile 例子
Dockerfile Best Practices
- 使用統(tǒng)一的base鏡像 - Centos, Ubuntu
- 使用小型基礎(chǔ)鏡像 - 生成鏡像也較小
- 動(dòng)靜分離 - 經(jīng)常變化的內(nèi)容和基本不會(huì)變化的內(nèi)容要分開(kāi)析恢,把不怎么變化的內(nèi)容放在下層, 創(chuàng)建出來(lái)不同基礎(chǔ)鏡像供上層使用秧饮。
- 最小原則:只安裝必需的東西
- 一個(gè)原則:每個(gè)鏡像只有一個(gè)功能 - 不要在容器里運(yùn)行多個(gè)不同功能的進(jìn)程映挂,每個(gè)鏡像中只安裝一個(gè)應(yīng)用的軟件包和文件,需要交互的程序通過(guò) pod 或者容器之間的網(wǎng)絡(luò)進(jìn)行交流盗尸。
- 使用更少的層 - 盡量把相關(guān)的內(nèi)容放到同一個(gè)層柑船,使用換行符進(jìn)行分割
- 切勿映射公有端口 - 鏡像應(yīng)該可以多次運(yùn)行在任何主機(jī)上
- 不要在構(gòu)建中升級(jí)軟件版本 - 應(yīng)在基礎(chǔ)鏡像中更新,類(lèi)似于類(lèi)的繼承泼各,在基類(lèi)里實(shí)現(xiàn)
- 利用 cache 來(lái)加快構(gòu)建速度 - docker build --cache-from 參數(shù)可以手動(dòng)指定一個(gè)鏡像來(lái)使用它的緩存鞍时。
- 版本控制和自動(dòng)構(gòu)建 - 最好把Dockerfile和對(duì)應(yīng)的應(yīng)用代碼一起放到版本控制中,然后能夠自動(dòng)構(gòu)建鏡像扣蜻。