One place for hosting & domains

      кластера

      Настройка и обеспечение безопасности кластера etcd с помощью Ansible в Ubuntu 18.04


      Автор выбрал фонд Wikimedia Foundation для получения пожертвования в рамках программы Write for DOnations.

      Введение

      etcd — это распределенное хранилище типа «ключ-значение», на которое опирается множество платформ и инструментов, включая Kubernetes, Vulcand и Doorman. Внутри Kubernetes etcd используется в качестве глобального хранилища, где хранится состояние кластера. Знание того, как управлять etcd, обязательно для управления кластером Kubernetes. Хотя существует большое количество предложений, использующих Kubernetes, также известных как Kubernetes как услуга, которые избавляют вас от необходимости выполнения административной работы, многие компании все еще предпочитают запускать управляемые кластеры Kubernetes самостоятельно, используя собственные ресурсы, по причине гибкости, которую дает этот подход.

      Первая половина этой статьи поможет вам настроить состоящий из 3 узлов кластер etcd на серверах Ubuntu 18.04. Вторая половина будет посвящена обеспечению безопасности кластера с помощью протокола безопасности транспортного уровня или TLS. Для автоматического запуска каждой настройки мы будем на протяжении всего руководства использовать Ansible. Ansible — это инструмент управления конфигурацией, аналогичный Puppet, Chef и SaltStack, который позволяет нам определять каждый шаг настройки в декларативной манере внутри файлов, которые называются плейбуками.

      В конце этого руководства у вас будет защищенный кластер etcd из 3 узлов, запущенный на ваших серверах. Также у вас будет плейбук Ansible, который позволяет вам многократно и последовательно воссоздавать одну и ту же настройку на новом наборе серверов.

      Предварительные требования

      Для прохождения этого обучающего руководства вам потребуется следующее:

      • Python, pip и пакет pyOpenSSL, установленные на вашем локальном компьютере. Чтобы узнать, как установить Python3, pip и пакеты Python, воспользуйтесь руководством по установке Python 3 и настройке локальной среды программирования в Ubuntu 18.04.

      • Три сервера Ubuntu 18.04 в одной локальной сети с минимум 2 ГБ оперативной памяти и доступом root через SSH. Также вам необходимо задать для серверов имена хостов etcd1, etcd2 и etcd3. Шаги, описанные в этой статье, будут работать на любом базовом сервере, а не только на дроплетах DigitalOcean. Однако если вы хотите разместить ваши серверы в DigitalOcean, вы можете воспользоваться руководством по созданию дроплета в панели управления DigitalOcean, чтобы выполнить это требование. Обратите внимание, что вы должны активировать опцию Private Networking (Частная сеть) при создании вашего дроплета. Чтобы активировать частную сеть для существующих дроплетов, воспользуйтесь руководством по активации частной сети в дроплетах.

      Предупреждение. Поскольку цель этой статьи состоит в знакомстве с настройкой кластера etcd в частной сети, три сервера Ubuntu 18.04 в рамках данной настройки не были протестированы с брандмауэром и доступны при работе с пользователем root. В производственной среде для любого узла, открытого для публичного Интернета, необходимо придерживаться передовых практик обеспечения безопасности при настройке брандмауэра и пользователя sudo. Дополнительную информацию см. в руководстве по начальной настройке сервера Ubuntu 18.04.

      • Пара ключей SSH, обеспечивающая для локального компьютера доступ к серверам etcd1, etcd2 и etcd3. Если вы не знаете, что такое SSH, или у вас нет пары ключей SSH, вы можете получить необходимую информацию, прочитав статью Основы SSH: работа с серверами, клиентами и ключами SSH.

      • Система Ansible, установленная на локальном компьютере. Например, если вы используете Ubuntu 18.04, вы можете установить Ansible, выполнив указания в шаге 1 статьи Установка и настройка Ansible в Ubuntu 18.04. После этого команды ansible и ansible-playbook будут доступны на вашем компьютере. Также вам может пригодиться статья Использование Ansible: справочное руководство. Команды в этом руководстве должны работать с Ansible версии 2.х; мы протестировали его на Ansible 2.9.7 с Python 3.8.2.

      Шаг 1 — Настройка Ansible для узла управления

      Ansible — это инструмент, используемый для управления серверами. Серверы, которыми управляет Ansible, называются управляемыми узлами, а компьютер, на котором запущен Ansible, называется узлом управления. Ansible использует ключи SSH на узле управления, чтобы получить доступ к управляемым узлам. После установки сеанса SSH Ansible запускает набор скриптов для предоставления и настройки управляемых узлов. На этом шаге мы протестируем возможность использования Ansible для подключения к управляемым узлам и запустим команду hostname.

      Типичный день системного администратора может включать управление различными наборами узлов. Например, вы можете использовать Ansible для предоставления новых серверов, а позже использовать Ansible для изменения конфигурации другого набора серверов. Чтобы позволить администраторам лучше организовать набор управляемых узлов, Ansible предоставляет концепцию inventory хостов (или inventory для краткости). Вы можете определить каждый узел, которым вы хотите управлять с помощью Ansible, внутри inventory-файла и организовать их в группы. Затем при запуске команд ansible и ansible-playbook вы можете указать, к каким хостам или группам применяется эта команда.

      По умолчанию Ansible считывает inventory-файл в каталоге /etc/ansible/hosts, однако мы можем указать другой inventory-файл с помощью флага --inventory (или -i для краткости).

      Для начала создайте новый каталог на локальном компьютере (узле управления) для размещения всех файлов данного руководства:

      • mkdir -p $HOME/playground/etcd-ansible

      Затем перейдите в только что созданный каталог:

      • cd $HOME/playground/etcd-ansible

      Внутри каталога создайте и откройте пустой inventory-файл с именем hosts с помощью вашего редактора:

      • nano $HOME/playground/etcd-ansible/hosts

      Внутри файла hosts перечислите все ваши управляемые узлы в следующем формате, заменив выделенные публичные IP-адреса на реальные IP-адреса ваших серверов:

      ~/playground/etcd-ansible/hosts

      [etcd]
      etcd1 ansible_host=etcd1_public_ip  ansible_user=root
      etcd2 ansible_host=etcd2_public_ip  ansible_user=root
      etcd3 ansible_host=etcd3_public_ip  ansible_user=root
      

      Строка [etcd] определяет группу с именем etcd. Под определением группы мы перечисляем все наши управляемые узлы. Каждая строка начинается с псевдонима (например, etcd1), который позволяет нам обращаться к каждому хосту, используя простое для запоминания имя вместо длинного IP-адреса. ansible_host и ansible_user — это переменные Ansible. В этом случае они используются для предоставления Ansible публичных IP-адресов и пользовательских имен SSH, которые используются при подключении через SSH.

      Чтобы гарантировать, что Ansible сможет подключаться к нашим управляемым узлам, мы можем протестировать подключение с помощью Ansible и запустить команду hostname на каждом хосте в группе etcd:

      • ansible etcd -i hosts -m command -a hostname

      Давайте подробно разберем эту команду, чтобы узнать, что означает каждая часть:

      • etcd: указывает шаблон хоста, который используется для определения того, какие хосты из inventory управляются с помощью этой команды. Здесь мы используем имя группы в качестве шаблона хоста.
      • -i hosts: указывает inventory-файл, который нужно использовать.
      • -m command: функциональность Ansible обеспечивается модулями. Модуль command принимает передаваемый в него аргумент и выполняет его как команду на каждом из управляемых узлов. В этом руководстве мы будем внедрять несколько дополнительных модулей Ansible по мере нашего прогресса.
      • -a hostname: аргумент, который необходимо передать в модуль. Количество и типы аргументов зависят от модуля.

      После запуска команды вы получите следующий вывод, который означает, что Ansible настроен корректно:

      Output

      etcd2 | CHANGED | rc=0 >> etcd2 etcd3 | CHANGED | rc=0 >> etcd3 etcd1 | CHANGED | rc=0 >> etcd1

      Каждая команда, которую запускает Ansible, называется задачей. Использование ansible в командной строке для запуска задач называется запуском ситуативных команд. Преимущество ситуативных команд состоит в том, что они быстрые и требуют минимальной настройки, а недостаток состоит в том, что они запускаются вручную, а значит не могут быть добавлены в систему контроля версий, например Git.

      Небольшим улучшением может быть запись скрипта оболочки и запуск команд с помощью модуля script Ansible. Это позволит нам записать этапы конфигурации, которые мы передали в систему контроля версий. Однако скрипты оболочки имеют императивный характер, что означает, что нам нужно определить команды для запуска («как») для приведения системы в желаемое состояние. Ansible, с другой стороны, выступает за декларативный подход, где мы определяем «какое» состояние сервера нам нужно внутри файлов конфигурации, а Ansible отвечает за приведение сервера в это желаемое состояние.

      Декларативный метод является предпочтительным, поскольку назначение файла конфигурации передается немедленно, что означает, что его легче понять и поддерживать. Также подобный подход возлагает ответственность за обработку пограничных случаев на Ansible, а не на администратора, избавляя от большого объема работы.

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

      Шаг 2 — Получение имен хостов управляемых узлов с помощью плейбуков Ansible

      На этом шаге мы воспроизведем то, что было сделано в шаге 1, т. е. выведем имена хостов управляемых узлов, но вместо запуска ситуативных задач мы определим каждую задачу декларативно в виде плейбука Ansible и запустим ее. Цель этого шага — продемонстрировать, как работают плейбуки Ansible. В последующих шагах мы будем выполнять гораздо более серьезные задачи с помощью плейбуков.

      Внутри каталога проекта создайте новый файл с именем playbook.yaml с помощью вашего редактора:

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Внутри playbook.yaml добавьте следующие строки:

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        tasks:
          - name: "Retrieve hostname"
            command: hostname
            register: output
          - name: "Print hostname"
            debug: var=output.stdout_lines
      

      Закройте и сохраните файл playbook.yaml, нажав CTRL+X, а затем Y.

      Плейбук содержит список инструкций; каждая инструкция содержит список задач, которые следует запускать на всех хостах, соответствующих шаблону хоста, указанному ключом hosts. В этом плейбуке у нас есть одна инструкция, содержащая две задачи. Первая задача запускает команду hostname, используя модуль command, и записывает вывод в переменную с именем output. Во второй задаче мы используем модуль debug для вывода свойства stdout_lines переменной output.

      Теперь мы можем запустить этот плейбук с помощью команды ansible-playbook:

      • ansible-playbook -i hosts playbook.yaml

      Вы получите следующий вывод, что означает, что ваш плейбук работает корректно:

      Output

      PLAY [etcd] *********************************************************************************************************************** TASK [Gathering Facts] ************************************************************************************************************ ok: [etcd2] ok: [etcd3] ok: [etcd1] TASK [Retrieve hostname] ********************************************************************************************************** changed: [etcd2] changed: [etcd3] changed: [etcd1] TASK [Print hostname] ************************************************************************************************************* ok: [etcd1] => { "output.stdout_lines": [ "etcd1" ] } ok: [etcd2] => { "output.stdout_lines": [ "etcd2" ] } ok: [etcd3] => { "output.stdout_lines": [ "etcd3" ] } PLAY RECAP ************************************************************************************************************************ etcd1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 etcd2 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 etcd3 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

      Примечание: ansible-playbook иногда использует cowsay для нестандартного вывода заголовков. Если вы обнаружите в своем терминале много нарисованных с помощью ASCII-графики коров, то впредь будете знать, почему это происходит. Чтобы отключить эту функцию, задайте для переменной среды ANSIBLE_NOCOWS значение 1 перед запуском ansible-playbook, запустив export ANSIBLE_NOCOWS=1 в оболочке.

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

      Шаг 3 — Установка etcd на управляемые узлы

      На этом шаге мы покажем вам команды для ручной установки etcd и продемонстрируем, как перевести эти самые команды в задачи внутри нашего плейбука Ansible.

      etcd и соответствующий клиент etcdctl доступны в качестве бинарных файлов, которые мы загрузим, извлечем и переместим в каталог, являющийся частью переменной среды PATH. При ручной настройке эти шаги необходимо выполнять для каждого управляемого узла:

      • mkdir -p /opt/etcd/bin
      • cd /opt/etcd/bin
      • wget -qO- https://storage.googleapis.com/etcd/v3.3.13/etcd-v3.3.13-linux-amd64.tar.gz | tar --extract --gzip --strip-components=1
      • echo 'export PATH="$PATH:/opt/etcd/bin"' >> ~/.profile
      • echo 'export ETCDCTL_API=3" >> ~/.profile

      Первые четыре команды загружают и извлекают бинарный файл в каталог /opt/etcd/bin/. По умолчанию клиент etcdctl использует API версии 2 для связи с сервером etcd. Поскольку мы запускаем etcd версии 3.x, последняя команда устанавливает для переменной среды ETCDCTL_API значение 3.

      Примечание. Здесь мы используем etcd версии 3.3.13 для компьютеров с процессорами, использующими набор инструкций AMD64. Вы можете найти бинарные файлы для других систем и других версий на официальной странице выпусков на GitHub.

      Чтобы воспроизвести аналогичные шаги в стандартизированном формате, мы можем добавить задачи в наш плейбук. Откройте файл playbook.yaml в вашем редакторе:

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Замените все содержимое файла playbook.yaml на следующее содержимое:

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        become: True
        tasks:
          - name: "Create directory for etcd binaries"
            file:
              path: /opt/etcd/bin
              state: directory
              owner: root
              group: root
              mode: 0700
          - name: "Download the tarball into the /tmp directory"
            get_url:
              url: https://storage.googleapis.com/etcd/v3.3.13/etcd-v3.3.13-linux-amd64.tar.gz
              dest: /tmp/etcd.tar.gz
              owner: root
              group: root
              mode: 0600
              force: True
          - name: "Extract the contents of the tarball"
            unarchive:
              src: /tmp/etcd.tar.gz
              dest: /opt/etcd/bin/
              owner: root
              group: root
              mode: 0600
              extra_opts:
                - --strip-components=1
              decrypt: True
              remote_src: True
          - name: "Set permissions for etcd"
            file:
              path: /opt/etcd/bin/etcd
              state: file
              owner: root
              group: root
              mode: 0700
          - name: "Set permissions for etcdctl"
            file:
              path: /opt/etcd/bin/etcdctl
              state: file
              owner: root
              group: root
              mode: 0700
          - name: "Add /opt/etcd/bin/ to the $PATH environment variable"
            lineinfile:
              path: /etc/profile
              line: export PATH="$PATH:/opt/etcd/bin"
              state: present
              create: True
              insertafter: EOF
          - name: "Set the ETCDCTL_API environment variable to 3"
            lineinfile:
              path: /etc/profile
              line: export ETCDCTL_API=3
              state: present
              create: True
              insertafter: EOF
      

      Каждая задача использует модуль; для этого набора задач мы используем следующие модули:

      • file: для создания каталога /opt/etcd/bin и последующей настройки разрешений файлов для бинарных файлов etcd и etcdctl.
      • get_url: для загрузки тарбола в формате GZIP на управляемые узлы.
      • unarchive: для извлечения и распаковки бинарных файлов etcd и etcdctl из тарбола в формате GZIP.
      • lineinfile: для добавления записи в файл .profile.

      Чтобы применить эти изменения, закройте и сохраните файл playbook.yaml, нажав CTRL+X, а затем Y. После этого в терминале снова запустите ту же команду ansible-playbook:

      • ansible-playbook -i hosts playbook.yaml

      Раздел PLAY RECAP в выводе будет отображать только ok и changed:

      Output

      ... PLAY RECAP ************************************************************************************************************************ etcd1 : ok=8 changed=7 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 etcd2 : ok=8 changed=7 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 etcd3 : ok=8 changed=7 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

      Чтобы подтвердить правильную установку etcd, выполните ручное подключение через SSH к одному из управляемых узлов и запустите etcd и etcdctl:

      etcd1_public_ip — это публичные IP-адреса сервера с именем etcd1. После получения доступа через SSH запустите etcd --version для вывода версии установленного etcd:

      Вы получите вывод, соответствующий представленному ниже, что означает, что бинарный файл etcd успешно установлен:

      Output

      etcd Version: 3.3.13 Git SHA: 98d3084 Go Version: go1.10.8 Go OS/Arch: linux/amd64

      Чтобы подтвердить успешную установку etcdctl, запустите etcdctl version:

      Вы увидите примерно следующий результат:

      Output

      etcdctl version: 3.3.13 API version: 3.3

      Обратите внимание, что в выводе указано API version: 3.3, что также подтверждает, что наша переменная среды ETCDCTL_API была настроена корректно.

      Выйдите из сервера etcd1, чтобы вернуться в локальную среду.

      Мы успешно установили etcd и etcdctl на все наши управляемые узлы. В следующем шаге мы добавим дополнительные задачи в нашу инструкцию для запуска etcd в качестве фоновой службы.

      Шаг 4 — Создание юнит-файла для etcd

      Может показаться, что самым быстрым способом запуска etcd с помощью Ansible может быть использование модуля command для запуска /opt/etcd/bin/etcd. Однако этот способ не сработает, поскольку он будет запускать etcd в качестве активного процесса. Использование модуля command будет приводить к зависанию Ansible в ожидании результата, возвращаемого командой etcd, чего никогда не произойдет. Поэтому в этом шаге мы обновим наш плейбук для запуска нашего бинарного файла etcd в качестве фоновой службы.

      Ubuntu 18.04 использует systemd в качестве инит-системы, что означает, что мы можем создавать новые службы, записывая юнит-файлы и размещая их внутри каталога /etc/systemd/system/.

      Во-первых, внутри каталога нашего проекта создайте новый каталог с именем files/:

      Затем с помощью вашего редактора создайте в этом каталоге новый файл с именем etcd.service:

      Скопируйте следующий блок кода в файл files/etcd.service:

      ~/playground/etcd-ansible/files/etcd.service

      [Unit]
      Description=etcd distributed reliable key-value store
      
      [Service]
      Type=notify
      ExecStart=/opt/etcd/bin/etcd
      Restart=always
      

      Этот юнит-файл определяет службу, которая запускает исполняемый файл в /opt/etcd/bin/etcd, уведомляет systemd о завершении инициализации и перезапускается при каждом случае сбоя.

      Примечание. Если вы хотите узнать больше о systemd и юнит-файлах или хотите настроить юнит-файл согласно вашим нуждам, ознакомьтесь с руководством Знакомство с юнитами systemd и юнит-файлами.

      Закройте и сохраните файл files/etcd.service, нажав CTRL+X, а затем Y.

      Далее нам нужно добавить в наш плейбук задачу, которая будет копировать локальный файл files/etcd.service в каталог /etc/systemd/system/etcd.service для каждого управляемого узла. Мы можем сделать это с помощью модуля copy.

      Откройте ваш плейбук:

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Добавьте следующую выделенную задачу после существующих задач:

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        become: True
        tasks:
          ...
          - name: "Set the ETCDCTL_API environment variable to 3"
            lineinfile:
              path: /etc/profile
              line: export ETCDCTL_API=3
              state: present
              create: True
              insertafter: EOF
          - name: "Create a etcd service"
            copy:
              src: files/etcd.service
              remote_src: False
              dest: /etc/systemd/system/etcd.service
              owner: root
              group: root
              mode: 0644
      

      После копирования юнит-файла в /etc/systemd/system/etcd.service служба будет определена.

      Сохраните и закройте плейбук.

      Запустите ту же команду ansible-playbook снова для применения изменений:

      • ansible-playbook -i hosts playbook.yaml

      Чтобы убедиться, что изменения вступили в силу, выполните подключение через SSH к одному из управляемых узлов:

      Затем запустите systemctl status etcd для отправки systemd запроса о состоянии службы etcd:

      Вы получите следующий вывод, который подтверждает, что служба загружена:

      Output

      ● etcd.service - etcd distributed reliable key-value store Loaded: loaded (/etc/systemd/system/etcd.service; static; vendor preset: enabled) Active: inactive (dead) ...

      Примечание. Последняя строка (Active: inactive (dead)) вывода указывает на неактивный статус службы, что означает, что она не будет запускаться автоматически при запуске системы. Это ожидаемое поведение, которое не является ошибкой.

      Нажмите q для возврата в оболочку, а затем запустите команду exit для выхода из управляемого узла и возврата в локальную оболочку:

      В этом шаге мы обновили наш плейбук для запуска бинарного файла etcd в качестве службы systemd. В следующем шаге мы продолжим настройку etcd, предоставив службе пространство для хранения данных.

      Шаг 5 — Настройка каталога данных

      etcd — это хранилище данных типа «ключ-значение», а это значит, что мы должны предоставить ему пространство для хранения данных. В этом шаге мы обновим наш плейбук для определения специального каталога хранения данных, который будет использовать etcd.

      Откройте ваш плейбук:

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Добавьте следующую задачу в конец списка задач:

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        become: True
        tasks:
          ...
          - name: "Create a etcd service"
            copy:
              src: files/etcd.service
              remote_src: False
              dest: /etc/systemd/system/etcd.service
              owner: root
              group: root
              mode: 0644
          - name: "Create a data directory"
            file:
              path: /var/lib/etcd/{{ inventory_hostname }}.etcd
              state: directory
              owner: root
              group: root
              mode: 0755
      

      Здесь мы используем /var/lib/etcd/hostname.etcd в качестве каталога данных, где hostname — это имя хоста текущего управляемого узла. inventory_hostname — это переменная, представляющая имя хоста текущего управляемого узла; Ansible подставляет ее значение автоматически. Конструкция с фигурными скобками (например, {{ inventory_hostname }}) применяется для подстановки переменной, поддерживаемой в механизме шаблонов Jinja2, используемом по умолчанию в Ansible.

      Закройте текстовый редактор и сохраните файл.

      Далее нам нужно будет указать etcd на необходимость использования этого каталога данных. Мы сделаем это, передав параметр data-dir в etcd. Чтобы задать параметры etcd, мы можем использовать сочетание переменных среды, флагов командной строки и файлов конфигурации. В этом руководстве мы будем использовать файл конфигурации, поскольку гораздо удобнее изолировать все конфигурации внутри файла вместо их размещения по всему плейбуку.

      В каталоге вашего проекта создайте новый каталог с именем templates/:

      Затем с помощью вашего редактора создайте в этом каталоге новый файл с именем etcd.conf.yaml.j2:

      • nano templates/etcd.conf.yaml.j2

      Затем скопируйте следующую строку и вставьте ее в файл:

      ~/playground/etcd-ansible/templates/etcd.conf.yaml.j2

      data-dir: /var/lib/etcd/{{ inventory_hostname }}.etcd
      

      Этот файл использует тот же синтаксис для подстановки переменной в Jinja2, что и наш плейбук. Чтобы подставить переменные и загрузить результат в каждый управляемый хост, мы можем использовать модуль template. Он работает схожим с модулем copy образом, но выполняет подстановку переменных перед загрузкой.

      Выйдите из etcd.conf.yaml.j2, а затем откройте ваш плейбук:

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Добавьте в список задач следующие задачи по созданию каталога и загрузке в него шаблонного файла конфигурации:

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        become: True
        tasks:
          ...
          - name: "Create a data directory"
            file:
              ...
              mode: 0755
          - name: "Create directory for etcd configuration"
            file:
              path: /etc/etcd
              state: directory
              owner: root
              group: root
              mode: 0755
          - name: "Create configuration file for etcd"
            template:
              src: templates/etcd.conf.yaml.j2
              dest: /etc/etcd/etcd.conf.yaml
              owner: root
              group: root
              mode: 0600
      

      Сохраните и закройте файл.

      Поскольку мы внесли это изменение, нам нужно обновить юнит-файл нашей службы, чтобы передать ему расположение нашего файла конфигурации (например, /etc/etcd/etcd.conf.yaml).

      Откройте файл службы etcd на локальном компьютере:

      Обновите файл files/etcd.service, добавив флаг --config-file, как показано в следующем выделенном фрагменте:

      ~/playground/etcd-ansible/files/etcd.service

      [Unit]
      Description=etcd distributed reliable key-value store
      
      [Service]
      Type=notify
      ExecStart=/opt/etcd/bin/etcd --config-file /etc/etcd/etcd.conf.yaml
      Restart=always
      

      Сохраните и закройте файл.

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

      Шаг 6 — Включение и запуск службы etcd

      При внесении изменений в юнит-файл службы мы должны перезапустить службу для вступления этих изменений в силу. Мы можем сделать это, запустив команду systemctl restart etcd. Кроме того, для автоматического запуска службы etcd при запуске системы нам нужно воспользоваться командой systemctl enable etcd. В этом шаге мы запустим эти две команды с помощью плейбука.

      Чтобы запустить команды, воспользуемся модулем command:

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Добавьте следующие задачи в конец списка задач:

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        become: True
        tasks:
          ...
          - name: "Create configuration file for etcd"
            template:
              ...
              mode: 0600
          - name: "Enable the etcd service"
            command: systemctl enable etcd
          - name: "Start the etcd service"
            command: systemctl restart etcd
      

      Сохраните и закройте файл.

      Запустите ansible-playbook -i hosts playbook.yaml еще раз:

      • ansible-playbook -i hosts playbook.yaml

      Чтобы убедиться, что служба etcd теперь перезапущена и активирована, подключитесь через SSH к одному из управляемых узлов:

      Затем запустите systemctl status etcd для проверки состоянии службы etcd:

      Вы получите значения enabled и active (running) в выделенных ниже местах; это означает, что изменения, которые мы внесли в наш плейбук, вступили в силу:

      Output

      ● etcd.service - etcd distributed reliable key-value store Loaded: loaded (/etc/systemd/system/etcd.service; static; vendor preset: enabled) Active: active (running) Main PID: 19085 (etcd) Tasks: 11 (limit: 2362)

      В этом шаге мы использовали модуль command для запуска команд systemctl, которые перезапускают и активируют службу etcd на наших управляемых узлах. Теперь, когда мы выполнили установку etcd, в следующем шаге мы протестируем ее функциональность, выполнив несколько базовых операций создания, чтения, обновления и удаления (CRUD).

      Шаг 7 — Тестирование etcd

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

      По умолчанию etcd предоставляет API, который прослушивает порт 2379 для связи с клиентом. Это означает, что мы можем отправлять etcd запросы API в чистом виде с помощью клиента HTTP. Однако быстрее будет использовать официальный клиент etcd etcdctl, который позволяет вам создавать/обновлять, получать и удалять пары «ключ-значение» с помощью подкоманд put, get и del соответственно.

      Убедитесь, что вы все еще находитесь внутри управляемого узла etcd1, и запустите следующие команды etcdctl, чтобы убедиться, что ваша установка etcd работает.

      Во-первых, создайте новую запись с помощью подкоманды put.

      Подкоманда put имеет следующий синтаксис:

      etcdctl put key value
      

      В etcd1 запустите следующую команду:

      Команда, которую мы только что запустили, указывает etcd записать значение "bar" для ключа foo в хранилище.

      После этого вы получите сообщение OK в выводе, что сигнализирует о сохранении данных:

      Output

      OK

      После этого вы можете получить эту запись с помощью подкоманды get, которая имеет синтаксис etcdctl get key:

      Вы получите данный вывод, который показывает ключ в первой строке и значение, которое вы вставили ранее, во второй строке:

      Output

      foo bar

      Мы можем удалить запись с помощью подкоманды del, которая имеет синтаксис etcdctl del key:

      Вы получите следующий вывод, который указывает количество удаленных записей:

      Output

      1

      Теперь давайте запустим подкоманду get еще раз, чтобы попытаться получить удаленную пару «ключ-значение».

      Вы не получите вывод, что означает, что etcdctl не может получить пару «ключ-значение». Это подтверждает, что запись удалена и не может быть найдена.

      Теперь, когда вы протестировали основные операции etcd и etcdctl, давайте выйдем из нашего управляемого узла и вернемся в локальную среду:

      В этом шаге мы использовали клиент etcdctl для отправки запросов в etcd. На этом этапе мы используем три отдельных экземпляра etcd, каждый из которых действует независимо друг от друга. Однако etcd — это распределенное хранилище данных типа «ключ-значение», что означает, что несколько экземпляров etcd можно сгруппировать для формирования одного кластера. Каждый экземпляр в этом случае становится членом кластера. После формирования кластера вы сможете получить пару «ключ-значение», которая была вставлена из другого члена кластера. В следующем шаге мы используем наш плейбук для преобразования трех кластеров с одним узлом в один кластер с 3 узлами.

      Шаг 8 — Создание кластера с помощью статического обнаружения

      Чтобы создать один кластер из 3 узлов вместо трех кластеров с 1 узлом, нам нужно сконфигурировать эти установки etcd, чтобы они могли общаться друг с другом. Это означает, что каждая из них должна знать IP-адреса остальных. Этот процесс называется обнаружением. Обнаружение можно реализовать в форме статической конфигурации или динамического обнаружения службы. В этом шаге мы будем обсуждать разницу между этими двумя подходами, а также обновим наш плейбук для настройки кластера etcd с помощью статического обнаружения.

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

      • известное количество членов
      • известные конечные точки каждого члена
      • статические IP-адреса для всех конечных точек

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

      Поскольку мы знаем, что нам нужен кластер etcd с 3 узлами, а все наши серверы имеют статические IP-адреса, мы будем использовать статическое обнаружение. Чтобы инициировать создание кластера с помощью статического обнаружения, нам нужно добавить несколько параметров в наш файл конфигурации. Воспользуйтесь редактором, чтобы открыть файл шаблона templates/etcd.conf.yaml.j2:

      • nano templates/etcd.conf.yaml.j2

      Затем добавьте следующие выделенные строки:

      ~/playground/etcd-ansible/templates/etcd.conf.yaml.j2

      data-dir: /var/lib/etcd/{{ inventory_hostname }}.etcd
      name: {{ inventory_hostname }}
      initial-advertise-peer-urls: http://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2380
      listen-peer-urls: http://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2380,http://127.0.0.1:2380
      advertise-client-urls: http://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2379
      listen-client-urls: http://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2379,http://127.0.0.1:2379
      initial-cluster-state: new
      initial-cluster: {% for host in groups['etcd'] %}{{ hostvars[host]['ansible_facts']['hostname'] }}=http://{{ hostvars[host]['ansible_facts']['eth1']['ipv4']['address'] }}:2380{% if not loop.last %},{% endif %}{% endfor %}
      

      Закройте и сохраните файл templates/etcd.conf.yaml.j2, нажав CTRL+X, а затем Y.

      Ниже представлено краткое разъяснение каждого параметра:

      • name — это человекочитаемое имя члена. По умолчанию etcd использует уникальный, генерируемый случайным образом идентификатор для каждого члена, а человекочитаемое имя позволяет легче ссылаться на них внутри файлов конфигурации и в командной строке. Здесь мы будем использовать имена хостов в качестве имен членов (т. е. etcd1, etcd2 и etcd3).
      • initial-advertise-peer-urls — это список комбинаций IP-адреса/порта, которые могут использовать другие члены для связи с этим членом. Помимо порта API (2379) etcd также предоставляет порт 2380 для коммуникации между членами etcd, что позволяет им отправлять сообщения друг другу и обмениваться данными. Обратите внимание, что эти URL-адреса должны быть доступны для других членов (и не быть локальными IP-адресами).
      • listen-peer-urls — это список комбинаций IP-адреса/порта, с помощью которых текущий член будет прослушивать данные, поступающие от других членов. Он должен включать все URL-адреса, переданные с флагом --initial-advertise-peer-urls, а также локальные URL-адреса, такие как 127.0.0.1:2380. Комбинации IP-адреса/порта назначения входящих сообщений других членов должны соответствовать одному из перечисленных здесь URL-адресов.
      • advertise-client-urls — это список комбинаций IP-адреса/порта, которые клиенты должны использовать для коммуникации с этим членом. Эти URL-адреса должны быть доступны клиенту (и не быть локальными адресами). Если клиент получает доступ к кластеру через общедоступную часть Интернета, это должен быть публичный IP-адрес.
      • listen-client-urls — это список комбинаций IP-адреса/порта, с помощью которых текущий член будет прослушивать данные, поступающие от клиентов. Он должен включать все URL-адреса, переданные с флагом --advertise-client-urls, а также локальные URL-адреса, такие как 127.0.0.1:2380. Комбинации IP-адреса/порта назначения входящих сообщений других клиентов должны соответствовать одному из перечисленных здесь URL-адресов.
      • initial-cluster — это список конечных точек для каждого члена кластера. Каждая конечная точка должна соответствовать одному из URL-адресов списка initial-advertise-peer-urls соответствующего члена.
      • initial-cluster-state — либо значение new, либо existing.

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

      Если для параметра initial-cluster-state установлено значение new, etcd будет знать, что это новый кластер, который будет запущен, и позволит членам начинать работу параллельно, не ожидая достижения кворума. Если говорить конкретнее, после запуска первого члена у него не будет кворума, поскольку одна треть (33,33%) меньше или равна 50%. Обычно etcd будет приостанавливать работу и отказывать в совершении любых действий, а кластер не будет сформирован. Однако, если для initial-cluster-state установлено значение new, отсутствие кворума будет игнорироваться.

      Если установлено значение existing, член будет пытаться присоединиться к существующему кластеру и ожидать, что кворум будет достигнут.

      Примечание. Дополнительную информацию обо всех поддерживаемых флагах конфигурации вы можете найти в разделе конфигурации документации etcd.

      В обновленном файле шаблонов templates/etcd.conf.yaml.j2 существует несколько экземпляров hostvars. Во время работы Ansible собирает переменные из разных источников. Мы уже использовали переменную inventory_hostname ранее, но существует большое количество других переменных. Эти переменные доступны в виде hostvars[inventory_hostname]['ansible_facts']. Здесь мы извлекаем частные IP-адреса каждого узла и используем их для построения значения нашего параметра.

      Примечание. Поскольку мы включили опцию Private Networking (Частная сеть) при создании наших серверов, каждый сервер будет иметь три связанных с ним IP-адреса.

      • Кольцевой IP-адрес — адрес, который действителен внутри одного компьютера. Он используется для ссылки компьютера на себя самого, например, 127.0.0.1.
      • Публичный IP-адрес — адрес, который размещается в общедоступной части Интернета, например, 178.128.169.51.
      • Частный IP-адрес — адрес, который маршрутизируется только в частной сети; в случае с дроплетами DigitalOcean внутри каждого набора данных существует частная сеть, например, 10.131.82.225.

      Каждый из этих IP-адресов ассоциируется с другим сетевым интерфейсом — кольцевой адрес ассоциируется с интерфейсом lo, публичный — с интерфейсом eth0, а частный — с интерфейсом eth1. Мы используем интерфейс eth1, чтобы весь трафик оставался внутри частной сети, не попадая в Интернет.

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

      Синтаксис {% %} Jinja2 определяет структуру цикла for для итерации по каждому узлу в группе etcd и получения строки initial-cluster в требуемом etcd формате.

      Чтобы сформировать новый кластер из трех членов, необходимо сначала остановить работу службы etcd и очистить каталог данных перед запуском кластера. Для этого откройте в редакторе файл playbook.yaml на локальном компьютере:

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Затем перед задачей "Create a data directory" (Создать каталог данных) добавьте задачу для остановки службы etcd:

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        become: True
        tasks:
          ...
              group: root
              mode: 0644
          - name: "Stop the etcd service"
            command: systemctl stop etcd
          - name: "Create a data directory"
            file:
          ...
      

      Затем обновите задачу "Create a data directory" (Создать каталог данных) таким образом, чтобы каталог данных сначала удалялся, а потом создавался снова:

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        become: True
        tasks:
          ...
          - name: "Stop the etcd service"
            command: systemctl stop etcd
          - name: "Create a data directory"
            file:
              path: /var/lib/etcd/{{ inventory_hostname }}.etcd
              state: "{{ item }}"
              owner: root
              group: root
              mode: 0755
            with_items:
              - absent
              - directory
          - name: "Create directory for etcd configuration"
            file:
          ...
      

      Свойство with_items определяет список строк, по которым эта задача будет итерироваться. Это эквивалентно повторению одной и той же задачи дважды, но с разными значениями свойства state. Здесь мы итерируемся по списку с элементами absent и directory, что гарантирует, что каталог данных сначала удаляется, а потом создается повторно.

      Закройте и сохраните файл playbook.yaml, нажав CTRL+X, а затем Y. Затем запустите ansible-playbook повторно. Ansible теперь создаст отдельный кластер etcd с 3 членами:

      • ansible-playbook -i hosts playbook.yaml

      Вы можете проверить это, выполнив подключение через SSH к любому узлу etcd:

      После установления подключения запустите команду etcdctl endpoint health --cluster:

      • etcdctl endpoint health --cluster

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

      Output

      http://etcd2_private_ip:2379 is healthy: successfully committed proposal: took = 2.517267ms http://etcd1_private_ip:2379 is healthy: successfully committed proposal: took = 2.153612ms http://etcd3_private_ip:2379 is healthy: successfully committed proposal: took = 2.639277ms

      Мы успешно создали кластер etcd из 3 узлов. Мы можем подтвердить это, добавив запись в etcd на одном узле и получив ее на другом узле. На одном из узлов запустите etcdctl put:

      Затем используйте новый терминал для подключения через SSH к другому узлу:

      Далее попытайтесь получить ту же запись с помощью ключа:

      Вы сможете получить запись, что доказывает, что кластер работает:

      Output

      foo bar

      В заключение выйдите из каждого управляемого узла и вернитесь на локальный компьютер:

      В этом шаге мы предоставили новый кластер с 3 узлами. На данный момент связь между членами etcd и другими узлами и клиентами осуществляется через HTTP. Это означает, что коммуникации не зашифрованы, и любая сторона, которая может перехватить трафик, сможет прочитать сообщения. Это не является большой проблемой, если кластер etcd и клиенты размещены внутри частной сети или виртуальной частной сети (VPN), которую вы полностью контролируете. Однако, если какой-либо трафик должен проходить через общую сеть (частную или публичную), вам нужно гарантировать, что этот трафик будет зашифрован. Кроме того, необходимо создать механизм, который позволяет клиенту или другому узлу проверить аутентичность сервера.

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

      Шаг 9 — Получение частных IP-адресов управляемых узлов

      Для шифрования сообщений, пересылаемых между узлами, etcd использует протокол защищенного переноса гипертекста, или HTTPS, который представляет собой слой поверх протокола безопасности транспортного уровня, или TLS. TLS использует систему приватных ключей, сертификатов и доверенных объектов, называемых центрами сертификации (ЦС), для аутентификации и отправки зашифрованных сообщений друг другу.

      В этом руководстве каждый узел должен генерировать сертификат для собственной идентификации и получать подпись ЦС для этого сертификата. Мы настроим все узлы членов так, чтобы они доверяли этому ЦС, а значит доверяли любым сертификатам, которые он подписал. Это позволяет узлам взаимно аутентифицировать друг друга.

      Сертификат, который генерирует узел, должен позволить другим узлам идентифицировать себя. Все сертификаты включают стандартное имя (CN) объекта, с которым они ассоциируются. Часто оно используется как идентификатор объекта. Однако при проверке сертификата клиентские реализации могут сравнить собранную информацию об объекте с информацией, представленной в сертификате. Например, когда клиент загрузил сертификат TLS с субъектом CN=foo.bar.com, но клиент фактически подключился к серверу с помощью IP-адреса (например, 167.71.129.110), возникает противоречие, и клиент может не доверять сертификату. Указанное в сертификате дополнительное имя субъекта (SAN) во время верификации сообщает, что оба имени принадлежат одному и тому же объекту.

      Поскольку наши члены etcd обмениваются данными, используя свои частные IP-адреса, когда мы определяем наши сертификаты, нам нужно будет предоставить эти частные IP-адреса в качестве дополнительных имен субъекта.

      Чтобы узнать частный IP-адрес управляемого узла, выполните подключение через SSH к этому узлу:

      Затем запустите следующую команду:

      • ip -f inet addr show eth1

      Вы увидите вывод примерно следующего содержания:

      Output

      3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000 inet 10.131.255.176/16 brd 10.131.255.255 scope global eth1 valid_lft forever preferred_lft forever

      В нашем примере вывод 10.131.255.176 — это частный IP-адрес управляемого узла, который является единственной интересующей нас информацией. Чтобы отфильтровать все остальное содержание помимо частного IP-адреса, мы можем передать вывод команды ip в утилиту sed, которая используется для фильтрации и преобразования текста.

      • ip -f inet addr show eth1 | sed -En -e 's/.*inet ([0-9.]+).*/1/p'

      Теперь единственной информацией в выводе является частный IP-адрес:

      Output

      10.131.255.176

      Когда вы убедитесь, что предыдущая команда работает, выйдите из управляемого узла:

      Чтобы включить предшествующие команды в наш плейбук, откройте файл playbook.yaml:

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Затем добавьте новую инструкцию с одной задачей перед существующей инструкцией:

      ~/playground/etcd-ansible/playbook.yaml

      ...
      - hosts: etcd
        tasks:
          - shell: ip -f inet addr show eth1 | sed -En -e 's/.*inet ([0-9.]+).*/1/p'
            register: privateIP
      - hosts: etcd
        tasks:
      ...
      

      Задача использует модуль shell для запуска команд ip и sed, которые получают частный IP-адрес управляемого узла. Затем он регистрирует возвращаемое значение команды shell внутри переменной с именем privateIP, которую мы будем использовать позже.

      В этом шаге мы добавили в плейбук задачу получения частного IP-адреса управляемых узлов. В следующем шаге мы будем использовать эту информацию для генерирования сертификатов для каждого узла и получим подпись для этих сертификатов в центре сертификации (ЦС).

      Шаг 10 — Генерация приватных ключей и запросов на подпись сертификата для членов etcd

      Чтобы узел мог принимать шифрованный трафик, отправитель должен использовать публичный ключ узла для шифрования данных, а узел должен использовать свой приватный ключ для расшифровки зашифрованного сообщения и получения оригинальных данных. Публичный ключ упаковывается в сертификат и подписывается ЦС для гарантии его подлинности.

      Следовательно, нам нужно будет создать приватный ключ и запрос на подпись сертификата (CSR) для каждого узла etcd. Чтобы облегчить эту задачу, мы сгенерируем все пары ключей и подпишем все сертификаты локально, на узле управления, а затем скопируем соответствующие файлы на управляемые хосты.

      Сначала создайте каталог с именем artifacts/, куда мы поместим файлы (ключи и сертификаты), сгенерированные в ходе этого процесса. Откройте файл playbook.yaml в редакторе:

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Используйте модуль file для создания каталога artifacts/:

      ~/playground/etcd-ansible/playbook.yaml

      ...
          - shell: ip -f inet addr show eth1 | sed -En -e 's/.*inet ([0-9.]+).*/1/p'
            register: privateIP
      - hosts: localhost
        gather_facts: False
        become: False
        tasks:
          - name: "Create ./artifacts directory to house keys and certificates"
            file:
              path: ./artifacts
              state: directory
      - hosts: etcd
        tasks:
      ...
      

      Затем добавьте еще одну задачу в конец инструкции для генерирования приватного ключа:

      ~/playground/etcd-ansible/playbook.yaml

      ...
      - hosts: localhost
        gather_facts: False
        become: False
        tasks:
              ...
          - name: "Generate private key for each member"
            openssl_privatekey:
              path: ./artifacts/{{item}}.key
              type: RSA
              size: 4096
              state: present
              force: True
            with_items: "{{ groups['etcd'] }}"
      - hosts: etcd
        tasks:
      ...
      

      Создание приватных ключей и запросов на подпись сертификата выполняется с помощью модулей openssl_privatekey и openssl_csr соответственно.

      Атрибут force: True гарантирует, что приватный ключ генерируется заново, даже если он уже существует.

      Аналогичным образом добавьте следующую новую задачу в ту же инструкцию для генерирования запроса на подпись сертификата для каждого члена с помощью модуля openssl_csr:

      ~/playground/etcd-ansible/playbook.yaml

      ...
      - hosts: localhost
        gather_facts: False
        become: False
        tasks:
          ...
          - name: "Generate private key for each member"
            openssl_privatekey:
              ...
            with_items: "{{ groups['etcd'] }}"
          - name: "Generate CSR for each member"
            openssl_csr:
              path: ./artifacts/{{item}}.csr
              privatekey_path: ./artifacts/{{item}}.key
              common_name: "{{item}}"
              key_usage:
                - digitalSignature
              extended_key_usage:
                - serverAuth
              subject_alt_name:
                - IP:{{ hostvars[item]['privateIP']['stdout']}}
                - IP:127.0.0.1
              force: True
            with_items: "{{ groups['etcd'] }}"
      

      Мы указываем, что данный сертификат может быть использован в механизме цифровой подписи для аутентификации сервера. Этот сертификат ассоциируется с именем хоста (например, etcd1), но при проверке необходимо также рассматривать приватные и локальные кольцевые IP-адреса каждого узла в качестве альтернативных имен. Обратите внимание на использование переменной privateIP, которую мы зарегистрировали в предыдущей инструкции.

      Закройте и сохраните файл playbook.yaml, нажав CTRL+X, а затем Y. Затем запустите наш плейбук повторно:

      • ansible-playbook -i hosts playbook.yaml

      Теперь мы найдем новый каталог с именем artifacts внутри каталога проекта; используйте ls для вывода его содержимого:

      Вы получите приватные ключи и запросы на подпись сертификата для каждого члена etcd:

      Output

      etcd1.csr etcd1.key etcd2.csr etcd2.key etcd3.csr etcd3.key

      В этом шаге мы использовали несколько модулей Ansible для генерирования приватных ключей и сертификатов публичного ключа для каждого узла. В следующем шаге мы узнаем, как подписать запрос на подпись сертификата (CSR).

      Шаг 11 — Генерирование сертификатов ЦС

      В кластере etcd узлы шифруют сообщения с помощью публичного ключа получателя. Чтобы убедиться в подлинности публичного ключа, получатель упаковывает публичный ключ в запрос на подпись сертификата (CSR) и просит доверенный объект (например, ЦС) подписать CSR. Поскольку мы контролируем все узлы и ЦС, которым они доверяют, нам не нужно использовать внешний ЦС, и мы можем выступать в качестве собственного ЦС. В этом шаге мы будем действовать в качестве собственного ЦС, что означает, что нам нужно будет генерировать приватный ключ и самоподписанный сертификат, который будет функционировать в качестве ЦС.

      Во-первых, откройте файл playbook.yaml в редакторе:

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Затем, как и в предыдущем шаге, добавьте задачу в инструкцию localhost для генерирования приватного ключа для ЦС:

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: localhost
        ...
        tasks:
          ...
        - name: "Generate CSR for each member"
          ...
          with_items: "{{ groups['etcd'] }}"
          - name: "Generate private key for CA"
            openssl_privatekey:
              path: ./artifacts/ca.key
              type: RSA
              size: 4096
              state: present
              force: True
      - hosts: etcd
        become: True
        tasks:
          - name: "Create directory for etcd binaries"
      ...
      

      Затем воспользуйтесь модулем openssl_csr для генерирования нового запроса на подпись сертификата. Это похоже на предыдущий шаг, но в этом запросе на подпись сертификата мы добавляем базовое ограничение и расширение использования ключа, чтобы показать, что данный сертификат можно использовать в качестве сертификата ЦС:

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: localhost
        ...
        tasks:
          ...
          - name: "Generate private key for CA"
            openssl_privatekey:
              path: ./artifacts/ca.key
              type: RSA
              size: 4096
              state: present
              force: True
          - name: "Generate CSR for CA"
            openssl_csr:
              path: ./artifacts/ca.csr
              privatekey_path: ./artifacts/ca.key
              common_name: ca
              organization_name: "Etcd CA"
              basic_constraints:
                - CA:TRUE
                - pathlen:1
              basic_constraints_critical: True
              key_usage:
                - keyCertSign
                - digitalSignature
              force: True
      - hosts: etcd
        become: True
        tasks:
          - name: "Create directory for etcd binaries"
      ...
      

      Наконец, воспользуйтесь модулем openssl_certificate для самостоятельной подписи CSR:

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: localhost
        ...
        tasks:
          ...
          - name: "Generate CSR for CA"
            openssl_csr:
              path: ./artifacts/ca.csr
              privatekey_path: ./artifacts/ca.key
              common_name: ca
              organization_name: "Etcd CA"
              basic_constraints:
                - CA:TRUE
                - pathlen:1
              basic_constraints_critical: True
              key_usage:
                - keyCertSign
                - digitalSignature
              force: True
          - name: "Generate self-signed CA certificate"
            openssl_certificate:
              path: ./artifacts/ca.crt
              privatekey_path: ./artifacts/ca.key
              csr_path: ./artifacts/ca.csr
              provider: selfsigned
              force: True
      - hosts: etcd
        become: True
        tasks:
          - name: "Create directory for etcd binaries"
      ...
      

      Закройте и сохраните файл playbook.yaml, нажав CTRL+X, а затем Y. Затем запустите наш плейбук повторно для применения изменений:

      • ansible-playbook -i hosts playbook.yaml

      Также вы можете запустить команду ls для проверки содержимого каталога artifacts/:

      Теперь вы получите заново созданный сертификат ЦС (ca.crt):

      Output

      ca.crt ca.csr ca.key etcd1.csr etcd1.key etcd2.csr etcd2.key etcd3.csr etcd3.key

      В этом шаге мы сгенерировали приватный ключ и самоподписанный сертификат для ЦС. В следующем шаге мы будем использовать сертификат ЦС для подписи CSR каждого члена.

      Шаг 12 — Подписание запросов на подпись сертификата для членов etcd

      В этом шаге мы будем подписывать CSR каждого узла. Это процесс аналогичен тому, как мы использовали модуль openssl_certificate для самостоятельной подписи сертификата ЦС, но вместо использования поставщика selfsigned мы будем использовать поставщика ownca, который позволяет добавлять подпись с помощью нашего собственного сертификата ЦС.

      Откройте ваш плейбук:

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Добавьте следующую выделенную задачу в задачу "Generate self-signed CA certificate" (Сгенерировать самоподписанный сертификат ЦС):

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: localhost
        ...
        tasks:
          ...
          - name: "Generate self-signed CA certificate"
            openssl_certificate:
              path: ./artifacts/ca.crt
              privatekey_path: ./artifacts/ca.key
              csr_path: ./artifacts/ca.csr
              provider: selfsigned
              force: True
          - name: "Generate an `etcd` member certificate signed with our own CA certificate"
            openssl_certificate:
              path: ./artifacts/{{item}}.crt
              csr_path: ./artifacts/{{item}}.csr
              ownca_path: ./artifacts/ca.crt
              ownca_privatekey_path: ./artifacts/ca.key
              provider: ownca
              force: True
            with_items: "{{ groups['etcd'] }}"
      - hosts: etcd
        become: True
        tasks:
          - name: "Create directory for etcd binaries"
      ...
      

      Закройте и сохраните файл playbook.yaml, нажав CTRL+X, а затем Y. Затем запустите плейбук повторно для применения изменений:

      • ansible-playbook -i hosts playbook.yaml

      Теперь выведите содержимое каталога artifacts/:

      Вы получите приватный ключ, CSR и сертификат для каждого члена etcd и ЦС:

      Output

      ca.crt ca.csr ca.key etcd1.crt etcd1.csr etcd1.key etcd2.crt etcd2.csr etcd2.key etcd3.crt etcd3.csr etcd3.key

      В этом шаге мы подписали CSR каждого узла с помощью ключа ЦС. В следующем шаге мы скопируем соответствующие файлы на каждый управляемый узел, чтобы etcd смог получить доступ к соответствующим ключам и сертификатам для настройки подключений TLS.

      Шаг 13 — Копирование приватных ключей и сертификатов

      Каждый узел должен иметь копию самоподписанного сертификата ЦС (ca.crt). Каждый узел etcd также должен иметь свой собственный приватный ключ и сертификат. В этом шаге мы загрузим эти файлы и поместим их в новый каталог /etc/etcd/ssl/.

      Для начала откройте файл playbook.yaml в редакторе:

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Чтобы внести эти изменения в наш плейбук Ansible, сначала обновите свойство path задачи Create directory for etcd configuration (Создать каталог для конфигурации ectd), чтобы создать каталог /etc/etcd/ssl/:

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        ...
        tasks:
          ...
            with_items:
              - absent
              - directory
          - name: "Create directory for etcd configuration"
            file:
              path: "{{ item }}"
              state: directory
              owner: root
              group: root
              mode: 0755
            with_items:
              - /etc/etcd
              - /etc/etcd/ssl
          - name: "Create configuration file for etcd"
            template:
      ...
      

      Затем сразу после измененной задачи добавьте еще три задачи для копирования файлов:

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        ...
        tasks:
          ...
          - name: "Copy over the CA certificate"
            copy:
              src: ./artifacts/ca.crt
              remote_src: False
              dest: /etc/etcd/ssl/ca.crt
              owner: root
              group: root
              mode: 0644
          - name: "Copy over the `etcd` member certificate"
            copy:
              src: ./artifacts/{{inventory_hostname}}.crt
              remote_src: False
              dest: /etc/etcd/ssl/server.crt
              owner: root
              group: root
              mode: 0644
          - name: "Copy over the `etcd` member key"
            copy:
              src: ./artifacts/{{inventory_hostname}}.key
              remote_src: False
              dest: /etc/etcd/ssl/server.key
              owner: root
              group: root
              mode: 0600
          - name: "Create configuration file for etcd"
            template:
      ...
      

      Закройте и сохраните файл playbook.yaml, нажав CTRL+X, а затем Y.

      Запустите ansible-playbook снова для внесения этих изменений:

      • ansible-playbook -i hosts playbook.yaml

      В этом шаге мы успешно загрузили приватные ключи и сертификаты на управляемые узлы. После копирования файлов нам нужно обновить наш файл конфигурации etcd, чтобы мы могли использовать эти файлы.

      Шаг 14 — Активация TLS на etcd

      В последнем шаге данного руководства мы обновим ряд конфигураций Ansible для активации TLS в кластере etcd.

      Сначала откройте файл templates/etcd.conf.yaml.j2 с помощью редактора:

      • nano $HOME/playground/etcd-ansible/templates/etcd.conf.yaml.j2

      Внутри файла измените все URL-адреса для использования https в качестве протокола вместо http. Кроме того, добавьте раздел в конце шаблона для указания расположения сертификата ЦС, сертификата сервера и ключа сервера:

      ~/playground/etcd-ansible/templates/etcd.conf.yaml.j2

      data-dir: /var/lib/etcd/{{ inventory_hostname }}.etcd
      name: {{ inventory_hostname }}
      initial-advertise-peer-urls: https://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2380
      listen-peer-urls: https://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2380,https://127.0.0.1:2380
      advertise-client-urls: https://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2379
      listen-client-urls: https://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2379,https://127.0.0.1:2379
      initial-cluster-state: new
      initial-cluster: {% for host in groups['etcd'] %}{{ hostvars[host]['ansible_facts']['hostname'] }}=https://{{ hostvars[host]['ansible_facts']['eth1']['ipv4']['address'] }}:2380{% if not loop.last %},{% endif %}{% endfor %}
      
      client-transport-security:
        cert-file: /etc/etcd/ssl/server.crt
        key-file: /etc/etcd/ssl/server.key
        trusted-ca-file: /etc/etcd/ssl/ca.crt
      peer-transport-security:
        cert-file: /etc/etcd/ssl/server.crt
        key-file: /etc/etcd/ssl/server.key
        trusted-ca-file: /etc/etcd/ssl/ca.crt
      

      Закройте и сохраните файл templates/etcd.conf.yaml.j2.

      Затем запустите ваш плейбук Ansible:

      • ansible-playbook -i hosts playbook.yaml

      Подключитесь по SSH к одному из управляемых узлов:

      Выполнив подключение, запустите команду etcdctl endpoint health для проверки использования HTTPS конечными точками и состояния всех членов:

      • etcdctl --cacert /etc/etcd/ssl/ca.crt endpoint health --cluster

      Поскольку наш сертификат ЦС по умолчанию не является доверенным корневым сертификатом ЦС, установленным в каталоге /etc/ssl/certs/, нам нужно передать его etcdctl с помощью флага --cacert.

      Результат будет выглядеть следующим образом:

      Output

      https://etcd3_private_ip:2379 is healthy: successfully committed proposal: took = 19.237262ms https://etcd1_private_ip:2379 is healthy: successfully committed proposal: took = 4.769088ms https://etcd2_private_ip:2379 is healthy: successfully committed proposal: took = 5.953599ms

      Чтобы убедиться, что кластер etcd действительно работает, мы можем снова создать запись на узле и получить ее из другого узла:

      • etcdctl --cacert /etc/etcd/ssl/ca.crt put foo "bar"

      Затем используйте новый терминал для подключения через SSH к другому узлу:

      Теперь попробуйте получить ту же запись с помощью ключа foo:

      • etcdctl --cacert /etc/etcd/ssl/ca.crt get foo

      Это позволит получить запись, показанную в выводе ниже:

      Output

      foo bar

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

      Заключение

      Вы успешно создали кластер etcd, состоящий из 3 узлов, обеспечили его безопасность с помощью TLS и подтвердили его работоспособность.

      etcd — это инструмент, первоначально созданный для CoreOS. Чтобы понять, как etcd используется вместе с CoreOS, прочитайте статью Использование Etcdctl и Etcd, распределенного хранилища типа «ключ-значение» для CoreOS. В этой статье вы также можете ознакомиться с настройкой модели динамического обнаружения, которая была описана, но не продемонстрирована в данном руководстве.

      Как отмечалось в начале данного руководства, etcd является важной частью экосистемы Kubernetes. Дополнительную информацию о Kubernetes и роли etcd в рамках этой системы вы можете найти в статье Знакомство с Kubernetes. Если вы развертываете etcd в рамках кластера Kubernetes, вам могут пригодиться другие доступные инструменты, такие как kubespray и kubeadm. Дополнительную информацию о последних можно найти в статье Создание кластера Kubernetes с помощью kubeadm в Ubuntu 18.04.

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



      Source link

      Рекомендуемые шаги по обеспечению безопасности кластера DigitalOcean Kubernetes


      Автор выбрал организацию Open Sourcing Mental Illness Ltd для получения пожертвований в рамках программы Write for DOnations.

      Введение

      Kubernetes, платформа для управления контейнерами с открытым исходным кодом, повсеместно становится предпочитаемым решением для автоматизации, масштабирования и управления кластерами с высоким уровнем доступности. Благодаря растущей популярности данной платформы, безопасность Kubernetes становится все более актуальной.

      Учитывая составные части Kubernetes, а также разнообразие сценариев развертывания, защита Kubernetes иногда может быть сопряжена с трудностями. Поэтому цель этой статьи заключается в том, чтобы заложить прочную основу безопасности для кластера DigitalOcean Kubernetes (DOKS). Необходимо отметить, что настоящий обучающий модуль охватывает базовые меры безопасности для Kubernetes и служит отправной точкой, а не исчерпывающим руководством. Дополнительные шаги можно найти в официальной документации Kubernetes.

      В этом руководстве вы будете выполнять основные шаги для защиты кластера DigitalOcean Kubernetes. Вы настроите защищенную локальную аутентификацию с сертификатами TLS/SSL, предоставите разрешения локальным пользователям с помощью механизмов управления доступом на базе ролей (RBAC), предоставите разрешения приложениям Kubernetes и развертыванию со служебными учетными записями, а также установите ограничения ресурсов при помощи контроллеров допуска ResourceQuota и LimitRange.

      Предварительные требования

      Чтобы выполнить это руководство, вам потребуется следующее:

      • Управляемый кластер DigitalOcean Kubernetes (DOKS) с тремя стандартными узлами, имеющими не менее чем 2 Гбайт ОЗУ и 1 виртуального процессора каждый. Подробные инструкции по созданию кластера DOKS можно найти в обучающем модуле Начало работы с Kubernetes. В этом обучающем руководстве используется версия DOKS 1.16.2-do.1.
      • Локальный клиент, настроенный для управления кластером DOKS, с файлом конфигурации кластера, загружаемым из панели управления DigitalOcean и сохраняемым как ~/.kube/config. Подробные инструкции по настройке удаленного управления DOKS можно найти в нашем руководстве «Подключение к кластеру DigitalOcean Kubernetes». В частности, вам потребуется следующее:
        • Интерфейс командной строки kubectl, установленный на локальном компьютере. Дополнительную информацию об установке и настройке kubectl можно найти в официальной документации. В этом обучающем руководстве будет использоваться версия kubectl 1.17.0-00.
        • Официальный инструмент командной строки DigitalOcean — doctl. Информацию о выполнении установки можно найти на странице doctl GitHub. В этом обучающем руководстве будет использоваться версия doctl 1.36.0.

      Шаг 1 — Активация аутентификации удаленного пользователя

      После завершения предварительных действий вы будете выполнять работу с одним суперпользователем Kubernetes, который аутентифицируется посредством заданного токена DigitalOcean. Однако обмен этими учетными данными не является эффективной практикой безопасности, поскольку данная учетная запись может вызвать масштабные и даже деструктивные изменения в вашем кластере. Чтобы снизить этот риск, вы можете настроить дополнительных пользователей, которые будут аутентифицироваться из соответствующих локальных клиентов.

      В этом разделе вы будете выполнять аутентификацию новых пользователей в удаленном кластере DOKS из локальных клиентов при помощи защищенных сертификатов SSL/TLS. Этот процесс будет проходить в три этапа: во-первых, вы создадите запросы подписи сертификатов (CSR) для каждого пользователя, а затем вы будете одобрять эти сертификаты непосредственно в кластере через kubectl. Наконец, вы создадите для каждого пользователя файл kubeconfig с соответствующими сертификатами. Расширенную информацию о дополнительных методах аутентификации при поддержке Kubernetes можно найти в документации по аутентификации Kubernetes.

      Создание запросов подписи сертификатов для новых пользователей

      Перед началом необходимо проверить подключение кластера DOKS к локальному компьютеру, настроенному с предварительными условиями:

      В зависимости от конфигурации, вы увидите примерно следующее:

      Output

      Kubernetes master is running at https://a6616782-5b7f-4381-9c0f-91d6004217c7.k8s.ondigitalocean.com CoreDNS is running at https://a6616782-5b7f-4381-9c0f-91d6004217c7.k8s.ondigitalocean.com/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

      Это означает, что вы подключены к кластеру DOKS.

      Далее создайте локальную папку для сертификатов клиента. Для целей настоящего руководства будет использоваться папка ~/certs для хранения всех сертификатов:

      В этом обучающем руководстве мы дадим новому пользователю с именем sammy доступ к кластеру. Вы можете изменить это на любого пользователя по вашему выбору. Используя библиотеку SSL и TLS OpenSSL, создайте новый закрытый ключ для вашего пользователя с помощью следующей команды:

      • openssl genrsa -out ~/certs/sammy.key 4096

      Флаг -out будет создавать выходной файл ~/certs/sammy.key, а 4096 устанавливает ключ длиной 4096 бит. Дополнительную информацию по OpenSSL можно найти в нашем руководстве по основам OpenSSL.

      Теперь создайте файл конфигурации запроса подписи сертификатов. Откройте следующий файл в текстовом редакторе (в этом обучающем модуле мы будем использовать nano):

      • nano ~/certs/sammy.csr.cnf

      Добавьте в файл sammy.csr.cnf следующее содержимое, чтобы задать в строке темы желаемое имя пользователя как обычное имя (CN) и группу в качестве организации (O):

      ~/certs/sammy.csr.cnf

      [ req ]
      default_bits = 2048
      prompt = no
      default_md = sha256
      distinguished_name = dn
      [ dn ]
      CN = sammy
      O = developers
      [ v3_ext ]
      authorityKeyIdentifier=keyid,issuer:always
      basicConstraints=CA:FALSE
      keyUsage=keyEncipherment,dataEncipherment
      extendedKeyUsage=serverAuth,clientAuth
      

      Файл конфигурации запроса подписи сертификатов содержит всю необходимую информацию, идентификационные данные пользователя и соответствующие параметры использования для данного пользователя. Последний аргумент extendedKeyUsage=serverAuth,clientAuth позволит пользователям аутентифицировать локальных клиентов с кластером DOKS при помощи сертификата после его подписания.

      Далее создайте запрос подписи сертификатов пользователя sammy:

      • openssl req -config ~/certs/sammy.csr.cnf -new -key ~/certs/sammy.key -nodes -out ~/certs/sammy.csr

      Параметр -config позволяет задать файл конфигурации для CSR, а сигналы -new, которые вы создаете — для нового CSR для ключа, указанного в -key.

      Вы можете проверить запрос подписи сертификатов посредством следующей команды:

      • openssl req -in ~/certs/sammy.csr -noout -text

      Здесь вы передаете в CSR с -in и используете -text, чтобы распечатать запрос сертификата в текстовом сообщении.

      В результатах будет показан запрос сертификатов, начало которого будет выглядеть следующим образом:

      Output

      Certificate Request: Data: Version: 1 (0x0) Subject: CN = sammy, O = developers Subject Public Key Info: Public Key Algorithm: rsaEncryption RSA Public-Key: (4096 bit) ...

      Повторите эту же процедуру для создания CSR для всех дополнительных пользователей. Когда все запросы подписи сертификатов будут сохранены в папке администратора ~/certs, выполните следующий шаг для их утверждения.

      Управление запросами подписи сертификатов с API Kubernetes

      Вы можете одобрять или отклонять сертификаты TLS, выданные для API Kubernetes, используя инструмент командной строки kubectl. Это дает возможность убедиться, что запрошенный доступ соответствует данному пользователю. В этом разделе мы направим запрос сертификатов для пользователя sammy и одобрим его.

      Чтобы направить CSR в кластер DOKS, используйте следующую команду:

      cat <<EOF | kubectl apply -f -
      apiVersion: certificates.k8s.io/v1beta1
      kind: CertificateSigningRequest
      metadata:
        name: sammy-authentication
      spec:
        groups:
        - system:authenticated
        request: $(cat ~/certs/sammy.csr | base64 | tr -d 'n')
        usages:
        - digital signature
        - key encipherment
        - server auth
        - client auth
      EOF
      

      Используя heredoc-синтаксис в Bash, эта команда использует cat для передачи запроса сертификатов в команду kubectl apply.

      Рассмотрим запрос сертификатов более подробно:

      • name: sammy-authentication создает идентификатор метаданных, в данном случае с именем sammy-authentication.
      • request: $(cat ~/certs/sammy.csr | bar64 | tr -d 'n' направляет запрос подписи сертификатов sammy.csr в кластер, кодифицированный как base64.
      • В server auth и client auth указывается предполагаемое использование сертификата. В данном случае цель — аутентификация пользователя.

      Результат будет выглядеть примерно следующим образом:

      Output

      certificatesigningrequest.certificates.k8s.io/sammy-authentication created

      Вы можете проверить состояние запроса подписи сертификатов с помощью команды:

      В зависимости от конфигурации кластера, вы увидите примерно следующее:

      Output

      NAME AGE REQUESTOR CONDITION sammy-authentication 37s your_DO_email Pending

      Далее одобрите CSR с помощью команды:

      • kubectl certificate approve sammy-authentication

      Вы получите сообщение, подтверждающее операцию:

      Output

      certificatesigningrequest.certificates.k8s.io/sammy-authentication approved

      Примечание: Как администратор, вы также можете отклонить CSR посредством команды ​​​kubectl certificate deny sammy-authentication. Дополнительную информацию по управлению сертификатами TLS можно найти в официальной документации Kubernetes.

      После утверждения CSR вы можете загрузить его на локальный компьютер с помощью следующей команды:

      • kubectl get csr sammy-authentication -o jsonpath='{.status.certificate}' | base64 --decode > ~/certs/sammy.crt

      Эта команда декодирует сертификат Base64 для надлежащего использования с помощью kubectl, а затем сохраняет его как ~/certs/sammy.crt.

      С подписанным сертификатом sammy вы можете создавать пользовательский файл kubeconfig.

      Создание удаленных пользователей Kubeconfig

      Далее вы создадите специальный файл kubeconfig для пользователя sammy. Это позволит лучше контролировать возможность доступа пользователя к вашему кластеру.

      Первый шаг в создании нового kubeconfig — создание копии текущего файла kubeconfig. Для целей настоящего руководства новый файл kubeconfig будет иметь имя config-sammy:

      • cp ~/.kube/config ~/.kube/config-sammy

      Далее, измените новый файл:

      • nano ~/.kube/config-sammy

      Сохраните первые восемь строк этого файла, поскольку они содержат необходимую информацию для подключения SSL/TLS к кластеру. Далее, начиная с параметра user, замените текст следующими выделенными строками, чтобы файл выглядел примерно следующим образом:

      config-sammy

      apiVersion: v1
      clusters:
      - cluster:
          certificate-authority-data: certificate_data
        name: do-nyc1-do-cluster
      contexts:
      - context:
          cluster: do-nyc1-do-cluster
          user: sammy
        name: do-nyc1-do-cluster
      current-context: do-nyc1-do-cluster
      kind: Config
      preferences: {}
      users:
      - name: sammy
        user:
          client-certificate: /home/your_local_user/certs/sammy.crt
          client-key: /home/your_local_user/certs/sammy.key
      

      Примечание: для client-certificate и client-key используйте абсолютный путь к соответствующему местоположению их сертификатов. В противном случае kubectl выдаст ошибку.

      Сохраните и закройте файл.

      Вы можете протестировать новое подключение пользователя с помощью kubectl cluster-info:

      • kubectl --kubeconfig=/home/your_local_user/.kube/config-sammy cluster-info

      Ошибка будет выглядеть примерно так:

      Output

      To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'. Error from server (Forbidden): services is forbidden: User "sammy" cannot list resource "services" in API group "" in the namespace "kube-system"

      Эта ошибка ожидается, поскольку пользователь sammy еще не имеет разрешения на запись каких-либо ресурсов в кластер. На следующем шаге будет рассмотрено предоставление разрешений пользователям. В результате вы увидите, что подключение SSL/TLS прошло успешно, а данные аутентификации sammy приняты в Kubernetes API.

      Шаг 2 — Авторизация пользователй через систему контроля доступа на основе ролей (RBAC)

      После аутентификации пользователя API определяет свои разрешения с помощью встроенной модели Kubernetes по контролю доступа на основе ролей (RBAC) RBAC — эффективный способ ограничения прав пользователя на основании его роли. С точки зрения безопасности, RBAC разрешает устанавливать детальные разрешения, чтобы ограничить возможность доступа пользователей к чувствительным данным или выполнения команд уровня суперпользователя. Более подробная информация о ролях пользователей содержится в документации Kubernetes RBAC.

      На этом шаге вы будете использовать kubectl для назначения заранее определенной роли edit пользователю sammy в области имен default. В производственной среде можно использовать настраиваемые роли и/или привязки ролей.

      Предоставление разрешений

      В Kubernetes предоставление разрешений означает назначение требуемой роли пользователю. Назначьте разрешения edit пользователю sammy в пространстве имен default с помощью следующей команды:

      • kubectl create rolebinding sammy-edit-role --clusterrole=edit --user=sammy --namespace=default

      Результат будет выглядеть примерно следующим образом:

      Output

      rolebinding.rbac.authorization.k8s.io/sammy-edit-role created

      Рассмотрим эту команду более подробно:

      • create rolebinding sammy-edit-role создает новую привязку ролей, в данном случае с именем sammy-edit-role.
      • --clusterrole=edit назначает заранее определенную роль edit в глобальном масштабе (роль кластера).
      • --user=sammy указывает, к какому пользователю следует привязать роль.
      • --namespace=default предоставляет пользователю разрешения ролей в пределах указанного пространства имен, в данном случае default.

      Далее проверьте разрешения пользователя посредством указания подов в пространстве имен default. Если ошибки не отображаются, то это означает, что авторизация RBAC работает так, как ожидается.

      • kubectl --kubeconfig=/home/your_local_user/.kube/config-sammy auth can-i get pods

      Результат будет выглядеть следующим образом:

      Output

      yes

      Теперь, когда вы назначили разрешения пользователю sammy, вы можете (в качестве упражнения) отозвать эти разрешения в следующем разделе.

      Отзыв разрешений

      Чтобы отозвать разрешения в Kubernetes, необходимо удалить привязку ролей пользователя.

      В этом обучающем модуле мы удалим роль edit пользователя sammy посредством следующей команды:

      • kubectl delete rolebinding sammy-edit-role

      Результат будет выглядеть следующим образом:

      Output

      rolebinding.rbac.authorization.k8s.io "sammy-edit-role" deleted

      Убедитесь, что разрешения пользователя были правильно отозваны посредством указания подов в пространстве имен default:

      • kubectl --kubeconfig=/home/localuser/.kube/config-sammy --namespace=default get pods

      Вы получите следующую ошибку:

      Output

      Error from server (Forbidden): pods is forbidden: User "sammy" cannot list resource "pods" in API group "" in the namespace "default"

      Это показывает, что авторизация отозвана.

      С точки зрения безопасности, модель авторизации Kubernetes дает администраторам кластеров возможность изменять права пользователей по запросу, по мере необходимости. Более того, контроль доступа на основе ролей не ограничивается физическим пользователем; вы можете также предоставлять разрешения кластерным службам и удалять их — это будет рассмотрено в следующем разделе.

      Дополнительную информацию об авторизации RBAC и создании настраиваемых ролей можно найти в официальной документации.

      Шаг 3 — Управление разрешениями приложений со служебными учетными записями

      Как упоминалось в предыдущем разделе, механизмы авторизации RBAC распространяются не только на физических пользователей. Виртуальные пользователи кластера — например, приложения, службы и процессы, запущенные внутри подов — аутентифицируются на сервере API при помощи того, что в Kubernetes называется «служебные учетные записи». Когда под создается в пространстве имен, вы можете либо позволить ему использовать служебную учетную запись default, либо определить служебную учетную запись по своему выбору. Благодаря способности назначать отдельные служебные учетные записи приложениям и процессам администраторы получают возможность предоставлять или отзывать разрешения, по мере необходимости. Кроме того, назначение конкретных служебных учетных записей для приложений, критичных для производства, считается наилучшей практикой безопасности. Поскольку учетные записи используются для аутентификации и, т.о., для проверки авторизации RBAC, администраторы кластеров могут устранять угрозы безопасности посредством изменения прав доступа к служебным учетным записям и изоляции процесса, угрожающего безопасности.

      Чтобы продемонстрировать служебные учетные записи, в этом обучающем руководстве будет использоваться веб-сервер Nginx как образец приложения.

      Перед назначением конкретной служебной учетной записи для вашего приложения ее необходимо создать. Создайте новую учетную запись с именем nginx-sa в пространстве имен default:

      • kubectl create sa nginx-sa

      Вы получите следующее:

      Output

      serviceaccount/nginx-sa created

      Убедитесь, что служебная учетная запись создана посредством следующей команды:

      В результате вы получите список ваших служебных учетных записей:

      Output

      NAME SECRETS AGE default 1 22h nginx-sa 1 80s

      Теперь вы назначите эту роль для служебной учетной записи nginx-sa. В данном примере необходимо предоставить для nginx-sa те же разрешения, что и для пользователя sammy:

      • kubectl create rolebinding nginx-sa-edit
      • --clusterrole=edit
      • --serviceaccount=default:nginx-sa
      • --namespace=default

      В результате будет получено следующее:

      Output

      rolebinding.rbac.authorization.k8s.io/nginx-sa-edit created

      Эта команда использует такой же формат, как для пользователя sammy, кроме флага --serviceaccount=default:nginx-sa, где вы назначаете служебную учетную запись nginx-sa в области имен default.

      Убедитесь, что привязка ролей успешно выполнена при помощи этой команды:

      Результат будет выглядеть следующим образом:

      Output

      NAME AGE nginx-sa-edit 23s

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

      В этом обучающем модуле в качестве образца приложения мы будем использовать под nginx. Создайте новый под и укажите служебную учетную запись nginx-sa с помощью следующей команды:

      • kubectl run nginx --image=nginx --port 80 --serviceaccount="nginx-sa"

      Первая часть команды создает новый под на веб-сервере nginx на порту :80, а последняя часть --serviceaccount="nginx-sa" "nginx-sa" показывает, что данный под должен использовать служебную учетную запись nginx-sa, а не служебную учетную запись default.

      Результат будет выглядеть примерно следующим образом:

      Output

      deployment.apps/nginx created

      Убедитесь, что новое приложение использует служебную учетную запись с помощью kubectl describe:

      • kubectl describe deployment nginx

      В результате будет выведено подробное описание параметров развертывания. В разделе Pod Template вы увидите примерно следующее:

      Output

      ... Pod Template: Labels: run=nginx Service Account: nginx-sa ...

      В этом разделе мы создали служебную учетную запись nginx-sa в пространстве имен default и назначили ее для веб-сервера nginx. Теперь вы можете контролировать разрешения nginx в реальном времени посредством изменения его роли, по мере необходимости. Также вы можете группировать приложения посредством назначения одной служебной учетной записи для каждого из них и последующего внесения массовых изменений в разрешения. Наконец, вы можете изолировать критические приложения посредством назначения для них уникальной служебной учетной записи.

      В целом, смысл назначения ролей для ваших приложений/развертываний состоит в отладке разрешений. В реальной производственной среде может быть установлено несколько развертываний, требующих различных разрешений — от «только для чтения» до полных административных прав. Использование RBAC обеспечивает гибкость для ограничения доступа к кластеру, по мере необходимости.

      Далее вы настроите контроллеры допуска для контроля ресурсов и защиты от атак, вызывающих нехватку ресурсов.

      Шаг 4 — Настройка контроллеров допуска

      Контроллеры допуска Kubernetes — это опциональные плагины, компилируемые в двоичный файл kube-apiserver для расширения возможностей безопасности. Контроллеры допуска перехватывают запросы после прохождения этапа аутентификации и авторизации. После перехвата запроса контроллеры допуска выполняют указанный код непосредственно перед применением запроса.

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

      На данном шаге мы будем использовать контроллеры допуска ResourceQuota и LimitRange допуска для защиты кластера посредством изменения запросов, которые могут способствовать возникновению нехватки ресурсов или DoS-атак. Контроллер допуска ResourceQuota позволяет администраторам ограничивать вычислительные ресурсы, ресурсы хранения, а также количество любых объектов в пространстве имен, а контроллер допуска LimitRange ограничивает количество ресурсов, используемых контейнерами. При совместном использовании этих двух контроллеров допуска ваш кластер будет защищен от атак, приводящих к недоступности ресурсов.

      Чтобы продемонстрировать работу ResourceQuota, зададим несколько ограничений в пространстве имен default. Начнем с создания нового файла объекта ResourceQuota:

      • nano resource-quota-default.yaml

      Добавьте следующее определение объекта для определения ограничений использования ресурсов в пространстве имен default. Вы можете изменить значения, если потребуется, в зависимости от физических ресурсов ваших узлов:

      resource-quota-default.yaml

      apiVersion: v1
      kind: ResourceQuota
      metadata:
        name: resource-quota-default
      spec:
        hard:
          pods: "2"
          requests.cpu: "500m"
          requests.memory: 1Gi
          limits.cpu: "1000m"
          limits.memory: 2Gi
          configmaps: "5"
          persistentvolumeclaims: "2"
          replicationcontrollers: "10"
          secrets: "3"
          services: "4"
          services.loadbalancers: "2"
      

      В этом определении используется ключевое слово hard для определения жестких ограничений — например, максимальное количество подов, карт configmaps, объектов PersistentVolumeClaims, контроллеров ReplicationControllers, объектов secrets, сервисов services и типов loadbalancers. В результате также устанавливаются ограничения вычислительных ресурсов:

      • requests.cpu, устанавливающий максимальное значение ЦП запросов в milliCPU, или одну тысячную ядра ЦП.
      • requests.memory, устанавливающий максимальное значение памяти запросов в байтах.
      • limits.cpu, устанавливающий максимальное значение ЦП предельных значений в milliCPU.
      • limits.memory, устанавливающий максимальное значение памяти предельных значений в байтах.

      Сохраните и закройте файл.

      Теперь создайте объект в пространстве имен при помощи следующей команды:

      • kubectl create -f resource-quota-default.yaml --namespace=default

      В результате вы получите следующее:

      Output

      resourcequota/resource-quota-default created

      Обратите внимание, что вы используете флаг -f для указания в Kubernetes места расположения файла ResourceQuota и флаг --namespace для указания, в каком пространстве будет обновлено пространство имен.

      После создания объекта ваш файл ResourceQuota будет активен. Вы можете проверить квоты пространств имен default с помощью describe quota:

      • kubectl describe quota --namespace=default

      Результат будет выглядеть примерно так, с жесткими ограничениями, которые вы задали в файле resource-quota-default.yaml:

      Output

      Name: resource-quota-default Namespace: default Resource Used Hard -------- ---- ---- configmaps 0 5 limits.cpu 0 1 limits.memory 0 2Gi persistentvolumeclaims 0 2 pods 1 2 replicationcontrollers 0 10 requests.cpu 0 500m requests.memory 0 1Gi secrets 2 3 services 1 4 services.loadbalancers 0 2

      ResourceQuotas выражаются в абсолютных единицах, поэтому добавление дополнительных узлов не будет автоматически увеличивать значения, определенные здесь. При добавлении дополнительных узлов вам потребуется вручную изменить эти значения для пропорционального распределения ресурсов. ResourceQuotas могут изменяться так часто, как вам потребуется, но они не могут удаляться, за исключением случаев, когда удаляется все пространство имен.

      Если вам потребуется изменить определенный файл ResourceQuota, обновите соответствующий файл .yaml и примените изменения с помощью следующей команды:

      • kubectl apply -f resource-quota-default.yaml --namespace=default

      Дополнительную информацию по контроллеру допуска ResourceQuota можно найти в официальной документации.

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

      Как и ранее, необходимо начать с создания файла объекта:

      • nano limit-range-default.yaml

      Теперь вы можете использовать объект LimitRange для ограничения использования ресурсов, по мере необходимости. Добавьте следующее содержимое в качестве образца типичного случая использования:

      limit-ranges-default.yaml

      apiVersion: v1
      kind: LimitRange
      metadata:
        name: limit-range-default
      spec:
        limits:
        - max:
            cpu: "400m"
            memory: "1Gi"
          min:
            cpu: "100m"
            memory: "100Mi"
          default:
            cpu: "250m"
            memory: "800Mi"
          defaultRequest:
            cpu: "150m"
            memory: "256Mi"
          type: Container
      

      Шаблонные значения, используемые в limit-ranges-default.yaml , ограничивают память контейнера максимальным значением 1Gi и ограничивают загрузку ЦП максимальным значением 400m — метрический эквивалент 400 milliCPU в Kubernetes, что означает, что контейнер ограничен использованием почти половины его ядра.

      Далее, разверните объект для сервера API с помощью следующей команды:

      • kubectl create -f limit-range-default.yaml --namespace=default

      Результат будет выглядеть следующим образом:

      Output

      limitrange/limit-range-default created

      Теперь вы можете проверить новые пределы с помощью следующей команды:

      • kubectl describe limits --namespace=default

      Результат будет выглядеть примерно следующим образом:

      Output

      Name: limit-range-default Namespace: default Type Resource Min Max Default Request Default Limit Max Limit/Request Ratio ---- -------- --- --- --------------- ------------- ----------------------- Container cpu 100m 400m 150m 250m - Container memory 100Mi 1Gi 256Mi 800Mi -

      Чтобы увидеть LimitRanger в действии, разверните стандартный контейнер nginx с помощью следующей команды:

      • kubectl run nginx --image=nginx --port=80 --restart=Never

      Результат будет выглядеть следующим образом:

      Output

      pod/nginx created

      Проверьте, как контроллер допуска изменил контейнер, с помощью следующей команды:

      • kubectl get pod nginx -o yaml

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

      Output

      ... spec: containers: - image: nginx imagePullPolicy: IfNotPresent name: nginx ports: - containerPort: 80 protocol: TCP resources: limits: cpu: 250m memory: 800Mi requests: cpu: 150m memory: 256Mi ...

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

      На данном шаге вы использовали контроллеры допуска ResourceQuota и LimitRange для защиты от нападений злоумышленников на ресурсы вашего кластера. Дополнительную информацию по контроллеру допуска LimitRange можно найти в официальной документации.

      Заключение

      В этом руководстве вы настроили базовый шаблон безопасности Kubernetes. В результате были установлены: аутентификация и авторизация, привилегии приложений и защита кластерных ресурсов. Все предложения, рассмотренные в этом модуле, дадут вам твердую основу для развертывания производственного кластера Kubernetes. Теперь вы можете начинать проработку отдельных аспектов вашего кластера, в зависимости от вашего сценария.

      Если вы хотите узнать больше о Kubernetes, ознакомьтесь с нашей страницей ресурсов Kubernetes или посмотрите наш самостоятельный обучающий модуль «Kubernetes для разработчиков широкого профиля».



      Source link

      Настройка кластера Galera с MySQL на серверах Ubuntu 18.04


      Автор выбрал фонд Free and Open Source Fund для получения пожертвования в рамках программы Write for DOnations.

      Введение

      Кластеризация повышает уровень доступности базы данных за счет распространения изменений на разные серверы. В случае выхода из строя одного из экземпляров другие остаются доступными для работы.

      Существует две стандартные конфигурации кластеров: активный-пассивный и активный-активный. В кластерах вида «активный-пассивный» все операции записи выполняются на одном активном сервере, а затем копируются на один или несколько пассивных серверов, которые активируются только в случае неисправности активного сервера. Некоторые кластеры вида «активный-пассивный» также поддерживают выполнение операций SELECT на пассивных узлах. В кластерах вида «активный-активный» каждый узел используется для чтения и записи, и изменения на одном узле воспроизводятся на всех остальных узлах.

      MySQL — популярная реляционная СУБД с открытым исходным кодом для баз данных SQL. Galera — решение кластеризации баз данных, которое позволяет настраивать кластеры с несколькими главными узлами, используя синхронную репликацию. Galera автоматически обрабатывает размещение данных на разных узлах, позволяя при этом отправлять запросы чтения и записи на любой узел кластера. Дополнительную информацию о Galera можно найти на странице официальной документации.

      В этом обучающем руководстве мы настроим кластер MySQL Galera вида «активный-активный». Для демонстрационных целей мы выполним настройку и тестирование трех дроплетов Ubuntu 18.04, которые будут выступать в качестве узлов кластера. Такое количество узлов представляет собой наименьший настраиваемый кластер.

      Предварительные требования

      Для этого вам потребуется учетная запись DigitalOcean, а также следующее:

      • Три дроплета Ubuntu 18.04 с включенной поддержкой частных сетей, на каждом имеется пользователь sudo без привилегий root.

      Хотя данное обучающее руководство написано для дроплетов DigitalOcean и протестировано с ними, многие из шагов также применимы к другим серверам с включенными частными сетями.

      Шаг 1 — Добавление репозиториев MySQL на все серверы

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

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

      В этом обучающем руководстве мы будем использовать MySQL версии 5.7. Вначале мы добавим внешний репозиторий Ubuntu, обслуживаемый проектом Galera, на все три ваших сервера.

      После обновления репозиториев на всех трех серверах мы будем готовы выполнить установку MySQL вместе с Galera.

      Вначале добавьте на все три сервера ключ репозитория Galera с помощью команды apt-key, которую диспетчер пакетов APT использует для проверки подлинности пакета:

      • sudo apt-key adv --keyserver keyserver.ubuntu.com --recv BC19DDBA

      Через несколько секунд вы увидите следующее:

      Output

      Executing: /tmp/apt-key-gpghome.RG5cTZjQo0/gpg.1.sh --keyserver keyserver.ubuntu.com --recv BC19DDBA gpg: key D669017EBC19DDBA: public key "Codership Oy <[email protected]>" imported gpg: Total number processed: 1 gpg: imported: 1

      Когда в базе данных каждого сервера будет доверенный ключ, вы сможете добавить репозитории. Для этог осоздайте новый файл с именем galera.list в директории /etc/apt/sources.list.d/ на каждом сервере:

      • sudo nano /etc/apt/sources.list.d/galera.list

      Добавьте в текстовом редакторе следующие строки, которые сделают соответствующие репозитории доступными для диспетчера пакетов APT:

      /etc/apt/sources.list.d/galera.list

      deb http://releases.galeracluster.com/mysql-wsrep-5.7/ubuntu bionic main
      deb http://releases.galeracluster.com/galera-3/ubuntu bionic main
      

      Сохраните и закройте файлы на каждом сервере (нажмите CTRL + X, Y, а затем ENTER).

      Теперь репозитории Codership доступны для всех трех ваших серверов. Однако важно указать apt приоритет репозиториев Codership перед другими репозиториями. Это необходимо для установки последних исправлений программного обеспечения, необходимого для создания кластера Galera. Для этого следует создать новый файл с именем galera.pref в директории /etc/apt/preferences.d/ на каждом сервере:

      • sudo nano /etc/apt/preferences.d/galera.pref

      Добавьте в текстовом редакторе следующие строки:

      /etc/apt/preferences.d/galera.pref

      # Prefer Codership repository
      Package: *
      Pin: origin releases.galeracluster.com
      Pin-Priority: 1001
      

      Сохраните и закройте файл, а затем запустите следующую команду на каждом сервере, чтобы добавить манифесты пакетов из новых репозиториев:

      Мы успешно добавили репозиторий пакетов на все три сервера и можем переходить к установке MySQL.

      Шаг 2 — Установка MySQL на все серверы

      На этом шаге мы выполним установку пакета MySQL на все три сервера.

      Запустите следующую команду на всех трех серверах, чтобы установить версию MySQL с исправлениями для работы с Galera, а также сам пакет Galera.

      • sudo apt install galera-3 mysql-wsrep-5.7

      Подтвердите установку в соответствующем диалоге. Введите Y для выполнения установки. Во время установки вам будет предложено задать пароль администратора MySQL. Выберите надежный пароль и нажмите ENTER, чтобы продолжить.

      После установки MySQL мы отключим профиль AppArmor по умолчанию, чтобы обеспечить надлежащую работу Galera. Данное требование содержится в официальной документации по Galera. AppArmor — это модуль ядра для Linux, обеспечивающий функции контроля доступа для служб через профили безопасности.

      Для отключения AppArmor нужно выполнить на каждом сервере следующую команду:

      • sudo ln -s /etc/apparmor.d/usr.sbin.mysqld /etc/apparmor.d/disable/

      Эта команда добавляет символьную ссылку профиля MySQL в директорию disable, которая отключает профиль при загрузке.

      Затем запустите следующую команду для удаления определения MySQL, которое уже загружено в ядро.

      • sudo apparmor_parser -R /etc/apparmor.d/usr.sbin.mysqld

      После установки MySQL и отключения профиля AppArmor на первом сервере следует повторить эти шаги для двух других серверов.

      Мы успешно установили MySQL на каждом из трех серверов и теперь можем перейти к настройке конфигурации.

      Шаг 3 — Настройка конфигурации первого узла

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

      По умолчанию СУБД MySQL настроена для проверки директории /etc/mysql/conf.d для получения дополнительных параметров конфигурации из файлов с расширением .cnf. Создайте в этой директории на первом сервере файл со всеми директивами для вашего кластера:

      • sudo nano /etc/mysql/conf.d/galera.cnf

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

      /etc/mysql/conf.d/galera.cnf

      [mysqld]
      binlog_format=ROW
      default-storage-engine=innodb
      innodb_autoinc_lock_mode=2
      bind-address=0.0.0.0
      
      # Galera Provider Configuration
      wsrep_on=ON
      wsrep_provider=/usr/lib/galera/libgalera_smm.so
      
      # Galera Cluster Configuration
      wsrep_cluster_name="test_cluster"
      wsrep_cluster_address="gcomm://First_Node_IP,Second_Node_IP,Third_Node_IP"
      
      # Galera Synchronization Configuration
      wsrep_sst_method=rsync
      
      # Galera Node Configuration
      wsrep_node_address="This_Node_IP"
      wsrep_node_name="This_Node_Name"
      
      • Первый раздел изменяет или подтверждает параметры MySQL, обеспечивающие правильную работу кластера. Например, Galera не работает с MyISAM и другими системами хранения без транзакций, а mysqld не следует привязывать к IP-адресу для узла localhost. Дополнительную информацию о настройках можно найти на странице по конфигурации системы Galera Cluster.
      • Раздел «Galera Provider Configuration» настраивает компоненты MySQL, которые обеспечивают API репликации WriteSet. В данном случае это означает Galera, поскольку Galera является поставщиком wsrep (репликации WriteSet). Мы зададим общие параметры настройки начальной среды репликации. Для этого не требуется персонализация, но вы можете узнать дополнительную информацию о параметрах конфигурации Galera в документации.
      • Раздел «Galera Cluster Configuration» определяет кластер, идентифицируя узлы кластера по IP-адресу или полному доменному имени и создавая имя кластера, чтобы участники присоединялись к правильной группе. Вы можете изменить значение параметра wsrep_cluster_name на любое другое имя или оставить имя test_cluster, но для параметра wsrep_cluster_address нужно указать IP-адреса трех ваших серверов.
      • В разделе Galera Synchronization Configuration определяются взаимодействие и синхронизация участников кластера. Он используется только для передачи состояния при включении узлов. Для первоначальной настройки мы используем rsync, поскольку это стандартный вариант, который подходит для наших целей.
      • В разделе Galera Node Configuration уточняются IP-адрес и имя текущего сервера. Это полезно для диагностики проблем в журналах и для использования нескольких видов ссылок на каждый сервер. Значение параметра wsrep_node_address должно соответствовать адресу текущего узла, но вы можете выбрать любое удобное название, чтобы вам было удобнее идентифицировать узел в файлах журнала.

      Когда вы будете довольны файлом конфигурации кластера, скопируйте содержимое в буфер обмена, а затем сохраните и закройте файл.

      Мы успешно настроили наш первый узел и теперь можем перейти к настройке конфигурации остальных узлов.

      Шаг 4 — Настройка остальных узлов

      На этом шаге мы выполним настройку конфигурации остальных двух узлов. Откройте файл конфигурации на втором узле:

      • sudo nano /etc/mysql/conf.d/galera.cnf

      Вставьте конфигурацию, скопированную из первого узла, а затем измените раздел Galera Node Configuration так, чтобы в нем использовались IP-адрес или доменное имя узла, который вы настраиваете. Затем измените название узла так, чтобы вам было легко распознавать его в файлах журнала:

      /etc/mysql/conf.d/galera.cnf

      . . .
      # Galera Node Configuration
      wsrep_node_address="This_Node_IP"
      wsrep_node_name="This_Node_Name"
      . . .
      
      

      Сохраните и закройте файл.

      После выполнения этих шагов повторите их для третьего узла.

      Мы уже почти запустили кластер, но прежде чем мы сделаем это, нам нужно убедиться, что на брандмауэре открыты соответствующие порты.

      Шаг 5 — Открытие портов брандмауэра на каждом сервере

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

      В данном случае разрешен только трафик SSH:

      Output

      Status: active To Action From -- ------ ---- OpenSSH ALLOW Anywhere OpenSSH (v6) ALLOW Anywhere (v6)

      Поскольку разрешен только трафик SSH, нам нужно добавить правила для трафика MySQL и Galera. Если мы попытаемся запустить кластер, эти правила брандмауэра помешают нам сделать это.

      Galera может использовать четыре порта:

      • 3306 для подключения клиентов MySQL и передачи снимков состояния с использованием метода mysqldump.
      • 4567 для трафика репликации кластера Galera. При репликации мультивещания на этом порту используются транспорт UDP и TCP.
      • 4568 для инкрементной передачи состояния.
      • 4444 для передачи всех других снимков состояния.

      В данном примере при настройке мы открываем все четыре порта. После проверки работы репликации нужно закрыть неиспользуемые порты и ограничить трафик серверами в составе кластера.

      Откройте порты с помощью следующих команд:

      • sudo ufw allow 3306,4567,4568,4444/tcp
      • sudo ufw allow 4567/udp

      Примечание. Если этого требуют другие приложения на ваших серверах, доступ можно ограничить сразу же. В этом поможет обучающее руководство Основы UFW: распространенные правила и команды брандмауэра.

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

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

      Шаг 6 — Запуск кластера

      На этом шаге мы запустим наш кластер MySQL Galera. Вначале нужно включить службу MySQL systemd, чтобы обеспечить автоматический запуск MySQL при перезагрузке сервера.

      Активация MySQL для запуска при загрузке на всех трех серверах

      Используйте следующую команду для включения службы MySQL systemd на всех трех серверах:

      • sudo systemctl enable mysql

      Вы увидите экран следующего вида, подтверждающий добавление службы в список автозагрузки:

      Output

      Created symlink /etc/systemd/system/multi-user.target.wants/mysql.service → /lib/systemd/system/mysql.service.

      Мы активировали автозагрузку mysql на всех трех серверах и теперь можем запускать кластер.

      Включение первого узла

      Для запуска первого узла нам потребуется специальный скрипт. При нашей конфигурации кластера каждый активируемый узел будет пытаться подключиться хотя бы к одному узлу, указанному в файле galera.cnf, для получения его начального состояния. Без использования скрипта mysqld_bootstrap, который позволяет systemd передавать параметр --wsrep-new-cluster, обычная команда systemctl start mysql не будет выполняться, поскольку первый узел не сможет подключиться ни к какому другому узлу.

      Запустите на первом сервере следующую команду:

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

      • mysql -u root -p -e "SHOW STATUS LIKE 'wsrep_cluster_size'"

      После ввода пароля вы увидите следующий экран, показывающий, что в кластере содержится один узел:

      Output

      +--------------------+-------+ | Variable_name | Value | +--------------------+-------+ | wsrep_cluster_size | 1 | +--------------------+-------+

      На остальных узлах можно запускать mysql обычным образом. Они будут искать активных участников кластера из списка и подключатся к кластеру, когда найдут их.

      Включение второго узла

      Теперь мы можем запустить второй узел. Запустите mysql:

      • sudo systemctl start mysql

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

      • mysql -u root -p -e "SHOW STATUS LIKE 'wsrep_cluster_size'"

      Вы увидите следующий экран, показывающий, что к кластеру подключился второй узел и что теперь в кластере содержится два узла.

      Output

      +--------------------+-------+ | Variable_name | Value | +--------------------+-------+ | wsrep_cluster_size | 2 | +--------------------+-------+

      Включение третьего узла

      Настало время запустить третий узел. Запустите mysql:

      • sudo systemctl start mysql

      Запустите следующую команду для определения размера кластера:

      • mysql -u root -p -e "SHOW STATUS LIKE 'wsrep_cluster_size'"

      Вы увидите следующий экран, показывающий, что к кластеру подключился третий узел и что всего кластер содержит три узла.

      Output

      +--------------------+-------+ | Variable_name | Value | +--------------------+-------+ | wsrep_cluster_size | 3 | +--------------------+-------+

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

      Шаг 7 — Тестирование репликации

      Теперь наш кластер может выполнять репликацию любых узлов в режиме «активный-активный». На этом шаге мы проверим, работает ли репликация ожидаемым образом.

      Запись в первый узел

      Вначале мы внесем изменения в базу данных на первом узле. Следующие команды создадут базу данных playground и таблицу equipment внутри этой базы данных.

      • mysql -u root -p -e 'CREATE DATABASE playground;
      • CREATE TABLE playground.equipment ( id INT NOT NULL AUTO_INCREMENT, type VARCHAR(50), quant INT, color VARCHAR(25), PRIMARY KEY(id));
      • INSERT INTO playground.equipment (type, quant, color) VALUES ("slide", 2, "blue");'

      В показанной выше команде выражение CREATE DATABASE создает базу данных с именем playground. Выражение CREATE создает таблицу с именем equipment в базе данных playground. Эта таблица содержит столбец идентификации id с инкрементным увеличением идентификатора, а также другие столбцы. Столбец type, столбец quant и столбец color определяют тип, количество и цвет оборудования. Выражение INSERT вставляет запись с типом slide, количеством 2 и цветом blue.

      Теперь наша таблица содержит одно значение.

      Чтение и запись на втором узле

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

      • mysql -u root -p -e 'SELECT * FROM playground.equipment;'

      Если репликация работает, введенные на первом узле данные будут видны здесь, на втором узле:

      Output

      +----+-------+-------+-------+ | id | type | quant | color | +----+-------+-------+-------+ | 1 | slide | 2 | blue | +----+-------+-------+-------+

      Выполните запись данных в кластер с этого узла:

      • mysql -u root -p -e 'INSERT INTO playground.equipment (type, quant, color) VALUES ("swing", 10, "yellow");'

      Чтение и запись на третьем узле

      На третьем узле вы можете снова отправить запрос в таблицу для чтения всех этих данных:

      • mysql -u root -p -e 'SELECT * FROM playground.equipment;'

      Вы увидите следующий экран, содержащий две строки:

      Output

      +----+-------+-------+--------+ | id | type | quant | color | +----+-------+-------+--------+ | 1 | slide | 2 | blue | | 2 | swing | 10 | yellow | +----+-------+-------+--------+

      На этом узле вы можете добавить в таблицу еще одно значение:

      • mysql -u root -p -e 'INSERT INTO playground.equipment (type, quant, color) VALUES ("seesaw", 3, "green");'

      Чтение на первом узле

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

      • mysql -u root -p -e 'SELECT * FROM playground.equipment;'

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

      Output

      +----+--------+-------+--------+ | id | type | quant | color | +----+--------+-------+--------+ | 1 | slide | 2 | blue | | 2 | swing | 10 | yellow | | 3 | seesaw | 3 | green | +----+--------+-------+--------+

      Мы подтвердили возможность записи на всех узлах и успешное выполнение репликации.

      Заключение

      Теперь у нас имеется работающий и настроенный тестовый кластер Galera с тремя узлами. Если вы планируете использовать кластер Galera в рабочей среде, рекомендуем начать как минимум с пяти узлов.

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

      Возможно вас также заинтересуют другие решения кластеризации для MySQL, и в этом случае мы рекомендуем ознакомиться с обучающим руководством Создание кластера MySQL с несколькими узлами в Ubuntu 18.04. Если вам требуется решение управляемой базы данных, ознакомьтесь с документацией по управляемым базам данных DigitalOcean.



      Source link