Dockerfile用于构建docker镜像,本章节从一个Dockerfile的例子讲起,先整体看下具体做了什么,在详细介绍每条指定的作用与用法。
1、找到mysql的Dockerfile,并分析
访问https://registry.hub.docker.com/,搜索mysql

点击图中红框进到mysql的github源码。

选择5.7,打开Dockerfile
#构建镜像的依赖
FROM debian:buster-slim
# 执行添加组和添加用户的命令,不熟悉的同学看下mysql添加组和用户命令
RUN groupadd -r mysql && useradd -r -g mysql mysql
#更新依赖版本库、--no-install-recommends不安装非必须依赖、删除指定目录的文件
RUN apt-get update && apt-get install -y --no-install-recommends gnupg dirmngr && rm -rf /var/lib/apt/lists/*
# 环境变量 指定gosu版本,以便后面使用
ENV GOSU_VERSION 1.12
#执行很多的脚本命令,以反斜杆结尾代表一个命令,此处建议这么写,不能写多个run,以减少镜像的层数
RUN set -eux; \
savedAptMark="$(apt-mark showmanual)"; \
apt-get update; \
apt-get install -y --no-install-recommends ca-certificates wget; \
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 --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
gpgconf --kill all; \
rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
apt-mark auto '.*' > /dev/null; \
[ -z "$savedAptMark" ] || apt-mark manual $savedAptMark > /dev/null; \
apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \
chmod +x /usr/local/bin/gosu; \
gosu --version; \
gosu nobody true
#创建docker-entrypoint-initdb.d文件
RUN mkdir /docker-entrypoint-initdb.d
#更新依赖源以及安装必要依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
# for MYSQL_RANDOM_ROOT_PASSWORD
pwgen \
# for mysql_ssl_rsa_setup
openssl \
# FATAL ERROR: please install the following Perl modules before executing /usr/local/mysql/scripts/mysql_install_db:
# File::Basename
# File::Copy
# Sys::Hostname
# Data::Dumper
perl \
# install "xz-utils" for .sql.xz docker-entrypoint-initdb.d files
xz-utils \
&& rm -rf /var/lib/apt/lists/*
RUN set -ex; \
# gpg: key 5072E1F5: public key "MySQL Release Engineering <mysql-build@oss.oracle.com>" imported
key='A4A9406876FCBD3C456770C88C718D3B5072E1F5'; \
export GNUPGHOME="$(mktemp -d)"; \
gpg --batch --keyserver ha.pool.sks-keyservers.net --recv-keys "$key"; \
gpg --batch --export "$key" > /etc/apt/trusted.gpg.d/mysql.gpg; \
gpgconf --kill all; \
rm -rf "$GNUPGHOME"; \
apt-key list > /dev/null
#两个环境变量
ENV MYSQL_MAJOR 5.7
ENV MYSQL_VERSION 5.7.31-1debian10
#输出信息到文件/etc/apt/sources.list.d/mysql.list
RUN echo "deb http://repo.mysql.com/apt/debian/ buster mysql-${MYSQL_MAJOR}" > /etc/apt/sources.list.d/mysql.list
# the "/var/lib/mysql" stuff here is because the mysql-server postinst doesn't have an explicit way to disable the mysql_install_db codepath besides having a database already "configured" (ie, stuff in /var/lib/mysql/mysql)
# also, we set debconf keys to make APT a little quieter
RUN { \
echo mysql-community-server mysql-community-server/data-dir select ''; \
echo mysql-community-server mysql-community-server/root-pass password ''; \
echo mysql-community-server mysql-community-server/re-root-pass password ''; \
echo mysql-community-server mysql-community-server/remove-test-db select false; \
} | debconf-set-selections \
&& apt-get update && apt-get install -y mysql-server="${MYSQL_VERSION}" && rm -rf /var/lib/apt/lists/* \
&& rm -rf /var/lib/mysql && mkdir -p /var/lib/mysql /var/run/mysqld \
&& chown -R mysql:mysql /var/lib/mysql /var/run/mysqld \
# ensure that /var/run/mysqld (used for socket and lock files) is writable regardless of the UID our mysqld instance ends up having at runtime
&& chmod 1777 /var/run/mysqld /var/lib/mysql \
# comment out a few problematic configuration values
&& find /etc/mysql/ -name '*.cnf' -print0 \
| xargs -0 grep -lZE '^(bind-address|log)' \
| xargs -rt -0 sed -Ei 's/^(bind-address|log)/#&/' \
# don't reverse lookup hostnames, they are usually another container
&& echo '[mysqld]\nskip-host-cache\nskip-name-resolve' > /etc/mysql/conf.d/docker.cnf
#挂载数据卷到宿主机,而不是在容器内部,防止重启容器数据丢失或容器主键增大
VOLUME /var/lib/mysql
#将docker-entrypoint.sh复制到容器的/usr/local/bin/下
COPY docker-entrypoint.sh /usr/local/bin/
#为usr/local/bin/docker-entrypoint.sh建立一个名为/entrypoint.sh的软连接
RUN ln -s usr/local/bin/docker-entrypoint.sh /entrypoint.sh # backwards compat
#指令用于设定容器启动时第一个运行的命令及其参数、具体命令在docker-entrypoint.sh中
ENTRYPOINT ["docker-entrypoint.sh"]
#声明端口3306(经典MySQL协议),33060(X协议)
EXPOSE 3306 33060
#运行
CMD ["mysqld"]
2、逐步分析每个涉及到的命令
2.1、FROM
基础镜像,构建的依赖基于该镜像。
2.2、RUN
执行命令行命令。
shell格式:
RUN [shell 命令]
例: yum install nginx
exec格式:
RUN ["执行文件", "参数1", "参数2"]
例:RUN ["./startup.sh" "dev" "standalone" ]
注意:RUN方法每执行一次,会创建一层镜像,会导致镜像膨胀。可以使用“&&”分隔,如下所示:
RUN yum install wget \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \
&& tar -xvf redis.tar.gz
2.3、ENV
设置环境变量,在后续步骤中可以使用。
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
例:
ENV NODE_VERSION 7.2.0
RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \
&& curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc"
2.4、VOLUME
匿名数据券,如果启动时忘记或未指定数据券,会自动挂载到匿名数据券。
优点:
a)防止因容器重启而导致的数据丢失
b)防止容器空间不断膨胀
VOLUME ["<路径1>", "<路径2>"...]
VOLUME <路径>
例:
VOLUME /var/lib/mysql
启动时可以进行覆盖挂载数据券:
#使用了 mydata 这个命名卷挂载到了 /data 这个位置,替代了 Dockerfile 中定义的匿名卷的挂载配置
docker run -d -v mydata:/data xxxx
2.5、COPY
从宿主机的路径中复制文件到容器中。目标路径是容器中的路径,会自动创建。
COPY [--chown=<user>:<group>] <源路径1>... <目标路径>
COPY [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]
例:
COPY docker-entrypoint.sh /usr/local/bin/
#镜像内的jdk属于jenkins:jenkins,否则默认是root:root
COPY --chown=jenkins:jenkins JDK/jdk /home/jenkins/jdk
ADD命令
ADD基本能做COPY所做的所有事情(建议使用COPY),除了不能用在 multistage 的场景下。
优点:在执行 <源文件> 为 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,会自动复制并解压到 <目标路径>。
缺点:在不解压的前提下,无法复制 tar 压缩文件。会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。具体是否使用,可以根据是否需要自动解压来决定。
2.6、ENTRYPOINT
类似于 CMD 指令,但其不会被 docker run 的命令行参数指定的指令所覆盖,而且这些命令行参数会被当作参数送给 ENTRYPOINT 指令指定的程序。
但是, 如果运行 docker run 时使用了 --entrypoint 选项,此选项的参数可当作要运行的程序覆盖 ENTRYPOINT 指令指定的程序。
优点:在执行 docker run 的时候可以指定 ENTRYPOINT 运行所需的参数。
注意:如果 Dockerfile 中如果存在多个 ENTRYPOINT 指令,仅最后一个生效。
格式:
ENTRYPOINT ["<executeable>","<param1>","<param2>",...]
可以搭配 CMD 命令使用:一般是变参才会使用 CMD ,这里的 CMD 等于是在给 ENTRYPOINT 传参,以下示例会提到。
示例:
假设已通过 Dockerfile 构建了 nginx:test 镜像:
FROM nginx
ENTRYPOINT ["nginx", "-c"] # 定参
CMD ["/etc/nginx/nginx.conf"] # 变参
1、不传参运行
$ docker run nginx:test
容器内会默认运行以下命令,启动主进程。
nginx -c /etc/nginx/nginx.conf
2、传参运行
$ docker run nginx:test -c /etc/nginx/new.conf
容器内会默认运行以下命令,启动主进程(/etc/nginx/new.conf:假设容器内已有此文件)
nginx -c /etc/nginx/new.conf
2.7、EXPOSE
仅仅只是声明端口。
作用:
帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射。
在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。
格式:
EXPOSE <端口1> [<端口2>...]
例:
#声明端口3306(经典MySQL协议),33060(X协议)
EXPOSE 3306 33060
2.8、CMD
类似于 RUN 指令,用于运行程序,但二者运行的时间点不同:
CMD 在docker run 时运行。
RUN 是在 docker build。
作用:为启动的容器指定默认要运行的程序,程序运行结束,容器也就结束。CMD 指令指定的程序可被 docker run 命令行参数中指定要运行的程序所覆盖。
注意:如果 Dockerfile 中如果存在多个 CMD 指令,仅最后一个生效。
用法:
CMD <shell 命令>
CMD ["<可执行文件或命令>","<param1>","<param2>",...]
CMD ["<param1>","<param2>",...] # 该写法是为 ENTRYPOINT 指令指定的程序提供默认参数
3、Build 上下文的概念

如上图所示:Docker 在运行时分为 Docker 引擎(也就是服务端守护进程)和客户端工具。Docker 的引擎提供了一组 REST API,被称为 DockerRemote API,而如 docker 命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。因此,虽然表面上我们好像是在本机执行各种 docker 功能,但实际上,一切都是使用的远程调用形式在服务端(Docker 引擎)完成。也因为这种 C/S 设计,让我们操作远程服务器的 Docker 引擎变得轻而易举。
当我们进行镜像构建的时候,并非所有定制都会通过 RUN 指令完成,经常会需要将一些本地文件复制进镜像,比如通过 COPY 指令、 ADD 指令等。而 docker build 命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker 引擎中构建的。那么在这种客户端/服务端的架构中,如何才能让服务端获得本地文件呢?
这就引入了上下文的概念。当构建的时候,用户会指定构建镜像上下文的路径, docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。
4、Dockerfile镜像构建
以下面的构建nginx举例:
docker build -t nginx:v4 .
上述命令中的这个 “.” ,实际上是在指定上下文的目录, docker build 命令会将该目录下的内容打包交给 Docker 引擎以帮助构建镜像。
写一个最简单的Dockerfile:
进入opt文件夹,创建一个Dockerfile
cd /opt
vi Dockerfile
Dockerfile:
FROM centos:7
RUN yum -y install epel-release \
&&yum -y install nginx
EXPOSE 80
构建成功后主要执行信息:
Step 1/3 : FROM centos:7
---> 7e6257c9f8d8
Step 2/3 : RUN yum -y install epel-release &&yum -y install nginx
...
Complete!
Removing intermediate container 180f2e55c821
---> 5c0e3cb8a914
Step 3/3 : EXPOSE 80
---> Running in 6996b4d9150f
Removing intermediate container 6996b4d9150f
---> 116f7ab71d60
Successfully built 116f7ab71d60
Successfully tagged jiang/nginx:v4
查看镜像:

参考文章:
https://www.runoob.com/docker/docker-dockerfile.html
https://www.cnblogs.com/lfxiao/p/9594185.html
网友评论