Недавно я начал работать в 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
Давайте запустим все наши службы в разных окнах терминала и попробуем.
💩 Обычно мы сталкиваемся с этими проблемами при такой настройке
- Терминальный ад: чтобы запустить все эти службы, нам нужно открыть несколько вкладок / окон терминала, а затем запустить их по отдельности (это будет выглядеть так). Управлять этим станет сложнее по мере роста количества услуг.
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