当前位置 博文首页 > bindot:《容器高手实战: Dockerfile最佳实践》

    bindot:《容器高手实战: Dockerfile最佳实践》

    作者:bindot 时间:2021-02-20 14:33

    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

    bk
    下一篇:没有了