为了账号安全,请及时绑定邮箱和手机立即绑定

”微服务一条龙“最佳指南-“最佳实践”篇:Dockerfile

标签:
Docker

webp

最佳指南.jpg

这是”微服务一条龙“的项目第三篇,最近有很多网友私聊我希望我能够讲讲Dockerfile的相关注意事项,毕竟Dockerfile也是微服务部署的一个最最最....基础的一个组件,大家不要小看Dockerfile,一个容器到底是不是构建的最合适,要看你一步一步构建起来的命令是不是合适,好,话不多说,接下来我们来看看Dockerfile的最佳实践:

1. 先把利弊谈一谈

虽然 Dockerfile 简化了镜像构建的过程,并且把这个过程可以进行版本控制,但是不正当的 Dockerfile 使用也会导致很多问题:
(1)docker 镜像太大。如果你经常使用镜像或者构建镜像,一定会遇到那种很大的镜像,甚至有些能达到数G,就像我这次想要构建一个python+node的镜像结果花了1.4G。
(2)docker 镜像的构建时间过长。每个 build 都会耗费很长时间,对于需要经常构建镜像(比如单元测试)的地方这可能是个大问题,就像你可能每次只改一两行代码,却要花上10多分钟来构建新的镜像,完全是浪费时间。
(3)重复劳动。多次镜像构建之间大部分内容都是完全一样而且重复的,但是每次都要做一遍,浪费时间和资源。

2.最佳实践指导方针和建议

1. 容器应该是短暂的

容器模型是进程而不是机器,不需要开机初始化。在需要时运行,不需要时停止,能够删除后重建,并且配置和启动的最小化。这就是Docker和虚拟机最大的区别,进程化,说白了就是使用宿主机上的虚拟内核来起一个进程,一个进程代表一个容器,这样可以做到即插即用。

2.使用.dockerignore文件

docker build 的时候,忽略部分无用的文件和目录可以提高构建的速度。比如.git目录。dockerignore的定义类似gitignore。具体使用方式可以参考链接。其实大家都是用过gitignore,这里的dockerignore不仅可以忽略部分无用的文件,而且还保证了构建速度的提高,因为一般来说使用 docker build的时候都会直接使用 docker build -t xx .这样直接利用上下文,最好是定义dockerignore文件来忽略部分无用的文件。

3.避免安装不必要的安装包

为了减少镜像的复杂性、镜像大小和构建时间,应该避免安装无用的包。例如Python一样,你的工程目录中的requirements文件应该只包含你所需要的包,而不应当是包含一些无用的包,这样不仅浪费构建时间还变相扩大了整个镜像的大小。

4.每个容器只运行一个进程

一个容器只运行一个进程。容器起到了隔离应用隔离数据的作用,不同的应用运行在不同的容器让集群的纵向扩展以及容器的复用都变的更加简单。需要多个应用交互时请使用 link 命令进行组合或者使用docker-compose。这点是很多人没有注意到的问题,因为比如一个容器起了两个进程,我们下次需要对于一个进程做扩展时会以容器为粒度增加容器,实际了把另一个无关容器扩展,这样对于集群的纵向扩展以及容器的复用都有限制,所以,最基本的理念,一个容器只起一个进程。

5.最小化层数

需要掌握好Dockerfile的可读性和镜像层数之间的平衡。不推荐使用过多的镜像层。记住!一个指令就会创建一层,所以,尽最大可能精简你的Dockerfile

6.多行命令按字母排序

命令行按字母顺序排序有助于避免重复执行和提高Dockerfile可读性。apt-get update 应与 apt-get install 组合,换行使用反斜杠(\)。例如:

RUN apt-get update && apt-get install -y \
  bzr \
  cvs \
  git \
  mercurial \
  subversion

这里要是把两个命令分开的话问题有会两个,在docker build的时候使用--no-cache这样会把这两个命令当成两个,也就是会多创建一层,增加系统构建时间和大小,如果不使用,系统构建时会把apt-get update当成系统命令,默认不执行,因此可能会导致新的包不会被安装。

7.构建缓存

Dockerfile的每条指令都会将结果提交为新的镜像。下一条指令基于上一条指令的镜像进行构建。如果一个镜像拥有相同的父镜像和指令(除了 ADD ),Docker将会使用镜像而不是执行该指令,即缓存。

因此,为了有效的利用缓存,尽量保持Dockerfile一致,并且将不变的放在前面而经常改变放在末尾。

如不希望使用缓存,在执行 docker build 的时候加上参数 --no-cache=true 。

Docker匹配镜像决定是否使用缓存的规则如下:
(1)从缓存中存在的基础镜像开始,比较所有子镜像,检查它们构建的指令是否和当前的是否完全一致。如果不一致则缓存不匹配。
(2)多数情况中,比较Dockerfile中的指令是足够的。然而,特定的指令需要做更多的判断。
(3)ADD COPY 指令中,将要添加到镜像中的文件也要被检查。通常是检查文件的校验和(checksum)。
(4)缓存匹配检查并不检查容器中的文件。例如,当使用 RUN apt-get -y update 命令更新了容器中的文件,并不会被缓存检查策略作为缓存匹配的依据。这也就是说虽然文件更新了,但是命令相同,依旧是使用缓存,所以会导致更新失效。

8.时区

官方Image 使用的时区基本上都是标准的 UTC 时间,如果容器想使用中国标准时间,基于Debian的系统在Dockerfile中加入

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

基于Centos的系统在Dockerfile中加入

RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

9.修改默认源

有时候你可能感觉官方的源更新或者安装软件比较慢,可以在Dockerfile修改官方默认源,例如alpine想使用阿里的源可以在Dockerfile中加入:

RUN echo -e "http://mirrors.aliyun.com/alpine/v3.5/
main\nhttp://mirrors.aliyun.com/alpine/v3.5/community" > /etc/apk/repositories

都是知识点!!!

10.正确理解Dockerfile指令

首先说说很多人其实并不是一定理解Dockerfile里面每一个指令的含义,以及与其他指令的微小区别,比如addcopy等等。

FROM

推荐使用官方仓库中的镜像作为基础镜像,不用说大家都理解。

RUN

把复杂的或过长的 RUN 语句写成以 \ 结尾的多行的形式,以提高可读性和可维护性。

apt-get update 和 apt-get install 一起执行,否则 apt-get install 会出现异常。
这个严重的问题必须要好好提提~~~

避免运行 apt-get upgradedist-upgrade,在无特权的容器中,很多必要的包不能正常升级。如果基础镜像过时了,应当联系维护者。 推荐apt-get update && apt-get install -y package-a package-b这种方式,先更新,之后安装最新的软件包。

RUN apt-get update && apt-get install -y \
    aufs-tools \
    automake \
    build-essential \
    curl \
    dpkg-sig \
    libcap-dev \
    libsqlite3-dev \
    mercurial \
    reprepro \
    ruby1.9.1 \
    ruby1.9.1-dev \
    s3cmd=1.1.* \
 && rm -rf /var/lib/apt/lists/*

此外,你可以通过移除/var/lib/apt/lists减少镜像大小。

    注意:官方的Ubuntu和Debian会自动运行apt-get clean,所以不需要显式的调用

CMD

推荐使用 CMD ["executable","param1","param2"] 这样的格式。
如果镜像是用来运行服务,需要使用 CMD["apache2","-DFOREGROUND"],这种格式的指令适用于任何服务性质的镜像。

ENTRYPOINT

ENTRYPOINT 应该用于镜像的主命令,并使用 CMD 作为默认设置,以 s3cmd 为例:

ENTRYPOINT ["s3cmd"]CMD ["--help"]

获取帮助:

docker run s3cmd

或者执行命令:

docker run s3cmd ls s3://mybucket

这在镜像名与程序重名时非常有用。

ENTRYPOINT 也可以启动自定义脚本: 定义脚本:

#!/bin/bashset -eif [ "$1" = 'postgres' ]; then
    chown -R postgres "$PGDATA"

    if [ -z "$(ls -A "$PGDATA")" ]; then
        gosu postgres initdb    fi

    exec gosu postgres "$@"fiexec "$@"
注意:这段脚本使用了exec命令以确保最终应用程序在容器内启动的PID为1。这段脚本允许容器接收Unix signals。
COPY ./docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]

这段脚本为用户提供了多种和 Postgres 交互的途径:

你可以简单地启动 Postgres:

docker run postgres。

或者运行 postgres 并传入参数:

docker run postgres postgres --help。

你甚至可以从镜像中启动一个完全不同的程序,比如 Bash:

docker run --rm -it postgres bash

EXPOSE

应该尽可能地使用默认端口。例如Apache web服务使用EXPOSE 80,MongoDB使用EXPOSE 27017。

ENV

可以使用ENV更新PATH环境变量。例如,ENV PATH /usr/local/nginx/bin:$PATH可以确保CMD [“nginx”]正常运行。
ENV还可以提供程序所需要的环境变量,例如Postgres的 PGDATA。
ENV可以设置版本等信息。使版本信息更易于维护。

ENV PG_MAJOR 9.3
ENV PG_VERSION 9.3.4
RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …
ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH

ADD or COPY

虽然 ADD 与 COPY 功能类似,但推荐使用 COPY 。 COPY 只支持基本的文件拷贝功能,更加的可控。而 ADD 具有更多特定,比如tar文件自动提取,支持URL。 通常需要提取tarball中的文件到容器的时候才会用到 ADD 。

如果在Dockerfile中使用多个文件,每个文件应使用单独的 COPY 指令。这样,只有出现文件变化的指令才会不使用缓存。

为了控制镜像的大小,不建议使用 ADD 指令获取URL文件。正确的做法是在 RUN 指令中使用 wget 或 curl 来获取文件,并且在文件不需要的时候删除文件。

RUN mkdir -p /usr/src/things \
    && curl -SL http://example.com/big.tar.gz \
    | tar -xJC /usr/src/things \
    && make -C /usr/src/things all

一句话,COPY透明度好,它只能干复制的工作,ADD鬼知道他还能干什么~

VOLUME

VOLUME 通常用作数据卷,对于任何可变的文件,包括数据库文件、代码库、或者容器所创建的文件/目录等都应该使用 VOLUME 挂载。这点有点生产环境体验的同学相信不会陌生。

USER

如果服务不需要特权来运行,使用 USER 指令切换到非root用户。使用 **RUN groupadd -r mysql && useradd -r -g mysql mysql **之后用**USER mysql**切换用户

要避免使用 sudo 来提升权限,因为它带来的问题远比它能解决的问题要多。如果你确实需要这样的特性,那么可以选择使用 gosu

最后,不要反复的切换用户。减少不必要的layers。

WORKDIR

为了清晰和可维护性,应该使用WORKDIR来定义工作路径。推荐使用WORKDIR来代替RUN cd … && do-something 这样的指令。
]

3.总结

废话不多说,Docker这东西你用起来才知道真的好用,上五楼都不费劲了!!



作者:Lateautumn_Lin
链接:https://www.jianshu.com/p/4a526bc6276b


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消