Docker技术入门与实战 第二版-学习笔记-2-镜像构建
3.利用 commit 理解鏡像構(gòu)成
在之前的例子中,我們所使用的都是來自于 Docker Hub 的鏡像。
直接使用這些鏡像是可以滿足一定的需求,而當(dāng)這些鏡像無法直接滿足需求時,我們就需要定制這些鏡像。
接下來的幾節(jié)就將講解如何定制鏡像
回顧一下之前我們學(xué)到的知識:
- 鏡像是多層存儲,每一層是在前一層的基礎(chǔ)上進行的修改;
- 容器同樣也是多層存儲,是在以鏡像為基礎(chǔ)層,在其基礎(chǔ)上加一層作為容器運行時的存儲層。
?
1)現(xiàn)在讓我們以定制一個 Web 服務(wù)器為例子,來講解鏡像是如何構(gòu)建的:
userdeMBP:~ user$ docker run --name webserver -d -p 80:80 nginx Unable to find image 'nginx:latest' locally latest: Pulling from library/nginx a5a6f2f73cd8: Pull complete 1ba02017c4b2: Pull complete 33b176c904de: Pull complete Digest: sha256:5d32f60db294b5deb55d078cd4feb410ad88e6fe77500c87d3970eca97f54dba Status: Downloaded newer image for nginx:latest f4936eb44ef38a8998ec78ab48a967a92f009d6c04dfd5cf2065ec8db0b1fbd7userdeMBP:~ user$ docker container ls
CONTAINER ID??????? IMAGE??????????????? COMMAND????????????????? CREATED???????????? STATUS????????????? PORTS???????????????????? NAMES
f4936eb44ef3??????? nginx??????????????? "nginx -g 'daemon of…"?? 2 minutes ago?????? Up 2 minutes??????? 0.0.0.0:80->80/tcp??????? webserver
run = pull鏡像(如果本地沒有) + start容器
這條命令會用 鏡像啟動一個容器,命名為 webserver,并且映射了 80 端口,這樣我們可以用瀏覽器去訪問這個服務(wù)器,調(diào)用:http://localhost/:
現(xiàn)在,假設(shè)我們非常不喜歡這個歡迎頁面,我們希望改成歡迎 Docker 的文字,我 們可以使用 docker exec 命令進入容器,修改其內(nèi)容:
?
userdeMBP:~ user$ docker exec -it webserver bash root@f4936eb44ef3:/# echo '<h1> hello docker!<h1>' > /usr/share/nginx/html/index.html root@f4936eb44ef3:/# exit exit我們以交互式終端方式(-it)進入 容器,并執(zhí)行了bash命令,也就是獲得一個可操作的 Shell。
然后,我們用<h1> hello docker!<h1> 覆蓋了 /usr/share/nginx/html/index.html的內(nèi)容。
現(xiàn)在我們再刷新瀏覽器的話,會發(fā)現(xiàn)內(nèi)容被改變了:
在上面,我們修改了容器的文件,也就是改動了容器的存儲層。我們可以通過 docker diff命令看到具體的改動:
userdeMBP:~ user$ docker diff webserver C /usr C /usr/share C /usr/share/nginx C /usr/share/nginx/html C /usr/share/nginx/html/index.html C /root A /root/.bash_history C /run A /run/nginx.pid C /var C /var/cache C /var/cache/nginx A /var/cache/nginx/client_temp A /var/cache/nginx/fastcgi_temp A /var/cache/nginx/proxy_temp A /var/cache/nginx/scgi_temp A /var/cache/nginx/uwsgi_temp
2)現(xiàn)在我們定制好了變化,我們希望能將其保存下來形成鏡像
?
當(dāng)我們運行一個容器的時候(如果不使用卷volume的話),我們做的任何文件修 改都會被記錄于容器存儲層里。
當(dāng)Docker提供一個docker commit 的命令時,可以將容器的存儲層保存下來成為鏡像。
換句話說,其實就是在原有鏡像的基礎(chǔ)上,在疊加上新的容器的存儲層,來構(gòu)成新的鏡像。
docker commit 的語法格式為:
我們可以用下面的命令將容器保存為鏡像:
userdeMBP:~ user$ docker commit --author "manXingHouJi" --message "修改了默認網(wǎng)頁" webserver nginx:v2 sha256:5dda481d7ed18aed71cfd5052ffe64ebac5dde02e8a7c743f7286413f7082610其中 --author是指定修改的作者,而 --message則是記錄本次修改的內(nèi)容。 這點和 git版本控制相似,不過這里這些信息可以省略留空。
然后我們可以在 docker images中看到這個新定制的鏡像:
userdeMBP:~ user$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE nginx v2 5dda481d7ed1 58 seconds ago 109MB nginx latest 568c4670fa80 2 weeks ago 109MB?我們還可以用 docker history具體查看鏡像內(nèi)的歷史記錄,如果比較nginx : latest的歷史記錄,我們會發(fā)現(xiàn)新增了我們剛剛提交的這一層
新的鏡像定制好后,我們可以來運行這個鏡像:userdeMBP:~ user$ docker run --name web2 -d -p 81:80 nginx:v2 889e5311532c39241510342cd5a15194071b3629c0d9a33dfcc60d10b792a785
這里我們命名為新的服務(wù)為web2 ,并且映射到 81端口,可以直接訪問 http://localhost:81:
?
??慎用 docker commit
使用 docker commit命令雖然可以比較直觀的幫助理解鏡像分層存儲的概念, 但是實際環(huán)境中并不會這樣使用。
?
1)首先,如果仔細觀察之前的 docker diff webserver的結(jié)果,你會發(fā)現(xiàn)除了真 正想要修改的 /usr/share/nginx/html/index.html文件外,由于命令的執(zhí)行,還有很多文件被改動或添加了。這還僅僅是最簡單的操作,如果是安裝軟件包、編譯構(gòu)建,那會有大量的無關(guān)內(nèi)容被添加進來,如果不小心清理,將會導(dǎo)致鏡像極為臃腫。
2)此外,使用 意味著所有對鏡像的操作都是黑箱操作,生成的鏡像也被稱為黑箱鏡像.換句話說,就是除了制作鏡像的人知道執(zhí)行過什么命令、怎 么生成的鏡像,別人根本無從得知。
雖然 docker diff或許可以告訴得到一些線索, 但是遠遠不到可以確保生成一致鏡像的地步。這種黑箱鏡像的維護工作是非常痛苦的。
3)而且,回顧之前提及的鏡像所使用的分層存儲的概念,除當(dāng)前層外,之前的每一層 都是不會發(fā)生改變的.
換句話說,任何修改的結(jié)果僅僅是在當(dāng)前層進行標(biāo)記、添加、修改(如標(biāo)記某一內(nèi)容不可見來代表刪除操作),而不會改動上一層。
如果使用 docker commit制作鏡像,以及后期修改的話,每一次修改都會讓鏡像更加臃腫一次,所刪除的上一層的東西并不會丟失,會一直如影隨形的跟著這個鏡像,這會讓鏡像更加臃 腫。
4)因此docker commit命令除了學(xué)習(xí)之外,還有一些特殊的應(yīng)用場合,比如被入侵后保存現(xiàn)場等。
但是,不要使用docker commit定制鏡像,定制行為應(yīng)該使用Dockerfile來完成。
下面的章節(jié)我們就來講述一下如何使用 Dockerfile定制鏡像。
?
?
?
4.使用 Dockerfile 定制鏡像
從上面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)建。
還以之前定制 鏡像為例,這次我們使用 Dockerfile 來定制,內(nèi)容為:
?
1)FROM 指定基礎(chǔ)鏡像
上面就指定了該Dockerfile的修改就是在nginx基礎(chǔ)鏡像上進行的,FROM是必備指令,并且必須是第一條指令
所以以后如果需要自己定制某個鏡像時,可以在Docker Hub中找到與自己目標(biāo)鏡像最符合的鏡像作為基礎(chǔ)鏡像來進行定制
?
除了選擇現(xiàn)有鏡像為基礎(chǔ)鏡像外,Docker 還存在一個特殊的鏡像,名為scratch 。
這個鏡像是虛擬的概念,并不實際存在,它表示一個空白的鏡像,使用即 FROM scratch。
?
如果你以 scratch為基礎(chǔ)鏡像的話,意味著你不以任何鏡像為基礎(chǔ),接下來所寫的指令將作為鏡像第一層開始存在。
?
2)RUN 執(zhí)行命令
?
其格式有兩種:
- shell 格式: RUN<命令>,就像直接在命令行中輸入的命令一樣。剛才寫的 Dockrfile 中的 指令就是這種格式。
- 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/redi s-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 中每一個指令都會建立一層, 也不例外。每一個的行為,就和剛才我們手工建立鏡像的過程一樣:新建立一層,在其上執(zhí)行這些命令,執(zhí)行結(jié)束后, 這一層的修改,構(gòu)成新的鏡像。
??而上面的這種寫法,創(chuàng)建了 7 層鏡像。這是完全沒有意義的,而且很多運行時不需要的東西,都被裝進了鏡像里,比如編譯環(huán)境、更新的軟件包等等。結(jié)果就是產(chǎn)生 非常臃腫、非常多層的鏡像,不僅僅增加了構(gòu)建部署的時間,也很容易出錯。 這是很多初學(xué) Docker 的人常犯的一個錯誤。
?
正確的寫法應(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/r edis-3.2.5.tar.gz" \ && mkdir -p /usr/src/redis \ && tar -xzf redis.tar.gz -C /usr/src/redis --strip-component s=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,并使用&&將各個命令串聯(lián)起來
?
??在撰寫 Dockerfile 的時候,要經(jīng)常提醒自己,這并不是在寫 Shell 腳本,而是在定義每一層該如何構(gòu)建。
?
此外,還可以看到這一組命令的最后添加了清理工作的命令,刪除了為了編譯構(gòu)建 所需要的軟件,清理了所有下載、展開的文件,并且還清理了 緩存文件。這 是很重要的一步,我們之前說過,鏡像是多層存儲,每一層的東西并不會在下一層 被刪除,會一直跟隨著鏡像。因此鏡像構(gòu)建時,一定要確保每一層只添加真正需要 添加的東西,任何無關(guān)的東西都應(yīng)該清理掉。
很多人初學(xué) Docker 制作出了很臃腫的鏡像的原因之一,就是忘記了每一層構(gòu)建的最后一定要清理掉無關(guān)文件。
?
?
3)構(gòu)建鏡像
接下來就使用上面生成Dockerfile文件去構(gòu)建nginx鏡像
在生成Dockerfile文件的目錄下執(zhí)行:
userdeMBP:mynginx user$ docker build -t nginx:v3 . Sending build context to Docker daemon 2.048kB Step 1/2 : FROM nginx ---> 568c4670fa80 Step 2/2 : RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html ---> Running in 0e5088418e3b Removing intermediate container 0e5088418e3b ---> beda48ecf85b Successfully built beda48ecf85b Successfully tagged nginx:v3從命令的輸出結(jié)果中,我們可以清晰的看到鏡像的構(gòu)建過程。
在 step 2中,如同我們之前所說的那樣, RUN指令啟動了一個容器 0e5088418e3b,執(zhí)行了所要求的命令,并最后提交了這一層 beda48ecf85b,隨后刪除了所用到的這個容器 0e5088418e3b。
docker build 命令格式:
docker build [選項] <上下文路徑/URL/->-t, --tag list????? Name and optionally a tag in the 'name:tag' format 指定生成的鏡像名字,標(biāo)簽是可選項,如上面-t nginx:v3
上面例子中的<上下文路徑/URL/->使用的是 . 來表示,表示當(dāng)前目錄
?
4)鏡像構(gòu)建上下文(Context)——注意看看
如果簡單理解上面所說的 . 是表示Dockerfile文件當(dāng)前所在目錄的話,是不準(zhǔn)確的,其指定的是上下文路徑。
那么為什么會有人誤以為 是指定 所在目錄呢?這是因為在默認情況下,如果不額外指定Dockerfile的話,會將上下文目錄下的名為Dockerfile的文件作為 Dockerfile。
這只是默認行為,實際上Dockerfile的文件名并不要求必須為Dockerfile ,而且并不要求必須位于上下文目錄中,比如可以用 -f ../Dockerfile.php 參數(shù)指定某個文件作為Dockerfile 。
?
?
首先我們要理解 docker build的工作原理。Docker 在運行時分為 Docker 引擎 (也就是服務(wù)端守護進程daemon)和客戶端工具。
Docker 的引擎提供了一組 REST API, 被稱為 Docker Remote API,而如 docker命令這樣的客戶端工具,則是通過這組 API 與 Docker 引擎交互,從而完成各種功能。
因此,雖然表面上我們好像是在 本機執(zhí)行各種 docker功能,但實際上,一切都是使用的遠程調(diào)用形式在服務(wù)端 (Docker 引擎)完成。也因為這種 C/S 設(shè)計,讓我們操作遠程服務(wù)器的 Docker 引 擎變得輕而易舉。
?
當(dāng)我們進行鏡像構(gòu)建的時候,并非所有定制都會通過 RUN指令完成,經(jīng)常會需要將一些本地文件復(fù)制進鏡像,比如通過 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)建鏡 像所需的一切文件。
從剛剛運行的docker build的結(jié)果中我們可以看見:
?
userdeMBP:mynginx user$ docker build -t nginx:v3 . Sending build context to Docker daemon 2.048kB //這就是發(fā)送給Docker引擎的上下文包?
?
如果在 Dockerfile中這么寫:
COPY ./package.json /app/這并不是要復(fù)制執(zhí)行docker build命令所在的目錄下的package.json,也不是要復(fù)制Dockerfile所在目錄下的package.json,而是復(fù)制傳到Docker引擎的上下文包的上下文下的package.json
這也就是為什么有時執(zhí)行COPY ../package.json /app 或者 COPY /opt/XXXX /app 會無法工作的原因,因為這些路徑已經(jīng)超出了上下文的范圍,Docker引擎是無法獲得這些文件的。
如果真的需要這些文件,你需要將其復(fù)制到Docker引擎的上下文中。
?
如果指定本地作為上下文的目錄下有些東西確實不希望構(gòu)建時傳給 Docker 引擎,那么可以用 .gitignore 一樣的語法寫一 個 .dockerignore,該文件是用于剔除不需要作為上下文傳遞給 Docker 引擎 的。
?
?
5)其它 docker build的用法
1>直接從 Git repo 進行構(gòu)建
docker build [選項] <上下文路徑/URL/->可見,docker build還支持URL構(gòu)建,所以可以直接從Git repo中構(gòu)建
userdeMBP:~ user$ docker build https://github.com/twang2218/gitlab-ce-zh.git#:8.14 unable to prepare context: unable to 'git clone' to temporary context directory: stat /var/folders/2_/g5wrlg3x75zbzyqvsd5f093r0000gn/T/docker-build-git776693632/8.14: no such file or directory userdeMBP:~ user$ docker build https://github.com/twang2218/gitlab-ce-zh.git#:11.0 Sending build context to Docker daemon 5.632kB Step 1/23 : FROM gitlab/gitlab-ce:11.0.5-ce.0 as builder 11.0.5-ce.0: Pulling from gitlab/gitlab-ce 3620e2d282dc: Downloading [=====> ] 4.758MB/43.19MB ef22f5e4b3b2: Download complete書中給的例子的8.14版本太舊了,所以找不到相應(yīng)的文件了,換成了新版本即可
?
2>用給定的 tar 壓縮包構(gòu)建
docker build http://server/context.tar.gzDocker 引擎會下 載這個包,并自動解壓縮,以其作為上下文,開始構(gòu)建
?
3>從標(biāo)準(zhǔn)輸入中讀取 Dockerfile 進行構(gòu)建
docker build - < Dockerfile或:
cat Dockerfile | docker build -上面的這兩個語句都會自動在終端運行該命令的當(dāng)前目錄下去尋找是否有名叫Dockerfile的文件,然后輸入命令
如果標(biāo)準(zhǔn)輸入傳入的是文本文件,則將其視為 Dockerfile,并開始構(gòu)建。
這種 形式由于直接從標(biāo)準(zhǔn)輸入中讀取 Dockerfile 的內(nèi)容,它沒有上下文,因此不可以像其他方法那樣可以將本地文件 COPY進鏡像之類的事情。
?
4>從標(biāo)準(zhǔn)輸入中讀取上下文壓縮包進行構(gòu)建
docker build - < context.tar.gz上面的這個語句都會自動在終端運行該命令的當(dāng)前目錄下去尋找是否有名叫context.tar.gz的壓縮包,然后輸入命令
如果發(fā)現(xiàn)標(biāo)準(zhǔn)輸入的文件格式是gzip 、bzip2 以及 xz的話,將會使其為上下文壓縮包,直接將其展開,將里面視為上下文,并開始構(gòu)建。
?
?
?
?
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/wanghui-garcia/p/10119559.html
新人創(chuàng)作打卡挑戰(zhàn)賽發(fā)博客就能抽獎!定制產(chǎn)品紅包拿不停!總結(jié)
以上是生活随笔為你收集整理的Docker技术入门与实战 第二版-学习笔记-2-镜像构建的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android中wifi输入的密码保存的
- 下一篇: 第三周 day14:内置函数