LINE Engineering
Blog
BitBarを利用したPrometheus通知モニタリング
現在、開発者向けの便利なサーバーモニタリングシステムと通知システムの構築に携わっています。
こんにちは。LINE Fukuokaの開発チームで働いているPaul Traylorです。LINE に入社してからの主な仕事は、内部サーバのモニタリングシステムをPrometheusやGrafanaを利用してより使い勝手の良いものに改良することです。最終的には、個々の開発者が自分の通知を簡単に設定できるようにするのが狙いです。モニタリングシステムの管理担当者という仕事柄、異なる環境に構築された沢山のサーバや様々なチャートをこまめにチェックする作業が欠かせませんが、あらゆることに目を配るのはますます大変になってきています。そこでモニタリング作業の手間を省くことができるシンプルなツールを調べてみました。中でも私が重宝しているのは、BitBarと呼ばれるものです。このツールを利用して簡単なステータスプラグインを作っておくとだいたい何でもモニタリングできますので、私が作成したプラグインについてご紹介します。
BitBarの要は、スクリプトから出力された結果を取得してカスタムメニューに表示することです。上記キャプチャ画面のように、メニューバーには現在の通知状態を示すスナップショットが表示され、そこをクリックすると現状の詳細情報が表示される仕組みにしたいと考えました。様々な環境をモニタリングしないといけないため、スクリプトが各環境をもれなくチェックできること、設定情報はスクリプトの外部に保存できること、といった点も考慮します。
:rotating_light: [0, 1, 0]
---
:warning: release Active: 0 Silenced: 0 Ignored: 3 | href=http://release.alertmanager.example/
---
:warning: beta Active: 3 Silenced: 0 Ignored: 0| href=http://beta.alertmanager.example/
DiskWillFill | href=http://beta.prometheus.example/<alert>
NginxDown job=nginx service=Foo project=api | href=http://beta.prometheus.example/<alert>
NginxDown job=nginx service=Bar project=web | href=http://beta.prometheus.example/<alert>
---
:warning: alpha Active: 0 Silenced: 0 Ignored: 0| href=http://alpha.alertmanager.example/
以下は、私がプラグインに主に採用している基本ヘッダです。Python 3でテストする際にしばしばUnicode問題が起きたため、下記のような方法でUTF-8でstdoutへ出力するようにしました。BitBarプラグインは単なるスクリプトですので、ほとんどの開発作業はコンソールで行われます。そしてコンソールで実行するときは、追加情報はなるべくstderrに出力されるようにしています。
#!/usr/local/bin/python3
import collections
import configparser
import logging
import os
import sys
import requests
if 'BitBar' in os.environ:
logging.basicConfig(level=logging.WARNING)
sys.stdout = open(sys.stdout.fileno(), mode='w', encoding='utf8')
else:
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
複数の環境をモニタリングしているので、設定情報を別の場所に保存することで多様な環境のモニタリングの設定を容易にする必要があります。デフォルトのPython ConfigParserを利用すれば、自分の設定情報が簡単に保存できます。
[release]
url = http://release.example.com
[beta]
url = http://beta.example.com
[alpha]
url = http://alpha.example.com
# Load our config file. I try to follow standards as much as possible, so I save my
# settings using the XDG Base Directory Specification
config = configparser.ConfigParser()
config.read([os.path.expanduser('~/.config/bitbar/alertmanager.ini')])
environments = [(section, config.get(section, 'url')) for section in config.sections()]
# Setup a bucket to hold our per-environment alerts
alerts = collections.defaultdict(list)
# and setup some counters
silenced = collections.defaultdict(int)
ignored = collections.defaultdict(int)
# Start by looping through each of our environments and query
# the correct Alertmanager from our configuration file.
# If we have an error while querying a server, we'll just skip it for now
# (we're probably getting email alerts for it anyways)
# I also like to add a custom user-agent to help with
# debugging where a request comes from
for env, url in environments:
try:
result = requests.get(
'{}/api/v1/alerts/groups'.format(url),
headers={'user-agent': USER_AGENT}
)
result.raise_for_status()
except:
logger.error('Error querying server %s', env)
continue
# A small helper function to handle formatting the labels from Alertmanager
def label(alert, label):
if label in alert['labels']:
if alert['labels'][label]:
return ' {}={}'.format(label, alert['labels'][label])
return ''
# Loop through each entry from Alertmanager, and build a list of our alerts
for entry in data:
if entry['blocks']:
for block in entry['blocks']:
for alert in block.get('alerts', []):
# I don't really care to see silenced alerts, so I'll skip those
# and only show them in the total count
if 'silenced' in alert:
logger.debug('Skipping silenced alert %s', alert['labels'])
silenced[env] += 1
continue
# I've been testing some heartbeat checks to ensure that
# prometheus is running well, so I want to skip my heartbeat
# checks from the output as well
if 'heartbeat' == alert['labels'].get('severity'):
logger.debug('Skipping heartbeat alert %s', alert['labels'])
ignored[env] += 1
continue
# We want to start each of our lines with the actual alert name
# being fired
_buffer = alert['labels']['alertname']
# And add to that a few specific Prometheus labels that we are
# interested in
_buffer += label(alert, 'job')
_buffer += label(alert, 'service')
_buffer += label(alert, 'project')
_buffer += ' | '
# And if we have the generatorURL (from Prometheus) then we
# want to allow directly linking to the query itself
if 'generatorURL' in alert:
_buffer += 'href=' + alert['generatorURL']
alerts[env].append(_buffer)
# Once we have processed all of our alerts from each instance of Alertmanager
# we are ready to build the actual output that will be rendered by BitBar
# We start with an Emoji of a rotating light, and then a quick formatting of
# the active alerts across each of our environments
print(':rotating_light: {}'.format(
[len(alerts[env[0]]) for env in environments]
))
# We then loop through each of our environments
for env, url in environments:
print('---')
# and we print a basic summary of that Alertmanager
print(':warning: {} Active: {} Silenced: {} Ignored: {}| href={}'.format(
env, len(alerts[env]), silenced[env], ignored[env], url
))
# And then loop through to show all the alerts. When we mess up and have
# a LOT of messages from Alertmanager, we will only show a limited number
# so that we do not make our MenuBar unreadable
if len(alerts[env]) > MAX_SHOW:
print(':bomb: Truncated error list to %s' % MAX_SHOW)
print(u'\n'.join(sorted(alerts[env][:MAX_SHOW])))
BitBarを利用するとどのようなスクリプトでもメニューバープラグインに変換できるので、様々なモニタリングを行うスクリプトが簡単に作成できます。たとえば、GitHubのレビュー待ちプルリクエストの有無をモニタリングするスクリプトを作ることも可能です。
今回ご紹介したBitBarプラグインを直接トライしてみたい方は、ぜひ下記Githubリポジトリをご確認ください。
BitBar Prometheus plugin monitoring
現在、開発者向けの便利なサーバーモニタリングシステムと通知システムの構築に携わっています。