Contents
どうもネトヲです。
当ブログは「業務用ネットワーク機器を運用するため」という目的で、自宅にサーバをたてそこで日夜動いています。
Google Search Console上は2023年末からデータが残っているので、少なくとも1年半ブログを運用していることになります。
アクセス数はピークで100件/日なので大したこと無いんですが、これはあくまで正規の手順でアクセスした件数で、直接IPアドレスを直打ちした無差別型のサイバー攻撃を受けた件数は全く分からないのが現状です。

ちゃんとその辺りの記録を見ようとすると、ログファイルを1行ずつ読んでいては日が暮れてしまいます。
なので、今回はElasitcSearchでApacheのログ可視化してしまおうという作戦です。
ElasitcSearch + Kibanaの構築
Webサーバであまりプロセスを動かしたくないので、ElasitcSearchは「運用監視サーバ」と呼んでいる別サーバにインストールします。
また、あまり環境を汚したくないので、Docker上に構築することにします。
環境構築手順はElastic公式ドキュメントをまんま実行するだけです。シンプル。
①Install Docker. Visit Get Docker to install Docker for your environment.
すでにDocker導入済みなのでスキップします。ちなみにRHEL8からはDockerは非推奨でPodmanが推奨です。
②Create a new Docker network for Elasticsearch and Kibana.
docker network create elastic
③Start an Elasticsearch container.
#メモリ割当を1GB→4GBに変更した以外はそのまま
docker run --name es01 --net elastic -p 9200:9200 -it -m 4GB docker.elastic.co/elasticsearch/elasticsearch:9.0.0
③を叩いたところ以下のエラーが発生しました。
…
{“@timestamp”:”2025-04-27T03:52:15.877Z”, “log.level”:”ERROR”, “message”:”node validation exception\n[1] bootstrap checks failed. You must address the points described in the following [1] lines before starting Elasticsearch. For more information see [https://www.elastic.co/docs/deploy-manage/deploy/self-managed/bootstrap-checks?version=9.0]\nbootstrap check failure [1] of [1]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]; for more information see [https://www.elastic.co/docs/deploy-manage/deploy/self-managed/bootstrap-checks?version=9.0#bootstrap-checks-max-map-count]”, “ecs.version”: “1.2.0”,”service.name”:”ES_ECS”,”event.dataset”:”elasticsearch.server”,”process.thread.name”:”main”,”log.logger”:”org.elasticsearch.bootstrap.Elasticsearch”,”elasticsearch.node.name”:”3ec751148616″,”elasticsearch.cluster.name”:”docker-cluster”}
…
という訳で、「vm.max_map_count」というカーネルパラメータを変更します。
やり方は色々ありますが、恒久的な変更としたいので/etc/sysctl.confを編集する方法を取りました。
#バックアップ取得
cp -p /etc/sysctl.conf /etc/sysctl.conf.org
#あとから見たときに何で変更したか分かるようにコメント付与
echo -e "Change for Elastic 20250427" >> /etc/sysctl.conf
#エラーメッセージ通りvm.max_map_countを262144に変更
echo -e "vm.max_map_count = 262144" >> /etc/sysctl.conf
#変更を即反映
sysctl -p
sysctl: /etc/sysctl.conf(11): invalid syntax, continuing...
vm.max_map_count = 262144
#変更されたか確認
sysctl -a | grep "vm.max_map_count"
vm.max_map_count = 262144
ここまでやったら再度docker runを叩くのですが、すでに「es01」コンテナが出来ている状態で同じコマンドを叩くと
docker: Error response from daemon: Conflict. The container name “/es01” is already in use by container “bcb10939605af661ac8d6f3e5b6b97fbc7ab5c604f1964a70d359265d5c94120”. You have to remove (or rename) that container to be able to reuse that name
と出るので、docker rm es01でコンテナを消します。
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
bcb10939605a docker.elastic.co/elasticsearch/elasticsearch:9.0.0 "/bin/tini -- /usr/l…" 41 minutes ago Exited (78) 41 minutes ago es01
docker rm es01
es01
そしたら再度docker runを叩きます。
ドカドカとメッセージがでてきますが、その中に
Password for the elastic user (reset with bin/elasticsearch-reset-password -u elastic): ぱすわーど
とElacticSearchの初期パスワードが発行されますのでコレをメモっておきます。
続いて
~ Copy the following enrollment token and paste it into Kibana in your browser (valid for the next 30 minutes):とーくん
とElasticSearchのトークンが発行されますのでコレもメモっておきます。
万が一逃した場合は再発行できます。手順はドキュメントに記載されてるのでここでは取扱いません。
「es01」コンテナ起動後はプロンプトが返ってこないと思いますが、いったんこのウィンドウはそのまま放置しておいて、新しいウィンドウを開き次の手順に進みます。
④Pull the Kibana Docker image.
docker pull docker.elastic.co/kibana/kibana:9.0.0
⑤Start a Kibana container.
docker run --name kib01 --net elastic -p 5601:5601 docker.elastic.co/kibana/kibana:9.0.0
…
[2025-04-27T04:16:19.867+00:00][INFO ][root] Holding setup until preboot stage is completed.
i Kibana has not been configured.
Go to http://0.0.0.0:5601/?code=347391 to get started
「kib01」コンテナについても、起動後はプロンプトが返ってこないと思いますが、いったんこのウィンドウはそのまま放置します。
メッセージ末尾の
Go to http://0.0.0.0:5601/?code=347391 to get started
をコピペしつつKibanaが動いているIPアドレスに変更し、URLをブラウザで開きます。

ここで先ほどメモっておいたトークンをぶち込みます。
正しいトークンが入力されると認証コード(Verification-code)を入力する画面が出てきます。

ここで再度新しくウィンドウを立ち上げてkibana-verification-codeを入力しコードを発行します。
今回は「kib01」コンテナ上にKibanaを構築したので、ホストOS(RHEL)上で上記コマンドを叩いても意味がありません。
なので「kib01」コンテナ上で叩く必要があります。
やり方は色々ありますが、今回は以下のやり方でコマンドを叩きコードを発行しました。
docker exec -it kib01 /usr/share/kibana/bin/kibana-verification-code
Your verification code is: xxx xxx
コードが発行できたらブラウザに戻り、コードを入力しましょう。

ElacticSearchが起動することが確認できたはずです。
そしたらUsernameを「elastic」、Passwrodはさっきメモった奴を入れてログインします。
ElacticSearchのパスワード変更
ElacticSearchへログイン後、左のタブから
Stack Management > Users
とたどっていくとパスワード変更画面が出てくるのでよしなに変更しておきます。

再起動しても起動するか確認
よくある話として「再起動すると立ち上がらない」が有ります。
なのでいったん再起動してみます。
適当なウィンドウから
docker restart kib01 es01
をたたきコンテナを再起動してみると…

はい。やっぱり上手く立ち上がってきません。
原因調査しましょう。
まずは「es01」コンテナから。
docker logs es01 | grep "WARN"
...
{"@timestamp":"2025-04-27T05:12:54.499Z", "log.level": "WARN", "message":"this node is locked into cluster UUID [hKWpHYMgRnGbbfk_6L2LmA] but [cluster.initial_master_nodes] is set to [0e04dbe110af]; remove this setting to avoid possible data loss caused by subsequent cluster bootstrap attempts; for further information see https://www.elastic.co/docs/deploy-manage/deploy/self-managed/important-settings-configuration?version=9.0#initial_master_nodes", "ecs.version": "1.2.0","service.name":"ES_ECS","event.dataset":"elasticsearch.server","process.thread.name":"main","log.logger":"org.elasticsearch.cluster.coordination.ClusterBootstrapService","elasticsearch.node.name":"0e04dbe110af","elasticsearch.cluster.name":"docker-cluster"}
...
「[cluster.initial_master_nodes] is set to [0e04dbe110af];」ということで、master_nodesの設定周りがおかしいっぽいので「elasticsearch.yml」を見てみます。
docker exec -it es01 cat /usr/share/elasticsearch/config/elasticsearch.yml
cluster.name: "docker-cluster"
network.host: 0.0.0.0
#----------------------- BEGIN SECURITY AUTO CONFIGURATION -----------------------
#
# The following settings, TLS certificates, and keys have been automatically
# generated to configure Elasticsearch security features on 27-04-2025 04:58:57
#
# --------------------------------------------------------------------------------
# Enable security features
xpack.security.enabled: true
xpack.security.enrollment.enabled: true
# Enable encryption for HTTP API client connections, such as Kibana, Logstash, and Agents
xpack.security.http.ssl:
enabled: true
keystore.path: certs/http.p12
# Enable encryption and mutual authentication between cluster nodes
xpack.security.transport.ssl:
enabled: true
verification_mode: certificate
keystore.path: certs/transport.p12
truststore.path: certs/transport.p12
# Create a new cluster with the current node only
# Additional nodes can still join the cluster later
cluster.initial_master_nodes: ["0e04dbe110af"]
#----------------------- END SECURITY AUTO CONFIGURATION -------------------------
なるほど、「cluster.initial_master_nodes」の値がコンテナID名になっているっぽいですね。であればコンテナ再起動で立ち上がらなくなったのも納得です。
直接コンテナ内でviしたいのですがviコマンドがないので、ホストOSから無理矢理書き換えます。
docker cp es01:/usr/share/elasticsearch/config/elasticsearch.yml .
vi elasticsearch.yml
cluster.initial_master_nodes: [“0e04dbe110af”]
↓
# cluster.initial_master_nodes: [“0e04dbe110af”]
cluster.initial_master_nodes: [“{ホストOSのIP}“]
と設定値を変更します。
そしてコンテナに送り返してやります。
docker cp elasticsearch.yml es01:/usr/share/elasticsearch/config/elasticsearch.yml
そしたらコンテナを再起動します。
無事上がることを確認します。

やったぜ
さも分かったようにトラブルを解決しているように見えるかもですが、実は解決までに3時間ほどかかってます。
理由としては
コンテナについても「localhost」を指定すればホストOSを参照すると思っていた
からです。
Random note
Have you wondered if you could just use http://localhost:9200 as the environment variable? The answer is no, and that’s because Kibana will just query its localhost network for port 9200, and Elasticsearch IS NOT on the same machine our Kibana is. It’ll just fail with the message: Unable to retrieve version information from Elasticsearch nodes. connect ECONNREFUSED 127.0.0.1:9200.
和訳
環境変数として http://localhost:9200 を使えるかと考えたことはありませんか?答えは「いいえ」です。Kibana はローカルホストネットワークのポート 9200 を照会するだけですが、Elasticsearch は Kibana と同じマシン上には存在しないからです。「Elasticsearch ノードからバージョン情報を取得できません。connect ECONNREFUSED 127.0.0.1:9200」というメッセージが表示され、失敗するだけです。
https://apollin.com/elasticsearch-kibana-docker-custom-ports/
つまり、コンテナ内のコンフィグに「localhost」と指定すると、ホストOSではなく、自身のコンテナにアクセスしに行きます。
なので、例えばkib01にてElasitSearchのIPアドレスを指定する際にlocalhostとすると、なんのサービスの立ち上がってない自身のコンテナの9200/HTTPSにアクセスし「Elasticにアクセスできないぞ」というエラー出るわけですね。
Kibana方は初回起動時に自動でElasticSearchを探すような設定が入っていて、elasticsearch.hostsの値が決まるのですが、elasticsearch.hostsの値を変えるとエラーになります。実はここでもハマりました(「ca_trusted_fingerprint」が関係してる?)。
今回はホストOSのIPを直書きしましたが、例えばコンテナのネットワークモードを「host」にすると、bridgeせずホストOSのNICを共有する形になるので、localhostと指定しても特段問題にならないと思います。多分…
逆に言うと、ネットワークモードを指定しないと「bridge」になるので、トラブルになるわけですねー。
いやぁ、こういったナレッジって、意外なところで業務に役立ったりするんですよね。
Logstashの構築 + データ投入
さて、ElasitcSearchとKibanaの構築が終わりましたので、続いてapcheのログを取り込むために必要な「Logstash」を構築していきます。
ちな、偉そうにいってますがLogstashを触るのは人生初になるので、イマイチよく分かってないのでCopilotに訊いてみると
Logstash とは?
Logstash は、データ収集やログ管理のためのオープンソースツールです。Elasticsearch や Kibana と組み合わせて利用される「Elastic Stack(旧称 ELK Stack)」の一部として知られており、さまざまなサーバーやアプリケーションから集めたログやメトリクスを一元的に管理、変換、保存するためのパワフルなツールです。
Logstash の役割
データの収集 (Input): Web サーバーやアプリケーションのログファイル、システムのメトリクス、さらにはデータベースや提供される API など、さまざまなデータソースから情報を収集します。たとえば、Apache のアクセスログ、システムのエラーログ、またはIOTデバイスのセンサーデータなど。
データの加工・整形 (Filter): 収集したデータは、そのままでは使いにくい場合が多いです。そこで Logstash は「フィルター」と呼ばれる仕組みで、データを分割したり、必要な情報を抽出したり、不要なデータを削除する処理を行います。たとえば、Apache のログから「クライアントの IP」や「リクエストの内容」を取り出すような処理です。
データの送信 (Output): 最終的に整形・加工されたデータは、Elasticsearch などのストレージに送信されます。送信されたデータは、Kibana を用いてグラフや地図などで可視化できるようになります。
らしいです。なるほど?
よく分かってないですが、とりあえず構築していきましょう。
例によって、環境構築手順はElastic公式ドキュメントをまんま実行するだけです。シンプル。
①Pulling the image
docker pull docker.elastic.co/logstash/logstash:9.0.0
②Pipeline Configuration
logstashコンテナ起動前に「Apacheログファイルの指定」「ログのフィルタ」「ElasticSearchへの接続」を設定するための設定ファイルを作成します。
今回は、設定ファイルをホストOS上に作成し、そのディレクトリをコンテナへマウントすることで、設定ファイルを読み込ませる感じの動きとします。
マウントは次の3つを指定しました。
- ~/logstash/pipeline/:/usr/share/logstash/pipeline/
- ~/logstash/apache_logs:/usr/share/logstash/apache_logs/
- ~/logstash/apache_logs:/usr/share/logstash/completed_log_file/
ホストOS側のディレクトリは好き勝手変えて大丈夫です。
コンテナ側も恐らく好き勝手やっちゃって大丈夫かもですが、保証は出来ません。
続いて~/logstash/pipeline/:/usr/share/logstash/pipeline/に以下のファイルを作成します。
ファイル名は「apache_access_log_ship.conf」としました。ファイル名は何でもOKかと思います。
input {
file {
# ログファイルの指定(ファイル名はよしなに変更のこと)
path => "/usr/share/logstash/apache_logs/access_log"
# ログファイルを最初から読み取り
start_position => "beginning"
file_completed_action => "log"
# なんかしらんけど必要(かも)
file_completed_log_path => "/usr/share/logstash/completed_log_file"
}
}
filter {
grok {
# COMBINEDAPACHELOG パターンでメッセージをパース
match => { "message" => "%{COMBINEDAPACHELOG}" }
}
# Grok により抽出された timestamp を @timestamp に変換
date {
match => [ "timestamp", "dd/MMM/yyyy:HH:mm:ss Z" ]
target => "@timestamp"
# オリジナルの timestamp フィールドを削除したい場合
remove_field => ["timestamp"]
}
}
output {
elasticsearch {
hosts => [ "{ホストOSのIP}:9200" ]
# ElasticSearchがHTTPS接続ならtrueに
ssl_enabled => true
# 証明書の検証をスキップ(自己署名証明書なら必須)
ssl_verification_mode => "none"
user => "{elasticのユーザ名}"
password => "{elasticのパスワード}"
# Elasticにデータ投入時のインデックス名
index => "access-%{{yyyy.MM}}"
}
}
これをlogstashコンテナ起動時に読み込ませることで、起動と同時にデータ投入が終わる感じですね。
続いて~/logstash/apache_logs:/usr/share/logstash/apache_logs/にApacheのログファイルを設置します。
そのログのフォーマットですが私はデフォルトのままです。カスタムしている場合はapache_access_log_ship.confの変更が必要かもです。
ちなみにこんな感じにの中身です。
154.81.156.54 - - [05/May/2025:14:25:03 +0900] "GET /cgi-bin/luci/;stok=/locale HTTP/1.1" 301 261 "-" "-"
154.81.156.54 - - [05/May/2025:14:25:04 +0900] "\x16\x03\x01\x05\xa8\x01" 400 226 "-"
1行目はどことは言わ(言え)ないですが、某有名ルータシリーズかどうかを判定するためのリクエストですねぇ。おもしろい。
ログファイルを設置したらコンテナをスタートします。
docker run --rm -it -v ~/logstash/pipeline/:/usr/share/logstash/pipeline/ -v ~/logstash/apache_logs:/usr/share/logstash/apache_logs -v ~/logstash/apache_logs:/usr/share/logstash/completed_log_file docker.elastic.co/logstash/logstash:9.0.0
上手くいくと
…
[2025-05-05T07:04:48,259][INFO ][logstash.javapipeline ][main] Starting pipeline {:pipeline_id=>”main”, “pipeline.workers”=>8, “pipeline.batch.size”=>125, “pipeline.batch.delay”=>50, “pipeline.max_inflight”=>1000, “pipeline.sources”=>[“/usr/share/logstash/pipeline/access_log_push.conf”, “/usr/share/logstash/pipeline/error_log_push.conf”], :thread=>”#”}]
…
が含まれるログが表示されます(error_log_push.confは無視してください)。
なんかトラブルとコンテナが落ちるので、適宜対応してください。
動作確認
Elasticの使い方はここでは取扱いません。
Elasticにアクセスし、データが投入されている事を確認できました。

ついでにDashboardを作成してみました。

ログファイルのパースが不十分で、UserAgentとか綺麗に入ってないのでもう少しapache_access_log_ship.confのチューニングが必要ですね。
あと、GeoIPとの紐付けもできるんですが、ちょっと面倒なので割愛。
思いのほか変なリクエストはないですね。
ElasticSearchを動作させた事によるリソース消費もあんまりですね。

ログファイルのレコード数が増えればキツくなる事もあるかもですが、その時はよさげなスペックのminiPCを複数買ってクラスタリングしてやりますわw
さいごに
という訳で今回はApacheのログをElasitcSearchで可視化してみました。
あえて言うことでも有りませんが、今回は「最低限動かすこと」を目標に構築しており、業務レベルでの利用となると証明書の部分やハードコードした認証情報等、結構ヤバい所が盛りだくさんです。
なので今回紹介した手順は、あくまで構築の足がかりとしての参考情報程度に留めておくようよろしくお願いします。