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

Оптимизация Docker-образов с кодом приложения для production через более грамотное кэширование слоёв и мультистадийную сборку.

  • 00:01:23 Установка пакетов в образе
  • 00:03:04 Отключение скриптов установки
  • 00:03:53 Производительность сборки слоёв
  • 00:05:40 Перемещение слоя с vendor
  • 00:08:26 Установка OPcache
  • 00:10:40 Мультистадийная сборка
  • 00:16:00 Обзор результата
Скрытый контент (код, слайды, ...) для подписчиков. Открыть →
Дмитрий Елисеев
elisdn.ru
Комментарии (77)
Альберт

Подскажите как быть к примеру с папкой изображений, если это интернет-магазин, не будешь же ее в образ копировать.

Ответить
Дмитрий Елисеев

Использовать для изображений отдельный файловый хостинг и загружать на него файлы из PHP по S3 или FTP.

Ответить
Вопросник

А есть ли еще какие-то варианты, можно же монтировать файлы через volumes прямо внутрь нужных контейнеров для прода?

Ответить
Дмитрий Елисеев

Если сервер один, то по аналогии с базами данных можно примонтировать папку uploads через вольюм. Но если серверов будет не один, а несколько с балансировкой, то к нескольким серверам одну папку примонтировать не получится.

Ответить
slo_nik

Добрый вечер.

Подскажите, как побороть проблему с сохранением файлов.

Требуется генерировать pdf файл на сайте.

Использую symfony bundle для генерации pdf, файл сохраняю в public/pdf.

После сохранения вывожу ссылку на файл.

На localhost всё работает, а когда выгружаю на рабочий сервер, то ссылка на файл не работает, выдаёт 404.

Посмотрел контейнеры fpm и nginx на localhost, в директории public/pdf, в обоих контейнерах, сохраняет файл.

На рабочем сайте файл сохраняется только в контейнере fpm, а в nginx его нет. Как я понимаю, при попытке открыть файл по ссылке обращение идёт к контейнеру nginx? Но раз в нём нет файла, то и получаю 404. И естественно, при deploy теряю все сгенерированные файлы.

В чём может быть причина?

Ответить
Дмитрий Елисеев

Причина в том, что на продакшене nginx и php-fpm - это отдельные контейнеры со своими копиями папки public, очищающимися после перезапуска.

Если они запущены на одном сервере, то как временное решение можно в продакшене через volumes создать общий том для файлов, который примонтировать в оба сервиса по адресу public/pdf.

Но как более корректное решение все сгенерированные или загруженные пользователем файлы обычно помещают в отдельный сервис файлового хранилища со своим томом:

services:
    nginx:
        ...
    php-fpm:
        ...
    storage:
        ...
        volumes:
            storage: /data

И потом уже из php-fpm загружают в него файлы по FTP или S3. А из nginx-контейнера можно сделать проксирование на это хранилище по какому-нибудь префиксу /static

Ответить
slo_nik

Директория data не важно где будет расположена? Или в корне системы или в директории /home/username?

Ответить
Дмитрий Елисеев

Зависит от того, какой сервис хранилища будете использовать.

Ответить
slo_nik

использовать, как Вы сказали, более корректное, в отдельном сервисе storage. Как в Вашем примере выше.

Но пока сайт не совсем рабочий, в стадии разработки, думаю сделать простой вариант, хранение через volume.

Ответить
slo_nik

Если они запущены на одном сервере, то как временное решение можно в продакшене через volumes создать общий том для файлов,

Вы имеете ввиду так сделать?

services:
    project-nginx:
        build:
            context: ./project/docker/development
            dockerfile: nginx.docker
        volumes:
             - ./project/public/pdf:/app/public/pdf
        depends_on:
             - project-php-fpm
        ports:
             - "80:80"
Ответить
Дмитрий Елисеев
services:
    nginx:
        ...
        volumes:
             - pdf:/app/public/pdf
    php-fpm
        ...
        volumes:
             - pdf:/app/public/pdf
volumes:
     pdf:
Ответить
slo_nik

Понял, буду пробовать.

В этом случае ссылка на файл будет такой?

http://domain.ru/pdf/file.pdf

Ответить
Дмитрий Елисеев

Да, если добавите проксирование в nginx:

location /pdf/ {
    set $upstream http://storage;
    proxy_set_header  Host $host;
    proxy_pass        $upstream;
    proxy_redirect    off;
}
Ответить
slo_nik

Не совсем понял.

Если я хочу вместо http://storage использовать доменное имя сайта, то как в этом случае?

location /pdf {
    set $upstream http://domain.ru;

Так или нет?

Ответить
Дмитрий Елисеев

Нет. Это внутреннее проксирование рядом с проксированием на php-fpm.

Ответить
slo_nik

Спасибо, буду пробовать.

Думаю, что получится)

Ответить
Дмитрий Елисеев
server {
    ...
    resolver 127.0.0.11 ipv6=off;

    location /pdf/ {
        set $upstream http://storage;
        proxy_set_header  Host $host;
        proxy_pass        $upstream;
        proxy_redirect    off;
    }

    location ~ \.php$ {
        set $upstream php-fpm:9000;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass $upstream;
        fastcgi_index index.php;
        ...
    }
}
Ответить
slo_nik

А почему именно api-php-fpm?

Ответить
Дмитрий Елисеев

Исправил.

Ответить
slo_nik

Благодарю за помощь.

Пошёл пробовать.

Ответить
slo_nik

Добрый вечер.

Не получается что-то.

Вроде сделал всё как Вы рекомендуете, но получаю ошибку 502

> [error] 30#30: *6 storage could not be resolved (3: Host not found), client: ***.***.***.***, server: site.ru, request: "GET /pdf/929ba7c6-5508-4301-ac67-f6388bc56631.pdf HTTP/1.1", host: "site.ru", referrer: "http://site.ru/admin/work/companies/929ba7c6-5508-4301-ac67-f6388bc56631"

Вот полный конфиг nginx в production

    server{
       listen 80;
       server_name site.ru;
       index index.php index.html;
       root /app/public;
       charset utf-8;

       add_header X-Frame-Options "SAMEORIGIN";

       location ~* \.(?:ico|gif|jpe?g|png|woff2?|eot|otf|ttf|svg|js|css)$ {
          access_log off;
          expires max;
          add_header Pragma public;
          add_header Cache-Control "public";
          try_files $uri /index.php?$args;
       }

       location / {
          try_files $uri /index.php?$args;
       }

       resolver 127.0.0.11 ipv6=off;

       location /pdf/ {
          set $upstream http://storage;
          proxy_set_header Host $host;
          proxy_pass $upstream;
          proxy_redirect off;
       }

       location ~ \.php$ {
          set $upstream php-fpm:9000;
          fastcgi_split_path_info ^(.+\.php)(/.+)$;
          fastcgi_pass $upstream;
          fastcgi_index index.php;
          include fastcgi_params;
          fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
          fastcgi_param PATH_INFO $fastcgi_path_info;
       }
    }
Ответить
Дмитрий Елисеев

storage could not be resolved (3: Host not found)

Значит сервис storage не поднялся.

Ответить
slo_nik

Это понятно. Может что-то не так делаю. Смотрел Ваши репозитории, для api и frontend у Вас созданы отдельные директории, со своими файлами конфигураций.

В моём случае нет директории storage. Нужно ли её создавать и создавать в ней файл конфигурации для nginx?

Ответить
Дмитрий Елисеев

Нужно не просто создать папку, а в docker-compose объявить полноценный сервис файлового хранилища storage на основе образа вроде minio для работы по протоколу S3 или какого-нибудь другого для работы по FTP. И ему как для postgres объявить volume и placement.

Ответить
slo_nik

Понятно, буду пробовать.

По поводу простого варианта хранилища

    services:
    nginx:
        ...
        volumes:
             - pdf:/app/public/pdf
    php-fpm
        ...
        volumes:
             - pdf:/app/public/pdf
    volumes:
       pdf:

Добавил в docker.yml, файлы записываются и отображаются по ссылке. Но при попытке обновить файл всё равно отображается старая версия.

Добавил разрешение за запись

    volumes:
       - pdf:/app/public/pdf:rw

Файлы стали перезаписываться.

Но иногда надо всё-равно обновить страницу просмотра файла, чтобы отобразилась новая версия. Это связано с кэшем в docker? Как сделать, чтобы файлы не кэшировались?

Ответить
Дмитрий Елисеев

Кэш обычно в браузере по заголовкам из nginx. Можете запретить кеширование pdf-файлов через Cache-Control.

Ответить
slo_nik

В конфигурации nginx?

Ответить
Дмитрий Елисеев

Да, в вашей секции:

location /pdf/ {
   ...
}
Ответить
slo_nik

Благодарю за подсказки.

Ответить
Дмитрий Елисеев

Либо вместо этого использовать любой готовый файловый хостинг, чтобы не поднимать свой storage.

Ответить
slo_nik

В этом вопросе ещё до конца не определились. Неизвестно, сколько файлов будет в конечном счёте.

Хотя думаю, что надо разобраться со своим storage, лишним не будет.

Ответить
slo_nik

только тогда как будет выглядеть ссылка на файл?

Ответить
Дмитрий Елисеев

Также.

Ответить
Arunas

в prod режиме как изменить параметры в файле параметров (напр.: params.php), где виде масива, хранится параметры: 'limitRowTable' => 20, 'limitRowSearchTable' => 35, и т.д. ? Каждый раз передеплоит сайт ?

Ответить
Дмитрий Елисеев

Да, все изменения через передеплой.

Ответить
Arunas

а где лудшее тогда хранит таких параметров для многократного использования?

Ответить
Дмитрий Елисеев

Часть можно хранить в переменных окружения.

Ответить
Arunas

у Вас на локалке стоит Ubuntu 18 или Debian.? (никак не получается make site error: ERROR! The file hosts.yml is marked as executable, but failed to execute correctly. If this is not supposed to be an executable script, correct this with chmod -x hosts.yml. ERROR! problem running /var/www/projects/auction/provisioning/hosts.yml --list ([Errno 8] Exec format error) ERROR! hosts.yml:6: Expected key=value host variable assignment, got: ssh )

Ответить
Дмитрий Елисеев

На локалке Ubuntu.

Ответить
Arunas

если у меня Ubuntu 16.04.6, то ansible-playbook будет ли коректно действовать?

Ответить
Дмитрий Елисеев

Утилита популярная, так что везде должна работать.

Ответить
Arunas

а будет чат (с centrifugo или под.)?

Ответить
Дмитрий Елисеев

Будут уведомления с Centrifugo.

Ответить
Arunas

:)

Ответить
Arunas

Наконец, сегодня деплоил проект https://demo-auction.skucai.com

Деплой заработал с исключением: для docker login задействовало 2 параметра - Username и Password, а в provisioning/docker-login.yml указанно 3: Registry, Username, Password. (В хостинге делал напрямую docker login). Почему не сработало make docker-login?

Ответить
Ruslan

Я еще не дошел до полного деплоя, не могли бы вы добавить в ДНС www поддомен? Мне кажется, что в скрипте сербота должен быть www поддомен для получения сертификата.

Ответить
Sergei

Т.е. я так понял, что бы сделали образ builder, де факто проинсталлированный композер с вендором, просто чтобы второй + n раз не скачивать/устанавливать?

Ответить
Дмитрий Елисеев

Да, сделали чтобы собирать чистовые образы легковесными без лишнего мусора.

Ответить
Arunas

в каком образе, в каком месте (при быльде для прода) собырается-копируется каталог api/src? Напр. api/public копируется в api/docker/production/nginx/Dockerfile (стр.: COPY ./public ./public), а где есть (COPY ./crc ./src) ни где ненашёл :(...

Ответить
Дмитрий

Какой сервер нужен для работы проекта? Я заказал на vscale самый дешевый за 200 руб. в месяц на убунте. Но похоже он не вывозит. При поднятии контейнеров пишет Killed и докер демон полностью отрубается.

Ответить
Дмитрий

Удалось поднять 3 контейнера на продакшн: gateway, frontend, registry. Реестр контейнеров поднял на этом же сервере заранее, запушил туда свои контейнеры и переподнял вот эти три. Не понятно сейчас с сервером что делать, я вроде видел что у Дмитрия тоже за 200 р. сервер, но на Debian. Перезаказать на Debian?

Ответить
Дмитрий Елисеев

У нас пока Debian за 200. Со временем потребуется наращивание мощности или числа виртуалок для добавления прочего софта.

Чтобы процессы не отваливались из-за нехватки памяти в пиках можно добавить файл подкачки:

dd if=/dev/zero of=/swap bs=1M count=1024
mkswap /swap
swapon /swap
echo '/swap none swap sw 0 0' | tee -a /etc/fstab
Ответить
Дмитрий

Применил. Заработало! Текущий проект поднялся!

Ответить
Олег

Дмитрий, а разве  OPCache не идет по умолчанию в php? Из документации: Это расширение доступно по умолчанию с PHP 5.5.0

Или докер образ alpine его не содержит?

Ответить
Дмитрий Елисеев

В образах его по умолчанию нет. Список встроенных можно вывести командой:

docker run --rm php:7.4-cli-alpine php -m
Ответить
Максим

Огромный минус - ссылка на гитхаб под каждым видео ведёт на некий финальный результат. Полностью не соответствует тому, что происходит на видео. Почему бы не бранчевать результат работы каждого выпуска? В том же мастере хранить актуальный-финальный результат на какой-то момент времени.

Ответить
Дмитрий Елисеев

Ветки здесь не подойдут, так как все коммиты идут друг за другом. Только можно разметить тэгами.

Без разметки неудобно только при просмотре на GitHub или в консоли. Но если использовать любой GUI или пользоваться вкладкой Git/Log в PHPStorm, то смотреть коммиты и переключаться будет удобнее.

Ответить
Максим

Может не совсем корректно описал, мы немного о разном. Предложение было такое: итогом работы в рамках конкретного урока является бранч, а мастер, например, финальный результат. И тут неважно, как были сделаный коммиты, пусть они даже были засквошены, главное, что в бранче лежит результат, достигнутый в рамках урока, который можно скопировать для опытов. Сейчас очень неудобно копаться в истории гита, даже в гуи.

Ответить
Дмитрий Елисеев

Я и пишу, что от бранчей здесь не больше пользы, как от тегов. На тег также мождо сделать checkout.

Ответить
kashamamina

а почему на dev мы запускали apk update, а на prod нет?

Ответить
Дмитрий Елисеев

Вместо apk update && apk add удобнее использовать сразу apk add --no-cache

Ответить
kashamamina

а почему Вы говорите что ставим флаг --no-scripts, потом показываете следующей коммит и там его уже нету, аналогично его и нету в проекте на гитхабе?

Ответить
Дмитрий Елисеев

Во всех строках с установкой hirak/prestissimo он есть.

Ответить
Евгений Горяев

Прошу прощения за некропостинг, но хотелось бы понять, как правильно готовить многоэтапную сборку, когда нужно поставить ряд стандартных расширений пхп, которых нет в алпейне, но которые нужны почти всегда - gd, imap, intl, zip - для всех них необходимо поставить кучу пакетов и сходниками в контейнер (libzip-dev, libpng-dev, libwebp-dev, libfreetype6-dev. libmemcached-dev и тд.

Соответственно, в новый контейнер надо перенести .so собранные расширения пхп, чтобы в новом контейнере были только сами расширения, а не исходные коды, необходимые для их сборки...

вот хотелось бы этот момент уточнить, как это правильно делать.

Ответить
Дмитрий Елисеев

Можно собрать отдельно по docker-php-ext-install, а потом уже в чистовик скопировать *.so файлы, доустановить libzip вместо libzip-dev, включить по docker-php-ext-enable как здесь. Но выигрыш от отсутствия исходников будет незначительный.

Ответить
Евгений Горяев

Спасибо. Все понял.

Ответить
slo_nik

Добрый вечер.

Дмитрий, подскажите пожалуйста, в примере по Вашей ссылке для директории extensions используется такая директория.

ENV PHP_EXT_DIR /usr/local/lib/php/extensions/no-debug-non-zts-20200930

Из чего формируется название директории no-debug-non-zts-20200930?

Если использовать подход при установке расширений, как показано по ссылке, то непонятно как угадать название директории.

Ответить
Дмитрий Елисеев

Из чего формируется название директории no-debug-non-zts-20200930?

Формируется из версии PHP API, которую можно подсмотреть в информации:

$ php -i

PHP API => 20200930
PHP Extension => 20200930
Zend Extension => 420200930
Zend Extension Build => API420200930,NTS
PHP Extension Build => API20200930,NTS
...
extension_dir => /usr/lib/php/20200930
Ответить
slo_nik

Благодарю.

Ответить
Игорь

Дмитрий, как композер узнает про проектные классы если вы инсталите до копирования проекта, после копирования тогда необходимо RUN composer dump

Ответить
Дмитрий Елисеев

Он пропишет себе в дамп папку src из секции "autoload". Сами классы его не интересуют.

Ответить
Игорь

тогда почему при обращении и сборке как у Вас сервис говорит не могу найти \API\Hi класс, но стоит мне сначало скопировать и проинталировать или запустить composer dump - работает

Ответить
Дмитрий Елисеев

А вы перед сборкой копируете composer.json и composer.lock?

Ответить
Игорь

Да, стоит копировать src вместе то работает, как только выношу как у Вас то теряются классы, но только composer dump лечит

Ответить
Artem Anoshin

Дмитрий, каждый раз надо перелогиниваться через гугл, может можно пофиксить?

Ответить
Павел

Получается, что на продакшне мы копируем проект в 3 разных образа? В nginx, в php-fpm и в php-cli? Если так, то для чего это делается (можно же через volume из одного места пробросить)? С подходом через копирование в образы работать будет быстрее или это классический способ такой?

Ответить
Дмитрий Елисеев

Это классический подход, когда всё приложение запаковано в образ, а через volume подключаются только папки для хранения данных. Про это ответил ранее на похожий вопрос про образы.

Ответить
Зарегистрируйтесь или войдите чтобы оставить комментарий

Или войти через:

Yandex
MailRu
GitHub
Google