в docker

Оптимизация docker-образов


Как известно, docker-контейнеры создаются и запускаются из образов, которые можно загрузить из общедоступных репозиториев (например, Docker Hub) или собрать самостоятельно с помощью инструкций в Dockerfile.

Самостоятельная сборка образов, на мой взгляд, обладает целым рядом преимуществ, однако в результате можно получить образ неприлично большого размера. Давайте разберемся, как оптимизировать docker-образы, не жертвуя при этом их функциональностью!

Действия производятся на:

lsb_release -a
No LSB modules are available.
Distributor ID:    Ubuntu
Description:    Ubuntu 16.04.2 LTS
Release:    16.04
Codename:    xenial

Используемая версия docker-engine:

docker version
Client:
 Version:      17.03.0-ce
 API version:  1.26
 Go version:   go1.7.5
 Git commit:   60ccb22
 Built:        Thu Feb 23 11:02:43 2017
 OS/Arch:      linux/amd64

Server:
 Version:      17.03.0-ce
 API version:  1.26 (minimum version 1.12)
 Go version:   go1.7.5
 Git commit:   60ccb22
 Built:        Thu Feb 23 11:02:43 2017
 OS/Arch:      linux/amd64
 Experimental: false

В примере рассмотрим сборку docker-образа для контейнера с Nginx (как полагается, с поддержкой Brotli и свеженьким OpenSSL для HTTP2.0). Кроме этого, при запуске контейнера из шаблонов будут генерироваться конфигурационные файлы сайтов (в зависимости от параметров из .env-файла).

Dockerfile (инструкции по сборке образа) выглядит так:

FROM debian:jessie

ENV NGINX_VERSION=1.11.10
ENV OPENSSL_VERSION=1.0.2g
ENV NGX_CACHE_PURGE_VERSION=2.3

RUN apt-get update \
    && apt-get install -y \
    build-essential \
    automake \
    autoconf \
    libtool \
    openssl \
    zlib1g-dev \
    libpcre3 \
    libpcre3-dev \
    libgeoip-dev \
    unzip \
    wget \
    git \
    curl

RUN cd /tmp \
    && git clone https://github.com/bagder/libbrotli \
    && cd libbrotli \
    && ./autogen.sh \
    && ./configure \
    && make && make install \
    && cd /tmp \
    && git clone https://github.com/google/ngx_brotli \
    && wget -qO - https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz \
       | tar xzf  - -C /tmp \
    && wget http://labs.frickle.com/files/ngx_cache_purge-$NGX_CACHE_PURGE_VERSION.tar.gz \
    && tar -zxvf ngx_cache_purge-$NGX_CACHE_PURGE_VERSION.tar.gz && mv ngx_cache_purge-$NGX_CACHE_PURGE_VERSION ngx_cache_purge && rm ngx_cache_purge-$NGX_CACHE_PURGE_VERSION.tar.gz \
    && wget https://github.com/openresty/echo-nginx-module/archive/v0.60.tar.gz \
    && tar -zxvf v0.60.tar.gz && rm v0.60.tar.gz \
    && mkdir -p /var/cache/nginx/client_temp \
    && wget http://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz \
    && tar -xvzf nginx-${NGINX_VERSION}.tar.gz \
    && cd /tmp/ngx_brotli && git submodule update --init \
    && cd /tmp/nginx-${NGINX_VERSION} \
    && ./configure \
        --prefix=/opt/nginx \
        --sbin-path=/usr/sbin/nginx \
    --modules-path=/usr/lib64/nginx/modules \
        --conf-path=/opt/nginx/nginx.conf \
    --error-log-path=/var/log/nginx/error.log \
    --http-log-path=/var/log/nginx/access.log \
        --pid-path=/var/run/nginx.pid \
        --lock-path=/var/run/nginx.lock \
    --http-client-body-temp-path=/var/cache/nginx/client_temp \
    --http-proxy-temp-path=/var/cache/nginx/proxy_temp \
    --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \
    --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \
    --http-scgi-temp-path=/var/cache/nginx/scgi_temp \
        --user=www-data \
        --group=www-data \
        --with-http_stub_status_module \
        --with-http_geoip_module \
        --with-http_ssl_module \
        --with-http_realip_module \
        --with-http_addition_module \
        --with-http_sub_module \
        --with-http_dav_module \
        --with-http_gunzip_module \
        --with-http_gzip_static_module \
        --with-http_random_index_module \
        --with-http_secure_link_module \
        --with-http_auth_request_module \
        --without-http_ssi_module \

        --with-threads \
        --with-stream \
        --with-stream_ssl_module \
        --with-file-aio \
        --with-http_v2_module \
        --with-ipv6 \
        --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic' \

        --with-openssl=/tmp/openssl-${OPENSSL_VERSION} \
        --add-module=/tmp/ngx_brotli \
    --add-module=/tmp/ngx_cache_purge \
    --add-module=/tmp/echo-nginx-module-0.60 \
    && make \
    && make install

RUN apt-get remove -y build-essential zlib1g-dev libpcre3-dev unzip \
    && apt-get autoremove -y \
    && apt-get clean \
    && rm -rf /tmp/nginx-${NGINX_VERSION}* \
    && rm -rf /tmp/ngx_brotli \
    && rm -rf /tmp/ngx_cache_purge \
    && rm -rf /tmp/libbrotli \
    && rm -rf /tmp/openssl-1.0.2g \
    && rm -rf /tmp/echo-nginx-module-0.60

ENV DOCKERIZE_VERSION v0.3.0
RUN wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
    && tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
    && rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz

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

ADD . /opt/nginx/
ARG PUID=1000
RUN usermod -u ${PUID} www-data
EXPOSE 80 443
CMD dockerize -template /opt/nginx/conf.d/main.tmpl:/opt/nginx/conf.d/main-gen.conf -template /opt/nginx/conf.d/landing.tmpl:/opt/nginx/conf.d/landing-gen.conf nginx

Для сборки образа, находясь в каталоге с Dockerfile, запускаем команду:

docker build -t nginx-test:latest .

Проверим наличие нового образа после сборки:

docker images nginx-test 
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
nginx-test          latest              43bf91aeb416        3 minutes ago       732 MB

Размер образа составляет целых 732 MB! Размер, конечно, не столь важен, если мы говорим про образы использующиеся только на локальной машине. Но это становится проблемой, когда нужно постоянно скачивать/отправлять эти образы через интернет.

Для оптимизации docker-образов можно воспользоваться утилитой docker-squash. Установить ее довольно просто:

pip install docker-squash

Данная утилита позволяет объединять слои docker-образов (не забываем о том, что каждая инструкция в Dockerfile создает новый слой, и в нашем образе содержится аж 16 слоев). Слои можно объединять частично (например, N последних или с X по Y) или полностью (как мы и поступим).

Запускаем команду:

docker-squash -t nginx-test:squashed nginx-test:latest
2017-03-20 13:42:39,562 root         INFO     docker-squash version 1.0.5, Docker 60ccb22, API 1.26...
2017-03-20 13:42:39,562 root         INFO     Using v2 image format
2017-03-20 13:42:39,582 root         INFO     Old image has 16 layers
2017-03-20 13:42:39,582 root         INFO     Checking if squashing is necessary...
2017-03-20 13:42:39,582 root         INFO     Attempting to squash last 16 layers...
2017-03-20 13:42:39,582 root         INFO     Saving image sha256:43bf91aeb416da42a75d2a0b825567aaed9742200a081259c85fa2d10e96f2ed to /tmp/docker-squash-sUSIVv/old directory...
2017-03-20 13:43:08,947 root         INFO     Image saved!
2017-03-20 13:43:08,947 root         INFO     Squashing image 'nginx-test:latest'...
2017-03-20 13:43:09,848 root         INFO     Starting squashing...
2017-03-20 13:43:09,849 root         INFO     Squashing file '/tmp/docker-squash-sUSIVv/old/e0d4b8ce48d8fd1930b367ed7aff1f7795b74756ed4ed9062acd18e59f6bdf06/layer.tar'...
2017-03-20 13:43:09,871 root         INFO     Squashing file '/tmp/docker-squash-sUSIVv/old/36675d6554a8ebdf72265fe739214048565925b04a739733b84caddb92702b02/layer.tar'...
2017-03-20 13:43:10,189 root         INFO     Squashing file '/tmp/docker-squash-sUSIVv/old/968002289f33e062ba172190dadeb0046ec6d34407992cbf1ca239d93d23e684/layer.tar'...
2017-03-20 13:43:10,191 root         INFO     Squashing file '/tmp/docker-squash-sUSIVv/old/23bc9170b2cb9a7aee432d93d651288fa506178be14fbadfb630bcda09977ad3/layer.tar'...
2017-03-20 13:43:10,345 root         INFO     Squashing file '/tmp/docker-squash-sUSIVv/old/1d8ab492b6fbfd6d2c00c43cccac25053b2669f590261c58a071204977426dcd/layer.tar'...
2017-03-20 13:43:11,172 root         INFO     Squashing file '/tmp/docker-squash-sUSIVv/old/dcda4589a5d2be254d81216d1b22a26e633d8cc36ed3f1a27117f1ce4e852baf/layer.tar'...
2017-03-20 13:43:11,945 root         INFO     Squashing file '/tmp/docker-squash-sUSIVv/old/d904382d2c552061a5a4198a9e0dcb56fb8515eb0bdc17969b28b366edb8dfb6/layer.tar'...
2017-03-20 13:43:20,774 root         INFO     Squashing file '/tmp/docker-squash-sUSIVv/old/c22ce97cf003214a558f7a68d9a0538e504321bae40a8fe6aad393bf5be114bc/layer.tar'...
2017-03-20 13:43:36,312 root         INFO     Squashing finished!
2017-03-20 13:43:37,681 root         INFO     New squashed image ID is 3c1cfae6f24c14d4417aa6e32bc96d41e2e2caf8fbd0a351b027e2619fc493b1
2017-03-20 13:43:45,236 root         INFO     Image registered in Docker daemon as nginx-test:squashed
2017-03-20 13:43:45,297 root         INFO     Done

Проверим информацию о docker-образах с именем nginx-test (теперь их станет 2):

docker images nginx-test 
REPOSITORY          TAG                 IMAGE ID            CREATED              SIZE
nginx-test          squashed            3c1cfae6f24c        About a minute ago   399 MB
nginx-test          latest              43bf91aeb416        9 minutes ago        732 MB

Как видим, последний образ с тегом squashed уменьшился на 46%.

Следует отметить, что docker-engine начиная с версии 1.13 в экспериментальном режиме содержит возможность сборки уже сжатых образов. Для этого в команду сборки необходимо добавить параметр --squash, например:

docker build --squash -t nginx-test:late .
Error response from daemon: squash is only supported with experimental mode

Чтобы включить експериментальный режим docker-engine в Ubuntu 16.04 необходимо в конфигурационном файле /lib/systemd/system/docker.service добавить параметр --experimental=true.

В моем случае это выглядит так:

[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
After=network.target docker.socket firewalld.service
Requires=docker.socket

[Service]
Type=notify
# the default is not to use systemd for cgroups because the delegate issues still
# exists and systemd currently does not support the cgroup feature set required
# for containers run by docker
ExecStart=/usr/bin/dockerd -H fd:// --experimental=true
ExecReload=/bin/kill -s HUP $MAINPID
LimitNOFILE=1048576
# Having non-zero Limit*s causes performance problems due to accounting overhead
# in the kernel. We recommend using cgroups to do container-local accounting.
LimitNPROC=infinity
LimitCORE=infinity
# Uncomment TasksMax if your systemd version supports it.
# Only systemd 226 and above support this version.
TasksMax=infinity
TimeoutStartSec=0
# set delegate yes so that systemd does not reset the cgroups of docker containers
Delegate=yes
# kill only the docker process, not all processes in the cgroup
KillMode=process

[Install]
WantedBy=multi-user.target

После изменения конфигурационного файла выполняем:

sudo systemctl daemon-reload
sudo systemctl restart docker

Проверим информацию о версии docker-engine (следует обратить внимание на последнюю строку):

docker version
Client:
 Version:      17.03.0-ce
 API version:  1.26
 Go version:   go1.7.5
 Git commit:   60ccb22
 Built:        Thu Feb 23 11:02:43 2017
 OS/Arch:      linux/amd64

Server:
 Version:      17.03.0-ce
 API version:  1.26 (minimum version 1.12)
 Go version:   go1.7.5
 Git commit:   60ccb22
 Built:        Thu Feb 23 11:02:43 2017
 OS/Arch:      linux/amd64
 Experimental: true

Теперь можно сразу собирать сжатые docker-образы командой:

docker build --squash -t nginx-test:late .

Добавить комментарий