Инструменты пользователя

Инструменты сайта


learning:docker

Различия

Показаны различия между двумя версиями страницы.

Ссылка на это сравнение

learning:docker [20.04.2023 10:59] – [Процесс конфигурации с помощью Докера] viacheslavlearning:docker [30.07.2024 19:21] (текущий) – внешнее изменение 127.0.0.1
Строка 1: Строка 1:
 +====== Docker (DKA-Develop) ======
 +[[https://www.youtube.com/playlist?list=PLD5U-C5KK50XMCBkY0U-NLzglcRHzOwAg|DKA-Develop]]
  
 +Образ (image) - основа для запуска контейнеров. Образы хранятся в реестрах (registries) - публичных (типа [[https://hub.docker.com/|hub.docker.com]]) или приватных, т. е., можно развернуть свой.
 +<code bash>
 +# Добавить пользователя в группу 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
 +</code>
 +
 +===== Dockerfile =====
 +Набор инструкций для сборки образа.
 +<code yaml>
 +# Базовый образ
 +FROM ubuntu
 +# Информация о создателе
 +MAINTAINER Username <mail@mail.com>
 +# Команды для выполнения внутри контейнера
 +RUN apt update && apt install cowsay -y && ln -s /usr/games/cowsay /usr/bin/cowsay
 +# Что выполнять по умолчанию при запуске контейнера
 +ENTRYPOINT ["cowsay"]
 +</code>
 +
 +<code bash>
 +# Собрать образ
 +docker build -t username/imagename .
 +# Запустить
 +docker run username/imagename "test"
 +# Если бы в Dockerfile не был указан параметр ENTRYPOINT,
 +# то запускать надо было бы так:
 +docker run username/imagename cowsay "test"
 +</code>
 +
 +===== Связь между контейнерами =====
 +Например, запускается MariaDB:
 +<code bash>
 +docker run --name mysqlserver -e MYSQL_ROOT_PASSWORD=123456 -d mariadb
 +</code>
 +Затем нужно запустить adminer и связать его с MariaDB.
 +Параметр %%--link%% делает в /etc/hosts контейнера запись db, указывающую на контейнер mysqlserver.
 +<code bash>
 +docker run --link mysqlserver:db -p 8080:8080 adminer
 +</code>
 +
 +Более современный вариант связи - через publish services.
 +
 +===== docker-compose =====
 +docker-compose**.yaml** = docker-compose**.yml**
 +<code yaml>
 +# 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
 +</code>
 +
 +<code bash>
 +# Запустить
 +docker-compose up
 +# Запуск в фоне
 +docker-compose up -d
 +# Список контейнеров, запущенных через compose
 +docker-compose ps
 +# Собрать, пересобрать проект
 +docker-compose build
 +# Удалить контейнер с БД (по имени сервиса)
 +docker-compose rm db
 +</code>
 +Если в Windows установлен Docker toolbox, то вход в браузере через localhost не сработает, нужно заходить на IP виртуальной машины, который можно узнать, выполнив команду
 +<code bash>
 +docker-machine ip default
 +</code>
 +
 +===== Volumes =====
 +Назначение путям внутри контейнера локальных каталогов на хосте. Удобно тем, что при остановке или уничтожении контейнера данные остаются в целости.
 +
 +Есть 3 способа указания томов:
 +
 +  - Объявить том при запуске контейнера.<code bash>docker run -v /data ubuntu</code>Здесь каталог /data внутри контейнера станет томом, и всё, что пишется в /data, сохраняется на этот том. Местоположение тома можно посмотреть командой<code bash>docker inspect -f {{.Mounts}} containername</code>Обратное тоже верно - если что-то положить в каталог монтирования тома, это сразу будет видно в контейнере. Можно явно указать соответствие каталогов на хосте и внутри контейнера:<code bash>docker run -v /localdir:/data ubuntu</code>
 +  - Объявить том с помощью инструкции VOLUME в Dockerfile, например,<code yaml>VOLUME /data</code>\\ <color #ed1c24>Сопоставление явно указанного локального каталога и каталога внутри контейнера не работает.</color><code yaml>VOLUME ./localdir:/data</code>
 +  - Объявить том в docker-compose.yml.
 +<code yaml>
 +volumes:
 + - ./localdir:/data
 + - ./database:/var/lib/mysql
 +</code>
 +
 +===== Пример (Laravel) =====
 +Необходимо 3 сервиса: PHP, MySQL и Composer.
 +
 +Файл .env с переменными:
 +<code>
 +# Paths
 +DB_PATH_HOST=./databases
 +APP_PATH_HOST=./blog
 +APP_PATH_CONTAINER=/var/www/html/
 +</code>
 +В данном случае в папку ./blog кладётся проект, например, клон репозитория Github.
 +
 +docker-compose.yml:
 +<code yaml>
 +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
 +</code>
 +
 +./web/Dockerfile
 +<code yaml>
 +FROM php:7.2-apache
 +RUN docker-php-ext-install pdo_mysql \
 +&& a2enmod rewrite
 +</code>
 +
 +<code bash>
 +# Сборка и запуск
 +docker-compose up --build
 +# Запустить bash в сервисе-контейнере web
 +# Благодаря указанию working_dir, оболочка сразу попадёт в /var/www/html/
 +docker-compose exec web bash
 +</code>
 +
 +===== Решение проблем =====
 +В 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]]
 +
 +<code bash>
 +## 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 <hash>           # Gracefully stop the specified container
 +docker container kill <hash>         # Force shutdown of the specified container
 +docker container rm <hash>        # 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 <image id>            # 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 <image> username/repository:tag  # Tag <image> 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 <composefile> <appname>  # Run the specified Compose file
 +docker service ls                 # List running services associated with an app
 +docker service ps <service>                  # List tasks associated with an app
 +docker inspect <task or container>                   # Inspect task or container
 +docker container ls -q                                      # List container IDs
 +docker stack rm <appname>                             # 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 <node ID>"        # 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 <file> <app>  # 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 <file> <app>"   # 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
 +
 +</code>
 +
 +====== Docker (udemy.com) ======
 +Докер упрощает установку и запуск программ без возни с зависимостями. Это платформа, создающая и запускающая т. н. контейнеры. Контейнер - это запущенный изолированный экземпляр приложения из заранее заготовленного образа (image).
 +
 +При установке Докера ставится 2 компонента - клиент (CLI, управление) и сервер (функциональная часть). При установке Докера рекомендуется регистрация на docker.com. После установки Докера и перезагрузки нужно вбить рег. данные в запущенный Докер для доступа к Docker Cloud.
 +
 +===== 01 Dive Into Docker =====
 +<code bash>
 +# Версия:
 +docker version
 +
 +# Запустить тестовый контейнер:
 +docker run hello-world
 +</code>
 +Если образа нет в локальном кэше (image cache), он скачивается автоматически из репозитория (Docker Hub).
 +
 +Программы взаимодействуют с железом через ядро (kernel) путём системных запросов (system call). Контейнер - это изолированная область для запуска процессов, использует namespacing для изолирования системных ресурсов для запуска процесса, и control groups (cgroups) для лимита выделяемой мощности. В частности, это позволяет, например, запускать программы с разными зависимостями, в то время как эти зависимости не могли бы работать одновременно в одной и той же ОС. Т. е., виртуализация приложения или сервиса внутри одной ОС.
 +
 +Так как namespacing и cgroups - сугубо линуксовое явление, то, когда Докер ставится на Windows или Mac, в реальности туда ставится виртуальная машина с линуксом, через которую и работает контейнеризация.
 +
 +===== 02 Manipulating Containers with the Docker Client =====
 +<code bash>
 +# Аргументы контейнеру
 +docker run busybox echo hi there
 +docker run busybox ls
 +# Список запущенных контейнеров
 +docker ps
 +# Список когда-либо запущенных контейнеров
 +docker ps --all
 +</code>
 +
 +docker run = docker create + docker start
 +<code bash>
 +docker create hello-world # создаёт контейнер и показывает его ID
 +docker start -a ID # запускает конкретный контейнер с выводом результатов
 +docker start ID # просто запускает контейнер без вывода результатов на экран. Выводится только ID запущенного контейнера.
 +docker logs ID # посмотреть вывод уже отработавшего контейнера по команде docker start ID
 +# (не в реальном времени, а то, что успело выполниться на момент запроса).
 +</code>
 +Запущенный однажды контейнер лежит на диске и его всегда можно вызвать по ID, но команду заменить не получится. Например, запускали
 +<code bash>
 +docker run busybox echo hi there
 +# Если запустить
 +docker start -a ID echo bye there
 +# то ничего не получится.
 +</code>
 +
 +<code bash>
 +# Удаление остановленных контейнеров, используемых ими сетей, кэша
 +# (образы нужно будет скачивать заново из Docker Hub)
 +docker system prune
 +</code>
 +<code bash>
 +docker stop ID
 +docker kill ID
 +# Если stop не отрабатывает за 10 сек, автоматически посылается kill.
 +</code>
 +
 +<code bash>
 +# Запуск команд внутри контейнера
 +docker exec -it ID <command>
 +# Например,
 +docker exec -it ID redis-cli
 +# Без ключа -it (-i -t) процесс запустится, но без возможности ввода.
 +# -i - STDIN, -t - вывод текста. Можно запустить и 
 +docker exec -i ID redis-cli
 +# но в этом случае не будет выводиться консоль и будет показываться
 +# только ввод пользователя, выглядит непривычно и неудобно, хотя всё работает.
 +</code>
 +
 +<code bash>
 +# Запустить "удалённую" консоль в контейнере
 +docker exec -it ID sh
 +# потом можно запускать redis-cli и всё остальное за один сеанс, чтобы не вводить каждый раз 
 +docker exec -i ID redis-cli
 +
 +# Создать контейнер и запустить там консоль
 +docker run -it busybox sh
 +</code>
 +
 +Файловые системы разных контейнеров изолированы, и созданное что-либо в одном контейнере не видится из других.
 +
 +===== 03 Building Custom Images Through Docker Server =====
 +Для создания собственного образа нужно написать Dockerfile, который через клиент передаётся серверу, который на основе докерфайла создаёт образ.
 +
 +Докерфайл пишется так:
 +  - Указывается базовый образ (типа выбора ОС)
 +  - Запускаются команды для установки доп. программ (типа скачивания инсталлятора и установки, команды относятся к базовому образу, не к Докеру)
 +  - Указываются команды, выполняемые в момент запуска контейнера (типа запуска .exe)
 +
 +Создаётся каталог, где размещается файл Dockerfile (с большой буквы, без расширения). Например:
 +<code>
 +FROM alpine
 +RUN apk add --update redis
 +CMD ["redis-server"]
 +</code>
 +
 +Затем запускается
 +<code bash>
 +docker build .
 +</code>
 +На каждом этапе сборки создаётся промежуточный контейнер, где выполняется команда, затем контейнер стирается и выполняется следующая.\\
 +Если добавить команду на 2 этап, например,
 +<code>
 +RUN apk add --update gcc
 +</code>
 +то предыдущие команды уже не будут выполняться, потому что результат уже есть в кэше. Но если поменять порядок команд, то всё будет выполняться заново, т. к. в кэше нет таких контейнеров.
 +
 +<code bash>
 +# Чтобы задать человеческое имя образу при сборке:
 +docker build -t YourDockerID/RepoOrProjectName:Version .
 +# Например,
 +docker build -t username/redis:latest .
 +# Версия latest грузится и запускается Докером по умолчанию.
 +# Если просто написать
 +docker build -t username/redis,
 +# то версия latest добавится автоматически.
 +</code>
 +
 +<code bash>
 +# Можно сделать образ руками, запустив все нужные команды в консоли, а затем "закоммитив" образ.
 +docker run -it alpine sh
 +apk add --update redis
 +# Из другой консоли:
 +docker ps # (узнать ID изменяемого контейнера)
 +docker commit -c 'CMD ["redis-server"]' ID
 +# Создастся образ с новым ID.
 +</code>
 +<WRAP round tip 75%>
 +Чтобы запустить образ, можно не копировать весь ID, достаточно части, которая будет уникальной.
 +</WRAP>
 +
 +===== 04 Making Real Projects with Docker =====
 +Задача: сделать образ, который при обращении браузером на порт 8080 показывает страничку.
 +Функционал состоит из package.json
 +<code>
 +{
 +  "dependencies": {
 +    "express": "*"
 +  },
 +  "scripts": {
 +    "start": "node index.js"
 +  }
 +}
 +</code>
 +и index.js
 +<code javascript>
 +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');
 +});
 +</code>
 +Dockerfile:
 +<code>
 +# FROM alpine - не сработает, т. к. там нет npm, и на втором шаге будет ошибка
 +FROM node:alpine # это вариант (тэг) alpine установки образа node, где npm есть
 +# RUN npm install - не сработает, т. к. внутри контейнера нет файла package.json,
 +# поэтому сначала надо их скопировать туда
 +COPY ./ ./ # копирование в контексте сборочного каталога. В данном случае - скопировать все файлы
 +RUN npm install # теперь можно запускать установку
 +CMD ["npm", "start"]
 +</code>
 +
 +<code bash>
 +# Собрать:
 +docker build -t username/simpleweb .
 +# Запуск:
 +docker run username/simpleweb
 +</code>
 +Но localhost:8080 работать не будет, т. к. нужно настроить проброс порта в контейнер.\\
 +Для этого нужно запустить контейнер так:
 +<code bash>
 +docker run -p 8080:8080 username/simpleweb # запросы на localhost:порт в контейнере
 +</code>
 +
 +COPY ./ ./ бросает файлы в корень файловой системы контейнера, что нехорошо. Для решения проблемы добавляется параметр
 +WORKDIR /usr/app
 +и все дальнейшие команды будут выполняться в контексте указанного каталога.
 +
 +Есть нюанс: если изменить что-либо в коде index.js и запустить пересборку образа, то это потянет за собой npm install заново. Решение - так как для запуска npm install нужен только package.json, то Dockerfile нужно писать так:
 +<code>
 +COPY ./package.json ./
 +RUN npm install
 +COPY ./ ./
 +</code>
 +Это предоствратит постоянную инсталляцию с нуля зависимостей 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.
 +<code yaml>
 +version: '3'
 +services:
 + redis-server:
 +  image: 'redis'
 + node-app:
 +  build .
 +  ports:
 +   - "4001:8081"
 +</code>
 +docker-compose автоматически открывает сетевой доступ между контейнерами, созданными в docker-compose.yml.
 +
 +В качестве хоста в index.js нужно указывать название сервиса, указанного в docker-compose.yml, например,
 +<code javascript>
 +host: 'redis-server',
 +port: 6379
 +</code>
 +
 +<code bash>
 +# Запуск:
 +docker-compose up # = docker run myimage
 +docker-compose up -d # (запуск в фоне)
 +# Сборка и запуск
 +docker-compose up --build # = docker build . + docker run myimage
 +# Остановка (всех контейнеров)
 +docker-compose down
 +</code>
 +
 +Автоперезапуск контейнера при падении\\
 +Варианты перезапуска:
 +  * no -  не перезапускать (по умолчанию)
 +  * always - всегда перезапускать
 +  * on-failure - перезапускать в случае ошибки (код возврата не 0)
 +  * unless-stopped - всегда перезапускать, если только это не было остановлено специально
 +
 +Добавляется в docker-compose.yml в параметры конкретного сервиса.
 +<code yaml>
 +restart: always
 +</code>
 +
 +<code bash>
 +# Статус контейнеров docker-compose
 +docker-compose ps
 +</code>
 +показывает статус контейнеров, перечисленных в docker-compose.yml. Если этого файла в каталоге нет, то будет ошибка, т. к. docker-compose не знает, о чём идёт речь.
 +
 +===== 06 Creating a Production-Grade Workflow =====
 +Этапы развития ПО - разработка, тестирование, продуктивное использование.\\
 +Создаётся 2 файла: Dockerfile.dev (для разработки) и просто Dockerfile (продакшн).\\
 +Вызвать Dockerfile.dev можно так:
 +<code bash>
 +docker build -f Dockerfile.dev .
 +</code>
 +
 +Volume - ссылки изнутри контейнера на ресурсы вовне, например, на файлы и каталоги, чтобы не пересобирать и не перезапускать контейнер каждый раз, как требуются изменения. Например,
 +<code bash>
 +docker run -p 3000:3000 -v /app/node_modules -v $(pwd):/app <image_id>
 +</code>
 +**$(pwd):/app -** $(pwd) - present working directory в хостовой системе, привязать её к каталогу /app внутри контейнера.\\
 +**-v /app/node_modules** указывает, что нужно использовать этот каталог внутри контейнера и не использовать ссылку вовне (синтаксис без :). Это используется, если ресурсы имеются в контейнере и их нет на хосте, вроде исключения.
 +
 +Чтобы избежать излишней сложности запуска контейнеров через docker run, можно использовать docker-compose. В docker-compose.yml нужно указать:
 +<code yaml>
 +version: '3'
 +services:
 + web:
 +  build:
 +   context: .
 +   dockerfile: Dockerfile.dev
 +  ports:
 +   - "3000:3000"
 +  volumes:
 +   - /app/node_modules
 +   - .:/app
 +</code>
 +и запустить docker-compose up. Секция build содержит опции context: и dockerfile:, т. к. используется нестандартное имя докерфайла Dockerfile-dev.
 +
 +После того, как создан Volume, который смотрит из контейнера на хост, возникает вопрос, нужна ли операция копирования (COPY . .) в докерфайле? Лучше её оставить, т. к. в будущем может быть принято решение не использовать docker-compose, или этот докерфайл будет использован впоследствии для создания докерфайла для продуктивного использования.
 +
 +Тестирование: после
 +<code bash>
 +docker build -f Dockerfile.dev .
 +# запустить
 +docker run -it ID npm run test
 +</code>
 +Тесты в данном случае находятся в src/app.test.js, и чтобы менять их на ходу, можно создать для них Volume в docker-compose.yml, но есть другой способ - выполнить команду для запущенного контейнера:
 +<code bash>
 +docker exec -it ID npm run test
 +</code>
 +Можно применять это как временное решение, т. к. надо выяснять ID запущенного контейнера.
 +
 +Есть ещё один способ - добавить сервис для тестирования в docker-compose.yml
 +<code yaml>
 +services:
 + tests:
 +  build:
 +   context: .
 +   dockerfile: Dockerfile.dev
 +  volumes:
 +   - /app/node_modules
 +   - .:/app
 +  command: ["npm", "run", "test"]
 +</code>
 +
 +<code bash>
 +# Далее нужно пересобрать и запустить контейнеры
 +docker-compose up --build
 +</code>
 +После этого можно менять файл тестов и видеть, как они сразу выполняются в терминале. Недостаток в том, что нельзя управлять выполнением тестов, т. к. нет меню управления.\\
 +<code bash>
 +# Вывод процесса в контейнере на стандартный вывод, т. е. экран, не помогает.
 +docker attach -it ID
 +</code>
 +Это связано с тем, что процесс запускается отдельным потоком, а подключение через attach происходит в первичную консоль. Можно посмотреть процессы, запустив
 +<code bash>
 +docker exec -it ID sh
 +# и там выполнив команду
 +ps
 +</code>
 +
 +==== Продакшен ====
 +Замена движка на nginx. Вопрос в том, что в данном случае нужно использовать ДВА исходных образа - node и nginx, который нужно добавить после сборки npm. Dockerfile:
 +<code>
 +#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
 +</code>
 +
 +<code bash>
 +# Собрать образ для продакшена:
 +docker build .
 +# Запустить:
 +docker run -p 8080:80 ID
 +</code>
 +
 +===== 07 Continuous Integration and Deployment with AWS =====
 +Теперь с задействованием Гитхаба - двух веток feature и master. Master делает pull request из feature. Код проверяется Travis CI, который при новых коммитах также делает pull request кода и запускает проверки.
 +Сначала нужно создать открытый пустой репозиторий на Гитхабе. Затем
 +<code>
 +git init
 +git add .
 +git commit -m "initial commit"
 +</code>
 +Содержимое каталога скопируется.
 +<code>
 +git remote add origin <github repo link>
 +</code>
 +
 +travis-ci.org, авторизовать Тревис на Гитхабе, указать нужный репозиторий.
 +
 +Настройки тревиса буду указаны в файле .travis.yml, его нужно положить в корень каталога проекта.
 +Схема файла - копировать запущенный докер, построить образ из Dockerfile.dev, настройки тестов, настройки выкладки кода на AWS (Amazon Web Services) и т. п.
 +
 +<code yaml>
 +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
 +</code>
 +-- --coverage нужен, чтобы после прохождения тестов скрипт выходил, а не ждал выбора пользователя.
 +Затем выполняем
 +<code>
 +git add .
 +git commit -m "added travis file"
 +git push origin master
 +</code>
 +После этого коммит проваливается в Гит и забирается Тревисом, который действует сообразно .travis.yml и выдаёт результат теста.
 +
 +Далее идём в AWS и ищем там Elastic beanstalk, создать приложение, например, docker-react, создать там окружение - web server environment, платформа - docker.
 +
 +Затем нужно добавить в .travis.yml
 +<code yaml>
 +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
 +</code>
 +
 +Затем на Амазоне надо добавить IAM - ключи для доступа и права для пользователя, например, docker-react-travis-ci. Ключи надо добавить в Environment variables Тревиса, но не в .travis.yml!
 +В .travis.yml в раздел deploy затем нужно добавить
 +<code yaml>
 +access_key_id: $AWS_ACCESS_KEY
 +secret_access_key:
 + secure: "$AWS_SECRET_KEY"
 +</code>
 +
 +<code>
 +git add .
 +git commit -m "added travis deploy config"
 +git push origin master
 +</code>
 +
 +Чтобы пробросить порт из контейнера в случае использования elastic-beanstalk, нужно прописать в Dockerfile после FROM nginx:
 +<code>
 +EXPOSE 80
 +</code>
 +
 +Потом опять та же мантра:
 +<code>
 +git add .
 +git commit -m "added expose 80"
 +git push origin master
 +</code>
 +
 +===== 09 Dockerizing Multiple Services =====
 +Сервис состоит из нескольких компонентов - самого приложения (подпапки client, api и worker), Postgres и Redis.
 +Dockerfile подпапок client, api и worker:
 +<code>
 +FROM node:alpine
 +WORKDIR '/app'
 +COPY ./package.json ./
 +RUN npm install
 +COPY . .
 +CMD ["npm", "run", "start"]
 +</code>
 +Docker-compose.yml в корне:
 +<code yaml>
 +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
 +</code>
 +
 +Далее нужно добавить nginx как редиректор разных URL в разные сервисы. Это нужно, чтобы не плодить порты доступа в контейнеры этих сервисов. На примере nginx - настройки в файле default.conf:
 +  * Указать upstream-сервер client:3000
 +  * Указать upstream-сервер server:5000
 +  * Слушать порт 80
 +  * Если кто приходит на /, слать на client upstream
 +  * Если кто приходит на /api, слать на server upstream
 +
 +Нужно создать в проекте папку nginx, а в ней - default.conf:
 +<code>
 +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;
 + }
 +}
 +</code>
 +
 +Дальше нужно создать Dockerfile в папке ./nginx:
 +<code>
 +FROM nginx
 +WORKDIR '/app'
 +COPY ./default.conf /etc/nginx/conf.d/default.conf
 +</code>
 +
 +Добавить в Docker-compose.yml сервис nginx:
 +<code yaml>
 + nginx:
 +  restart: always
 +  build:
 +   dockerfile: Dockerfile
 +   context: ./nginx
 +  ports:
 +   - 3050:80
 +</code>

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki