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

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

Скрытый контент
Комментарии (67)
Альберт

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

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

Использовать для изображений отдельный файловый хостинг и загружать на него файлы из 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?

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

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

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

Google
GitHub
Yandex
MailRu