使用Dockerfile构建自己的镜像

Dockerfile是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。使用docker build命令可以根据Dockerfile里面的指令编排来打包定制我们自己的docker镜像,首先我们来看一个通用的例子,制作自己的nginx镜像。

1.构建nginx镜像

# Base image
FROM centos:7

# MAINTAINER
MAINTAINER cbmiao <miaocbin@126.com>

# 将nginx以及pcre源代码加入镜像
ADD nginx-1.20.1.tar.gz /usr/local/src/
ADD pcre-8.45.tar.gz /usr/local/src/

# 安装编译器
RUN yum install -y gcc gcc-c++ make openssl-devel lsof
RUN useradd -s /sbin/nologin -M nginx

# 指定工作目录
WORKDIR /usr/local/src/nginx-1.20.1/

# 编译nginx
RUN ./configure --prefix=/usr/local/nginx --user=nginx --group=nginx --with-http_ssl_module --with-http_stub_status_module --with-pcre=/usr/local/src/pcre-8.45 && make && make install
RUN echo "daemon off;" >> /usr/local/nginx/conf/nginx.conf

# 设置环境变量
ENV PATH /usr/local/nginx/sbin:$PATH

# 暴露80端口
EXPOSE 80

# 容器默认启动命令
ENTRYPOINT ["nginx"]

以上是通过Dockerfile来定制我们自己的nginx的Dockerfile,包含了如下指令:

2. Dockerfile基本格式

2.1 FROM指令

FROM指令用于指定基础镜像,必须为Dockerfile的第一个指令

# 格式:
# FROM <image>
# FROM <image>:<tag>
# 示例:
FROM mysql:5.7
# 注意:
# tag是可选的,如果不使用tag,则默认会使用latest版本的基础镜像

参数解释:

  • FROM mysql:5.7:第一行必须指定 基础镜像信息

2.2 MAINTAINER 指令

用于说明镜像维护者的信息,名字,邮箱,联系方式,实例如下

# 格式:
# 	MAINTAINER <name>
# 示例:
	MAINTAINER Michael Miu
    MAINTAINER miaocbin@126.com
    MAINTAINER Michael Miu <miaocbin@126.com>

2.3 COPY|ADD指令

这两个命令的用法类似,都可以用于添加本地文件到镜像中(同样需求下,官方推荐使用 COPY),功能也类似,如:

  • 源路径可以有多个,相对于执行build的路径,如果源路径是一个目录,则该目录下面的所有内容都会被加入到容器,但目录本身不会被加入到容器;
  • 目标路径:必须是绝对路径或者相对于WORKDIR的相对路径;若目标路径不存在,则会自动创建完整路径;目标路径如果是文件夹,则必须已/结尾;
  • 路径中可以使用通配符

二者区别如下:

  • ADD 的优点:在执行 <源文件> 为 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,会自动复制并解压到 <目标路径>。
  • ADD 的缺点:在不解压的前提下,无法复制 tar 压缩文件。会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。具体是否使用,可以根据是否需要自动解压来决定。
  • COPY:指令能够将构建命令所在的主机本地的文件或目录,复制到镜像文件系统。
  • ADD:指令不仅能够将构建命令所在的主机本地的文件或目录,而且能够将远程URL所对应的文件或目录,作为资源复制到镜像文件系统。
    所以,可以认为ADD是增强版的COPY,支持将远程URL的资源加入到镜像的文件系统。
# 格式:
# 	ADD|COPY <src>... <dest>
# 示例:
    ADD|COPY tes* /mydir/    # 添加所有以"tes"开头的文件
    ADD|COPY test mydir/     # 添加 "test" 到 WORKDIR/mydir/
    ADD|COPY test /opt/      # 添加 "test" 到 /opt/
# [--chown=<user>:<group>]:可选参数,用户改变复制到容器内文件的拥有者和属组。

说明:

  1. 对于从远程URL获取资源的情况,由于ADD指令不支持认证,如果从远程获取资源需要认证,则只能使用RUN wget或RUN curl替代。
  2. 如果源路径的资源发生变化,则该ADD指令将使Docker Cache失效,Dockerfile中后续的所有指令都不能使用缓存。因此尽量将ADD指令放在Dockerfile的后面。
  3. <源路径>:源文件或者源目录,这里可以是通配符表达式,其通配符规则要满足 Go 的 filepath.Match 规则。
  4. <目标路径>:容器内的指定路径,该路径不用事先建好,路径不存在的话,会自动创建。

2.4 WORKDIR

指定工作目录,通过WORKDIR设置工作目录后,Dockerfile中其后的命令RUN、CMD、ENTRYPOINT、ADD、COPY等命令都会在该目录下执行。(WORKDIR 指定的工作目录,必须是提前创建好的)

# 格式:
# 	WORKDIR /path/to/workdir
# 示例:
    WORKDIR /abc  (这时工作目录为/abc)

2.5 RUN

构建镜像过程中执行命令(注意:RUN指令创建的中间镜像会被缓存,并会在下次构建中使用。如果不想使用这些缓存镜像,可以在构建时指定--no-cache参数,如:docker build --no-cache)

# 格式:
# 	RUN <command>
# 示例:
    RUN yum -y install nginx
    RUN pip install djaong
    RUN mkdir test && rm -rf /var/lib/testfile

2.6 CMD

类似于RUN指令,用于运行程序,但二者运行的时间点不同:

  • CMD:用于指定在容器启动时所要执行的命令,在docker run 时运行。
  • RUN:RUN用于指定镜像构建时所要执行的命令,是在 docker build时运行。

作用:为启动的容器指定默认要运行的程序,也就是在容器启动时才进行调用。程序运行结束,容器也就结束。CMD 指令指定的程序可被 docker run 命令行参数中指定要运行的程序所覆盖。

# 格式:
#     CMD ["executable","param1","param2"] (执行可执行文件,优先)
#     CMD ["param1","param2"] (设置了ENTRYPOINT,则直接调用ENTRYPOINT添加参数)
#     CMD command param1 param2 (执行shell内部命令)
# 示例:
    CMD ["/usr/bin/wc","--help"]
    CMD ping www.baidu.com

注意:如果 Dockerfile 中如果存在多个 CMD 指令,仅最后一个生效。

2.7 ENTRYPOINT

类似于 CMD 指令,但其不会被 docker run 的命令行参数指定的指令所覆盖,而且这些命令行参数会被当作参数送给 ENTRYPOINT 指令指定的程序。但是, 如果运行 docker run 时使用了 --entrypoint 选项,将覆盖 CMD 指令指定的程序。

# 格式:
#     ENTRYPOINT ["executable", "param1", "param2"] (可执行文件, 优先)
#     ENTRYPOINT command param1 param2 (shell内部命令)
# 示例:
    ENTRYPOINT ["/usr/bin/wc","--help"]

注意:ENTRYPOINT与CMD非常类似,不同的是通过docker run执行的命令不会覆盖ENTRYPOINT,而docker run命令中指定的任何参数,都会被当做参数再次传递给ENTRYPOINT。

优点:在执行 docker run 的时候可以指定 ENTRYPOINT 运行所需的参数。

注意:Dockerfile中只允许有一个ENTRYPOINT命令,多指定时会覆盖前面的设置,而只执行最后的ENTRYPOINT指令。

2.8 ENV

设置环境变量,定义了环境变量,那么在后续的指令中,就可以使用这个环境变量。

# 格式:
#     ENV <key> <value>
#     ENV <key>=<value>
# 示例:
    ENV myName John
    ENV myCat=tomcat

2.9 EXPOSE指令

声明端口,作用如下:

  • 1)帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;
  • 2)在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口;但是,EXPOSE并不会让容器的端口访问到主机。要使其可访问,需要在docker run运行容器时通过-p来发布这些端口,或通过-P参数来发布EXPOSE导出的所有端口。
  • 3)docker ps的时候看到的PORTS字段信息就是这里声明的信息;
  • 4)需要说明的是,即便这里不写EXPOSE指令,也不影响镜像构建和运行,只是会导致其他的使用者不清楚镜像监听的端口,使用的时候映射端口没有那么直观。
# 格式:
#     EXPOSE <port> [<port>...]
# 示例:
    EXPOSE 80 443
    EXPOSE 8080
    EXPOSE 11211/tcp 11211/udp

2.10 LABEL指令

用来给镜像添加一些元数据(metadata),以键值对的形式

# 语法格式:
# LABEL <key>=<value> <key>=<value> <key>=<value> ...
# 例如:添加镜像作者
LABEL org.opencontainers.image.authors="runoob"

2.11 VOLUME指令

定义匿名数据卷。在启动容器时忘记挂载数据卷,会自动挂载到匿名卷。作用:

  • 避免重要的数据,因容器重启而丢失,这是非常致命的。
  • 避免容器不断变大。
# 格式:
# 	VOLUME ["<路径1>", "<路径2>"...]
# 	VOLUME <路径>
# 举例:
VOLUME [/var/lib/mysql]

3. Dockerfile镜像构建命令

利用Dockerfile构建自己的镜像命令

docker build . -t ImageName:ImageTag -f Dockerfile
  • . 是上下文路径
  • 上下文路径,是指 docker 在构建镜像,有时候想要使用到本机的文件(比如复制),docker build 命令得知这个路径后,会将路径下的所有内容打包。

解析:由于 docker 的运行模式是 C/S。我们本机是 C,docker 引擎是 S。实际的构建过程是在 docker 引擎下完成的,所以这个时候无法用到我们本机的文件。这就需要把我们本机的指定目录下的文件一起打包提供给 docker 引擎使用。

如果未说明最后一个参数,那么默认上下文路径就是 Dockerfile 所在的位置。

注意:上下文路径下不要放无用的文件,因为会一起打包发送给 docker 引擎,如果文件过多会造成过程缓慢。

3.1 执行构建过程

docker build . -t my-nginx:v1.20.1 -f Dockerfile

Sending build context to Docker daemon  3.163MB
Step 1/13 : FROM centos:7
 ---> 8652b9f0cb4c
Step 2/13 : LABEL maintainer="cbmiao <miaocbin@126.com>"
 ---> Using cache
 ---> 16c2487146bc
Step 3/13 : ENV DEBIAN_FRONTEND noninteractive
 ---> Using cache
 ---> 5f3132177398
Step 4/13 : ADD nginx-1.20.1.tar.gz /usr/local/src/
 ---> Using cache
 ---> 116344a28ea2
Step 5/13 : ADD pcre-8.45.tar.gz /usr/local/src
 ---> Using cache
 ---> 23451b87c29b
Step 6/13 : RUN yum install -y wget gcc gcc-c++ make openssl-devel
 ---> Using cache
 ---> dcaa1cbaf4bd
Step 7/13 : RUN useradd -s /sbin/nologin -M nginx
 ---> Using cache
 ---> e0ace7f20ffc
Step 8/13 : WORKDIR /usr/local/src/nginx-1.20.1/
 ---> Using cache
 ---> d437722febe2
Step 9/13 : RUN ./configure --prefix=/usr/local/nginx --user=nginx --group=nginx --with-http_ssl_module --with-http_stub_status_module --with-pcre=/usr/local/src/pcre-8.45 && make && make install
 ---> Using cache
 ---> 307b6228d110
Step 10/13 : RUN echo "daemon off;" >> /usr/local/nginx/conf/nginx.conf
 ---> Using cache
 ---> dc8e0e800987
Step 11/13 : ENV PATH /usr/local/nginx/sbin:$PATH
 ---> Using cache
 ---> e5a1119dced2
Step 12/13 : EXPOSE 80
 ---> Using cache
 ---> 63c669be20e4
Step 13/13 : ENTRYPOINT ["nginx"]
 ---> Using cache
 ---> 391f75b825bd
Successfully built 391f75b825bd
Successfully tagged my-nginx:v1.20.1

3.2 查看构建结果

docker image ls
REPOSITORY                TAG       IMAGE ID       CREATED          SIZE
my-nginx                  v1.20.1   391f75b825bd   17 minutes ago   490MB

3.3 运行镜像

# 利用我们刚构建的镜像来启动容器
docker run -d --name nginx-test my-nginx:v1.20.1
348a73f420181c36abf7a99c958ed76862503e930793331ab819629ea2bdc4a2

# 进入容器查看nginx状态
docker exec -ti nginx-test bash
[root@348a73f42018 nginx-1.20.1]#

# 确认80端口
[root@348a73f42018 nginx-1.20.1]# lsof -i :80
COMMAND PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
nginx     1 root    6u  IPv4 645923      0t0  TCP *:http (LISTEN)
# 确认nginx进程已经启动成功
[root@348a73f42018 nginx-1.20.1]# ps aux
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root          1  0.1  0.0  46096  3280 ?        Ss   05:43   0:00 nginx: master process nginx
nginx         7  0.0  0.0  46532  1880 ?        S    05:43   0:00 nginx: worker process
root          8  0.2  0.0  11828  1908 pts/0    Ss   05:43   0:00 bash
root         26  0.0  0.0  51732  1712 pts/0    R+   05:43   0:00 ps aux
# 访问nginx也正常
[root@348a73f42018 nginx-1.20.1]# curl localhost 
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
         35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

若想让我们的nginx向外提供服务,还需要在启动容器时,将端口映射到主机,然后我们通过主机IP加上映射端口,就可以访问了

docker run -d --name nginx-test1 -p 80:80 my-nginx:v1.20.1

以上就添加了一个[-p 80:80]参数,将容器内的nginx的80端口,映射到宿主机。然后我们就可以通过宿主机的IP加上80端口访问刚才启动的nginx容器里的nginx服务,有关docker启动容器有不清楚的,可以看我的另外一篇博客:Docker简介及基本使用

4. Dockerfile构建镜像的原则

  • 不必要的内容不要放在镜像中:以免增大镜像,同时也带来安全隐患;

  • 减少不必要的层文件

  • 减少网络传输操作:尽量使用本地文件,加快镜像构建速度,特别是后面我们将利用Jenkins做自动化构建的时候;

  • 可以适当的包含一些调试命令

5. JAR包制作成docker镜像实例

FROM java:8u211

ENV JAVA_OPTS "
-Xmx4096m 
-XX:MetaspaceSize=256m 
-XX:MaxMetaspaceSize=256m"
ENV JAVA_HOME /usr/local/java
ENV PATH ${PATH}:${JAVA_HOME}/bin

COPY target/myweb.jar myweb.jar

RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' >/etc/timezone

EXPOSE 8080
CMD java ${JAVA_OPTS} -jar myweb.jar

人们永远没有足够的时间把它做好,但永远有足够的时间重新来过。 可是,因为并不是总有机会重做一遍,你必须做得更好,换句话说, 人们永远没有足够的时间去考虑到底是不是想要它,但永远有足够的时间去为之后悔。 ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ 浅掘千口井,不如深挖一口井!当知识支撑不了野心时,那就静下心来学习吧!运维技术交流QQ群:618354452

个人微信公众号,定期发布技术文章和运维感悟。欢迎大家关注交流。

原文地址:https://www.cnblogs.com/miaocbin/p/15245836.html