あきろぐ

いろいろめもするよ🐈🐈🐈

Zabbixで1秒間隔の監視にしたらヒストリログの肥大化により1週間でディスクフルになった

概要

先日、Zabbixの監視間隔を1秒にしたらMySQLのバイナリファイルによってディスクフルになった話を書いた。
akng-engineer.hatenablog.com
しかし、バイナリファイルの肥大化より気にしなければならなかったのは、Zabbixで収集したデータのヒストリ保存期間だった。1秒間隔の監視だから、ヒストリの保存期間は7日程にして様子見ようと思ったが、予想上にデータが蓄積するのが早く1週間でディスクを逼迫してしまった。。。検証環境だとは言え、データベース設計の重要性を思い知ったので、今回実施した対処法を記録しておきたい。

環境

  • Zabbix3.0
  • MySQL5.6
  • 割り当てディスク: 128GB

チャットのアラート「ディスクの空き残り3%」

あれ、2日前にバイナリログの保持期間変更したのに....?(やばい)
急いでdfコマンドでディスクチェックしたが、やっぱり肥大化しているのはMySQL関連のディレクトリだった。しかし、今回はバイナリログそんなに出力されていないので、Zabbixデータベースのテーブルサイズを確認した。

#ディスク使用率を確認
df -h
# Zabbixデータベースのテーブルサイズを確認(上位5つを表示)
use zabbix;
select table_name, engine, table_rows as tbl_rows, avg_row_length as rlen,  
floor((data_length+index_length)/1024/1024) as allMB,  
floor((data_length)/1024/1024) as dMB,  
floor((index_length)/1024/1024) as iMB   
from information_schema.tables  
where table_schema=database()  
order by (data_length+index_length) desc limit 5; 

Zabbixデータベースのテーブルを確認すると、historyテーブルのレコードが8億以上でサイズが60GB以上だった。ヒストリ関連のテーブルはほかにもhistory_uint, history_strがあるが、合計するとサイズは100GBを超えていた。なるほど.......(泣)

ZabbixのWebUI画面でディスク使用率の時系列グラフを見たが、1日に約15%の増加量だった。すさまじい。。。

対処策①:テーブル圧縮する

下記コマンドでヒストリ関連テーブルを圧縮しようかと思ったが、空き容量が3%のため巨大なテーブルを圧縮することはできなかったので断念。

#テーブルを圧縮する
alter table history row_format=compressed;

対処策②:ヒストリ関連のテーブルを作り直す

どうするかと思い色々調べていたところ、別テーブルを作って一部レコードを挿入するやり方が紹介されていたので試してみた。
phucnw.blogspot.com

#テーブルを複製して新しいテーブルを作る
create table history_new like history;
create table history_uint_new like history_uint;
create table history_str_new like history_str;

#元テーブルから一部レコードを新しいテーブルに流し込む
insert into history_new select * from history where clock > [unix_time];
insert into history_uint_new select * from history_uint where clock > [unix_time];
insert into history_str_new select * from history_str where clock > [unix_time];

#テーブル名を変更する
alter table history rename history_old;
alter table history_new rename history;
alter table history_uint rename history_uint_old;
alter table history_uint _new rename history_uint;
alter table history_str rename history_str_old;
alter table history_str_new rename history_str;

#一度Zabbixサーバーを立ち上げて問題ないか確認する
systemctl start zabbix-server

#旧テーブルを削除する
drop table history_old;
drop table history_uint_old;
drop table history_str_old;

#ディスク容量確認する
df -h

このやり方で、ディスク使用量が16%まで減少!!!なるほどー!
うまくいってよかった。めでたし。

【Zabbix】MySQLのバイナリファイルが肥大化してDBが立ち上がらなかった

概要

検証環境のZabbixサーバーがディスクフルでサービス停止していたため調べたところ、/data/mysql配下にmysql-bin-*ファイルが大量に作成されており、ディスク使用量が100%になっていた。なるほど。

原因

expire_logs_days設定していなかった?

いや、していた。"expire_logs_days=7"で設定しており、今までバイナリファイルによるディスクフルは発生していなかった。

じゃあ何が原因だったの?

最近、検証のためにZabbixエージェントの監視間隔を30秒⇒1秒にしていたため、バイナリファイルに書き込まれるイベント数が大幅に増加し、バイナリファイルの出力が増えたことが原因だと考えれる。4日間でディスクを50GB以上使っていたのでそれはディスク圧迫するよね。。。

そもそもmysql-binファイル何者?

公式ドキュメントを読んでみる。

バイナリログには、テーブル作成操作やテーブルデータへの変更などのデータベース変更を記述する「イベント」が格納されます。

なるほど。データのレプリケーションリカバリに使われるのがこのバイナリファイルらしい。現状、レプリケーションする予定もないし7日間もバイナリファイル保持しなくてもいいので、今回はこの期間を短くして対応したい。
dev.mysql.com

対処

そもそもMariaDBサービスが立ち上がらない

ディスクフルでサービス起動失敗するので、/data/mysql/mysql-bin*ファイルを空きディスクに移行させる。1つずつファイル移動させないとだめだったので、複数ファイル移動させてからサービス立ち上げる。

# 適当にディレクトリ作成して移動させる
mkdir /home/tmp/
mv /data/mysql/mysql-bin.xxxx /home/tmp
# ディスクに空きができたらサービス立ち上げる
systemctl restart mariadb

purgeコマンドでバイナリファイル削除する

MySQLのコマンドpurgeを使ってログファイルを消していく。rmコマンドでこのバイナリファイルを削除するとエラーになってしまうらしいので注意。。

# バイナリファイル削除
mysql -u root -p
purge master logs before 'YYYY-MM-DD hh:mm';
exit;
# ディスクの空き容量確認
df -h

この後退避させたバイナリファイルも削除を忘れずに。(これはrmコマンドで消した)

my.cnfを書き換える

バイナリファイルのサイズと出力頻度を見ると、1日に1.1GBのファイルが12回出力されていたのでディスクのサイズ的に2日以上保持しないように変更した。

# バイナリファイル保持期間変更
vi /etc/my.cnf
expire_logs_days=2
# mariaDB再起動
systemctl restart mariadb

正常にmariaDBが立ち上がれば問題なし。

追加設定

追記。
やっぱり不安だから1日の保持期間より短くしたいと思って、cronで定期的にqurgeしてくれるように設定を追加した。

vi purge_binary_log.spl
frush logs;
purge master logs before now() - interval 5 hour;
crontab -e 
0 * * * * mysql -u[username] -p[password] < purge_binary_log.sql

まとめ

Zabbixのデータ取得間隔変えるとバイナリファイルの出力も増えることが今回の学び。
おしまい。

prometheus+garfanaで遊んでみるよ

この前Prometheusを初めて使ってみたので、復習を兼ねてPrometheusで取得したメトリクスをGrafanaで可視化してみたいと思います。

Prometheus?

  • オープンソースの監視ツールです。
  • プル型なのでPrometehusサーバーが監視先サーバーに聞きまわり、監視メトリクスをかき集めているイメージです。
  • 可視化ツールのGrafanaと親和性が高いです。
  • Prometehusのデフォルトのダッシュボードがありますが、可視化には強くないので取得したメトリクスを分かりやすく可視化したい場合はGrafanaを使います。
  • セットアップが非常に簡単で、バイナリをインストールして起動させるだけで監視開始させることができます。

環境

  • CentOS7.6
  • Prometheus server v2.32
  • Prometheus node v0.16

Exporterのインストール

導入は本当に簡単です。さっと終わります。

#インストール先のディレクトリを作成する
$ mkdir /etc/prometheus
$ cd /etc/prometehus
#バイナリファイルをインストールする
$ wget https://github.com/prometheus/node_exporter/releases/download/v0.16.0/node_exporter-0.16.0.linux-amd64.tar.gz
#インストールしたバイナリを解凍する
tar xvzf node_exporter-0.16.0.linux-amd64.tar.gz
#ディレクトリ名を変更する
$ mv node_exporter-0.16.0.linux-amd64.tar.gz node_exporter
#exporterが動くかテストする
$ cd node_exporter
$ ./node_exporter

以下のようにデータ取得できていればOKです。

$ ./node_exporter
INFO[0000] Starting node_exporter (version=0.16.0, branch=HEAD, revision=d42bd70f4363dced6b77d8fc311ea57b63387e4f)  source="node_exporter.go:82"
INFO[0000] Build context (go=go1.9.6, user=root@a67a9bc13a69, date=20180515-15:52:42)  source="node_exporter.go:83"
INFO[0000] Enabled collectors:                           source="node_exporter.go:90"
INFO[0000]  - arp                                        source="node_exporter.go:97"
INFO[0000]  - bcache                                     source="node_exporter.go:97"
INFO[0000]  - bonding                                    source="node_exporter.go:97"
INFO[0000]  - conntrack                                  source="node_exporter.go:97"
INFO[0000]  - cpu                                        source="node_exporter.go:97"
INFO[0000]  - diskstats                                  source="node_exporter.go:97"
INFO[0000]  - edac                                       source="node_exporter.go:97"
INFO[0000]  - entropy                                    source="node_exporter.go:97"
INFO[0000]  - filefd                                     source="node_exporter.go:97"
INFO[0000]  - filesystem                                 source="node_exporter.g

動作確認できたら、exporterをサービス化します。

#サービスファイルの作成
$ vi /etc/systemd/system/node_exporter.service
[Unit]
Description=Node Exporter
Documentation=https://github.com/prometheus/node_exporter

[Service]
Type=simple
ExecStart=/etc/prometheus/node_exporter/node_exporter
Restart=always

[Install]
WantedBy=multi-user.target

サービスを有効にするためにデーモンをリロードし、サービスを起動する

$systemctl daemon-reload
$systemctl enable node_exporter
$systemctl start node_exporter
$systemctl status node_exporter
● prometheus_node.service - Node Exporter
   Loaded: loaded (/etc/systemd/system/prometheus_node.service; enabled; vendor preset: disabled)
   Active: active (running) since Sun 2019-07-07 06:43:19 UTC; 34min ago
     Docs: https://github.com/prometheus/node_exporter
 Main PID: 8700 (node_exporter)
   CGroup: /system.slice/prometheus_node.service
           mq8700 /etc/prometheus/node_exporter/node_exporter

Jul 07 06:43:19 localhost.localdomain node_exporter[8700]: time="2019-07-07T0...
Jul 07 06:43:19 localhost.localdomain node_exporter[8700]: time="2019-07-07T0...
Jul 07 06:43:19 localhost.localdomain node_exporter[8700]: time="2019-07-07T0...

ステータスがアクティブになっていればOKです。

Prometheus_serverのインストール

続いて、Prometheus_serverの準備をしていきます。

#prometheus serverのインストール
$ wget https://github.com/prometheus/prometheus/releases/download/v2.3.2/prometheus-2.3.2.linux-amd64.tar.gz
#バイナリファイルを解凍する
$ tar xvzf prometheus-2.3.2.linux-amd64.tar.gz
#ディレクトリ名を変更する
$ mv prometheus-2.3.2.linux-amd64 prometheus_server
$ cd prometheus_server
#設定ファイルの修正
$ vi prometheus.yml
# my global config
global:
  scrape_interval:     15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
  # scrape_timeout is set to the global default (10s).

# Alertmanager configuration
alerting:
  alertmanagers:
  - static_configs:
    - targets:
      # - alertmanager:9093

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  - job_name: 'node_exporter' #変更箇所

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    static_configs:
    - targets: ['localhost:9100']  #変更箇所

今回は同じホストでexporterを起動させているので、ターゲット先は"localhost:9100"となります。exporterによって指定するポートが違うので注意が必要です。

prometheusを起動させ、正常に監視開始するか確認します。"Server is ready to receive web requests."とうメッセージが表示されていればOKです。

$ ./prometheus
level=info ts=2019-07-07T08:12:19.556134535Z caller=main.go:222 msg="Starting Prometheus" version="(version=2.3.2, branch=HEAD, revision=71af5e29e815795e9dd14742ee7725682fa14b7b)"
level=info ts=2019-07-07T08:12:19.556251007Z caller=main.go:223 build_context="(go=go1.10.3, user=root@5258e0bd9cc1, date=20180712-14:02:52)"
level=info ts=2019-07-07T08:12:19.556276083Z caller=main.go:224 host_details="(Linux 3.10.0-957.10.1.el7.x86_64 #1 SMP Mon Mar 18 15:06:45 UTC 2019 x86_64 localhost.localdomain (none))"
level=info ts=2019-07-07T08:12:19.556296925Z caller=main.go:225 fd_limits="(soft=1024, hard=4096)"
level=info ts=2019-07-07T08:12:19.556926921Z caller=main.go:533 msg="Starting TSDB ..."
level=info ts=2019-07-07T08:12:19.559585106Z caller=web.go:415 component=web msg="Start listening for connections" address=0.0.0.0:9090
level=info ts=2019-07-07T08:12:19.570862184Z caller=main.go:543 msg="TSDB started"
level=info ts=2019-07-07T08:12:19.570898772Z caller=main.go:603 msg="Loading configuration file" filename=prometheus.yml
level=info ts=2019-07-07T08:12:19.573468239Z caller=main.go:629 msg="Completed loading of configuration file" filename=prometheus.yml
level=info ts=2019-07-07T08:12:19.573559206Z caller=main.go:502 msg="Server is ready to receive web requests."

起動確認ができたら、サービス化ファイルを作成します。

$ vi /etc/systemd/system/prometheus_server.serivice
[Unit]
Description=Prometheus
Documentation=https://prometheus.io/docs/introduction/overview/
After=network-online.target
After=network-online.target

[Service]
Type=simple
ExecStart=/etc/prometheus/prometheus_server/prometheus \
  --config.file=/etc/prometheus/prometheus_server/prometheus.yml \

[Install]
WantedBy=multi-user.target

#デーモンをリロードする
$systemctl daemon-reload
#サービスを有効にする
$systemctl enable prometheus_server
#サービスを起動する
$systemctl start prometheus_server
$systemctl status prometheus_server
● prometheus_server.service - Prometheus
   Loaded: loaded (/etc/systemd/system/prometheus_server.service; enabled; vendor preset: disabled)
   Active: active (running) since Sun 2019-07-07 08:24:42 UTC; 6s ago
     Docs: https://prometheus.io/docs/introduction/overview/
 Main PID: 16275 (prometheus)
   CGroup: /system.slice/prometheus_server.service
           mq16275 /etc/prometheus/prometheus_server/prometheus --config.file...

Jul 07 08:24:42 localhost.localdomain prometheus[16275]: level=info ts=2019-0...
Jul 07 08:24:42 localhost.localdomain prometheus[16275]: level=info ts=2019-0...
Jul 07 08:24:42 localhost.localdomain prometheus[16275]: level=info ts=2019-0...
Jul 07 08:24:42 localhost.localdomain prometheus[16275]: level=info ts=2019-0...
Jul 07 08:24:42 localhost.localdomain prometheus[16275]: level=info ts=2019-0...
Jul 07 08:24:42 localhost.localdomain prometheus[16275]: level=info ts=2019-0...
Jul 07 08:24:42 localhost.localdomain prometheus[16275]: level=info ts=2019-0...
Jul 07 08:24:42 localhost.localdomain prometheus[16275]: level=info ts=2019-0...
Jul 07 08:24:42 localhost.localdomain prometheus[16275]: level=info ts=2019-0...
Jul 07 08:24:42 localhost.localdomain prometheus[16275]: level=info ts=2019-0...

サービスがアクティブであることを確認できたらOKです。

prometheusのGUI画面を確認する

"http://[prometheus_server_IPaddress]:9100/graph"にアクセスすると、下記のような画面が表示されます。[Execuse]ボタン横のドロップダウンからグラフ化したいメトリクスを選択し、[Execuse]ボタンをクリックするとメトリクスの時系列グラフが表示されます。今回は、nodeのディスクIOの現在値を表示してみました。
f:id:akngo22:20190708214912j:plain

Grafanaにデータソース登録する

prometheusのGUI画面にてメトリクスを取得できていることを確認できたら、Grafanaにデータソース登録していきます。
Grafanaにログイン後、prometheusをデータソースに登録していきます。URLは9090ポートを指定し「Save&Test」をクリックし問題なければ登録完了となります。
f:id:akngo22:20190708215708j:plain

既存のテンプレートを使ってダッシュボートをインポートする

Grafanaは個人でダッシュボード開発している方がいるので、公式ドキュメント見るとテンプレートが豊富にあります。せっかくなので使えるものは使っていきます。今回は以下のPrometheusサーバーの状態を示すダッシュボートを使用しました。
grafana.com

上記のURLにアクセスしてダッシュボードIDをコピーした後、GrafanaGUI画面の右上「▼」から「Import Dashboard」をクリックしダッシュボードIDを入力します。
f:id:akngo22:20190814174228j:plain
後はちょこちょこっと微調整すれば簡単にダッシュボード作成できました!わーい!
f:id:akngo22:20190814174448j:plain

今回はこんな感じで。
おしまい

Grafanaのトップ画面をユーザーグループごとに変えたい

Grafana便利だよね

Grafana便利ですよね。最近はGrafanaを使って監視ダッシュボードの開発をメインにしているので、Grafana職人なのかなって思うくらい使っています。

今回は、ユーザー(グループ)ごとに見せたいダッシュボードを変えたいときの実例について書きます。管理者と閲覧者に見せる画面を変えて閲覧権限を制限したりなどができるかと思います。

やりたいこと

管理者と閲覧者のトップページ(ログイン後の画面)を変えて、アクセスできるダッシュボードに制限をかけたいと思います。
イメージは以下のようなものを想定しています。
f:id:akngo22:20190814110149p:plain

お試し環境

今回は、Grafana6.16で試しました。

手順

①管理者でログインしユーザーを作成する

「configuration」⇒「users」⇒「invite」をクリックして、必要事項を入力します。
※ユーザー名とロールは必須事項となっているので入力してください。ユーザー追加は招待形式なので、E-mailで招待をもらいたい場合はメールアドレスを入力する必要があります。
f:id:akngo22:20190813175848j:plain

②作成したユーザーを認証する

ユーザーを作成すると「Pending Invites」一覧に表示されると思うので、ユーザー右の「Conpy Invite」をクリックしコピーされたURLにアクセスします。
※おそらくURLがlocalhostになっているので、IPアドレスでGrafanaGUI画面にアクセスしている場合はURLを書き換えてください。
f:id:akngo22:20190813180611j:plain
ログインするためのパスワードを入力し「Sign Up」をクリックすると、追加したユーザーでログインできると思います。
f:id:akngo22:20190813180824j:plain
管理者のページでユーザーが「Pending Invites」からユーザー一覧に表示されていることを確認します。
f:id:akngo22:20190813180846j:plain

③チームを作成する

Grafanaはユーザーをチームというユーザーグループで管理するようになっており、チーム別にトップ画面を変更することができます。先ほど作成したユーザーは閲覧者のため、Viewerというチームを作成することとします。
「Teams」タブ⇒「New team」をクリックし、チーム名を入力した後「Create」します。
f:id:akngo22:20190813182054j:plain

チームが作成されたらユーザーを追加します。
「Add member」⇒「Add to team」でユーザーを追加していきます。
f:id:akngo22:20190814102139j:plain
一覧に表示されればOKです。
f:id:akngo22:20190814102231j:plain

④チームの設定を変更する

ここでチームのホームダッシュボードを変更します。
「Teams」タブから作成したチームをクリックします。
f:id:akngo22:20190814103426j:plain
「Settings」タブ⇒「Home dashboard」で変更可能です。
※ホームダッシュボードは、スターを付けているダッシュボードからしか選択できない仕様なので、ドロップダウンリストに表示されない場合はダッシュボードにスターを付けてから実施してください。
f:id:akngo22:20190814103542j:plain

⑤ログインして確認

作成したユーザーでログインし、ホーム画面が指定したダッシュボードになっていればOKです。ログインユーザーは閲覧権限しかないので、パネルを編集しようとしてもできないようになっています。
f:id:akngo22:20190814104609j:plain

ダッシュボードの閲覧権限変更したい

このままだとダッシュボードの権限はデフォルトのままなので、トップページ変更したとしても他のダッシュボードにアクセスできてしまいます。そのため、ダッシュボードをフォルダごとで閲覧権限を変更しておきます。(※ダッシュボードごとにも権限を変更することは可能ですが、面倒なのでフォルダごとにやります。)
イメージとしては閲覧者と管理者用のフォルダを作成し、管理者用のフォルダには閲覧者の権限を外すようにします。
f:id:akngo22:20190814113330j:plain

①フォルダを作成する

画面右上の「▼」をクリックし、「New Folder」を選択します。
フォルダ作成したらダッシュボードの格納フォルダを変更します。(ダッシュボードのSettingsから変更可能)
f:id:akngo22:20190814113547j:plain

②フォルダ権限を変更する

フォルダのSettingsから「Permissions」タブを選択し、今回は管理者用のフォルダなので閲覧者の権限を外すためViewerの「×」をクリックします。これで管理者フォルダに格納されているダッシュボードはロールがViewerの場合見れないようになりました。
f:id:akngo22:20190814114606j:plain

とはいえ全部GUIでやるのめんどくさい

めんどくさいので、GrafanaAPIでできるようにシェルスクリプト書きます。流れとしては、トークン取得からユーザー作成、チーム作成、チームにメンバー追加、ダッシュボードの検索、作成チームのトップ画面変更までをスクリプト化しました。

#!/bin/bash

#obtain api token
API_TOKEN=`curl -X POST -H "Content-Type: application/json" -d '{"name":"api-test-key","role":"Admin"}' http://[admin_user]:[admin_pass]@localhost:3000/api/auth/keys | jq -r .key`
echo $API_TOKEN

#create user
USER_ID=`curl -X POST -u '[admin_user]:[admin_pass]' -H "Content-Type: application/json" -d '{"name":"test-user","email":"test-user@localhost","login":"test-user","password":"hogehoge"}' http://localhost:3000/api/admin/users | jq -r .id`
echo $USER_ID

#create team
TEAM_ID=`curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer ${API_TOKEN}" -d '{"name":"test-team","email":"test-team@localhost"}' http://localhost:3000/api/teams | jq -r .teamId`
echo $TEAM_ID

#add team member
curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer ${API_TOKEN}" -d '{"userId":'$USER_ID'}' http://localhost:3000/api/teams/$TEAM_ID/members

#search dashboard
DASHBOARD_ID=`curl -X GET -H "Content-Type: application/json" -H "Authorization: Bearer ${API_TOKEN}" http://localhost:3000/api/search?query=VM%20information | jq -r .[].id`

#star the dashboard
curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer ${API_TOKEN}" http://localhost:3000/api/user/stars/dashboard/$DASHBOARD_ID

#update team preference
curl -X PUT -H "Content-Type: application/json" -H "Authorization: Bearer ${API_
TOKEN}" -d '{"theme":"dark","homeDashboardId":'$DASHBOARD_ID',"timezone":""}' ht
tp://localhost:3000/api/teams/$TEAM_ID/preferences

GrafanaAPIを使ってスクリプト書くとき、気を付けたいのがものによって認証方法が違うことです。APIトークンを使う(管理者権限がいらない)Bearer認証とAdmin情報が必要な(管理者権限のいる)Basic認証の2つがあります。
上記のスクリプトの場合、ユーザー作成のときはBasic認証でそれ以外はBearer認証となっています。見分け方は基本的にリクエスト例のAuthorizationがBearerかBasicかで判断することができますが、時々公式ドキュメントが間違えているのでうまくいかない場合は認証方法を変更してみてください。
grafana.com

おしまい。