構(gòu)建鏡像
前面我們使用各種鏡像進(jìn)行測試演示,很多情況下我們是需要自己的鏡像,滿足自己業(yè)務(wù)需要的鏡像,這就需要我們能夠定制自己需要的鏡像,構(gòu)建 Docker 鏡像有以下兩種方法。
- 使用 docker commit 命令。
- 使用 docker build 命令和 Dockerfile 構(gòu)建文件。
現(xiàn)在我們不推薦使用 docker commit 命令,而應(yīng)該使用更靈活、更強(qiáng)大的 Dockerfile 來構(gòu)建 Docker 鏡像。
1、使用 commit 命令構(gòu)建
docker commit 命令是創(chuàng)建新鏡像最直觀的方法,其過程包含三個(gè)步驟:
- 運(yùn)行容器;
- 修改容器;
- 將容器保存為新的鏡像。
先從創(chuàng)建一個(gè)新容器開始,這個(gè)容器我們就使用很常見的 ubuntu 鏡像,操作步驟如下
1.1 運(yùn)行一個(gè)要進(jìn)行修改的容器
root@ubuntu:~# docker run -ti ubuntu /bin/bash
root@733a4b080491:/#
1.2 安裝 Apache 軟件包
root@733a4b080491:/# apt-get update
... ...
root@733a4b080491:/# apt-get install -y apache2
... ...
我們啟動(dòng)了一個(gè)容器,并在里面安裝了 Apache 。我們將會(huì)拿這個(gè)容器作為一個(gè) Web 服務(wù)器來運(yùn)行,我們需要把它保存下來,這樣就不用每次都運(yùn)行這個(gè)步驟了。
1.3 提交定制容器
root@ubuntu:~# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
733a4b080491 ubuntu "/bin/bash" 11 minutes ago Exited (0) 5 seconds ago suspicious_mestorf
root@ubuntu:~# docker commit 733a4b080491 wzlinux/ubuntu_with_apache
sha256:902ac2c87147fefc5b70c741ce9550dcda426cea9f824f442d5cc2744bdc90ae
root@ubuntu:~# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
wzlinux/ubuntu_with_apache latest 902ac2c87147 33 seconds ago 261MB
ubuntu latest 20c44cd7596f 10 days ago 123MB
可以看到,我們使用 docker commit 提交了修改過的容器,從 size 上可以看到鏡像因?yàn)榘惭b軟件而變大了,docker commit 提交的只是創(chuàng)建容器的鏡像與容器的當(dāng)前狀態(tài)之間有差異的部分,這使得該更新非常輕量。
以上演示了如何用 docker commit 創(chuàng)建新鏡像。然而,Docker 并不建議用戶通過這種方式構(gòu)建鏡像。因?yàn)檫@是一種手工創(chuàng)建鏡像的方式,容易出錯(cuò),效率低且可重復(fù)性弱。比如要在 debian base 鏡像中也加入 apache,還得重復(fù)前面的所有步驟。更重要的:使用者并不知道鏡像是如何創(chuàng)建出來的,里面是否有惡意程序。也就是說無法對鏡像進(jìn)行審計(jì),存在安全隱患。
不過,為了對 Docker 有一個(gè)更全面的了解,我們還是要了解一下如何使用 docker commit 構(gòu)建 Docker 鏡像。因?yàn)榧幢闶怯?Dockerfile(推薦方法)構(gòu)建鏡像,底層也 docker commit 一層一層構(gòu)建新鏡像的。學(xué)習(xí) docker commit 能夠幫助我們更加深入地理解構(gòu)建過程和鏡像的分層結(jié)構(gòu)。
2、使用 Dockerfile 構(gòu)建
Dockerfile 使用基本的基于DSL(Domain Specific Language)語法的指令來構(gòu)建一個(gè) Docker 鏡像,我們推薦使用 Dockerfile 方法來代替 docker commit,因?yàn)橥ㄟ^前者構(gòu)建鏡像更具備可重復(fù)性、透明性以及冪等性。
一旦有了 Dockerfile,我們就可以使用 docker build 命令基于該 Dockerfile 中的指令構(gòu)建一個(gè)新的鏡像。
2.1 我們的第一個(gè) Dockerfile
用 Dockerfile 創(chuàng)建上面的 ubuntu_with_apache,內(nèi)容如下。
# Version 0.0.1
FROM ubuntu
RUN sed -i 's/archive.ubuntu.com/cn.archive.ubuntu.com/g' /etc/apt/sources.list
RUN sed -i 's/security.ubuntu/cn.archive.ubuntu/g' /etc/apt/sources.list
RUN apt-get -y update && apt-get -y install apache2
EXPOSE 80
執(zhí)行 docker build 命令時(shí),Dockerfile 中的所有指令都會(huì)被執(zhí)行并且提交,并且在該命令成功結(jié)束后返回一個(gè)新鏡像。
root@ubuntu:~/sample# docker build -t ubuntu_with_apache_dockerfile . ①
Sending build context to Docker daemon 6.144kB ②
Step 1/5 : FROM ubuntu ③
---> 20c44cd7596f
Step 2/5 : RUN sed -i 's/archive.ubuntu.com/cn.archive.ubuntu.com/g' /etc/apt/sources.list
---> Running in bac6dc3b900f
---> c66ad94ad8a4
Removing intermediate container bac6dc3b900f
Step 3/5 : RUN sed -i 's/security.ubuntu/cn.archive.ubuntu/g' /etc/apt/sources.list
---> Running in 5158558b6403
---> 0a4c480147c5
Removing intermediate container 5158558b6403
Step 4/5 : RUN apt-get -y update && apt-get -y install apache2 ④
---> Running in f547ce7a1b39 ⑤
……
……
---> 118bde35120a ⑥
Removing intermediate container f547ce7a1b39 ⑦
Step 5/5 : EXPOSE 80
---> Running in e546786de05b
---> f55d7b07365b
Removing intermediate container e546786de05b
Successfully built f55d7b07365b ⑧
Successfully tagged ubuntu_with_apache_dockerfile:latest
![](/d/20211016/524482197a5e5e68e5bba8182bcd502b.gif)
① 運(yùn)行 docker build 命令,-t 將新鏡像命名為 ubuntu-with-apache-dockerfile,命令末尾的 . 指明 build context 為當(dāng)前目錄。Docker 默認(rèn)會(huì)從 build context 中查找 Dockerfile 文件,我們也可以通過 -f 參數(shù)指定 Dockerfile 的位置。
② 從這步開始就是鏡像真正的構(gòu)建過程。 首先 Docker 將 build context 中的所有文件發(fā)送給 Docker daemon。build context 為鏡像構(gòu)建提供所需要的文件或目錄。
Dockerfile 中的 ADD、COPY 等命令可以將 build context 中的文件添加到鏡像。此例中,build context 為當(dāng)前目錄 /sample,該目錄下的所有文件和子目錄都會(huì)被發(fā)送給 Docker daemon。
所以,使用 build context 就得小心了,不要將多余文件放到 build context,特別不要把 /、/usr 作為 build context,否則構(gòu)建過程會(huì)相當(dāng)緩慢甚至失敗。
③ Step 1:執(zhí)行 FROM,將 ubuntu 作為 base 鏡像。ubuntu 鏡像 ID 為 452a96d81c30。
④ Step 4:執(zhí)行 RUN,安裝 apache,具體步驟為 ⑤ ~ ⑬。
⑤ 啟動(dòng) ID 為 e38bc83df8b1 的臨時(shí)容器,在容器中通過 apt-get 安裝 apache。
⑥ 安裝成功后,將容器保存為鏡像,其 ID 為 fbc9af08328d。這一步底層使用的是類似 docker commit 的命令。
⑦ 刪除臨時(shí)容器 02a4f3243dda。
⑧ 鏡像構(gòu)建成功。
通過 docker images 查看鏡像信息。
root@ubuntu:~/sample# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu_with_apache_dockerfile latest f55d7b07365b 27 minutes ago 261MB
wzlinux/ubuntu_with_apache latest 902ac2c87147 About an hour ago 261MB
ubuntu latest 20c44cd7596f 10 days ago 123MB
2.2 查看鏡像分成結(jié)構(gòu)
ubuntu_with_apache_dockerfile 是通過在 base 鏡像的頂部添加幾個(gè)新的鏡像層而得到的。
![](/d/20211016/6d2b4a1984f3c5bd4237e98f2520c8b4.gif)
上圖是從原文中拷貝的,下圖是在我的電腦上面實(shí)驗(yàn)得到的數(shù)據(jù),IMAGE的ID不同,但是其他都是相同的。
![](/d/20211016/b1a6f544635c9a6649ab9be56a96bd51.gif)
查看我本機(jī)的Ubuntu的IMAGE歷史如下:
![](/d/20211016/b25521f9e6eacf480611a45a32ea8c56.gif)
從輸出的結(jié)果可以看出來,每個(gè)命令都會(huì)生成一個(gè)鏡像層。
docker history 會(huì)顯示鏡像的構(gòu)建歷史,也就是 Dockerfile 的執(zhí)行過程。
ubuntu_with_apache_dockerfile 與 ubuntu 鏡像相比,確實(shí)只是多了幾層,Dockerfile 中的每個(gè)指令都會(huì)創(chuàng)建一層,docker history 也向我們展示了鏡像的分層結(jié)構(gòu),每一層由上至下排列。
2.3 鏡像的緩存特性
由于每一步的構(gòu)建過程都會(huì)將結(jié)果提交為鏡像,所以 Docker 的構(gòu)建鏡像過程就顯得非常聰明。它會(huì)將之前的鏡像層看作緩存。
比如我們把 EXPOSE 80 改為 EXPOSE 8080。
root@ubuntu:~/sample# docker build -t ubuntu_with_apache_8080 .
Sending build context to Docker daemon 6.144kB
Step 1/5 : FROM ubuntu
---> 20c44cd7596f
Step 2/5 : RUN sed -i 's/archive.ubuntu.com/cn.archive.ubuntu.com/g' /etc/apt/sources.list
---> Using cache ---> c66ad94ad8a4Step 3/5 : RUN sed -i 's/security.ubuntu/cn.archive.ubuntu/g' /etc/apt/sources.list
---> Using cache
---> 0a4c480147c5
Step 4/5 : RUN apt-get -y update && apt-get -y install apache2
---> Using cache
---> 118bde35120a
Step 5/5 : EXPOSE 8080
---> Running in c89f8210c56a
---> ac88967e578e
Removing intermediate container c89f8210c56a
Successfully built ac88967e578e
Successfully tagged ubuntu_with_apache_8080:latest
我們可以看到,之前的指令都是一樣的,所以 docker 直接利用之前的緩存,只構(gòu)建我們更改的指令,新的鏡像層如下。
![](/d/20211016/a5e4258c1bcb20730d9678c74f701474.gif)
如果我們希望在構(gòu)建鏡像時(shí)不使用緩存,可以在 docker build 命令中加上 --no-cache 參數(shù)。
Dockerfile 中每一個(gè)指令都會(huì)創(chuàng)建一個(gè)鏡像層,上層是依賴于下層的。無論什么時(shí)候,只要某一層發(fā)生變化,其上面所有層的緩存都會(huì)失效。也就是說,如果我們改變 Dockerfile 指令的執(zhí)行順序,或者修改或添加指令,都會(huì)使緩存失效。比如我們在前面添加指令 MAINTAINER wzlinux "admin@wzlinux.com"。如下:
# Version 0.0.1
FROM ubuntu
MAINTAINER wzlinux "admin@wzlinux.com"
RUN sed -i 's/archive.ubuntu.com/cn.archive.ubuntu.com/g' /etc/apt/sources.list
RUN sed -i 's/security.ubuntu/cn.archive.ubuntu/g' /etc/apt/sources.list
RUN apt-get -y update && apt-get -y install apache2
EXPOSE 80
然后使用docker進(jìn)行構(gòu)建,查看其過程。
root@ubuntu:~/sample# docker build -t ubuntu_with_apache_author .
Sending build context to Docker daemon 6.144kB
Step 1/6 : FROM ubuntu
---> 20c44cd7596f
Step 2/6 : MAINTAINER wzlinux "admin@wzlinux.com"
---> Running in 637bb3457407
---> 829b24531d69
Removing intermediate container 637bb3457407
Step 3/6 : RUN sed -i 's/archive.ubuntu.com/cn.archive.ubuntu.com/g' /etc/apt/sources.list
---> Running in 416ae8aefb61
---> 84643fe8447a
Removing intermediate container 416ae8aefb61
Step 4/6 : RUN sed -i 's/security.ubuntu/cn.archive.ubuntu/g' /etc/apt/sources.list
---> Running in 58d8375fd5c3
---> 1cb5776982d3
Removing intermediate container 58d8375fd5c3
Step 5/6 : RUN apt-get -y update && apt-get -y install apache2
---> Running in 0514a7d04814
……
……
Processing triggers for sgml-base (1.26+nmu4ubuntu1)
...
---> 30eb21527fee
Removing intermediate container 0514a7d04814
Step 6/6 : EXPOSE 80
---> Running in 476ca5f98886
---> 30672998f3d0
Removing intermediate container 476ca5f98886
Successfully built 30672998f3d0
Successfully tagged ubuntu_with_apache_author:latest
![](/d/20211016/c294a34ca31303c76db87b5347d625bc.gif)
從輸出的結(jié)果生成了很多新的鏡像層,緩存已經(jīng)失效。
2.4 調(diào)試 Dockerfile
包括 Dockerfile 在內(nèi)的任何腳本和程序都會(huì)出錯(cuò)。有錯(cuò)并不可怕,但必須有辦法排查,那我們測試一下在構(gòu)建的過程中指令出現(xiàn)錯(cuò)誤怎么辦,比如我們把第二個(gè)sed指令寫錯(cuò)了,寫錯(cuò)了sd。
# Version 0.0.1
FROM ubuntu
MAINTAINER wzlinux "admin@wzlinux.com"
RUN sed -i 's/archive.ubuntu.com/cn.archive.ubuntu.com/g' /etc/apt/sources.list
RUN sd -i 's/security.ubuntu/cn.archive.ubuntu/g' /etc/apt/sources.list
RUN apt-get -y update && apt-get -y install apache2
EXPOSE 80
執(zhí)行 docker build,如下。
![](/d/20211016/0593929cedf2160a78aea8dd122b17d0.gif)
Dockerfile 在執(zhí)行第四步 RUN 指令時(shí)失敗。我們可以利用第三步創(chuàng)建的鏡像 84643fe8447a 進(jìn)行調(diào)試,方式是通過 docker run -it 啟動(dòng)鏡像的一個(gè)容器。
root@ubuntu:~/sample# docker run -ti 84643fe8447a /bin/bash
root@422ecce78664:/# sd
bash: sd: command not found
其實(shí)我們肯定不會(huì)傻到連 sd 不存在也不知道,我這里只是作為一個(gè)例子,其他更難的排錯(cuò)方法我們就使用這種方式。
2.5 Dockerfile 指令
FROM
指定 base 鏡像。
MAINTAINER
設(shè)置鏡像的作者,可以是任意字符串。
COPY
將文件從 build context 復(fù)制到鏡像。
COPY 支持兩種形式:
COPY src destCOPY ["src", "dest"]
注意:src 只能指定 build context 中的文件或目錄。
ADD
與 COPY 類似,從 build context 復(fù)制文件到鏡像。不同的是,如果 src 是歸檔文件(tar, zip, tgz, xz 等),文件會(huì)被自動(dòng)解壓到 dest。
ENV
設(shè)置環(huán)境變量,環(huán)境變量可被后面的指令使用。例如:
ENV MY_VERSION 1.3RUN apt-get install -y mypackage=$MY_VERSION
EXPOSE
指定容器中的進(jìn)程會(huì)監(jiān)聽某個(gè)端口,Docker 可以將該端口暴露出來。
VOLUME
將文件或目錄聲明為 volume。
WORKDIR
為后面的 RUN, CMD, ENTRYPOINT, ADD 或 COPY 指令設(shè)置鏡像中的當(dāng)前工作目錄。
RUN
在容器中運(yùn)行指定的命令。
CMD
容器啟動(dòng)時(shí)運(yùn)行指定的命令。
Dockerfile 中可以有多個(gè) CMD 指令,但只有最后一個(gè)生效。CMD 可以被 docker run 之后的參數(shù)替換。
ENTRYPOINT
設(shè)置容器啟動(dòng)時(shí)運(yùn)行的命令。
Dockerfile 中可以有多個(gè) ENTRYPOINT 指令,但只有最后一個(gè)生效。CMD 或 docker run 之后的參數(shù)會(huì)被當(dāng)做參數(shù)傳遞給 ENTRYPOINT。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。