Docker friends

Привет Хабр! Я продолжаю цикл статей о том, как построить свой облачный хостинг за 5 минут. В прошлой статье мы рассмотрели инструменты, которые помогут решить нам проблему обнаружения сервисов (Service Discovery). В это части мы приступим к практике, построим облако и посмотрим как эти инструменты ведут себя в реальной жизни.

Как и прежде, всю работу может выполнить обычный программист в течение 5 минут, просто запустив набор сценариев для Ansible, которые я подготовил специально для вас и выложил на GitHub.

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

Содержание


  1. Ansible, Docker, Docker Swarm
  2. Service Discovery
  3. Consul, Registrator, Consul-Template
  4. ...

Приступаем


У вас на клиентской машине должен быть установлен Ansible и Docker. В наличии должно быть 3 сервера с авторизацией по ключу и Debian 8.1 x64 на борту (вы можете использовать любой другой дистрибутив, внеся небольшие изменения в сценарии).

Скачиваем набор сценариев или клонируем репозиторий:

» git clone https://github.com/vkozlovski/ansible-cloud-hosting
» cd ansible-cloud-hosting
» git checkout v2.x

IP адреса


Открываем файл stage и заменяем в нем IP адреса на IP своих серверов:

[dc1-cloud]
192.168.1.1
192.168.1.2
192.168.1.3

Если вы хотите построить облако в нескольких датацентрах, то просто добавьте дополнительные группы с соответствующими IP адресами (по аналогии с тем, как это уже сделано):

Пример
[dc1-cloud]
192.168.1.1
192.168.1.2
192.168.1.3

[dc2-cloud]
192.168.2.1
192.168.2.2
192.168.2.3

#--- in all DC ---#

# cloud in all DC
[cloud:children]
dc1-cloud
dc2-cloud

#--- everything in DC ---#

[dc1:children]
dc1-cloud

[dc2:children]
dc2-cloud


Центр сертификации


Теперь надо сгенерировать ключи для нашего центра сертификации, которыми будут подписаны сертификаты клиентов и серверов Docker'a (более подробно об этом написано в первой статье). Для этого я создал небольшой помощник, поэтому из корневой директории проекта выполняем команду:

» make gen-ca

Пример
Generating RSA private key, 4096 bit long modulus
...++
................++
e is 65537 (0x10001)
Enter pass phrase for certs/ca/ca-key.pem:
Verifying - Enter pass phrase for certs/ca/ca-key.pem:
Enter pass phrase for certs/ca/ca-key.pem:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:California
Locality Name (eg, city) []:Cupertino
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Ansible Cloud Hosting
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:example.com
Email Address []:postmaster@example.com


Отвечаем на вопросы (тут нет никаких конкретный требований, домен можете указать любой) и запоминаем пароль. Пароль необходимо присвоить переменной certs_ca_password в файле group_vars/all.yml.

Результат group_vars/all.yml
---
common_packages:
  - sudo
  - htop
  - mc
  - git
  - apt-transport-https
  - python-setuptools # easy_install (necessary for install python pip)

debian_release: jessie
certs_ca_password: '1234' # ;)


Сертификаты


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

» make gen-consul-certs

Пример
Generating a 2048 bit RSA private key
..........................+++
.................................................+++
writing new private key to 'privkey.pem'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:California
Locality Name (eg, city) []:Cupertino
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Ansible Cloud Hosting
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:example.com
Email Address []:postmaster@example.com
Generating a 1024 bit RSA private key
...........................++++++
..............++++++
writing new private key to 'consul.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:California
Locality Name (eg, city) []:Cupertino
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Ansible Cloud Hosting
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:example.com
Email Address []:postmaster@example.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
Using configuration from myca.conf
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
countryName           :PRINTABLE:'US'
stateOrProvinceName   :PRINTABLE:'California'
localityName          :PRINTABLE:'Cupertino'
organizationName      :PRINTABLE:'Ansible Cloud Hosting'
commonName            :PRINTABLE:'example.com'
emailAddress          :IA5STRING:'postmaster@example.com'
Certificate is to be certified until Nov 22 16:25:08 2025 GMT (3650 days)

Write out database with 1 new entries
Data Base Updated
------------------------------------------------------------


Если вы хотите подробнее разобраться в том, что именно происходит на этом шаге, то можете ознакомиться с отличной статьёй на DigitalOcean.

Секретный ключ


image
Теперь нам надо сгенерировать секретный ключ, который Consul будет использовать для шифрования своего сетевого трафика. Для этого выполняем команду:

» docker run --rm --entrypoint "/bin/consul" progrium/consul:latest keygen
L+3UkrkFeXHQBT97nTZI/g==

Ключ необходимо присвоить переменной docker_consul_encrypt в файле group_vars/cloud.yml.

Результат group_vars/cloud.yml
---
# docker
docker_api_version: 1.18
docker_key_server: "hkp://pgp.mit.edu:80"
docker_key_id: "58118E89F3A912897C070ADBF76221572C52609D"

# docker-consul
docker_consul_encrypt: 'L+3UkrkFeXHQBT97nTZI/g=='
docker_consul_start_join_wan:
  - "{{ hostvars[groups['dc1'][0]]['ansible_eth0']['ipv4']['address'] }}"  # first host in DC1


Настройки для датацентра


Файл dc1.yml в директории group_vars содержит конфигурацию, специфичную для конкретного датацентра. Если у вас их больше одного, то можете создать dc2.yml, dc3.yml, ... и заполнить их по аналогии.

---
# docker-consul
# first host in "my_name_dc" DC
docker_consul_join: '{{ hostvars[groups["my_name_dc"][0]]["ansible_eth0"]["ipv4"]["address"] }}'
docker_consul_dc: 'dc1'

# docker-swarm-manager
# first host in "my_name_dc" DC
docker_swarm_manager_ip: '{{ hostvars[groups["my_name_dc"][0]]["ansible_eth0"]["ipv4"]["address"] }}'

Consul

Если вы строите облако в нескольких центрах обработки данных, то у меня для вас хорошие новости – Consul поддерживает это «из коробки». Единственное, что вам необходимо сделать, это добавить под одному IP из каждого ЦОДа в переменную docker_consul_start_join_wan:

Пример group_vars/cloud.yml
---
# docker
docker_api_version: 1.18
docker_key_server: "hkp://pgp.mit.edu:80"
docker_key_id: "58118E89F3A912897C070ADBF76221572C52609D"

# docker-consul
docker_consul_encrypt: 'L+3UkrkFeXHQBT97nTZI/g=='
docker_consul_start_join_wan:
  - "{{ hostvars[groups['dc1'][0]]['ansible_eth0']['ipv4']['address'] }}"  # first host in DC1
  - "{{ hostvars[groups['dc2'][0]]['ansible_eth0']['ipv4']['address'] }}"  # first host in DC2
  ...


Запускаем


Если вы дошли до этого шага – вас ждёт вознаграждение. Запускаем помощник:

» make run

Теперь вы можете «откинуться на спинку кресла и отдохнуть».



Владельцы табуреток — берегите себя.

После того, как магия закончится, я рекомендую перезагрузить все машины.

Готово!

Consul UI


Открываем браузер и переходим по любому из IP адресов наших машин (http://192.168.1.1:8500/). Если вы настраивали несколько датацентров, то должны увидеть похожую картину:

Если центр обработки данных у вас один или вы выбрали его из списка выше:

Consul отображает список сервисов из которых состоит наше облако. Зелёным цветом отображаются «здоровые» сервисы, жёлтым цветом – проблемные (в прошлой статье я упоминал, что Consul умеет проверять здоровье сервисов).

Docker Swarm


Давайте проверим Docker Swarm (более подробно можете почитать о нём в первой статье). Docker Swarm Manager устанавливается на первый IP адрес каждого датацентра из списка в файле stage. Например из списка:

[dc1-cloud]
192.168.1.1
192.168.1.2
192.168.1.3

[dc2-cloud]
192.168.2.1
192.168.2.2
192.168.2.3

#--- in all DC ---#

# cloud in all DC
[cloud:children]
dc1-cloud
dc2-cloud

#--- everything in DC ---#

[dc1:children]
dc1-cloud

[dc2:children]
dc2-cloud

это будут 192.168.1.1 и 192.168.2.1.

Для того, что бы подключиться к Docker Swarm Manager необходимо выполнить:

» docker -H tcp://192.168.1.1:8000 --tlsverify=true --tlscacert=certs/ca/ca.pem --tlscert=certs/docker/cert.pem --tlskey=certs/docker/key.pem info

Вы должны лицезреть что-то похожее:

Containers: 13
Images: 12
Role: primary
Strategy: spread
Filters: health, port, dependency, affinity, constraint
Nodes: 3
 debian1: 192.168.1.1:2376
  L Containers: 5
  L Reserved CPUs: 0 / 1
  L Reserved Memory: 0 B / 519.2 MiB
  L Labels: executiondriver=native-0.2, kernelversion=3.16.0-4-amd64, operatingsystem=Debian GNU/Linux 8 (jessie), storagedriver=aufs
 debian2: 192.168.1.2:2376
  L Containers: 4
  L Reserved CPUs: 0 / 1
  L Reserved Memory: 0 B / 519.2 MiB
  L Labels: executiondriver=native-0.2, kernelversion=3.16.0-4-amd64, operatingsystem=Debian GNU/Linux 8 (jessie), storagedriver=aufs
 debian3: 192.168.1.3:2376
  L Containers: 4
  L Reserved CPUs: 0 / 1
  L Reserved Memory: 0 B / 519.2 MiB
  L Labels: executiondriver=native-0.2, kernelversion=3.16.0-4-amd64, operatingsystem=Debian GNU/Linux 8 (jessie), storagedriver=aufs
CPUs: 3
Total Memory: 1.521 GiB
Name: debian1

Если это так, а должно быть именно так, тогда остаётся только поздравить вас. Если возникли какие-то трудности – добро пожаловать в комментарии.

Тестируем


Пришло время что-нибудь запустить в нашем крутом облаке. И что же это может быть, если не Nginx? Вот именно!

Запускаем:

» docker -H tcp://178.62.232.38:8000 --tlsverify=true --tlscacert=certs/ca/ca.pem --tlscert=certs/docker/cert.pem --tlskey=certs/docker/key.pem run -d -p 80:80 -p 443:443 -e "SERVICE_80_NAME=http" -e "SERVICE_443_NAME=https" nginx

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

Смотрим на какой машине был запущен Nginx:

» docker -H tcp://192.168.1.1:8000 --tlsverify=true --tlscacert=certs/ca/ca.pem --tlscert=certs/docker/cert.pem --tlskey=certs/docker/key.pem ps
CONTAINER ID        IMAGE                               COMMAND                  CREATED             STATUS              PORTS                                                                                                                                                            NAMES
e96b351a857e        nginx                               "nginx -g 'daemon off"   3 minutes ago       Up 3 minutes        192.168.1.2:80->80/tcp, 192.168.1.2:443->443/tcp                                                                                                           debian2/fervent_dubinsky

...

Открываем в браузере http://192.168.1.2:80/:


Есть контакт. Теперь глянем появился ли наш сервис в панели Consul'а:

Появились 2 сервиса (по количеству портов): http и https (их названия мы передали в переменных SERVICE_80_NAME и SERVICE_443_NAME).

DNS


Давайте теперь проверим работу службы DNS, которую нам любезно предоставил Consul. Для этого запустим на какой-нибудь машине контейнер с Debian:

» docker -H tcp://192.168.1.1:8000 --tlsverify=true --tlscacert=certs/ca/ca.pem --tlscert=certs/docker/cert.pem --tlskey=certs/docker/key.pem run -ti debian:testing /bin/bash
root@2e68749354b2:/#

Смотрим есть ли наша служба http:

root@2e68749354b2:/# ping http
PING http.service.consul (172.17.0.6): 56 data bytes
64 bytes from 172.17.0.6: icmp_seq=0 ttl=64 time=0.076 ms
64 bytes from 172.17.0.6: icmp_seq=1 ttl=64 time=0.118 ms
64 bytes from 172.17.0.6: icmp_seq=2 ttl=64 time=0.075 ms
^C--- http.service.consul ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.075/0.090/0.118/0.000 ms

Полный адрес нашего сервиса http.service.consul, но мы можем обращаться и по короткому http (потому что Docker мы запустили с параметром --dns-search service.consul). Также мы можем использовать и более длинный вариант http.service.dc1.consul с указанием датацентра (если вы хотите достучаться до сервиса из другого ЦОДа, например). Более подробно об этом вы можете почитать в официальной документации.

Давайте запустим еще несколько копий Nginx. Откройте другую вкладку в консоли (контейнер с Debian нам понадобится) и выполните 2 раза команду:

» docker -H tcp://178.62.232.38:8000 --tlsverify=true --tlscacert=certs/ca/ca.pem --tlscert=certs/docker/cert.pem --tlskey=certs/docker/key.pem run -d -p 80:80 -p 443:443 -e "SERVICE_80_NAME=http" -e "SERVICE_443_NAME=https" nginx

Docker Swarm достаточно умён, что бы запустить все 3 сервиса на разных машинах (смотрит где есть свободные 80 и 443 порты). И если вы попробуете запустить больше копий Nginx, чем у вас машин, то он сообщит об этом:

Error response from daemon: unable to find a node with port 443 available

Теперь вернёмся в контейнер с Debian и поставим пакет:

root@2e68749354b2:/# apt-get update && apt-get install dnsutils --no-install-recommends

Посмотрим появились ли новые http сервисы:

root@866f410a5f18:/# dig http.service.dc1.consul. ANY

; <<>> DiG 9.9.5-12+b1-Debian <<>> http.service.dc1.consul. ANY
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 17731
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;http.service.dc1.consul.	IN	ANY

;; ANSWER SECTION:
http.service.dc1.consul. 0	IN	A	192.168.1.1
http.service.dc1.consul. 0	IN	A	192.168.1.2
http.service.dc1.consul. 0	IN	A	192.168.1.3

;; Query time: 4 msec
;; SERVER: 172.17.0.1#53(172.17.0.1)
;; WHEN: Thu Nov 26 10:22:41 UTC 2015
;; MSG SIZE  rcvd: 158

Всё работает.

Если вы будете обращаться к вашему сервису по имени http, то нагрузка будет распределяться по алгоритму Round-robin. Если вы сейчас остановите один из контейнеров с Nginx и повторно выполните вышеуказанную команду, то заметите, что его уже нет в списке.

Таким образом нагрузка распределяется только между «живыми» сервисами. Также у вас есть возможность воспользоваться мониторингом здоровья, который предоставляет Consul, в таком случае вы можете распределить нагрузку только между «здоровыми» сервисами (не путайте с просто «живыми»).

Вы можете добавлять и удалять сервисы динамически и всё будет продолжать работать без вашего вмешательства.

Заключение


В этой статье я хотел рассказать вам, как поднять своё персональное облако.

Если вы были внимательны, а я уверен, что это так, то заметили, что мы не воспользовались Consul-Template. Я решил открыть для вас ещё одну часть своих наработок и описать процесс автоматического развёртывания проектов в наше облако в следующей статье. Понадобилось какое-то время, что бы найти подходящий вариант для этих целей и теперь это экономит нам массу времени.

Какими сервисами «наполнить» ваше облако – решать вам. Я поработал на этой конфигурации достаточно долго и не встретил каких-либо проблем.

На этом всё. Всем спасибо за внимание. Стабильных вам облаков и удачи!

P.S. Я ищу разработчиков в стартап, подробности у меня в профиле.

Также вы можете подписываться на меня в социальных сетях (указаны в профиле) и задавать там свои вопросы. Работы у меня всегда много, но в свободное время я стараюсь всем отвечать.

Комментарии (6)


  1. Verdel
    26.11.2015 21:33
    +2

    Так совпало, что я сейчас изучаю эту же тему со связкой Ansible+Consul+Docker Swarm. Вы используете для service discovery при построении кластера Docker Swarm подключение к Consul первый IP адрес в датацентре. В таком случае, при сбое этой ноды кластера Consul, у вас развалится кластер Docker Swarm. Это проверено на личном опыте. Правда я кластеризую еще и роль Docker Swarm Manager.

    В issue репозитария Swarm на гитхабе разработчиков просили дать возможность указывать несколько адресов нод Consul, но это невозможно из-за ограничение go библиотеки клиента Consul, которая используется в коде Swarm. В итоге обсуждений пришли к решению использовать в качестве адреса подключение consul.service.consul. Если указать в качестве дополнительных DNS адресов на холстах в датацентре адреса нод кластера Consul, то в вашем случае consul.service.dc1.consul вернёт вам как раз «здоровые» ноды кластера Consul.

    К сожалению я не успел проверить отказоустойчивость этого решения, так как в данный момент борюсь со связкой Ansible+VMware vSphere.


    1. vladkozlovski
      26.11.2015 23:01
      +1

      Вы правы. Это связано с тем, что у меня сам Docker Swarm Manager запущен в единственном экземпляре. Если выпадет первая нода, тогда и самого Docker Swarm Manager не будет.

      Если запустить Docker Swarm Manager в нескольких экземплярах, то ваше решение с подключением к consul.service.consul вполне может иметь право на жизнь, я думаю. Я прямо сейчас проверю это и если всё будет ок, то залью новую версию.

      Спасибо за комментарий.


      1. Verdel
        02.12.2015 10:02

        В процессе развертывания обнаружилась еще одна проблема. Для хранения метаданных multi-host network в Docker тоже используется Consul. В таком случае на тех хостах, где запускаются контейнеры c Consul Docker придется запускать без использования multi-host network. Так как до старта контейнеров с Consul Docker пытается обратиться к key-value store Consul и становится практически недоступным для управления. После старта контейнера все нормализуется.
        Это накладывает ограничение на способы запуска контейнеров с Consul. Их придется запускать политикой restart=always. В случае использования systemd для старта контейнеров через docker run или любой другой init системы вы просто замучаетесь ждать, когда демон Docker среагирует на ваш запрос, так как в это время он будет постоянно пытаться подключится к кластеру Consul.


  1. icoz
    28.11.2015 11:10
    +1

    Спасибо. Особенно доставила винда 98. :)


    1. vladkozlovski
      28.11.2015 11:19
      +1

      Терпеть не могу винду, но те времена вспоминаю с теплотой.

      У меня был 486DX, 4Mb RAM, 70Mb HDD. Сосед приносил своё ОЗУ, что бы я мог установить 98 винду (она не хотела устанавливаться на 8Mb). Я устанавливал её всю ночь, потом сосед забирал память и всё начинало безбожно тормозить. Но я был счастлив.

      В те времена только изучал винду и часто удалял какой-нибудь системный файл, так что процедуру надо было повторять раз в 2 недели.


      1. icoz
        28.11.2015 11:25
        +1

        Прошло много лет. Железо стало мощнее на порядки. Но винда всё также тормозит…