• Как написать Docker файл для связки Front+Back+DB?

    И вам привет.

    Docker-compose вам в помощь, для начала. Голыми командами докера оперировать достаточно муторно, да и не требуется в реальных условиях.

    Возможно ли это всё запихнуть в контейнер и связать вместе?


    В один контейнер все это дело запихнуть однозначно можно, но - не нужно. Как понять, из какого набора контейнеров будет состоять ваша платформа? Очень просто.

    есть проект состоящий из Front (JS+React) + Back (бинарник на Go + config files) + DB (PostgreSQL)


    Вот и определились. Ваше приложение - состоит из frontend, backend и database. Названия взяты исключительно ради примера, но я не думаю, что в реалиях разработки продуктовых систем - вашему тим лиду понравится название "binarnik_na_go" вместо названия "backend" на код-ревью.

    Нужно ли в этот Docker файл интегрировать саму среду PostgreSQL или можно только базу данных?


    В каждом контейнере - содержится только одно приложение. Каждый контейнер - выполняет только одну команду. Если говорить о вашем бинарнике на Go, то команда, которую будет выполнять контейнер с backend - будет именем бинарника. Если это контейнер с БД, то в нем будет выполнятся только одна команда - запускающая сервер PostgreSQL. Для фронта - это будет команда pm2, или другая, с помощью которой вы запускаете сервер frontend.

    Бинарник бэка запускается на определённом порту, будет ли доступ к этому бинарнику по порту из-вне?


    Когда вы напишите конфигурацию своих сервисов в docker-compose.yml - и сделаете docker-compose up, docker-compose создаст виртуальную сеть. Сеть будет называться в формате <наименование директории, где лежит ваш docker-compose.yml>_default. Внутри этой сети, все сервисы будут иметь доступ друг к другу по имени сервиса и порту, указанному в EXPOSE в Dockerfile сервиса. Соответственно, ваш backend будет доступен по имени backend и порту, который вы укажите при сборке образа. Например, http://backend:8080. При этом, вы не сможете обратиться к этому имени/порту за пределами виртуальной сети. Чтобы вытащить порт "наружу", потребуется настроить биндинг портов от контейнера с backend на локальный хост, делается это с помощью директивы ports в описании сервиса в файле docker-compose.yml.

    Спрашиваю потому, что если запущу несколько подобных контейнеров не будут ли они конфликтовать за порты?


    Внутри сети - конфликтов не будет. Т.к. запустить несколько контейнеров с одинаковым именем их сервиса - не возможно. Но, можно скалировать контейнеры - с помощью определенных команд, о которых на данном этапе говорить нет смысла. На локальный хост, соответственно, вытащить два и более сервиса на один и тот же порт не получится, по понятным причинам.

    Или каждый образ надо кофигурировать под отдельный порт?


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

    К практике.

    Dockerfile для backend-а на Go может выглядеть так:

    # Тут берем официальный образ Golang для компиляции бинарника
    # В нем есть все для сборки, за редким исключением
    FROM golang:stretch as builder
    
    # Устанавливаем рабочую директорию внутри образа сборки бинарника
    WORKDIR /app
    
    # Копируем содержимое директории с вашим проектом в образ для сборки
    COPY . .
    
    # Скачиваем зависимости
    RUN go get -d -v
    
    # Собираем бинарник в директорию /app/bin
    RUN GOOS=linux GOARCH=amd64 go build -i -o bin/backend main.go
    
    # А теперь берем минимальный образ для запуска бинарника
    FROM scratch
    
    # Из образа для сборки, копируем готовый бинарник backend-а
    COPY --from=builder /app/bin/backend /app/bin/backend
    
    # Назначаем полученный бинарник точкой входа в контейнер
    ENTRYPOINT ["/app/bin/backend"]


    Теперь, если зайти в директорию с проектом backend и выполнить:

    docker build -t backend:latest .

    У вас соберется image с именем backend и тегом latest. Можно запускать, собственно.

    Образ для frontend собирите своими силами, зато разберетесь. Там суть таже, первым этапом можно взять image с node-js, собрать в нем frontend, а для запуска - использовать image c pm2 - скопировав директорию с приложением из этапа сборки.

    Как будет выглядеть ваш docker-compose.yml:

    version: "3.7"
    
    volumes:
      database:
    
    services:
    
      # Postgres. Берем готовый официальный образ
      database:
        image: postgres:12-alpine
        # Вот собственно тут и указывается, какие порты будут видны на вашей локали
        ports:
          - 5432:5432
        # Там выше в volumes указывали набор volumes. Используем вольюм database, чтобы хранить БД
        volumes:
          - database:/var/lib/postgresql/data
        restart: always
    
      # Backend. Берем образ, что собрали выше
      backend:
        image: backend:latest
        # В Dockerfile мы для гибкости не указывали порт backend-a. Укажем его тут. 
        expose: 
          - 8080
        ports:
          - 8080:8080
        restart: always
    
      # Frontend
      frontend:
        image: frontend:latest
        expose: 
          - 80
        ports:
          - 80:80
        restart: always


    Собственно и все. Остается поправить синтаксические ошибки и запустить, командой docker-compose up. Кстати, в интернетах есть очень много примеров проектов с кучами сервисов, с отдельными сетями для backend/frontend, с проксированием api через nginx/traefik и прочими фишками.

    Удачи.
    Ответ написан
    Комментировать
  • Как использовать docker в несколько потоков?

    Доброго времени.

    В целом, если я все правильно понял - Вам требуется сделать docker-compose.yml, в котором описать этот сервис.

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

    docker-compose scale тут_имя_вашего_сервиса=2 worker=3


    Документация
    Ответ написан
    Комментировать
  • Как организовывывать структуру каталогов внутри docker-контейнера "правильно"?

    Уж не знаю, поможет ли ответ вашему холивару с коллегами.

    Любая best practice в первую очередь опирается на строгое соблюдение стандартов в той области, о которой идет речь.

    Для Linux - есть стандарт FHS (Filesystem Hierarchy Standard), который описывает что, где, куда. Не соблюдать этот стандарт - можно, но нужно ли? Тем более, упомянутый образ Tomcat четко соответствует этому стандарту.

    Я, например, заменил бы /app на /jaba-code. Ну а почему нет то? Ведь мы можем :)
    Ответ написан
    Комментировать
  • Почему в докере долго импортируется дамп базы?

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

    При этом ваша ОС - не совсем понимает, что происходит. Раньше (без docker) пользователь запускал демонов, каждый из которых был чадом от init.d процесса, а сейчас - пользователь порешил запустить некоторую странную службу (типа /usr/bin/containerd) и начал активно ветвить с ее помощью детей на детей, тех детей еще на детей :)

    "Так как же мне распределять нагрузку, генерируемую со стороны всего этого детского сада в сторону ваших винчестеров, уважаемый пользователь?" - спросила ваша ОС.

    Подскажем ей. И подскажем примерно следующее: "Уважаемая ОС! Распределяй, пожалуйста, ресурсы винчестеров (особенно SSD) по принципу "кто первый встал, того и тапки!".

    Подсказать довольно просто:

    sudo nano /etc/default/grub

    Указываем в параметре GRUB_CMD_LINE_LINUX (а можно и через пробел в GRUB_CMDLINE_LINUX_DEFAULT) следующее:

    GRUB_CMD_LINE_LINUX="elevator=noop transparent_hugepage=never"


    Сохраняем файл. Записываем изменения в загрузчике:

    sudo update-grub

    Перезагружаем хост.

    Собственно elevator=noop - устанавливает noop в качестве I/O scheduler ядра по умолчанию. Noop - формирует весь стек комманд в сторону вашего HDD в виде FIFO-очереди, которая работает тем самым нужным нам способом - "кто первый встал, того и тапки". Прирост по OLTP операциям в БД заметите тут же.

    transparent_hugepage=never - можете и не ставить, данный параметр выключает штатный механизм THP, который на облачных хостингах формирует в целом бОльшую нагрузку на хост, чем хотелось бы видеть. Сильно на запись в БД он влиять не будет, если нет мощных нагрузок. Если же нагрузка крутая, есть смысл выключать данный механизм.

    "А что же за гадость такая этот ваш docker? Что же мне - теперь каждую виртуалку конфигурировать при создании вот в таких интимных местах?" - спросите вы.

    Отвечу.

    Linux - бесплатная ОС для самого широкого спектра задач. Она не windows, она не будет хардкодить за вас все параметры ядра, которые могут отличаться в рамках этих самых задач, решаемых на вашем хосте. А потом - колосально глючить без вариантов. Linux - ставится даже на стиральные машины, холодильники, и черт знает куда еще. Ваша задача - подсказать ОС, что вы делаете. ОС - не подведет. В свою очередь - разработчики служб dockerd и containerd не могут нести ответственность за поведение их продукта на хостах, работающих на коробочных настройках. Docker - дает вам экосистему, которая позволяет решать очень крутые задачи быстрее, чем это сделает матерый старообрядский сис-админ руками в консоли.

    Можно документировать нужные настройки хоста в confluence, написать подробные инструкции по шагам - что нужно сделать с ОС после установки, чтобы на ней все летало. Удобно, советую. Можно обратить внимание на продукт Teraform, который может помочь вам автоматизировать создание хостов в промышленных количествах с заданными параметрами.

    Как-то так. Пробуйте :)
    Ответ написан
    Комментировать
  • Есть ли какие-то best practices как организовывать файлы веб-проекта с докером?

    Уж не так все и просто, комрад ;)

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

    Итого, у вас в группе проектов на GitLab (или в организации на GitHub) должно выйти примерно следующее:

    /project-group (organization):
        /api - сюда можно вынести api вашего проекта.
        /desktop - здесь ваш web-проект для desktop на php, например.
        /mobile - здесь ваш web-проект для мобильной версии сайта, на той-же node.js.
        /admin - здесь ваша админка.
        /infrastructure - ну, или назовите проще, "server". Здесь ваши Dockerfile, docker-compose, ci/cd скрипты и т.п.


    Чтобы моч варировать вашу инфраструктуру в зависимости от окружения, вы должны разделить репозиторий инфраструктуры на отдельные сервисы. Например, не все сервисы проекта должны запускаться в test окружении или на сервере тестовых веток на поддоменах, когда вы делаете домены вида "feature-1.test.project.com", чтобы ваши тестировщики могли тестировать фичи параллельно и независимо друг от друга. Так же, на dev и/или test окружениях может потребоваться сделать сервисы-заглушки на внешние api которые вы используете. Ну, например, на сервисы онлайн оплаты товаров на вашем сайте, которые будут отвечать всегда "OK" или не "OK".

    Поэтому, чтобы можно было поднять среду с отличным от prod составом сервисов, нужно разделение.

    Таким образом, в репозитории infrastructure должно получиться примерно следующее:

    /infrastructure
       /api
           service.yml
           build.yml
           deploy.yml
       /mobile
           service.yml
           build.yml
           deploy.yml
       /desktop
           service.yml
           build.yml
           deploy.yml
       /admin
           service.yml
           build.yml
           deploy.yml
       /mysql
           mysql.cnf
           service.yml
       /redis
           redis.conf
           service.yml
       /php-fpm
           Dockerfile
           www-conf
           php.ini
           service.yml
       /nginx
           nginx.conf
           /prod
                site.conf
           /test
                site.conf
           /dev
                site.conf
       /cron
           Dockerfile
           crontab
           service.yml
    mobile-dev.sh
    desktop-dev.sh
    prod.sh
    test.sh


    В этих самых *.sh файлах, что указаны выше, вы производите запуск нужного состава сервисов. К примеру, разработчику мобильной версии на react не обязательно покупать мощный ПК, чтобы на его машине запускать вообще весь проект, с mysql, redis, admin и другими сервисами. Вам будет достаточно собрать образ мобильного приложения, и написать в mobile-dev.sh примерно следующее:

    docker-compose -f mobile/service.yml pull;
    docker-compose -f mobile/service.yml down;
    docker-compose -f mobile/service.yml up --detach;


    Ну а в mobile/service.yml может быть что-то следующее:

    version: "3.7"
    
    services:
      mobile:
        image: keymetrics/pm2:10-alpine
        environment:
          APP_ENV: ${APP_ENV}
          APP_LANG: ${APP_LANG}
          API_URL: ${API_URL}
          BASIC_TOKEN: ${BASIC_TOKEN}
        working_dir: /app
        command: pm2-runtime start config.js --env $APP_ENV
        volumes:
          - /etc/timezone:/etc/timezone:ro
          - /etc/localtime:/etc/localtime:ro
          - ${APP_DIR}:/app
        restart: always
        container_name: mobile


    API_URL при этом, можно приземлить на ваш тестовый или dev сервер. Таким образом, разработчик мобильной версии сайта будет иметь всегда ту версию api, которая по факту используется в проекте. Соответственно, код приложения должен уметь работать с переменными окружения для настройки своих параметров.

    Ваш сайт - так же, должен запускаться отдельным service.yml файлом. Service.yml - можно поменять на docker-compose.yml в директории каждого сервиса, но, service здесь смотрится красивее.

    Если требуется поднять все сервисы, необходимо передать последовательно все service.yml файлы через -f флаг вашему docker-compose. Это будет громоздкая команда, поэтому и делаются отдельные *.sh файлы для запуска.

    Требуется понимать, что не следует хранить Dockerfile в репозитории каждого проекта, если наличие образа потребуется для этого проекта. Не каждый разработчик умеет делать Dockerfile и, например, устанавилвать расширения для PHP в рамках этих Dockerfile. Делегировав ответственность по сборке Dockerfile разработчикам проекта, вы рискуете получить неработающий сервис. Лучшее место для Dockerfile - в нашем примере, это репозиторий infrastructure, находящийся в распоряжении ваших DevOps-инженеров.

    Та же история и с описанием build-процессов и процессов CI/CD. Не следует доаверять разработчику сборку его приложения в рамках целой огромной инфраструктуры проекта. Для этого в примере репозитория infrastructure - есть файлы build.yml/deploy.yml, в которых описана сборка и доставка сервисов. Любые CI/CD средства могут подключать фрагменты pipeline с внешних репозиториев, поэтому, когда у вас возникнет потребность сделать автобилд и автодеплой - проблем не будет, вы ведь уже учли все подводные камни совместной работы специалистов в вашей компании.

    Прошу учесть следующее.

    Выше описано, как правильно реализовать ваш проект на docker + docker-compose. Здесь не учтено наличие swarm-mode или kubernetes, тем не менее, данную структуру будет гораздо проще использовать в режиме swarm или c использованием kubernetes.

    ЗЫ. Конечно, есть и другие рабочие практики на эту тему ;) Эта нравится мне больше всего, и работает универсальней остальных.
    Ответ написан
    3 комментария