最近公司項目上線原因梁呈,一直加班婚度。沒有時間更新文章。隔壁部門需要我提供sdk的打包的支持官卡,所以一直在學(xué)習(xí)docker蝗茁。原文
從剛才的 docker commit
的學(xué)習(xí)中,我們可以了解到寻咒,鏡像的定制實際上就是定制每一層所添加的配置哮翘、文件。如果我們可以把每一層修改毛秘、安裝饭寺、構(gòu)建、操作的命令都寫入一個腳本叫挟,用這個腳本來構(gòu)建艰匙、定制鏡像,那么之前提及的無法重復(fù)的問題霞揉、鏡像構(gòu)建透明性的問題旬薯、體積的問題就都會解決晰骑。這個腳本就是 Dockerfile适秩。
Dockerfile 是一個文本文件,其內(nèi)包含了一條條的指令(Instruction)硕舆,每一條指令構(gòu)建一層秽荞,因此每一條指令的內(nèi)容,就是描述該層應(yīng)當(dāng)如何構(gòu)建抚官。
還以之前定制 nginx
鏡像為例扬跋,這次我們使用 Dockerfile 來定制。
在一個空白目錄中凌节,建立一個文本文件钦听,并命名為 Dockerfile
:
$ mkdir mynginx
$ cd mynginx
$ touch Dockerfile
其內(nèi)容為:
FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
這個 Dockerfile 很簡單洒试,一共就兩行。涉及到了兩條指令朴上,FROM
和 RUN
垒棋。
FROM 指定基礎(chǔ)鏡像
所謂定制鏡像,那一定是以一個鏡像為基礎(chǔ)痪宰,在其上進(jìn)行定制叼架。就像我們之前運行了一個 nginx
鏡像的容器,再進(jìn)行修改一樣衣撬,基礎(chǔ)鏡像是必須指定的乖订。而 FROM
就是指定基礎(chǔ)鏡像,因此一個 Dockerfile
中 FROM
是必備的指令具练,并且必須是第一條指令乍构。
在 Docker Hub1 上有非常多的高質(zhì)量的官方鏡像, 有可以直接拿來使用的服務(wù)類的鏡像靠粪,如 nginx
蜡吧、redis
、mongo
占键、mysql
昔善、httpd
、php
畔乙、tomcat
等君仆; 也有一些方便開發(fā)、構(gòu)建牲距、運行各種語言應(yīng)用的鏡像返咱,如 node
、openjdk
牍鞠、python
咖摹、ruby
、golang
等难述。 可以在其中尋找一個最符合我們最終目標(biāo)的鏡像為基礎(chǔ)鏡像進(jìn)行定制萤晴。 如果沒有找到對應(yīng)服務(wù)的鏡像,官方鏡像中還提供了一些更為基礎(chǔ)的操作系統(tǒng)鏡像胁后,如 ubuntu
店读、debian
、centos
攀芯、fedora
屯断、alpine
等,這些操作系統(tǒng)的軟件庫為我們提供了更廣闊的擴(kuò)展空間。
除了選擇現(xiàn)有鏡像為基礎(chǔ)鏡像外殖演,Docker 還存在一個特殊的鏡像氧秘,名為 scratch
。這個鏡像是虛擬的概念趴久,并不實際存在敏储,它表示一個空白的鏡像。
FROM scratch
...
如果你以 scratch
為基礎(chǔ)鏡像的話朋鞍,意味著你不以任何鏡像為基礎(chǔ)已添,接下來所寫的指令將作為鏡像第一層開始存在。
不以任何系統(tǒng)為基礎(chǔ)滥酥,直接將可執(zhí)行文件復(fù)制進(jìn)鏡像的做法并不罕見更舞,比如 swarm
、coreos/etcd
坎吻。對于 Linux 下靜態(tài)編譯的程序來說缆蝉,并不需要有操作系統(tǒng)提供運行時支持,所需的一切庫都已經(jīng)在可執(zhí)行文件里了瘦真,因此直接 FROM scratch
會讓鏡像體積更加小巧刊头。使用 Go 語言 開發(fā)的應(yīng)用很多會使用這種方式來制作鏡像,這也是為什么有人認(rèn)為 Go 是特別適合容器微服務(wù)架構(gòu)的語言的原因之一诸尽。
RUN 執(zhí)行命令
RUN
指令是用來執(zhí)行命令行命令的原杂。由于命令行的強(qiáng)大能力,RUN
指令在定制鏡像時是最常用的指令之一您机。其格式有兩種:
-
shell 格式:
RUN <命令>
穿肄,就像直接在命令行中輸入的命令一樣。剛才寫的 Dockerfile 中的RUN
指令就是這種格式际看。
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
-
exec 格式:
RUN ["可執(zhí)行文件", "參數(shù)1", "參數(shù)2"]
咸产,這更像是函數(shù)調(diào)用中的格式。
既然 RUN
就像 Shell 腳本一樣可以執(zhí)行命令仲闽,那么我們是否就可以像 Shell 腳本一樣把每個命令對應(yīng)一個 RUN 呢脑溢?比如這樣:
FROM debian:jessie
RUN apt-get update
RUN apt-get install -y gcc libc6-dev make
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz"
RUN mkdir -p /usr/src/redis
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
RUN make -C /usr/src/redis
RUN make -C /usr/src/redis install
之前說過,Dockerfile 中每一個指令都會建立一層赖欣,RUN
也不例外屑彻。每一個 RUN
的行為,就和剛才我們手工建立鏡像的過程一樣:新建立一層畏鼓,在其上執(zhí)行這些命令酱酬,執(zhí)行結(jié)束后壶谒,commit
這一層的修改云矫,構(gòu)成新的鏡像。
而上面的這種寫法汗菜,創(chuàng)建了 7 層鏡像让禀。這是完全沒有意義的挑社,而且很多運行時不需要的東西,都被裝進(jìn)了鏡像里巡揍,比如編譯環(huán)境痛阻、更新的軟件包等等。結(jié)果就是產(chǎn)生非常臃腫腮敌、非常多層的鏡像阱当,不僅僅增加了構(gòu)建部署的時間,也很容易出錯糜工。 這是很多初學(xué) Docker 的人常犯的一個錯誤弊添。
Union FS 是有最大層數(shù)限制的,比如 AUFS捌木,曾經(jīng)是最大不得超過 42 層油坝,現(xiàn)在是不得超過 127 層。
上面的 Dockerfile
正確的寫法應(yīng)該是這樣:
FROM debian:jessie
RUN buildDeps='gcc libc6-dev make' \
&& apt-get update \
&& apt-get install -y $buildDeps \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz" \
&& mkdir -p /usr/src/redis \
&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
&& make -C /usr/src/redis \
&& make -C /usr/src/redis install \
&& rm -rf /var/lib/apt/lists/* \
&& rm redis.tar.gz \
&& rm -r /usr/src/redis \
&& apt-get purge -y --auto-remove $buildDeps
首先刨裆,之前所有的命令只有一個目的猾浦,就是編譯怔鳖、安裝 redis 可執(zhí)行文件。因此沒有必要建立很多層,這只是一層的事情杏愤。因此,這里沒有使用很多個 RUN
對一一對應(yīng)不同的命令蒲凶,而是僅僅使用一個 RUN
指令熟妓,并使用 &&
將各個所需命令串聯(lián)起來。將之前的 7 層慈俯,簡化為了 1 層渤刃。在撰寫 Dockerfile 的時候,要經(jīng)常提醒自己贴膘,這并不是在寫 Shell 腳本卖子,而是在定義每一層該如何構(gòu)建。
并且刑峡,這里為了格式化還進(jìn)行了換行洋闽。Dockerfile 支持 Shell 類的行尾添加 \
的命令換行方式,以及行首 #
進(jìn)行注釋的格式突梦。良好的格式诫舅,比如換行、縮進(jìn)宫患、注釋等刊懈,會讓維護(hù)、排障更為容易,這是一個比較好的習(xí)慣虚汛。
此外匾浪,還可以看到這一組命令的最后添加了清理工作的命令,刪除了為了編譯構(gòu)建所需要的軟件卷哩,清理了所有下載蛋辈、展開的文件,并且還清理了 apt
緩存文件将谊。這是很重要的一步冷溶,我們之前說過,鏡像是多層存儲尊浓,每一層的東西并不會在下一層被刪除挂洛,會一直跟隨著鏡像。因此鏡像構(gòu)建時眠砾,一定要確保每一層只添加真正需要添加的東西虏劲,任何無關(guān)的東西都應(yīng)該清理掉。
很多人初學(xué) Docker 制作出了很臃腫的鏡像的原因之一褒颈,就是忘記了每一層構(gòu)建的最后一定要清理掉無關(guān)文件柒巫。
構(gòu)建鏡像
好了,讓我們再回到之前定制的 nginx 鏡像的 Dockerfile 來」韧瑁現(xiàn)在我們明白了這個 Dockerfile 的內(nèi)容堡掏,那么讓我們來構(gòu)建這個鏡像吧。
在 Dockerfile
文件所在目錄執(zhí)行:
$ docker build -t nginx:v3 .
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM nginx
---> e43d811ce2f4
Step 2 : RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
---> Running in 9cdc27646c7b
---> 44aa4490ce2c
Removing intermediate container 9cdc27646c7b
Successfully built 44aa4490ce2c
從命令的輸出結(jié)果中刨疼,我們可以清晰的看到鏡像的構(gòu)建過程泉唁。在 Step 2
中,如同我們之前所說的那樣揩慕,RUN
指令啟動了一個容器 9cdc27646c7b
亭畜,執(zhí)行了所要求的命令,并最后提交了這一層 44aa4490ce2c
迎卤,隨后刪除了所用到的這個容器 9cdc27646c7b
拴鸵。
這里我們使用了 docker build
命令進(jìn)行鏡像構(gòu)建。其格式為:
docker build [選項] <上下文路徑/URL/->
在這里我們指定了最終鏡像的名稱 -t nginx:v3
蜗搔,構(gòu)建成功后劲藐,我們可以像之前運行 nginx:v2
那樣來運行這個鏡像,其結(jié)果會和 nginx:v2
一樣樟凄。
鏡像構(gòu)建上下文(Context)
如果注意聘芜,會看到 docker build
命令最后有一個 .
。.
表示當(dāng)前目錄缝龄,而 Dockerfile
就在當(dāng)前目錄汰现,因此不少初學(xué)者以為這個路徑是在指定 Dockerfile
所在路徑挂谍,這么理解其實是不準(zhǔn)確的。如果對應(yīng)上面的命令格式服鹅,你可能會發(fā)現(xiàn),這是在指定上下文路徑百新。那么什么是上下文呢企软?
首先我們要理解 docker build
的工作原理。Docker 在運行時分為 Docker 引擎(也就是服務(wù)端守護(hù)進(jìn)程)和客戶端工具饭望。Docker 的引擎提供了一組 REST API仗哨,被稱為 Docker Remote API,而如 docker
命令這樣的客戶端工具铅辞,則是通過這組 API 與 Docker 引擎交互厌漂,從而完成各種功能。因此斟珊,雖然表面上我們好像是在本機(jī)執(zhí)行各種 docker
功能苇倡,但實際上,一切都是使用的遠(yuǎn)程調(diào)用形式在服務(wù)端(Docker 引擎)完成囤踩。也因為這種 C/S 設(shè)計旨椒,讓我們操作遠(yuǎn)程服務(wù)器的 Docker 引擎變得輕而易舉。
當(dāng)我們進(jìn)行鏡像構(gòu)建的時候堵漱,并非所有定制都會通過 RUN
指令完成综慎,經(jīng)常會需要將一些本地文件復(fù)制進(jìn)鏡像,比如通過 COPY
指令勤庐、ADD
指令等示惊。而 docker build
命令構(gòu)建鏡像,其實并非在本地構(gòu)建愉镰,而是在服務(wù)端米罚,也就是 Docker 引擎中構(gòu)建的。那么在這種客戶端/服務(wù)端的架構(gòu)中丈探,如何才能讓服務(wù)端獲得本地文件呢阔拳?
這就引入了上下文的概念。當(dāng)構(gòu)建的時候类嗤,用戶會指定構(gòu)建鏡像上下文的路徑糊肠,docker build
命令得知這個路徑后,會將路徑下的所有內(nèi)容打包遗锣,然后上傳給 Docker 引擎货裹。這樣 Docker 引擎收到這個上下文包后,展開就會獲得構(gòu)建鏡像所需的一切文件精偿。
如果在 Dockerfile
中這么寫:
COPY ./package.json /app/
這并不是要復(fù)制執(zhí)行 docker build
命令所在的目錄下的 package.json
弧圆,也不是復(fù)制 Dockerfile
所在目錄下的 package.json
赋兵,而是復(fù)制 上下文(context) 目錄下的 package.json
。
因此搔预,COPY
這類指令中的源文件的路徑都是相對路徑霹期。這也是初學(xué)者經(jīng)常會問的為什么 COPY ../package.json /app
或者 COPY /opt/xxxx /app
無法工作的原因,因為這些路徑已經(jīng)超出了上下文的范圍拯田,Docker 引擎無法獲得這些位置的文件历造。如果真的需要那些文件,應(yīng)該將它們復(fù)制到上下文目錄中去船庇。
現(xiàn)在就可以理解剛才的命令 docker build -t nginx:v3 .
中的這個 .
吭产,實際上是在指定上下文的目錄,docker build
命令會將該目錄下的內(nèi)容打包交給 Docker 引擎以幫助構(gòu)建鏡像鸭轮。
如果觀察 docker build
輸出臣淤,我們其實已經(jīng)看到了這個發(fā)送上下文的過程:
$ docker build -t nginx:v3 .
Sending build context to Docker daemon 2.048 kB
...
理解構(gòu)建上下文對于鏡像構(gòu)建是很重要的,避免犯一些不應(yīng)該的錯誤窃爷。比如有些初學(xué)者在發(fā)現(xiàn) COPY /opt/xxxx /app
不工作后邑蒋,于是干脆將 Dockerfile
放到了硬盤根目錄去構(gòu)建,結(jié)果發(fā)現(xiàn) docker build
執(zhí)行后按厘,在發(fā)送一個幾十 GB 的東西寺董,極為緩慢而且很容易構(gòu)建失敗。那是因為這種做法是在讓 docker build
打包整個硬盤刻剥,這顯然是使用錯誤遮咖。
一般來說,應(yīng)該會將 Dockerfile
置于一個空目錄下造虏,或者項目根目錄下御吞。如果該目錄下沒有所需文件,那么應(yīng)該把所需文件復(fù)制一份過來漓藕。如果目錄下有些東西確實不希望構(gòu)建時傳給 Docker 引擎陶珠,那么可以用 .gitignore
一樣的語法寫一個 .dockerignore
,該文件是用于剔除不需要作為上下文傳遞給 Docker 引擎的享钞。
那么為什么會有人誤以為 .
是指定 Dockerfile
所在目錄呢揍诽?這是因為在默認(rèn)情況下,如果不額外指定 Dockerfile
的話栗竖,會將上下文目錄下的名為 Dockerfile
的文件作為 Dockerfile暑脆。
這只是默認(rèn)行為,實際上 Dockerfile
的文件名并不要求必須為 Dockerfile
狐肢,而且并不要求必須位于上下文目錄中添吗,比如可以用 -f ../Dockerfile.php
參數(shù)指定某個文件作為 Dockerfile
。
當(dāng)然份名,一般大家習(xí)慣性的會使用默認(rèn)的文件名 Dockerfile
碟联,以及會將其置于鏡像構(gòu)建上下文目錄中妓美。
其它 docker build
的用法
直接用 Git repo 進(jìn)行構(gòu)建
或許你已經(jīng)注意到了,docker build
還支持從 URL 構(gòu)建鲤孵,比如可以直接從 Git repo 中構(gòu)建:
$ docker build https://github.com/twang2218/gitlab-ce-zh.git#:8.14
docker build https://github.com/twang2218/gitlab-ce-zh.git\#:8.14
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM gitlab/gitlab-ce:8.14.0-ce.0
8.14.0-ce.0: Pulling from gitlab/gitlab-ce
aed15891ba52: Already exists
773ae8583d14: Already exists
...
這行命令指定了構(gòu)建所需的 Git repo壶栋,并且指定默認(rèn)的 master
分支,構(gòu)建目錄為 /8.14/
普监,然后 Docker 就會自己去 git clone
這個項目贵试、切換到指定分支、并進(jìn)入到指定目錄后開始構(gòu)建鹰椒。
用給定的 tar 壓縮包構(gòu)建
$ docker build http://server/context.tar.gz
如果所給出的 URL 不是個 Git repo锡移,而是個 tar
壓縮包呕童,那么 Docker 引擎會下載這個包漆际,并自動解壓縮,以其作為上下文夺饲,開始構(gòu)建奸汇。
從標(biāo)準(zhǔn)輸入中讀取 Dockerfile 進(jìn)行構(gòu)建
docker build - < Dockerfile
或
cat Dockerfile | docker build -
如果標(biāo)準(zhǔn)輸入傳入的是文本文件,則將其視為 Dockerfile
往声,并開始構(gòu)建擂找。這種形式由于直接從標(biāo)準(zhǔn)輸入中讀取 Dockerfile 的內(nèi)容,它沒有上下文浩销,因此不可以像其他方法那樣可以將本地文件 COPY
進(jìn)鏡像之類的事情贯涎。
從標(biāo)準(zhǔn)輸入中讀取上下文壓縮包進(jìn)行構(gòu)建
$ docker build - < context.tar.gz
如果發(fā)現(xiàn)標(biāo)準(zhǔn)輸入的文件格式是 gzip
、bzip2
以及 xz
的話慢洋,將會使其為上下文壓縮包塘雳,直接將其展開,將里面視為上下文普筹,并開始構(gòu)建败明。
- Docker Store是發(fā)現(xiàn)公共Docker內(nèi)容,鏡像發(fā)布和發(fā)行軟件的新地方 ?