Привет Хабр! Я продолжаю цикл статей о том, как построить свой облачный хостинг за 5 минут. В прошлой статье мы рассмотрели инструменты, которые помогут решить нам проблему обнаружения сервисов (Service Discovery). В это части мы приступим к практике, построим облако и посмотрим как эти инструменты ведут себя в реальной жизни.
Как и прежде, всю работу может выполнить обычный программист в течение 5 минут, просто запустив набор сценариев для Ansible, которые я подготовил специально для вас и выложил на GitHub.
Несмотря на то, что наше облако стало сложнее и теперь в нём используется бо́льшее число инструментов, построить его стало проще. Я полностью переписал набор сценариев из прошлых статей, удалил всё лишнее, остальное упростил настолько, насколько это вообще возможно.
Содержание
- Часть 0: Виртуализация
- Часть 1: Ansible, Docker, Docker Swarm
- Часть 2: Service Discovery
- Часть 3: Consul, Registrator, Consul-Template
- ...
Приступаем
У вас на клиентской машине должен быть установлен 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.
Секретный ключ
Теперь нам надо сгенерировать секретный ключ, который 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
└ Containers: 5
└ Reserved CPUs: 0 / 1
└ Reserved Memory: 0 B / 519.2 MiB
└ 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
└ Containers: 4
└ Reserved CPUs: 0 / 1
└ Reserved Memory: 0 B / 519.2 MiB
└ 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
└ Containers: 4
└ Reserved CPUs: 0 / 1
└ Reserved Memory: 0 B / 519.2 MiB
└ 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. Я решил открыть для вас ещё одну часть своих наработок и описать процесс автоматического развёртывания проектов в наше облако в следующей статье. Понадобилось какое-то время, что бы найти подходящий вариант для этих целей и теперь это экономит нам массу времени.
Какими сервисами «наполнить» ваше облако – решать вам. Я поработал на этой конфигурации достаточно долго и не встретил каких-либо проблем.
На этом всё. Всем спасибо за внимание. Стабильных вам облаков и удачи!
Подписывайтесь на меня в Twitter, я рассказываю о работе в стартапе, своих ошибках и правильных решениях, о python и всём, что касается веб-разработки.
P.S. Я ищу разработчиков в компанию, подробности у меня в профиле.