有三種創(chuàng)建自定義鏡像的方式
一、交互式創(chuàng)建
- 選取基礎(chǔ)鏡像瞳别,運(yùn)行容器征候,并進(jìn)入交互窗口
docker container run -it --name sample alpine /bin/sh
- 進(jìn)行你所需要的修改,變更祟敛。這里假設(shè)需要安裝ping命令所需的相關(guān)軟件疤坝。
/ # apk update && apk add iputils
- 核實(shí)ping是否可用了,退出容器馆铁,查看容器狀態(tài)
/ # ping 114.114.114.114
/ # exit
docker container ls -a | grep sample
- 查看我們的容器與基礎(chǔ)鏡像有什么不同
docker container diff sample
圖片中跑揉,首字母含義:
- A代表新增文件
- C代表修改過的文件
- D代表被刪除的文件
- 將修改過的容器導(dǎo)出為自己的鏡像
docker container commit sample my-alpine
- 查看鏡像
docker image ls
- 查看自定義的鏡像的創(chuàng)建過程
docker image history my-alpine
通過IMAGE的id可以看到最后的一層是我們添加的,并且是在alpine這個(gè)鏡像的基礎(chǔ)上埠巨。
這種方式創(chuàng)建鏡像的優(yōu)缺點(diǎn):
Manually creating custom images as shown in the previous section of this chapter is very helpful when doing exploration, creating prototypes, or making feasibility studies. But it has a serious drawback: it is a manual process and thus is not repeatable or scalable. It is also as error-prone as any task executed manually by humans.
二历谍、通過Dockerfile創(chuàng)建鏡像
一個(gè)簡(jiǎn)單的Dockerfile示例如下:
FROM python:2.7
RUN mkdir -p /app
WORKDIR /app
COPY ./requirements.txt /app/
RUN pip install -r requirements.txt
CMD ["python", "main.py"]
Dockerfile與鏡像層級(jí)的關(guān)系
關(guān)鍵字詳解:
- FROM
每一個(gè)Dockerfile文件都從這個(gè)關(guān)鍵字開始,它指明從哪個(gè)基礎(chǔ)鏡像開始進(jìn)行你的鏡像構(gòu)建辣垒。通常望侈,我們將docker hub里面提供的官方鏡像作為基礎(chǔ)鏡像,例如:FROM centos:7
或者FROM python:2.7
勋桶,如果你想從空白鏡像開始脱衙,可以使用FROM scratch
。 - RUN
RUN
后面可以跟隨任意有效的linux命令哥遮,一般用來進(jìn)行我們?cè)诨A(chǔ)鏡像上的變更操作岂丘,由于每個(gè)RUN命令都會(huì)在鏡像上增加一層,因此眠饮,建議將多個(gè)linux命令整合成一個(gè)命令奥帘。可使用類似如下方式:
- COPY 和 ADD
COPY
和ADD
都是用于將主機(jī)中的文件拷貝到鏡像中的仪召,不同之處在于寨蹋,ADD
可以拷貝和解壓tar文件松蒜,還可以通過URL來拷貝網(wǎng)絡(luò)上的文件到容器中。
一些示例如下:
COPY . /app #將上下文目錄中所有文件或目錄遞歸拷貝到容器的/app目錄下
COPY ./web /app/web #將上下文目錄中的web目錄下的內(nèi)容拷貝到容器的/app/web目錄下
COPY sample.txt /data/my-sample.txt #拷貝單個(gè)文件并重命名
ADD sample.tar /app/bin/ #解壓tar包內(nèi)的文件到指定目錄
ADD http://example.com/sample.txt /data/ #拷貝遠(yuǎn)程文件到指定目錄
COPY ./sample* /mydir/ #支持源路徑中使用通配符
默認(rèn)已旧,通過COPY
和ADD
拷貝到鏡像內(nèi)的文件的UID和GID都為0秸苗,如果你需要改變,可以使用如下命令:
ADD --chown=11:22 ./data/files* /app/data/
除了指定UID和GID运褪,你也可以指定用戶的用戶名稱 和組名稱惊楼,但是這些名稱必須存在與鏡像的/etc/passwd 和 /etc/group中,否則Dockerfile構(gòu)建過程會(huì)失敗秸讹。
- WORKDIR
WORKDIR
用來定義工作目錄或者上下文目錄檀咙。常見的問題:
RUN cd /app/bin
RUN touch sample.txt
由于每執(zhí)行一次RUN,都是在原有鏡像上添加一個(gè)新層璃诀,因此上面的命令只是在root目錄下新建了sample.txt文件弧可。
正確的切換至一個(gè)目錄下,并新建文件劣欢,命令如下:
WORKDIR /app/bin
RUN touch sample.txt
- CMD 和 ENTRYPOINT
CMD
和ENTRYPOINT
命令比較特殊棕诵,因?yàn)槌诉@兩個(gè)命令以外的其他命令,都是在構(gòu)建鏡像時(shí)就會(huì)運(yùn)行并生效凿将,而這兩個(gè)命令是在使用鏡像啟動(dòng)容器時(shí)校套,才會(huì)執(zhí)行的命令。這兩個(gè)命令用于告訴Docker在啟動(dòng)容器時(shí)丸相,應(yīng)該執(zhí)行什么命令搔确,并以何種方式去執(zhí)行(指定一些參數(shù))彼棍。
兩者的區(qū)別:
先來看一個(gè)典型的Dockerfile示例
FROM alpine:latest
ENTRYPOINT ["ping"]
CMD ["8.8.8.8", "-c", "3"]
用ENTRYPOINT
來指定需要執(zhí)行的命令灭忠,用CMD
來指定該命令所需的參數(shù)。
這是首選的推薦用法,也被稱為exec格式。我們使用這個(gè)Dockerfile來構(gòu)建一個(gè)鏡像川蒙,命名為pinger洽故,并使用該鏡像運(yùn)行一個(gè)容器,然后刪除牙咏。
通過這種方式創(chuàng)建的鏡像,我們還可以按照如下方式運(yùn)行,可以實(shí)現(xiàn)修改Dockerfile中
ENTRYPOINT
的命令萨西,也可以修改Dockerfile中CMD
的參數(shù),非常靈活旭旭。如下:
再來看一個(gè)使用CMD
谎脯,并且沒有ENTRYPOINT
的Dockerfile示例:
FROM alpine:latest
CMD wget -O - http://www.baidu.com
當(dāng)沒有明確定義ENTRYPOINT
參數(shù)時(shí),其默認(rèn)參數(shù)為/bin/sh -c
持寄,而CMD
內(nèi)的參數(shù)將以字符串形式源梭,傳遞給該命令娱俺,因此以上Dockerfile最終執(zhí)行的就是:
/bin/sh -c "wget -O - http://www.baidu.com"
一個(gè)Dockerfile文檔示例
FROM node:9.4 #指定基礎(chǔ)鏡像
RUN mkdir -p /app #在鏡像的文件系統(tǒng)內(nèi),創(chuàng)建一個(gè)/app目錄
WORKDIR /app #切換上下文(工作目錄)至/app目錄下
COPY package.json /app/ #從主機(jī)上Dockerfile文件所在目錄下废麻,將package.json文件拷貝到鏡像中的/app目錄下
RUN npm install #npm會(huì)安裝package.json內(nèi)Node.js所需的依賴包
COPY . /app #與第四步一樣荠卷,拷貝主機(jī)內(nèi)文件(這里是應(yīng)用程序文件)到鏡像的/app目錄下
ENTRYPOINT ["npm"] #與最后一行的命令結(jié)合,啟動(dòng)Node.js服務(wù)
CMD ["start"]
通過Dockerfile構(gòu)造鏡像的過程及相關(guān)解釋
常用的通過Dockerfile構(gòu)造鏡像的過程:
- 創(chuàng)建一個(gè)空目錄cmd_entrypoint烛愧。
- 進(jìn)入目錄中油宜,創(chuàng)建一個(gè)Dockerfile文件,寫入如下內(nèi)容:
FROM alpine:latest
ENTRYPOINT ["ping"]
CMD ["8.8.8.8", "-c", "3"]
- 在目錄下執(zhí)行命令:
docker image build -t pinger .
當(dāng)你的配置文件不是默認(rèn)的名字時(shí)怜姿,可以使用-f參數(shù)指定:
docker image build -t pinger -f my-Dockerfile .
構(gòu)造過程如下:
以上過程解釋如下:
- 拉取基礎(chǔ)鏡像
alpine
验庙,它的id為196d - 執(zhí)行
ENTRYPOINT
,會(huì)通過第一步的基礎(chǔ)鏡像生成一個(gè)容器9ad9社牲,在容器內(nèi)執(zhí)行ENTRYPOINT
所需的修改或變更操作等粪薛,操作完成后刪除容器9ad9,然后新加一層成為新的鏡像0669搏恤。 -
這個(gè)鏡像將作為下一個(gè)操作的基礎(chǔ)鏡像违寿,然后重復(fù)上面的過程,直至最后成為一個(gè)你所需的鏡像熟空。
流程也可以參考:
多階段構(gòu)建
示例如下:
C程序源文件hello.c:
#include <stdio.h>
int main (void)
{
printf ("Hello, world!\n");
return 0;
}
常用的Dockerfile編寫方式如下:
FROM alpine:3.7
RUN apk update &&
apk add --update alpine-sdk
RUN mkdir /app
WORKDIR /app
COPY . /app
RUN mkdir bin
RUN gcc -Wall hello.c -o bin/hello
CMD /app/bin/hello
這種方式構(gòu)造的鏡像文件如下:
$ docker image ls | grep hello-world
hello-world latest e9b... 2 minutes ago 176MB
多階段構(gòu)建Dockerfile如下:
FROM alpine:3.7 AS build
RUN apk update && \
apk add --update alpine-sdk
RUN mkdir /app
WORKDIR /app
COPY . /app
RUN mkdir bin
RUN gcc hello.c -o bin/hello
FROM alpine:3.7
COPY --from=build /app/bin/hello /app/hello
CMD /app/hello
得到的鏡像文件如下:
$ docker image ls | grep hello-world
hello-world-small latest f98... 20 seconds ago 4.16MB
hello-world latest 469... 10 minutes ago 176MB
多階段構(gòu)建優(yōu)點(diǎn):減少黑客可攻擊的范圍藤巢、節(jié)省內(nèi)存和磁盤空間、更少的啟動(dòng)容器時(shí)間息罗、減少下載鏡像所需的帶寬掂咒。
Dockerfile編寫最佳實(shí)踐
一些建議:
- 首先,我們要意識(shí)到容器的生命周期是短暫的迈喉。短暫的绍刮,意味著一個(gè)運(yùn)行著的容器可能很快就會(huì)被停止以及銷毀,而一個(gè)增加了新功能的容器挨摸,需要我們能夠快速的構(gòu)建和啟用孩革。我們需要盡可能的保證能夠快速的啟動(dòng)、停止容器內(nèi)的應(yīng)用得运。
- 我們可能會(huì)針對(duì)同一個(gè)Dockerfile不停的構(gòu)建自己的鏡像膝蜈。我們應(yīng)該合理的組織Dockerfile內(nèi)的每個(gè)命令,以盡可能的利用容器構(gòu)建過程中的緩存功能熔掺。例如:
FROM node:9.4 RUN mkdir -p /app WORKIR /app COPY . /app RUN npm install CMD ["npm", "start"]
修改為:
FROM node:9.4 RUN mkdir -p /app WORKIR /app COPY package.json /app/ RUN npm install COPY . /app CMD ["npm", "start"]
以上兩個(gè)Dockerfile一般來說饱搏,都會(huì)在通過npm install安裝依賴包的時(shí)候花費(fèi)大量時(shí)間,而針對(duì)一個(gè)開發(fā)項(xiàng)目來說置逻,依賴包的變動(dòng)的情況是比較少的推沸,大部分時(shí)間都是應(yīng)用程序的變動(dòng)而導(dǎo)致重新構(gòu)建鏡像,因此將安裝依賴包的過程獨(dú)立出來诽偷,利用緩存的功能坤学,可以加速下一次的構(gòu)建過程疯坤。
- 另一個(gè)最佳實(shí)踐就是盡可能減少你鏡像的layer數(shù)。通常來說深浮,layer越少压怠,鏡像越小,啟動(dòng)速度越快飞苇。而最好的減少layer數(shù)量的辦法就是整合你的命令菌瘫,因?yàn)椋赿okcerfile中布卡,每一個(gè)以FROM雨让、COPY、RUN這樣的關(guān)鍵字開頭的行忿等,就意味著一個(gè)新的layer栖忠。最常見的辦法就是整合多個(gè)RUN行為一行。例如:
RUN apt-get update RUN apt-get install -y ca-certificates RUN rm -rf /var/lib/apt/lists/*
整合為:
RUN apt-get update \ && apt-get install -y ca-certificates \ && rm -rf /var/lib/apt/lists/*
- 通過.dockerignore文件贸街,除去那些不需要拷貝到鏡像內(nèi)的文件庵寞,達(dá)到減小鏡像體積的作用。用法與git中.gitignore一樣薛匪。
- 在鏡像的文件系統(tǒng)內(nèi)捐川,減少安裝不必要的軟件。
- 合理利用多階段構(gòu)建逸尖。
三古沥、保存和加載鏡像
第三種制作一個(gè)容器鏡像的方法是,通過一個(gè)文件進(jìn)行導(dǎo)入或加載娇跟。因?yàn)檠页荩粋€(gè)容器鏡像本質(zhì)上就是一個(gè)tar包的文件。請(qǐng)看以下示例:
docker image save -o ./backup/my-alpine.tar my-alpine
上面的命令逞频,將一個(gè)鏡像保存為指定的tar包纯衍。
docker image load -i ./backup/my-alpine.tar
上面的命令,將一個(gè)tar包加載為可用的鏡像苗胀。
管理鏡像(分享和運(yùn)送鏡像)
為了能夠把我們自定義的鏡像運(yùn)送到我們的生產(chǎn)環(huán)境中,我們需要給鏡像一個(gè)全球唯一的名稱瓦堵,通常這個(gè)動(dòng)作稱為:tagging基协。通常,我們把鏡像存放在一個(gè)集中管理的地方菇用,以方便他人獲取澜驮,這個(gè)地方稱為:倉庫(image registries)。
鏡像的標(biāo)簽
每個(gè)鏡像都有一個(gè)標(biāo)簽惋鸥,通常用來指定鏡像的版本號(hào)杂穷。
docker image pull alpine #沒有指定tag時(shí)悍缠,默認(rèn)為lates
docker image pull alpine:3.5 #指定tag
鏡像的命名空間
一個(gè)完整的鏡像命名如下:
<registry URL>/<User or Org>/<name>:<tag>
- <registry URL>
鏡像存放的倉庫的名字,通常是一些公用的鏡像存儲(chǔ)及管理服務(wù)器耐量,例如docker hub飞蚓。 - <User or Org>
在前面的倉庫中定義的一個(gè)用戶或者組織。 - <name>:<tag>
上面已經(jīng)介紹過廊蜒,就是你的鏡像名以及標(biāo)簽趴拧。
一些特殊的約定:
- 如果忽略<registry URL>,那么默認(rèn)是使用docker hub倉庫山叮。
- 如果忽略<tag>名稱著榴,那么默認(rèn)是latest。
- 如果是docker hub上的官方鏡像屁倔,那么鏡像名不需要<User or Org>脑又。
常見鏡像名稱解釋:
Image | Description |
---|---|
alpine | Official alpine image on Docker Hub with the latest tag. |
ubuntu:16.04 | Official ubuntu image on Docker Hub with the 16.04 tag or version. |
microsoft/nanoserver | nanoserver image of Microsoft on Docker Hub with the latest tag. |
acme/web-api:12.0 | web-api image version 12.0 associated with the acme org.The image is on Docker Hub. |
gcr.io/gnschenker/sampleapp:1.1 | sample-app image with the 1.1 tag belonging to an individual with the gnschenker ID on Google's container registry. |
推送鏡像到一個(gè)倉庫
- 將需要推送的鏡像取一個(gè)自定義的tag
docker image tag alpine:latest gnschenker/alpine:1.0
- 登錄到你的倉庫
docker login -u gnschenker -p <my secret password>
- 推送鏡像
docker image push gnschenker/alpine:1.0
以上是默認(rèn)使用docker hub倉庫,你需要有相關(guān)的賬號(hào)密碼锐借。
相關(guān)參考:Docker鏡像上傳到阿里云