Основи Docker ч.2 | robot_dreams
Для відстеження статусу замовлення - авторизуйтесь
Введіть код, який був надісланий на пошту Введіть код із SMS, який був надісланий на номер
 
Код дійсний протягом 2 хвилин Код з SMS дійсний протягом 2 хвилин
Ви впевнені, що хочете вийти?
Сеанс завершено
На головну
Опановуємо Docker: Як запускати та керувати мультиконтейнерними середовищами

Опановуємо Docker: Як запускати та керувати мультиконтейнерними середовищами

Практичний посібник з прикладами коду (частина друга)

У попередній статті ми побачили, як легко і цікаво запускати застосунки за допомогою Docker. Ми почали з простого статичного сайту, а потім спробували застосунок на Flask. Обидва ці застосунки ми змогли запустити як локально, так і в хмарі всього кількома командами. Одне, що їх об'єднувало, — це те, що вони працювали в одному контейнері.

Ті з вас, хто має досвід запуску сервісів у продакшн-середовищі, знають, що сучасні застосунки зазвичай не такі прості. Майже завжди є база даних (або інший вид постійного зберігання). Системи на кшталт Redis та Memcached стали невіддільною частиною більшості вебархітектур. Отже, в цьому розділі ми витратимо час на вивчення того, як Docker’изувати застосунки, що залежать від різних сервісів для роботи.

Зокрема, ми побачимо, як можна запускати й керувати мультиконтейнерними Docker-середовищами. Чому мультиконтейнерними, ви запитаєте? Ну, одна з ключових ідей Docker — це ізоляція. Ідея об'єднання процесу з його залежностями в контейнерах — це те, що робить Docker таким потужним.

Так само, як це гарна стратегія — розділяти рівні вашого застосунку, доцільно зберігати контейнери для кожного із сервісів окремо. Кожен рівень, імовірно, має різні вимоги до ресурсів, і вони можуть зростати з різною швидкістю. Розділяючи рівні на різні контейнери, ми можемо комбінувати їх, використовуючи найбільш доцільний тип екземпляра, орієнтуючись на різні вимоги до ресурсів. Це також добре вписується в концепцію мікросервісів, що є однією з основних причин, чому Docker (або інші технології контейнеризації) перебувають на передньому плані сучасних мікросервісних архітектур.

SF Food Trucks

Застосунок, який ми будемо Docker’изувати, має назву SF Food Trucks. Метою його створення було розробити щось корисне (що нагадує реальний застосунок), що використовує хоча б одну службу, але водночас не є надто складним для цієї інструкції. Ось що вийшло.

Задня частина застосунку написана на Python (Flask), а для пошуку залучає Elasticsearch. Як і все інше в цій інструкції, весь вихідний код доступний на GitHub. Ми використаємо цей застосунок як приклад, щоб навчитися створювати, запускати й розгортати багатоконтейнерне середовище.

Спочатку клонуйте репозиторій на локальний комп’ютер.

$ git clone https://github.com/prakhar1989/FoodTrucks
$ cd FoodTrucks
$ tree -L 2
.
├── Dockerfile
├── README.md
├── aws-compose.yml
├── docker-compose.yml
├── flask-app
│   ├── app.py
│   ├── package-lock.json
│   ├── package.json
│   ├── requirements.txt
│   ├── static
│   ├── templates
│   └── webpack.config.js
├── setup-aws-ecs.sh
├── setup-docker.sh
├── shot.png
└── utils
    ├── generate_geojson.py
    └── trucks.geojson

Тека flask-app містить Python-застосунок, а тека utils має утиліти для завантаження даних в Elasticsearch. Директорія також охоплює деякі YAML-файли та Dockerfile. Все це ми розглянемо детальніше по ходу цієї інструкції. Якщо вам цікаво, можете переглянути ці файли.

Тепер, коли ви готові (сподіваюся), подумаємо, як ми можемо Docker’изувати цей застосунок. Бачимо, що застосунок складається із сервера Flask та служби Elasticsearch. Природний спосіб розподілення цього застосунку — це мати два контейнери: один для процесу Flask і інший для процесу Elasticsearch (ES). Це дасть змогу, якщо застосунок стане популярним, масштабувати його, додаючи більше контейнерів залежно від того, де виникає вузьке місце.

Отже, нам потрібно два контейнери. Це не має бути важко, правда? Ми вже створили власний контейнер Flask у попередньому розділі. А для Elasticsearch подивимося, чи можемо знайти щось на Docker Hub.

$ 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 локально.

Примітка: Elastic, компанія, що стоїть за Elasticsearch, підтримує власний реєстр для продуктів Elastic. Рекомендовано використовувати образи з цього реєстру, якщо ви плануєте застосовувати Elasticsearch.

Спочатку завантажимо образ:

$ docker pull docker.elastic.co/elasticsearch/elasticsearch:6.3.2

І потім запустимо його в режимі розробки, вказавши порти та встановивши змінну середовища, що конфігурує кластер Elasticsearch для роботи в режимі одноконтейнерного вузла:

$ docker run -d --name es -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:6.3.2
277451c15ec183dd939e80298ea4bcf55050328a39b04124b387d668e3ed3943

Як видно, ми застосовуємо параметр --name es, щоб дати нашому контейнеру ім’я, що полегшує подальше використання в командах. Після запуску контейнера ми можемо переглянути журнали, послуговуючись командою docker container logs з ім’ям контейнера (або ID), щоб перевірити, чи успішно стартував Elasticsearch.

Примітка: Elasticsearch потребує кілька секунд для запуску, тому, можливо, вам доведеться почекати, перш ніж побачите повідомлення про ініціалізацію в логах.

$ docker container ls
CONTAINER ID        IMAGE                                                 COMMAND                  CREATED             STATUS              PORTS                                            NAMES
277451c15ec1        docker.elastic.co/elasticsearch/elasticsearch:6.3.2   "/usr/local/bin/dock..."   2 minutes ago       Up 2 minutes        0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp   es

$ docker container logs es
[2018-07-29T05:49:09,304][INFO ][o.e.n.Node               ] [] initializing ...
[2018-07-29T05:49:09,385][INFO ][o.e.e.NodeEnvironment    ] [L1VMyzt] using [1] data paths, mounts [[/ (overlay)]], net usable_space [54.1gb], net total_space [62.7gb], types [overlay]
[2018-07-29T05:49:09,385][INFO ][o.e.e.NodeEnvironment    ] [L1VMyzt] heap size [990.7mb], compressed ordinary object pointers [true]
[2018-07-29T05:49:11,979][INFO ][o.e.p.PluginsService     ] [L1VMyzt] loaded module [x-pack-security]
[2018-07-29T05:49:11,980][INFO ][o.e.p.PluginsService     ] [L1VMyzt] loaded module [x-pack-sql]
[2018-07-29T05:49:11,980][INFO ][o.e.p.PluginsService     ] [L1VMyzt] loaded module [x-pack-upgrade]
[2018-07-29T05:49:11,980][INFO ][o.e.p.PluginsService     ] [L1VMyzt] loaded module [x-pack-watcher]
[2018-07-29T05:49:11,981][INFO ][o.e.p.PluginsService     ] [L1VMyzt] loaded plugin [ingest-geoip]
[2018-07-29T05:49:11,981][INFO ][o.e.p.PluginsService     ] [L1VMyzt] loaded plugin [ingest-user-agent]
[2018-07-29T05:49:17,659][INFO ][o.e.d.DiscoveryModule    ] [L1VMyzt] using discovery type [single-node]
[2018-07-29T05:49:18,962][INFO ][o.e.n.Node               ] [L1VMyzt] initialized
[2018-07-29T05:49:18,963][INFO ][o.e.n.Node               ] [L1VMyzt] starting ...
[2018-07-29T05:49:19,218][INFO ][o.e.t.TransportService   ] [L1VMyzt] publish_address {172.17.0.2:9300}, bound_addresses {0.0.0.0:9300}
[2018-07-29T05:49:19,302][INFO ][o.e.x.s.t.n.SecurityNetty4HttpServerTransport] [L1VMyzt] publish_address {172.17.0.2:9200}, bound_addresses {0.0.0.0:9200}
[2018-07-29T05:49:19,303][INFO ][o.e.n.Node               ] [L1VMyzt] started
[2018-07-29T05:49:19,439][WARN ][o.e.x.s.a.s.m.NativeRoleMappingStore] [L1VMyzt] Failed to clear cache for realms [[]]
[2018-07-29T05:49:19,542][INFO ][o.e.g.GatewayService     ] [L1VMyzt] recovered [0] indices into cluster_state

Тепер перевіримо, чи можемо ми надіслати запит до контейнера Elasticsearch. Для цього використаємо порт 9200 і надішлемо cURL-запит:

$ curl 0.0.0.0:9200
{
  "name" : "ijJDAOm",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "a_nSV3XmTCqpzYYzb-LhNw",
  "version" : {
    "number" : "6.3.2",
    "build_flavor" : "default",
    "build_type" : "tar",
    "build_hash" : "053779d",
    "build_date" : "2018-07-20T05:20:23.451332Z",
    "build_snapshot" : false,
    "lucene_version" : "7.3.1",
    "minimum_wire_compatibility_version" : "5.6.0",
    "minimum_index_compatibility_version" : "5.0.0"
  },
  "tagline" : "You Know, for Search"
}

Супер! Має гарний вигляд! Поки ми тут, запустимо наш контейнер Flask. Але перш ніж візьмемося до цього, нам потрібен Dockerfile. У попередньому розділі ми використовували образ python:3.8 як базовий. Цього разу, однак, окрім встановлення Python-залежностей через pip, ми хочемо, щоб застосунок також генерував мінімізований JavaScript-файл для продакшн-середовища. Для цього нам потрібен Node.js. Оскільки ми потребуємо власного кроку побудови, ми почнемо з базового образу Ubuntu, щоб створити наш Dockerfile з нуля.

Наш Dockerfile для Flask-застосунку має ось такий вигляд:

# start from base
FROM ubuntu:18.04

MAINTAINER Prakhar Srivastav <prakhar@prakhar.me>

# install system-wide deps for python and node
RUN apt-get -yqq update
RUN apt-get -yqq install python3-pip python3-dev curl gnupg
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash
RUN apt-get install -yq nodejs

# 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 pip3 install -r requirements.txt

# expose port
EXPOSE 5000

# start app
CMD [ "python3", "./app.py" ]

Тут є кілька нових моментів, тож швидко розглянемо цей файл. Ми починаємо з образу Ubuntu LTS і використовуємо менеджер пакетів apt-get, щоб встановити залежності, зокрема Python та Node. Прапорець yqq застосовують для подавлення виводу й автоматичної відповіді "Yes" на всі запити.

Далі ми залучаємо команду ADD, щоб скопіювати застосунок у новий том у контейнері — /opt/flask-app. Тут розташовуватиметься наш код. Також ми встановлюємо цю директорію як робочу, щоб усі наступні команди виконувалися в цьому контексті. Тепер, коли системні залежності встановлено, ми починаємо встановлення залежностей для застосунку. Спочатку займаємося Node.js, встановлюючи пакунки з npm, і виконуємо команду build, як визначено у файлі package.json. Завершуємо файл установкою Python-залежностей, відкриттям порту та визначенням команди CMD для запуску, як ми робили в попередньому розділі.

Нарешті можемо побудувати образ і запустити контейнер (замініть yourusername на ваше ім’я користувача):

$ docker build -t yourusername/foodtrucks-web .

Під час першого запуску це займе деякий час, оскільки Docker клієнт завантажить образ Ubuntu, виконає всі команди й підготує ваш образ. Повторний запуск docker build після будь-яких подальших змін у коді застосунку буде майже миттєвим. Тепер спробуймо запустити наш застосунок.

$ docker run -P --rm yourusername/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...

Ой! Наш Flask-застосунок не зміг запуститися, оскільки не зміг підключитися до Elasticsearch. Як ми можемо повідомити одному контейнеру про інший і змусити їх спілкуватися між собою? Відповідь — у наступному розділі.

Docker Network (Docker Мережі)

Перш ніж ми поговоримо про можливості, які Docker надає для розв’язання таких ситуацій, спробуймо знайти спосіб обійти проблему. Сподіваюся, це дасть вам розуміння конкретної функції, яку ми вивчатимемо.

Отже, запустимо команду docker container ls (це те ж саме, що й docker ps), щоб подивитися, що у нас є.

$ docker container ls
CONTAINER ID        IMAGE                                                 COMMAND                  CREATED             STATUS              PORTS                                            NAMES
277451c15ec1        docker.elastic.co/elasticsearch/elasticsearch:6.3.2   "/usr/local/bin/dock..."   17 minutes ago      Up 17 minutes       0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp   es

Отже, у нас є один контейнер ES, який працює на порту 0.0.0.0:9200, і до нього можна підключитися напряму. Якщо ми зможемо налаштувати наш Flask-застосунок для підключення до цього URL, він повинен зуміти підключитися та взаємодіяти з ES, правда? Загляньмо в наш код на Python і подивимося, як визначені дані для підключення.

es = Elasticsearch(host='es')

Щоб це працювало, нам потрібно вказати контейнеру Flask, що контейнер ES працює на хості 0.0.0.0 (порт за замовчуванням — 9200), і це повинно працювати, правильно? На жаль, це не зовсім так, оскільки IP-адреса 0.0.0.0 є адресою для доступу до контейнера ES з хостової машини. Інший контейнер не зможе достукатися до цього на тій самій IP-адресі. Добре, якщо не цей IP, то яку IP-адреси повинен використовувати контейнер ES? 

Тут ми й підходимо до вивчення мереж Docker. Коли Docker встановлюється, він автоматично створює три мережі.

$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
c2c695315b3a        bridge              bridge              local
a875bec5d6fd        host                host                local
ead0e804a67b        none                null                local

Мережа bridge — це мережа, в якій за замовчуванням працюють контейнери. Це означає, що коли ви запустили контейнер ES, він працював у цій мережі bridge. Щоб перевірити, проаналізуємо мережу.

 $ docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "c2c695315b3aaf8fc30530bb3c6b8f6692cedd5cc7579663f0550dfdd21c9a26",
        "Created": "2018-07-28T20:32:39.405687265Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "277451c15ec183dd939e80298ea4bcf55050328a39b04124b387d668e3ed3943": {
                "Name": "es",
                "EndpointID": "5c417a2fc6b13d8ec97b76bbd54aaf3ee2d48f328c3f7279ee335174fbb4d6bb",
                "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"
        },
        "Labels": {}
    }
]

Ви можете побачити, що наш контейнер 277451c15ec1 зазначений у розділі "Containers" виводу. Що ми також знаємо — це IP-адреса, яку було надано контейнеру, — 172.17.0.2. Чи є це саме та IP-адреса, яку ми шукаємо? Перевіримо, запустивши наш контейнер Flask і спробувавши підключитися до цієї IP-адреси.

$ docker run -it --rm yourusername/foodtrucks-web bash
root@35180ccc206a:/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"
}
root@35180ccc206a:/opt/flask-app# exit

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

Хоча ми знайшли спосіб змусити контейнери взаємодіяти один з одним, все ж таки існує дві проблеми з таким підходом:

1. Як ми можемо сказати контейнеру Flask, що ім’я хоста es означає IP-адресу 172.17.0.2 або іншу IP-адресу, адже вона може змінюватися?

2. Оскільки мережа bridge є спільною для кожного контейнера за замовчуванням, цей метод не є безпечним. Як ми можемо ізолювати нашу мережу?

Добра новина, що Docker має чудове рішення для наших запитань. Він дає змогу створювати власні мережі, ізолюючи їх за допомогою команди docker network.

Спершу створимо власну мережу.

$ docker network create foodtrucks-net
0815b2a3bb7a6608e850d05553cc0bda98187c4528d94621438f31d97a6fea3c

$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
c2c695315b3a        bridge              bridge              local
0815b2a3bb7a        foodtrucks-net      bridge              local
a875bec5d6fd        host                host                local
ead0e804a67b        none                null                local

Команда network create створює нову мережу типу bridge, яка нам зараз потрібна. У термінах Docker мережа типу bridge використовує програмний міст, який дозволяє контейнерам, підключеним до однієї й тієї самої мережі bridge, спілкуватися між собою, водночас ізолюючи їх від контейнерів, які не підключені до цієї мережі. Драйвер bridge Docker автоматично встановлює правила на хостовій машині, щоб контейнери на різних мережах bridge не могли безпосередньо взаємодіяти один з одним. Є й інші типи мереж, які можна створювати, і вам рекомендовано ознайомитися з ними в офіційній документації.

Тепер, коли у нас є мережа, можемо запустити наші контейнери в цій мережі за допомогою прапорця --net. Зробимо це, але спершу, щоб запустити новий контейнер з тим самим ім’ям, ми зупинимо і видалимо наш контейнер ES, який працює в мережі bridge (за замовчуванням).

$ docker container stop es
es

$ docker container rm es
es

$ docker run -d --name es --net foodtrucks-net -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:6.3.2
13d6415f73c8d88bddb1f236f584b63dbaf2c3051f09863a3f1ba219edba3673

$ docker network inspect foodtrucks-net
[
    {
        "Name": "foodtrucks-net",
        "Id": "0815b2a3bb7a6608e850d05553cc0bda98187c4528d94621438f31d97a6fea3c",
        "Created": "2018-07-30T00:01:29.1500984Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "13d6415f73c8d88bddb1f236f584b63dbaf2c3051f09863a3f1ba219edba3673": {
                "Name": "es",
                "EndpointID": "29ba2d33f9713e57eb6b38db41d656e4ee2c53e4a2f7cf636bdca0ec59cd3aa7",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]

Як ви бачите, наш контейнер es тепер працює в мережі bridge foodtrucks-net. Тепер перевіримо, що відбудеться, коли ми запустимо контейнер у нашій мережі foodtrucks-net.

$ docker run -it --rm --net foodtrucks-net yourusername/foodtrucks-web bash
root@9d2722cf282c:/opt/flask-app# curl es:9200
{
  "name" : "wWALl9M",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "BA36XuOiRPaghPNBLBHleQ",
  "version" : {
    "number" : "6.3.2",
    "build_flavor" : "default",
    "build_type" : "tar",
    "build_hash" : "053779d",
    "build_date" : "2018-07-20T05:20:23.451332Z",
    "build_snapshot" : false,
    "lucene_version" : "7.3.1",
    "minimum_wire_compatibility_version" : "5.6.0",
    "minimum_index_compatibility_version" : "5.0.0"
  },
  "tagline" : "You Know, for Search"
}
root@53af252b771a:/opt/flask-app# ls
app.py  node_modules  package.json  requirements.txt  static  templates  webpack.config.js
root@53af252b771a:/opt/flask-app# python3 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)
root@53af252b771a:/opt/flask-app# exit

Це працює! У визначених користувачем мережах, як-от foodtrucks-net, контейнери можуть не тільки спілкуватися через IP-адресу, але також можуть розпізнавати ім'я контейнера як IP-адресу. Цю можливість називають автоматичним виявленням сервісів. Чудово! Тепер запустимо наш контейнер Flask для реальної роботи!

$ docker run -d --net foodtrucks-net -p 5000:5000 --name foodtrucks-web yourusername/foodtrucks-web
852fc74de2954bb72471b858dce64d764181dca0cf7693fed201d76da33df794

$ docker container ls
CONTAINER ID        IMAGE                                                 COMMAND                  CREATED              STATUS              PORTS                                            NAMES
852fc74de295        yourusername/foodtrucks-web                           "python3 ./app.py"       About a minute ago   Up About a minute   0.0.0.0:5000->5000/tcp                           foodtrucks-web
13d6415f73c8        docker.elastic.co/elasticsearch/elasticsearch:6.3.2   "/usr/local/bin/dock..." 17 minutes ago       Up 17 minutes       0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->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 yourusername/foodtrucks-web .

# create the network
docker network create foodtrucks-net

# start the ES container
docker run -d --name es --net foodtrucks-net -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:6.3.2

# start the flask app container
docker run -d --net foodtrucks-net -p 5000:5000 --name foodtrucks-web yourusername/foodtrucks-web

Тепер уявіть, що ви передаєте свою програму другу або запускаєте її на сервері з уже встановленим Docker. Ви можете запустити всю програму лише однією командою!

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

І ось і все! 

Docker Compose

До цього моменту ми вивчали лише клієнт Docker. Однак в екосистемі Docker є багато інших інструментів з відкритим кодом, які чудово інтегруються з Docker. Ось кілька з них:

  • Docker Machine — створення хостів Docker на вашому комп’ютері, в хмарних провайдерах та у вашому власному дата-центрі.
  • Docker Compose — інструмент для визначення і запуску багатоконтейнерних Docker-застосунків.
  • Docker Swarm — нативне рішення для кластеризації Docker.
  • Kubernetes — система з відкритим кодом для автоматизації розгортання, масштабування й управління контейнеризованими застосунками.

У цьому розділі ми розглянемо один з цих інструментів — Docker Compose, і побачимо, як він може полегшити роботу з багатоконтейнерними застосунками.

Отже, для чого використовувати Compose? Compose — це інструмент для визначення і запуску багатоконтейнерних Docker-застосунків у зручний спосіб. Він надає конфігураційний файл під назвою docker-compose.yml, який можна використовувати для підняття застосунку та набору сервісів, від яких цей застосунок залежить, лише за одну команду. Compose працює в усіх середовищах: виробництво, стадійне середовище, розробка, тестування, а також CI робочі процеси, хоча Compose найкраще підходить для середовищ розробки й тестування.

Тепер спробуймо створити файл docker-compose.yml для нашого застосунку SF-Foodtrucks та оцінити, чи відповідає Docker Compose очікуванням.

Однак перший крок — це встановлення Docker Compose. Якщо ви використовуєте Windows або Mac, Docker Compose вже встановлений, оскільки він іде в складі Docker Toolbox. Користувачі Linux можуть легко встановити Docker Compose, слідуючи інструкціям у документації. Оскільки Compose написаний на Python, ви також можете просто виконати команду pip install docker-compose. Перевірте свою установку за допомогою:

$ docker-compose --version
docker-compose version 1.21.2, build a133471

Тепер, коли Docker Compose встановлений, ми можемо перейти до наступного етапу — створення файлу Docker Compose docker-compose.yml. Синтаксис YAML досить простий, а репозиторій вже містить файл docker-compose, який ми використовуватимемо.

version: "3"
services:
  es:
    image: docker.elastic.co/elasticsearch/elasticsearch:6.3.2
    container_name: es
    environment:
      - discovery.type=single-node
    ports:
      - 9200:9200
    volumes:
      - esdata1:/usr/share/elasticsearch/data
  web:
    image: yourusername/foodtrucks-web
    command: python3 app.py
    depends_on:
      - es
    ports:
      - 5000:5000
    volumes:
      - ./flask-app:/opt/flask-app
volumes:
  esdata1:
    driver: local

Дозвольте роз’яснити, що означає цей файл. На батьківському рівні ми визначаємо імена наших сервісів — es і web. Параметр image завжди є обов’язковим, і для кожного сервісу, який ми хочемо запустити за допомогою Docker, можемо додавати інші параметри. Для es ми просто посилаємося на образ elasticsearch, доступний у реєстрі Elastic. Для нашого Flask-застосунку ми посилаємося на образ, який створили на початку цього розділу.

Інші параметри, як-от command і ports, надають більше інформації про контейнер. Параметр volumes визначає точку монтування в контейнері web, де розташовуватиметься код. Це абсолютно необов’язково і корисно, якщо вам потрібен доступ до логів тощо. Згодом ми побачимо, як це може стати в пригоді під час розробки. Ознайомтеся з онлайн-довідкою, щоб дізнатися більше про параметри, які підтримує цей файл. Ми також додаємо volumes для контейнера es, щоб дані, які ми завантажуємо, зберігалися між перезапусками. Також зазначаємо depends_on, що вказує Docker запускати контейнер es перед web. Детальніше можете прочитати в документації Docker Compose.

Примітка: для виконання більшості команд Compose вам потрібно бути всередині директорії з файлом docker-compose.yml.

Тепер файл готовий, подивимося на дію docker-compose. Але перед тим, як почати, нам потрібно переконатися, що порти й імена вільні. Тому якщо у вас запущені контейнери Flask і ES, вимкнемо їх.

$ docker stop es foodtrucks-web
es
foodtrucks-web

$ docker rm es foodtrucks-web
es
foodtrucks-web

Тепер ми можемо запустити docker-compose. Перейдіть до директорії з проєктом і запустіть команду docker-compose up.

$ docker-compose up
Creating network "foodtrucks_default" with the default driver
Creating foodtrucks_es_1
Creating foodtrucks_web_1
Attaching to foodtrucks_es_1, foodtrucks_web_1
es_1  | [2016-01-11 03:43:50,300][INFO ][node                     ] [Comet] version[2.1.1], pid[1], build[40e2c53/2015-12-15T13:05:55Z]
es_1  | [2016-01-11 03:43:50,307][INFO ][node                     ] [Comet] initializing ...
es_1  | [2016-01-11 03:43:50,366][INFO ][plugins                  ] [Comet] loaded [], sites []
es_1  | [2016-01-11 03:43:50,421][INFO ][env                      ] [Comet] using [1] data paths, mounts [[/usr/share/elasticsearch/data (/dev/sda1)]], net usable_space [16gb], net total_space [18.1gb], spins? [possibly], types [ext4]
es_1  | [2016-01-11 03:43:52,626][INFO ][node                     ] [Comet] initialized
es_1  | [2016-01-11 03:43:52,632][INFO ][node                     ] [Comet] starting ...
es_1  | [2016-01-11 03:43:52,703][WARN ][common.network           ] [Comet] publish address: {0.0.0.0} is a wildcard address, falling back to first non-loopback: {172.17.0.2}
es_1  | [2016-01-11 03:43:52,704][INFO ][transport                ] [Comet] publish_address {172.17.0.2:9300}, bound_addresses {[::]:9300}
es_1  | [2016-01-11 03:43:52,721][INFO ][discovery                ] [Comet] elasticsearch/cEk4s7pdQ-evRc9MqS2wqw
es_1  | [2016-01-11 03:43:55,785][INFO ][cluster.service          ] [Comet] new_master {Comet}{cEk4s7pdQ-evRc9MqS2wqw}{172.17.0.2}{172.17.0.2:9300}, reason: zen-disco-join(elected_as_master, [0] joins received)
es_1  | [2016-01-11 03:43:55,818][WARN ][common.network           ] [Comet] publish address: {0.0.0.0} is a wildcard address, falling back to first non-loopback: {172.17.0.2}
es_1  | [2016-01-11 03:43:55,819][INFO ][http                     ] [Comet] publish_address {172.17.0.2:9200}, bound_addresses {[::]:9200}
es_1  | [2016-01-11 03:43:55,819][INFO ][node                     ] [Comet] started
es_1  | [2016-01-11 03:43:55,826][INFO ][gateway                  ] [Comet] recovered [0] indices into cluster_state
es_1  | [2016-01-11 03:44:01,825][INFO ][cluster.metadata         ] [Comet] [sfdata] creating index, cause [auto(index api)], templates [], shards [5]/[1], mappings [truck]
es_1  | [2016-01-11 03:44:02,373][INFO ][cluster.metadata         ] [Comet] [sfdata] update_mapping [truck]
es_1  | [2016-01-11 03:44:02,510][INFO ][cluster.metadata         ] [Comet] [sfdata] update_mapping [truck]
es_1  | [2016-01-11 03:44:02,593][INFO ][cluster.metadata         ] [Comet] [sfdata] update_mapping [truck]
es_1  | [2016-01-11 03:44:02,708][INFO ][cluster.metadata         ] [Comet] [sfdata] update_mapping [truck]
es_1  | [2016-01-11 03:44:03,047][INFO ][cluster.metadata         ] [Comet] [sfdata] update_mapping [truck]
web_1 |  * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

Перейдіть на IP, щоб побачити ваш застосунок у реальному часі. Це було неймовірно, чи не так? Лише кілька рядків конфігурації — і ми успішно запустили два Docker-контейнери, які працюють разом. Тепер зупинимо сервіси й запустимо їх у фоновому режимі.

web_1 |  * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
Killing foodtrucks_web_1 ... done
Killing foodtrucks_es_1 ... done

$ docker-compose up -d
Creating es               ... done
Creating foodtrucks_web_1 ... done

$ docker-compose ps
      Name                    Command               State                Ports
--------------------------------------------------------------------------------------------
es                 /usr/local/bin/docker-entr ...   Up      0.0.0.0:9200->9200/tcp, 9300/tcp
foodtrucks_web_1   python3 app.py                   Up      0.0.0.0:5000->5000/tcp

Не дивно, що ми бачимо обидва контейнери, які успішно працюють. Звідки ж взялися ці імена? Вони були створені автоматично за допомогою Compose. Але чи Compose також автоматично створює мережу? Гарне запитання! Перевірмо.

Передусім зупинимо сервіси. Ми завжди можемо запустити їх знову за допомогою однієї команди. Дані збережуться, тому можна знову запустити кластер з тими самими даними за допомогою команди docker-compose up. Щоб знищити кластер і дані, просто введіть команду docker-compose down -v.

$ docker-compose down -v
Stopping foodtrucks_web_1 ... done
Stopping es               ... done
Removing foodtrucks_web_1 ... done
Removing es               ... done
Removing network foodtrucks_default
Removing volume foodtrucks_esdata1

Поки ми на цьому етапі, видалимо також мережу foodtrucks, яку створили минулого разу.

$ docker network rm foodtrucks-net
$ docker network ls
NETWORK ID          NAME                 DRIVER              SCOPE
c2c695315b3a        bridge               bridge              local
a875bec5d6fd        host                 host                local
ead0e804a67b        none                 null                local

Тепер, коли ми маємо чистий старт, запустимо знову наші сервіси та подивимося, чи зможе Compose виконати свою магію.

$ docker-compose up -d
Recreating foodtrucks_es_1
Recreating foodtrucks_web_1

$ docker container ls
CONTAINER ID        IMAGE                        COMMAND                  CREATED             STATUS              PORTS                    NAMES
f50bb33a3242        yourusername/foodtrucks-web  "python3 app.py"         14 seconds ago      Up 13 seconds       0.0.0.0:5000->5000/tcp   foodtrucks_web_1
e299ceeb4caa        elasticsearch                "/docker-entrypoint.s"   14 seconds ago      Up 14 seconds       9200/tcp, 9300/tcp       foodtrucks_es_1

Поки все йде добре. Час перевірити, чи створено які-небудь мережі.

$ docker network ls
NETWORK ID          NAME                 DRIVER
c2c695315b3a        bridge               bridge              local
f3b80f381ed3        foodtrucks_default   bridge              local
a875bec5d6fd        host                 host                local
ead0e804a67b        none                 null                local

Як бачите, Compose створив нову мережу під назвою foodtrucks_default та підключив обидва нові сервіси до цієї мережі, щоб кожен з них був доступним для іншого. Кожен контейнер для сервісу приєднується до цієї мережі за замовчуванням і є доступним для інших контейнерів цієї мережі за допомогою імені контейнера.

$ docker ps
CONTAINER ID        IMAGE                                                 COMMAND                  CREATED              STATUS              PORTS                              NAMES
8c6bb7e818ec        docker.elastic.co/elasticsearch/elasticsearch:6.3.2   "/usr/local/bin/dock..." About a minute ago   Up About a minute   0.0.0.0:9200->9200/tcp, 9300/tcp   es
7640cec7feb7        yourusername/foodtrucks-web                           "python3 app.py"         About a minute ago   Up About a minute   0.0.0.0:5000->5000/tcp             foodtrucks_web_1

$ docker network inspect foodtrucks_default
[
    {
        "Name": "foodtrucks_default",
        "Id": "f3b80f381ed3e03b3d5e605e42c4a576e32d38ba24399e963d7dad848b3b4fe7",
        "Created": "2018-07-30T03:36:06.0384826Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.19.0.0/16",
                    "Gateway": "172.19.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": true,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "7640cec7feb7f5615eaac376271a93fb8bab2ce54c7257256bf16716e05c65a5": {
                "Name": "foodtrucks_web_1",
                "EndpointID": "b1aa3e735402abafea3edfbba605eb4617f81d94f1b5f8fcc566a874660a0266",
                "MacAddress": "02:42:ac:13:00:02",
                "IPv4Address": "172.19.0.2/16",
                "IPv6Address": ""
            },
            "8c6bb7e818ec1f88c37f375c18f00beb030b31f4b10aee5a0952aad753314b57": {
                "Name": "es",
                "EndpointID": "649b3567d38e5e6f03fa6c004a4302508c14a5f2ac086ee6dcf13ddef936de7b",
                "MacAddress": "02:42:ac:13:00:03",
                "IPv4Address": "172.19.0.3/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {
            "com.docker.compose.network": "default",
            "com.docker.compose.project": "foodtrucks",
            "com.docker.compose.version": "1.21.2"
        }
    }
]

Development Workflow

Перед тим, як перейти до наступного розділу, є ще одна річ, яку варто охопити щодо docker-compose. Як було зазначено раніше, docker-compose справді чудовий для розробки й тестування. Тож подивимося, як можна налаштувати Compose, щоб полегшити наше життя під час розробки.

За цим посібником ми працювали з готовими Docker-образами. Хоч і створювали образи з нуля, але ще не торкалися коду застосунку, здебільшого обмежуючись редагуванням Dockerfile та YAML-конфігурацій. Одна з речей, яку ви, напевно, хочете дізнатися, — це який вигляд має робочий процес під час розробки? Чи потрібно щоразу створювати нові Docker-образи для кожної зміни, потім публікувати їх і запускати, щоб перевірити, чи зміни працюють як очікують? Це навіть звучить нудно. Має бути кращий спосіб. У цьому розділі ми якраз і дослідимо це.

Подивимося, як ми можемо внести зміни в застосунок Foodtrucks, який щойно запустили. Переконайтеся, що застосунок працює:

$ docker container ls
CONTAINER ID        IMAGE                                                 COMMAND                  CREATED             STATUS              PORTS                              NAMES
5450ebedd03c        yourusername/foodtrucks-web                           "python3 app.py"         9 seconds ago       Up 6 seconds        0.0.0.0:5000->5000/tcp             foodtrucks_web_1
05d408b25dfe        docker.elastic.co/elasticsearch/elasticsearch:6.3.2   "/usr/local/bin/dock..."   10 hours ago        Up 10 hours         0.0.0.0:9200->9200/tcp, 9300/tcp   es

Тепер перевіримо, чи можемо змінити цей застосунок, щоб він показував повідомлення "Hello world!" за запиту до маршруту /hello. Наразі застосунок відповідає помилкою 404.

$ curl -I 0.0.0.0:5000/hello
HTTP/1.0 404 NOT FOUND
Content-Type: text/html
Content-Length: 233
Server: Werkzeug/0.11.2 Python/2.7.15rc1
Date: Mon, 30 Jul 2018 15:34:38 GMT

Чому це відбувається? Оскільки у нас Flask-застосунок, ми можемо подивитися на файл app.py (посилання), щоб знайти відповідь. У Flask маршрути визначаються за допомогою @app.route syntax. У файлі ви побачите, що ми визначили лише три маршрути: /, /debug і /search. Маршрут / рендерить головну сторінку застосунку, маршрут /debug використовують для повернення деякої інформації для налагоджування, а маршрут /search задіюється застосунком для запитів до Elasticsearch.

$ curl 0.0.0.0:5000/debug
{
  "msg": "yellow open sfdata Ibkx7WYjSt-g8NZXOEtTMg 5 1 618 0 1.3mb 1.3mb\n",
  "status": "success"
}

З огляду на цей контекст як би ми додали новий маршрут для /hello?  Відкрийте файл flask-app/app.py у вашому улюбленому редакторі та внесіть таку зміну:

@app.route('/')
def index():
  return render_template("index.html")

# add a new hello route
@app.route('/hello')
def hello():
  return "hello world!"

Тепер знову спробуємо зробити запит:

$ curl -I 0.0.0.0:5000/hello
HTTP/1.0 404 NOT FOUND
Content-Type: text/html
Content-Length: 233
Server: Werkzeug/0.11.2 Python/2.7.15rc1
Date: Mon, 30 Jul 2018 15:34:38 GMT

Це не спрацювало, еге ж. Що ми зробили не так? Хоч і внесли зміни до app.py, цей файл перебуває на нашій машині (або на хост-машині), але оскільки Docker запускає наші контейнери на основі образу yourusername/foodtrucks-web, він не знає про ці зміни. Щоб перевірити це, спробуймо таке:

$ docker-compose run web bash
Starting es ... done
root@581e351c82b0:/opt/flask-app# ls
app.py        package-lock.json  requirements.txt  templates
node_modules  package.json       static            webpack.config.js
root@581e351c82b0:/opt/flask-app# grep hello app.py
root@581e351c82b0:/opt/flask-app# exit

Що ми намагаємося зробити тут, то це перевірити, що наших змін немає в app.py, який працює в контейнері. Ми це робимо, запустивши команду docker-compose run, яка схожа на команду docker run, але приймає додаткові аргументи для сервісу (у нашому випадку це web). Щойно запускаємо bash, оболонка відкривається в /opt/flask-app, як вказано в нашому Dockerfile. З команди grep ми бачимо, що наших змін у файлі немає.

Тепер подивимося, як це виправити. Передусім потрібно сказати Docker Compose не використовувати образ, а натомість застосовувати файли локально. Ми також встановимо режим відлагодження в True, щоб Flask знав, що потрібно перезавантажити сервер, коли змінюється app.py. Замінимо частину конфігурації для сервісу web у файлі docker-compose.yml таким чином:

version: "3"
services:
  es:
    image: docker.elastic.co/elasticsearch/elasticsearch:6.3.2
    container_name: es
    environment:
      - discovery.type=single-node
    ports:
      - 9200:9200
    volumes:
      - esdata1:/usr/share/elasticsearch/data
  web:
    build: . # замінили образ на build
    command: python3 app.py
    environment:
      - DEBUG=True # встановлюємо змінну середовища для flask
    depends_on:
      - es
    ports:
      - "5000:5000"
    volumes:
      - ./flask-app:/opt/flask-app
volumes:
  esdata1:
    driver: local

З цією зміною (diff) зупинимо та запустимо контейнери.

$ docker-compose down -v
Stopping foodtrucks_web_1 ... done
Stopping es               ... done
Removing foodtrucks_web_1 ... done
Removing es               ... done
Removing network foodtrucks_default
Removing volume foodtrucks_esdata1

$ docker-compose up -d
Creating network "foodtrucks_default" with the default driver
Creating volume "foodtrucks_esdata1" with local driver
Creating es ... done
Creating foodtrucks_web_1 ... done

Як останній крок внесемо зміни до app.py, додавши новий маршрут. Тепер спробуємо зробити запит через curl:

$ curl 0.0.0.0:5000/hello
hello world

А от цього разу все вдалося! Спробуйте ще змінювати застосунок експериментуючи.

Це завершить наш огляд Docker Compose. За допомогою Docker Compose ви також можете призупинити свої сервіси, виконати одноразову команду в контейнері або навіть масштабувати кількість контейнерів. Також раджу ознайомитися з іншими випадками використання Docker Compose. Сподіваюся, я зміг показати, як легко керувати багатоконтейнерними середовищами за допомогою Compose. У фінальному розділі ми розгорнемо застосунок на AWS!

AWS Elastic Container Service

В останньому розділі ми використовували docker-compose для запуску нашого застосунку локально за допомогою однієї команди: docker-compose up. Тепер, коли у нас є застосунок, що функціонує, хочемо поділитися ним зі світом, тому зосередимося на тому, як розгорнути наші багатоконтейнерні застосунки в хмарі за допомогою AWS.

Якщо ви читаєте це, то, ймовірно, вже переконалися, що Docker — це досить крута технологія. І ви не самотні. Спостерігаючи за швидким зростанням Docker, майже всі постачальники хмарних послуг почали працювати над підтримкою розгортання Docker-застосунків на своїх платформах. На сьогодні ви можете розгорнути контейнери на Google Cloud Platform, AWS, Azure та багатьох інших. Ми вже ознайомилися з розгортанням застосунків з одним контейнером за допомогою Elastic Beanstalk, а в цьому розділі розглянемо Elastic Container Service (або ECS) від AWS.

AWS ECS — це масштабована і дуже гнучка служба керування контейнерами, яка підтримує Docker-контейнери. Вона дозволяє вам керувати Docker кластером на EC2-інстансах через зручний API. Якщо Beanstalk пропонує розумні за замовчуванням налаштування, то ECS дає змогу повністю налаштувати середовище відповідно до ваших потреб. Це робить ECS, на мою думку, досить складним для початку.

На щастя для нас, ECS має зручний CLI-інструмент (інструмент командного рядка), який розуміє файли Docker Compose та автоматично налаштовує кластер на ECS. Оскільки ми вже маємо робочий docker-compose.yml, запустити застосунок на AWS не займе багато часу. Почнімо!

Першим кроком є встановлення CLI. Інструкції щодо встановлення CLI для Mac і Linux дуже чітко описано в офіційній документації. Встановіть CLI, а після завершення перевірте, виконавши:

$ ecs-cli --version
ecs-cli version 1.18.1 (7e9df84)

Наступним кроком налаштуємо CLI, щоб мати змогу взаємодіяти з ECS. Ми йтимемо за кроками, описаними в офіційному посібнику з AWS ECS. Якщо у вас виникнуть труднощі, будь ласка, звертайтеся до цього посібника.

Першим кроком буде створення профілю, який ми використовуватимемо для решти посібника. Для продовження вам знадобляться AWS_ACCESS_KEY_ID та AWS_SECRET_ACCESS_KEY. Щоб отримати ці дані, дотримуйтеся кроків, описаних у розділі "Access Key and Secret Access Key" на цій сторінці.

$ ecs-cli configure profile --profile-name ecs-foodtrucks --access-key $AWS_ACCESS_KEY_ID --secret-key $AWS_SECRET_ACCESS_KEY

Далі потрібно отримати пару ключів, які ми застосовуватимемо для входу в інстанси. Перейдіть до вашої EC2-консолі та створіть нову пару ключів. Завантажте пару ключів і збережіть її в безпечному місці. Ще одне, на що варто звернути увагу перед тим, як покинути цей екран, — це ім’я регіону. У цьому випадку ключ "ecs" і регіон us-east-1. Це буде припущенням для решти цього посібника.

Наступним кроком є налаштування CLI.

$ ecs-cli configure --region us-east-1 --cluster foodtrucks
INFO[0000] Saved ECS CLI configuration for cluster (foodtrucks)

Ми надаємо команді configure ім’я регіону, в якому ми хочемо розмістити наш кластер, та ім’я самого кластера. Переконайтеся, що ви вказали той самий регіон, який ви використовували під час створення пари ключів. Якщо ще не налаштовували AWS CLI на своєму комп’ютері, то можете скористатися офіційним посібником, який докладно пояснює, як налаштувати все для роботи.

Наступний крок дозволяє CLI створити шаблон CloudFormation.

$ ecs-cli up --keypair ecs --capability-iam --size 1 --instance-type t2.medium
INFO[0000] Using recommended Amazon Linux 2 AMI with ECS Agent 1.39.0 and Docker version 18.09.9-ce
INFO[0000] Created cluster                               cluster=foodtrucks
INFO[0001] Waiting for your cluster resources to be created
INFO[0001] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
INFO[0062] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
INFO[0122] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
INFO[0182] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
INFO[0242] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
VPC created: vpc-0bbed8536930053a6
Security Group created: sg-0cf767fb4d01a3f99
Subnet created: subnet-05de1db2cb1a50ab8
Subnet created: subnet-01e1e8bc95d49d0fd
Cluster creation succeeded.

Тут ми надаємо ім’я ключа, який завантажили на початку (у цьому випадку ecs), кількість інстансів, які ми хочемо використовувати (--size), і тип інстансів, на яких мають працювати контейнери. Параметр --capability-iam вказує CLI, що ми підтверджуємо, що ця команда може створювати ресурси IAM.

Останній крок — застосувати наш файл docker-compose.yml. Нам потрібно внести кілька незначних змін, тому замість того, щоб змінювати оригінальний файл, створимо його копію. Зміст цього файлу після змін матиме такий вигляд:

version: '2'
services:
  es:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.6.2
    cpu_shares: 100
    mem_limit: 3621440000
    environment:
      - discovery.type=single-node
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    logging:
      driver: awslogs
      options:
        awslogs-group: foodtrucks
        awslogs-region: us-east-1
        awslogs-stream-prefix: es
  web:
    image: yourusername/foodtrucks-web
    cpu_shares: 100
    mem_limit: 262144000
    ports:
      - "80:5000"
    links:
      - es
    logging:
      driver: awslogs
      options:
        awslogs-group: foodtrucks
        awslogs-region: us-east-1
        awslogs-stream-prefix: web

Єдині зміни, які ми внесли в порівнянні з оригінальним docker-compose.yml, — це надання значень для mem_limit (в байтах) та cpu_shares для кожного контейнера, а також додавання конфігурації логування. Це дає змогу переглядати логи, згенеровані нашими контейнерами, в AWS CloudWatch. Перейдіть до CloudWatch і створіть групу логів з назвою foodtrucks. Зверніть увагу, що, оскільки ElasticSearch зазвичай використовує більше пам’яті, ми надали обмеження на пам’ять близько 3,4 ГБ. Ще одна річ, яку потрібно зробити перед тим, як перейти до наступного кроку, — опублікувати наш образ на Docker Hub.

$ docker push yourusername/foodtrucks-web

Чудово! Тепер виконаймо останню команду, яка розгорне наш застосунок на ECS!

$ cd aws-ecs
$ ecs-cli compose up
INFO[0000] Using ECS task definition                     TaskDefinition=ecscompose-foodtrucks:2
INFO[0000] Starting container...                         container=845e2368-170d-44a7-bf9f-84c7fcd9ae29/es
INFO[0000] Starting container...                         container=845e2368-170d-44a7-bf9f-84c7fcd9ae29/web
INFO[0000] Describe ECS container status                 container=845e2368-170d-44a7-bf9f-84c7fcd9ae29/web desiredStatus=RUNNING lastStatus=PENDING taskDefinition=ecscompose-foodtrucks:2
INFO[0000] Describe ECS container status                 container=845e2368-170d-44a7-bf9f-84c7fcd9ae29/es desiredStatus=RUNNING lastStatus=PENDING taskDefinition=ecscompose-foodtrucks:2
INFO[0036] Describe ECS container status                 container=845e2368-170d-44a7-bf9f-84c7fcd9ae29/es desiredStatus=RUNNING lastStatus=PENDING taskDefinition=ecscompose-foodtrucks:2
INFO[0048] Describe ECS container status                 container=845e2368-170d-44a7-bf9f-84c7fcd9ae29/web desiredStatus=RUNNING lastStatus=PENDING taskDefinition=ecscompose-foodtrucks:2
INFO[0048] Describe ECS container status                 container=845e2368-170d-44a7-bf9f-84c7fcd9ae29/es desiredStatus=RUNNING lastStatus=PENDING taskDefinition=ecscompose-foodtrucks:2
INFO[0060] Started container...                          container=845e2368-170d-44a7-bf9f-84c7fcd9ae29/web desiredStatus=RUNNING lastStatus=RUNNING taskDefinition=ecscompose-foodtrucks:2
INFO[0060] Started container...                          container=845e2368-170d-44a7-bf9f-84c7fcd9ae29/es desiredStatus=RUNNING lastStatus=RUNNING taskDefinition=ecscompose-foodtrucks:2

Не випадково, що виклик вище схожий на той, що ми використовували з Docker Compose. Якщо все минуло добре, ви повинні побачити desiredStatus=RUNNING lastStatus=RUNNING в останньому рядку.

Чудово! Наш застосунок запущено, але як до нього отримати доступ?

$ ecs-cli ps
Name                                      State    Ports                     TaskDefinition
845e2368-170d-44a7-bf9f-84c7fcd9ae29/web  RUNNING  54.86.14.14:80->5000/tcp  ecscompose-foodtrucks:2
845e2368-170d-44a7-bf9f-84c7fcd9ae29/es   RUNNING                            ecscompose-foodtrucks:2

Перейдіть за адресою http://54.86.14.14 у своєму браузері, і ви повинні побачити Food Trucks у всій його чорній та жовтій славі! Оскільки ми говоримо на цю тему, подивимося, який вигляд має наша консоль AWS ECS.

Ми бачимо вище, що наш ECS-кластер під назвою 'foodtrucks' було створено і зараз він виконує 1 задачу з 2 контейнерами. Проведіть деякий час, переглядаючи цю консоль, щоб ознайомитися з усіма доступними варіантами.

Очищення

Після того, як ви пограли з розгорнутим застосунком, не забудьте вимкнути кластер:

$ ecs-cli down --force
INFO[0001] Waiting for your cluster resources to be deleted...
INFO[0001] Cloudformation stack status                   stackStatus=DELETE_IN_PROGRESS
INFO[0062] Cloudformation stack status                   stackStatus=DELETE_IN_PROGRESS
INFO[0124] Cloudformation stack status                   stackStatus=DELETE_IN_PROGRESS
INFO[0155] Deleted cluster                               cluster=foodtrucks

Ось і все. За допомогою кількох команд ми змогли розгорнути наш чудовий застосунок у хмарі AWS!

На завершення

Docker відкриває нову еру в розробці, тестуванні та розгортанні застосунків, пропонуючи простоту, гнучкість і масштабованість. Цей гайд мав на меті не лише ознайомити вас із основами контейнеризації, але й надати практичний досвід, потрібний для впевненого використання Docker у повсякденній роботі.

Опанувавши Docker, ви отримали інструмент, який дає змогу уникнути багатьох проблем із налаштуванням середовищ, спрощує командну роботу та пришвидшує доставку продукту. Завдяки можливостям масштабування та інтеграції з хмарними платформами, як-от AWS, Docker відкриває нові можливості для виведення ваших застосунків у світ.

Контейнеризація — це більше, ніж технологія. Він змінює підхід до розробки та управління застосунками. І хоча опанування Docker може здатися викликом, результати варті того. Тож не зупиняйтеся на цьому. Експериментуйте, створюйте, масштабуйте. Адже тепер у ваших руках є потужний інструмент, здатний змінити спосіб, яким ви будуєте цифрові продукти. 

Ще статті
Порівнюємо швидкість, якість і відповідальність за результат