第8章 使用Dockerfile创建镜像

Dockerfile是一个文本格式的配置文件,用户可以使用Dockerfile来快速创建自定义镜像。

本章首先介绍Dockerfile典型的基本结构和它支持的众多指令,并具体讲解通过这些指令来编写定制镜像的Dockerfile,以及如何生成镜像。最后介绍使用Dockerfile的一些最佳实践经验。

8.1 基本结构

下在是Docker Hub 上两个热门镜像的Dockerfile的例子,可以帮助读者对Dockerfile结构有个基本的认识:

第一个例子是在debian:stretch基础镜像上安装Nginx环境,从而创建一个新的nginx 镜像

FROM debian:stretch-slim

LABEL maintainer="NGINX Docker Maintainers <[email protected]>"

ENV NGINX_VERSION 1.13.12-1~stretch
ENV NJS_VERSION   1.13.12.0.2.0-1~stretch

RUN set -x \
	&& apt-get update \
	&& apt-get install --no-install-recommends --no-install-suggests -y gnupg1 apt-transport-https ca-certificates \
	&& \
	NGINX_GPGKEY=573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62; \
	found=''; \
	for server in \
		ha.pool.sks-keyservers.net \
		hkp://keyserver.ubuntu.com:80 \
		hkp://p80.pool.sks-keyservers.net:80 \
		pgp.mit.edu \
	; do \
		echo "Fetching GPG key $NGINX_GPGKEY from $server"; \
		apt-key adv --keyserver "$server" --keyserver-options timeout=10 --recv-keys "$NGINX_GPGKEY" && found=yes && break; \
	done; \
	test -z "$found" && echo >&2 "error: failed to fetch GPG key $NGINX_GPGKEY" && exit 1; \
	apt-get remove --purge --auto-remove -y gnupg1 && rm -rf /var/lib/apt/lists/* \
	&& dpkgArch="$(dpkg --print-architecture)" \
	&& nginxPackages=" \
		nginx=${NGINX_VERSION} \
		nginx-module-xslt=${NGINX_VERSION} \
		nginx-module-geoip=${NGINX_VERSION} \
		nginx-module-image-filter=${NGINX_VERSION} \
		nginx-module-njs=${NJS_VERSION} \
	" \
	&& case "$dpkgArch" in \
		amd64|i386) \
# arches officialy built by upstream
			echo "deb https://nginx.org/packages/mainline/debian/ stretch nginx" >> /etc/apt/sources.list.d/nginx.list \
			&& apt-get update \
			;; \
		*) \
# we're on an architecture upstream doesn't officially build for
# let's build binaries from the published source packages
			echo "deb-src https://nginx.org/packages/mainline/debian/ stretch nginx" >> /etc/apt/sources.list.d/nginx.list \
			\
# new directory for storing sources and .deb files
			&& tempDir="$(mktemp -d)" \
			&& chmod 777 "$tempDir" \
# (777 to ensure APT's "_apt" user can access it too)
			\
# save list of currently-installed packages so build dependencies can be cleanly removed later
			&& savedAptMark="$(apt-mark showmanual)" \
			\
# build .deb files from upstream's source packages (which are verified by apt-get)
			&& apt-get update \
			&& apt-get build-dep -y $nginxPackages \
			&& ( \
				cd "$tempDir" \
				&& DEB_BUILD_OPTIONS="nocheck parallel=$(nproc)" \
					apt-get source --compile $nginxPackages \
			) \
# we don't remove APT lists here because they get re-downloaded and removed later
			\
# reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies
# (which is done after we install the built packages so we don't have to redownload any overlapping dependencies)
			&& apt-mark showmanual | xargs apt-mark auto > /dev/null \
			&& { [ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; } \
			\
# create a temporary local APT repo to install from (so that dependency resolution can be handled by APT, as it should be)
			&& ls -lAFh "$tempDir" \
			&& ( cd "$tempDir" && dpkg-scanpackages . > Packages ) \
			&& grep '^Package: ' "$tempDir/Packages" \
			&& echo "deb [ trusted=yes ] file://$tempDir ./" > /etc/apt/sources.list.d/temp.list \
# work around the following APT issue by using "Acquire::GzipIndexes=false" (overriding "/etc/apt/apt.conf.d/docker-gzip-indexes")
#   Could not open file /var/lib/apt/lists/partial/_tmp_tmp.ODWljpQfkE_._Packages - open (13: Permission denied)
#   ...
#   E: Failed to fetch store:/var/lib/apt/lists/partial/_tmp_tmp.ODWljpQfkE_._Packages  Could not open file /var/lib/apt/lists/partial/_tmp_tmp.ODWljpQfkE_._Packages - open (13: Permission denied)
			&& apt-get -o Acquire::GzipIndexes=false update \
			;; \
	esac \
	\
	&& apt-get install --no-install-recommends --no-install-suggests -y \
						$nginxPackages \
						gettext-base \
	&& apt-get remove --purge --auto-remove -y apt-transport-https ca-certificates && rm -rf /var/lib/apt/lists/* /etc/apt/sources.list.d/nginx.list \
	\
# if we have leftovers from building, let's purge them (including extra, unnecessary build deps)
	&& if [ -n "$tempDir" ]; then \
		apt-get purge -y --auto-remove \
		&& rm -rf "$tempDir" /etc/apt/sources.list.d/temp.list; \
	fi

# forward request and error logs to docker log collector
RUN ln -sf /dev/stdout /var/log/nginx/access.log \
	&& ln -sf /dev/stderr /var/log/nginx/error.log

EXPOSE 80

STOPSIGNAL SIGTERM

CMD ["nginx", "-g", "daemon off;"]

第二个例子是基于buildpack-deps:stretch-scm基础镜像,安装Golang相关环境,制作一个GO语言的运行环境镜像:

FROM buildpack-deps:stretch-scm

# gcc for cgo
RUN apt-get update && apt-get install -y --no-install-recommends \
		g++ \
		gcc \
		libc6-dev \
		make \
		pkg-config \
	&& rm -rf /var/lib/apt/lists/*

ENV GOLANG_VERSION 1.10.2

RUN set -eux; \
	\
# this "case" statement is generated via "update.sh"
	dpkgArch="$(dpkg --print-architecture)"; \
	case "${dpkgArch##*-}" in \
		amd64) goRelArch='linux-amd64'; goRelSha256='4b677d698c65370afa33757b6954ade60347aaca310ea92a63ed717d7cb0c2ff' ;; \
		armhf) goRelArch='linux-armv6l'; goRelSha256='529a16b531d4561572db6ba9d357215b58a1953437a63e76dc0c597be9e25dd2' ;; \
		arm64) goRelArch='linux-arm64'; goRelSha256='d6af66c71b12d63c754d5bf49c3007dc1c9821eb1a945118bfd5a539a327c4c8' ;; \
		i386) goRelArch='linux-386'; goRelSha256='ea4caddf76b86ed5d101a61bc9a273be5b24d81f0567270bb4d5beaaded9b567' ;; \
		ppc64el) goRelArch='linux-ppc64le'; goRelSha256='f0748502c90e9784b6368937f1d157913d18acdae72ac75add50e5c0c9efc85c' ;; \
		s390x) goRelArch='linux-s390x'; goRelSha256='2266b7ebdbca13c21a1f6039c9f6887cd2c01617d1e2716ff4595307a0da1d46' ;; \
		*) goRelArch='src'; goRelSha256='6264609c6b9cd8ed8e02ca84605d727ce1898d74efa79841660b2e3e985a98bd'; \
			echo >&2; echo >&2 "warning: current architecture ($dpkgArch) does not have a corresponding Go binary release; will be building from source"; echo >&2 ;; \
	esac; \
	\
	url="https://golang.org/dl/go${GOLANG_VERSION}.${goRelArch}.tar.gz"; \
	wget -O go.tgz "$url"; \
	echo "${goRelSha256} *go.tgz" | sha256sum -c -; \
	tar -C /usr/local -xzf go.tgz; \
	rm go.tgz; \
	\
	if [ "$goRelArch" = 'src' ]; then \
		echo >&2; \
		echo >&2 'error: UNIMPLEMENTED'; \
		echo >&2 'TODO install golang-any from jessie-backports for GOROOT_BOOTSTRAP (and uninstall after build)'; \
		echo >&2; \
		exit 1; \
	fi; \
	\
	export PATH="/usr/local/go/bin:$PATH"; \
	go version

ENV GOPATH /go
ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH

RUN mkdir -p "$GOPATH/src" "$GOPATH/bin" && chmod -R 777 "$GOPATH"
WORKDIR $GOPATH

下面讲解Dockerfile中各种指令的应用。

8.2 指令说明

    指令的一般格式为INSTRUCTION argument,指令包括FROM、MAINTAINER、RUn等,参见表8-1

    表8-1 Dokcerfile指令说明


下面分别进行介绍。

1.FROM

指定所创建镜像的基础镜像,如果本地不存在,则默认会去Docker Hub下载指定镜像。

格式为 FROM<image>,或FROM<image><tag>,或FROM<image>@<digest>。

2. MAINTAINER

指定维护者信息,格式为MAINTAINER<name>。例如:

MAINTAINER [email protected]

该信息会写入生成镜像的Author属性域中。

3. RUN

运行指定命令。

格式为RUN<command>或RUN["executable","parame1","param2"]。注意,后一个指令会被解析为Json数组,因此必须用双引号。

前者默认将在shell终端中运行命令,即/bin/sh -c;后者则使用exec执行,不会启动shell环境。

指定使用其他终端类型可以通过第二种方式来实现,例如RUN["/bin/bash","-c","ehco hello"]。

每条RUN指令将在当前镜像的基础上执行指定命令,并提交为新的镜像。当命令较长时可以使用\来换行。例如:

RUN set -x \
	&& apt-get update \
	&& apt-get install --no-install-recommends --no-install-suggests -y gnupg1 apt-transport-https ca-certificates \
	&& \
	NGINX_GPGKEY=573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62; \
	found=''; \

4.CMD 

CMD指令用来指定启动容器时默认执行的命令。它支持三种格式:

        CMD ["executable","param1","param2"]使用exec执行,是推荐使用的方式;

        CMD command param1 param2 在/bin/sh中执行,提供给需要交互的应用;

        CMD["param1","parma2"]提供给ENTRYPOINT的默认参数。

每个Dockerfile只能有一条CMD命令。如果指定了多条命令,只有最后一条会被执行。

如果用户启动容器时手动指定了运行的命令(作为run的参数),则会覆盖掉CMD指定的命令。

5.LABEL

LABEL指令用来指定生成镜像的元数据标签信息。

格式为LABEL <key>=<value> <key>=<value> <key>=<value>...。

例如:

LABEL version="1.0"
LABEL description="This text illustrates \ that label -v alues can span multiple lines."

6. EXPOSE

    声明镜像内服务所监听端口。

    格式为EXPOSE <port>[<port>...]。

    例如:

EXPOSE 22 80 443

注意,该指令只是起到声明作用,并不会自动完成端口映射。

         在启动容器时需要使用-P,Docker主机会自动分配一个宿主主机的临时端口转发到指定端口;使用-p,则可以具体指定哪个个宿主主机的本地端口会映射过来。

7.ENV 

指定环境变量,在镜像生成过程中会被后续RUN指令使用,在镜像启动的容器中也会存在。

格式为ENV<key><value> 或ENV<key>=<value>...

例如:

ENV NGINX_VERSION 1.13.12-1~stretch
ENV NJS_VERSION   1.13.12.0.2.0-1~stretch

8. ADD

该命令将复制指定的<src>路径下的内容到容器中的<dest>路径下。

    格式为ADD<src><dest>。

其中<src>可以是Dockerfile所在目录的一个相对路径(文件或目录),也可以是一个URL,还可以是一个tar文件(如果为tar文件,会自动解压到<dest>路径下)。<dest>可以是镜像内的绝对路径,或者相对于工作目录(WORKDIR)的相对路径。

路径支持正则格式,例如:

ADD *.c /code/

9.COPY

    格式为COPY<src><dest>。

    复制本地主机的<src>(为Dockerfile所在目录的相对路径、文件或目录)下的内容到镜像中的<dest>下。目标路径不存在时,会自动创建。

    路径同样支持正则格式。

    当使用本地目录为源目录时,推荐使用COPY。

10.ENTRYPOINT

指定镜像的默认入口命令,该入口命令会在启动容器时作为根命令执行,所有传入值作为该命令的参数。

支持两种格式:

ENTRYPOINT ["executable","param1","param2"] (exec调用执行);

ENTRYPOINT command param1 param2 (shell中执行)。

此时,CMD指令指定值将作为根命令的参数。

每个Dockerfile中只能有一个ENTRYPOINT,当指定多个时,只有最后一个有效。

在运行时,可以被--entrypoint参数覆盖掉,如docker run --entrypoint。

 11.VOLUME

创建一个数据卷挂载点。

格式为VOLUME["/data"]

可以从本地主机或其它容器挂载数据卷,一般用户来存放数据库和需要保存的数据等。

12.USER

指定运行容器时的用户名或UID,后续的RUN等指令也会使用指定的用户身份。

RUN groupadd -r postgres && useradd -r -g postgres postgres

格式人USER daemon。

当服务不需要管理员权限时,可以通过命令指定运行用户,并且可以在之前创建所需要的用户。例如:

要临时获取管理员权限可以使用gosu或sudo。

13.WORKDIR

为后续的RUN、 CMD和ENTRYPOINT指令配置工作目录。

格式为WORKDIR /path /to / wrokdir。

可以使用多个WORKDIR指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。例如:

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

则最终路径为/a/b/c。

14.ARG 

指定一些镜像内使用的参数(例如版本号信息等),这些参数在执行docker build命令时才以--build-arg<varname>=<value>格式传入。

格式为ARG<name>[default valus>]。

则可以用docker build --build arg <name>=<value>,来指定参数值。

15.ONBUILD

配置当所创建的镜像作为其他镜像的基础镜像时,所执行的创建操作指令。

格式为ONBUILD[INSTRUCTION]。

例如,Dockerfile使用如下的内容创建了镜像image-A:

[...]
ONBUILD ADD ./app/src
ONBUILD RUN /usr/local/bin/python-build --dir /ap/src
[...]

如果基于image-A 创建新的镜像时,新的Dockerfile中使用FROM image-A 指定基础镜像,会自动执行ON-BUILD指令内容,等价与在后面添加了两条指令:

FROM image-A
#Automatically run the following
ADD ./app/src
RUN /usr/local/bin/python-build --dir /app/src

使用ONBUILD指令的镜像,推荐在标签中注明,例如ruby:1.9-onbuild。

16. STOPSIGNAL

    指定所创建镜像启动的容器接收退出的信息值。例如:

STOPSIGNAL signal

17.HEALTHCHECK

配置所启动容器如何进行键康检查(如何判断健康与否),自Docker 1.12开始。

格式有两种:

        HEALTHCHECK [OPTIONS] CMD command:根据所执行命令返回值是否为0来判断;

        HEALTHCHECK NONE:禁止基础镜像中的健康检查。

         -- interval=DURATION(默认为:30s):过多久检查一次;

        --timeout=DURATION(默认为30s):每次检查等待结果的超时;

        --retries=N(默认为:3):如果失败了,重试几次才最终确定失败。

18. SHELL

    指定其他命令使用shell时的默认shell类型。

SHELL ["executable","parametres"]

注意:

    对于Window系统,建议在Docker开头添加#escapt=`来指定转义信息。

8.3 创建镜像

    编写完成Dockerfile之后,可以通过docker build命令创建镜像

    基本的格式为docker build[选项][内容路径]该命令将读取指定路径下,(包括子目录)的Dockerfile,并将该路径下的所有内容发送给Docker服务端,由服务端来创建镜像。因此除非生成镜像需要,否则一般建议放置Dockerfile的目录为空目录。

例如,指定Dockerfilel所在路径为/tmp/docker_builder/,并且希望生成镜像标签为build_repo/first_image,可以使用下面的命令:

$docker build -t build_repo/first_image /tmp/docker_builder/

8.4 使用.dockerignore文件

8.5 最佳实践

        精简镜像用途:

        选用合适的基础镜像

        提供足够清晰的命令注释和维护者信息:

        正确使用版本号

        减少镜像层数

        及时删除临时文件和缓存文件

        提高生成速度

    调整合理的指令顺序

    减少外部源的干扰

8.6 本章小结

    本章主要介绍了围绕Dockerfile文件构建镜像的过程,包括Dockerfile的基本结构、所支持的内部指令,使用它创建镜像的基本过程,以及合理构建镜像的最佳实践。在使用Dockerfile构建镜像的过程中,读者会体会到Docker "一点修改代替大量更新"的灵活之处。

    当然,编写一个高质量的Dockerfile并不是一件容易的事情,需要一定时间的学习和实践,在本书的第二部分中,笔者也给出了大量热门镜像的Dockerfile,供大家学习参考。

猜你喜欢

转载自blog.csdn.net/tjjingpan/article/details/80262329
今日推荐