Полное практическое руководство по Docker: с нуля до кластера на AWS (часть 3)

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

Инструкция состоит  из четырех частей. В третьей части рассматриваются следующие темы:

—  SF Food Trucks

—  Сети Docker

3.0 Многоконтейнерные окружения

В прошлом разделе мы увидели, как легко и просто запускать приложения с помощью Докера. Мы начали с простого статического сайта, а потом запустили Flask-приложение. Оба варианта можно было запускать локально или в облаке, несколькими командами. Общая черта этих приложений: каждое из них работало в одном контейнере.

 

Если у вас есть опыт управления сервисами в продакшене, то вы знаете, что современные приложения обычно не такие простые. Почти всегда есть база данных (или другой тип постоянного хранилища). Системы вроде Redis и Memcached стали практически обязательной частью архитектуры веб-приложений. Поэтому, в этом разделе мы научимся “докеризировать” приложения, которым требуется несколько запущенных сервисов.

 

В частности, мы увидим, как запускать и управлять многоконтейнерными Докер-окружениями. Почему нужно несколько контейнеров, спросите вы? Ну, одна из главных идей Докера в том, что он предоставляет изоляцию. Идея совмещения процесса и его зависимостей в одной песочнице (называемой контейнером) и делает Докер мощным инструментом.

 

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

3.1 SF Food Trucks

Приложение, которое мы переведем в Докер, называется SF Food Trucks. Моя цель была сделать что-то полезное (и похожее на настоящее приложение из реального мира), что-то, что использует как минимум один сервис, но не слишком сложное для этого пособия. Вот что я придумал.

7

Бэкэнд приложения написано на Питоне (Flask), а для поиска используется Elasticsearch. Как и все остальное в этом пособии, код находится на Github. Мы используем это приложение, чтобы научиться запускать и деплоить много-контейнерное окружение.

Теперь, когда вы завелись (надеюсь), давайте подумаем, как будет выглядеть этот процесс. В нашем приложении есть бэкэнд на Flask и сервис Elasticsearch. Очевидно, что можно поделить приложение на два контейнера: один для Flask, другой для Elasticsearch (ES). Если приложение станет популярным, то можно будет добавлять новые контейнеры в нужном месте, смотря где будет узкое место.

Отлично, значит нужно два контейнера. Это не сложно, правда? Мы уже создавали Flask-контейнер в прошлом разделе. А для Elasticsearch:

$ docker search elasticsearch
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
elasticsearch Elasticsearch is a powerful open source se… 697 [OK]
itzg/elasticsearch Provides an easily configurable Elasticsea… 17 [OK]
tutum/elasticsearch Elasticsearch image – listens in port 9200. 15 [OK]
barnybug/elasticsearch Latest Elasticsearch 1.7.2 and previous re… 15 [OK]
digitalwonderland/elasticsearch Latest Elasticsearch with Marvel & Kibana 12 [OK]
monsantoco/elasticsearch ElasticSearch Docker image 9 [OK]

Не удивительно, но существуют официальный образ для Elasticsearch. Чтобы запустить ES, нужно всего лишь выполнить docker run, и вскоре у нас будет локальный, работающий контейнер с одним узлом ES.

$ docker run -dp 9200:9200 elasticsearch
d582e031a005f41eea704cdc6b21e62e7a8a42021297ce7ce123b945ae3d3763

$ curl 0.0.0.0:9200
{
name” : “Ultra-Marine“,
cluster_name” : “elasticsearch“,
version” : {
number” : “2.1.1“,
build_hash” : “40e2c53a6b6c2972b3d13846e450e66f4375bd71“,
build_timestamp” : “2015-12-15T13:05:55Z“,
build_snapshot” : false,
“lucene_version” : “5.3.1
},
tagline” : “You Know, for Search
}

Заодно давайте запустим контейнер с Flask. Но вначале нужен Dockerfile. В прошлой секции мы использовали образ python:3-onbuild в качестве базового. Однако, в этом раз, кроме установки зависимостей через pip, нам нужно, чтобы приложение генерировало минимизированный Javascript-файл для продакшена. Для этого понадобится Nodejs. Так что нужен свой билд с нуля, поэтому начнем с базового образа ubuntu.

Замечание: если оказывается, что существующий образ не подходит для вашей задачи, то спокойно создавайте свой образ на основе другого базового образа. В большинстве случаем, для образов на Docker Hub можно найти соответствующий Dockerfile на Github. Почитайте существующий Докерфайлы — это один из лучших способов научиться делать свои образы.

Наш Dockerfile для Flask-приложения выглядит следующим образом:

# start from base
FROM ubuntu:14.04
MAINTAINER Prakhar Srivastav <[email protected]>
# install system-wide deps for python and node
RUN apt-get -yqq update
RUN apt-get -yqq install python-pip python-dev
RUN apt-get -yqq install nodejs npm
RUN ln -s /usr/bin/nodejs /usr/bin/node
# copy our application code
ADD flask-app /opt/flask-app
WORKDIR /opt/flask-app
# fetch app specific deps
RUN npm install
RUN npm run build
RUN pip install -r requirements.txt
# expose port
EXPOSE 5000
# start app
CMD [ “python“, “./app.py” ]

Тут много всего нового. Вначале указан базовый образ Ubuntu LTS, потом используется пакетный менеджер apt-get для установки зависимостей, в частности — Python и Node. Флаг yqq нужен для игнорирования вывода и автоматического выбора “Yes” во всех местах. Также создается символическая ссылка для бинарного файла node. Это нужно для решения проблем обратной совместимости.

Потом мы используем команду ADD для копирования приложения в нужную директорию в контейнере — /opt/flask-app. Здесь будет находиться весь наш код. Мы также устанавливаем эту директорию в качестве рабочей, так что следующие команды будут выполняться в контексте этой локации. Теперь, когда наши системные зависимости установлены, пора установить зависимости уровня приложения. Начнем с Node, установки пакетов из npm и запуска команды сборки, как указано в нашем файле package.json. В конце устанавливаем пакеты Python, открываем порт и определяем запуск приложения с помощь CMD, как в предыдущем разделе.

Наконец, можно собрать образ и запустить контейнер (замените prakhar1989 на свой username ниже).

$ docker build -t prakhar1989/foodtrucks-web .

При первом запуске нужно будет больше времени, так как клиент Докера будет скачивать образ ubuntu, запускать все команды и готовить образ. Повторный запуск docker build после последующих изменений будет практически моментальным. Давайте попробуем запустить приложение

$ docker run -P prakhar1989/foodtrucks-web
Unable to connect to ES. Retying in 5 secs…
Unable to connect to ES. Retying in 5 secs…
Unable to connect to ES. Retying in 5 secs…
Out of retries. Bailing out…

Упс! Наше приложение не смогло запуститься, потому что оно не может подключиться к Elasticsearch. Как сообщить одному контейнеру о другом и как заставить их взаимодействовать друг с другом? Ответ — в следующей секции.

3.2 Сети Docker

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

Ладно, давайте запустим docker ps, что тут у нас:

$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e931ab24dedc elasticsearch “/docker-entrypoint.s” 2 seconds ago Up 2 seconds 0.0.0.0:9200->9200/tcp, 9300/tcp cocky_spence

Итак, у нас есть контейнер ES по адресу и порту 0.0.0.0:9200, и мы можем напрямую обращаться к нему. Если можно было бы сообщить нашему приложению подключаться к этому адресу, то оно сможет общаться с ES, верно? Давайте взглянем на код на Питоне, туда, где описано подключение.

es = Elasticsearch(host=’es‘)

Нужно сообщить Flask-контейнеру, что контейнер ES запущен на хосте 0.0.0.0 (порт по умолчанию 9200), и все заработает, да? К сожалению, нет, потому что IP 0.0.0.0 это адрес для доступа к контейнеру с  хост-машины, то есть с моего Мака. Другой контейнер не сможет обратиться по этому адресу. Ладно, если не этот адрес, то какой другой адрес нужно использовать для работы с контейнером ES? Рад, что вы спросили.

Это хороший момент, чтобы изучить работу сети в Докере. После установки, Докер автоматически создает три сети:

$ docker network ls
NETWORK ID NAME DRIVER
075b9f628ccc none null
be0f7178486c host host
8022115322ec bridge bridge

Сеть bridge — это сеть, в которой контейнеры запущены по умолчанию. Это значит, что когда я запускаю контейнер ES, он работает в этой сети bridge. Чтобы удостовериться, давайте проверим:

$ docker network inspect bridge
[
{
Name“: “bridge“,
Id“: “8022115322ec80613421b0282e7ee158ec41e16f565a3e86fa53496105deb2d7“,
Scope“: “local“,
Driver“: “bridge“,
IPAM“: {
Driver“: “default“,
Config“: [
{
Subnet“: “172.17.0.0/16
}
]
},
Containers“: {
e931ab24dedc1640cddf6286d08f115a83897c88223058305460d7bd793c1947“: {
EndpointID“: “66965e83bf7171daeb8652b39590b1f8c23d066ded16522daeb0128c9c25c189“,
MacAddress“: “02:42:ac:11:00:02“,
IPv4Address“: “172.17.0.2/16“,
IPv6Address“: “”
}
},
Options“: {
com.docker.network.bridge.default_bridge“: “true“,
com.docker.network.bridge.enable_icc“: “true“,
com.docker.network.bridge.enable_ip_masquerade“: “true“,
com.docker.network.bridge.host_binding_ipv4“: “0.0.0.0“,
com.docker.network.bridge.name“: “docker0“,
com.docker.network.driver.mtu“: “1500
}
}
]

Видно, что контейнер e931ab24dedc находится в секции Containers. Также виден IP-адрес, выданный этому контейнеру — 172.17.0.2. Именно этот адрес мы и искали? Давайте проверим: запустим Flask-приложение и попробуем обратиться к нему по IP:

$ docker run -it –rm prakhar1989/foodtrucks-web bash
[email protected]:/opt/flask-app# curl 172.17.0.2:9200
bash: curl: command not found
[email protected]:/opt/flask-app# apt-get -yqq install curl
[email protected]:/opt/flask-app# curl 172.17.0.2:9200
{
name” : “Jane Foster“,
cluster_name” : “elasticsearch“,
version” : {
number” : “2.1.1“,
build_hash” : “40e2c53a6b6c2972b3d13846e450e66f4375bd71“,
“build_timestamp” : “2015-12-15T13:05:55Z“,
“build_snapshot” : false,
lucene_version” : “5.3.1
},
tagline” : “You Know, for Search
}
[email protected]:/opt/flask-app# exit

Сейчас все должно быть понятно. Мы запустили контейнер в интерактивном режиме с процессом bash. Флаг --rm нужен для удобства, благодаря нему контейнер автоматически удаляется после выхода. Мы попробуем curl, но нужно сначала установить его. После этого можно удостовериться, что по адресу 172.17.0.2:9200 на самом деле можно обращаться к ES! Супер!

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

1. Придется добавлять записи в файл /etc/hosts внутри Flask-контейнера, чтобы приложение понимало, что имя хоста es означает 172.17.0.2. Если IP-адрес меняется, то придется вручную менять запись.

2. Так как сеть bridge используется всеми контейнерами по умолчанию, этот метод не безопасен.

Но есть хорошие новости: в Докере есть отличное решение этой проблемы. Докер позволяет создавать собственные изолированные сети. Это решение также помогает справиться с проблемой /etc/hosts, сейчас увидим как.

Во-первых, давайте создадим свою сеть:

$ docker network create foodtrucks
1a3386375797001999732cb4c4e97b88172d983b08cd0addfcb161eed0c18d89

$ docker network ls
NETWORK ID NAME DRIVER
1a3386375797 foodtrucks bridge
8022115322ec bridge bridge
075b9f628ccc none null
be0f7178486c host host

Команда network create создает новую сеть bridge. Нам сейчас нужен именно такой тип. Существуют другие типы сетей, и вы можете почитать о них в официальной документации.

Теперь у нас есть сеть. Можно запустить наши контейнеры внутри сети с помощью флага --net. Давайте так и сделаем, но сначала остановим контейнер с ElasticSearch, который был запущен в сети bridge по умолчанию.

$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e931ab24dedc elasticsearch “/docker-entrypoint.s” 4 hours ago Up 4 hours 0.0.0.0:9200->9200/tcp, 9300/tcp cocky_spence

$ docker stop e931ab24dedc
e931ab24dedc

$ docker run -dp 9200:9200 –net foodtrucks –name es elasticsearch
2c0b96f9b8030f038e40abea44c2d17b0a8edda1354a08166c33e6d351d0c651

$ docker network inspect foodtrucks
[
{
Name“: “foodtrucks“,
Id“: “1a3386375797001999732cb4c4e97b88172d983b08cd0addfcb161eed0c18d89“,
Scope“: “local“,
Driver“: “bridge“,
IPAM“: {
Driver“: “default“,
Config“: [
{}
]
},
Containers“: {
2c0b96f9b8030f038e40abea44c2d17b0a8edda1354a08166c33e6d351d0c651“: {
EndpointID“: “15eabc7989ef78952fb577d0013243dae5199e8f5c55f1661606077d5b78e72a“,
MacAddress“: “02:42:ac:12:00:02“,
IPv4Address“: “172.18.0.2/16“,
IPv6Address“: “”
}
},
Options“: {}
}
]

Мы сделали то же, что и раньше, но на этот раз дали контейнеру название es. Перед тем, как запускать контейнер с приложением, давайте проверим что происходит, когда запуск происходит в сети.

$ docker run -it –rm –net foodtrucks prakhar1989/foodtrucks-web bash
[email protected]:/opt/flask-app# cat /etc/hosts
172.18.0.3 53af252b771a
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.18.0.2 es
172.18.0.2 es.foodtrucks
[email protected]:/opt/flask-app# curl es:9200
bash: curl: command not found
[email protected]:/opt/flask-app# apt-get -yqq install curl
[email protected]:/opt/flask-app# curl es:9200
{
name” : “Doctor Leery“,
“cluster_name” : “elasticsearch“,
version” : {
number” : “2.1.1“,
build_hash” : “40e2c53a6b6c2972b3d13846e450e66f4375bd71“,
build_timestamp” : “2015-12-15T13:05:55Z“,
build_snapshot” : false,
“lucene_version” : “5.3.1
},
tagline” : “You Know, for Search”
}
[email protected]:/opt/flask-app# ls
app.py node_modules package.json requirements.txt static templates webpack.config.js
[email protected]:/opt/flask-app# python app.py
Index not found…
Loading data in elasticsearch
Total trucks loaded: 733
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
[email protected]:/opt/flask-app# exit

Ура! Работает! Магическим образом Докер внес нужные правки в файл /etc/hosts, и поэтому es:9200 можно использовать в приложении — этот адрес корректно направляет запросы в контейнер ES. Отлично! Давайте теперь запустим Flask-контейнер по-настоящему:

$ docker run -d –net foodtrucks -p 5000:5000 –name foodtrucks-web prakhar1989/foodtrucks-web
2a1b77e066e646686f669bab4759ec1611db359362a031667cacbe45c3ddb413

$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2a1b77e066e6 prakhar1989/foodtrucks-web “python ./app.py” 2 seconds ago Up 1 seconds 0.0.0.0:5000->5000/tcp foodtrucks-web
2c0b96f9b803 elasticsearch “/docker-entrypoint.s” 21 minutes ago Up 21 minutes 0.0.0.0:9200->9200/tcp, 9300/tcp es

$ curl -I 0.0.0.0:5000
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 3697
Server: Werkzeug/0.11.2 Python/2.7.6
Date: Sun, 10 Jan 2016 23:58:53 GMT>

Зайдите на http://0.0.0.0:5000, и увидите приложение в работе. Опять же, может показаться, что было много работы, но на самом деле мы ввели всего 4 команды чтобы с нуля дойти до работающего приложения. Я собрал эти команды в bash-скрипт.

#!/bin/bash
# build the flask container
docker build -t prakhar1989/foodtrucks-web .
# create the network
docker network create foodtrucks
# start the ES container
docker run -d –net foodtrucks -p 9200:9200 -p 9300:9300 –name es elasticsearch
# start the flask app container
docker run -d –net foodtrucks -p 5000:5000 –name foodtrucks-web prakhar1989/foodtrucks-web

Теперь представьте, что хотите поделиться приложением с другом. Или хотите запустить на сервере, где установлен Докер. Можно запустить всю систему с помощью одной команды!

$ git clone https://github.com/prakhar1989/FoodTrucks
$ cd FoodTrucks
$ ./setup-docker.sh

Вот и все! По-моему, это невероятно крутой и мощный способ распространять и запускать приложения!

Docker Links

Перед тем, как завершить этот раздел, стоит отметить, что docker network это относительно новая фича, она входит в релиз Docker 1.9 .

До того, как появился network, ссылки были допустимым способом настройки взаимодействия между контейнерами. В соответствии с официальной документацией, linking вскоре будет переведены в статус deprecated. Если вам попадется туториал или статья, где используется link для соединения контейнеров, то просто не забывайте использовать вместо этого network (на момент публикации перевода links является legacy, — прим. пер.)

Ссылка на источник: https://habrahabr.ru/post/310460/#sfft

Ссылка на четвертую часть: https://www.linuxspace.org/archives/14911