Микрооблако. Давайте знакомить машины друг с другом

В своей предыдущей статье я уже упоминал, что создаю своё микрооблако. Точнее, ну как своё, это будет облако как продукт, а не как услуга. Поэтому оно такое же моё, как и ваше. Как и всего мира. То есть каждый сможет его развернуть на своё железо, будь то датацентр или пара-тройка виртуальных машин и пользоваться им в своё удовольствие, запуская на нем свои потрясающие проекты. Можно даже найти какой-то готовый проект, который лежит где-нибудь на гитхабе и давно просится на старт, только автору исходного кода запускать его было негде, потому что хранение исходников у нас бесплатное, а вот поддержка работы своего сервиса к дешевому сегменту экономики на мой взгляд не относится. А я такой запустил облако, вкинул туда чужой проект и помог миру увидеть то, что так и могло остаться в глубинах гитхаба. Но это фантазии, до реализации которых еще далеко, но путешествие в тысячу миль начинается с первого шага, главное не прекращать перебирать ногами. Поэтому мы продолжаем…

Так вот в прошлом посте в рамках проекта kubos мы остановились на том, что объединили все машинки нашего будущего облака в общую виртуальную сеть. Теперь пора сделать так, чтобы машинки видели друг друга не только по IP-адресам, но и по именам. Для этого нужно запустить и настроить DNS сервер, чем мы сейчас и займемся. Для этого я отвел в гитлабе отдельную ветку, где и будет приведен полный код ansible и не только скриптов, о которых пойдет речь ниже.

Собираем инфу о хостах

Для начала нужно с каждой машины в нашей сети получить информацию о том, какой ip-адрес ей был выдан при подключении в виртуальную сеть. Вероятно, я сделал это несколько костыльно, но не нашел ничего лучше, как на каждой машине создавать json-файл с описанием того, как ее зовут и какой ip-адрес ей выдан. Код ниже был добавлен в ансибл скрипты создания openvpn сети тут и тут

# подготовка реестра какому хосту какой IP был выдан при подключении к сети OpenVPN - name: Create {{inventory_hostname}}.json file   shell: |     VPN_IP=$(ip route | grep tun0 | grep -v via | sed 's/.* src \([0-9.]*\).*/\1/');     REVERSE_VPN_IP=$(echo $VPN_IP | awk -F . '{print $4"."$3"."$2"."$1}');     echo "{ \"vpn_hosts\": [ { \"name\": \"{{ inventory_hostname }}\", \"ip\": \"$VPN_IP\", \"reverse_ip\": \"$REVERSE_VPN_IP\" } ] }" > {{inventory_hostname}}.json;                args:     chdir: $HOME     creates: "{{inventory_hostname}}.json"  - name: Fetch {{inventory_hostname}}.json   fetch:     src: "$HOME/{{inventory_hostname}}.json"     dest: "{{ playbook_dir }}/inventory/"     flat: yes

Такой же код запускается и в скрипте запуска ансибл скриптов, то есть в файле entrypoint.sh. Напоминаю, что скрипт запуска ансибл скриптов мы запускаем из докер контейнера и этот контейнер становится частью виртуальной сети

# подготовка реестра какому хосту какой IP был выдан при подключении к сети OpenVPN VPN_IP=$(ip route | grep tun0 | grep -v via | sed 's/.* src \([0-9.]*\).*/\1/'); REVERSE_VPN_IP=$(echo $VPN_IP | awk -F . '{print $4"."$3"."$2"."$1}'); echo "{ \"vpn_hosts\": [ { \"name\": \"docker\", \"ip\": \"$VPN_IP\", \"reverse_ip\": \"$REVERSE_VPN_IP\" } ] }" > openvpn/inventory/docker.json;     

Ниже приведен пример такого json-файла, который будет создан на каждой виртуальной машине и в докер-контейнере

{   "vpn_hosts": [     {        "name": "worker1",        "ip": "10.10.0.6",        "reverse_ip": "6.0.10.10"     }    ] }

В качестве имени хоста используется имя хоста с точки зрения ансибла, ip — это адрес выданный в openvpn сети (он получен скриптом выше из виртуального устройства tun0). Также в этот же файл добавляется реверсный ip-адрес, т.к. он понадобится в таком виде при настройке реверсной DNS-зоны (так называется зона DNS, которая нужна для поиска имени хоста по его ip-адресу). Реверсную зону настраивать необязательно, если это и правда не нужно. Но я все же решил сделать полноценную настройку DNS, чтоб уж до конца во всем разобраться. Также обращаю внимание, что я специально в json создаю массив из одного объекта, чтобы дальше, получив все такие файлы со всем машин, их проще было смержить в один общий массив при помощи замечательной консольной утилиты jq.

Давайте как раз перейдем к рассмотрению этого мержинга в скрипте запуска всех плейбуков entrypoint.sh

# https://e.printstacktrace.blog/merging-json-files-recursively-in-the-command-line/ jq -s '       def deepmerge(a;b):           reduce b[] as $item (a;               reduce ($item | keys_unsorted[]) as $key (.;                   $item[$key] as $val | ($val | type) as $type | .[$key] = if ($type == "object") then                             deepmerge({}; [if .[$key] == null then {} else .[$key] end, $val])                   elif ($type == "array") then                       (.[$key] + $val)                   else                       $val                   end               )           );       deepmerge({}; .)' openvpn/inventory/* > inventories.json;

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

По итогу отработки jq будет создан inventories.json файл вида

{   "vpn_hosts": [     {        "name": "master",        "ip": "10.10.0.1",        "reverse_ip": "1.0.10.10"     },     {        "name": "worker1",        "ip": "10.10.0.6",        "reverse_ip": "6.0.10.10"     },     {        "name": "worker2",        "ip": "10.10.0.8",        "reverse_ip": "8.0.10.10"     }    ] }

Пора уже запустить этот паспортный стол

Для разворачивания DNS-сервера BIND создаем отдельный ансибл playbook

--- - hosts: master   become: true   become_user: root   become_method: sudo   roles:     - dns-server  - hosts: all,!master   become: true   become_user: root   become_method: sudo   roles:     - dns-client

В данном плейбуке 2 роли: днс-сервер и днс-клиент. Этот плейбук как и ранее запускается из докера в скрипте entrypoint.sh при помощи следующего ряда команд

# подготовка файла инвентаризации хостов для настройки DNS {   echo "[servers]";   echo "master ansible_host=host.docker.internal ansible_port=${MASTER_PORT} ansible_user=${USER_NAME} ansible_password=${USER_PASSWORD} ansible_sudo_pass=${USER_PASSWORD}";   echo "";   echo "[clients]";   echo "worker1 ansible_host=host.docker.internal ansible_port=${WORKER1_PORT} ansible_user=${USER_NAME} ansible_password=${USER_PASSWORD} ansible_sudo_pass=${USER_PASSWORD}";   echo "worker2 ansible_host=host.docker.internal ansible_port=${WORKER2_PORT} ansible_user=${USER_NAME} ansible_password=${USER_PASSWORD} ansible_sudo_pass=${USER_PASSWORD}";   echo "worker3 ansible_host=host.docker.internal ansible_port=${WORKER3_PORT} ansible_user=${USER_NAME} ansible_password=${USER_PASSWORD} ansible_sudo_pass=${USER_PASSWORD}";   echo "worker4 ansible_host=host.docker.internal ansible_port=${WORKER4_PORT} ansible_user=${USER_NAME} ansible_password=${USER_PASSWORD} ansible_sudo_pass=${USER_PASSWORD}";       } > dns/hosts;  # установка DNS ansible-playbook -i dns/hosts dns/playbook.yml \   --extra-vars "dns_ip=$VIRTUAL_NETWORK_GATEWAY" \   --extra-vars "@inventories.json";

Сначала как обычно создается файл инвентаризации хостов для ансибла, а далее происходит самое главное: передача переменных в плейбук. Обратите внимание, что переменные передаются путем скармливания inventories.json файла, созданного выше после мержа.

Когда работает плейбук, то на днс-сервере сначала устанавливается сам bind, а потом происходит настройка его конфигов путем заполнения jinja2 шаблонов, которые так любят ансибл девопсы :). Сначала приведу эту часть скрипта ансибла, а потом разберем сами шаблоны

- name: Install bind9   apt:     update_cache: yes     name: [ 'bind9', 'bind9utils', 'bind9-doc' ]     state: present   register: bind9_installed  - name: Replace named.conf.options   template:     src: named.conf.options.j2     dest: /etc/bind/named.conf.options   when: bind9_installed.changed  - name: Replace named.conf.local   template:     src: named.conf.local.j2     dest: /etc/bind/named.conf.local   when: bind9_installed.changed  - name: Ensure /etc/bind/zones directory exists   file:     path: /etc/bind/zones     state: directory  - name: Create db.host.name files   template:     src: db.host.name.j2     dest: "/etc/bind/zones/db.{{ item.name }}"   with_items: "{{ vpn_hosts }}"   when: bind9_installed.changed  - name: Create db.host.ip files   template:     src: db.host.ip.j2     dest: "/etc/bind/zones/db.{{ item.ip }}"   with_items: "{{ vpn_hosts }}"   when: bind9_installed.changed

Тут всего 4 конфига, хотя не такое уж и «всего» :). Сначала создаются опции named.conf.options

acl trusted_clients { {% for vpn_host in vpn_hosts %}     {{ vpn_host.ip }}; # {{ vpn_host.name }} {% endfor %} };  options { directory "/var/cache/bind";  allow-query { trusted_clients; };  forwarders { 8.8.8.8; 8.8.4.4; };  //======================================================================== // If BIND logs error messages about the root key being expired, // you will need to update your keys.  See https://www.isc.org/bind-keys //======================================================================== dnssec-validation auto;  listen-on-v6 { any; }; };

Тут из важного, это настройка ip адресов тех машин, которым разрешено пользоваться DNS-сервером (trusted_clients). В блоке acl данного шаблона идет перечисление всех ip-адресов всех машин в виртуальной сети. В блоке options данный acl подключается, как разрешенный, при помощи директивы allow-query. Также в блоке options важно задать раздел forwarders, чтобы для имен, которые не относятся к нашей виртуальной сети, поиск ip-адресов выполнялся на DNS-серверах google. Все остальное оставлено без изменений в том виде, в котором данный файл поставляется с bind9.

Далее рассмотрим базовый конфиг named.conf.local

{% for vpn_host in vpn_hosts %} // For {{ vpn_host.name }}  zone "{{ vpn_host.name }}" {     type master;     file "/etc/bind/zones/db.{{ vpn_host.name }}"; # zone file path };  zone "{{ vpn_host.reverse_ip }}.in-addr.arpa" {     type master;     file "/etc/bind/zones/db.{{ vpn_host.ip }}";  # reverse zone file path for {{ vpn_host.name }}       };  {% endfor %}

Для упрощения автоматизации заполнения данного шаблона будем создавать по одной зоне и реверсивной зоне для каждого хоста в виртуальной сети. Идеологически это не совсем корректно, потому что нужно всего 2 зоны: зона виртуальной сети и реверсивная зона виртуальной сети. Однако из-за сложности конфигов bind, особенно реверсивных конфигов, где нужно использовать реверсивные адреса, мы упростим себе жизнь, создав по одному файлу на каждое имя хоста и на каждый ip-адрес в виртуальной сети.

Теперь рассмотрим конфиг зоны db.host.name

; ; BIND data file ; $TTL604800 @INSOAlocalhost. root.localhost. (       3; Serial  604800; Refresh   86400; Retry 2419200; Expire  604800 ); Negative Cache TTL ; ; name servers - NS records                         IN      NS      localhost.  ; name servers - A records localhost.              IN      A       {{ dns_ip }}  ; {{ item.name }} - A records {{ item.name }}.        IN      A       {{ item.ip }}

где важно в Serial поставить 3 вместо дефолтной 2-ки, чтобы bind при перезапуске увидел, что в файл внесены изменения. Также нужно заполнить 3 раздела:

  • NS-запись с локалхостом

  • A-запись, чтобы задать свой собственный ip, как ip DNS-сервера

  • A-запись, чтобы задать связь между именем и ip адресом конкретного хоста виртуальной сети

Такой файл будет создаваться для каждого виртуального хоста в сети.

И осталось рассмотреть конфиг реверсной зоны db.host.ip

; ; BIND reverse data file ; $TTL604800 @INSOAlocalhost. root.localhost. (       3; Serial  604800; Refresh   86400; Retry 2419200; Expire  604800 ); Negative Cache TTL ; ; name servers     IN  NS  localhost.  ; PTR Records     IN  PTR {{ item.name }}.  ; {{ item.ip }}

Тут с полем Serial и NS-записью все аналогично конфигу выше для не реверсивной зоны. Единственная разница, что вместо A-записи заполняется одна PTR-запись с именем привязанным к ip-адресу, для которого создается этот файл конфига.

Вернемся к ансибл скрипту с ролью днс-сервера. После того, как все шаблоны файлов конфигов заполнены, нужно проверить корректность конфигурации и рестартовать днс-сервер. Хорошо, что для проверки конфигов bind он поставляется вместе с такими утилитами, как named-checkconf и named-checkzone. Ими мы и воспользуемся

- name: Run named-checkconf   shell: |     named-checkconf;   when: bind9_installed.changed  - name: Run named-checkzone for zones   shell: |     named-checkzone {{ item.name }} /etc/bind/zones/db.{{ item.name }};   with_items: "{{ vpn_hosts }}"   when: bind9_installed.changed  - name: Run named-checkzone for reverse zones   shell: |     named-checkzone {{ item.reverse_ip }}.in-addr.arpa /etc/bind/zones/db.{{ item.ip }};         with_items: "{{ vpn_hosts }}"   when: bind9_installed.changed  - name: Restart named   service:     name: named     state: restarted   when: bind9_installed.changed

Проверять никогда не лишне

Теперь днс-сервер настроен верно и на этом можно заканчивать, но я предлагаю добавить в ансибл скрипты и скрипт запуска ансибл скриптов, который работает в докер контейнере, следующий код проверки корректности отработки всех настроек

- name: Check DNS working correct   shell: |     # проверить, что DNS настроен правильно     if [ $(nslookup google.com | grep -c "Address:\s\+{{ dns_ip }}#53") != 1 ]; then       echo "Using incorrect DNS server";       exit 1;     fi     if [ $(nslookup google.com | grep -c "** server can't find") != 0 ]; then       echo "DNS not working";       exit 1;     fi     for (( index=0; index<$(echo "{{ vpn_hosts }}" | jq length); index++ )); do       name=$(echo "{{ vpn_hosts }}" | jq -sr ".[0][$index].name");       ip=$(echo "{{ vpn_hosts }}" | jq -sr ".[0][$index].ip");       reverse_ip=$(echo "{{ vpn_hosts }}" | jq -sr ".[0][$index].reverse_ip");       echo "Check DNS for name=$name ip=$ip reverse_ip=$reverse_ip";       if [ $(nslookup $name | grep -c "Address: $ip") != 1 ]; then         echo "DNS server's zones configured incorrectly";         exit 1;       fi       if [ $(nslookup $ip | grep -c "$reverse_ip.in-addr.arpa\s\+name = $name.") != 1 ]; then              echo "DNS server's reverse zones configured incorrectly";         exit 1;       fi     done     echo "DNS - OK";      echo "" > $HOME/dns.checked;   args:     creates: $HOME/dns.checked     executable: /bin/bash # меняю, потому что /bin/sh не справится с for выше

В этом коде просто проверяется, что и интернет имена доступны на примере google.com, и что доступны имена всех наших машин. Для наших машин также проверяется и реверсивный днс-поиск.

Подводим итоги

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

Настало время устроить рок-н-ролл на этих машинах. На следующем этапе я планирую запустить на них кубер. Так шаг за шагом мы создадим что-то приличное в нашем микрооблаке.

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


ссылка на оригинал статьи https://habr.com/ru/post/663868/

Добавить комментарий

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