Хабрахабр

Простой service discovery в Prometheus через Consul

Закон Парето (принцип Парето, принцип 80/20) — «20 % усилий дают 80 % результата, а остальные 80 % усилий — лишь 20 % результата».
Wikipedia

Приветствую тебя, дорогой читатель!

Моя первая статья на Хабр посвящена простому и, надеюсь, полезному решению, сделавшим для меня сбор метрик в Prometheus с разнородных серверов удобным. Я затрону некоторые подробности, в которые многие могли не погружаться, эксплуатируя Prometheus, и поделюсь своим подходом по организации в нём легковесного service discovery.

Для этого понадобится: Prometheus, HashiCorp Consul, systemd, немного кода на Bash и осознание происходящего.

Если интересно узнать, как все это связано и как оно работает, добро пожаловать под кат.

Prometheus + Bash + Consul

Встречайте: 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.

Don’t specify default values unnecessarily: simple, minimal configuration will make errors less likely.

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.

consul.yml

# https://consul.io/docs/agent/options.html ---apiVersion: v1kind: Servicemetadata: name: consul labels: app: consulspec: selector: app: consul ports: - name: http port: 8500 ---apiVersion: apps/v1kind: StatefulSetmetadata: name: consul labels: app: consulspec: 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/v1beta1kind: Ingressmetadata: name: consul labels: app: consul annotations: traefik.ingress.kubernetes.io/frontend-entry-points: http,https traefik.ingress.kubernetes.io/redirect-entry-point: httpsspec: 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.comExecStartPre=-/usr/bin/docker rm --force %NExecStart=/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=errorExecStartPost=/opt/bin/consul-service register -e prod -n %N -p 9100 -t prometheus,node-exporterExecStop=/opt/bin/consul-service deregister -e prod -n %NExecStop=-/usr/bin/docker stop %NRestart=alwaysStartLimitInterval=0RestartSec=10KillMode=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.

В заключение

Наверное, можно считать всю эту конструкцию неким велосипедом. Но в эксплуатации и передачи коллегам такого велосипеда на поддержку стало сразу ясно, что он простой как «рама+колеса+педали+руль», и ездить на нем удобно. И почти в обслуживании не нуждается.
А лучшей похвалой этой статье станет мысль читающего — “Интересное решение! Надо попробовать!”.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть