注:本文章基于docker-ce版本:Client 19.03.8,Server 19.03.8

Dockerfile简介

Dockerfile主要有这几个指令,每个指令都会添加新的层,但是镜像大小不一定增长:

指令 用途 用法 简单示例
FROM 指定基础镜像 FROM <image>FROM <image>:<tag> FROM ubuntu:16.04
MAINTAINER 维护者信息 MAINTAINER <name> MAINTAINER HuangXianDong
ADD 复制指定的文件到容器中包括tar,URL等 ADD <src> <dest> ADD conf/jail.local /etc/fail2ban/jail.local
COPY 复制host上下文环境的文件或者前一阶段镜像的文件到容器 COPY <src> <dest>有两个标志 –from= –chown= COPY /usr/local/app /usr/local/app
USER 指定用户 USER <username> USER root
WORKDIR 指定工作目录 WORKDIR /path/to/workdir WORKDIR /root
RUN 终端执行sh或者可执行程序 RUN <command>RUN [“executable”, “param1”, “param2”] RUN apt-get update
ENV 指定一个环境变量 ENV <key> <value>ENV <key>=<value> ENV TZ=Asia/Shanghai
ONBUILD 配置当所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令 ONBUILD [INSTRUCTION] ONBUILD ADD . /app/src
VOLUME 创建一个挂载点 VOLUME [“/data”] VOLUME [“/data”]
EXPOSE 打算暴露的端口号 EXPOSE <port> [<port>…] EXPOSE 5060/tcp 5060/udp
ENTRYPOINT 容器入口点,不可被docker run覆盖 ENTRYPOINT [“executable”, “param1”, “param2”]或ENTRYPOINT command param1 param2 ENTRYPOINT entrypoint.sh
CMD 指定启动容器时执行的命令,可以被docker run覆盖 CMD [“executable”,”param1″,”param2″]CMD command param1 param2CMD [“param1″,”param2”]提供给 ENTRYPOINT 的默认参数 CMD python /app/app.py

常用镜像缩小方法

在哪层引入,就在哪层清理

众所周知,Dockerfile每个指令都会叠加新的一层文件系统,由于docker镜像这种叠加的文件系统,也就是说在构建镜像时,前面一层的文件对于本层来说是只读的,从而不能够在本层文件系统去清理上一层引入的文件,虽然可以删除掉对应的文件或文件夹,但是镜像体积并没有减少:

WORKDIR /usr/src
RUN git clone http://xxx.git \
    && cd ./xxx \
    && ./bootstrap.sh \
    && ./configure --enable-core-pgsql-support \
    && make \
    && make install      #这一层引入文件

RUN rm -rf /usr/src/*      #在这一层清理,达不到体积减小的目的,反而增加层数

应该这样:

WORKDIR /usr/src
RUN git clone http://xxx.git \
    && cd ./xxx \
    && ./bootstrap.sh \
    && ./configure --enable-core-pgsql-support \
    && make \
    && make install \      
    && rm -rf /usr/src/*      # 在哪层引入,就在哪层清理

此外,如果在ADDCOPY指令中添加了多余的文件,此后在其他层删除掉改文件或文件夹虽然文件删除了,但是镜像大小是不变或增加的:

FROM busybox
ADD . /root        # busytest0 157MB
RUN rm -rf /root   # busytest1 157MB

另外,在使用包管理工具的时候经常要安装各种工具,安装时应该将所有要安装的工具写在一起并且加以清理,甚至可以卸载掉:

RUN apt-get update && apt-get install -y autoconf automake bison \
    build-essential fail2ban gawk git-core groff groff-base \
    && apt-get autoclean \
    && rm -rf /var/lib/apt/lists/*

合并RUN中的命令

多个RUN指令会增加很多层镜像,应该尽量把能合在一起写的shell写在同一个RUN指令里:

RUN apt-get update \
    && ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone \
    && apt-get install tzdata \
    && apt-get clean \
    && apt-get autoclean \
    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

而不是:

RUN apt-get update 
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime 
RUN echo $TZ > /etc/timezone 
RUN apt-get install tzdata 
RUN apt-get clean 
RUN apt-get autoclean

分阶段构建

有些应用,如go构建的可执行文件,可以不需要依赖构建他的系统而运行的程序,就适合进行分步构建,也就是一个阶段编译,一个阶段用于执行程序。分步构建很大程度上能减少镜像的体积。

FROM golang
COPY hello.go .
RUN go build hello.go
FROM scratch
COPY --from=0 /go/hello .
CMD ["./hello"]

分阶段构建有时候对一些C程序和大型的Go程序来说,凡是有依赖动态运行库的程序进行分阶段构建就很头疼,因为复制过来的可执行文件可能需要某个动态库的支持。

与之相对的解决办法是:静态编译或者复制动态库。

静态编译是一个不错的方法,但是如果某种原因不适合静态编译,比如分阶段编译等等,这就需要拷贝库文件了。

就像某些程序,编译出来的可执行文件只有十几kb,其他的都是以动态库的形势存在,那么怎么寻找应用对应的依赖库呢?

使用ldd命令,该命令可以查询对应的库文件:

# ldd /usr/local/freeswitch/bin/freeswitch
        linux-vdso.so.1 =>  (0x00007ffd0bb61000)
        libfreeswitch.so.1 => /usr/local/freeswitch/lib/libfreeswitch.so.1 (0x00007f0fa2a1d000)
        libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f0fa27f3000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0fa2429000)
        libuuid.so.1 => /lib/x86_64-linux-gnu/libuuid.so.1 (0x00007f0fa2223000)
        ...

将这些对应的库文件复制到镜像之后需要执行ldconfig命令,任何改动库文件的操作都应该执行该命令。

复制动态库文件可能会导致后续维护变得困难,并且可能存在未知的隐患。

制作基础镜像包

构建镜像时有些主要是下载依赖包导致的时间变长,可以考虑专门做一个环境编译的基础镜像,后续编译就可以基于该镜像编译,大大减少构建时间。

一些容易增大镜像体积的操作

慎用chown命令:chown命令在有些文件系统在实现多层联合文件系统的原理不同,可能会导致无端增加文件夹的体积。

原因是在docker实现的某些文件系统如overlayfs2chown 只更改 metadata 数据, 而 metadata 是作用在 inode 节点上的, 一个硬链接文件的属性被修改, 同步的, 所有指向这个 inode节点的文件的属性都会变化,而overlayfs2每层都是独立的,即使文件属性的变化,也会导致整个文件被拷贝, 所以在 overlayfs2 下, 会产生多余的空间浪费。

常用的工具

docker history命令

docker自带了简单的镜像分析命令,直接执行docker history <IMAGE ID> 或者 docker history REPOSITORY:TAG就行:

[root@RqInternTest3 freeswitch-container]# docker history dda43e8b9e21
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
dda43e8b9e21        9 hours ago         /bin/sh -c #(nop)  CMD ["/usr/local/freeswit…   0B
76f11898568d        9 hours ago         /bin/sh -c #(nop)  EXPOSE 64535-65535/udp       0B
124e52a5fdb8        9 hours ago         /bin/sh -c #(nop)  EXPOSE 8021/tcp              0B
d05fa7854fa3        9 hours ago         /bin/sh -c #(nop)  EXPOSE 5066/tcp 7443/tcp     0B
fae9a2737d1b        9 hours ago         /bin/sh -c #(nop)  EXPOSE 5060/tcp 5060/udp …   0B
d817769a9e83        9 hours ago         /bin/sh -c ldconfig                             81.5kB
e4347f4a3f7f        9 hours ago         /bin/sh -c #(nop) ADD file:305fb1821e6c9f4f7…   19.5kB
7fa9aac435b0        9 hours ago         /bin/sh -c #(nop) ADD file:ef5aa87255557ffc9…   340B
2872fc32f715        9 hours ago         /bin/sh -c #(nop) ADD file:aa24cd419ee5ea6ca…   249B
681ea1a83b9b        9 hours ago         /bin/sh -c chmod +x /etc/init.d/freeswitch  …   7.28kB
b3af584e7595        9 hours ago         /bin/sh -c #(nop) ADD file:e450d50f8d60a1861…   5.76kB
8c2e2a5f2de4        9 hours ago         /bin/sh -c #(nop) COPY dir:8ecfbdecc9f674459…   417MB
9a2938106462        9 hours ago         /bin/sh -c #(nop) COPY dir:1ddadc455338c149f…   112MB
d03606f65563        9 hours ago         /bin/sh -c #(nop) COPY dir:cd305b7f15031b530…   20.4MB
3e21911eea87        9 hours ago         /bin/sh -c #(nop) COPY --chown=freeswitch:da…   410MB
6ce315566979        9 hours ago         /bin/sh -c useradd -r -g daemon freeswitch      329kB
b7c170867677        9 hours ago         /bin/sh -c apt-get update     && ln -snf /us…   2.76MB
9a800e72f239        10 hours ago        /bin/sh -c #(nop)  ENV TZ=Asia/Shanghai         0B
77be327e4b63        4 weeks ago         /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B
<missing>           4 weeks ago         /bin/sh -c mkdir -p /run/systemd && echo 'do…   7B
<missing>           4 weeks ago         /bin/sh -c set -xe   && echo '#!/bin/sh' > /…   745B
<missing>           4 weeks ago         /bin/sh -c rm -rf /var/lib/apt/lists/*          0B
<missing>           4 weeks ago         /bin/sh -c #(nop) ADD file:1f70668251e2e58ce…   124MB

dive分析镜像工具

可以看出每一层所添加的大小,并针对大的层进行优化。

分析镜像还有一个很有用的工具dive

下载安装dive

# wget https://github.com/wagoodman/dive/releases/download/v0.9.2/dive_0.9.2_linux_amd64.tar.gz
# tar -zvxf dive_0.9.2_linux_amd64.tar.gz
# cp dive /usr/bin

使用:dive <IMAGE ID> 或者 dive REPOSITORY:TAG

┃ ● Layers ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ Current Layer Contents ├──────────────────────────────────
Cmp   Size  Command                                          Permission     UID:GID       Size  Filetree
    5.2 MB  FROM d970df9dd8bab4b                             drwxr-xr-x         0:0     1.0 MB  ├── bin
                                                             -rwxr-xr-x         0:0     1.0 MB  │   ├── [
│ Layer Details ├─────────────────────────────────────────── -rwxr-xr-x         0:0        0 B  │   ├── [[ → bin/[
                                                             -rwxr-xr-x         0:0        0 B  │   ├── acpid → bin/[
Tags:   (unavailable)                                        -rwxr-xr-x         0:0        0 B  │   ├── add-shell → bin/[
Id:     d970df9dd8bab4b7a604d73763aee9e732d107cf3b02000ca193 -rwxr-xr-x         0:0        0 B  │   ├── addgroup → bin/[
712fe48a460f                                                 -rwxr-xr-x         0:0        0 B  │   ├── adduser → bin/[
Digest: sha256:8a9aa5c55905476e9412fcf0e18942123b239333aafc4 -rwxr-xr-x         0:0        0 B  │   ├── adjtimex → bin/[
d7b3656faf67b980b41                                          -rwxr-xr-x         0:0        0 B  │   ├── ar → bin/[
Command:                                                     -rwxr-xr-x         0:0        0 B  │   ├── arch → bin/[
#(nop) ADD file:d5fc1686f667af49c2cb01970701b62eeb9c818d2d4e -rwxr-xr-x         0:0        0 B  │   ├── arp → bin/[
8f967993dab1de737f28 in /                                    -rwxr-xr-x         0:0        0 B  │   ├── arping → bin/[
                                                             -rwxr-xr-x         0:0        0 B  │   ├── ash → bin/[
│ Image Details ├─────────────────────────────────────────── -rwxr-xr-x         0:0        0 B  │   ├── awk → bin/[
                                                             -rwxr-xr-x         0:0        0 B  │   ├── base64 → bin/[
                                                             -rwxr-xr-x         0:0        0 B  │   ├── basename → bin/[
Total Image size: 5.2 MB                                     -rwxr-xr-x         0:0        0 B  │   ├── bc → bin/[
Potential wasted space: 0 B                                  -rwxr-xr-x         0:0        0 B  │   ├── beep → bin/[
Image efficiency score: 100 %                                -rwxr-xr-x         0:0        0 B  │   ├── blkdiscard → bin/
                                                             -rwxr-xr-x         0:0        0 B  │   ├── blkid → bin/[
Count   Total Space  Path                                    -rwxr-xr-x         0:0        0 B  │   ├── blockdev → bin/[
                                                             -rwxr-xr-x         0:0        0 B  │   ├── bootchartd → bin/
                                                             -rwxr-xr-x         0:0        0 B  │   ├── brctl → bin/[
                                                             -rwxr-xr-x         0:0        0 B  │   ├── bunzip2 → bin/[
                                                             -rwxr-xr-x         0:0        0 B  │   ├── busybox → bin/[
                                                             -rwxr-xr-x         0:0        0 B  │   ├── bzcat → bin/[
                                                             -rwxr-xr-x         0:0        0 B  │   ├── bzip2 → bin/[
                                                             -rwxr-xr-x         0:0        0 B  │   ├── cal → bin/[

可以非常清晰的看到每一层添加的镜像大小以及文件的变化,通过右边的Content可以观察到文件夹的变化。

各种基础镜像的构建

各类基础镜像分析

常用的基础镜像CentOS,Debian,Fedora,Ubuntu,Alpine镜像:

类别 版本 镜像大小(MB) 描述 包管理
CentOS latest 69.84 适用于正式的生产环境镜像制作,但是构建出来的镜像大小会很大,可以来考虑分步构建 yum,rpm
- centos8 69.84 - -
- centos7 72.15 - -
- centos6 66.83 - -
Debian buster-slim 26.46 当前稳定版本的体积更小版本(slim) apt-get, dpkg
- stretch 43.69 旧的稳定版 -
- jessie 52.08 测试版本 -
- stable 48.78 稳定版本 -
- sid 50.54 不稳定版本 -
- wheezy 38.66 Debian 7,被淘汰的稳定版 -
Fedora 33 67.21 稳定版本 dpkg/apt,pacman
- rawhide 67.21 永远不会被冻结, 也永远不会被正式发布. 对稳定性没有任何保证. 主要用于最新代码的初步测试 -
Ubuntu latest 25.9 适用于正式的生产环境镜像制作,但是构建出来的镜像大小会很大,可以来考虑分步构建 apt-get
- 18.04 25.9 - -
- 16.04 42.13 - -
Alpine latest 2.68 面向安全的、轻量级的Linux系统,基于musl libc和busybox,使用glibc库的大型c语言可能会遇到很多坑 apk, lbu
busybox latest 1 包含了基本的linux命令 apk
- glibc 5 GNU C library版本 -
scratch latest 0 空白镜像,sh工具也没有

也可以使用编程语言对应的基础镜像,适合分步构建完成之后在其上运行或者在对应的基础镜像中编译。

各个基础镜像换源

CentOS yum源

mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-6.repo

Ubuntu apt源 清华源:

sed -i 's http://.*.ubuntu.com http://mirrors.tuna.tsinghua.edu.cn g' /etc/apt/sources.list

阿里云源:

sed -i 's http://.*.ubuntu.com http://mirrors.aliyun.com g' /etc/apt/sources.list

Debian apt源

sed -i 's http://.*.debian.org http://mirrors.aliyun.com g' /etc/apt/sources.list

Alphine apk源

阿里源:

sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories

科大源:

sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories

各个基础镜像设置时区方法

Ubuntu

FROM ubuntu:16.04

ENV TZ=Asia/Shanghai
RUN apt-get update \
    && ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone \
    && apt-get install tzdata \
    && apt-get clean \
    && apt-get autoclean \
    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

CentOS

ENV TimeZone=Asia/Shanghai   
RUN ln -snf /usr/share/zoneinfo/$TimeZone /etc/localtime && echo $TimeZone > /etc/timezone

Debian

ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

Alphine

RUN apk add tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
    && echo "Asia/Shanghai" > /etc/timezone \
    && apk del tzdata

把日志文件输出到标准输出流

有时候我们写的程序不是以标准输出到控制台的方式来打印日志,而是存在某个日志文件,这就造成容器跑起来之后docker logs命令查看不到日志,这是因为docker容器只接管标准输出流和标准错误流,因此还需要重定向到stdoutstderr

RUN ln -sf /dev/stdout /var/log/nginx/access.log \
    && ln -sf /dev/stderr /var/log/nginx/error.log