Закон Парето (принцип Парето, принцип 80/20) — «20 % усилий дают 80 % результата, а остальные 80 % усилий — лишь 20 % результата».
Wikipedia
Приветствую тебя, дорогой читатель!
Моя первая статья на Хабр посвящена простому и, надеюсь, полезному решению, сделавшим для меня сбор метрик в Prometheus с разнородных серверов удобным. Я затрону некоторые подробности, в которые многие могли не погружаться, эксплуатируя Prometheus, и поделюсь своим подходом по организации в нём легковесного service discovery.
Для этого понадобится: Prometheus, HashiCorp Consul, systemd, немного кода на Bash и осознание происходящего.
Если интересно узнать, как все это связано и как оно работает, добро пожаловать под кат.
Встречайте: Prometheus
Мое знакомство с Prometheus произошло, когда возникла необходимость собирать метрики с кластера Kubernetes. Пробежавшись по материалам в интернете стало понятно, что работать с Prometheus и его pull-моделью, очень удобно, когда он самостоятельно узнаёт о сервисах, с которых необходимо собирать метрики. Для настройки Prometheus под Kubernetes в конфигурационном файле prometheus.yml
есть директива kubernetes_sd_configs
. Она отвечает за коммуникацию с kube-apiserver с целью получения IP-адресов и метаинформации о pod'ах в кластере с которых нужно собирать метрики.
scrape_configs:
- job_name: kubernetes-pods
kubernetes_sd_configs:
- role: pod
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
tls_config:
insecure_skip_verify: true
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_pod_ip, __meta_kubernetes_pod_annotation_prometheus_io_port]
action: replace
regex: (.+);(.+)
replacement: $1:$2
target_label: __address__
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
action: replace
regex: (.+)
target_label: __metrics_path__
- action: labelmap
regex: __meta_(kubernetes_namespace|kubernetes_pod_name|kubernetes_pod_label_manifest_sha1|kubernetes_pod_node_name)
С первого взгляда картинка не сильно понятная, но с документацией и в несколько экспериментов разобраться можно. Помимо role=pod
есть и другие роли у kubernetes_sd_configs
.
Наблюдая, как эффективно Prometheus узнает о сервисах в Kubernetes, например, о DaemonSet prometheus/node_exporter, сразу появилось желание реализовать аналогичный подход для сбора метрик и мониторинга доступности сервисов вне кластера Kubernetes: node_exporter, Zookeeper, Kafka, ClickHouse, CEPH, Elasticsearch, Tarantool ...
Писать каждый раз targets
в static_configs
при добавлении нового сервера в текущий кластер Kafka или OSD в CEPH ну совсем не про удобное управление инфраструктурой. А еще это нужно не забывать делать. Да, можно все автоматизировать, например через Ansible, но что делать, если на некоторое время отключить один CEPH OSD для обслуживания. Тогда нужно еще и автоматизировать временное выведение этого сервиса из конфига prometheus.yml
. В итоге получается огромная куча слоев автоматизации, которые тоже нужно не забывать запускать.
Именно слоев, потому что одна такая автоматизация порождает необходимость в другой. Так еще ко всему этому сильно разрастается prometheus.yml
, в котором нужно не просто перечислять сервисы, а еще делить их на разные job_name
для удобного доступа к метрикам. Тут легко прикинуть сколько строк в prometheus.yml
нам создаст один кластер Kafka из 6 брокеров. А если кластеров 3. И добавим к ним еще 3 кластера ClickHouse, в каждом по 4 ноды. А уж про CEPH и вообще говорить страшно. А еще на каждом сервере не по одному сервису, с которого нужно собирать метрики — про prometheus/node_exporter не надо забывать. Представив всю свою инфраструктуру в количественной мере можно прикинуть и размер prometheus.yml
при использовании только static_configs
.
Kubernetes Documentation, Configuration Best Practices, General Configuration Tips.
*Админы почему-то очень любят хранить в конфигах строки по-умолчанию в виде комментариев, создавая из файла конфигурации настоящую свалку. Не нужно этого делать!
В файле конфигурации должны быть только важные строки, отвечающие за работу приложения. Файл, который шел в инсталляционном пакете, можно оставить рядом, добавив ему постфикс .default
или .original
. А можно вообще ничего не хранить рядом и в файл конфигурации, в шапке, положить 2-3 полезные ссылки на описание этого файла или его содержимое по-умолчанию во внешнем источнике: документация, GitHub репозиторий проекта. Совсем не сложно сделать файл конфигурации удобным и читаемым для коллег и себя самого, в будущем.*
Вспомним про HashiCorp Consul
Нужна была максимально простая и понятная автоматизация управления сервисами в prometheus.yml
. Самое знакомое что сразу бросалось в глаза из документации Prometheus — consul_sd_configs
. То есть, Prometheus, посредством HashiCorp Consul, может узнавать о сервисах с которых нужно собирать метрики и заодно мониторить их доступность. Например:
scrape_configs:
- job_name: SERVICE_NAME
consul_sd_configs:
- server: consul.example.com
scheme: https
tags: [test] # dev|test|stage|prod|...
services: [prometheus-SERVICE_NAME-exporter]
Будучи уже знакомым с Consul, я знал, что сам он не узнает о сервисах даже на тех машинах, на которых запущен его agent. Через его простое HTTP API сервис нужно зарегистрировать в Consul, а еще его можно разрегестрировать. Ведь бывает, что сервис больше никому не нужен: попробовали, и не подошел. В полном объеме я никогда не использовал Consul. Возникали только задачи, с которыми Consul отлично справлялся частью своих функций: KV-хранилище, HashiCorp Vault, кластеризация Traefik. И, например, никогда не нужен был его DNS. Вот и сейчас задача стояла не усложнить себе жизнь, запуская на каждом сервере по дополнительном сервису в виде Consul agent. Достаточно запустить один инстанс Consul, который будет принимать запросы в HTTP API и к нему будет обращаться Prometheus за списком адресов, по которым расположены те или иное сервисы. Идеально, если это все будет по HTTPS, хотя сеть и так закрытая.
На такой волне стало понятно, что уже имеющийся в Kubernetes StatefulSet Consul, обеспечивающий кластерную работу Traefik, можно использовать для service discovery в Prometheus. И даже Ingress в локальную сеть у него уже был, так как было пару моментов использования Consul’s web UI. Бонусом Traefik обеспечивал ему HTTPS-транспорт с сертификатом от Let`s Encrypt через DNS Challenge.
# https://consul.io/docs/agent/options.html
---
apiVersion: v1
kind: Service
metadata:
name: consul
labels:
app: consul
spec:
selector:
app: consul
ports:
- name: http
port: 8500
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: consul
labels:
app: consul
spec:
serviceName: consul
selector:
matchLabels:
app: consul
volumeClaimTemplates:
- metadata:
name: data
spec:
storageClassName: cephfs
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
template:
metadata:
labels:
app: consul
spec:
automountServiceAccountToken: false
terminationGracePeriodSeconds: 60
containers:
- name: consul
image: consul:1.6
volumeMounts:
- name: data
mountPath: /consul/data
args:
- agent
- -server
- -client=0.0.0.0
- -bind=127.0.0.1
- -bootstrap
- -bootstrap-expect=1
- -disable-host-node-id
- -dns-port=0
- -ui
ports:
- name: http
containerPort: 8500
readinessProbe:
initialDelaySeconds: 10
httpGet:
port: http
path: /v1/agent/members
livenessProbe:
initialDelaySeconds: 30
httpGet:
port: http
path: /v1/agent/members
resources:
requests:
cpu: 0.2
memory: 256Mi
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: consul
labels:
app: consul
annotations:
traefik.ingress.kubernetes.io/frontend-entry-points: http,https
traefik.ingress.kubernetes.io/redirect-entry-point: https
spec:
rules:
- host: consul.example.com
http:
paths:
- backend:
serviceName: consul
servicePort: http
На этом этапе у меня имелся Prometheus, который готов забрать из Consul адреса сервисов и начать собирать с них метрики, и инстанс Consul, в котором можно регистрировать сервисы. Возникает задача автоматизировать децентрализованную регистрацию сервисов во время запуска на любом сервере инфраструктуры. И на помощь приходит простой Bash.
Время для Bash и systemd.service
В своей инфраструктуре я давно использую иммутабельную CoreOS Container Linux. Было даже дело рассказать об этой ОС на DevOpsConf Russia 2018. Я и по сей день использую эту ОС как основную для запуска сервисов в эфемерных Docker контейнерах, оборачивая в systemd.service. Но только теперь это Flatcar Linux, разработчики которой подхватили идею и не дали ей погибнуть, обеспечив полную совместимость с CoreOS Container Linux. А перейти с CoreOS на Flatcar можно простым обновлением системы!
Все сервисы на моём сервере — это systemd.service. А systemd — это очень мощный инструмент, который активно развивается и решает очень много задач в Linux. И есть в systemd.service, в секции [Service]
, такие параметры как ExecStartPost
, ExecStop
. Они-то и помогут мне с регистрацией и разрегистрацией сервиса в Consul.
Опишу сразу на примере юнита prometheus-node-exporter.service
. Этот сервис примечателен тем, что запускается абсолютно на каждом сервере и ему одному в static_configs
можно было бы выделить больше 100 строк.
[Unit]
After=docker.service
[Service]
Environment=CONSUL_URL=https://consul.example.com
ExecStartPre=-/usr/bin/docker rm --force %N
ExecStart=/usr/bin/docker run --name=%N --rm=true --network=host --pid=host --volume=/:/rootfs:ro --label=logger=json --stop-timeout=30 prom/node-exporter:v0.18.1 --log.format=logger:stdout?json=true --log.level=error
ExecStartPost=/opt/bin/consul-service register -e prod -n %N -p 9100 -t prometheus,node-exporter
ExecStop=/opt/bin/consul-service deregister -e prod -n %N
ExecStop=-/usr/bin/docker stop %N
Restart=always
StartLimitInterval=0
RestartSec=10
KillMode=process
[Install]
WantedBy=multi-user.target
Как раз вот тут появляется некий /opt/bin/consul-service. У него всего пара переменных системного окружения и несколько обязательных аргументов, которые я опишу поподробней.
Переменные окружения:
- CONSUL_URL — адрес Consul в котором будут регистрироваться сервисы во время запуска.
- CONSUL_TOKEN — он же HTTP-заголовок «X-Consul-Token», позволяющий ограничить доступ в HTTP API Consul. Его можно сформировать в Consul web UI, а в последних версиях на него еще навесили ACLs.
Обязательные аргументы:
- register/deregister — всегда первый аргумент. Отвечает за регистрацию сервиса и его разрегистрацию.
- -e — окружение в котором запускается сервис — [dev|test|prod|…].
- -n — название сервиса, например prometheus-node-exporter. В примере выше используется переменная %N, которая в systemd.unit присваивает значение из названия самого юнита.
- -p — порт на котором сервис будет отдавать в Prometheus метрики. Используется только во время регистрации сервиса в Consul.
- -t — произвольные теги через запятую. С их помощью можно иначе фильтровать выборки сервисов в Consul. Используется только во время регистрации сервиса в Consul.
Использование consul-service в systemd.service оказалось очень практичным и преподнесло один очень приятный бонус. systemd.service во время старта, а именно ExecStartPost
с consul-service register
, регистрирует сервис в Consul, и Prometheus сразу же может забирать с него метрики, а также мониторить его доступность. Но самое удобное, когда нужно ненадолго оставить сервис или перезагрузить сервер. В этот момент выполняются все ExecStop
, в том числе consul-service deregister
, и сервис разрегистрируется. Consul и следом Prometheus о нем забывают, и тогда нет никакого лишнего оповещения о недоступности намеренно остановленного сервиса. Особенно это практично, когда работы проводятся в ночное время одним инженером, а другой в этот момент в ответе за Service Availability и получает пугающие его оповещения о недоступности сервиса. Но если сервис упал, и никто специально для проведения работ его не останавливал, и разрегистрации сервиса не было, то он будет под мониторингом со всеми вытекающими.
Очень важным моментом в работе consul-service является правильно установленные hostname
сервера и search
(домены поиска) в resolv.conf
. Этот же адрес должен присутствовать в локальном DNS-сервере, что как минимум best practice при работе с серверами в локальной среде.
Если вы до сих пор работаете со своими серверами в локальной сети по IP, то самое время присвоить себе тоже какой-нибудь идентификатор и обязать коллег обращаться к вам только по нему. И больше ни на какие обращения не реагировать. Так будет справедливо и быстро осознается ценность имени.
consul-service написан на чистом Bash в ~60 строчек. В зависимостях у него только хорошо известный всем cURL и Bash. Уверен, что админ без опыта разработки бегло разберется в нескольких строчках этого скрипта. Лицензия этого макро-решения MIT, поэтому если появится необходимость доработать его под свои нужды, не стесняйтесь.
Тот самый scrape_configs
в prometheus.yml
для prometheus/node_exporter, запущенном на всех серверах, лаконичен и не зависит никак от количества серверов.
scrape_configs:
- job_name: node-exporter
consul_sd_configs:
- server: consul.example.com
scheme: https
tags: [prod]
services: [prometheus-node-exporter]
tags: [prod]
тут отвечает за выборку инстансов только production среды. Это как раз тот самый аргумент -e
в consul-service, который по факту превращается в один из тегов сервиса при попадании в Consul.
На мои сервера с Flatcar Linux доставка скрипта в /opt/bin
осуществляется посредством Ansible. Playbook всего на пару tasks
. Но это реализуется и любым другим инструментом, хоть строчкой в crontab
.
tasks:
- name: Create directory "/opt/bin"
with_items: [/opt/bin]
file:
state: directory
path: "{{ item }}"
owner: root
group: root
mode: 0755
- name: Download "consul-service.sh" to "/opt/bin/consul-service"
get_url:
url: https://raw.githubusercontent.com/devinotelecom/consul-service/master/consul-service.sh
dest: /opt/bin/consul-service
owner: root
group: root
mode: 0755
force: yes
О том как Ansible, а точнее Python, попадает и работает на серверах Flatcar Linux думаю, можно рассказать в будущих статьях. В планах посвятить целый цикл статей про иммутабельную ОС Flatcar Linux.
В заключение
Наверное, можно считать всю эту конструкцию неким велосипедом. Но в эксплуатации и передачи коллегам такого велосипеда на поддержку стало сразу ясно, что он простой как «рама+колеса+педали+руль», и ездить на нем удобно. И почти в обслуживании не нуждается.
А лучшей похвалой этой статье станет мысль читающего — “Интересное решение! Надо попробовать!”.
pepemon
Не сочтите за невежество, но Consul нужно выкатывать по всей инфре с агентом на каждой ноде, всё остальное — негуманный анти-паттерн. Consul для такого не предназначен и даже небольшая часть его потенциала не может быть реализована (конечно, возможно его использование в качестве распределенного KV или механизма для распределённых локов, но моё субъективное мнение — в таком случае лучше взять etcd). Имея Consul на всём флоте, не нужно будет писать свои решения, можно использовать стандартные и существующие из коробки service definitions с healthcheck'aми и прочими радостями. При таком подходе на Consul'e можно строить что угодно, хоть DNS для инфры (для кэша лучше взять что-нибудь отдельное спереди, дабы не нагружать Consul-сервера и не вешать их на 53-й порт), хоть RR-балансировку (я, например, так обеспечиваю HA для куберовских
kube-apiserver
). C queries вообще можно реализовывать ещё более интересные вещи, я делал возможность через DNS получить текущего PostgreSQL-мастера в кластере Patroni.Плюс, учитывая что у Prometheus есть конвенция, что метрики обычно отдаются по пути
/metrics
, я в SD просто забираю все сервисы, у которых есть тэгscrapable
и ставлю именем job-лейбла имя сервиса через обычный релэйблинг. Однимjob_name
сразу всех зайцев, эдакий DRY.MaximZalysin Автор
Спасибо за изъявление своей мысли.
Такого рода комментарий был самым ожидаемым при написании статьи. Я описал именно опытное решение, которое позволило в минимум телодвижений организовать удобный service discovery в Prometheus — «20% усилий дают 80% результата».
У меня сохраняется уверенность что такое же «местечковое» применение Consul было и есть не только у меня в инфраструктуре — когда Consul запускается чтобы решить конкретную задачу, типо кластеризации Traefik. В итоге этот же инстанс легко обрел дополнительный функционал в роли service discovery для Prometheus.
Пока статью писал задумался — почему бы и в правду не попробовать Consul на всю катушку. И сейчас анализирую рентабельность его применения.
pepemon
Очень рекомендую, я тоже изначально в некотором роде следовал принципу Парето — нужно было что-то для организации динамического автоконфигурируемого DNS для VPC, взял Consul. После того как начал знакомиться с ним ближе, просто был ошарашен насколько продуманно он сделан и какой пласт задач способен решать. Не зря всё-таки в нашей индустрии вокруг HashiCorp сложился в некотором роде культ, они делают нереально крутые вещи.
kt97679
Прежде чем делать consul tier1 сервисом крайне рекомендую оценить риски и в частности рассмотреть ситуацию, когда консул нужно остановить на всех машинах. Такое может произойти если возникнет проблема в raft-е. Это не надуманная ситуация, в инфраструктуре, с которой я сейчас работаю, за последние 3 недели эта проблема возникала дважды и каждый раз починка занимала несколько часов и привлечение людей из других команд, которые завязаны на консул. Наша гипотеза на данный момент состоит в том, что на одной машине шалит память и как следствие консул кладет в raft битые данные, что приводит к коллапсу всего кластера. Мы начали работать непосредственно с hashicorp, чтобы разобраться с ситуацией, но пока что ясности нет. У нас консул используется исключительно для service discovery в связке с consul-template. Мне страшно подумать как бы мы чинили проблему, если бы на консул был завязан еще и днс.
pepemon
Я правильно понимаю, что у вас Consul Enterprise с честным суппортом? Если нет, то поделитесь пожалуйста issue на GitHub, очень интересно посмотреть на кейс, у меня разваливался консенсус серверов пару раз, но чтобы Raft наедался, такого не было.
kt97679
Покупка Consul Enterprise обсуждается прямо сейчас. issue на GitHub, к сожалению зафайлить не могу, потому что не обладаю всей информацией, разбираются в проблеме другие люди. Проблема проявляется в том, что в рафт попадают огромное бинарные блобы, от которых на мастерах начинает увеличиваться потребление памяти и загрузка процессора. Это в частности приводит к тому, что операции с консулом становятся ощутимо медленными. Если в обозримом будущем мы получим полезный ответ от hashicorp я отпишусь здесь.