Автор выбрал фонд 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.
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-адреса ваших серверов:
[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 настроен корректно:
Outputetcd2 | CHANGED | rc=0 >>
etcd2
etcd3 | CHANGED | rc=0 >>
etcd3
etcd1 | CHANGED | rc=0 >>
etcd1
Каждая команда, которую запускает Ansible, называется задачей. Использование ansible
в командной строке для запуска задач называется запуском ситуативных команд. Преимущество ситуативных команд состоит в том, что они быстрые и требуют минимальной настройки, а недостаток состоит в том, что они запускаются вручную, а значит не могут быть добавлены в систему контроля версий, например Git.
Небольшим улучшением может быть запись скрипта оболочки и запуск команд с помощью модуля script
Ansible. Это позволит нам записать этапы конфигурации, которые мы передали в систему контроля версий. Однако скрипты оболочки имеют императивный характер, что означает, что нам нужно определить команды для запуска («как») для приведения системы в желаемое состояние. Ansible, с другой стороны, выступает за декларативный подход, где мы определяем «какое» состояние сервера нам нужно внутри файлов конфигурации, а Ansible отвечает за приведение сервера в это желаемое состояние.
Декларативный метод является предпочтительным, поскольку назначение файла конфигурации передается немедленно, что означает, что его легче понять и поддерживать. Также подобный подход возлагает ответственность за обработку пограничных случаев на Ansible, а не на администратора, избавляя от большого объема работы.
Теперь, когда вы настроили узел управления Ansible для связи с управляемыми узлами, в следующем шаге мы познакомим вас с плейбуками Ansible, которые позволяют определять задачи декларативным образом.
На этом шаге мы воспроизведем то, что было сделано в шаге 1, т. е. выведем имена хостов управляемых узлов, но вместо запуска ситуативных задач мы определим каждую задачу декларативно в виде плейбука Ansible и запустим ее. Цель этого шага — продемонстрировать, как работают плейбуки Ansible. В последующих шагах мы будем выполнять гораздо более серьезные задачи с помощью плейбуков.
Внутри каталога проекта создайте новый файл с именем playbook.yaml
с помощью вашего редактора:
- nano $HOME/playground/etcd-ansible/playbook.yaml
Внутри 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
Вы получите следующий вывод, что означает, что ваш плейбук работает корректно:
OutputPLAY [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.
На этом шаге мы покажем вам команды для ручной установки 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
на следующее содержимое:
- 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
:
- ssh root@etcd1_public_ip
etcd1_public_ip
— это публичные IP-адреса сервера с именем etcd1. После получения доступа через SSH запустите etcd --version
для вывода версии установленного etcd:
- etcd --version
Вы получите вывод, соответствующий представленному ниже, что означает, что бинарный файл etcd
успешно установлен:
Outputetcd Version: 3.3.13
Git SHA: 98d3084
Go Version: go1.10.8
Go OS/Arch: linux/amd64
Чтобы подтвердить успешную установку etcdctl
, запустите etcdctl version
:
- etcdctl version
Вы увидите примерно следующий результат:
Outputetcdctl version: 3.3.13
API version: 3.3
Обратите внимание, что в выводе указано API version: 3.3
, что также подтверждает, что наша переменная среды ETCDCTL_API
была настроена корректно.
Выйдите из сервера etcd1, чтобы вернуться в локальную среду.
Мы успешно установили etcd
и etcdctl
на все наши управляемые узлы. В следующем шаге мы добавим дополнительные задачи в нашу инструкцию для запуска etcd в качестве фоновой службы.
Может показаться, что самым быстрым способом запуска etcd с помощью Ansible может быть использование модуля command
для запуска /opt/etcd/bin/etcd
. Однако этот способ не сработает, поскольку он будет запускать etcd
в качестве активного процесса. Использование модуля command
будет приводить к зависанию Ansible в ожидании результата, возвращаемого командой etcd
, чего никогда не произойдет. Поэтому в этом шаге мы обновим наш плейбук для запуска нашего бинарного файла etcd
в качестве фоновой службы.
Ubuntu 18.04 использует systemd в качестве инит-системы, что означает, что мы можем создавать новые службы, записывая юнит-файлы и размещая их внутри каталога /etc/systemd/system/
.
Во-первых, внутри каталога нашего проекта создайте новый каталог с именем files/
:
- mkdir files
Затем с помощью вашего редактора создайте в этом каталоге новый файл с именем etcd.service
:
- nano files/etcd.service
Скопируйте следующий блок кода в файл 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
Добавьте следующую выделенную задачу после существующих задач:
- 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 к одному из управляемых узлов:
- ssh root@etcd1_public_ip
Затем запустите systemctl status etcd
для отправки systemd запроса о состоянии службы etcd
:
- systemctl status 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
для выхода из управляемого узла и возврата в локальную оболочку:
- exit
В этом шаге мы обновили наш плейбук для запуска бинарного файла etcd
в качестве службы systemd. В следующем шаге мы продолжим настройку etcd, предоставив службе пространство для хранения данных.
etcd — это хранилище данных типа «ключ-значение», а это значит, что мы должны предоставить ему пространство для хранения данных. В этом шаге мы обновим наш плейбук для определения специального каталога хранения данных, который будет использовать etcd.
Откройте ваш плейбук:
- nano $HOME/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/
:
- mkdir templates
Затем с помощью вашего редактора создайте в этом каталоге новый файл с именем etcd.conf.yaml.j2
:
- nano 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
Добавьте в список задач следующие задачи по созданию каталога и загрузке в него шаблонного файла конфигурации:
- 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 на локальном компьютере:
- nano files/etcd.service
Обновите файл files/etcd.service
, добавив флаг --config-file
, как показано в следующем выделенном фрагменте:
[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
и ее автоматической загрузки при запуске.
При внесении изменений в юнит-файл службы мы должны перезапустить службу для вступления этих изменений в силу. Мы можем сделать это, запустив команду systemctl restart etcd
. Кроме того, для автоматического запуска службы etcd
при запуске системы нам нужно воспользоваться командой systemctl enable etcd
. В этом шаге мы запустим эти две команды с помощью плейбука.
Чтобы запустить команды, воспользуемся модулем command
:
- nano $HOME/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 к одному из управляемых узлов:
- ssh root@etcd1_public_ip
Затем запустите systemctl status etcd
для проверки состоянии службы etcd
:
- systemctl status 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).
Хотя у нас есть работающая установка 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 запустите следующую команду:
- etcdctl put foo "bar"
Команда, которую мы только что запустили, указывает etcd записать значение "bar"
для ключа foo
в хранилище.
После этого вы получите сообщение OK
в выводе, что сигнализирует о сохранении данных:
OutputOK
После этого вы можете получить эту запись с помощью подкоманды get
, которая имеет синтаксис etcdctl get key
:
- etcdctl get foo
Вы получите данный вывод, который показывает ключ в первой строке и значение, которое вы вставили ранее, во второй строке:
Outputfoo
bar
Мы можем удалить запись с помощью подкоманды del
, которая имеет синтаксис etcdctl del key
:
- etcdctl del foo
Вы получите следующий вывод, который указывает количество удаленных записей:
Output1
Теперь давайте запустим подкоманду get
еще раз, чтобы попытаться получить удаленную пару «ключ-значение».
- etcdctl get foo
Вы не получите вывод, что означает, что etcdctl
не может получить пару «ключ-значение». Это подтверждает, что запись удалена и не может быть найдена.
Теперь, когда вы протестировали основные операции etcd и etcdctl
, давайте выйдем из нашего управляемого узла и вернемся в локальную среду:
- exit
В этом шаге мы использовали клиент etcdctl
для отправки запросов в etcd. На этом этапе мы используем три отдельных экземпляра etcd, каждый из которых действует независимо друг от друга. Однако etcd — это распределенное хранилище данных типа «ключ-значение», что означает, что несколько экземпляров etcd можно сгруппировать для формирования одного кластера. Каждый экземпляр в этом случае становится членом кластера. После формирования кластера вы сможете получить пару «ключ-значение», которая была вставлена из другого члена кластера. В следующем шаге мы используем наш плейбук для преобразования трех кластеров с одним узлом в один кластер с 3 узлами.
Чтобы создать один кластер из 3 узлов вместо трех кластеров с 1 узлом, нам нужно сконфигурировать эти установки etcd, чтобы они могли общаться друг с другом. Это означает, что каждая из них должна знать IP-адреса остальных. Этот процесс называется обнаружением. Обнаружение можно реализовать в форме статической конфигурации или динамического обнаружения службы. В этом шаге мы будем обсуждать разницу между этими двумя подходами, а также обновим наш плейбук для настройки кластера etcd с помощью статического обнаружения.
Обнаружение с помощью статической конфигурации — это метод, который требует наименьшей настройки. Именно в этом случае конечные точки каждого члена передаются в команду etcd
перед ее выполнением. Для использования статической конфигурации необходимо соблюсти следующие условия перед инициализацией кластера:
Если соблюсти эти условия невозможно, вы можете использовать службу динамического обнаружения. При динамическом обнаружении службы все экземпляры будут регистрироваться в службе обнаружения, что позволяет каждому члену получать информацию о расположении других членов.
Поскольку мы знаем, что нам нужен кластер etcd с 3 узлами, а все наши серверы имеют статические IP-адреса, мы будем использовать статическое обнаружение. Чтобы инициировать создание кластера с помощью статического обнаружения, нам нужно добавить несколько параметров в наш файл конфигурации. Воспользуйтесь редактором, чтобы открыть файл шаблона templates/etcd.conf.yaml.j2
:
- nano 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-адреса.
127.0.0.1
.178.128.169.51
.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
:
- 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"
(Создать каталог данных) таким образом, чтобы каталог данных сначала удалялся, а потом создавался снова:
- 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:
- ssh root@etcd1_public_ip
После установления подключения запустите команду etcdctl endpoint health --cluster
:
- etcdctl endpoint health --cluster
В результате выполнения команды будут выведены данные о состоянии каждого члена кластера:
Outputhttp://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
:
- etcdctl put foo "bar"
Затем используйте новый терминал для подключения через SSH к другому узлу:
- ssh root@etcd2_public_ip
Далее попытайтесь получить ту же запись с помощью ключа:
- etcdctl get foo
Вы сможете получить запись, что доказывает, что кластер работает:
Outputfoo
bar
В заключение выйдите из каждого управляемого узла и вернитесь на локальный компьютер:
- exit
- exit
В этом шаге мы предоставили новый кластер с 3 узлами. На данный момент связь между членами etcd
и другими узлами и клиентами осуществляется через HTTP. Это означает, что коммуникации не зашифрованы, и любая сторона, которая может перехватить трафик, сможет прочитать сообщения. Это не является большой проблемой, если кластер etcd
и клиенты размещены внутри частной сети или виртуальной частной сети (VPN), которую вы полностью контролируете. Однако, если какой-либо трафик должен проходить через общую сеть (частную или публичную), вам нужно гарантировать, что этот трафик будет зашифрован. Кроме того, необходимо создать механизм, который позволяет клиенту или другому узлу проверить аутентичность сервера.
В следующем шаге мы рассмотрим то, как обеспечить безопасность коммуникации между клиентом и серверами, а также другими узлами, используя TLS.
Для шифрования сообщений, пересылаемых между узлами, etcd использует протокол защищенного переноса гипертекста, или HTTPS, который представляет собой слой поверх протокола безопасности транспортного уровня, или TLS. TLS использует систему приватных ключей, сертификатов и доверенных объектов, называемых центрами сертификации (ЦС), для аутентификации и отправки зашифрованных сообщений друг другу.
В этом руководстве каждый узел должен генерировать сертификат для собственной идентификации и получать подпись ЦС для этого сертификата. Мы настроим все узлы членов так, чтобы они доверяли этому ЦС, а значит доверяли любым сертификатам, которые он подписал. Это позволяет узлам взаимно аутентифицировать друг друга.
Сертификат, который генерирует узел, должен позволить другим узлам идентифицировать себя. Все сертификаты включают стандартное имя (CN) объекта, с которым они ассоциируются. Часто оно используется как идентификатор объекта. Однако при проверке сертификата клиентские реализации могут сравнить собранную информацию об объекте с информацией, представленной в сертификате. Например, когда клиент загрузил сертификат TLS с субъектом CN=foo.bar.com
, но клиент фактически подключился к серверу с помощью IP-адреса (например, 167.71.129.110
), возникает противоречие, и клиент может не доверять сертификату. Указанное в сертификате дополнительное имя субъекта (SAN) во время верификации сообщает, что оба имени принадлежат одному и тому же объекту.
Поскольку наши члены etcd обмениваются данными, используя свои частные IP-адреса, когда мы определяем наши сертификаты, нам нужно будет предоставить эти частные IP-адреса в качестве дополнительных имен субъекта.
Чтобы узнать частный IP-адрес управляемого узла, выполните подключение через SSH к этому узлу:
- ssh root@etcd1_public_ip
Затем запустите следующую команду:
- ip -f inet addr show eth1
Вы увидите вывод примерно следующего содержания:
Output3: 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-адрес:
Output10.131.255.176
Когда вы убедитесь, что предыдущая команда работает, выйдите из управляемого узла:
- exit
Чтобы включить предшествующие команды в наш плейбук, откройте файл playbook.yaml
:
- nano $HOME/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-адреса управляемых узлов. В следующем шаге мы будем использовать эту информацию для генерирования сертификатов для каждого узла и получим подпись для этих сертификатов в центре сертификации (ЦС).
Чтобы узел мог принимать шифрованный трафик, отправитель должен использовать публичный ключ узла для шифрования данных, а узел должен использовать свой приватный ключ для расшифровки зашифрованного сообщения и получения оригинальных данных. Публичный ключ упаковывается в сертификат и подписывается ЦС для гарантии его подлинности.
Следовательно, нам нужно будет создать приватный ключ и запрос на подпись сертификата (CSR) для каждого узла etcd. Чтобы облегчить эту задачу, мы сгенерируем все пары ключей и подпишем все сертификаты локально, на узле управления, а затем скопируем соответствующие файлы на управляемые хосты.
Сначала создайте каталог с именем artifacts/
, куда мы поместим файлы (ключи и сертификаты), сгенерированные в ходе этого процесса. Откройте файл playbook.yaml
в редакторе:
- nano $HOME/playground/etcd-ansible/playbook.yaml
Используйте модуль file
для создания каталога artifacts/
:
...
- 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:
...
Затем добавьте еще одну задачу в конец инструкции для генерирования приватного ключа:
...
- 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
:
...
- 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
для вывода его содержимого:
- ls artifacts
Вы получите приватные ключи и запросы на подпись сертификата для каждого члена etcd:
Outputetcd1.csr etcd1.key etcd2.csr etcd2.key etcd3.csr etcd3.key
В этом шаге мы использовали несколько модулей Ansible для генерирования приватных ключей и сертификатов публичного ключа для каждого узла. В следующем шаге мы узнаем, как подписать запрос на подпись сертификата (CSR).
В кластере etcd узлы шифруют сообщения с помощью публичного ключа получателя. Чтобы убедиться в подлинности публичного ключа, получатель упаковывает публичный ключ в запрос на подпись сертификата (CSR) и просит доверенный объект (например, ЦС) подписать CSR. Поскольку мы контролируем все узлы и ЦС, которым они доверяют, нам не нужно использовать внешний ЦС, и мы можем выступать в качестве собственного ЦС. В этом шаге мы будем действовать в качестве собственного ЦС, что означает, что нам нужно будет генерировать приватный ключ и самоподписанный сертификат, который будет функционировать в качестве ЦС.
Во-первых, откройте файл playbook.yaml
в редакторе:
- nano $HOME/playground/etcd-ansible/playbook.yaml
Затем, как и в предыдущем шаге, добавьте задачу в инструкцию localhost
для генерирования приватного ключа для ЦС:
- 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
для генерирования нового запроса на подпись сертификата. Это похоже на предыдущий шаг, но в этом запросе на подпись сертификата мы добавляем базовое ограничение и расширение использования ключа, чтобы показать, что данный сертификат можно использовать в качестве сертификата ЦС:
- 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:
- 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/
:
- ls artifacts/
Теперь вы получите заново созданный сертификат ЦС (ca.crt
):
Outputca.crt ca.csr ca.key etcd1.csr etcd1.key etcd2.csr etcd2.key etcd3.csr etcd3.key
В этом шаге мы сгенерировали приватный ключ и самоподписанный сертификат для ЦС. В следующем шаге мы будем использовать сертификат ЦС для подписи CSR каждого члена.
В этом шаге мы будем подписывать CSR каждого узла. Это процесс аналогичен тому, как мы использовали модуль openssl_certificate
для самостоятельной подписи сертификата ЦС, но вместо использования поставщика selfsigned
мы будем использовать поставщика ownca
, который позволяет добавлять подпись с помощью нашего собственного сертификата ЦС.
Откройте ваш плейбук:
- nano $HOME/playground/etcd-ansible/playbook.yaml
Добавьте следующую выделенную задачу в задачу "Generate self-signed CA certificate"
(Сгенерировать самоподписанный сертификат ЦС):
- 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/
:
- ls artifacts/
Вы получите приватный ключ, CSR и сертификат для каждого члена etcd и ЦС:
Outputca.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.
Каждый узел должен иметь копию самоподписанного сертификата ЦС (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/
:
- 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:
...
Затем сразу после измененной задачи добавьте еще три задачи для копирования файлов:
- 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, чтобы мы могли использовать эти файлы.
В последнем шаге данного руководства мы обновим ряд конфигураций Ansible для активации TLS в кластере etcd.
Сначала откройте файл templates/etcd.conf.yaml.j2
с помощью редактора:
- nano $HOME/playground/etcd-ansible/templates/etcd.conf.yaml.j2
Внутри файла измените все URL-адреса для использования https
в качестве протокола вместо http
. Кроме того, добавьте раздел в конце шаблона для указания расположения сертификата ЦС, сертификата сервера и ключа сервера:
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 к одному из управляемых узлов:
- ssh root@etcd1_public_ip
Выполнив подключение, запустите команду etcdctl endpoint health
для проверки использования HTTPS конечными точками и состояния всех членов:
- etcdctl --cacert /etc/etcd/ssl/ca.crt endpoint health --cluster
Поскольку наш сертификат ЦС по умолчанию не является доверенным корневым сертификатом ЦС, установленным в каталоге /etc/ssl/certs/
, нам нужно передать его etcdctl
с помощью флага --cacert
.
Результат будет выглядеть следующим образом:
Outputhttps://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 к другому узлу:
- ssh root@etcd2_public_ip
Теперь попробуйте получить ту же запись с помощью ключа foo
:
- etcdctl --cacert /etc/etcd/ssl/ca.crt get foo
Это позволит получить запись, показанную в выводе ниже:
Outputfoo
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.
Наконец, в этом руководстве мы использовали множество инструментов, но не могли достаточно подробно описать каждый из них. Ниже вы найдете ссылки, которые позволят вам более детально познакомиться с каждым из этих инструментов:
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Sign up for Infrastructure as a Newsletter.
Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.