Еще не используете Docker? Вы прочитали множество статей о плюсах и минусах докеров, но еще не полностью проданы, потому что ваша повседневная работа не готова к переключению инфраструктуры (и поскольку они еще не пробовали это, и вы тоже ). Ваши товарищи по команде и люди из других областей начали его изучать, и вы понимаете общую идею, но все еще задаетесь вопросом, как интегрировать ее с вашими обычными инструментами, в то время как вы все еще боретесь с ошибками в CI (сборка проекта в Linux ), чего не происходит на вашем Mac, а заказ на покупку сообщает вам, что в проекте появился новый внештатный разработчик, которому нужна ваша помощь в настройке среды разработки в Windows. И посреди всего этого вы слышите объявление архитектора:

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

Сначала немного предыстории!

Я разработчик JavaScript, некоторое время работал фронтенд-разработчиком, а совсем недавно, с появлением Node.js, я перешел на роль разработчика Full Stack. Мне очень нравятся оба мира, адаптирую пользовательский интерфейс как к внешнему виду, так и к оптимизированной производительности, делая все возможное, чтобы код оставался чистым, СУХИМ и удобочитаемым.

Вам интересно, о каком процессе разработки я буду говорить? Конечно, JavaScript. Но если вы пишете код на другом языке, не волнуйтесь, поскольку большинство концепций легко применимы к любому другому языку. (На самом деле, я читал, что вы можете контейнеризовать приложения ASP.NET с помощью образов .NET Core Docker. Разве это не здорово?) Так что никаких оправданий по поводу ОС!

Еще один отказ от ответственности: это компиляция того, что я сделал до сих пор, и это никоим образом не является единственным правильным способом работы с Docker. Я знаю, что в контейнерах уже работает много людей с гораздо большим опытом. Если вы один из них, пожалуйста, позвоните мне, и давайте обновим этот пост (и меня самого).

И нет, Docker не платит мне за это. Я могу только пожелать…

Учти это

Следует рассмотреть возможность разбиения вашего приложения на контейнеры, чтобы получить все преимущества децентрализованной архитектуры (многие команды работают над многими частями приложения) без всех болевых точек взаимодействия между языками, операционными системами, прокси-серверами, службами и т. Д. Абстрагирование среды помогает сосредоточиться на реальных задачах, которыми, конечно же, должно быть кодирование вашего приложения. Если вас беспокоит контроль над производительностью, возможность масштабирования (или автоматического масштабирования) по запросу и получение максимальной отдачи от серверного решения, Docker может помочь вам в этом. Как это случилось со мной как разработчиком, понимание того, как масштабировать приложение, является ключом к развитию вашей карьеры, смысла жизни и прочего.

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

Структура проекта

Начнем с некоторых предположений:

  1. Вы хотите иметь точную копию вашей производственной среды в вашей локальной среде. (и в вашем решении CI / CD)
  2. Вы хотите сохранить все преимущества местной среды разработки. (в мире JS это означает просмотр файлов, горячую / прямую перезагрузку, исходные карты, транспилирование, управление пакетами, файлы конфигурации среды и т. д.)
  3. Вы хотите иметь возможность хранить свои файлы в своем решении для управления версиями (например, git) и не допускать все, что может не нуждаться в управлении версиями. (файлы сборки, зависимости, node_modules и т. д.)
  4. Вы хотите, чтобы каждый новый разработчик, который присоединяется к вашей команде, мог настроить среду как можно быстрее и безболезненно, независимо от того, какую ОС он выбрал.
  5. Вы хотите развернуть в производственной среде с той же легкостью, что и №4. Т.е. вы хотите, чтобы ваш проект был максимально переносимым.

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

project-root/
|  nginx/
|  |  Dockerfile
|  |  nginx.conf
|  node/
|  |  Dockerfile__local   <---- We'll go about this later on
|  |  src/
|  |  |  app/             <---- Plain ol' Nuxt.js app
|  |  |  nuxt.config.js
|  |  |  package.json
|  docker-compose.yml

Обратите внимание на файлы Dockerfile и docker-compose.yml. Это будут ваши настройки по умолчанию как для ваших контейнеров, так и для вашей среды в целом.

# project-root/docker-compose.yml
version: "3"
services:
    node:
        # This binds your container to a virtual network
        # that can access all containers belonging to it
        networks:
            - mynetwork
        build:
            context: "./node"
            dockerfile: Dockerfile
        # This is the port Nuxt.js exposes. It is exposed
        # to all containers in the network. You can customize it
        expose: [ "3000" ]
        volumes:
            - static:/usr/static
    nginx:
        networks:
            - mynetwork
        build:
            context: "./nginx"
            dockerfile: Dockerfile
            args:
                confname: "nginx.conf"
        # This binds your webserver port to the actual
        # host port. As you can tell, this is for prod
        # configuration, as you might not want to use
        # port 80 in your local development env
        ports:
            - "80:80"
        volumes:
            - static:/usr/share/nginx/html
# Define the network. Not mandatory, but nice to have
# in order to keep track of what can access what
networks:
    mynetwork:
# Define the volumes. We'll get to them later on
volumes:
    static: {}

И для содержания Dockerfile:

  1. В nginx
# project-root/nginx/Dockerfile
FROM nginx:alpine
# This argument is configurable from docker-compose.yml
ARG confname

COPY $confname /etc/nginx/nginx.conf

2. В узле

# project-root/node/Dockerfile__local
FROM node:alpine
WORKDIR /usr/src
# Expose env host
# This is needed to ensure communication between containers
# between docker containers
ENV HOST 0.0.0.0

# Run server app
# Detect whether you have a yarn.lock already and if so
# just install deps listed on lock file
CMD yarn $([ -f yarn.lock ] && echo "install") && $(yarn bin)/nuxt dev

Время для вопросов и ответов

Да, Милхаус! node: образы Alpine Docker, перечисленные в DockerHub, уже поставляются с предустановленной Yarn. Но давайте вернемся в нужное русло.

🤔: Зачем настраивать docker-compose.yml готовый к производству, когда мы хотим настроить локальную среду?
🦊: Мы собираемся создать еще один файл с именем docker-compose__local.yml рядом с исходным, который будет содержать замену конфигурации по умолчанию в соответствии с нашими местными потребностями. Таким образом мы сможем сохранить наши конфиги СУХИМИ.

# project-root/docker-compose__local.yml
# Stating version here is important, as Docker will complain if
# we try to override files with different versions
version: "3"
services:
    node:
        build:
            # Notice that in local dev we're referencing the file
            # we created to store our local Dockerfile version
            dockerfile: Dockerfile__local
        # This below is the magic that makes Docker suited for
        # local development. We'll get to it later
        volumes:
            - ./node/src:/usr/src
    nginx:
        build:
            args:
                # Remember the configurable argument? We could've
                # created another Dockerfile__local in our nginx
                # folder, but since it's only a filename change
                # we can use an argument and pass it down to
                # Dockerfile. Solid!
                confname: "nginx__local.conf"
        ports:
            # Let's also override the exposed port, so we can
            # work in http://localhost:9000
            - "9000:80"

🤔: Зачем создавать веб-сервер (nginx) для нашей локальной разработки? Разве мы не умеем работать только с Nuxt.js и его Webpack Dev Server?
🦊: Это правда, но мы копируем производственную среду. Как вы, возможно, знаете, Node.js неплох в некоторых вещах, но отстой, когда дело доходит до обслуживания статических файлов. В большинстве производственных конфигураций обслуживание статических файлов откладывается на выделенный веб-сервер, и в нашем случае это не исключение. Чтобы полностью воспроизвести среду prod, нам необходимо обслуживать локальные статические файлы через nginx, чтобы мы могли устранять проблемы на локальном компьютере, а не на производстве.

🤔: Как связать вывод Nuxt.js и nginx?
🦊: Вам необходимо настроить приложение Nuxt.js для вывода статических файлов в папку, отличную от папки по умолчанию. Главное - указать папку, к которой могут получить доступ как Nuxt.js, так и nginx. И если вам интересно, нет, это не тот, который мы связали. Вам нужно будет добавить Docker volume в docker-compose.yml. В нашем случае это будет том, который мы определили как static. Поскольку Nuxt.js по умолчанию генерирует свой статический вывод в папке .nuxt, нам нужно изменить nuxt.config.js, чтобы приспособиться к нашим потребностям:

// project-root/node/src/nuxt.config.js
module.exports = {
    // This will generate all static files in /usr/static
    // a.k.a. our Docker Volume
    buildDir: '../static/nuxt',
    build: {
        // This will append this path to all static resources
        // so we can route them easily on nginx
        publicPath: '/static/'
    }
    // ... your other nuxt config.
}

Нам также нужно будет изменить наш nginx.conf для поддержки маршрутизации к статическим ресурсам:

# project-root/nginx/nginx.conf
server {
    root /usr/share/nginx/html/nuxt;
    location /static/ {
        try_files $uri $uri/ @app;
    }
    location @app {
        # Proxy pass to your node docker container
    }
    # ... your other nginx config.
}

Больше вопросов и ответов!

🤔: Как Docker будет выбирать мои локальные изменения, чтобы включить HMR?
🦊: Конечно, с помощью привязки монтирования. Как и в случае с томами, привязка монтирования обеспечивает совместное использование файлов между контейнерами. Что действительно важно, так это то, что ими можно поделиться с хозяином. Таким образом мы можем сохранить копию сгенерированного yarn.lock, увидеть установленный node_modules и увидеть другие файлы, относящиеся к локальной среде. Конечно, не пытайтесь использовать команды вашего хоста, такие как Yarn, для установки пакетов или запуска сценариев, поскольку установленные модули были установлены для среды Linux, и они могут отличаться по своим двоичным файлам и поддерживаемой архитектуре.

🤔: Хм, но если я не могу установить модули с помощью Yarn на моем хосте, как мне их установить?
🦊: Еще одно лакомство от Docker. Вы можете запускать эфемерные контейнеры как двоичные файлы так же, как вы использовали бы установленные команды в вашем CLI, и они запускаются с использованием определенной среды контейнера. Это означает, что вы сможете использовать всю Yarn, не беспокоясь о версиях, и все будет частью контейнера. И лучшая часть: если у вас есть привязки, все сгенерированные файлы также будут на вашем хосте (так что вы можете зафиксировать их на git):

$ docker-compose -f ./docker-compose.yml -f ./docker-compose__local.yml run --rm node yarn <all other args from yarn>

Конечно, запуск этой ужасно длинной команды - отстой, так что вы можете присвоить ей псевдоним, более удобный для разработчиков. Я использую alias для сред * nix, но вы можете найти эквивалент в Windows или создать многоплатформенное решение, добавив сценарии npm для определения псевдонима.

$ alias docker-yarn="docker-compose -f ./docker-compose.yml -f ./docker-compose__local.yml run --rm node yarn"
...then
$ docker-yarn add lodash

Также обратите внимание, что мы запускаем run --rm node yarn. Чтобы избежать путаницы, run - это команда docker-compose, node - имя нашей службы Docker и, конечно же, yarn - это команда, которую мы хотим выполнить в этом контейнере. Если имя нашей службы в docker-compose.yml было названо, например, nuxt-app, команда, которую мы хотим запустить, будет run --rm nuxt-app yarn.

Еще один совет: никогда не пропускайте флаг --rm, иначе Docker съест вашу оперативную память, накапливая контейнеры. Наличие флага --rm гарантирует, что после выхода из команды контейнер будет удален и уничтожен.

🤔: И еще кое-что, зачем использовать «__» для отделения имен файлов от их версий?
🦊: Это не имеет особого значения. Вы можете использовать любое соглашение! При запуске команды для создания вашего приложения вам просто нужно указать на файл, независимо от имени, которое вы ему дали:

$ docker-compose -f ./docker-compose.yml -f ./docker-compose__local.yml -f ./my-awesome.override.yml up

Готовы к поездке?

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

$ docker-compose -f ./docker-compose.yml -f ./docker-compose__local.yml up

Если все прошло хорошо, все должно работать: живая перезагрузка, HMR, nginx, и у вас будет точная копия вашего производственного приложения, работающего на вашем локальном компьютере. Расширение концепции - это просто вопрос добавления конфигурации в docker-compose и обеспечения связи между контейнерами.

А как насчет производства?

Используя эту конфигурацию в качестве основы, можно сделать множество улучшений. Разделение кода на контейнеры и обеспечение их взаимодействия друг с другом через Docker значительно упрощает производственное развертывание и масштабирование. Если вы используете один сервер в продукте, вы можете использовать функцию масштабирования docker-compose и использовать nginx в качестве балансировщика нагрузки, чтобы обслуживать большую нагрузку с меньшими ресурсами. Если у вас есть несколько серверов, вы можете расширить эту концепцию до Docker Swarm или Kubernetes, чтобы получить все преимущества автоматического масштабирования, обнаружения сервисов и множества функций, которые могут помочь вашему продукту реагировать на повышенный спрос на трафик. .

Есть гораздо больше вариантов использования производственной конфигурации, таких как отправка / извлечение изображений в DockerHub или любой другой частный реестр, управление сертификатами для тестирования необходимых функций HTTPS, таких как HTTP / 2 или Service Workers, и многое другое, о чем я расскажу в других сообщениях. , включая ужасную историю о потере сертификатов Letsencrypt.

🙌 Спасибо за чтение!

Это мой первый пост на Medium. Я постараюсь написать больше историй о проектах, над которыми я работаю, включая этот, который стал путешествием по обучению и саморазвитию. Я уверен, что вы увидите больше сообщений, связанных с этим. Не забудьте позвонить мне в разделе комментариев, если я облажался, или если есть лучшие подходы к локальным рабочим процессам разработки с помощью Docker или аналогичных инструментов. Или что-то еще в частности.

Спасибо Дэну, il fello и Gustavo за их технический, редакторский (и грамматический) обзор.

Также большое спасибо Полу Кереру, Шону Шульте и Элли Янг за Frinkiac.com, потрясающий мем про Симпсоны и генератор GIF, в котором я провожу большую часть своего дня. Вы, ребята, делаете мир лучше!