第四章 使用docker鏡像和倉庫
docker鏡像是由文件系統(tǒng)疊加而成。最底端是一個引導(dǎo)文件系統(tǒng),即bootfs,第二層是root文件系統(tǒng)rootfs。
一次同時加載多個文件系統(tǒng),但從外面看起來,只能看到一個文件系統(tǒng),聯(lián)合加載會把各層文件系統(tǒng)疊加起來,這樣最終的文件系統(tǒng)會包含所有底層的文件和目錄
docker將這樣的文件系統(tǒng)稱之為鏡像。

在同個鏡像中,docker用寫時復(fù)制來保證鏡像都是只讀的,并且以后永遠(yuǎn)不會變化。
docker images利用這個命令可以看到我們的鏡像。

鏡像保存在/var/lib/docker目錄下。其中containers目錄是所有的容器。
這個鏡像是我們在公有registry中拉取下來的,地址是這個。dockerhub
docker pull可以拉取鏡像倉庫中的鏡像。
通常會下載最新的鏡像,可以在鏡像名后面增加標(biāo)簽來指定要下載的鏡像內(nèi)容。

在拉取鏡像的時候指定標(biāo)簽是個好習(xí)慣,從而能讓你知道你要拉取的鏡像的內(nèi)容。
區(qū)分倉庫名,docker中的倉庫有兩種,用戶倉庫和頂層倉庫。
用戶倉庫是docker用戶自己創(chuàng)建的,可能存在風(fēng)險,命名由兩部分組成,用戶名/倉庫名。
頂層倉庫是有docker管理人管理的,是安全的,命名只有倉庫名部分,如Ubuntu。
查找鏡像
用docker search可以在docker倉庫中查找鏡像,查找列表可以看到倉庫名,鏡像描述,用戶評價,是否官方,自動構(gòu)建

構(gòu)建鏡像
最好使用docker build命令和Dockerfile文件。雖然也可以使用docker commit,但是不推薦。
docker commit
docker可以將當(dāng)前狀態(tài)的容器,提交成為一個鏡像,這樣就不用了每次都需要重新執(zhí)行相關(guān)的命令成為一個你想要的鏡像了。而且他是基于鏡像差異去提交的,所以該次commit會很小。
Dockerfile
利用Dockerfile可以構(gòu)建一個屬于你自己的鏡像,而且鏡像構(gòu)建過程是透明的。
比如新建一個目錄,在該目錄下創(chuàng)建Dockerfile文件。內(nèi)容是:
FROM ubuntu
MAINTAINER huangzelin "838120089@qq.com" #標(biāo)識該鏡像的所有者和聯(lián)系方式
RUN apt-get update
RUN apt-get install -y nginx
RUN echo 'this is huangzelin' > /usr/share/nginx/html/index.html
EXPOSE 80 #暴露該鏡像的80端口
在當(dāng)前目錄下執(zhí)行
docker build -t="static_web"
等構(gòu)建完畢就能看到剛剛構(gòu)建的鏡像了。

除了指定目錄以外,還可以通過指定git地址來獲取Dockerfile,這樣在多服務(wù)器的情況下,也能統(tǒng)一一份Dockerfile了。
運行該鏡像。
root@DESKTOP-3JK8RKR:/data/docker_test/static_web# docker run -t -i static_web /bin/bash
root@038c56a4542d:/# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
可以看到該鏡像已經(jīng)安裝了nginx。
docker在構(gòu)建的過程中會將每一步的構(gòu)建結(jié)果都提交為鏡像,所以每一步都會有緩存,如果你在第四步發(fā)生了變動,那么第一到三步是不用重新構(gòu)建的。所以最基礎(chǔ)的構(gòu)建應(yīng)該放在前面 如果不想要使用緩存,可以在build的時候指定
docker build --no-cache -t="static_web" .
利用docker history可以查看鏡像的構(gòu)建過程。
root@DESKTOP-3JK8RKR:/data/docker_test/static_web# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
static_web latest 6c9a53fca598 19 minutes ago 158MB
<none> <none> 9eed8ce3f990 5 days ago 158MB
ubuntu latest d70eaf7277ea 4 weeks ago 72.9MB
hello-world latest bf756fb1ae65 10 months ago 13.3kB
root@DESKTOP-3JK8RKR:/data/docker_test/static_web# docker history 6c9a53fca598
IMAGE CREATED CREATED BY SIZE COMMENT
6c9a53fca598 19 minutes ago /bin/sh -c #(nop) EXPOSE 80 0B
a2283161422c 19 minutes ago /bin/sh -c echo 'this is huangzelin' > /usr/… 19B
631da22a6ab8 19 minutes ago /bin/sh -c apt-get install -y nginx 59.2MB
136d23864f2b 19 minutes ago /bin/sh -c apt-get update 26.1MB
78e58f68d48e 20 minutes ago /bin/sh -c #(nop) MAINTAINER huangzelin "83… 0B
d70eaf7277ea 4 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 4 weeks ago /bin/sh -c mkdir -p /run/systemd && echo 'do… 7B
<missing> 4 weeks ago /bin/sh -c [ -z "$(apt-get indextargets)" ] 0B
<missing> 4 weeks ago /bin/sh -c set -xe && echo '#!/bin/sh' > /… 811B
<missing> 4 weeks ago /bin/sh -c #(nop) ADD file:435d9776fdd3a1834… 72.9MB
讓nginx鏡像能真正跑起來。
docker run -d -p 80 --name test_nginx static_web nginx -g "daemon off;"
ec4077f4ae9259d63b2b6fbfb541a70129d4b0c0deaeae26d6f6f60c2463623b
-d:是讓nginx以分離的方式跑起來。
nginx -g "daemon off;":這是在指定容器中要運行的命令。
-p:是暴露容器的端口給宿主機,這里沒有指定綁定到宿主機的那些端口,就會隨機選擇一個4915365535一個比較大的端口號來映射到容器的80端口中。
可以通過docker ps來查看端口分配情況。
root@DESKTOP-3JK8RKR:/data/docker_test/static_web# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ec4077f4ae92 static_web "nginx -g 'daemon of…" 11 minutes ago Up 11 minutes 0.0.0.0:32768->80/tcp test_nginx
可以看到32768指向了容器的80端口。
打開本地的32768頁面。

還可以通過docker port查看端口映射情況。
root@DESKTOP-3JK8RKR:/data/docker_test/static_web# docker port test_nginx 80
0.0.0.0:32768
但是這種隨機的端口實用性不大,我們更多的是指定特定的端口去映射。
docker run -d -p 80:80 --name test_nginx static_web nginx -g "daemon off;"
ec4077f4ae9259d63b2b6fbfb541a70129d4b0c0deaeae26d6f6f60c2463623b
這樣我們就指定了本地的80端口映射到容器的80端口了,但是需要注意,一個端口只能被映射一次。
還可以
docker run -d -p 127.0.0.1:80:80 --name test_nginx static_web nginx -g "daemon off;"
ec4077f4ae9259d63b2b6fbfb541a70129d4b0c0deaeae26d6f6f60c2463623b
來指定綁定的ip。
docker還有個-P選項,這個選項會暴露容器中EXPOSE指定的端口,不過不建議,因為多個端口提供服務(wù)違反了一個docker一個容器只運行一個服務(wù)的建議。
Dockerfile指令
CMD
cmd用于指定一個容器啟動時要運行的命令,這有點兒類似RUN指令,只是RUN指令是指定鏡像被構(gòu)建時要運行的命令,而CMD是指定容器被啟動時要運行的命令。這和docker run在后面指定容器要運行的命令非常相似。
當(dāng)然也可以為要運行的命令指定參數(shù),如
CMD ["/bin/bash", "-l"]
需要注意的是,我們在docker run的時候,顯式給他啟動命令,會覆蓋Dockerfile里面的CMD指令。
如果在Dockerfile中指定了多條CMD命令,也只有最后一條會生效。
ENTRYPOINT
ENTRYPOINT和CMD又有點類似,剛剛提到,docker run會覆蓋掉CMD指令,那我們有時候并不想覆蓋指令,而只是想指定啟動參數(shù)呢。
那么ENTRYPOINT就能做到。
ENTRYPOINT ["/usr/sbin/nginx"]
docker run -d -p 127.0.0.1:80:80 --name test_nginx static_web -g "daemon off;"
那么我們的容器啟動命令就是
["/usr/sbin/nginx", -g, "daemon off"]
和CMD組合能構(gòu)成一個默認(rèn)啟動的Dockerfile。
ENTRYPOINT ["/usr/sbin/nginx"]
CMD ["-h"]
WORKDIR
WORKDIR 會指定一個工作目錄,進(jìn)入到該目錄內(nèi),ENTRYPOINT,RUN或CMD指定的程序?qū)谶@個目錄下執(zhí)行。
比如:
WORKDIR /opt/webapp/db
RUN bundle install
WORKDIR /opt/webapp
ENTRYPOINT ["rackup"]
相當(dāng)于我們cd到/opt/webapp/db目錄下,然后執(zhí)行安裝bundle,之后再cd到/opt/webapp目錄下,等待啟動命令的啟動。
我們可以在docker run的時候指定-w在運行時覆蓋工作目錄。
docker run -ti -w /var/log ubuntu pwd
/var/log
可以看到目錄切到了/var/log下面。
ENV
ENV用來設(shè)置鏡像的環(huán)境變量。
ENV RVM_PATH /home/rvm/
加入環(huán)境變量之后可以直接使用
WORKDIR $RVM_PATH
可以 docker run的時候用-e標(biāo)志來傳遞環(huán)境變量。
docker run -ti -e "WEB_PORT=8080" ubuntu env
USER
USER指令用來指定該鏡像會以什么樣的用戶去運行。
默認(rèn)是root。
也可以在docker run的時候指定-u去修改用戶。
VOLUME
VOLUME指令用來向基于鏡像創(chuàng)建的容器添加卷。這個卷可以在一個或多個容器中共享數(shù)據(jù)和持久化。
卷會一直存在直到?jīng)]有任何容器再使用它。
卷功能讓我們可以講數(shù)據(jù),數(shù)據(jù)庫或者其他東西添加到鏡像中,而不是將這些東西提交到鏡像中。
ADD
ADD指令用來將構(gòu)建環(huán)境的文件或目錄復(fù)制到鏡像中。
ADD software.lic /opt/application/software.lic
指向源文件的位置參數(shù)還可以是一個URL。
docker判斷文件還是目錄是看后面有沒有以/結(jié)尾。
而且如果傳輸?shù)脑次募莟ar包,docker還會幫我們解壓縮。
ADD lastest.tar.gz /var/www/wordpress/
這條命令會把lastest.tar.gz解壓縮到/var/www/wordpress/目錄下。不過url的行為可能不一樣。
PS:ADD會使構(gòu)建緩存失效。
COPY
COPY和ADD類似,但是COPY不能從URL中獲取數(shù)據(jù),也不會自動解壓縮。
ONBUILD
ONBUILD指令能為鏡像添加觸發(fā)器。當(dāng)一個鏡像被用作其他鏡像的基礎(chǔ)鏡像時,觸發(fā)器會被執(zhí)行??梢哉J(rèn)為這些指令是在FORM之后指定的。
ONBUILD ADD ./app/src
ONBUILD RUN cd /app/src && make
ONBUILD指令可以通過在鏡像上運行docker inspect命令來查看。
ONBUILD只會被繼承一次,所以只會被子鏡像中執(zhí)行,而不會在孫子鏡像中執(zhí)行。
刪除鏡像
如果你不需要一個鏡像了,你可以用
docker rmi image_name
進(jìn)行刪除。