专业编程基础技术教程

网站首页 > 基础教程 正文

Dockerfile完全指南(dockerfile go)

ccvgpt 2024-07-17 17:50:23 基础教程 4 ℃

一、前言


Dockerfile完全指南(dockerfile go)


Dockerfile是一个文本文档,它包含用户可以在命令行上调用的所有指令,每一条指令构建一层镜像。在日常开发中我们常常需要自己编写Dockerfile来构建镜像,而构建一个精巧、实用且高品质的镜像对运行环境来说尤为重要。下面我们来排一排如何构建这样的镜像。


二、我们的目标


构建一个精巧、实用且高品质的镜像需要达成的目标:

  • 更快的构建镜像速度
  • 更小的镜像大小
  • 更少的镜像层
  • 合理使用镜像缓存
  • 增加Dockerfile可读性
  • 让Docker容器使用起来更简单



三、构建技巧

3.1 基础镜像的选择 (FROM)

任何一个docker镜像都会依赖一个基础镜像,也就是Dockerfile的第一个FROM命令,这是docker镜像的基础。这个基础镜像的选择,在很大程度上会决定一个docker镜像的起点,所以选择一个好的基础镜像还是十分重要的。通常我们选择基础镜像的时候,会遵循以下几点:

  • 官方镜像优于非官方的镜像,如果没有官方镜像,则尽量选Dockerfile开源的
  • 要选择固定版本tag而不是每次都使用latest,选择最新的版本有风险
  • 尽量选择体积小且适合当前需求的镜像(同一个版本,不同的TAG,其镜像大小也不一样)


3.2 镜像的大小和分层


每一行的RUN命令都会产生一层image layer, 若是Dockerfile存在很多RUN,就会导致镜像的臃肿。如下面的Dockerfile有4个RUN,就会有4层。

FROM xiaohezi/centos7:jdk1.8
ENV VERSION=6.5.4
MAINTAINER xiaohezi
COPY ./elasticsearch-${VERSION}.tar.gz /usr/local
RUN cd /usr/local && tar zxvf elasticsearch-${VERSION}.tar.gz && rm -f elasticsearch-${VERSION}.tar.gz
RUN mkdir -p  /data/elasticsearch/data && mkdir /data/elasticsearch/log
ADD ./elasticsearch.yml /usr/local/elasticsearch-${VERSION}/config/elasticsearch.yml
RUN useradd es
RUN chown -R es:es /usr/local/elasticsearch-${VERSION}/ && chown -R es:es /data
USER es
#WORKDIR指令用于指定容器的一个目录, 容器启动时执行的命令会在该目录下执行
WORKDIR /usr/local/elasticsearch-${VERSION}
#目录就会在运行时自动挂载为匿名卷,任何向/data/elasticsearch/data中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化
VOLUME ["/data/elasticsearch/data","/data/elasticsearch/log"]
#CMD用于设置默认执行的命令
CMD ["/usr/local/elasticsearch-6.5.4/bin/elasticsearch"]
EXPOSE 9200 9300

有一首歌不知道大家听过没,“如果你愿意一层一层剥开我的心 你会鼻酸 你会流泪”,这是歌曲《洋葱》中的歌词,镜像就好比是洋葱,都有很多层,每一条 Dockerfile 指令都会提交为一个镜像层,下一条指令都是基于上一条指令构建的。而将RUN指令合并的话,有助于减少docker的层级。但是并不是所有的RUN指令都需要合并在一起,通常情况下我们把变化频率相同的指令合并在一起,变化频率不一样的还是分开放,因为如果频率不一样,会造成很多无用的变更和构建,得不偿失。

如下改进后的Dockerfile只有一个RUN:

FROM pkulaw/centos7:jdk1.8
ENV VERSION=6.5.4
MAINTAINER xiaohezi
COPY ./elasticsearch-${VERSION}.tar.gz /usr/local
RUN cd /usr/local && tar zxvf elasticsearch-${VERSION}.tar.gz && \
    rm -f elasticsearch-${VERSION}.tar.gz && \
    mkdir -p  /data/elasticsearch/data && \
    mkdir -p /data/elasticsearch/log && \
    useradd es && \
    chown -R es:es /usr/local/elasticsearch-${VERSION}/ && \
    chown -R es:es /data 
ADD ./elasticsearch.yml /usr/local/elasticsearch-${VERSION}/config/elasticsearch.yml
USER es
#WORKDIR指令用于指定容器的一个目录, 容器启动时执行的命令会在该目录下执行
WORKDIR /usr/local/elasticsearch-${VERSION}
#目录就会在运行时自动挂载为匿名卷,任何向/data/elasticsearch/data中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化
VOLUME ["/data/elasticsearch/data","/data/elasticsearch/log"]
#CMD用于设置默认执行的命令
CMD ["/usr/local/elasticsearch-6.5.4/bin/elasticsearch"]
EXPOSE 9200 9300


3.3 文件复制和目录操作 (ADD,COPY)

COPY 和 ADD都可以把文件复制到镜像中,但是两个命令的效果却不一样。COPY 和 ADD 都可以把local的文件复制到镜像里,如果目标目录不存在,还会自动创建。ADD 比 COPY高级一点的地方就是,如果复制的是一个压缩文件时,ADD会自动去解压缩文件。若复制的是一个普通文件用COPY即可,若是需要自动解压缩就用ADD。


3.4 构建参数和环境变量 (ARG vs ENV)

ARG 和 ENV 是经常容易被混淆的两个Dockerfile的语法,都可以用来设置一个“变量”。但实际上两者有很多的不同。

1)ENV

如下实例:

FROM pkulaw/centos7:jdk1.8
ENV VERSION=6.5.4
MAINTAINER xiaohezi
COPY ./elasticsearch-${VERSION}.tar.gz /usr/local
RUN cd /usr/local && tar zxvf elasticsearch-${VERSION}.tar.gz && \
    rm -f elasticsearch-${VERSION}.tar.gz && \
    mkdir -p  /data/elasticsearch/data && \
    mkdir -p /data/elasticsearch/log && \
    useradd es && \
    chown -R es:es /usr/local/elasticsearch-${VERSION}/ && \
    chown -R es:es /data 
ADD ./elasticsearch.yml /usr/local/elasticsearch-${VERSION}/config/elasticsearch.yml
USER es
#WORKDIR指令用于指定容器的一个目录, 容器启动时执行的命令会在该目录下执行
WORKDIR /usr/local/elasticsearch-${VERSION}
#目录就会在运行时自动挂载为匿名卷,任何向/data/elasticsearch/data中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化
VOLUME ["/data/elasticsearch/data","/data/elasticsearch/log"]
#CMD用于设置默认执行的命令
CMD ["/usr/local/elasticsearch-6.5.4/bin/elasticsearch"]
EXPOSE 9200 9300

通过ENV定义的环境变量,会永久的保存在该镜像创建的任何容器中。

2)ARG

如下实例:

FROM pkulaw/centos7:jdk1.8
ARG VERSION=6.5.4
MAINTAINER xiaohezi
COPY ./elasticsearch-${VERSION}.tar.gz /usr/local
RUN cd /usr/local && tar zxvf elasticsearch-${VERSION}.tar.gz && \
    rm -f elasticsearch-${VERSION}.tar.gz && \
    mkdir -p  /data/elasticsearch/data && \
    mkdir -p /data/elasticsearch/log && \
    useradd es && \
    chown -R es:es /usr/local/elasticsearch-${VERSION}/ && \
    chown -R es:es /data 
ADD ./elasticsearch.yml /usr/local/elasticsearch-${VERSION}/config/elasticsearch.yml
USER es
#WORKDIR指令用于指定容器的一个目录, 容器启动时执行的命令会在该目录下执行
WORKDIR /usr/local/elasticsearch-${VERSION}
#目录就会在运行时自动挂载为匿名卷,任何向/data/elasticsearch/data中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化
VOLUME ["/data/elasticsearch/data","/data/elasticsearch/log"]
#CMD用于设置默认执行的命令
CMD ["/usr/local/elasticsearch-6.5.4/bin/elasticsearch"]
EXPOSE 9200 9300

ARG 定义的变量只会存在于镜像构建过程,启动容器后并不保留这些变量。ENV和ARG区别是:ARG 可以在镜像build的时候通过添加 --build-arg来动态修改value。


3.5 CMD 和ENTRYPOINT命令

1、CMD命令

官网CMD命令介绍

在官网中有这么一句话,原文如下:

The main purpose of a CMD is to provide defaults for an executing container. These defaults can include an executable, or they can omit the executable, in which case you must specify an ENTRYPOINT instruction as well.

翻译如下:cmd给出的是一个容器的默认的可执行体。也就是容器启动以后,默认的执行的命令。这些默认值可以包含可执行文件,也可以省略可执行文件,在这种情况下,您还必须指定一条ENTRYPOINT 指令。

重点就是这个“默认”。这意味着,如果docker run既没有指定任何的执行命令且dockerfile里面也没有entrypoint命令,这个时候,就会使用这个默认的CMD指定的默认的执行命令执行。从这里我们可以看出CMD的角色定位是默认的容器启动执行命令。而entrypoint,它才是真正的容器启动以后要执行命令。还有个说法是“CMD会被覆盖”,其实为什么会覆盖?因为CMD的角色定位就是默认,如果你不额外指定,那么就执行CMD的命令,否则呢?只要你指定了,那么就不会执行CMD,也就是CMD会被覆盖。

1)CMD的三种用法

CMD指令有三种形式:
CMD ["executable","param1","param2"] (执行形式,这是首选形式)
CMD ["param1","param2"] (作为ENTRYPOINT命令的默认参数)
CMD command param1 param2 (shell形式)
  • 用法1带有中括号的形式,表示exec形式,官网首推这种形式。这时,命令没有再任何shell终端环境下,如果我们要执行shell,必须把shell加入到中括号的参数中。这种用法就像一个c语言的exec函数,意思是我们要执行一个进程。如果采用非shell的方法,就如下实例:
FROM nginx:stable-alpine
COPY default.conf /etc/nginx/conf.d/default.conf
COPY dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

注:如果采用中括号形式,那么第一个参数必须是命令的全路径才行。而且,一个dockerfile至多只能有一个cmd,如果有多个,只有最后一个生效。

  • 用法2:就是执行命令带参数,就是只有参数,而这些参数将作为ENTRYPOINT命令的默认参数,当以第二种方式使用的时候,Dockerfile文件中必须包含一条ENTRYPOINT命令,两者联合使用。
  • 用法3:shell form,即没有中括号的形式。那么命令command默认是在“/bin/sh -c”下执行的。比如下面的Dockerfile:
FROM centos
CMD echo "hello xiaohezi!"

2)CMD总结

  • CMD可以用来设置容器启动时默认会执行的命令
  • 如果docker container run启动容器时指定了其它命令,则CMD命令会被忽略
  • 如果定义了多个CMD,只有最后一个会被执行。

2、ENTRYPOINT命令

官网ENTRYPOINT介绍

在官网中有这么一句话:

An ENTRYPOINT allows you to configure a container that will run as an executable.

翻译如下:entrypoint才是正统地用于定义容器启动以后的执行体的,其实我们从名字也可以理解,这个是容器的“入口”。

1)ENTRYPOINT的两种用法

ENTRYPOINT 有两种形式:
ENTRYPOINT ["executable", "param1", "param2"](执行形式,首选)
ENTRYPOINT command param1 param2(shell形式)
  • 用法1:命令行模式(官网推荐模式)也就是带中括号的。即使在docker run中有指定命令的情况下,它依然会被执行,一般情况下不会被覆盖,除非我们在docker run命令中指定--entrypoint参数,这个命令才会被覆盖。而且run命令后面的东西,全部都会作为entrypoint的参数,如果run后面没有额外的东西,但是有cmd,那么cmd的全部内容会作为entrypoint的参数(这种情况就是ENTRYPOINT 和 CMD 联合使用,也是CMD的用法2)。切记每个 Dockerfile 中只能有一个 ENTRYPOINT,当指定多个时,只有最后一个起效。

如下实例:

FROM openjdk:8u292-oraclelinux8
EXPOSE 9999
ADD test.jar /deployments/test.jar
RUN bash -c 'touch /deployments/test.jar'
ENTRYPOINT ["java","-jar","/deployments/test.jar","--spring.config.location=/deployments/config/application-prod.yml"]
  • 法2:shell模式,在这种模式下,任何RUN和CMD的参数都无法被传入到ENTRYPOINT里。

2)ENTRYPOINT总结

ENTRYPOINT 也可以设置容器启动时要执行的命令,但是和CMD是有区别的。CMD 设置的命令,可以在docker container run 时传入其它命令,覆盖掉 CMD 的命令,但是 ENTRYPOINT 所设置的命令是一定会被执行的。ENTRYPOINT 和 CMD 可以联合使用,ENTRYPOINT 设置执行的命令,CMD传递参数

3、综合推荐用法

一般还是会用entrypoint的中括号形式作为docker 容器启动以后的默认执行命令,里面放的是不变的部分,可变部分比如命令参数可以使用cmd的形式提供默认版本,也就是run里面没有任何参数时使用的默认参数。如果我们想用默认参数,就直接run,否则想用其他参数,就run里面加参数。


3.6 合理使用缓存

Docker 构建过程中,每一条 Dockerfile 指令都会提交为一个镜像层,下一条指令都是基于上一条指令构建的。如果构建时发现要构建的镜像层的父镜像层已经存在,并且下一条命令使用了相同的指令,即可命中构建缓存。因此,基于 Docker 构建时的缓存特性,我们可以把不轻易改变的指令放到 Dockerfile 前面(例如安装软件包),而可能经常发生改变的指令放在 Dockerfile 末尾(例如编译应用程序)。这样前面的一些命令就可以命中构建缓存,即使用CACHED,提高构建速度。


3.7 合理使用 .dockerignore

什么是Docker build context?

Docker是client-server架构,理论上Client和Server可以不在一台机器上。在构建docker镜像的时候,需要把所需要的文件由CLI(client)发给Server,这些文件实际上就是build context。构建的时候,第一行输出就是发送build context。. 这个点参数就是代表了build context所指向的目录。有了.dockerignore文件后,我们可以把一些不需要的文件或目录给忽略掉。

*/temp*
*/*/temp*
.git
.idea
logs
*.iml
!test.iml 

注:上面代码表示,这几个路径要排除,不要打包进入 image 文件。当然了若是没有要排除的,此文件可以不新建。

参数解析:

*/temp*        #排除根目录的任何直接子目录中以temp开头的文件和目录。例如,普通文件/somedir/temporary.txt被排除在外,目录/somedir/temp也是如此。
*/*/temp*      #将以temp开头的文件和目录从比根级低两级的任何子目录中排除。例如,/somedir/subdir/temporary.txt被排除在外。
temp?          #排除根目录中名称为一个字符扩展名的文件和目录temp。例如,/tempa和/tempb被排除在外。
.git           #排除.git文件
.idea          #排除.idea文件
logs           #排除logs文件
*.iml          #排除以.iml文件,不包含test.iml文件
!test.iml      #排除不包含test.iml文件

如需更多用法,请查看官网:

https://docs.docker.com/engine/reference/builder/#dockerignore-file


四、编写Dockerfile


在日常开中中我们编写最多的就是应用服务的Dockerfile文件了,如下实例:

FROM openjdk:8u292-oraclelinux8
EXPOSE 8702
#拷贝字体文件
COPY sim /usr/share/fonts/
#设置字符集
ENV LANG en_US.UTF-8
#创建字体文件的缓存信息,完成字体配置
RUN fc-cache
ADD search.jar /deployments/search.jar
RUN bash -c 'touch /deployments/search.jar'
ENTRYPOINT ["java","-jar","/deployments/search.jar","--spring.config.location=/deployments/config/application-prod.yml"]

构建:

docker build -f Dockerfile -t search:1.0.0 .

编写docker-compose-search.yml文件:

version: '3.7'

services:
  search:
    image: search:1.0.0
    container_name: search
    restart: always
    environment:
      TZ: Asia/Shanghai
    volumes:
      - /data/back-end/case/search/config:/deployments/config
      - /data/back-end/case/search/logs:/logs
    ports:
      - "8702:8702"
    networks:
      - pk_net
networks:
  pk_net:
    external: true

创建docker自定义网络:

sudo docker network create --driver bridge --subnet 10.139.0.0/16 --gateway 10.139.0.1 pk_net
##预先创建一个自定义的网络pkulaw_net,此处的10.139可以自定义,不冲突即可

创建容器并启动:

docker-compose -p search -f docker-compose-search.yml up -d


作者:键客小盒子

来源:微信公众号:架构至美

出处:https://mp.weixin.qq.com/s/q9UsjuUo8M2yiyC5BSHCiQ

Tags:

最近发表
标签列表