本ページの位置づけについて
ロードマップ上のPhase 2完成条件(DoD)は「FortiGate/Web/DB/OSの全ログがLokiでリアルタイム検索できること」「攻撃検知・自律再起動時に数秒で通知が届くこと」の2点。本ページはその両方を実機で確認できた記録である。一方、Grafanaの外部公開(Caddy経由・authentik SSO連携)とRocky側メトリクス収集は、境界ファイアウォールの既知不具合により未達のまま保留とした(9章参照)。
目次
1. 全体構成
FortiGate(syslog転送)
│
▼
AlmaLinux 9(192.168.2.3)= 観測基盤の集約先
├─ Prometheus(メトリクス、ポート9090)
├─ Loki(ログストア、ポート3100、保持30日)
├─ Grafana(可視化・アラート、ポート3000)
├─ Promtail(自ホストのjournald + syslog中継ログを収集)
└─ node-exporter(自ホストのメトリクス)
▲
│ ログをpush(Loki 3100番)
│
Rocky Linux 9(172.16.1.2)= Web/メールサーバー本体
├─ Promtail(journald + Apache + メールログ + fail2banログを収集)
└─ node-exporter(メトリクス、現状AlmaLinuxから到達不可)
Prometheus/Loki/Grafanaは1台(AlmaLinux)に集約し、各ホストには軽量なエージェント(Promtail・node-exporter)だけを配置する構成とした。Grafana/Prometheus/LokiはPodman Quadlet(/etc/containers/systemd/*.container)で構築している。
2. 構築手順
実際の作業はStage 0〜7の段階適用で進めた(Rocky Linuxのパッチ適用と同じ、低リスクから高リスクへの順)。全コンポーネントをPodman Quadlet(/etc/containers/systemd/*.container)で構築し、設定ファイルは/srv/containers/observability/{prometheus,loki,promtail,grafana}/配下に置いている。
| Stage | 内容 | 結果 |
|---|---|---|
| 0 | 設定ディレクトリ作成・ベースライン記録 | 完了 |
| 1 | Prometheus + node-exporter構築 | AlmaLinux完了/Rockyは境界FW不具合で保留 |
| 2 | Loki構築(ホットストレージのみ) | 完了 |
| 3 | Promtail構築(両ホスト) | 完了 |
| 4 | FortiGateログ連携(syslog中継) | 完了 |
| 5 | Grafana構築・データソース登録 | 内部完全動作/外部公開は保留 |
| 6 | systemd自律復旧の整備 | 完了 |
| 7 | Slack通知パイプライン構築 | 完了・実機検証済み(設定手順は観測基盤運用ガイドを参照) |
Prometheus + node-exporter
Prometheusの設定ファイル(prometheus.yml)。自ホストのnode-exporterはhost.containers.internalで指定する(コンテナが素のbridgeネットワークのため、localhostではコンテナ自身を指してしまい失敗する)。
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'node-exporter'
static_configs:
- targets: ['host.containers.internal:9100']
labels:
host: 'almalinux'
- targets: ['<Rockyのアドレス>:9100']
labels:
host: 'rocky'
node-exporterのQuadlet定義(両ホスト共通)。Network=hostでホストのメトリクスを直接取得し、systemdサービス監視のためD-Busソケットもマウントする。
[Container]
Image=docker.io/prom/node-exporter:v1.8.2
Network=host
Volume=/proc:/host/proc:ro
Volume=/sys:/host/sys:ro
Volume=/:/rootfs:ro,rslave
Volume=/var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket:ro
Exec=--path.procfs=/host/proc --path.sysfs=/host/sys --path.rootfs=/rootfs --collector.systemd
[Service]
Restart=on-failure
RestartSec=5
Rocky側はSELinuxでD-Bus接続が拒否される
上記のD-Busマウントを追加しても、Rocky(SELinux Enforcing)側ではpermission deniedで失敗する。[Container]セクションにSecurityLabelDisable=trueを追加することで解決した。
Loki
ローカルファイルシステム+boltdb-shipper構成、保持期間30日(720h)のホットストレージのみ。
auth_enabled: false
server:
http_listen_port: 3100
common:
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
schema_config:
configs:
- from: 2024-01-01
store: boltdb-shipper
object_store: filesystem
schema: v11
index:
prefix: index_
period: 24h
limits_config:
retention_period: 720h
compactor:
working_directory: /loki/compactor
compaction_interval: 10m
retention_enabled: true
Promtail(両ホスト)
journald(OS・コンテナログ)に加え、Rocky側はApache/メール/fail2banの各ログファイルも収集対象に含める。
clients:
- url: http://<Lokiのアドレス>:3100/loki/api/v1/push
scrape_configs:
- job_name: journal
journal:
max_age: 12h
labels:
job: systemd-journal
host: rocky
- job_name: apache
static_configs:
- targets: [localhost]
labels:
job: apache
__path__: /var/log/httpd/*log*
- job_name: mail
static_configs:
- targets: [localhost]
labels:
job: mail
__path__: /var/log/maillog
- job_name: fail2ban
static_configs:
- targets: [localhost]
labels:
job: fail2ban
__path__: /var/log/fail2ban.log
永続ジャーナルが未設定だと起動に失敗する
両ホストとも/var/log/journalが存在せず(揮発性ジャーナルのみ)、Promtailのjournalターゲットがstatfs: no such file or directoryで起動クラッシュループしていた。事前に永続化しておく。
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald
sudo journalctl --flush
FortiGateログ連携
Promtailの標準syslogターゲット(RFC5424想定)では、FortiGate独自形式のログを正しく解釈できなかった。rsyslogで一度ファイルに書き出し、Promtailの通常のファイル監視で読む方式に変更して解決した。
# AlmaLinux側:rsyslogでUDP 1514を受信しファイルへ
module(load="imudp")
input(type="imudp" port="1514")
if $fromhost-ip == '<FortiGateのアドレス>' then {
action(type="omfile" file="/var/log/fortigate.log")
stop
}
FortiGate側は「Log & Report」→「Syslog Server」でAlmaLinuxのアドレス・ポート1514・UDPを指定するだけで、追加設定は不要だった。
Grafana
データソースは手動登録ではなく、Provisioning用YAMLで宣言的に登録する。
apiVersion: 1
datasources:
- name: Loki
type: loki
url: http://localhost:3100
- name: Prometheus
type: prometheus
url: http://localhost:9090
isDefault: true
systemd自律復旧の整備
Podman Quadlet管理のコンテナ群は既定でRestart=が入っていたが、Rockyのパッケージ標準サービス(httpd/postfix/dovecot)には入っていなかった。systemdのドロップイン設定で追加した。
sudo mkdir -p /etc/systemd/system/httpd.service.d
sudo tee /etc/systemd/system/httpd.service.d/override.conf << 'EOF'
[Service]
Restart=on-failure
RestartSec=5
EOF
sudo systemctl daemon-reload
postfix・dovecotにも同様の設定を追加し、低リスクなコンテナ(wing-nginx)で実際に強制終了→自動復旧を確認した。
3. 使い方ガイド(Grafanaへのアクセスと確認方法)
構築後、実際にGrafanaの画面を操作してみると迷いやすい箇所が多かったため、日常的な確認手順として整理する。
Grafanaへアクセスする
現状、外部公開(Caddy経由)は境界ファイアウォールの不具合で未達のため、SSHのローカルポートフォワードで接続する。管理端末から以下を実行し、接続したままブラウザでhttp://localhost:3000/を開く。
ssh -L 3000:localhost:3000 <ユーザー名>@192.168.2.3
ログを横断検索する(Explore)
- 左メニューの「Explore」をクリック
- 画面左上のデータソース選択(初期値は「Prometheus」)をクリックし、「Loki」に切り替える
- クエリ入力欄の右上にある「Builder」/「Code」の切り替えで「Code」を選ぶと、LogQLをテキストで直接入力できる
- 例:
{job=~"fortigate|apache|systemd-journal"}のように入力し、右上の「Run query」をクリック
FortiGate・Webサーバー・OSのログが時系列で横断表示される。
アラートルールを確認する
- 左メニューの「Alerting」→「Alert rules」で、現在登録済みのルールと状態(Normal/Firing/No data)が一覧できる
- 「Alerting」→「Contact points」で、Slack等の通知先設定を確認・編集できる(Contact point編集画面の「Test」ボタンで、実際のアラート発火を待たずにテスト通知を送れる)
- 評価間隔を変更したい場合は、「Alert rules」一覧の対象グループ行にある鉛筆アイコンから変更できる
Slackで通知を確認する
登録したSlackワークスペースの該当チャンネルに、[FIRING:N] <ルール名>という形式で投稿される。復旧すると同じスレッドに近い形で[RESOLVED]が届く。
初回セットアップ時のつまずきポイント
Grafanaの「Alert rules」新規作成画面は上から「1. ルール名」「2. クエリと条件」「3. 評価の頻度(フォルダ・評価グループ・保留期間)」「4. 通知先」の4ステップで構成されている。特に3番目のフォルダ・評価グループは初回は必ず「+ New folder」「+ New evaluation group」から新規作成が必要(2回目以降は既存のものを選べる)。
4. DoDエビデンス
DoD①:FortiGate/Web/DB/OSの全ログがLokiでリアルタイム検索できること
GrafanaのExplore画面で{job=~"fortigate|apache|systemd-journal"}を実行し、直近1時間で5,420件のログを横断確認。内訳はFortiGateのイベントログ(DHCP払い出し等)、Apacheログ、authentik-db(PostgreSQL)等のコンテナログ、両ホストのsystemd-journalすべてを含む。
DoD②:攻撃検知・自律再起動時に数秒で通知が届くこと
実機で低リスクなコンテナ(wing-nginx)を意図的に強制終了し、systemdによる自律復旧とSlack通知到達までを実測。停止指示から60秒以内にSlackへ実際の障害通知([FIRING])が到達することを確認。評価間隔を10秒に設定しているため、実際の検知自体はより短時間で行われている。
5. 落とし穴①:永続ジャーナル未設定
Error: statfs /var/log/journal: no such file or directory
Promtailのjournalターゲットがこのエラーで起動に失敗し、再起動を繰り返した。
両ホストとも、OSのジャーナルが揮発性(メモリ上のみ)で運用されており、永続ストレージ用の/var/log/journalディレクトリが存在しなかった。
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald
sudo journalctl --flush
6. 落とし穴②:観測基盤が自分自身を誤検知する無限ループ
「systemdサービスの異常終了・再起動」を検知するアラートルールが、Grafana自身・Loki自身のコンテナに対して継続的にFiringし続けた。実際には1度も再起動していないことをsystemctl show <unit> -p NRestartsで確認済み(0のまま)。
アラート判定用のログ検索クエリに含めた検索語(例:「Scheduled restart job」)を、Grafana・Lokiの両方が「実行したクエリ内容」としてそのまま自分自身のログに書き出していた。その結果、検索語を含むクエリ自身が自分にヒットし続ける自己参照ループが発生した。
クエリのラベルセレクタで、観測基盤自身のコンテナを明示的に除外した。
count_over_time({job="systemd-journal", container!~"grafana|loki"} |~ "Failed with result|Scheduled restart job" [1m])
7. 落とし穴③:No Dataが誤アラート化する
ログベースの件数集計クエリ(count_over_time)は、該当ログが0件の場合「値0のシリーズ」ではなく「シリーズが存在しない」を返すことが多く、Grafanaはこれを「No Data」として扱う。デフォルト設定のままだと、これも異常とみなされDatasourceNoDataという別のアラートが発報されてしまう(「異常が起きていない」という正常な状態なのに通知が飛ぶ)。
対応
各アラートルールの「Configure no data and error handling」で、「Alert state if no data or all values are null」をNormalに変更する。
8. 落とし穴④:境界ファイアウォールの新規ポリシー不通
AlmaLinuxからRockyのnode-exporter(メトリクス収集)、およびRocky/CaddyからAlmaLinuxのGrafana(外部公開用)への通信が、いずれも設定は完全に正しいにもかかわらず境界ファイアウォールで転送されなかった。パケットキャプチャで確認したところ、着信はしているが対向インターフェースへの転送記録が一切なかった。
ポリシー本体・アドレスオブジェクト・サービスオブジェクト・DoSポリシー・セキュリティプロファイル・セッションテーブル・ARPテーブル、いずれも確認したが問題は見つからなかった。境界ファイアウォール機体固有の内部的な不具合と推測しているが、根本原因は特定できていない。
- ポリシーの再確認・作り直し(CLI経由・GUI経由の両方)
- セッションテーブルの全クリア
- ARPテーブルの確認(実際のMACアドレスと照合、問題なし)
- 機器の完全な再起動(1件目のケースでは再起動後も改善しなかった)
Grafanaの画面だけがどうしても必要な場合は、SSHのローカルポートフォワードで境界ファイアウォールを経由せずにアクセスする(3章STEP 1参照)。Rocky側のメトリクス収集は、ログベースの監視(Loki)で代替できる範囲は代替し、次フェーズで改めて調査する。
9. 落とし穴⑤:node-exporterのsystemd監視がD-Bus/SELinuxで失敗
node_scrape_collector_success{collector="systemd"} 0(失敗)。node-exporterのsystemd監視機能(サービスの起動状態をメトリクスとして公開する機能)が動作していなかった。
2段階の原因があった。①コンテナ定義にホストのD-Bus通信用ソケット(/var/run/dbus/system_bus_socket)のマウントが抜けていた。②マウントを追加してもRocky Linux側ではSELinuxの強制モードによりpermission deniedで拒否され続けた(AlmaLinux側はこの追加拒否は発生しなかった)。
# D-Busソケットのマウントを追加
Volume=/var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket:ro
# Rocky Linux側はSELinuxのコンテナラベル付けを無効化(システム全体の監視用エージェントとしての性質上、妥当な例外)
SecurityLabelDisable=true
両ホストともnode_scrape_collector_success{collector="systemd"} 1(成功)に変わったことを確認した。
ログ横断検索・自律復旧・Slack通知の実機検証達成 🎉
Prometheus/Loki/Promtail/Grafanaによる観測基盤構築から、systemdによる自律復旧、Slackへの実際の障害通知到達までを実機で確認しました。境界ファイアウォールの不具合により一部項目は未達のまま保留していますが、Phase 2の主要DoDは達成しています。全体ロードマップは「ゼロトラスト統合基盤検証ロードマップ」をご参照ください。
10. 未実施項目と次ステップ
- Lokiログのコールドストレージ(S3互換/MinIO、1年保存)への自動移送 未実施
- Grafanaの外部公開(Caddy経由)・authentik SSO連携 境界FW不具合により保留
- Rocky側node-exporterのメトリクス収集 境界FW不具合により保留
- 境界ファイアウォールの新規ポリシー不通問題の根本原因調査 未特定
今後の優先候補は、境界ファイアウォールの不具合の根本原因調査、またはコールドストレージの実装のいずれかとなる。