docker小冊學習筆記之三——操作鏡像

第十一課:保存和共享鏡像

提交容器更改

Docker鏡像的本質是多個基於UnionFS的鏡像層依次掛載的結果,而容器的文件系統則是在以只讀方式掛載鏡像後增加的一個可讀可寫的沙盒環境。
基於這樣的結構,Docker中爲我們提供了將容器中的這個可讀可寫的沙盒環境持久化爲一個鏡像層的方法。也就是,我們能夠輕鬆的將docker裏的容器內的修改記錄下來,保存爲一個新的鏡像。

docker commit webapp   // 將名爲webapp的容器保存爲鏡像的命令,可以形象稱爲提交容器的更改

docker images // 可以從本地鏡像列表中找到它
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
<none>                <none>              0bc42f7ff218        3 seconds ago       372MB
## ......

docker commit -m"Configured" webapp  //類似代碼提交,提交容器更改時給出一個提交信息,方便查詢

爲鏡像命名

docker tag 0bc42 webapp:1.0    //爲一個(未命名)鏡像取名
docker tag webapp:1.0 webapp:latest //爲已命名的鏡像重新取名

//對未命名的驚喜取名後,docker不會顯示原未命名鏡像,只會顯示有命名的鏡像。對已命名的鏡像再次命名後,則,舊的鏡像依舊會存在鏡像列表中

docker image
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
webapp                1.0                 0bc42f7ff218        29 minutes ago      372MB
webapp                latest              0bc42f7ff218        29 minutes ago      372MB
## ......


docker commit -m "Upgrade" webapp webapp:2.0 //提交容器更改時直接指定鏡像名

鏡像的遷移

將容器導出爲鏡像後,需要遷移鏡像。由於Docker是以集中的方式管理鏡像的,所以遷移之前,需要從Docker中取出鏡像。docker save命令可以將鏡像輸出,保存鏡像Docker外部。

docker save webapp:1.0>webapp-1.0.tar //使用管道進行接收(即 > 符號) 不推薦使用管道

docker save -o ./webapp-1.0.tar webapp:1.0 //-o 指定輸出文件
// 輸出後,可以在相應路徑查看已經存儲鏡像內容的wepapp-1.0.tar的文件了

導入鏡像

當從A機器的docker中導出鏡像文件至A機器中後,我們可以通過多種方式將A機器中的文件複製到B機器上。 之後, 我們需要將鏡像導入到B機器中運行的Docker中。
與docker save 相對的 docker load

docker load < webapp-1.0.tar    //管道方式 從輸入流中讀取鏡像數據

docker load -i webapp-1.0.tar   //-i 指定輸入文件

//鏡像導入後,可以通過 docker images 看到它
docker images

批量遷移

docker save -o ./images.tar webapp:1.0 nginx:1.12 mysql:5.7  
// 只需要在docker save中傳入多個鏡像名做參數,它能夠將鏡像都打成一個包,便於一次性遷移

導出和導入容器

docker export -o ./webapp.tar webapp  // docker export直接導出容器,docker commit 和 docker save 的結合體

使用docker export導出的容器包,可以使用docker import導入。需要注意,docker import 並非直接將容器導入,而是以鏡像形式導入,所以 導入結果仍然是鏡像,不是容器。 docker import中 也可以給鏡像命名

docker import ./webapp.tar webapp:1.0

思考題

問 : 通過Docker進行的集羣部署和其他虛擬化形式中的集羣部署有怎樣的區別,在部署過程中Docker又是怎樣發揮它的優勢的。

答:可以將容器直接導出壓縮包,利用命令直接導入鏡像。

第十二課 通過Dockerfile創建鏡像

關於Dockerfile

Dockerfile是Docker中用於鏡像自動化構建流程的配置文件,在Dockerfile中,包含了構建鏡像過程中需要執行的命令和其他操作。

Dockerfile的內容很簡單,主要以兩種形式 一種註釋行,另一種指令行。

# Comment
INSTRUCTION arguments

Dockerfile在容器體系下能夠完成自動構建。無需人工執行每一個搭建流程。

編寫Dockerfile

與提交容器的修改,再進行鏡像遷移的方式相比,使用Dockerfile的優勢

  1. Dockerfile的體積遠小於鏡像包,更容易進行快速遷移和部署
  2. 環境構建流程記錄了Dockerfile中,能夠直觀的看到鏡像構建的順了和邏輯
  3. 使用Dockerfile來構建鏡像能夠輕鬆實現自動部署等自動化流程。
  4. 在修改環境搭建細節時,修改Dockerfile文件要比從新提交鏡像來的輕鬆。

Docker官方提供的Redis鏡像的Dockerfile文件

FROM debian:stretch-slim

# add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added
RUN groupadd -r redis && useradd -r -g redis redis

# grab gosu for easy step-down from root
# https://github.com/tianon/gosu/releases
ENV GOSU_VERSION 1.10
RUN set -ex; \
	\
	fetchDeps=" \
		ca-certificates \
		dirmngr \
		gnupg \
		wget \
	"; \
	apt-get update; \
	apt-get install -y --no-install-recommends $fetchDeps; \
	rm -rf /var/lib/apt/lists/*; \
	\
	dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
	wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
	wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
	export GNUPGHOME="$(mktemp -d)"; \
	gpg --keyserver ha.pool.sks-keyservers.net --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
	gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
	gpgconf --kill all; \
	rm -r "$GNUPGHOME" /usr/local/bin/gosu.asc; \
	chmod +x /usr/local/bin/gosu; \
	gosu nobody true; \
	\
	apt-get purge -y --auto-remove $fetchDeps

ENV REDIS_VERSION 3.2.12
ENV REDIS_DOWNLOAD_URL http://download.redis.io/releases/redis-3.2.12.tar.gz
ENV REDIS_DOWNLOAD_SHA 98c4254ae1be4e452aa7884245471501c9aa657993e0318d88f048093e7f88fd

# for redis-sentinel see: http://redis.io/topics/sentinel
RUN set -ex; \
	\
	buildDeps=' \
		wget \
		\
		gcc \
		libc6-dev \
		make \
	'; \
	apt-get update; \
	apt-get install -y $buildDeps --no-install-recommends; \
	rm -rf /var/lib/apt/lists/*; \
	\
	wget -O redis.tar.gz "$REDIS_DOWNLOAD_URL"; \
	echo "$REDIS_DOWNLOAD_SHA *redis.tar.gz" | sha256sum -c -; \
	mkdir -p /usr/src/redis; \
	tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1; \
	rm redis.tar.gz; \
	\
# disable Redis protected mode [1] as it is unnecessary in context of Docker
# (ports are not automatically exposed when running inside Docker, but rather explicitly by specifying -p / -P)
# [1]: https://github.com/antirez/redis/commit/edd4d555df57dc84265fdfb4ef59a4678832f6da
	grep -q '^#define CONFIG_DEFAULT_PROTECTED_MODE 1$' /usr/src/redis/src/server.h; \
	sed -ri 's!^(#define CONFIG_DEFAULT_PROTECTED_MODE) 1$!\1 0!' /usr/src/redis/src/server.h; \
	grep -q '^#define CONFIG_DEFAULT_PROTECTED_MODE 0$' /usr/src/redis/src/server.h; \
# for future reference, we modify this directly in the source instead of just supplying a default configuration flag because apparently "if you specify any argument to redis-server, [it assumes] you are going to specify everything"
# see also https://github.com/docker-library/redis/issues/4#issuecomment-50780840
# (more exactly, this makes sure the default behavior of "save on SIGTERM" stays functional by default)
	\
	make -C /usr/src/redis -j "$(nproc)"; \
	make -C /usr/src/redis install; \
	\
	rm -r /usr/src/redis; \
	\
	apt-get purge -y --auto-remove $buildDeps

RUN mkdir /data && chown redis:redis /data
VOLUME /data
WORKDIR /data

COPY docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]

EXPOSE 6379
CMD ["redis-server"]

Dockerfile的結構

Dockerfile可以理解爲一個由上往下執行指令的腳本。
Dockerfile的指令簡單分爲五大類:

  1. 基礎指令:用於定義新鏡像的基礎和性質
  2. 控制指令:是指導鏡像構建的核心部分,用於描述鏡像在構建構成中需要執行的指令
  3. 引入指令:用於將外部文件直接引入到構建鏡像內部
  4. 執行指令:能夠爲基於鏡像所創建的容器,指定在啓動時需要執行的腳本或命令。
  5. 配置指令:對鏡像以及基於鏡像所創建的容器,可以通過配置指定對其網絡、用戶等內容進行配置。

常見的Dockerfile指令

FROM

通過FROM指令指定一個基礎鏡像,接下來的操作都是基本這個鏡像所展開的。所以FROM指令經常作爲第一條指令。

//FROM指令的三種形式
FROM <image> [AS <name>]
FROM <image>[:<tag>] [AS <name>]
FROM <image>[@<digest>] [AS <name>]

在Dockerfile中可以多次出現FROM指令,當FROM第二次或者之後出現時,表示此刻需要將當前指出的鏡像的內容合併到此刻構建的鏡像內容裏。直接合並兩個鏡像。

RUN

RUN指令就是用於向控制檯發送命令的指令。

//RUN後面直接拼上需要執行的命令,在構建時,docker會執行這些命令,並將我們對文件系統的修改記錄下來,形成鏡像的變化。
RUN <commond>
RUN ["execute","param1","param2"]

支持 \ 換行。

ENTRYPOINT和CMD

基於鏡像啓動的容器,在容器啓動時會根據鏡像所定義的一條命令來啓動容器中進程號爲1的進程。而這個命令的定義,就是通過Dockerfile中的ENTRYPOINT和CMD來實現。

ENTRYPOINT ["executable","param1","param2"]
ENTRYPOINT command param1 param2

CMD ["executable","param1","param2"]
CMD ["param1","param2"]
CMD command param1 param2

ENTRYPOINT和CMD指令用法近似,都是給出需要執行的命令,並且都可以爲空,或者不再Dockerfile裏指出。

EXPOSE

通過EXPOSE指令就可以爲鏡像指定要暴露的端口

//配置了鏡像的端口暴露定義,那麼基於這個鏡像所創建的容器,
//在被其他容器通過--link連接時,就能夠直接允許來自其他容器對這些端口的訪問了
EXPOSE <port> [<port>/<protocol>...]

VOLUME

使用VOLUME指令來定義基於此鏡像的容器所自動建立的數據卷。

//在VOLUME指令中定義的目錄,在基於新鏡像創建容器時,會自動建立爲數據卷,不需要我們再單獨使用-v選項來配置
VOLUME ["/data"]

COPY和ADD
使用COPY或ADD指令能夠幫助我們直接從宿主機的文件系統裏拷貝內容到鏡像裏的文件系統中。

COPY [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] <src>... <dest>

COPY [--chown=<user>:<group>] ["<src>",..."<dest>"]
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]

兩者的區別主要在於ADD能夠支持網絡端的URL地址作爲src源,並且在源文件被識別爲壓縮包時,自動進行解壓,而COPY沒有這兩個能力。

構建鏡像

構建鏡像,參數是目錄路徑(本地路徑或URL路徑),而非dockerfile的文件路徑。
這個目錄作爲構建的環境目錄,我們的很多操作都是基於這個目錄進行的。如,使用COPY或是ADD拷貝文件到構建的新環境時,會以這個目錄作爲基礎目錄。

docker build ./webapp

默認情況下,docker build也會從這個目錄下尋找目錄名爲Dockerfile的文件,將它作爲Dockerfile內容來源。如果我們的Dockerfile文件路徑不在目錄下,或者有另外的文件名,我們可以通過-f選先單獨給出Dockerfile的文件的路徑

docker build -t webapp:latest -f ./webapp/a.Dockerfile ./webapp

構建時最好帶上-t選項 指定新生成鏡像的名稱

docker build -t webapp:latest ./webapp

第十三課 常見Dockerfile使用技巧

構建中使用變量

在Dockerfile中,可以用ARG指令來建立一個參數,在構建時通過構建指令傳入這個參數變量,並且在Dockerfile裏使用它。例如:可以使用ARG佔位符來控制Dockerfile中某個程序的版本。

FROM  debian:stretch-slim

## ......

ARG TOMCAT_MAJOR
ARG TOMCAT_VERSION

## ......

RUN wget -O tomcat.tar.gz "https://www.apache.org/dyn/closer.cgi?action=download&filename=tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz"

## ......

本例中,將tomcat的版本號通過ARG指令定義爲參數變量,在調用下載tomcat包時,使用變量替換掉下載地址中的版本號。通過這樣的定義,就可以在不對Dockerfile進行大幅修改的前提下,輕鬆實現對Tomcat版本的切換並重新構建鏡像了。

使用這個Dockerfile文件構建TOmcat時,可以在構建時通過docker build的 --build-arg 選項來設置參數變量

docker build --build-arg TOMCAT_MAJOR --build-arg TOMCAT_VERSION=8.0.53 -t tomcat:8.0 ./tomcat

構建緩存

Docker在鏡像構建的過程中,還支持一種緩存策略來提高鏡像的構建速度。

原理:由於鏡像是多個指令所創建的鏡像層組合而得,那麼如果我們判斷新編譯的鏡像層與已經存在的鏡像層未發生變化,那麼我們完全可以直接利用之前構建的結果,而不需要再執行這條構建指令,這就是鏡像構建緩存的原理。

在另外一些時候,我們不希望Docker在構建鏡像時使用構建緩存,這時我們通過 --no-cache 選項來禁用它。

docker build --no-cache ./webapp

搭配 ENTRYPOINT 和 CMD

ENTRYPOINT 和 CMD 這兩個命令的目的,都是用來指定基於此鏡像所創建容器裏主進程的啓動命令的。
兩個指令的區別在於,ENTRYPOINT 指令的優先級高於CMD指令。

爲了更好的讓大家理解,這裏索性列出所有的 ENTRYPOINT 與 CMD 的組合,供大家參考。

ENTRYPOINT CMD 實際執行
ENTRYPOINT ["/bin/ep", “arge”] /bin/ep arge
ENTRYPOINT /bin/ep arge /bin/sh -c /bin/ep arge
CMD ["/bin/exec", “args”] /bin/exec args
CMD /bin/exec args /bin/sh -c /bin/exec args
ENTRYPOINT ["/bin/ep", “arge”] CMD ["/bin/exec", “argc”] /bin/ep arge /bin/exec argc
ENTRYPOINT ["/bin/ep", “arge”] CMD /bin/exec args /bin/ep arge /bin/sh -c /bin/exec args
ENTRYPOINT /bin/ep arge CMD ["/bin/exec", “argc”] /bin/sh -c /bin/ep arge /bin/exec argc
ENTRYPOINT /bin/ep arge CMD /bin/exec args /bin/sh -c /bin/ep arge /bin/sh -c /bin/exec args

ENTRYPOINT 和 CMD 設計的目的是不同的。ENTRYPOINT指令主要用於對容器進行一些初始化,而CMD指令則用於真正定義容器中主程序的啓動命令。

這是redis鏡像中對ENTRYPOINT 和 CMD的定義

## ......

COPY docker-entrypoint.sh /usr/local/bin/

ENTRYPOINT ["docker-entrypoint.sh"]

## ......

CMD ["redis-server"]

可以看到,CMD指令定義的正是啓動redis的服務程序,而ENTRYPOINT使用的是一個外部引入的腳本文件。

事實上,使用腳本文件來作爲ENTRYPOINT的內容是常見的做法,因爲對容器運行初始化的命令相對較多,全部放置在ENTRYPOINT後會特別複雜。
我們來看看Redis中ENTRYPOINT腳本,可以看到其中會根據腳本參數進行一些處理,而腳本的參數,其實就是CMD中定義的內容。

#!/bin/sh
set -e

# first arg is `-f` or `--some-option`
# or first arg is `something.conf`
if [ "${1#-}" != "$1" ] || [ "${1%.conf}" != "$1" ]; then
	set -- redis-server "$@"
fi

# allow the container to be started with `--user`
if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
	find . \! -user redis -exec chown redis '{}' +
	exec gosu redis "$0" "$@"
fi

exec "$@"

這裏我們要關注腳本最後的一條命令,也就是 exec “$@”。在很多鏡像的 ENTRYPOINT 腳本里,我們都會看到這條命令,其作用其實很簡單,就是運行一個程序,而運行命令就是 ENTRYPOINT 腳本的參數。反過來,由於 ENTRYPOINT 腳本的參數就是 CMD 指令中的內容,所以實際執行的就是 CMD 裏的命令。

所以說,雖然 Docker 對容器啓動命令的結合機制爲 CMD 作爲 ENTRYPOINT 的參數,合併後執行 ENTRYPOINT 中的定義,但實際在我們使用中,我們還會在 ENTRYPOINT 的腳本里代理到 CMD 命令上。

第十四課 使用 Docker Hub 中的鏡像

本節可以看看,可記錄的東西比較少

PS: 這是學習docker小冊的筆記,附上小冊地址:https://juejin.im/book/5b7ba116e51d4556f30b476c

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章