自宅EdgerouterXへアクセスしているIPアドレスをGrafanaで地図上にプロットしてみた。

自宅のEdgerouterXにアクセスしているIPアドレスをGeoLite2+Rsyslog+Prometheus+Grafanaで地図上にプロットするダッシュボードを作って見ました。日によって違いますが、アクセスが多いのは、Netherlands、United States、China、United Kingdonm、Russiaの順ですかね。

GeoLite2を使ってルータにアタックアクセスしているIPを元に地域を割り出しGrafanaで表示したいと思います。以前、grok-exporterに自前でmmdlookupを組み込んで実現したことはありますが、今回はrsyslog-mmdblookupを利用してさっと実現したいと思います。

EdgerouterXの監視そのものはsnmp-exporterを使ってトラフィック量など監視していますが、今回は、EdgerouterXのシステムログを解析して実現することにします。

前回、PrometheusとGrafanaをインストール済みなので、今回は、rsyslog、promtailを追加したいと思います。

コンテナの構成!

なおvictoria(Victoria Metrics)とLokiも合わせて追加します。victoriaはprometheusのデータ永続化を狙って入れてます。Grafanaもpromethus経由ではなく今回からvictoria経由にします。lokiは、ログの確認に利用します。

全体構成!

rsyslogコンテナの作成

rsyslogは豊富な機能と高度な処理を高速にできる素晴らしいソフトウェアだと思います。AlpineにGeoLite2を利用するためのモジュールを組み込んでコンテナ化したいと思います。コンテナはFedora CoreOSのPodmanで構築していきます。

ビルド用フォルダ

nginx_rule.datは別途nginxのログも解析したいと思いますのでrsyslogで取り込めるように入れてあります。

backend
:
├── rsyslog
│   ├── Dockerfile
│   └── build
│       ├── nginx_rule.dat
│       ├── router_rule.dat
│       └── rsyslog.conf

rsyslog Dockerfile

Alpineイメージを利用してそこに必要なパッケージをインストールします。ローカルタイムを利用したいのでタイムゾーンパッケージも入れました。

backend/rsyslog/Dockerfile
FROM alpine
LABEL version="1.0" "akiboi.net"="akiboI"

RUN apk add --update --no-cache rsyslog rsyslog-mmdblookup rsyslog-mmnormalize liblognorm tzdata && \
  cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
  apk del tzdata
RUN mkdir /var/lib/geodb && \
  mkdir /etc/rsyslog.d
COPY build/rsyslog.conf /etc
COPY build/*_rule.dat /etc/rsyslog.d

#
CMD ["rsyslogd","-f","/etc/rsyslog.conf","-n"]

router_rule.datの設定

rsyslog.confファイルでアクションで使用するルールを設定します。ログからアクセス元のIPをパースしたいので、mmnormailzeモジュールを利用しています。IPのみ取り出せれば良いので他はダミーとしてます。パースのルールは、Liblognormを参照してください。

backend/rsyslog/build/router_rule.dat
rule=: [%dummy0:word% %dummy1:word% %dummy2:word% SRC=%clientip:ipv4% %dummy3:rest%

rsyslog.conf

アクセス元のIPを元にmmdblookupモジュールを使ってcontinent code、continent、country code、country、city、latitude、longitudeを取り出します。latitude、longitudeはGrafanaで地図の該当場所に表示するために利用します。

設定ファイル全文を載せていますが、関連するものは40-42行当たりになります。他はnginxのログ解析用とあとは私の環境に合わせて対象のログを取り出すために記載しています。44行はコメントアウトしていますが、テスト時にはJSONファイルで確認すると該当フィールドが正しいか確認に役立ちます。

backend/build/rsyslog.conf
#### Global directives ####

# Sets the directory that rsyslog uses for work files.
$WorkDirectory /var/lib/rsyslog

# Sets default permissions for all log files.
$FileOwner root
$FileGroup adm
$FileCreateMode 0640
$DirCreateMode 0755
$Umask 0022

#### Modules ####
module(load="imudp")
module(load="mmnormalize")module(load="mmdblookup")
#### Input ####
input(type="imudp" port="52200")

template( name="etcfile" type="string" string="/var/log/rsyslog/%hostname%/%programname%_msg.log")
template( name="locationlog" type="string" string="%timegenerated:::date-rfc3339% %hostname% %programname%: \"%$!iplocation!continent!code%\" \"%$!iplocation!continent!names!en%\" \"%$!iplocation!country!iso_code%\" \"%$!iplocation!country!names!en%\" \"%$!iplocation!city!names!en%\" \"%$!iplocation!location!latitude%\" \"%$!iplocation!location!longitude%\"%msg%\n")
template(name="all-json" type="list"){
  property(name="$!all-json")
}

if $programname == "proxy_access" or $programname == "web_access" then {
    if $msg startswith " -" or $msg startswith " 192.168.1" or $msg startswith " 127.0.0"  or $msg startswith " 24●●:●●●●:●●●●:●●●●" then {
        action(type="omfile" dynaFile="etcfile")
    }
    else {
        action(type="mmnormalize" rulebase="/etc/rsyslog.d/nginx_rule.dat")
        action(type = "mmdblookup" mmdbfile = "/var/lib/geodb/GeoLite2-City.mmdb"
             fields=["!continent!code","!continent!names!en","!country!iso_code","!country!names!en","!city!names!en","!location!latitude","!location!longitude"] key="!clientip" )
        action(type="omfile" template="locationlog" File="/var/log/rsyslog/nginx_access.log")
#        action(type="omfile" template="all-json" File="/var/log/nginx_json_access.log")
    }
} else if $hostname == "akiboiRT" and $programname == "kernel" then {
    if ($msg contains "[DSLite" or $msg contains "[PPPoE" or $msg contains "[WANv6") and not ($msg contains "DST=255.255.255.255") then {
        action(type="mmnormalize" rulebase="/etc/rsyslog.d/router_rule.dat")        action(type = "mmdblookup" mmdbfile = "/var/lib/geodb/GeoLite2-City.mmdb"            fields=["!continent!code","!continent!names!en","!country!iso_code","!country!names!en","!city!names!en","!location!latitude","!location!longitude"] key="!clientip" )        action(type="omfile" template="locationlog" File="/var/log/rsyslog/router_access.log")
#       action(type="omfile" template="all-json" File="/var/log/router_json_access.log")
    } else {
            action(type="omfile" dynaFile="etcfile")
    }
} else {
    action(type="omfile" dynaFile="etcfile")
}

rsyslogの.gitlab-ci.yml

私の開発環境のgitlab-ci.ymlを載せておきます。前回作成した他のコンテナとほとんど同じで、ビルド判断するフォルダとコンテナ名、ボリュームマッピングが違うのみです。ホスト側にマッピングしているフォルダにあらかじめGeoLite2のDBを入手して登録しておきます。

backend/.gitlab-ci.yml
grafana:
  stage: buildContainer
  only:
    changes:
      - backend/rsyslog/**/*  script:
    - HOST="xxxx@yyyy"
    - POD="backend"
    - BASEDIR="/vol"
    - HOMEDIR=`pwd`
    - WORKDIR=`echo ${HOMEDIR} | sed -e "s/^\/home\/gitlab-runner/\/vol\/gitlab-runner\/data/g"`
    - CONTAINER=rsyslog    - VOLUME="-v $BASEDIR/$CONTAINER/data:/var/lib/geodb:Z -v $BASEDIR/$CONTAINER/log:/var/log:Z "    - REGISTRY="registry.akiboi.net:5000"
    - ssh $HOST mkdir -p $BASEDIR/$CONTAINER/data $BASEDIR/$CONTAINER/log    - ssh $HOST podman build -t $REGISTRY/$CONTAINER $WORKDIR/$POD/$CONTAINER
    - ssh $HOST podman push $REGISTRY/$CONTAINER
    - ssh $HOST podman stop -i $CONTAINER
    - ssh $HOST podman rm -i $CONTAINER
    - ssh $HOST podman run -dt --pod $POD --restart=always --name $CONTAINER $VOLUME $REGISTRY/$CONTAINER

EdgerouterXでシステムログを監視サーバに転送する設定をするとアクセス元IPをキーに地域情報を出力することができます。

/var/log/rsyslog/router_access.log
2021-03-06T18:00:42.776830+09:00 akiboiRT kernel: "NA" "North America" "MX" "Mexico" "Castanos" "26.504000" "-101.156600"...
2021-03-06T18:00:45.820711+09:00 akiboiRT kernel: "NA" "North America" "MX" "Mexico" "Castanos" "26.504000" "-101.156600"...
2021-03-06T18:01:10.228675+09:00 akiboiRT kernel: "EU" "Europe" "SE" "Sweden" "Örebro" "59.274000" "15.211900"...

Promtailコンテナの作成

Promtailは、各アプリのログを読み取りログ可視化のためにLokiに転送する役割がありますが、ログからメトリックスを作成してPrometheusにAPIで提供しています。今回はrsyslogでbackendポッドにログが転送されていますので、このログを元に設定していきます。通常はアプリ側にPromtailを配置してログを転送すると思いますが、EdgerouterXに余計なものは入れたくない、rsyslogで地域情報を付加するのでこのような構成にしています。

ビルド用フォルダ

backend
:
├── promtail
│   ├── Dockerfile
│   └── build
│       └── config.yml

Promtail Dockerfile

grafana/promtailイメージをそのまま利用します。

backend/promtail/Dockerfile
FROM grafana/promtail

LABEL version="1.0" "akiboi.net"="akiboI"

COPY build/config.yml /etc/promtail

config.ymlの設定

clientsは、Lokiサーバを指定します。同じポッド内にLokiコンテナを構築しますので、転送先はlocalhost:3100となっています。scrapeステージはnginx用とrouter用を設定。地域情報を付加したログそのものだけでもよかったのですが、国別や市別などサマリで検索しやすくするために3種類用意しました。max_idle_durationを入れているのは、余計なメトリックスを増やさないために、同じキーのものが24時間アクセスなければメトリックスから外すために指定しています。メトリックスの詳しい情報はGlafanaLabsのドキュメントを参照してください。LabelsはPrometheusで検索フィールドとしても利用できますが、Grafana(Loki)でLabelsをキーにすぐにセレクトしてログを見れるので便利だと思います。大量のログから特定のものを探すのにLokiは素晴らしいと感じます。latitudeとlongitudeのLabelsは、GrafanaでWorldmap Panelで利用します。

backend/promtail/build/config.yml
server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://localhost:3100/loki/api/v1/push

scrape_configs:
  - job_name: nginx
    static_configs:
      - targets:
          - localhost
        labels:
          job: varlogs
          __path__: /var/log/rsyslog/nginx*.log
  - job_name: router
    static_configs:
      - targets:
          - localhost
        labels:
          job: varlogs
          __path__: /var/log/rsyslog/router*.log
    pipeline_stages:
      - regex:
          expression: '^(?P<time>.*) (?P<server>.*) (?P<facility>.*) "(?P<continetcd>.*)" "(?P<continent>.*)" "(?P<countrycd>.*)" "(?P<country>.*)" "(?P<city>.*)" "(?P<latitude>.*)" "(?P<longitude>.*)" .*]IN=(?P<IF>.*) OUT.*SRC=(?P<SRC>.*) DST=(?P<DST>.*) LEN.*PROTO=(?P<PROTO>.*) SPT=.*$'
      - labels:
          continetcd:
          continent:
          countrycd:
          country:
          city:
          latitude:
          longitude:
      - metrics:
          router_log_counter:
            type: Counter
            description: total number of router logs
            max_idle_duration: 24h
            config:
              match_all: true
              action: inc
      - labels:
          country:
      - metrics:
          router_log_country:
            type: Counter
            description: total number of router logs
            max_idle_duration: 24h
            source: country
            config:
              action: inc
      - labels:
          city:
      - metrics:
          router_log_city:
            type: Counter
            description: total number of router logs
            max_idle_duration: 24h
            source: city
            config:
              action: inc

rsyslog.confとこのconfig.ymlの設定が今回の肝でここさえうまく設定できればあとは単純作業でさっと終わります。

このため、他のファイルは参考に載せておきますが解説はなくてもいいでしょう。

その他のファイル

ビルド用フォルダ

backend
├── .gitlab-ci.yml ← add promtail,loki,victory├── blackbox-exporter
│   └── Dockerfile
├── grafana
│   ├── Dockerfile
│   └── build
│       └── grafana.ini
├── loki           ← add│   ├── Dockerfile ← add│   └── build
│       └── local-config.yaml ← add├── pr1
│   ├── Dockerfile
│   ├── build
│   │   ├── conf.d
│   │   │   ├── default.conf
│   │   │   ├── header.conf
│   │   │   └── log.conf
│   │   ├── nginx.conf
│   │   └── vhost.d
│   │       ├── 01_ap-Server.conf
│   │       ├── 03_test-Server.conf
│   │       └── 99_default.conf
│   └── html
│       ├── index4.html
│       └── index6.html
├── prometheus
│   ├── Dockerfile
│   └── build
│       └── prometheus.yml ← add pormtail,victoria├── promtail
│   ├── Dockerfile
│   └── build
│       └── config.yml
├── rsyslog
│   ├── Dockerfile
│   └── build
│       ├── nginx_rule.dat
│       ├── router_rule.dat
│       └── rsyslog.conf
├── snmp-exporter
│   ├── Dockerfile
│   └── build
│       └── snmp.yml
└── victoria           ← add    └── Dockerfile     ← add

.gitlab-ci.yml

backend/.gitlab-ci.yml
:
promtail:
  stage: buildContainer
  only:
    changes:
      - backend/promtail/**/*
  script:
    - HOST="xxxx@yyyy"
    - POD="backend"
    - BASEDIR="/vol"
    - HOMEDIR=`pwd`
    - WORKDIR=`echo ${HOMEDIR} | sed -e "s/^\/home\/gitlab-runner/\/vol\/gitlab-runner\/data/g"`
    - CONTAINER=promtail
    - VOLUME=" -v $BASEDIR/rsyslog/log:/var/log:ro " ← rsyslogのログを利用する。    - REGISTRY="registry.akiboi.net:5000"
    - ssh $HOST podman build -t $REGISTRY/$CONTAINER $WORKDIR/$POD/$CONTAINER
    - ssh $HOST podman push $REGISTRY/$CONTAINER
    - ssh $HOST podman stop -i $CONTAINER
    - ssh $HOST podman rm -i $CONTAINER
    - ssh $HOST podman run -dt --pod $POD --restart=always --name $CONTAINER $VOLUME $REGISTRY/$CONTAINER

loki:
  stage: buildContainer
  only:
    changes:
      - backend/loki/**/*
  script:
    - HOST="xxxx@yyyy"
    - POD="backend"
    - BASEDIR="/vol"
    - HOMEDIR=`pwd`
    - WORKDIR=`echo ${HOMEDIR} | sed -e "s/^\/home\/gitlab-runner/\/vol\/gitlab-runner\/data/g"`
    - CONTAINER=loki
    - VOLUME="-v $BASEDIR/$CONTAINER/data:/victoria-metrics-data:Z "
    - REGISTRY="registry.akiboi.net:5000"
    - ssh $HOST mkdir -p $BASEDIR/$CONTAINER/data
    - ssh $HOST podman build -t $REGISTRY/$CONTAINER $WORKDIR/$POD/$CONTAINER
    - ssh $HOST podman push $REGISTRY/$CONTAINER
    - ssh $HOST podman stop -i $CONTAINER
    - ssh $HOST podman rm -i $CONTAINER
    - ssh $HOST podman run -dt --pod $POD --restart=always -- name $CONTAINER $VOLUME $REGISTRY/$CONTAINER

lovictoriaki:
  stage: buildContainer
  only:
    changes:
      - backend/victoria/**/*
  script:
    - HOST="xxxx@yyyy"
    - POD="backend"
    - BASEDIR="/vol"
    - HOMEDIR=`pwd`
    - WORKDIR=`echo ${HOMEDIR} | sed -e "s/^\/home\/gitlab-runner/\/vol\/gitlab-runner\/data/g"`
    - CONTAINER=victoria
    - VOLUME=""
    - REGISTRY="registry.akiboi.net:5000"
    - ssh $HOST podman build -t $REGISTRY/$CONTAINER $WORKDIR/$POD/$CONTAINER
    - ssh $HOST podman push $REGISTRY/$CONTAINER
    - ssh $HOST podman stop -i $CONTAINER
    - ssh $HOST podman rm -i $CONTAINER
    - ssh $HOST podman run -dt --pod $POD --restart=always -- name $CONTAINER $VOLUME $REGISTRY/$CONTAINER
:

lokiファイル

backend/loki/Dockerfile
FROM grafana/loki
LABEL version="1.0" "akiboi.net"="akiboI"

COPY build/local-config.yaml /etc/loki
backend/loki/build/local-config.yaml
auth_enabled: false

server:
  http_listen_port: 3100

ingester:
  lifecycler:
    address: 127.0.0.1
    ring:
      kvstore:
        store: inmemory
      replication_factor: 1
    final_sleep: 0s
  chunk_idle_period: 5m
  chunk_retain_period: 30s
  max_transfer_retries: 0

schema_config:
  configs:
    - from: 2018-04-15
      store: boltdb
      object_store: filesystem
      schema: v11
      index:
        prefix: index_
        period: 168h

storage_config:
  boltdb:
    directory: /loki/index

  filesystem:
    directory: /loki/chunks

limits_config:
  enforce_metric_name: false
  reject_old_samples: true
  reject_old_samples_max_age: 168h

chunk_store_config:
  max_look_back_period: 0s

table_manager:
  retention_deletes_enabled: false
  retention_period: 0s

victoriaファイル

backend/vicroria/Dockerfile
FROM victoriametrics/victoria-metrics
LABEL version="1.0" "akiboi.net"="akiboI"

prometheus.ymlにpromtail、victoria追加

backend/prometheus/build/prometheus.yml
scrape_configs:
:
  - job_name: 'promtail'    static_configs:      - targets:          - localhost:9080
# Remote write configuration (for victoria)
remote_write:  - url: http://localhost:8428/api/v1/write

テスト

環境ができましたので、http://prometheusサーバ:9080にアクセスしてメトリックスを確認してみます。メトリックス名は、promtailのconfig.ymlにはprefixを設定していませんのでDefaultのpromtail_customが先頭に付きます。promtail_custom_router_log_counterと入力してExecuteボタンを押すとメトリックスが表示されればOKです。Labelsに記載されたものがキーととしてカウントアップされています。

prometheusテスト!

同様に、promtail_custom_router_country、promtail_custom_router_cityも検索できればOKです。

Grafanaでダッシュボード作成

以下のようなダッシュボードを作りたいと思います。 Grafanaダッシュボード-Worldmap Panel-!

データソース

データソースは、Prometheusではなく、victoriを立ち上げしたのでこちらから読み込むことにします。データタイプにPrometheusを選択して、以下のように設定しました。

No. 項目  設定内容 備考
1 Name Vicroria
2 URL http://localhost:8428
3 Access Server(default)

タイトルパネルの作成

タイトルは、Visualizationにtextを選んでhtmlで記載しました。

<div style="background-image: url('https://xxx.xxx.xxx/bg01.jpg')">
  <div style="
    padding: 1rem;
    color: #fff;
    font-size:32px;
    font-family: sans-serif;
    font-weight:bold;
    text-align:center;
    text-shadow:
      0 0 5px #fff,
      0 0 10px #fff,
      0 0 20px #fff,
      0 0 40px #0ff,
      0 0 60px #0ff;
    ">
    EdgeRouterX monitoring
  </div>
</div>

Traffic Mapの作成

アクセス元の場所を地図上にプロットしたいと思います。Worldmap Panelをインストールします。イントールは、grafanaのコンテナからgrafana-cliでインストールします。

$ podman exec -it grafana sh
grafana-cli plugins install grafana-worldmap-panel

メトリックスは以下のようにdelta関数で2点間の時間(Absolute time range)の差をcity,latitude,longitude単に集計して利用します。またFormatをTableにしておきます。

worldmapメトリックス!

次にPanelでVusualizationをWorldmapを選択して以下のように設定ましす。

No. 項目  設定内容 備考
1 Location Data table
2 Aggregation current
3 Table Query Format coordinates
4 Location Name Field city
5 Metric Field Value
6 Latitude Field latitude
7 Longitude Field longitude
8 Thresholds 1,10,100 お好みで
9 Colors 青、黄色、オレンジ、赤 お好みで

これで、地図上にアクセス元をプロットすることができました。なお、今回のqueryは空白のcityを除きましたが、cityが空白でもlatitude,longitudeの値が入っていることが多くありますので、最終的にはcityが空白のものもプロットするようにしました。マウスで該当箇所に持って行った時にcityがn/aと表示するだけです。無料のGeoLite2はそれほど精度は高くないと思いますので自宅で見る分は十分かと思います。

国別パネルの作成

集計していたメトリックスを使って表示させています。設定内容は以下の要領です。

No. 項目  設定内容 備考
1 Metrics sum(delta(promtail_custom_router_log_country{country!=""})) by (country) > 0 Query
2 Legend {{country}} Query
3 Format Time series Query
4 Panel title Country Traffic Panel
5 Visualization Stat Panel
6 Caluculation Max Panel
7 Fields Numeric Fields Panel
8 Text mode Auto Panel
9 Unit short Field
10 Color scheme Green-Yellow-Red(by value) Field

市別パネルの作成

集計していたメトリックスを使って表示させています。設定内容は以下の要領です。

No. 項目  設定内容 備考
1 Metrics sort_desc(sum(delta(promtail_custom_router_log_city{city!=""})) by (city) > 0) Query
2 Legend {{city}}} Query
3 Format Time series Query
4 Panel title City Traffic Panel
5 Visualization Bar gauge Panel
6 Caluculation Max Panel
7 Fields Numeric Fields Panel
8 Orientation Horizontal Panel
9 Dislplay mode Retro LCD Panel
8 Unit short Field
10 Color scheme Green-Yellow-Red(by value) Field

Request/Secパネルの作成

集計していたメトリックスを使って表示させています。設定内容は以下の要領です。

No. 項目  設定内容 備考
1 Metrics sum(rate(promtail_custom_router_log_country{country!=""}[1s])) by (country) Query
2 Legend {{country}}}} Query
3 Format Time series Query
4 Panel title Request/Sec Panel
5 Visualization Graph Panel
6 show On Panel-Axes-LeftY
7 Unit requests/sec(rps) Panel-Axes-LeftY
8 Scale log(base 10) Panel-Axes-LeftY
9 show On Panel-X-Axis
10 Mode Time Panel-X-Axis
11 Unit requests/sec(rps) Field

以上で完了です。今回は、アクセス元のIPアドレスをRsyslogを使って緯度経度に変換してGrafanのWorldmap Panelで実現してみました。マップでの表示により視覚的でわかりやすくなったと感じます。GeoIP2のIPv6対応はされているようですが、どの程度の範囲がカバーされているのか、また今回のRsyslogではIPv6にはまだ対応していないかと思われます。IPv6のアクセスも最近多くなっていますので、こちらは課題として残しておきたいと思います。



© 2021 akibo.I Blog v2.0.8