《容器高手实战: Dockerfile最佳实践》
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最佳实践》的更多相关文章
- 简单物联网:外网访问内网路由器下树莓派Flask服务器
最近做一个小东西,大概过程就是想在教室,宿舍控制实验室的一些设备. 已经在树莓上搭了一个轻量的flask服务器,在实验室的路由器下,任何设备都是可以访问的:但是有一些限制条件,比如我想在宿舍控制我种花 ...
- 利用ssh反向代理以及autossh实现从外网连接内网服务器
前言 最近遇到这样一个问题,我在实验室架设了一台服务器,给师弟或者小伙伴练习Linux用,然后平时在实验室这边直接连接是没有问题的,都是内网嘛.但是回到宿舍问题出来了,使用校园网的童鞋还是能连接上,使 ...
- 外网访问内网Docker容器
外网访问内网Docker容器 本地安装了Docker容器,只能在局域网内访问,怎样从外网也能访问本地Docker容器? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Docker容器 ...
- 外网访问内网SpringBoot
外网访问内网SpringBoot 本地安装了SpringBoot,只能在局域网内访问,怎样从外网也能访问本地SpringBoot? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装Java 1 ...
- 外网访问内网Elasticsearch WEB
外网访问内网Elasticsearch WEB 本地安装了Elasticsearch,只能在局域网内访问其WEB,怎样从外网也能访问本地Elasticsearch? 本文将介绍具体的实现步骤. 1. ...
- 怎样从外网访问内网Rails
外网访问内网Rails 本地安装了Rails,只能在局域网内访问,怎样从外网也能访问本地Rails? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Rails 默认安装的Rails端口 ...
- 怎样从外网访问内网Memcached数据库
外网访问内网Memcached数据库 本地安装了Memcached数据库,只能在局域网内访问,怎样从外网也能访问本地Memcached数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装 ...
- 怎样从外网访问内网CouchDB数据库
外网访问内网CouchDB数据库 本地安装了CouchDB数据库,只能在局域网内访问,怎样从外网也能访问本地CouchDB数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Cou ...
- 怎样从外网访问内网DB2数据库
外网访问内网DB2数据库 本地安装了DB2数据库,只能在局域网内访问,怎样从外网也能访问本地DB2数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动DB2数据库 默认安装的DB2 ...
- 怎样从外网访问内网OpenLDAP数据库
外网访问内网OpenLDAP数据库 本地安装了OpenLDAP数据库,只能在局域网内访问,怎样从外网也能访问本地OpenLDAP数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动 ...
随机推荐
- Codeforces 1220D 思维 数学 二分图基础
原题链接 题意 我们有一个含多个正整数的集合B,然后我们将所有的整数,也就是Z集合内所有元素,都当做顶点 两个整数 \(i , j\) 能建立无向边,当且仅当 \(|i - j|\) 这个数属于B集合 ...
- JavaWeb——Servlet开发
什么是Servlet? Servlet运行的过程 Servlet的生命周期 生命周期的各个阶段 Servlet的配置 使用Web.xml配置 使用注解配置 Servlet相关接口 ServletCon ...
- samba 、 FTP 、 lrzsz工具
samba Samba是一个实现不同操作系统(Windows.Linux.UNIX)之间文件共享和打印机共享的一种SMB协议的免费软件:linux与linux之间共享用NFS:samba基于cs架构: ...
- 34.vsftpd服务程序--虚拟用户模式
1.创建用于进行FTP 认证的用户数据库文件,其中奇数行为账户名,偶数行为密码. [root@localhost ~]# cd /etc/vsftpd/ [root@localhost vsftpd] ...
- CSAPP_BombLab实验报告
Lab_2实验报告 目录 Lab_2实验报告 屏幕截图 考察内容 各题答案 bomb1 bomb2 bomb3 bomb4 bomb5 bomb6 secret_phase 解题思路 bomb1 bo ...
- STL中去重函数unique
一:unique(a.begin(),a.end());去重函数只是去掉连续的重复值,对于不连续的值没有影响,SO,在使用前一般需要进行排序处理: 二: vector<int>::ite ...
- Codeforces Round #428 (Div. 2) C. Journey (简单搜索)
题意:给你一颗树(边是无向的),从根节点向下走,统计走到每个子节点的概率,求所有叶子节点的深度乘上概率的和. 题解:每层子节点的概率等于上一层节点的概率乘\(1\)除以这层的子节点数,所以我们用\(d ...
- Codeforces Round #672 (Div. 2) D. Rescue Nibel! (思维,组合数)
题意:给你\(n\)个区间,从这\(n\)区间中选\(k\)个区间出来,要求这\(k\)个区间都要相交.问共有多少种情况. 题解:如果\(k\)个区间都要相交,最左边的区间和最右边的区间必须要相交,即 ...
- 2017, X Samara Regional Intercollegiate Programming Contest E. Bonuses and Teleports (思维,模拟)
题意:在\(x\)轴上有很多传送点和钻石,当位于传送点上时,可以传送到其他任意传送点(不记操作数),位于钻石上时可以吃掉它,每次可以移动一个单位,问最少多少次可以吃掉所有的钻石. 题解:对于某个位置上 ...
- 【转】Docker 核心技术与实现原理
转自:https://draveness.me/docker 提到虚拟化技术,我们首先想到的一定是 Docker,经过四年的快速发展 Docker 已经成为了很多公司的标配,也不再是一个只能在开发阶段 ...