Docker(二):Dockerfile使用介紹
上一篇文章Docker(一):Docker入門教程介紹了 Docker 基本概念,其中鏡像、容器和 Dockerfile 。我們使用 Dockerfile 定義鏡像,依賴鏡像來運(yùn)行容器,因此 Dockerfile 是鏡像和容器的關(guān)鍵,Dockerfile 可以非常容易的定義鏡像內(nèi)容,同時(shí)在我們后期的微服務(wù)實(shí)踐中,Dockerfile 也是重點(diǎn)關(guān)注的內(nèi)容,今天我們就來一起學(xué)習(xí)它。
首先通過一張圖來了解 Docker 鏡像、容器和 Dockerfile 三者之間的關(guān)系。
通過上圖可以看出使用 Dockerfile 定義鏡像,運(yùn)行鏡像啟動(dòng)容器。
Dockerfile 概念
Docker 鏡像是一個(gè)特殊的文件系統(tǒng),除了提供容器運(yùn)行時(shí)所需的程序、庫、資源、配置等文件外,還包含了一些為運(yùn)行時(shí)準(zhǔn)備的一些配置參數(shù)(如匿名卷、環(huán)境變量、用戶等)。鏡像不包含任何動(dòng)態(tài)數(shù)據(jù),其內(nèi)容在構(gòu)建之后也不會(huì)被改變。
鏡像的定制實(shí)際上就是定制每一層所添加的配置、文件。如果我們可以把每一層修改、安裝、構(gòu)建、操作的命令都寫入一個(gè)腳本,用這個(gè)腳本來構(gòu)建、定制鏡像,那么之前提及的無法重復(fù)的問題、鏡像構(gòu)建透明性的問題、體積的問題就都會(huì)解決。這個(gè)腳本就是 Dockerfile。
Dockerfile 是一個(gè)文本文件,其內(nèi)包含了一條條的指令(Instruction),每一條指令構(gòu)建一層,因此每一條指令的內(nèi)容,就是描述該層應(yīng)當(dāng)如何構(gòu)建。有了 Dockerfile,當(dāng)我們需要定制自己額外的需求時(shí),只需在 Dockerfile 上添加或者修改指令,重新生成 image 即可,省去了敲命令的麻煩。
Dockerfile 文件格式
Dockerfile文件格式如下:
- ## Dockerfile文件格式
- # This dockerfile uses the ubuntu image
- # VERSION 2 - EDITION 1
- # Author: docker_user
- # Command format: Instruction [arguments / command] ..
- # 1、***行必須指定 基礎(chǔ)鏡像信息
- FROM ubuntu
- # 2、維護(hù)者信息
- MAINTAINER docker_user docker_user@email.com
- # 3、鏡像操作指令
- RUN echo "deb http://archive.ubuntu.com/ubuntu/ raring main universe" >> /etc/apt/sources.list
- RUN apt-get update && apt-get install -y nginx
- RUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf
- # 4、容器啟動(dòng)執(zhí)行指令
- CMD /usr/sbin/nginx
Dockerfile 分為四部分:基礎(chǔ)鏡像信息、維護(hù)者信息、鏡像操作指令、容器啟動(dòng)執(zhí)行指令。一開始必須要指明所基于的鏡像名稱,接下來一般會(huì)說明維護(hù)者信息;后面則是鏡像操作指令,例如 RUN 指令。每執(zhí)行一條RUN 指令,鏡像添加新的一層,并提交;***是 CMD 指令,來指明運(yùn)行容器時(shí)的操作命令。
構(gòu)建鏡像
docker build 命令會(huì)根據(jù) Dockerfile 文件及上下文構(gòu)建新 Docker 鏡像。構(gòu)建上下文是指 Dockerfile 所在的本地路徑或一個(gè)URL(Git倉庫地址)。構(gòu)建上下文環(huán)境會(huì)被遞歸處理,所以構(gòu)建所指定的路徑還包括了子目錄,而URL還包括了其中指定的子模塊。
將當(dāng)前目錄做為構(gòu)建上下文時(shí),可以像下面這樣使用docker build命令構(gòu)建鏡像:
- docker build .
- Sending build context to Docker daemon 6.51 MB
- ...
說明:構(gòu)建會(huì)在 Docker 后臺(tái)守護(hù)進(jìn)程(daemon)中執(zhí)行,而不是CLI中。構(gòu)建前,構(gòu)建進(jìn)程會(huì)將全部內(nèi)容(遞歸)發(fā)送到守護(hù)進(jìn)程。大多情況下,應(yīng)該將一個(gè)空目錄作為構(gòu)建上下文環(huán)境,并將 Dockerfile 文件放在該目錄下。
在構(gòu)建上下文中使用的 Dockerfile 文件,是一個(gè)構(gòu)建指令文件。為了提高構(gòu)建性能,可以通過.dockerignore文件排除上下文目錄下不需要的文件和目錄。
在 Docker 構(gòu)建鏡像的***步,docker CLI 會(huì)先在上下文目錄中尋找.dockerignore文件,根據(jù).dockerignore 文件排除上下文目錄中的部分文件和目錄,然后把剩下的文件和目錄傳遞給 Docker 服務(wù)。
Dockerfile 一般位于構(gòu)建上下文的根目錄下,也可以通過-f指定該文件的位置:
- docker build -f /path/to/a/Dockerfile .
構(gòu)建時(shí),還可以通過-t參數(shù)指定構(gòu)建成鏡像的倉庫、標(biāo)簽。
鏡像標(biāo)簽
- docker build -t nginx/v3 .
如果存在多個(gè)倉庫下,或使用多個(gè)鏡像標(biāo)簽,就可以使用多個(gè)-t參數(shù):
- docker build -t nginx/v3:1.0.2 -t nginx/v3:latest .
在 Docker 守護(hù)進(jìn)程執(zhí)行 Dockerfile 中的指令前,首先會(huì)對(duì) Dockerfile 進(jìn)行語法檢查,有語法錯(cuò)誤時(shí)會(huì)返回:
- docker build -t nginx/v3 .
- Sending build context to Docker daemon 2.048 kB
- Error response from daemon: Unknown instruction: RUNCMD
緩存
Docker 守護(hù)進(jìn)程會(huì)一條一條的執(zhí)行 Dockerfile 中的指令,而且會(huì)在每一步提交并生成一個(gè)新鏡像,***會(huì)輸出最終鏡像的ID。生成完成后,Docker 守護(hù)進(jìn)程會(huì)自動(dòng)清理你發(fā)送的上下文。 Dockerfile文件中的每條指令會(huì)被獨(dú)立執(zhí)行,并會(huì)創(chuàng)建一個(gè)新鏡像,RUN cd /tmp等命令不會(huì)對(duì)下條指令產(chǎn)生影響。 Docker 會(huì)重用已生成的中間鏡像,以加速docker build的構(gòu)建速度。以下是一個(gè)使用了緩存鏡像的執(zhí)行過程:
- $ docker build -t svendowideit/ambassador .
- Sending build context to Docker daemon 15.36 kB
- Step 1/4 : FROM alpine:3.2
- ---> 31f630c65071
- Step 2/4 : MAINTAINER SvenDowideit@home.org.au
- ---> Using cache
- ---> 2a1c91448f5f
- Step 3/4 : RUN apk update && apk add socat && rm -r /var/cache/
- ---> Using cache
- ---> 21ed6e7fbb73
- Step 4/4 : CMD env | grep _TCP= | (sed 's/.*_PORT_\([0-9]*\)_TCP=tcp:\/\/\(.*\):\(.*\)/socat -t 100000000 TCP4-LISTEN:\1,fork,reuseaddr TCP4:\2:\3 \&/' && echo wait) | sh
- ---> Using cache
- ---> 7ea8aef582cc
- Successfully built 7ea8aef582cc
構(gòu)建緩存僅會(huì)使用本地父生成鏈上的鏡像,如果不想使用本地緩存的鏡像,也可以通過--cache-from指定緩存。指定后將不再使用本地生成的鏡像鏈,而是從鏡像倉庫中下載。
尋找緩存的邏輯
Docker 尋找緩存的邏輯其實(shí)就是樹型結(jié)構(gòu)根據(jù) Dockerfile 指令遍歷子節(jié)點(diǎn)的過程。下圖可以說明這個(gè)邏輯。
- FROM base_image:version Dockerfile:
- +----------+ FROM base_image:version
- |base image| RUN cmd1 --> use cache because we found base image
- +-----X----+ RUN cmd11 --> use cache because we found cmd1
- / \
- / \
- RUN cmd1 RUN cmd2 Dockerfile:
- +------+ +------+ FROM base_image:version
- |image1| |image2| RUN cmd2 --> use cache because we found base image
- +---X--+ +------+ RUN cmd21 --> not use cache because there's no child node
- / \ running cmd21, so we build a new image here
- / \
- RUN cmd11 RUN cmd12
- +-------+ +-------+
- |image11| |image12|
- +-------+ +-------+
大部分指令可以根據(jù)上述邏輯去尋找緩存,除了 ADD 和 COPY 。這兩個(gè)指令會(huì)復(fù)制文件內(nèi)容到鏡像內(nèi),除了指令相同以外,Docker 還會(huì)檢查每個(gè)文件內(nèi)容校驗(yàn)(不包括***修改時(shí)間和***訪問時(shí)間),如果校驗(yàn)不一致,則不會(huì)使用緩存。
除了這兩個(gè)命令,Docker 并不會(huì)去檢查容器內(nèi)的文件內(nèi)容,比如 RUN apt-get -y update,每次執(zhí)行時(shí)文件可能都不一樣,但是 Docker 認(rèn)為命令一致,會(huì)繼續(xù)使用緩存。這樣一來,以后構(gòu)建時(shí)都不會(huì)再重新運(yùn)行apt-get -y update。
如果 Docker 沒有找到當(dāng)前指令的緩存,則會(huì)構(gòu)建一個(gè)新的鏡像,并且之后的所有指令都不會(huì)再去尋找緩存。
簡單示例
接下來用一個(gè)簡單的示例來感受一下 Dockerfile 是如何用來構(gòu)建鏡像啟動(dòng)容器。我們以定制 nginx 鏡像為例,在一個(gè)空白目錄中,建立一個(gè)文本文件,并命名為 Dockerfile:
- mkdir mynginx
- cd mynginx
- vi Dockerfile
構(gòu)建一個(gè) Dockerfile 文件內(nèi)容為:
- FROM nginx
- RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
- vi Dockerfile
這個(gè) Dockerfile 很簡單,一共就兩行涉及到了兩條指令:FROM 和 RUN,F(xiàn)ROM 表示獲取指定基礎(chǔ)鏡像,RUN 執(zhí)行命令,在執(zhí)行的過程中重寫了 nginx 的默認(rèn)頁面信息,將信息替換為:Hello, Docker!。
在 Dockerfile 文件所在目錄執(zhí)行:
- docker build -t nginx:v1 .
命令***有一個(gè). 表示當(dāng)前目錄
構(gòu)建完成之后,使用 docker images 命令查看所有鏡像,如果存在 REPOSITORY 為 nginx 和 TAG 是 v1 的信息,就表示構(gòu)建成功。
- docker images
- REPOSITORY TAG IMAGE ID CREATED SIZE
- nginx v1 8c92471de2cc 6 minutes ago 108.6 MB
接下來使用 docker run 命令來啟動(dòng)容器
- docker run --name docker_nginx_v1 -d -p 80:80 nginx:v1
這條命令會(huì)用 nginx 鏡像啟動(dòng)一個(gè)容器,命名為docker_nginx_v1,并且映射了 80 端口,這樣我們可以用瀏覽器去訪問這個(gè) nginx 服務(wù)器:http://192.168.0.54/,頁面返回信息:
這樣一個(gè)簡單使用 Dockerfile 構(gòu)建鏡像,運(yùn)行容器的示例就完成了!
修改容器內(nèi)容
容器啟動(dòng)后,需要對(duì)容器內(nèi)的文件進(jìn)行進(jìn)一步的完善,可以使用docker exec -it xx bash命令再次進(jìn)行修改,以上面的示例為基礎(chǔ),修改 nginx 啟動(dòng)頁面內(nèi)容:
- docker exec -it docker_nginx_v1 bash
- root@3729b97e8226:/# echo '<h1>Hello, Docker neo!</h1>' > /usr/share/nginx/html/index.html
- root@3729b97e8226:/# exit
- exit
以交互式終端方式進(jìn)入 docker_nginx_v1 容器,并執(zhí)行了 bash 命令,也就是獲得一個(gè)可操作的 Shell。然后,我們用<h1>Hello, Docker neo!</h1>覆蓋了 /usr/share/nginx/html/index.html 的內(nèi)容。
再次刷新瀏覽器,會(huì)發(fā)現(xiàn)內(nèi)容被改變。
修改了容器的文件,也就是改動(dòng)了容器的存儲(chǔ)層,可以通過 docker diff 命令看到具體的改動(dòng)。
- docker diff docker_nginx_v1
- ...
這樣 Dockerfile 使用方式就為大家介紹完了,下期為大家介紹 Dockerfile 命令的詳細(xì)使用。
【本文為51CTO專欄作者“純潔的微笑”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)通過微信公眾號(hào)聯(lián)系作者獲取授權(quán)】