Pull to refresh

Comments 26

Отличный набор рекомендаций как делать не надо…
1) networks:
— app-network
указывать явно сеть не нужно, docker-compose и так сведет эти контейнеры в одну сеть
2) container_name: app
лучше не указывать явно имя для контейнеров, это может привести к конфликтам с другими проектами и прочим проблемам
вместо этого следует использовать директиву depends_on для связи между сервисами
3)
# Install dependencies
RUN apt-get update && apt-get install -y \

# Clear cache
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
Запуск этих двух команд в разных слоях убивает весь смысл 2-й команды

И таких недочетов в приведенных скриптах еще вагон…

По правде говоря, когда я делал это все у меня не было цели сделать правильно. Была цель просто сделать. А как правильно я надеюсь получить у вас в комментариях

Алексей, а не сможете написать какие ещё ошибки допущены? Желательно с пояснениями почему именно так делать нельзя

Ну еще из того что режет глаза это монтирование путей на сервере внутрь контейнеров:


volumes:
      - ./:/var/www

Это очень плохая практика которая может создать в дальнейшем множество проблем. Такое решение может быть приемлемым для локальной разработки (и то его необходимо использовать с осторожностью поскольку оно легко приводит ко множеству трудно воспроизводимых багов из-за разницы в окружениях), но его однозначно не стоит использовать на серверах. Тут как раз можно использовать разные наборы docker-compose файлов для локально разработки и для сервера

В смысли, что файлы проекта нужно держать внутри докера и не монтировать их снаружи утратив контроль?

Если мы говорим о продакшен конфигурации для laravel и вообще PHP и подобных языках, то да, исходники, vendor и т. п. должны быть в образе докера, монтироваться только файлы с данными, типа загружаемых пользователем аватарок и/или расшариваемые между контейнерами данные типа public, расшариваемый с nginx.


В случае с Laravel есть спорный момент насчёт storage — нужно ли его монтировать, а если нужно то на постоянные тома или временные, переживающие перезапуск контейнера, но не переживающие перезапуск системы.

Это вредные советы для использования Docker и Gitlab CI, ни в коем случае так не делайте.
UFO just landed and posted this here
На самом деле так и есть. Я только начал изучать докер и гитлаб, и была очень большая сложность с тем, чтобы понять где что находится, что и где нужно располагать и вообще с какой точки начинать. А статью написал в надежде, что она возможно поможет начать такому же новичку как я, а мне получить рекомендации куда двигаться дальше.

Хотелось бы все таки пояснений

Я пытался написать хоть какие-то адекватные пояснения, но понял что надо писать отдельную статью. Тут в рамках одного комментария не напишешь.
ReDev1L, Alexei_987 если не трудно киньте ссылок на чтиво, где будет написано как правильно, если такое есть. Думаю всем будет полезно, в том числе и автору.

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

Большую часть вещей стоит делать исходя из здравого смысла.

Например в моем случае общий подход такой:
1) Докерфайлы надо писать так чтобы время пересборки образа при изменении кода было минимальным. Для этого обычно докерфайл структурируется на 3 части:
* Установка системных пакетов (которые меняются очень редко)
* Установка проектных зависимостей (composer, etc.)
* Копирование/установка кода
В результате должен получаться образ готовый к запуску без каких либо дальнейших действий (при старте конфиги монтируются в предопределенную папку).

2) Для сборки всех нужных образов я обычно использую простенький Makefile (ну или можно брать скрипты по вкусу)

3) В docker-compose лучше использовать только директиву image и не использовать директиву build (образы уже собраны на этапе 2), такой подход удобнее для деплоя на серверах куда прилетают уже готовые образы. Кроме того docker-compose позволяет реализовать «наследование» файлов и обычно у меня базовая конфигурация сервисов описана в файле compose-base и она наследуюется файлом который дополняет ее под конкретное окружение (local/dev/stage/prod). В итоге сам docker-compose вызывается примерно вот так:
$ docker-compose -f compose-base.yml -f compose-local.yml
Делает у меня это тоже Makefile

4) Для гитлаба обычно я собираю отдельный образ («build-box») с теми зависимостями которые нужны для запуска пайплайнов, тогда можно большую часть команд из before_script упаковать в этот образ и экономить время на всех пайплайнах

А за это большое спасибо!
Не сможете дать какой нибудь пример с пояснением как структурировать dockerfile?

Перед сборкой образа нужно проконтролировать какие именно файлы попадают в build-context и настроить .dockerignore таким образом чтобы лишние файлы не были в него включены. К лишним относится папка .git, и прочие локальные файлы которые не нужно копировать в финальный образ


Пример 1 (Простой) — https://gist.github.com/Alexei-Kornienko/b048cc969798428740658393018e91c9


Данный Dockerfile использует multi-stage builds (https://docs.docker.com/develop/develop-images/multistage-build/)
Посколько одним Dockerfile описываются правила сборки сразу 2 образов:


  • минимальный production образ который используется на серверах
  • чуть более толстый тестовый образ в котором есть дополнительные зависимости необходимые для запуска тестов, а также сам код тестов

Схема стейджей выглядит вот так — image


1) Выбираем базовый образ наиболее подходящий под наше приложение. В данном примере благодаря этому нет необходимости доставлять системные пакеты
2) Добавляем файл который описывает проектные зависимости нашего приложения и устанавливаем их (https://gist.github.com/Alexei-Kornienko/b048cc969798428740658393018e91c9#file-simple-dockerfile-L4). Если правильно построить процесс сборки образа то эти слои будут пересобираться очень редко и будут переиспользоваться по всех образах


3) Далее собирается отдельный стейдж с тестовыми зависимостями которые также меняются редко (test_base)


4) После этого в образ добавляется непосредственно код приложения — https://gist.github.com/Alexei-Kornienko/b048cc969798428740658393018e91c9#file-simple-dockerfile-L15. При нормальном процессе разработки как правило все слои выше этой строчки закешированы и пересобираются только нижележащие слои


5) Отдельно собирается тестовая версия образа — https://gist.github.com/Alexei-Kornienko/b048cc969798428740658393018e91c9#file-simple-dockerfile-L19


В результате у меня например получается вот такой образ:
app develop c7f7a0ff09d3 2 minutes ago 181MB


Очень полезно будет воспользоваться командой docker history для того чтобы посмотреть его слои:


c7f7a0ff09d3        2 minutes ago       |1 version=v0.3.6 /bin/sh -c echo $version &…   84B                 
6cfd15cab214        2 minutes ago       /bin/sh -c #(nop)  ARG version=unknown          0B                  
2d1197adf7ef        2 minutes ago       /bin/sh -c #(nop) COPY dir:753c8e55ed96ed762…   112kB               
f6ae0c62044f        4 months ago        /bin/sh -c pip install -r requirements.txt      37.2MB              
57c774cffbf4        4 months ago        /bin/sh -c #(nop) COPY file:6998a5aae7dd8b37…   133B                
59fb244f987c        7 months ago        /bin/sh -c #(nop)  ENV PYTHONUNBUFFERED=True    0B                  
c00de12b7b5a        7 months ago        /bin/sh -c #(nop) WORKDIR /app              0B                  
338ae06dfca5        9 months ago        /bin/sh -c #(nop)  CMD ["python3"]              0B                  
<missing>           9 months ago        /bin/sh -c set -ex;   savedAptMark="$(apt-ma…   7.26MB              
<missing>           9 months ago        /bin/sh -c #(nop)  ENV PYTHON_PIP_VERSION=19…   0B                  
<missing>           9 months ago        /bin/sh -c cd /usr/local/bin  && ln -s idle3…   32B                 
<missing>           9 months ago        /bin/sh -c set -ex   && savedAptMark="$(apt-…   74.3MB              
<missing>           9 months ago        /bin/sh -c #(nop)  ENV PYTHON_VERSION=3.7.3     0B                  
<missing>           9 months ago        /bin/sh -c #(nop)  ENV GPG_KEY=0D96DF4D4110E…   0B                  
<missing>           9 months ago        /bin/sh -c apt-get update && apt-get install…   6.48MB              
<missing>           9 months ago        /bin/sh -c #(nop)  ENV LANG=C.UTF-8             0B                  
<missing>           9 months ago        /bin/sh -c #(nop)  ENV PATH=/usr/local/bin:/…   0B                  
<missing>           9 months ago        /bin/sh -c #(nop)  CMD ["bash"]                 0B                  
<missing>           9 months ago        /bin/sh -c #(nop) ADD file:5ffb798d64089418e…   55.3MB    

Основная задача это сделать так чтобы наиболее толстые слои были расположены ниже в истории, а те слои которые часто пересобираются были вверху и имели минимально возможный размер.
В данном примере сборка образа с нуля занимает — real 0m54.348s.
Пересборка образа при изменении кода — real 0m18.450s (На старом HDD, на относительно новом SSD порядка 5 секунд)


Пример 2 (Сложный) — https://gist.github.com/Alexei-Kornienko/a50f22f1e30839596c985684e8c0fdae


Основная идея таже самая, данный Dockerfile намного сложнее изза того что пришлось заворачивать в контейнер legacy приложение изначально написанное без учета возможностей контейнеризации.
Также используется немного другая схема стейджей — image изза необходимости компилировать большое количество статических файлов и всяких js скриптов. В результате в финальный образ переносятся только скомпилированные артефакты без кучи js мусора.

Спасибо большое, это то что нужно!
Исходя из текста непонятно почему вы ваш подход считаете лучше, чем подход автора статьи.
У автора есть множество мелких недочетов в организации процесса которые делают его не очень эффективным и довольно хрупким (но это уже вопрос опыта наступания на грабли). Из самых больших проблем которые я вижу это установка гитлаб раннера на сервер и сборка образов и запуск прямо на «боевом» сервере. Это очень плохо с точки зрения безопасности. Кроме того такой подход вызовет проблемы с производительностью если сервер обслуживающий клиентов будет заодно запускать юнит тесты для десятков пайплайнов гитлаба.
Гораздо лучший вариант это иметь отдельный раннер который выполняет все пайплайны и уже готовые образы запускает на сервере/кластере.
Как уже написали выше для того чтобы описать все недостатки приведенных скриптов и расписать как правильно (и почему) надо писать отдельную статью, на это у меня сейчас к сожалению нет времени.

Отдельный раннер на отдельном сервере, извините за глупый вопрос? А как образ должен попадать на боевой сервер? С помощью ssh — экзекутора?

У меня обычно пайплайн собирает новый образ приложения с временным именем образа и запускает тесты для этого нового образа, в случае успешного прохождения всех тестов данный образ получает полное имя и заливается в docker-registry (открытый либо закрытый в зависимости от проекта). Следующий пайплайн дергает хук на сервере и сервер уже тянет хорошие образы из этого registry. Это позволяет хранить версии образов на registry и откатываться на нужную стабильную версию при необходимости.

Настройте .gitlab-ci.yml в своем проекте.

Начал писать большой комментарий, понял что замечаний много чтобы из головы текстом описывать и могут быть ошибки. Начал поднимать локально с нуля пошагово. Пока вот так https://gitlab.com/volch/laravel-demo


Если есть вопросы по конфигам (собственно ничего и нет больше), то спрашивайте. Ну и не знаю имеет ли смысл продолжать дальше, по CI в частности.

Вообще про сборку контейнера много статей написано. Но вот про нюансы именно ларавеля все авторы забывают или забивают. Например, представьте проект: Laravel 6 + Postgres 12 + Nginx + Vue + Horizon + Websockets + Redis. И всё из коробки. А теперь все технологии надо разложить по контейнерам. Связать сетью и вывести наружу только Nginx. А еще выполнить несколько artisan команд перед запуском. Чуть не забыл про сертификат и https. А локально еще и xdebug. Vue собирается с помощью laravel-mix.

Про какие, например, нюансы забывают?


Ну и docker-compose в принципе слабо подходит для автоматизации сложных флоу CI/CD с командами и прочим c минимальным даунтаймом, а лучше без него.

Например, про запуск очередей и вебсокета в отдельных контейнерах, а не через supervisor. Про certbot или traefik. Про сбор логов всех контейнеров в одном месте. Про сборку фронта в одном контейнере с nginx. Вроде бы мелочи, но ни в одном блоге, мануале, примере такого не нашел. Чтоб кто то взял, разложил настройку для крупного проекта, расписал конфиги, как правильно всё настроить и не изобретать велосипед каждый раз.
Sign up to leave a comment.

Articles