AlmaLinux 9 一覧へ戻る トップAlmaLinux 9 / Phase 2 Observability実践マニュアル
AlmaLinux 9 ・ ゼロトラスト統合基盤検証ロードマップ

Phase 2 Observability実践マニュアル

Prometheus/Loki/Promtail/Grafana/Slack通知:ログ・メトリクス一元可視化と自律復旧の実機記録

全ログ横断検索:達成 systemd自律復旧:達成 Slack通知:実機検証済み(60秒以内) コールドストレージ:未実施(次工程)

本ページの位置づけについて

ロードマップ上のPhase 2完成条件(DoD)は「FortiGate/Web/DB/OSの全ログがLokiでリアルタイム検索できること」「攻撃検知・自律再起動時に数秒で通知が届くこと」の2点。本ページはその両方を実機で確認できた記録である。一方、Grafanaの外部公開(Caddy経由・authentik SSO連携)とRocky側メトリクス収集は、境界ファイアウォールの既知不具合により未達のまま保留とした(9章参照)。

目次

  1. 1. 全体構成
  2. 2. 構築手順
  3. 3. 使い方ガイド(Grafanaへのアクセスと確認方法)
  4. 4. DoDエビデンス
  5. 5. 落とし穴①:永続ジャーナル未設定
  6. 6. 落とし穴②:観測基盤が自分自身を誤検知する無限ループ
  7. 7. 落とし穴③:No Dataが誤アラート化する
  8. 8. 落とし穴④:境界ファイアウォールの新規ポリシー不通
  9. 9. 落とし穴⑤:node-exporterのsystemd監視がD-Bus/SELinuxで失敗
  10. 10. 未実施項目と次ステップ

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設定ディレクトリ作成・ベースライン記録完了
1Prometheus + node-exporter構築AlmaLinux完了/Rockyは境界FW不具合で保留
2Loki構築(ホットストレージのみ)完了
3Promtail構築(両ホスト)完了
4FortiGateログ連携(syslog中継)完了
5Grafana構築・データソース登録内部完全動作/外部公開は保留
6systemd自律復旧の整備完了
7Slack通知パイプライン構築完了・実機検証済み(設定手順は観測基盤運用ガイドを参照)
STAGE 1

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を追加することで解決した。

STAGE 2

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
STAGE 3

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
STAGE 4

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を指定するだけで、追加設定は不要だった。

STAGE 5

Grafana

データソースは手動登録ではなく、Provisioning用YAMLで宣言的に登録する。

apiVersion: 1
datasources:
  - name: Loki
    type: loki
    url: http://localhost:3100
  - name: Prometheus
    type: prometheus
    url: http://localhost:9090
    isDefault: true
STAGE 6

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の画面を操作してみると迷いやすい箇所が多かったため、日常的な確認手順として整理する。

STEP 1

Grafanaへアクセスする

現状、外部公開(Caddy経由)は境界ファイアウォールの不具合で未達のため、SSHのローカルポートフォワードで接続する。管理端末から以下を実行し、接続したままブラウザでhttp://localhost:3000/を開く。

ssh -L 3000:localhost:3000 <ユーザー名>@192.168.2.3
STEP 2

ログを横断検索する(Explore)

  1. 左メニューの「Explore」をクリック
  2. 画面左上のデータソース選択(初期値は「Prometheus」)をクリックし、「Loki」に切り替える
  3. クエリ入力欄の右上にある「Builder」/「Code」の切り替えで「Code」を選ぶと、LogQLをテキストで直接入力できる
  4. 例:{job=~"fortigate|apache|systemd-journal"} のように入力し、右上の「Run query」をクリック

FortiGate・Webサーバー・OSのログが時系列で横断表示される。

STEP 3

アラートルールを確認する

  1. 左メニューの「Alerting」→「Alert rules」で、現在登録済みのルールと状態(Normal/Firing/No data)が一覧できる
  2. 「Alerting」→「Contact points」で、Slack等の通知先設定を確認・編集できる(Contact point編集画面の「Test」ボタンで、実際のアラート発火を待たずにテスト通知を送れる)
  3. 評価間隔を変更したい場合は、「Alert rules」一覧の対象グループ行にある鉛筆アイコンから変更できる
STEP 4

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テーブル、いずれも確認したが問題は見つからなかった。境界ファイアウォール機体固有の内部的な不具合と推測しているが、根本原因は特定できていない。

試して効果がなかった対処
現実的な回避策

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. 未実施項目と次ステップ

今後の優先候補は、境界ファイアウォールの不具合の根本原因調査、またはコールドストレージの実装のいずれかとなる。

AlmaLinux 9 一覧へ戻る