宅EdgerouterXへアクセスしているIPアドレスをGrafa自naで地図上にプロットしてみた。
自宅の 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 イメージを利用してそこに必要なパッケージをインストールします。ローカルタイムを利用したいのでタイムゾーンパッケージも入れました。
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を参照してください。
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 ファイルで確認すると該当フィールドが正しいか確認に役立ちます。
#### 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を入手して登録しておきます。
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 をキーに地域情報を出力することができます。
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 イメージをそのまま利用します。
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 で利用します。
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
:
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 ファイル
FROM grafana/loki
LABEL version="1.0" "akiboi.net"="akiboI"
COPY build/local-config.yaml /etc/loki
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 ファイル
FROM victoriametrics/victoria-metrics
LABEL version="1.0" "akiboi.net"="akiboI"
prometheus.yml に promtail、victoria 追加
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 に記載されたものがキーととしてカウントアップされています。
同様に、promtail_custom_router_country、promtail_custom_router_city も検索できれば OK です。
Grafana でダッシュボード作成
以下のようなダッシュボードを作りたいと思います。
データソース
データソースは、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 にしておきます。
次に 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 のアクセスも最近多くなっていますので、こちらは課題として残しておきたいと思います。