====== Docker (DKA-Develop) ======
[[https://www.youtube.com/playlist?list=PLD5U-C5KK50XMCBkY0U-NLzglcRHzOwAg|DKA-Develop]]
Образ (image) - основа для запуска контейнеров. Образы хранятся в реестрах (registries) - публичных (типа [[https://hub.docker.com/|hub.docker.com]]) или приватных, т. е., можно развернуть свой.
# Добавить пользователя в группу Docker, чтобы он имел там все права:
sudo usermod -aG docker username
newgrp docker # чтобы не перезапускать машину
# Запустить контейнер с Ubuntu и зайти в его терминал
docker run -it ubuntu bash
# Список всех контейнеров, в т. ч. остановленных
docker ps -a
# Запустить остановленный контейнер
docker start containername
# Остановить
docker stop containername
# Запуск с указанием имени хоста
docker run -h hostname -it ubuntu bash
# Список запущенных контейнеров
docker ps
# Информация о контейнере
docker inspect containername
# Задать имя контейнера
docker run --name containername -it ubuntu bash
# Посмотреть изменения в контейнере
docker diff containername
# Журнал контейнера
docker logs containername
# Удалить контейнер
docker rm containername
# Удалить все остановленные контейнеры
docker rm -v $(docker ps -aq -f status=exited)
# Удалить все образы
docker rmi $(docker images -q) --force
# Запустить в фоне с пробросом порта хост:контейнер
docker run -d -p 8000:8080 apache
# Список образов
docker images
# Создать образ из контейнера
docker commit containername username/imagename
# Закачать образ в реестр-репозиторий
# Тэг можно не указывать, тогда будет присвоено значение latest
docker push username/imagename:tag
===== Dockerfile =====
Набор инструкций для сборки образа.
# Базовый образ
FROM ubuntu
# Информация о создателе
MAINTAINER Username
# Команды для выполнения внутри контейнера
RUN apt update && apt install cowsay -y && ln -s /usr/games/cowsay /usr/bin/cowsay
# Что выполнять по умолчанию при запуске контейнера
ENTRYPOINT ["cowsay"]
# Собрать образ
docker build -t username/imagename .
# Запустить
docker run username/imagename "test"
# Если бы в Dockerfile не был указан параметр ENTRYPOINT,
# то запускать надо было бы так:
docker run username/imagename cowsay "test"
===== Связь между контейнерами =====
Например, запускается MariaDB:
docker run --name mysqlserver -e MYSQL_ROOT_PASSWORD=123456 -d mariadb
Затем нужно запустить adminer и связать его с MariaDB.
Параметр %%--link%% делает в /etc/hosts контейнера запись db, указывающую на контейнер mysqlserver.
docker run --link mysqlserver:db -p 8080:8080 adminer
Более современный вариант связи - через publish services.
===== docker-compose =====
docker-compose**.yaml** = docker-compose**.yml**
# Use root/example as user/password credentials
version: '3.1'
services:
db:
image: mariadb
# build: ./db Можно указать вместо image сборку из Dockerfile в каталоге db
restart: always
environment:
MYSQL_ROOT_PASSWORD: example
adminer:
image: adminer
restart: always
ports:
- 8080:8080
# Запустить
docker-compose up
# Запуск в фоне
docker-compose up -d
# Список контейнеров, запущенных через compose
docker-compose ps
# Собрать, пересобрать проект
docker-compose build
# Удалить контейнер с БД (по имени сервиса)
docker-compose rm db
Если в Windows установлен Docker toolbox, то вход в браузере через localhost не сработает, нужно заходить на IP виртуальной машины, который можно узнать, выполнив команду
docker-machine ip default
===== Volumes =====
Назначение путям внутри контейнера локальных каталогов на хосте. Удобно тем, что при остановке или уничтожении контейнера данные остаются в целости.
Есть 3 способа указания томов:
- Объявить том при запуске контейнера.docker run -v /data ubuntu
Здесь каталог /data внутри контейнера станет томом, и всё, что пишется в /data, сохраняется на этот том. Местоположение тома можно посмотреть командойdocker inspect -f {{.Mounts}} containername
Обратное тоже верно - если что-то положить в каталог монтирования тома, это сразу будет видно в контейнере. Можно явно указать соответствие каталогов на хосте и внутри контейнера:docker run -v /localdir:/data ubuntu
- Объявить том с помощью инструкции VOLUME в Dockerfile, например,VOLUME /data
\\ Сопоставление явно указанного локального каталога и каталога внутри контейнера не работает.VOLUME ./localdir:/data
- Объявить том в docker-compose.yml.
volumes:
- ./localdir:/data
- ./database:/var/lib/mysql
===== Пример (Laravel) =====
Необходимо 3 сервиса: PHP, MySQL и Composer.
Файл .env с переменными:
# Paths
DB_PATH_HOST=./databases
APP_PATH_HOST=./blog
APP_PATH_CONTAINER=/var/www/html/
В данном случае в папку ./blog кладётся проект, например, клон репозитория Github.
docker-compose.yml:
version: '3'
services:
web:
build: ./web
environment:
- APACHE_RUN_USER=www-data
volumes:
- ${APP_PATH_HOST}:${APP_PATH_CONTAINER}
ports:
- 8080:80
working_dir: ${APP_PATH_CONTAINER}
db:
image: mariadb
restart: always
environment:
MYSQL_ROOT_PASSWORD: example
volumes:
- ${DB_PATH_HOST}:/var/lib/mysql
adminer:
image: adminer
restart: always
ports: # должны быть разными для хоста у разных сервисов
- 6080:8080
composer:
image: composer:1.6
volumes:
- ${APP_PATH_HOST}:${APP_PATH_CONTAINER}
working_dir: ${APP_PATH_CONTAINER}
command: composer install
./web/Dockerfile
FROM php:7.2-apache
RUN docker-php-ext-install pdo_mysql \
&& a2enmod rewrite
# Сборка и запуск
docker-compose up --build
# Запустить bash в сервисе-контейнере web
# Благодаря указанию working_dir, оболочка сразу попадёт в /var/www/html/
docker-compose exec web bash
===== Решение проблем =====
В Windows при запуске контейнера с Volume ругается на то, что нет прав. Для этого нужно зайти в настройки Docker Desktop и на вкладке Shared Drives поставить галки на нужных дисках.\\
Ситуация может осложняться тем, что у пользователя нет пароля, и в таком случае расшарить диски не получится, так как Докер не даёт нажать ОК, если нет пароля пользователя.
Решение - завести нового локального пользователя с бессрочным паролем, дать ему права локального админа, расшарить диски под его именем, а затем дать ему полные права на локальный каталог с проектами.
====== Get started ======
[[https://docs.docker.com/get-started/|Get started]]\\
[[https://docs.docker.com/engine/reference/commandline/docker/|Docker CLI]]\\
[[https://medium.com/the-code-review/top-10-docker-commands-you-cant-live-without-54fb6377f481|Top 10 Docker CLI commands you can’t live without]]
## List Docker CLI commands
docker
docker container --help
## Display Docker version and info
docker --version
docker version
docker info
## Execute Docker image
docker run hello-world
## List Docker images
docker image ls
## List Docker containers (running, all, all in quiet mode)
docker container ls
docker container ls --all
docker container ls -aq
# Containers
docker build -t friendlyhello . # Create image using this directory's Dockerfile
docker run -p 4000:80 friendlyhello # Run "friendlyname" mapping port 4000 to 80
docker run -d -p 4000:80 friendlyhello # Same thing, but in detached mode
docker container ls # List all running containers
docker container ls -a # List all containers, even those not running
docker container stop # Gracefully stop the specified container
docker container kill # Force shutdown of the specified container
docker container rm # Remove specified container from this machine
docker container rm $(docker container ls -a -q) # Remove all containers
docker image ls -a # List all images on this machine
docker image rm # Remove specified image from this machine
docker image rm $(docker image ls -a -q) # Remove all images from this machine
docker login # Log in this CLI session using your Docker credentials
docker tag username/repository:tag # Tag for upload to registry
docker push username/repository:tag # Upload tagged image to registry
docker run username/repository:tag # Run image from a registry
# Services
docker stack ls # List stacks or apps
docker stack deploy -c # Run the specified Compose file
docker service ls # List running services associated with an app
docker service ps # List tasks associated with an app
docker inspect # Inspect task or container
docker container ls -q # List container IDs
docker stack rm # Tear down an application
docker swarm leave --force # Take down a single node swarm from the manager
# Swarms
docker-machine create --driver virtualbox myvm1 # Create a VM (Mac, Win7, Linux)
docker-machine create -d hyperv --hyperv-virtual-switch "myswitch" myvm1 # Win10
docker-machine env myvm1 # View basic information about your node
docker-machine ssh myvm1 "docker node ls" # List the nodes in your swarm
docker-machine ssh myvm1 "docker node inspect " # Inspect a node
docker-machine ssh myvm1 "docker swarm join-token -q worker" # View join token
docker-machine ssh myvm1 # Open an SSH session with the VM; type "exit" to end
docker node ls # View nodes in swarm (while logged on to manager)
docker-machine ssh myvm2 "docker swarm leave" # Make the worker leave the swarm
docker-machine ssh myvm1 "docker swarm leave -f" # Make master leave, kill swarm
docker-machine ls # list VMs, asterisk shows which VM this shell is talking to
docker-machine start myvm1 # Start a VM that is currently not running
docker-machine env myvm1 # show environment variables and command for myvm1
eval $(docker-machine env myvm1) # Mac command to connect shell to myvm1
& "C:\Program Files\Docker\Docker\Resources\bin\docker-machine.exe" env myvm1 | Invoke-Expression # Windows command to connect shell to myvm1
docker stack deploy -c # Deploy an app; command shell must be set to talk to manager (myvm1), uses local Compose file
docker-machine scp docker-compose.yml myvm1:~ # Copy file to node's home dir (only required if you use ssh to connect to manager and deploy the app)
docker-machine ssh myvm1 "docker stack deploy -c " # Deploy an app using ssh (you must have first copied the Compose file to myvm1)
eval $(docker-machine env -u) # Disconnect shell from VMs, use native docker
docker-machine stop $(docker-machine ls -q) # Stop all running VMs
docker-machine rm $(docker-machine ls -q) # Delete all VMs and their disk images
====== Docker (udemy.com) ======
Докер упрощает установку и запуск программ без возни с зависимостями. Это платформа, создающая и запускающая т. н. контейнеры. Контейнер - это запущенный изолированный экземпляр приложения из заранее заготовленного образа (image).
При установке Докера ставится 2 компонента - клиент (CLI, управление) и сервер (функциональная часть). При установке Докера рекомендуется регистрация на docker.com. После установки Докера и перезагрузки нужно вбить рег. данные в запущенный Докер для доступа к Docker Cloud.
===== 01 Dive Into Docker =====
# Версия:
docker version
# Запустить тестовый контейнер:
docker run hello-world
Если образа нет в локальном кэше (image cache), он скачивается автоматически из репозитория (Docker Hub).
Программы взаимодействуют с железом через ядро (kernel) путём системных запросов (system call). Контейнер - это изолированная область для запуска процессов, использует namespacing для изолирования системных ресурсов для запуска процесса, и control groups (cgroups) для лимита выделяемой мощности. В частности, это позволяет, например, запускать программы с разными зависимостями, в то время как эти зависимости не могли бы работать одновременно в одной и той же ОС. Т. е., виртуализация приложения или сервиса внутри одной ОС.
Так как namespacing и cgroups - сугубо линуксовое явление, то, когда Докер ставится на Windows или Mac, в реальности туда ставится виртуальная машина с линуксом, через которую и работает контейнеризация.
===== 02 Manipulating Containers with the Docker Client =====
# Аргументы контейнеру
docker run busybox echo hi there
docker run busybox ls
# Список запущенных контейнеров
docker ps
# Список когда-либо запущенных контейнеров
docker ps --all
docker run = docker create + docker start
docker create hello-world # создаёт контейнер и показывает его ID
docker start -a ID # запускает конкретный контейнер с выводом результатов
docker start ID # просто запускает контейнер без вывода результатов на экран. Выводится только ID запущенного контейнера.
docker logs ID # посмотреть вывод уже отработавшего контейнера по команде docker start ID
# (не в реальном времени, а то, что успело выполниться на момент запроса).
Запущенный однажды контейнер лежит на диске и его всегда можно вызвать по ID, но команду заменить не получится. Например, запускали
docker run busybox echo hi there
# Если запустить
docker start -a ID echo bye there
# то ничего не получится.
# Удаление остановленных контейнеров, используемых ими сетей, кэша
# (образы нужно будет скачивать заново из Docker Hub)
docker system prune
docker stop ID
docker kill ID
# Если stop не отрабатывает за 10 сек, автоматически посылается kill.
# Запуск команд внутри контейнера
docker exec -it ID
# Например,
docker exec -it ID redis-cli
# Без ключа -it (-i -t) процесс запустится, но без возможности ввода.
# -i - STDIN, -t - вывод текста. Можно запустить и
docker exec -i ID redis-cli
# но в этом случае не будет выводиться консоль и будет показываться
# только ввод пользователя, выглядит непривычно и неудобно, хотя всё работает.
# Запустить "удалённую" консоль в контейнере
docker exec -it ID sh
# потом можно запускать redis-cli и всё остальное за один сеанс, чтобы не вводить каждый раз
docker exec -i ID redis-cli
# Создать контейнер и запустить там консоль
docker run -it busybox sh
Файловые системы разных контейнеров изолированы, и созданное что-либо в одном контейнере не видится из других.
===== 03 Building Custom Images Through Docker Server =====
Для создания собственного образа нужно написать Dockerfile, который через клиент передаётся серверу, который на основе докерфайла создаёт образ.
Докерфайл пишется так:
- Указывается базовый образ (типа выбора ОС)
- Запускаются команды для установки доп. программ (типа скачивания инсталлятора и установки, команды относятся к базовому образу, не к Докеру)
- Указываются команды, выполняемые в момент запуска контейнера (типа запуска .exe)
Создаётся каталог, где размещается файл Dockerfile (с большой буквы, без расширения). Например:
FROM alpine
RUN apk add --update redis
CMD ["redis-server"]
Затем запускается
docker build .
На каждом этапе сборки создаётся промежуточный контейнер, где выполняется команда, затем контейнер стирается и выполняется следующая.\\
Если добавить команду на 2 этап, например,
RUN apk add --update gcc
то предыдущие команды уже не будут выполняться, потому что результат уже есть в кэше. Но если поменять порядок команд, то всё будет выполняться заново, т. к. в кэше нет таких контейнеров.
# Чтобы задать человеческое имя образу при сборке:
docker build -t YourDockerID/RepoOrProjectName:Version .
# Например,
docker build -t username/redis:latest .
# Версия latest грузится и запускается Докером по умолчанию.
# Если просто написать
docker build -t username/redis,
# то версия latest добавится автоматически.
# Можно сделать образ руками, запустив все нужные команды в консоли, а затем "закоммитив" образ.
docker run -it alpine sh
apk add --update redis
# Из другой консоли:
docker ps # (узнать ID изменяемого контейнера)
docker commit -c 'CMD ["redis-server"]' ID
# Создастся образ с новым ID.
Чтобы запустить образ, можно не копировать весь ID, достаточно части, которая будет уникальной.
===== 04 Making Real Projects with Docker =====
Задача: сделать образ, который при обращении браузером на порт 8080 показывает страничку.
Функционал состоит из package.json
{
"dependencies": {
"express": "*"
},
"scripts": {
"start": "node index.js"
}
}
и index.js
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('How are you doing');
});
app.listen(8080, () => {
console.log('Listening on port 8080');
});
Dockerfile:
# FROM alpine - не сработает, т. к. там нет npm, и на втором шаге будет ошибка
FROM node:alpine # это вариант (тэг) alpine установки образа node, где npm есть
# RUN npm install - не сработает, т. к. внутри контейнера нет файла package.json,
# поэтому сначала надо их скопировать туда
COPY ./ ./ # копирование в контексте сборочного каталога. В данном случае - скопировать все файлы
RUN npm install # теперь можно запускать установку
CMD ["npm", "start"]
# Собрать:
docker build -t username/simpleweb .
# Запуск:
docker run username/simpleweb
Но localhost:8080 работать не будет, т. к. нужно настроить проброс порта в контейнер.\\
Для этого нужно запустить контейнер так:
docker run -p 8080:8080 username/simpleweb # запросы на localhost:порт в контейнере
COPY ./ ./ бросает файлы в корень файловой системы контейнера, что нехорошо. Для решения проблемы добавляется параметр
WORKDIR /usr/app
и все дальнейшие команды будут выполняться в контексте указанного каталога.
Есть нюанс: если изменить что-либо в коде index.js и запустить пересборку образа, то это потянет за собой npm install заново. Решение - так как для запуска npm install нужен только package.json, то Dockerfile нужно писать так:
COPY ./package.json ./
RUN npm install
COPY ./ ./
Это предоствратит постоянную инсталляцию с нуля зависимостей npm при пересборке.
===== 05 Docker Compose with Multiple Local Containers =====
Задача: сделать веб-страницу со счётчиком посещений. Node отвечает за веб-часть, а Redis считает посещения. Можно засунуть Node и Redis в один контейнер, но при масштабировании (увеличении кол-ва контейнеров) получится так, что посещения будут записываться в разные экземпляры Redis. Поэтому нужно сделать два контейнера - один с Node, другой с Redis, и масштабировать только контейнеры с Node.
Так как два контейнера должны взаимодействовать между собой, нужно их соответственно настроить, т. к. изначально всё, что внутри контейнера, изолировано от внешней среды. Есть два способа - использовать Docker CLI (что очень неудобно) или docker-compose, который позволяет избежать постоянного ввода одних и тех же команд через Docker CLI и позволяет автоматизировать работу.
Docker-compose использует конфиг docker-compose.yml, куда добавляются команды Docker CLI.
version: '3'
services:
redis-server:
image: 'redis'
node-app:
build .
ports:
- "4001:8081"
docker-compose автоматически открывает сетевой доступ между контейнерами, созданными в docker-compose.yml.
В качестве хоста в index.js нужно указывать название сервиса, указанного в docker-compose.yml, например,
host: 'redis-server',
port: 6379
# Запуск:
docker-compose up # = docker run myimage
docker-compose up -d # (запуск в фоне)
# Сборка и запуск
docker-compose up --build # = docker build . + docker run myimage
# Остановка (всех контейнеров)
docker-compose down
Автоперезапуск контейнера при падении\\
Варианты перезапуска:
* no - не перезапускать (по умолчанию)
* always - всегда перезапускать
* on-failure - перезапускать в случае ошибки (код возврата не 0)
* unless-stopped - всегда перезапускать, если только это не было остановлено специально
Добавляется в docker-compose.yml в параметры конкретного сервиса.
restart: always
# Статус контейнеров docker-compose
docker-compose ps
показывает статус контейнеров, перечисленных в docker-compose.yml. Если этого файла в каталоге нет, то будет ошибка, т. к. docker-compose не знает, о чём идёт речь.
===== 06 Creating a Production-Grade Workflow =====
Этапы развития ПО - разработка, тестирование, продуктивное использование.\\
Создаётся 2 файла: Dockerfile.dev (для разработки) и просто Dockerfile (продакшн).\\
Вызвать Dockerfile.dev можно так:
docker build -f Dockerfile.dev .
Volume - ссылки изнутри контейнера на ресурсы вовне, например, на файлы и каталоги, чтобы не пересобирать и не перезапускать контейнер каждый раз, как требуются изменения. Например,
docker run -p 3000:3000 -v /app/node_modules -v $(pwd):/app
**$(pwd):/app -** $(pwd) - present working directory в хостовой системе, привязать её к каталогу /app внутри контейнера.\\
**-v /app/node_modules** указывает, что нужно использовать этот каталог внутри контейнера и не использовать ссылку вовне (синтаксис без :). Это используется, если ресурсы имеются в контейнере и их нет на хосте, вроде исключения.
Чтобы избежать излишней сложности запуска контейнеров через docker run, можно использовать docker-compose. В docker-compose.yml нужно указать:
version: '3'
services:
web:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
volumes:
- /app/node_modules
- .:/app
и запустить docker-compose up. Секция build содержит опции context: и dockerfile:, т. к. используется нестандартное имя докерфайла Dockerfile-dev.
После того, как создан Volume, который смотрит из контейнера на хост, возникает вопрос, нужна ли операция копирования (COPY . .) в докерфайле? Лучше её оставить, т. к. в будущем может быть принято решение не использовать docker-compose, или этот докерфайл будет использован впоследствии для создания докерфайла для продуктивного использования.
Тестирование: после
docker build -f Dockerfile.dev .
# запустить
docker run -it ID npm run test
Тесты в данном случае находятся в src/app.test.js, и чтобы менять их на ходу, можно создать для них Volume в docker-compose.yml, но есть другой способ - выполнить команду для запущенного контейнера:
docker exec -it ID npm run test
Можно применять это как временное решение, т. к. надо выяснять ID запущенного контейнера.
Есть ещё один способ - добавить сервис для тестирования в docker-compose.yml
services:
tests:
build:
context: .
dockerfile: Dockerfile.dev
volumes:
- /app/node_modules
- .:/app
command: ["npm", "run", "test"]
# Далее нужно пересобрать и запустить контейнеры
docker-compose up --build
После этого можно менять файл тестов и видеть, как они сразу выполняются в терминале. Недостаток в том, что нельзя управлять выполнением тестов, т. к. нет меню управления.\\
# Вывод процесса в контейнере на стандартный вывод, т. е. экран, не помогает.
docker attach -it ID
Это связано с тем, что процесс запускается отдельным потоком, а подключение через attach происходит в первичную консоль. Можно посмотреть процессы, запустив
docker exec -it ID sh
# и там выполнив команду
ps
==== Продакшен ====
Замена движка на nginx. Вопрос в том, что в данном случае нужно использовать ДВА исходных образа - node и nginx, который нужно добавить после сборки npm. Dockerfile:
#Build phase
FROM node:alpine as builder
WORKDIR '/app'
COPY package.json .
RUN npm install
COPY . .
RUN npm run build
# всё барахло (результат сборки) теперь находится в /app/build
# Run phase
FROM nginx
# копировать из builder (пред. стадии),
# из каталога с барахлом в стандартный каталог nginx
COPY --from=builder /app/build /usr/share/nginx/html
# Собрать образ для продакшена:
docker build .
# Запустить:
docker run -p 8080:80 ID
===== 07 Continuous Integration and Deployment with AWS =====
Теперь с задействованием Гитхаба - двух веток feature и master. Master делает pull request из feature. Код проверяется Travis CI, который при новых коммитах также делает pull request кода и запускает проверки.
Сначала нужно создать открытый пустой репозиторий на Гитхабе. Затем
git init
git add .
git commit -m "initial commit"
Содержимое каталога скопируется.
git remote add origin
travis-ci.org, авторизовать Тревис на Гитхабе, указать нужный репозиторий.
Настройки тревиса буду указаны в файле .travis.yml, его нужно положить в корень каталога проекта.
Схема файла - копировать запущенный докер, построить образ из Dockerfile.dev, настройки тестов, настройки выкладки кода на AWS (Amazon Web Services) и т. п.
sudo: required
services:
- docker
before_install:
- docker build -t username/docker-react (or testme or smth else) -f Dockerfile.dev .
script:
- docker run username/docker-react npm run test -- --coverage
-- --coverage нужен, чтобы после прохождения тестов скрипт выходил, а не ждал выбора пользователя.
Затем выполняем
git add .
git commit -m "added travis file"
git push origin master
После этого коммит проваливается в Гит и забирается Тревисом, который действует сообразно .travis.yml и выдаёт результат теста.
Далее идём в AWS и ищем там Elastic beanstalk, создать приложение, например, docker-react, создать там окружение - web server environment, платформа - docker.
Затем нужно добавить в .travis.yml
deploy:
provider: elasticbeanstalk
region: "us-west-2" (берётся из URL приложения на Амазоне)
app: "docker-react"
env: "Docker-env"
bucket_name: "elasticbeanstalk-us-west-2-28354762534"
bucket_path: "docker" (= app:)
on:
branch: master
Затем на Амазоне надо добавить IAM - ключи для доступа и права для пользователя, например, docker-react-travis-ci. Ключи надо добавить в Environment variables Тревиса, но не в .travis.yml!
В .travis.yml в раздел deploy затем нужно добавить
access_key_id: $AWS_ACCESS_KEY
secret_access_key:
secure: "$AWS_SECRET_KEY"
git add .
git commit -m "added travis deploy config"
git push origin master
Чтобы пробросить порт из контейнера в случае использования elastic-beanstalk, нужно прописать в Dockerfile после FROM nginx:
EXPOSE 80
Потом опять та же мантра:
git add .
git commit -m "added expose 80"
git push origin master
===== 09 Dockerizing Multiple Services =====
Сервис состоит из нескольких компонентов - самого приложения (подпапки client, api и worker), Postgres и Redis.
Dockerfile подпапок client, api и worker:
FROM node:alpine
WORKDIR '/app'
COPY ./package.json ./
RUN npm install
COPY . .
CMD ["npm", "run", "start"]
Docker-compose.yml в корне:
version: '3'
services:
# просто добавляем соответствующие контейнеры последних версий
postgres:
image: 'postgres:latest'
redis:
image: 'redis:latest'
api:
build:
dockerfile: Dockerfile # без путей
context: ./api # путь здесь
volumes:
- /app/node_modules # не трогать эту папку внутри контейнера
- ./api:/app # скопировать в /app контейнера содержимое ./server
# Следующая секция - переменные (configuration variables).
# Переменные задают параметры контейнера при его запуске (не при создании образа).
# В примере параметры хранятся в файле ./server/keys.js,
# там всякие настройки базы, адреса хостов и порты, пароли и т. п.
# Есть два способа задать значение переменной:
# variableName=value - жёстко задать значение
# variableName - взять значение с хоста
environment:
- REDIS_HOST=redis # в данном случае, имя сервиса
- REDIS_PORT=6379
# и т. д.
client:
build:
dockerfile: Dockerfile
context: ./client
volumes:
- /app/node_modules
- ./client:/app
worker:
build:
dockerfile: Dockerfile
context: ./worker
volumes:
- /app/node_modules
- ./worker:/app
Далее нужно добавить nginx как редиректор разных URL в разные сервисы. Это нужно, чтобы не плодить порты доступа в контейнеры этих сервисов. На примере nginx - настройки в файле default.conf:
* Указать upstream-сервер client:3000
* Указать upstream-сервер server:5000
* Слушать порт 80
* Если кто приходит на /, слать на client upstream
* Если кто приходит на /api, слать на server upstream
Нужно создать в проекте папку nginx, а в ней - default.conf:
upstream client {
server client:3000;
}
upstream api {
server api:5000;
}
server {
listen 80;
location / {
proxy_pass http://client;
}
# Починить ругань на websockets,
# которые обращаются на /sockjs-node
location /sockjs-node {
proxy_pass http://client;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
location /api {
rewrite /api/(.*) /$1 break;
proxy_pass http://api;
}
}
Дальше нужно создать Dockerfile в папке ./nginx:
FROM nginx
WORKDIR '/app'
COPY ./default.conf /etc/nginx/conf.d/default.conf
Добавить в Docker-compose.yml сервис nginx:
nginx:
restart: always
build:
dockerfile: Dockerfile
context: ./nginx
ports:
- 3050:80