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. Java 操作 HBase 教程

    Java 操作 HBase 教程 一.简介 二.hbase-client 引入 三.连接操作 四.表操作 五.运行测试 相关博文原文地址: 博客园:美码师:HBase(2) Java 操作 HBase ...

  2. Hadoop优势,组成的相关架构,大数据生态体系下的模式

    Hadoop优势,组成的相关架构,大数据生态体系下的模式 一.Hadoop的优势 二.Hadoop的组成 2.1 HDFS架构 2.2 Yarn架构 2.3 MapReduce架构 三.大数据生态体系 ...

  3. 浅聊ARP

    今天借用思科公司的Cisco Packet Tracer Student这款软件浅聊ARP 什么是ARP? ARP即地址解析协议(Address Resolution Protocol),是根据Ip地 ...

  4. (31)grep命令详解:查找文件内容

    1.grep命令用于不需要列出文件的全部内容,而是从文件中找到包含指定信息的那些行. grep命令能够在一个或多个文件中,搜索某一特定的字符模式(也就是正则表达式),此模式可以是单一的字符.字符串.单 ...

  5. Codeforces Round #602 Div2 D1. Optimal Subsequences (Easy Version)

    题意:给你一个数组a,询问m次,每次返回长度为k的和最大的子序列(要求字典序最小)的pos位置上的数字. 题解:和最大的子序列很简单,排个序就行,但是题目要求字典序最小,那我们在刚开始的时候先记录每个 ...

  6. Ubuntu中Unable to acquire the dpkg frontend lock (/var/lib/dpkg/lock-frontend)问题的解决

    参考博客:https://blog.csdn.net/shimadear/article/details/90598646 问题描述: 解决方法: 第一种情况: 进程中存在与apt相关的正在运行的进程 ...

  7. codeforces 949B :A Leapfrog in the Array 找规律

    题意: 现在给你一个n,表示有2*n-1个方格,第奇数方格上会有一个数字 1-n按顺序放.第偶数个方格上是没有数字的.变动规则是排在最后一个位置的数字,移动到它前边最近的空位 . 直到数字之间没有空位 ...

  8. Spring Cloud实战: 基于Spring Cloud Gateway + vue-element-admin 实现的RBAC权限管理系统,实现网关对RESTful接口方法权限和自定义Vue指令对按钮权限的细粒度控制

    一. 前言 信我的哈,明天过年. 这应该是农历年前的关于开源项目 的最后一篇文章了. 有来商城 是基于 Spring Cloud OAuth2 + Spring Cloud Gateway + JWT ...

  9. Python 3的f-Strings:增强的字符串格式语法(指南)

    最近也在一个视频网站的爬虫,项目已经完成,中间有不少需要总结的经验. 从Python 3.6开始,f-Strings是格式化字符串的一种很棒的新方法.与其他格式化方式相比,它们不仅更具可读性,更简洁且 ...

  10. sdut2878 环形依赖的DP(高斯消元,剪枝后的模板

    这题的状态是循环依赖的有环.. 之前一道概率DP,类似有环..但是它是可以消掉的 比如dp[i]=0.3*dp[i+1]+0.2*dp[i+2]+0.5*dp[i]; 完全可以变成,0.5*dp[i] ...