Свой облачный хостинг за 5 минут. Часть 3: Consul, Registrator, Consul-Template

    Docker friends

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

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

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

    Содержание



    Приступаем


    У вас на клиентской машине должен быть установлен 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
      └ 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. Я ищу разработчиков в компанию, подробности у меня в профиле.
    Метки:
    Поделиться публикацией
    Комментарии 12
    • +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
        Вы правы. Это связано с тем, что у меня сам Docker Swarm Manager запущен в единственном экземпляре. Если выпадет первая нода, тогда и самого Docker Swarm Manager не будет.

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

        Спасибо за комментарий.
        • 0
          В процессе развертывания обнаружилась еще одна проблема. Для хранения метаданных 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.
          • 0
            Может, стоит вынести Consul из докера и ставить его прямо на хост?
            • 0
              В итоге к этому и пришёл. С consul кластером, запущенным не в контейнерах, все прекрасно работает.
              Кстати, на днях смотрел состояние Docker UCP. Там в качестве KV хранилища выступает etcd, развёрнутый в контейнере. И если делать разворачивать все по официальной инструкции все прекрасно работает до тех пор, пока не выполняешь шаг, необходимый для поддержки multi-host networking. Как только docker engine меняет настройки для использования etcd для хранения конфигурации overlay сетей все встаёт колом по той же самой причине, что и у меня с consul, до его запуска вне контейнера. Вот такая вот официальная документация.0
      • +1
        Спасибо. Особенно доставила винда 98. :)
        • +1
          Терпеть не могу винду, но те времена вспоминаю с теплотой.

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

          В те времена только изучал винду и часто удалял какой-нибудь системный файл, так что процедуру надо было повторять раз в 2 недели.
          • +1
            Прошло много лет. Железо стало мощнее на порядки. Но винда всё также тормозит…
        • 0
          Балансировка, получается, делается средствами DNS? Вы написали про consul-template, я думал он будет использоваться для изменения конфигов…
          • 0
            Всё никак статью про deploy руки не дойдут написать и рассказать про это. Он и используется у нас для изменения конфигов, конечно.
            • 0
              Напишите, да.
              Я вот думаю ещё terraform как-то приспособить для управления инфраструктурой. А docker-compose используете, для масштабирования? И как приложение деплоите, собираете из Dockerfile или herokuish приспособили?
              • 0
                В данный момент в production мы даже docker-swarm не используем, у нас мало машин пока. Деплоим с помощью ansible, который автоматом запускается после коммита в git репозиторий. Приложения собираем из Dockerfile, да.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.