В Kubernetes etcd является основным хранилищем информации о состоянии кластера. В этой статье мы расскажем основы работы etcd в Kubernetes и покажем, как настроить etcd-кластер.

Дополнительно мы покажем, как задействовать SSL/TLS-шифрование для сообщения между нодами кластера и etcd, чтобы повысить безопасность и надежность использования etcd.

etcd — это распределенное хранилище типа «ключ-значение» c открытым исходным кодом. Изначально etcd создавался для работы с CoreOS, но сейчас оно доступно для OS X, Linux и BSD.

Как и Kubernetes, etcd написан на языке Go и использует алгоритм консенсуса Raft для обеспечения высокодоступной репликации данных. Благодаря поддержке распределенных систем он активно применяется в Kubernetes, Cloud Foundry, Fleet и других проектах.

Зачем нужен кластер etcd в Kubernetes?

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

etcd можно запускать 1) на тех же нодах, что и кластер Kubernetes, 2) на отдельных машинах либо 3) как под в кластере Kubernetes. Вне зависимости от выбранной конфигурации основная задача — обеспечить консистентность данных и отказоустойчивость кластера. Если etcd запущен в нескольких репликах, будь то на отдельных машинах или машинах с Kubernetes, это обеспечит непрерывность работы кластера Kubernetes в случае сбоя отдельных нод etcd.

Кластер etcd из трех нод

Каким должен быть кластер etcd?

Количество нод в кластере etcd теоретически неограничено. И чем больше нод, тем выше надежность etcd, однако при увеличении размера кластера растет задержка при записи информации, так как данные реплицируются на большем числе нод. Оптимальны etcd-кластеры не более чем из 7 нод, а в большинстве случаев достаточно 5 нод — в похожем на etcd внутреннем сервисе Google использует именно такие кластеры.

Чтобы застраховаться от поломки двух нод сразу, нужен кластер из 5 нод, в котором против против двух упавших будут три работающие ноды. От поломки трех нод нужен кластер из 7 нод, и так далее.

Как вы видите, в кластере рекомендуется использовать нечётное количество нод. Это связано с механизмом согласования данных на нодах в кластере через кворум, и в конечном итоге — его устойчивости. Например, в кластере из 5 нод кворум составляет 3 ноды (то есть, кластер может потерять 2 ноды и при этом сохранить работоспособность). Казалось бы, чем больше нод, чем надёжнее, и добавление 6 ноды будет полезно, но в кластере из 6 нод кворум составляют 4 узла, поэтому такой кластер устойчив к потере тех же двух узлов, что и кластер из 5 нод. При этом в кластере больше нод, которые могут упасть. Подробнее об оптимальном числе нод в кластере здесь.

Еще несколько важных моментов:

  • Кластер etcd предпочтительно запускать на отдельных нодах.
  • Необходимо следить, чтобы на всех нодах всегда оставалось достаточное количество свободных ресурсов для сохранения новых данных.
  • Рекомендуемая версия etcd для запуска в production — 3.2.24 и выше. Более ранние версии нестабильно работают с новыми версиями Kubernetes (1.12 и 1.13).

Эффективность и стабильность работы кластеров во многом зависят от дискового I/O. Для обеспечения стабильности etcd записывает все метаданные в лог и постоянно проверяет свое состояние (health checks), чтобы извлечь уже неактуальную информацию из этого лога. При высокой задержке записи/чтения возникает риск рассинхронизации данных о состоянии кластера, что приведет к его нестабильной работе и переизбранию мастера etcd. Кроме того, недостаток ресурсов может привести к рассинхронизации компонентов кластера etcd, невозможности записи информации в etcd и запуска новых подов в кластерах Kubernetes. Поэтому рекомендуется запускать etcd на SSD-дисках.

Kubernetes как сервис с сертификацией CNCF
Беспроблемная доставка ваших приложений
Перейти

Архитектура нашего примера

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

Для работы с etcd необходимо иметь кластер Kubernetes и настроенную утилиту kubectl.

Архитектура кластера etcd в нашем примере

Установка кластера etcd

В этом разделе мы подготовим кластер и установим etcd из стандартного репозитория. Мы будем использовать 3 виртуальные ноды с CentOS 7.5. Коммуникация между компонентами кластера обеспечивается по IP и имени хоста:

  • 10.10.0.10 etcd1
  • 10.10.0.11 etcd2
  • 10.10.0.12 etcd3

Примечание: На нодах, на которых будет запущен etcd, рекомендуется настроить отдельную внутреннюю подсеть для их peer-to-peer-взаимодействия.

Предварительная настройка

В нашем примере мы не используем внутренний DNS, поэтому на каждой ноде кластера etcd в файл /etc/hosts нам нужно будет прописать имена хостов. Если бы у нас был DNS-сервер, мы бы просто могли обращаться к нодам по именам хостов напрямую.

Итак, пропишем имена хостов:

# Замените имена хостов и их адреса на актуальные
10.10.0.10 etcd1
10.10.0.11 etcd2
10.10.0.12 etcd3
setenforce 0
sed -i 's/^SELINUX=.*/SELINUX=disabled/g' /etc/sysconfig/selinux
firewall-cmd --add-port={2379,2380}/tcp --permanent
firewall-cmd --reload

Установка пакетов etcd на все ноды

На каждой ноде установим пакеты etcd через менеджер пакетов yum, выполнив следующую команду:

yum install -y etcd

Мы готовы конфигурировать etcd.

Конфигурирование etcd

На каждой ноде открываем /etc/etcd/etcd.conf прописываем адреса нод кластера в etcd.conf. На каждой ноде ноде файл должен выглядеть так:

# [member]
ETCD_NAME=<NAME>
ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_PEER_URLS="http://<IP>:2380"
ETCD_LISTEN_CLIENT_URLS="http://<IP>:2379,http://127.0.0.1:2379"
#[cluster]
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://<IP>:2380"
ETCD_INITIAL_CLUSTER="etcd1=http://10.10.0.10:2380,etcd2=http://10.10.0.11:2380,etcd3=http://10.10.0.12:2380"
ETCD_INITIAL_CLUSTER_STATE="new"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster-1"
ETCD_ADVERTISE_CLIENT_URLS="http://<IP>:2379"

где — имя, а — адрес ноды.

Завершение конфигурации

После того, как мы запустили кластер etcd, изменим его статус с new на existing. Для этого на каждой ноде выполним:

sed -i s'/ETCD_INITIAL_CLUSTER_STATE="new"/ETCD_INITIAL_CLUSTER_STATE="existing"/'g /etc/etcd/etcd.conf

Конфигурирование Kubernetes API Server

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

На мастере Kubernetes откроем /etc/kubernetes/apiserver и пропишем адреса нод:

KUBE_ETCD_SERVERS="--etcd_servers=http://10.10.0.10:2379,http://10.10.0.11:2379, http://10.10.0.12:2379"

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

etcdctl member list

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

Однако для работы с etcd в production необходимо настроить взаимодействие между компонентами кластера etcd и Kubernetes по https.

Настройка SSL/TLS для взаимодействия между нодами etcd

Если etcd хранит информацию, которая не должна быть общедоступной, необходимо использовать шифрование соединений. etcd поддерживает SSL/TLS, а также аутентификацию серверов/клиентов с сертификатами для взаимодействия «клиент-сервер» и «сервер-сервер» (peer-to-peer).

SSL/TLS обеспечивает передачу данных в зашифрованном виде по https. В рамках нашей задачи будем шифровать соединения нод etcd (в роли SSL/TLS серверов) и сервера API Kubernetes (в роли SSL/TLS-клиента), а также соединение между нодами etcd (соединения сервер-сервер). Для этого создадим по одному сертификату для нод etcd и сервера API (мастера) в кластере Kubernetes.

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

Генерирование самоподписанных TLS-сертификатов

Инструмент cfssl позволяет легко генерировать новые сертификаты. Чтобы установить cfssl, выполним следующие команды на каждой ноде:

curl https://pkg.cfssl.org/R1.2/cfssl_linux-amd64 -o /usr/local/bin/cfssl
curl https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64 -o /usr/local/bin/cfssljson
chmod +x /usr/local/bin/cfssl /usr/local/bin/cfssljson

Создадим директорию для генерации сертификатов и файлы конфигурации CA по умолчанию:

mkdir ~/cfssl
cd ~/cfssl
cfssl print-defaults config > ca-config.json
cfssl print-defaults csr > ca-csr.json

Отредактируем файл ca-config.json следующим образом, создав профили для коммуникации серверов с клиентом и взаимодействия между серверами:

{
     "signing": {
         "default": {
             "expiry": "43800h"
         },
         "profiles": {
             "server": {
                 "expiry": "43800h",
                 "usages": [
                     "signing",
                     "key encipherment",
                     "server auth"
                 ]
             },
             "client": {
                 "expiry": "43800h",
                 "usages": [
                     "signing",
                     "key encipherment",
                     "client auth"
                 ]
             },
             "peer": {
                 "expiry": "43800h",
                 "usages": [
                     "signing",
                     "key encipherment",
                     "server auth",
                     "client auth"
                 ]
             }
         }
     }
 }

Сертификаты для нод etcd

Создадим файлы конфигурации сертификата для нод etcd в роли серверов SSL/TLS:

cfssl gencert -initca ca-csr.json | cfssljson -bare ca -
cfssl print-defaults csr > server.json

На выходе мы получим файл server.json с конфигурацией сертификата.

Создадим копию server.json для каждой ноды (в конечном итоге должно быть три файла) и назовем их для удобства server1.json, server2.json и server3.json. Отредактируем каждый из них следующим образом, указав актуальные имя хоста и IP-адрес во внутренней сети:

{
     "CN": "etcd1",
     "hosts": [
         "etcd1",
         "10.0.0.11"
     ],
     "key": {
         "algo": "ecdsa",
         "size": 256
     }
 }

Сгенерируем сертификаты для серверов etcd:

cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=server server1.json | cfssljson -bare server1
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=server server2.json | cfssljson -bare server2
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=server server3.json | cfssljson -bare server3

Сертификат для клиента

Теперь создадим файл конфигурации для мастер-ноды кластера Kubernetes в роли SSL/TLS-клиента:

cfssl print-defaults csr > client.json
{
     "CN": "root",
     "key": {
         "algo": "ecdsa",
         "size": 256
     }
}

Сгенерируем сертификат клиента:

cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=client client.json | cfssljson -bare client

Сертификаты для peer-to-peer в кластере etcd

Наконец, создадим файлы конфигурации для peer-to-peer-взаимодействия между нодами etcd:

cfssl print-defaults csr > member1.json

Как и при создании сертификатов для серверов, создадим копии файла для каждой ноды кластера и назовем их member1.json, member2.json и member3.json. Отредактируем каждый из них следующим образом, указав актуальные имя хоста и IP-адрес во внутренней сети:

{
     "CN": "etcd1",
     "hosts": [
         "etcd1",
         "10.0.0.11"
     ],
     "key": {
         "algo": "ecdsa",
         "size": 256
     }
 }

Сгенерируем сертификаты:

cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=peer member1.json | cfssljson -bare member1
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=peer member2.json | cfssljson -bare member2
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=peer member3.json | cfssljson -bare member3

Копируем сертификаты на соответствующие ноды

Создаем на каждой ноде (компонентах кластера etcd и сервере Kubernetes API) директорию /etc/ssl/etcd для хранения сертификатов etcd:

mkdir /etc/ssl/etcd

Копируем сертификаты на каждую ноду согласно следующей схеме:

  • etcd1: server1.pem, member1.pem, member1-key.pem, server1-key.pem, ca.pem
  • etcd2: server2.pem, member2.pem, member2-key.pem, server2-key.pem, ca.pem
  • etcd3: server3.pem, member3.pem, member3-key.pem, server3-key.pem, ca.pem
  • клиент etcd: client.pem, client-key.pem. ca.pem

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

chown etcd: /etc/ssl/etcd/*
chmod 660 /etc/ssl/etcd/*-key.pem

Настройка взаимодействия по HTTPS без аутентификации

Теперь настроим взаимодействие по HTTPS-протоколу. На каждой ноде кластера etcd в /etc/etcd/etcd.conf исправим http на https и добавим новые строки в раздел Security, чтобы разрешить использование TLS-сертификатов.

Пример для ноды 1 (выполните аналогичные настройки на ноде 2 и 3):

# [member]
ETCD_NAME=etcd1
ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_PEER_URLS="https://10.10.0.10:2380"
ETCD_LISTEN_CLIENT_URLS="https://10.10.0.10:2379,https://127.0.0.1:2379"
 
#[cluster]
ETCD_INITIAL_ADVERTISE_PEER_URLS="https://10.10.0.10:2380"
ETCD_INITIAL_CLUSTER="etcd1=https://10.10.0.10:2380,etcd2=https://10.10.0.11:2380,etcd3=https://10.10.0.12:2380"
ETCD_INITIAL_CLUSTER_STATE="new"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster-1"
ETCD_ADVERTISE_CLIENT_URLS="https://10.10.0.10:2379"
 
#[Security]
ETCD_CERT_FILE="/etc/ssl/etcd/server1.pem"
ETCD_KEY_FILE="/etc/ssl/etcd/server1-key.pem"
ETCD_TRUSTED_CA_FILE="/etc/ssl/etcd/ca.pem"
ETCD_CLIENT_CERT_AUTH="false"
ETCD_PEER_CERT_FILE="/etc/ssl/etcd/member1.pem"
ETCD_PEER_KEY_FILE="/etc/ssl/etcd/member1-key.pem" 
ETCD_PEER_CLIENT_CERT_AUTH="true"
ETCD_PEER_TRUSTED_CA_FILE="/etc/ssl/etcd/ca.pem" 

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

export ETCDCTL_API=3
export ETCDCTL_DIAL_TIMEOUT=3s
export ETCDCTL_ENDPOINTS='https://10.10.0.10:2379,https://10.10.0.10:2379,https://10.10.0.10:2379'
export ETCDCTL_CACERT=/etc/ssl/etcd/ca.crt
export ETCDCTL_CERT=/etc/ssl/etcd/client.pem
export ETCDCTL_KEY=/etc/ssl/etcd/client-key.pem

Настройка клиент-серверного и peer-to-peer-соединения по HTTPS завершена.

Проверка TLS-соединения (клиент-сервер) без аутентификации

Чтобы проверить правильность настройки взаимодействия, подсоединимся к etcd по HTTPS с мастер-ноды Kubernetes:

curl --cacert /etc/ssl/certs/ca.crt https://etcd(1|2|3):2379/v2/keys/foo -XPUT -d value=bar -v

Настройка взаимодействия по HTTPS с аутентификацией

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

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

Для этого на каждой ноде кластера etcd необходимо отредактировать файл /etc/etcd/etcd.conf и указать значение ETCD_CLIENT_CERT_AUTH=»true»

Проверка TLS-соединения (клиент-сервер) с аутентификацией

Попробуем соединиться к etcd с аутентификацией с мастер-ноды Kubernetes:

curl --cacert /etc/ssl/certs/ca.crt --cert /etc/ssl/certs/client.crt --key /etc/ssl/certs/client.key \
  -L https://etcd(1|2|3):2379/v2/keys/foo -XPUT -d value=bar -v

Поскольку в примере мы используем самоподписанный CA-сертификат, мы указываем удостоверяющий центр (CA) вручную при помощи флага —cacert. Чтобы этого не делать, можно добавить сертификат CA в системную директорию доверенных сертификатов (обычно это /etc/pki/tls/certs или /etc/ssl/certs).

Авторизация и аутентификация в  etcd-кластере

Авторизация в etcd реализована на основе Role-Based Access Control (RBAC), то есть — правил доступа пользователей к данным, хранящимся в etcd, в зависимости от роли этих пользователей. Чтобы выдать права конечному пользователю, нужно назначить ему роль, для которой эти права описаны.

Создадим пользователя root, которому по умолчанию будет присвоена роль root:

etcdctl user add root

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

Аутентификация в etcd по умолчанию отключена. Для ее включения выполним команду:

etcdctl auth enable

После включения аутентификации мы можем настроить авторизацию. В примере ниже мы создаём пользователя, роль, задаём права роли и назначаем роль для пользователя:

etcdctl user add user1
etcdctl role add role1
etcdctl role grant-permission role1 --prefix=true readwrite /dir1/
etcdctl user grant-role user1 role1

Это даст пользователю user1 полные права на всё, что лежит в директории /dir1/.

Что в итоге

Мы только что настроили кластер etcd из 3 нод, который обеспечивает надежность хранения состояния кластера Kubernetes. Такая минимальная конфигурация etcd позволяет повысить отказоустойчивость сервисов. Если вы отключите 1 ноду etcd, то увидите, что Kubernetes продолжает работать. Однако, если вы отключите 2 ноды, кластер станет недоступен, поэтому для работы критических приложений с большой нагрузкой рекомендуется использовать кластер etcd из 5 нод.