• 【Docker】Dockerfile使用技巧


    在这里插入图片描述

    开启Buildkit

    BuildKit是Docker官方社区推出的下一代镜像构建神器,可以更加快速,有效,安全地构建docker镜像

    尽管目前BuildKit不是Docker的默认构建工具,但是完全可以考虑将其作为Docker(v18.09+)的首选构建工具。

    官方文档:https://docs.docker.com/build/buildkit/

    下面介绍一下怎么开启BuildKit。

    /etc/docker/daemon.json里添加(如果没有这个文件,则新建), 然后重启docker

    { "features": { "buildkit": true } }
    
    • 1

    或者在执行docker build命令时设置

    $ DOCKER_BUILDKIT=1 docker build .
    
    • 1

    镜像的选择

    可以前往官网学习官方的镜像怎么制作的:https://github.com/docker-library/official-images

    基础镜像的选择原则:

    • 官方镜像优于非官方的镜像,如果没有官方镜像,则尽量选择Dockerfile开源的。
    • 固定版本tag而不是每次都使用latest
    • 尽量选择体积小的镜像

    下面的镜像列表中,都是jre8的镜像,但是由于基础的镜像不同导致最终镜像大小不同。

    $ docker image ls
    REPOSITORY        TAG             IMAGE ID       CREATED         SIZE
    eclipse-temurin   8-jre-jammy     ec4a8981544b   6 days ago      223MB
    eclipse-temurin   8-jre-focal     e588bf105eb7   3 weeks ago     227MB
    eclipse-temurin   8-jre-alpine    f7a454a165ae   3 weeks ago     135MB
    eclipse-temurin   8-jre-centos7   e5b6f35176e9   23 months ago   350MB
    openjdk           8-jre-alpine    f7a292bbb70c   4 years ago     84.9MB
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Jammy和Focal:这两个都是Ubuntu的版本代号。"Focal"对应的是Ubuntu 20.04 LTS(长期支持版),"Jammy"对应的则是更加新的Ubuntu版本。Ubuntu是一个非常受欢迎的Linux发行版,因为它既有强大的功能,又有着广大的用户和开发者社区。

    Alpine:Alpine Linux是一个面向安全、简单和轻量级的Linux发行版,它的镜像大小通常远小于基于 Ubuntu或其他发行版的镜像。这使得它非常适合于Docker镜像,因为小的镜像可以更快地被拉取和部署。

    AdoptOpenJDK停止发布OpenJDK二进制,而Eclipse Temurin是它的延伸,提供更好的稳定性。

    减少镜像的分层

    例如下面的镜像:

    from centos:7
    
    label author=morirs
    
    env WORK_DIR /usr/local
    
    arg version=6.2.5
    
    workdir $WORK_DIR
    
    ADD redis-${version}.tar.gz .
    
    run yum -y update
    run yum install -y wget gcc gcc-c++ automake autoconf libtool make
    run make -C redis-${version}
    
    expose 6379
    
    env REDIS_HOME $WORK_DIR/redis-6.2.5
    
    env PATH $PATH:$REDIS_HOME/src
    
    entrypoint ["redis-server"]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    查询镜像的分层:

    $ docker image history redis:6.2.5.1
    IMAGE          CREATED             CREATED BY                                      SIZE      COMMENT
    444f31557106   57 seconds ago      ENTRYPOINT ["redis-server"]                     0B        buildkit.dockerfile.v0
    <missing>      57 seconds ago      ENV PATH=/usr/local/sbin:/usr/local/bin:/usr…   0B        buildkit.dockerfile.v0
    <missing>      57 seconds ago      ENV REDIS_HOME=/usr/local/redis-6.2.5           0B        buildkit.dockerfile.v0
    <missing>      57 seconds ago      EXPOSE map[6379/tcp:{}]                         0B        buildkit.dockerfile.v0
    <missing>      57 seconds ago      RUN |1 version=6.2.5 /bin/sh -c make -C redi…   122MB     buildkit.dockerfile.v0
    <missing>      About an hour ago   RUN |1 version=6.2.5 /bin/sh -c yum install …   302MB     buildkit.dockerfile.v0
    <missing>      About an hour ago   RUN |1 version=6.2.5 /bin/sh -c yum -y updat…   358MB     buildkit.dockerfile.v0
    <missing>      About an hour ago   ADD redis-6.2.5.tar.gz . # buildkit             10.4MB    buildkit.dockerfile.v0
    <missing>      2 hours ago         WORKDIR /usr/local                              0B        buildkit.dockerfile.v0
    <missing>      2 hours ago         ARG version=6.2.5                               0B        buildkit.dockerfile.v0
    <missing>      2 hours ago         ENV WORK_DIR=/usr/local                         0B        buildkit.dockerfile.v0
    <missing>      2 hours ago         LABEL author=morirs                             0B        buildkit.dockerfile.v0
    <missing>      2 years ago         /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B
    <missing>      2 years ago         /bin/sh -c #(nop)  LABEL org.label-schema.sc…   0B
    <missing>      2 years ago         /bin/sh -c #(nop) ADD file:b3ebbe8bd304723d4…   204MB
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    每一行的RUN命令都会产生一层image layer, 导致镜像的臃肿。

    修改为如下:

    from centos:7
    
    label author=morirs
    
    env WORK_DIR /usr/local
    
    arg version=6.2.5
    
    workdir $WORK_DIR
    
    ADD redis-${version}.tar.gz .
    
    run yum -y update \
            && yum install -y wget gcc gcc-c++ automake autoconf libtool make \
            && run make -C redis-${version}
    
    expose 6379
    
    env REDIS_HOME $WORK_DIR/redis-6.2.5
    
    env PATH $PATH:$REDIS_HOME/src
    
    entrypoint ["redis-server"]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    新的镜像分层如下:

    $ docker history redis:6.2.5.2
    IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
    fa6f23e424bc   30 seconds ago   ENTRYPOINT ["redis-server"]                     0B        buildkit.dockerfile.v0
    <missing>      30 seconds ago   ENV PATH=/usr/local/sbin:/usr/local/bin:/usr…   0B        buildkit.dockerfile.v0
    <missing>      30 seconds ago   ENV REDIS_HOME=/usr/local/redis-6.2.5           0B        buildkit.dockerfile.v0
    <missing>      30 seconds ago   EXPOSE map[6379/tcp:{}]                         0B        buildkit.dockerfile.v0
    <missing>      30 seconds ago   RUN |1 version=6.2.5 /bin/sh -c yum -y updat…   593MB     buildkit.dockerfile.v0
    <missing>      2 hours ago      ADD redis-6.2.5.tar.gz . # buildkit             10.4MB    buildkit.dockerfile.v0
    <missing>      2 hours ago      WORKDIR /usr/local                              0B        buildkit.dockerfile.v0
    <missing>      2 hours ago      ARG version=6.2.5                               0B        buildkit.dockerfile.v0
    <missing>      2 hours ago      ENV WORK_DIR=/usr/local                         0B        buildkit.dockerfile.v0
    <missing>      2 hours ago      LABEL author=morirs                             0B        buildkit.dockerfile.v0
    <missing>      2 years ago      /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B
    <missing>      2 years ago      /bin/sh -c #(nop)  LABEL org.label-schema.sc…   0B
    <missing>      2 years ago      /bin/sh -c #(nop) ADD file:b3ebbe8bd304723d4…   204MB
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    镜像的大小变化如下:

    $ docker image ls
    REPOSITORY        TAG             IMAGE ID       CREATED              SIZE
    redis             6.2.5.2         fa6f23e424bc   About a minute ago   807MB
    redis             6.2.5.1         444f31557106   15 minutes ago       996MB
    
    • 1
    • 2
    • 3
    • 4

    合理使用缓存

    在构建的时候尽量将不变的结构放在Dockerfile的前面,经常变化的结构放在Dockerfile的后面,这样构建的时候不变的部分层构建过了就无需再次构建,节约时间,可以在构建的日志中看到CACHED

    s$ docker image build -f redis.dockerfile2 -t redis:6.2.5.2 .
    [+] Building 268.3s (9/9) FINISHED
     => [internal] load build definition from redis.dockerfile2                                                                                0.0s
     => => transferring dockerfile: 412B                                                                                                       0.0s
     => [internal] load .dockerignore                                                                                                          0.0s
     => => transferring context: 2B                                                                                                            0.0s
     => [internal] load metadata for docker.io/library/centos:7                                                                                0.0s
     => [1/4] FROM docker.io/library/centos:7                                                                                                  0.0s
     => [internal] load build context                                                                                                          0.0s
     => => transferring context: 42B                                                                                                           0.0s
     => CACHED [2/4] WORKDIR /usr/local                                                                                                        0.0s
     => CACHED [3/4] ADD redis-6.2.5.tar.gz .                                                                                                  0.0s
     => [4/4] RUN yum -y update  && yum install -y wget gcc gcc-c++ automake autoconf libtool make  && make -C redis-6.2.5                   265.0s
     => exporting to image                                                                                                                     3.2s
     => => exporting layers                                                                                                                    3.2s
     => => writing image sha256:fa6f23e424bc6088f6cc84d5ff67ae1376f18fbc906e14b670bc3e4c316046e6                                               0.0s
     => => naming to docker.io/library/redis:6.2.5.2                                                                                           0.0s
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    合理使用.dockerignore

    Docker是client-server架构,理论上Client和Server可以不在一台机器上。

    在构建docker镜像的时候,需要把所需要的文件由CLI(client)发给Server,这些文件实际上就是build context。

    举例:

    $ cat redis6.2.6.dockerfile
    from centos:7
    
    label author=morirs
    
    env WORK_DIR /usr/local
    
    arg version=6.2.6
    
    workdir $WORK_DIR
    
    COPY . .
    
    run tar -zxvf redis-${version}.tar.gz
    run yum -y update
    run yum install -y wget gcc gcc-c++ automake autoconf libtool make
    run make -C redis-${version}
    
    expose 6379
    
    env REDIS_HOME $WORK_DIR/redis-6.2.5
    
    env PATH $PATH:$REDIS_HOME/src
    
    entrypoint ["redis-server"]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    构建目录下的文件:

    total 4848
    drwxrwxr-x 2 morris morris    4096 Sep 19 19:30 ./
    drwxrwxr-x 4 morris morris    4096 Sep 18 10:00 ../
    -rw-rw-r-- 1 morris morris 2465302 Jul 22  2021 redis-6.2.5.tar.gz
    -rw-rw-r-- 1 morris morris 2476542 Oct  4  2021 redis-6.2.6.tar.gz
    -rw-rw-r-- 1 morris morris     356 Sep 18 11:28 redis.dockerfile
    -rw-rw-r-- 1 morris morris     360 Sep 18 11:39 redis.dockerfile2
    -rw-rw-r-- 1 morris morris     373 Sep 19 19:30 redis6.2.6.dockerfile
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    构建的时候,第一行输出就是发送build context大小为4.947MB,这里包含了不需要的文件redis-6.2.5.tar.gz

    $ docker image build -f redis6.2.6.dockerfile -t redis:6.2.6.1 .
    Sending build context to Docker daemon  4.947MB
    Step 1/14 : from centos:7
     ---> eeb6ee3f44bd
    
    • 1
    • 2
    • 3
    • 4

    编写.dockerignore文件,忽略掉不需要的文件,然后放到docker构建上下文的根路径下。

    $ docker image build -f redis6.2.6.dockerfile -t redis:6.2.6.1 .
    Sending build context to Docker daemon  2.482MB
    Step 1/14 : from centos:7
     ---> eeb6ee3f44bd
    
    • 1
    • 2
    • 3
    • 4

    再次构建build context大小变为2.482MB

    镜像的多阶段构建

    假如有一个C的程序,我们想用Docker去做编译,然后执行可执行文件。

    #include 
    
    void main(int argc, char *argv[])
    {
        printf("hello %s\n", argv[argc - 1]);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    构建一个Docker镜像,因为要有C的环境,所以我们选择gcc这个image

    FROM gcc:9.4
    
    COPY hello.c /src/hello.c
    
    WORKDIR /src
    
    RUN gcc --static -o hello hello.c
    
    ENTRYPOINT [ "/src/hello" ]
    
    CMD []
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    build和测试:

    $ docker image build -f c.dockerfile -t hello:1.0 .
    ... ...
    
    $ docker container run --rm -it hello:1.0 hello
    hello hello
    
    $ docker image ls
    REPOSITORY        TAG             IMAGE ID       CREATED          SIZE
    hello             1.0             93b8a1824b80   45 seconds ago   1.14GB
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    可以看到镜像非常的大,1.14GB

    实际上当我们把hello.c编译完以后,并不需要这样一个大的GCC环境,一个小的alpine镜像就可以了。

    这时候我们就可以使用多阶段构建了。

    FROM gcc:9.4 AS builder
    
    COPY hello.c /src/hello.c
    
    WORKDIR /src
    
    RUN gcc --static -o hello hello.c
    
    
    FROM alpine:3.13.5
    
    COPY --from=builder /src/hello /src/hello
    
    ENTRYPOINT [ "/src/hello" ]
    
    CMD []
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    构建和测试:

    $ docker image build -f c2.dockerfile -t hello:2.0 .
    ... ...
    
    $ docker container run --rm -it hello:2.0 hi
    hello hi
    
    $ docker image ls
    REPOSITORY        TAG             IMAGE ID       CREATED          SIZE
    hello             2.0             8127f3ac5ea6   4 seconds ago    6.55MB
    hello             1.0             93b8a1824b80   45 seconds ago   1.14GB
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    可以看到这个镜像非常小,只有6.55MB。

    尽量使用非root用户

    Root的危险性

    docker的root权限一直是其遭受诟病的地方,docker的root权限有那么危险么?我们举个例子。

    假如我们有一个用户,叫morris,它本身不具有sudo的权限,所以就有很多文件无法进行读写操作,比如/root目录它是无法查看的。

    $ ls /root
    ls: cannot open directory '/root': Permission denied
    
    • 1
    • 2

    但是这个用户有执行docker的权限,也就是它在docker这个group里。

    $ groups
    morris docker
    
    • 1
    • 2

    这时,我们就可以通过Docker做很多越权的事情了,比如,我们可以把这个无法查看的/root目录映射到docker container里,你就可以自由进行查看了。

    $ docker container run -it --rm -v /root:/root/tmp centos:7 bash
    [root@9cc0d1275a15 /]# ls /root/tmp
    snap
    
    • 1
    • 2
    • 3

    更甚至我们可以给我们自己加sudo权限:

    $ docker container run -it --rm -v /etc/sudoers:/root/sudoers centos:7 bash
    [root@744b21d23ba9 /]# echo "demo    ALL=(ALL)       ALL" >> /root/sudoers
    [root@744b21d23ba9 /]# more /root/sudoers | grep demo
    demo    ALL=(ALL)       ALL
    
    • 1
    • 2
    • 3
    • 4

    然后退出container,morris用户已经有sudo权限了。

    $ ls /etc/sudoers
    /etc/sudoers
    
    • 1
    • 2

    如何使用非root用户

    通过groupadd和useradd创建一个nonroot的组和用户,通过USER指定后面的命令要以nonroot这个用户的身份运行

    from centos:7
    
    label author=morirs
    
    env WORK_DIR /usr/local
    
    arg version=6.2.5
    
    workdir $WORK_DIR
    
    ADD redis-${version}.tar.gz .
    
    run yum -y update \
            && yum install -y wget gcc gcc-c++ automake autoconf libtool make \
            && make -C redis-${version} \
            && groupadd -r nonroot \
            && useradd -r -g nonroot nonroot \
            && chown -R nonroot:nonroot $WORK_DIR
    
    USER nonroot
    
    expose 6379
    
    env REDIS_HOME $WORK_DIR/redis-6.2.5
    
    env PATH $PATH:$REDIS_HOME/src
    
    entrypoint ["redis-server"]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
  • 相关阅读:
    智能呼叫中心系统的未来发展趋势:为企业开启全新服务模式
    js/vue/tsx 中获取dom元素的方法集合
    数据库高级 I
    Tomcat 源码解析一类加载器-狂狮吟
    湖北省支持企业技术创新发展项目支持对象、奖励和申报流程、要求、材料
    Java面试题总结(二)
    dubbo学习(一)dubbo简介与原理
    VideoPlayerWithOpenCVForUnityExample
    期末复习【C++】
    Python语义分割与街景识别(3):数据集准备
  • 原文地址:https://blog.csdn.net/u022812849/article/details/134005726