Dockerfile最佳实践
一个容器对应一个进程
一个Docker容器应该只对应一个进程,也就是一个Docker 镜像一般只包含一个应用的制品包(比如.jar)。

在需要组合多个进程的场景,使用容器组(比如Docker Compose,或Kubernetes Pod)。

选用合适的基础镜像

  • 选用基础镜像的原则:
  • 使用官方提供的基础镜像(official)
  • 基础镜像应该提供足够的支持,使得Dockerfile尽量简单(easy enough)
  • 基础镜像要足够精简,尽量不要包含不需要的内容(simple enough)
  • 使用指定标签(版本)的基础镜像,不使用latest标签的基础镜像 (explicit)

把最少改动的步骤放在最前面
把最少改动的步骤放在最前面,也就是准备应用的COPY步骤要放在安装工具和准备环境的RUN命令之后,能够重用前面构建的层的cache,防止每次都要重复构建。Dockefile中步骤一般为:

选择基础镜像,比如:
FROM openjdk:8-jdk-stretch

安装Dockerfile后面步骤需要用到的工具和docker execdebug时需要用到的工具,比如:
RUN apt-get update && apt-get upgrade -y && apt-get install -y git curl && rm -rf /var/lib/apt/lists/*
其他的一些准备环境步骤,比如:
RUN curl -fsSL ${JENKINS_URL} -o /usr/share/jenkins/jenkins.war \
&& echo "${JENKINS_SHA} /usr/share/jenkins/jenkins.war" | sha256sum -c -

准备应用,比如:
COPY jenkins.sh /usr/local/bin/jenkins.sh

声明程序的入口点,比如:
ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/jenkins.sh"]

暴露端口,比如:
EXPOSE 8080

Dockerfile的步骤中只有RUN、COPY和ADD才会创建层,其它指令的先后顺序主要是根据Dockerfile的语法、逻辑结构和习惯。

参考文档:

https://github.com/jenkinsci/docker/blob/master/Dockerfile
Docker构建上下文中不要包含不需要的文件
运行docker build [options] PATH构建Docker镜像时,Docker会将PATH路径下的全部内容作为构建上下文传给Docker Daemon。参见docker build日志中的第一句Sending build context to Docker daemon。

如果PATH路径下的内容太多,会导致镜像构建很慢,如果Dockerfile书写不当,还会引入不必要的文件,从而导致镜像变大,影响镜像构建和拉取速度。可以通过.dockerignore文件在Docker构建时不要将指定内容包含在构建上下文中。

.dockerignore例子:

# ignore .git and .cache folders
.git
.cache
# ignore all *.class files in all folders, including build root
**/*.class
# ignore all markdown files (md)
*.md

还可以将Dockerfile和制品包(比如.jar)放到一个干净的新目录下,再来构建镜像,参见:

https://github.com/cookcodeblog/gs-rest-service-maven/blob/master/docker-build.sh

https://github.com/cookcodeblog/gs-rest-service-maven/blob/master/Dockerfile

参考文档:

https://docs.docker.com/engine/reference/builder/#dockerignore-file
https://codefresh.io/docker-tutorial/not-ignore-dockerignore/
多阶段构建
Docker提供了多阶段构建(multistag-builds)的功能,但是一般在做多阶段构建时不需要这么复杂,只需要分成以下2步:

先构建出应用制品包,比如用Maven构建出应用的.jar包

在docker build构建Docker镜像时,将上一步生成的.jar包复制到镜像中

减少Docker镜像层的数量
Dokcerfile中的RUN、COPY和ADD命令才会创建镜像层,因此减少Docker镜像层的数量就是要减少这几个命令的次数,特别是RUN命令的次数。

在安装工具时可以在一句命令中安装多个工具:

正例:

apt-get install -y git curl

反例:

apt-get install -y git
apt-get install -y curl

用&&拼接多个命令:

正例:
RUN apt-get update && apt-get upgrade -y && apt-get install -y git curl

反例:
RUN apt-get update
RUN apt-get upgrade -y
RUN apt-get install -y git curl

用\来在拼接多个命令时换行,增加可读性

RUN curl -fsSL https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-static-$(dpkg --print-architecture) -o /sbin/tini \
&& curl -fsSL https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-static-$(dpkg --print-architecture).asc -o /sbin/tini.asc \
&& gpg --no-tty --import ${JENKINS_HOME}/tini_pub.gpg \
&& gpg --verify /sbin/tini.asc \
&& rm -rf /sbin/tini.asc /root/.gnupg \
&& chmod +x /sbin/tini

使用专门的user和group
如果不以root用户来运行应用,则可以使用专门的user和group。

以Jenkins为例,Jenkins镜像使用了jenkins(1000)/jenkins(1000)的user(uid)/group(gid):

ARG user=jenkins
ARG group=jenkins
ARG uid=1000
ARG gid=1000
ARG JENKINS_HOME=/var/jenkins_home

RUN mkdir -p $JENKINS_HOME \
&& chown ${uid}:${gid} $JENKINS_HOME \
&& groupadd -g ${gid} ${group} \
&& useradd -d "$JENKINS_HOME" -u ${uid} -g ${gid} -m -s /bin/bash ${user}

USER ${user}

查看Jenkins的user/group信息:

# 进入容器
docker exec -it <cotainer_id> /bin/bash

# 查看user / group信息
cat /etc/passwd | grep jenkins

# 每行7个字段,以:隔开
# 1. Username: jenkins
# 2. Password: x表示加密的密码
# 3. User ID(UID): 1000
# 4. Group ID(GID): 1000
# 5. User ID Info: User描述信息
# 6. Home directory: Home目录
# 7. Command/Shell: Command或Shell的绝对路径,比如/bin/bash,为/bin/false表示不允许执行bash
jenkins:x:1000:1000:Linux User,,,:/var/jenkins_home:/bin/bash

参考文档:

https://www.cyberciti.biz/faq/understanding-etcpasswd-file-format/
一次构建,多环境运行
在Dockerfile中使用ENV指令定义环境变量并可设置缺省值,通过docker run -e指定运行时环境变量。

容易混淆的Dockerfile的指令
ADD和COPY
推荐使用更为简单的COPY指令。

ADD命令虽然可以提供额外的下载和解压等功能,但是下载可以通过curl命令,解压可以通过tar -zxvf指令来操作。

VOLUME
在Dockerfile中VOLUME指令在docker run 时会在宿主机下的/var/lib/docker/volumes目录下新建<volume_id>的持久卷目录。如果是下次docker start时则会重用之前的持久卷。

# 列出持久卷
docker volume ls

# 查看某个容器的持久卷
docker inspect <container_id> | less
# 查找Mounts关键字

使用docker run -v可以在运行容器时指定持久卷目录,也可以使用已存在的目录。

CMD和ENTRYPOINT
CMD语法:CMD ["executable", "param1", "param2"…]

ENTRYPOINT语法:ENTRYPOINT ["executable", "param1", "param2"…]

Dockerfile中应该只包含一个CMD或一个ENTRYPOINT。

包含多个CMD时,只有最后一个CMD才会生效,并会让Dockerfile难懂。

同时包含CMD和ENTRYPOINT时,CMD中的参数其实是ENTRYPOINT的参数,让Dockerfile难懂。

ARG和ENV
ARG是构建时参数,通过docker build --build-arg arg=value指定。

ENV是运行时参数,通过docker run -e var=value指定。

在Dockerfile中也可以用ARG来给ENV赋值,例如:

ARG JENKINS_HOME=/var/jenkins_home
ENV JENKINS_HOME $JENKINS_HOME

ARG JENKINS_VERSION
ENV JENKINS_VERSION ${JENKINS_VERSION:-2.121.1}

参考文档
https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
https://takacsmark.com/dockerfile-tutorial-by-example-dockerfile-best-practices-2018/
https://spring.io/guides/gs/spring-boot-docker/

转载:https://blog.csdn.net/nklinsirui/article/details/96113636

《容器高手实战: Dockerfile最佳实践》的更多相关文章

  1. 简单物联网:外网访问内网路由器下树莓派Flask服务器

    最近做一个小东西,大概过程就是想在教室,宿舍控制实验室的一些设备. 已经在树莓上搭了一个轻量的flask服务器,在实验室的路由器下,任何设备都是可以访问的:但是有一些限制条件,比如我想在宿舍控制我种花 ...

  2. 利用ssh反向代理以及autossh实现从外网连接内网服务器

    前言 最近遇到这样一个问题,我在实验室架设了一台服务器,给师弟或者小伙伴练习Linux用,然后平时在实验室这边直接连接是没有问题的,都是内网嘛.但是回到宿舍问题出来了,使用校园网的童鞋还是能连接上,使 ...

  3. 外网访问内网Docker容器

    外网访问内网Docker容器 本地安装了Docker容器,只能在局域网内访问,怎样从外网也能访问本地Docker容器? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Docker容器 ...

  4. 外网访问内网SpringBoot

    外网访问内网SpringBoot 本地安装了SpringBoot,只能在局域网内访问,怎样从外网也能访问本地SpringBoot? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装Java 1 ...

  5. 外网访问内网Elasticsearch WEB

    外网访问内网Elasticsearch WEB 本地安装了Elasticsearch,只能在局域网内访问其WEB,怎样从外网也能访问本地Elasticsearch? 本文将介绍具体的实现步骤. 1. ...

  6. 怎样从外网访问内网Rails

    外网访问内网Rails 本地安装了Rails,只能在局域网内访问,怎样从外网也能访问本地Rails? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Rails 默认安装的Rails端口 ...

  7. 怎样从外网访问内网Memcached数据库

    外网访问内网Memcached数据库 本地安装了Memcached数据库,只能在局域网内访问,怎样从外网也能访问本地Memcached数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装 ...

  8. 怎样从外网访问内网CouchDB数据库

    外网访问内网CouchDB数据库 本地安装了CouchDB数据库,只能在局域网内访问,怎样从外网也能访问本地CouchDB数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Cou ...

  9. 怎样从外网访问内网DB2数据库

    外网访问内网DB2数据库 本地安装了DB2数据库,只能在局域网内访问,怎样从外网也能访问本地DB2数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动DB2数据库 默认安装的DB2 ...

  10. 怎样从外网访问内网OpenLDAP数据库

    外网访问内网OpenLDAP数据库 本地安装了OpenLDAP数据库,只能在局域网内访问,怎样从外网也能访问本地OpenLDAP数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动 ...

随机推荐

  1. Spring Boot中的静态资源文件

    Spring Boot中的静态资源文件 1.SSM中的配置 2.Spring Boot 中的配置 2.1 整体规划 2.2 源码解读 2.3 自定义配置 2.3.1 application.prope ...

  2. 提高 Kafka 吞吐量

    提高 Kafka 吞吐量 1.了解分区的数据速率,以确保提供合适的数据保存空间 2.除非您有其他架构上的需要,否则在写 Topic 时请使用随机分区 3.如果 Consumers 运行的是比 Kafk ...

  3. 跟着Vimtutor学习Vim

    跟着Vimtutor学习Vim Lesson 1 1.1 移动光标 在Vim中移动光标,分别使用h.j.k.l键代表左.下.上.右方向. 1.2 退出VIM :q! <ENTER> 退出V ...

  4. yum.repos.d中的变量($releasever与$basearch)

    今天打算更新一下centos的repo源,把原先国外的repo地址换成国内的,速度快一些.主要替换的文件是/etc/yum.repos.d/Centos-Base.repo .替换的时候,不知道大家有 ...

  5. unix环境高级编程第三章笔记

    文件描述符 1.文件描述符的概念 对于内核而言,所有打开的文件都会用一个文件描述符来引用,打开或和创建一个新文件的时候,内核会给进程返回一个文件描述符,而当使用read write时,可以使用这个文件 ...

  6. Grakn Forces 2020

    比赛链接:https://codeforces.com/contest/1408 A. Circle Coloring 题意 给出三个长为 $n$ 的序列 $a,b,c$,对于每个 $i$,$a_i ...

  7. C# Dictionary(字典)源码解析&效率分析

    通过查阅网上相关资料和查看微软源码,我对Dictionary有了更深的理解. Dictionary,翻译为中文是字典,通过查看源码发现,它真的内部结构真的和平时用的字典思想一样. 我们平时用的字典主要 ...

  8. HttpClient客户端网络编程——高可用、高并发

    本文是HttpClient的学习博客,RestTemplate是基于HttpClient的封装,feign可基于HttpClient进行网络通信. 那么作为较底层的客户端网络编程框架,该怎么配置使其能 ...

  9. Python——控制鼠标键盘

    一.安装包 pip install pynput 二.引用包 from pynput import mouse,keyboard 三.控制鼠标 from pynput.mouse import But ...

  10. hive+postgres安装部署过程

    master节点安装元数据库,采用postgres:#useradd postgres#password postgressu - postgreswget https://ftp.postgresq ...