Такое чувство, будто в последние месяцы я черезчур уж сильно сосредоточился на дебаггинге и .NET Core, и как-то подзабил на топик, ради которого весь этот блог и затевался — DevOps и распределённые приложения. Ошибку осознал, и сегодня мы посмотрим на что-нибудь более релевантное. Например, etcd.
Во время моего романа с распределёнными приложениями etcd всегда был где-то неподалёку. То он был альтернативой Consul, когда я разбирался с сервис-дискавери и управлением конфигурациями. То etcd всплыл как основное хранилище настроек кластера в Kubernetes. В общем, он везде. Так что я думаю, стоит разобраться, на что же это за зверь такой.
Что такое etcd
etcd — это примитивное но при этом высоконадёжное распределённое хранилище для пар ключ-значение. «Примитивное» не значит, что тупое. Это просто значит что etcd фокусируется только на хранении и связанными с этим задачами. Ну и «высоконадёжный» и «распределённый» подразумевает, что если даже одна из кластера машин с etcd упадёт, то данными и кластером всё будет в порядке. По сравнению с тем же Consul фич тут будет поменьше, но в реальных задачах для того, чтобы забить гвоздь, отбойный молоток и не нужен.
Правда, то, что etcd просто хранит пары ключ-значение, не означает, что там всего три команды (get/set/remove) и на этом всё. Данные можно хранить по-разному, и etcd поддерживает и транзакции для работы с данными, и возможность отслеживать изменения, и настраивать пользователей с ролями, и всякие примитивы для синхронизации использовать, и даже даёт возможность установить время жизни значений (TTL).
В общем, будем смотреть.
Установка
Etcd поддерживает лучший из способов для установки — скачай-и-готово, и это прекрасно. Как всегда, я очень ленивый и параноидальный, поэтому вместо того, чтобы запускать временное и непонятно что на своей машине, я лучше напишу Vagrantfile с инструкциями для закачки и развёртывания, и получу готовую к надругиванию виртуальную с etcd внутри в одну команду.
По итогу, вот какой Vagrantfile у меня получился.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Vagrant.configure("2") do |config| config.vm.box = "ubuntu/xenial64" etcd_version = "v3.3.1" download_url = "https://storage.googleapis.com/etcd" install_dir = "/tmp/etcd-download" config.vm.provision "shell", inline: %{ mkdir -p #{install_dir} curl -L #{download_url}/#{etcd_version}/etcd-#{etcd_version}-linux-amd64.tar.gz -o /tmp/etcd-${etcd_version}-linux-amd64.tar.gz tar xzvf /tmp/etcd-${etcd_version}-linux-amd64.tar.gz -C #{install_dir} --strip-components=1 rm -f /tmp/etcd-${etcd_version}-linux-amd64.tar.gz echo 'PATH=$PATH:#{install_dir}' >> /home/vagrant/.bashrc } end |
Код вполне простой. Первая подсвеченная секция задаёт переменные и адреса, а вторая качает, распаковывает и обновляет PATH для удобства. Как обычно, vagrant up
запустит это всё великолепие, vagrant ssh
запустит меня внутрь виртуальной машины, а там, например, можно посмотреть, с какой же версией etcd мы имеем дело:
1 2 3 4 5 |
etcd --version #etcd Version: 3.3.1 #Git SHA: 28f3f26c0 #Go Version: go1.9.4 #Go OS/Arch: linux/amd64 |
Запускаем маленький etcd кластер
В официальной документации команда для развёртывания даже однохостового кластера выглядит очень уже заморочено. На самом деле для экспериментов на localhost запускать etcd можно вообще без параметров:
1 2 3 |
etcd # 2018-03-07 04:28:55.591939 I | etcdmain: etcd Version: 3.3.1 # 2018-03-07 04:28:55.592331 I | etcdmain: Git SHA: 28f3f26c0 |
Чтобы убедиться, что кластер живой и реагирует на свет, его можно пнуть ещё одной утилиткой — etcdctl
, и запросить членов кластера:
1 2 |
etcdctl member list # 8e9e05c52164694d: name=default peerURLs=http://localhost:2380 clientURLs=http://localhost:2379 isLeader=true |
Если бы я хотел посмотреть на кластер на другой машине, то пришлось бы подавать дополнительные параметр на вход (вроде -c http://localhost:2379
), но опять же, на localhost всё работает и так.
Добавляем, запрашиваем, удаляем данные
Тот же самый etcdctl
, что общался с кластером, может манипулировать и данными в нём. Правда, есть больше чем один API, которым можно это сделать, поэтому сразу стоит поставить переменную окружения с версией, которая нам подходит. Например, export ETCDCTL_API=3
переключит etcdctl на самый свежий 3й API .
И это на самом деле меняет много чего. Например, во второй версии была команда set
для сохранения данных. А в третьей это уже put
. Да и вообще, если вывести список команд, которые поддерживает API 2 и 3, можно очень хорошо удивиться.
Кстати о put
, Вот как можно добавить и запросить назад парочку ключей-значений:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
etcdctl put debug-mode-server on # создать debug-mode-server ключ со значением'on' #OK etcdctl put debug-mode-client off # создать ещё одину пару ключ-значение #OK etcdctl get debug-mode-server # запрашиваем один ключ #debug-mode-client #on etcdctl get debug --prefix # запрашиваем все, начинающиеся с 'debug' #debug-mode-client #off #debug-mode-server #on etcdctl del debug-mode-server # удаляем debug-mode-server #1 |
Работаем с данными по HTTP
etcdctl
, конечно, не самый удобный способ скрестить свою программу с etcd. Хотя под капотом etcd общается по протоколу gRPC, к нему можно подключаться и по старому доброму HTTP при помощи встроенного grpc-gateway. HTTP API, конечно, немного с душком, и определённо застрял где-то у основания Модели Зрелости Ричардсона, но работает. Хотя кодировать и ключ и значение в base64 в каждом запросе, конечно, раздражает.
Вот как можно отправить в кластер пару mykey:myvalue
по HTTP:
1 2 |
curl -L http://localhost:2379/v3beta/kv/put -X POST -d "{\"key\":\"`echo 'mykey' | base64`\",\"value\":\"`echo 'myvalue' | base64`\"}" #{"header":{"cluster_id":"14841639068965178418","member_id":"10276657743932975437","revision":"7","raft_term":"6"}} |
И забрать назад:
1 2 3 |
curl -L http://localhost:2379/v3beta/kv/range -X POST -d "{\"key\":\"`echo 'mykey' | base64`\"}" #{"header":{"cluster_id":"14841639068965178418","member_id":"10276657743932975437","revision":"7","raft_term":"6"}, #"kvs":[{"key":"bXlrZXkK","create_revision":"7","mod_revision":"7","version":"1","value":"bXl2YWx1ZQo="}],"count":"1"} |
Стрёмно, конечно, но работает ведь.
Другие приятные фичи
Дожидаемся изменений значений
А что если нам хочется, чтобы etcd как-то сообщил нам, когда значение какого-нибудь полезного ключа меняется? Без проблем. Можно даже ждать изменений ключа, которого вообще нет:
1 2 |
etcdctl watch shutdown-requested #...долго и утомительно ждем |
В Consul кстати была такая же штука. Так вот, watch
запрос будет ждать до тех пор, пока кто-нибудь не обновит значение нашего shutdown-requested
ключа.
1 |
etcdctl put shutdown-requested now |
И всё только для того, чтобы продолжить ждать дальше.
1 2 3 4 5 |
etcdctl watch shutdown-requested #PUT #shutdown-requested #now #... ждем дальше |
Транзакции
Или вот такой сценарий: нам нужно установить новое значение в ключ только в том случае, если текущее равно чему-то особенному. Ну вроде как есть кластер машин, в etcd хранится имя лидера кластера — ключ leader, и нам нужно установить имя нашей машины только в том случае, если лидера пока нет (none
). Интерактивно это можно сделать при помощи команды txn
.
Допустим, сначала leader
-таки есть.
1 2 3 4 5 6 7 8 9 10 11 12 |
etcdctl put leader me etcdctl txn --interactive #compares: #value("leader") = "none" # #success requests (get, put, del): #put leader myHostName # #failure requests (get, put, del): # #FAILURE |
При таких начальных данных транзакция, разумеется ответила FAILURE
, ведь значение ключа отличалось от none
. Но если лидера убрать, то всё пройдёт хорошо:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
etcdctl put leader none #OK etcdctl txn --interactive #compares: #value("leader") = "none" # #success requests (get, put, del): #put leader myHostName # #failure requests (get, put, del): # #SUCCESS # #OK etcdctl get leader #leader #myHostName |
Мораль
Вот такой он etcd
. Не весь конечно, но достаточный кусок для того, чтобы сформировать какое-то мнение. Я всё ещё играюсь с его документацией, и практически в восторге от их «Demos» секции. Анимация, конечно, там зря, но короткие куски кода для всех основных фич — это прекрасно, и очень помогает на первых порах. Ну и, как это часто бывает, я уже вижу, где etcd
можно было бы засунуть нам в продакшен, как бы двусмысленно эта фраза не звучала.