Недавно я начал работать в Anyfin. Как новый инженер в команде, мне пришлось настраивать всю среду разработки. Исходя из моих ожиданий от моих предыдущих рабочих заданий, я подумал, что это займет у меня пару дней. Но, к моему удивлению, у меня была рабочая установка довольно большого количества серверных сервисов, написанных на NodeJS, Golang и Python, вместе с веб-сайтом и порталом (Javascript) за ~ 5 часов. В этом посте будет объяснено, как мы используем Docker в Anyfin для простой настройки продуктивной локальной среды разработки. Я видел такие попытки на своих предыдущих рабочих местах и ​​раньше, но ни один из них не работал так гладко, как тот, который есть у нас здесь.

👮🏻‍ Заявление об отказе от ответственности: 🚨

Вся заслуга в установке принадлежит моим коллегам из Anyfin. Я нашел настройку Anyfin чрезвычайно крутой и поэтому хотел поделиться ею со всеми.

🧦 Пример архитектуры

Допустим, у нас есть набор сервисов со следующей архитектурой.

Из диаграммы видно, что мы имеем:

  • NJS1 - служба NodeJS, работающая на порту 7000 и зависящая от базы данных Db1 (работающая на порту 5433).
  • Py1 - служба Python, работающая на порту 9000 и зависящая от базы данных Db1 (работающая на порту 5433).
  • Go1 - служба Golang, работающая на порту 5000 и зависящая от базы данных Db1 (работающая на порту 5433).
  • NJS2 - служба NodeJS, работающая на порту 8000 и зависящая от базы данных Db2 (работающая на порту 5432).
  • Интернет - простой сервер разработки на основе веб-пакетов для внешнего интерфейса, работающего на порту 8080.

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

Для простоты предположим, что все эти службы просто возвращают сообщение:

Hello from <service name>

Итак, если вы нажмете GET http: // localhost: 7000, вы должны получить

Hello from njs1

Код для этих сервисов находится здесь: https://github.com/master-atul/blog-docker-dev-environment-example

Давайте запустим все наши службы в разных окнах терминала и попробуем.

💩 Обычно мы сталкиваемся с этими проблемами при такой настройке

  1. Терминальный ад: чтобы запустить все эти службы, нам нужно открыть несколько вкладок / окон терминала, а затем запустить их по отдельности (это будет выглядеть так). Управлять этим станет сложнее по мере роста количества услуг.

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

3. Свежая настройка. Настройка всех этих служб на новом компьютере может быть сложной задачей, поскольку нам нужно отслеживать все необходимые нам зависимости и их соответствующие версии. Это приводит к популярному «Работает на моей машине».

Все это откровенно раздражает 🤯.

🕶 Локальная среда разработки на основе Docker

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

Ментальная модель 🧞‍

Контейнер Docker

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

Docker-Compose

Docker compose - это инструмент для определения и запуска многоконтейнерных приложений Docker. В Compose вы используете файл YAML для настройки служб вашего приложения. Затем с помощью одной команды вы создаете и запускаете все службы из своей конфигурации.

TL; DR; Docker compose позволяет запускать все ваши сервисы одновременно (также в правильном порядке) и управлять ими через единый интерфейс.

Docker compose в целом будет содержать:

  • Службы. Службы - это список отдельных контейнеров докеров, которые будут запускаться инструментом создания. Здесь мы укажем порты и другие конфигурации, необходимые для запуска контейнеров докеров.
  • Сети. Сеть обеспечивает способ взаимодействия различных служб друг с другом. Каждый контейнер может подключаться к сети, и все контейнеры в одной сети могут взаимодействовать друг с другом. В нашем случае мы будем использовать единую сеть.
  • Тома: контейнеры Docker по умолчанию не содержат никаких постоянных хранилищ. Если докер-контейнер убит, все данные в его памяти будут потеряны. Итак, чтобы сохранить некоторые постоянные данные, вам нужны тома. Думайте о томах как о постоянных жестких дисках для этих контейнеров. У нас будет один том на каждую услугу.

🚀 Настройка

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

git clone https://github.com/master-atul/blog-docker-dev-environment-example.git
cd blog-docker-dev-environment-example
git checkout tags/basic-setup
git checkout -b tryingout

Теперь у вас должна быть следующая структура проекта:

.
├── go1
│   ├── README.md
│   └── main.go
├── njs1
│   ├── README.md
│   ├── index.js
│   ├── package-lock.json
│   └── package.json
├── njs2
│   ├── README.md
│   ├── index.js
│   ├── package-lock.json
│   └── package.json
└── py1
    ├── README.md
    ├── requirements.txt
    └── server
        ├── __init__.py
        └── __main__.py

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

Войдите в докер 🚪

Убедитесь, что у вас запущен докер, следуя инструкциям здесь https://www.docker.com/get-started.

Первый шаг - создать файл докера для нашего сервиса njs1. Создайте файл Dockerfile: blog-docker-dev-environment-example / njs1 / Dockerfile.

njs1/Dockerfile

FROM node:6.17.0
WORKDIR /root
ADD . /root

Теперь перейдем к docker-compose.

Давайте посмотрим, как выглядит простой файл docker-compose:

docker-compose.yml

version: '3'
services:
  <service_name_1>:
    build: <path_to_docker_file_of_service>
    command: <start_command_to_run>
    environment:
      - <env_var_1>=<env_val_1>
      - <env_var_2>=<env_val_2>
    ports:
      - '<port_inside_container>:<port_of_host_machine>'
    working_dir: <path_inside_the_docker_container_where_command_should_run>
  <service_name_2>: ....
    ....
    ....

Создайте файл по адресу blog-docker-dev-environment-example / docker-compose.yml.

Добавим к нему наш первый сервис (njs1).

docker-compose.yml

version: '3'
services:
  njs1:
    build: ./njs1
    command: sh -c "npm install && npm start"
    environment:
      - NODE_ENV=development
      - PORT=7000
    ports:
      - '7000:7000'
    working_dir: /root/njs1

В приведенном выше файле компоновки докеров есть только одна служба (njs1). Мы будем добавлять больше услуг постепенно. Перед этим давайте запустим его и посмотрим, что у нас получится.

В папке, содержащей наш docker-compose.yml, запустите:

docker-compose up

если все пойдет хорошо, вы увидите, что он создает контейнер нашего приложения и, в конце концов,

NJS1 app listening on port 7000!

Откройте его в браузере: http://localhost:7000, чтобы проверить.

Подробная информация о сервисе docker-compose:

  • build:: Путь к файлу докеров. Примечание: вы можете указать папку, содержащую Dockerfile, или полный путь к самому Dockerfile. Оба работают.
  • command:: Команда, запускаемая при запуске контейнера докеров.
  • среда: все переменные среды, которые вам нужно установить.
  • ports :: Определяет сопоставление порта внутри контейнера с портом хост-компьютера. Они не обязательно должны быть одинаковыми.
  • working_dir:: Это путь внутри контейнера, по которому вы хотите выполнить указанную выше команду.

Все это хорошо и прекрасно, но как это использовать для эффективной разработки? 🤷🏻‍♀️

Чтобы создать эффективную среду разработки, нам нужно иметь возможность редактировать исходный код (что при текущей настройке невозможно без повторной сборки контейнера снова и снова). Для этого мы воспользуемся объемами.

Сделайте следующие изменения. Сначала скажем нашему njs1 / Dockerfile не копировать файлы проекта в контейнер.

njs1/Dockerfile

FROM node:6.17.0
# WORKDIR /root <-- comment out
# ADD . /root   <-- these two lines

затем скажите docker-compose смонтировать каталог нашего проекта с нашего локального компьютера как каталог внутри контейнера.

docker-compose.yml

version: '3'
services:
  njs1:
    build: ./njs1
    command: sh -c "npm install && npm start"
    environment:
      - NODE_ENV=development
      - PORT=7000
    ports:
      - '7000:7000'
    working_dir: /root/njs1
    volumes:
      - ./njs1:/root/njs1:cached # <--- This will map ./njs1 to /root/njs1 inside the container.

Подробно

  • тома: - тома дает нам возможность сопоставить наши локальные каталоги с каталогом внутри контейнера. Здесь мы говорим, что сопоставьте папку njs1 с нашего локального компьютера в / root / njs1 внутри контейнера докеров. Здесь мы не копируем файлы в контейнер, вместо этого мы монтируем его как общий том. И в этом трюк, который делает его полезным.

Чтобы проверить это. Давайте добавим nodemon в нашу службу njs1.

cd njs1
npm install --save-dev nodemon

Теперь сделайте следующее изменение в

njs1/package.json

...
...
...
  "description": "A sample nodejs server",
   "main": "index.js",
   "scripts": {
-    "start": "node index.js"
+    "start": "nodemon index.js"
   },
...
...

Пришло время проверить это! Зайдите в корневую папку и запустите

docker-compose up --build

--build сообщает docker-compose о необходимости перестроить образы.

Тебе следует увидеть:

➜  blog-docker-dev-environment-example git:(master) ✗ docker-compose up --build
Building njs1
Step 1/1 : FROM node:6.17.0
 ---> 0dea7f33fa21
Successfully built 0dea7f33fa21
Successfully tagged blog-docker-dev-environment-example_njs1:latest
Starting blog-docker-dev-environment-example_njs1_1 ... done
Attaching to blog-docker-dev-environment-example_njs1_1
njs1_1  | npm WARN [email protected] No repository field.
njs1_1  |
njs1_1  | > [email protected] start /root/njs1
njs1_1  | > nodemon index.js
njs1_1  |
njs1_1  | [nodemon] 1.19.1
njs1_1  | [nodemon] to restart at any time, enter `rs`
njs1_1  | [nodemon] watching: *.*
njs1_1  | [nodemon] starting `node index.js`
njs1_1  | NJS1 app listening on port 7000!

Попробуйте отредактировать исходные файлы njs1! 🎉

Попробуйте внести некоторые изменения в файл njs1 / index.js, и вы увидите, что nodemon автоматически перезагружается при изменении файла.

Завершение дела! 🏁

После того, как вы добавите другие службы в файл docker-compose. Это должно выглядеть примерно так:

docker-compose.yml

version: '3'
services:
  njs1:
    build: ./njs1
    command: sh -c "npm install && npm start"
    environment:
      - NODE_ENV=development
      - PORT=7000
    ports:
      - '7000:7000'
    working_dir: /root/njs1
    volumes:
      - ./njs1:/root/njs1:cached # <--- This will map ./njs1 to /root/njs1 inside the container.
  njs2:
    image: node:12.3-alpine
    command: sh -c "npm install && npm start"
    environment:
      - NODE_ENV=development
      - PORT=8000
    ports:
      - '8000:8000'
    working_dir: /root/njs2
    volumes:
      - ./njs2:/root/njs2:cached # <--- This will map ./njs2 to /root/njs2 inside the container.
  py1:
    image: python:3-stretch
    command: sh -c "pip install -r requirements.txt && python -m server"
    environment:
      - PORT=9000
      - FLASK_ENV=development
    ports:
      - '9000:9000'
    working_dir: /root/py1
    volumes:
      - ./py1:/root/py1:cached # <--- This will map ./py1 to /root/py1 inside the container.
  go1:
    image: golang:1.12-alpine
    command: sh -c "go run ."
    environment:
      - PORT=5000
    ports:
      - '5000:5000'
    working_dir: /root/go1
    volumes:
      - ./go1:/root/go1:cached # <--- This will map ./py1 to /root/py1 inside the container.

Несколько изменений

  • изображение: вместо сборки:. В docker-compose мы можем указать образ докера напрямую из docker-hub вместо файла докера, используя свойство image:. Следовательно, для простых настроек нам не нужно писать собственный Dockerfile.

Есть еще много других параметров конфигурации, которые вы можете использовать в файле docker-compose.yml. Чтобы увидеть полную информацию о них, вы можете перейти по этой ссылке: https://docs.docker.com/compose/compose-file/

🧙‍ ​​Шпаргалка по командам

Теперь, когда вы настроили свои службы для запуска через docker-compose для локальной разработки. Есть несколько команд, которые могут помочь.

Запустить все сервисы

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

docker-compose start

Остановить все службы

Соответствующая команда остановки

docker-compose stop

Запустить конкретную услугу

Это запустит только njs1 из списка служб в docker-compose.yml.

docker-compose up njs1

Вы можете использовать аналогичные команды для остановки и запуска индивидуальных служб.

перезапустить одну службу

docker-compose restart njs1

журналы из конкретной службы

Это покажет журналы только для njs1, а также будет следить за другими журналами

docker-compose logs -f njs1

ssh в конкретный сервисный контейнер

docker-compose exec njs1 bash

🕵️‍ Несколько советов для более плавного рабочего процесса

Контейнеры / службы работают слишком медленно?

Вы можете заметить, что ваши службы работают / запускаются очень медленно по сравнению с тем, когда вы запускаете их без docker-compose. Это может быть связано с тем, что вы выделили меньше ЦП / ОЗУ для службы докеров. Значения по умолчанию очень низкие, что вызывает проблемы при запуске нескольких служб.

Go to : dockerIcon -> preferences -> Advanced

Измените ползунок, чтобы выделить ЦП ›3 ядра и ОЗУ› 6 ГБ.

Мало места / возникла проблема, и вы хотите перезапустить все с нуля?

Удаление всех изображений, а затем обновление всего объекта.

Чтобы удалить все контейнеры докеров:

docker rm $(docker ps -a -q) -f

Чтобы удалить все образы докеров:

docker rmi $(docker images) -f

🧙‍ ​​Вот и все! Спасибо за чтение!

Автор Атул Р, разработчик, автор и тренер. В основном он работает с экосистемой Javascript и иногда занимается C ++, Rust и Python. Он энтузиаст открытого исходного кода и ❤ создает полезные инструменты для людей. Вы должны подписаться на него в Twitter