Elastic Stack構築:Rocky Linux 9・TLS・ILMの運用ガイド
EdwardMoon
Elastic Stackは4パッケージを入れるだけでは完成しません。収集経路の認証、ElasticsearchのTLSと権限、Kibanaの公開範囲、保持方針、スナップショット復旧までを1つの運用境界として設計します。Rocky Linux 9とElastic Stack 9.4.2で、再現可能な導入と検証を説明します。
演習は単一ホストですが、本番では役割と障害領域を分離します。各コマンドの目的と確認方法を示します。パスワードとAPI鍵を本文や履歴へ直接書かず、既定で有効なセキュリティを無効にしません。

対象範囲と構成要素
| 要素 | 役割 | 運用の要点 |
|---|---|---|
| Elasticsearch | 文書の索引、検索、集計、shard管理 | 3ノードの合意、TLS、容量、shard数 |
| Kibana | 検索、ダッシュボード、管理UI | loopback、HTTPSプロキシ、RBAC |
| Logstash | 解析、変換、ルーティング | back pressure、persistent queue、秘密情報 |
| Filebeat/Elastic Agent | ログとメトリクス収集 | TLS信頼、フィールド方針、再送 |
| ILM | rolloverと保持期間の自動化 | shardサイズ、保持要件、実行状態 |
| Snapshot/SLM | 外部バックアップと復旧 | 保存先検証、復旧訓練、アクセス分離 |
事前設計と要件
Rocky Linux 9はRHEL互換ですが、商用サポートが必要ならElasticのマトリックスで正確なOS対応を確認します。各要素は同じ版へそろえます。ここでの9.4.2は例の基準で、現在の最新版という意味ではありません。導入時に公式RPMの提供とサポートを確認してください。
演習と本番を分ける
| 項目 | 単一ノード演習 | 本番の設計方針 |
|---|---|---|
| Elasticsearch | 1台、discovery.type=single-node | 少なくとも3台のmaster-eligibleを別障害領域へ配置 |
| Kibana | 同じホストの127.0.0.1 | 別ホストまたは複数インスタンスとHTTPSプロキシ |
| Logstash | ローカルpipeline | 収集量に応じた別ノードとpersistent queue |
| バックアップ | 機能確認用の別パス | クラスター外の共有・オブジェクトストレージ |
| 公開ポート | loopbackだけ | source制限、TLS、管理網分離 |
リソースと時刻同期
sudo dnf install -y curl jq
cat /etc/rocky-release
uname -r
timedatectl status
free -h
df -hT /var/lib /var/log
sysctl vm.max_map_count
ulimit -n
ElasticsearchはJVM heapだけでなくFS cacheも積極利用します。同じホストの重いLogstashはメモリー競合を招きます。まず自動heap sizingを使い、負荷試験の根拠がある場合だけXmsとXmxを同じ値へ変更します。
9.4.2のリポジトリとパッケージ
公式GPG鍵と9.x RPMリポジトリを使います。既定で無効にすると、日常のdnf updateで予期せぬメジャー変更が混ざるのを減らせます。導入前に変更点と互換性を確認します。
GPG鍵とリポジトリの登録
sudo rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch
sudo tee /etc/yum.repos.d/elastic-9.x.repo >/dev/null <<'EOF'
[elastic-9.x]
name=Elastic repository for 9.x packages
baseurl=https://artifacts.elastic.co/packages/9.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=0
type=rpm-md
EOF
sudo dnf clean metadata
sudo dnf --disablerepo='*' --enablerepo=elastic-9.x list available elasticsearch kibana logstash filebeat
同じ版を導入して固定する
STACK_VERSION='9.4.2'
sudo dnf install --enablerepo=elastic-9.x "elasticsearch-${STACK_VERSION}" "kibana-${STACK_VERSION}" "logstash-${STACK_VERSION}" "filebeat-${STACK_VERSION}"
rpm -q elasticsearch kibana logstash filebeat
sudo dnf install python3-dnf-plugin-versionlock
sudo dnf versionlock add elasticsearch kibana logstash filebeat
versionlockがなければpython3-dnf-plugin-versionlockを先に導入します。固定はパッチを永久延期する意味ではなく、スナップショット、互換性、ロールバックを検証した保守時間に明示的に解除して更新するための仕組みです。
Elasticsearch単一ノードのセキュリティ
演習ではloopbackだけへbindします。Elastic 9.xは初回開始時に認証とHTTP・transport TLSを自動構成します。無効にせず、生成CAを信頼するよう各クライアントを設定します。
elasticsearch.ymlの最小設定
次でバックアップして編集します。後続YAMLの各キーは既存ファイル内で1回だけ設定し、自動生成された認証とTLSは保持します。この短いYAMLでファイル全体を置き換えません。
sudo cp -a /etc/elasticsearch/elasticsearch.yml /etc/elasticsearch/elasticsearch.yml.before-lab
sudoedit /etc/elasticsearch/elasticsearch.yml
cluster.name: logs-lab
node.name: es01
network.host: 127.0.0.1
http.host: 127.0.0.1
transport.host: 127.0.0.1
discovery.type: single-node
sudo systemctl daemon-reload
sudo systemctl enable --now elasticsearch
sudo systemctl status elasticsearch --no-pager
既存キーがあれば重複追加せず修正します。開始失敗時に防御を無効にせず、journalとログでbootstrap check、権限、YAMLを調べます。
sudo journalctl -u elasticsearch --since '-10 min' --no-pager
sudo tail -n 100 /var/log/elasticsearch/logs-lab.log
sudo ss -lntp | grep -E ':(9200|9300)\b'
パスワード再設定とCAによるHTTPS検証
一般シェルのcurlで読めるよう公開CA証明書だけを別パスへコピーします。CA秘密鍵とtransport/http keystoreはコピーしません。
sudo install -d -m 0755 /etc/elastic-client
sudo install -o root -g root -m 0644 /etc/elasticsearch/certs/http_ca.crt /etc/elastic-client/http_ca.crt
openssl x509 -in /etc/elastic-client/http_ca.crt -noout -fingerprint -sha256
sudo /usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic -i
read -rsp 'elastic password: ' ELASTIC_PASSWORD
echo
export ELASTIC_PASSWORD
curl --fail --silent --show-error --cacert /etc/elastic-client/http_ca.crt -u "elastic:${ELASTIC_PASSWORD}" https://127.0.0.1:9200/ | jq .
unset ELASTIC_PASSWORD
証明書検証を迂回すると接続できても偽サーバーを見分けられません。CA fingerprintかCAファイルを配布し、個人パスワードよりサービス別最小権限アカウントかAPI鍵を使います。
Kibanaの登録とHTTPS公開
enrollment tokenでCAと認証情報を受け取れます。5601を直接公開せず127.0.0.1へbindし、組織証明書を持つreverse proxyでHTTPSを終端する構成が扱いやすくなります。
トークン生成と登録
sudo /usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s kibana
# 上記で出力された短期トークンを次のコマンドへ入力します。
sudo /usr/share/kibana/bin/kibana-setup --enrollment-token '<ONE_TIME_ENROLLMENT_TOKEN>'
sudo tee -a /etc/kibana/kibana.yml >/dev/null <<'EOF'
server.host: "127.0.0.1"
server.port: 5601
server.publicBaseUrl: "https://kibana.example.com"
EOF
sudo systemctl enable --now kibana
sudo journalctl -u kibana --since '-10 min' --no-pager
ドメイン、証明書パス、許可範囲は実値へ変更します。トークンは短時間だけ有効なので資料やチケットへ保存せず、露出・期限切れなら再発行します。
Nginx HTTPS reverse proxy
ドメインに合う証明書と秘密鍵を先に配置し、serverブロックを/etc/nginx/conf.d/kibana.confへ保存します。nginx.confのhttpがこのディレクトリをincludeするか確認します。
sudo dnf install -y nginx policycoreutils-utils
sudoedit /etc/nginx/conf.d/kibana.conf
# SELinux enforcingでNginxからloopbackのKibanaへの接続を許可します。
sudo setsebool -P httpd_can_network_connect on
server {
listen 443 ssl http2;
server_name kibana.example.com;
ssl_certificate /etc/pki/tls/certs/kibana-fullchain.pem;
ssl_certificate_key /etc/pki/tls/private/kibana.key;
location / {
proxy_pass http://127.0.0.1:5601;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
sudo nginx -t
sudo systemctl enable --now nginx
sudo systemctl reload nginx
# 管理網のアドレス範囲へ変更してください。
sudo firewall-cmd --permanent --new-zone=kibana-admin || true
sudo firewall-cmd --permanent --zone=kibana-admin --add-source=10.20.0.0/16
sudo firewall-cmd --permanent --zone=kibana-admin --add-service=https
sudo firewall-cmd --reload
sudo firewall-cmd --zone=kibana-admin --list-all
収集前にデータストリームとILMを準備する
ログ送信前にpolicyとtemplateを作成します。template変更は既存backing indexへ遡及しません。既存streamでは現policyを確認し、変更とrolloverを別手順で行います。ILM explainは収集後、backing indexができてから実行します。
1索引へ書き続けるとshardが巨大化し、復旧が長引きます。data streamとILMで条件に応じて新backing indexへ切り替え、期限を過ぎたデータを削除できます。例は50GBまたは1日で切り替えます。
30日保持のILM policy
read -rsp 'elastic password: ' ELASTIC_PASSWORD
echo
curl --fail --silent --show-error --cacert /etc/elastic-client/http_ca.crt -u "elastic:${ELASTIC_PASSWORD}" -X PUT https://127.0.0.1:9200/_ilm/policy/logs-30d -H 'Content-Type: application/json' -d @- <<'JSON'
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": {"max_primary_shard_size": "50gb", "max_age": "1d"}
}
},
"delete": {
"min_age": "30d",
"actions": {"delete": {}}
}
}
}
}
JSON
data stream用index template
curl --fail --silent --show-error --cacert /etc/elastic-client/http_ca.crt -u "elastic:${ELASTIC_PASSWORD}" -X PUT https://127.0.0.1:9200/_index_template/logs-app -H 'Content-Type: application/json' -d @- <<'JSON'
{
"index_patterns": ["logs-app-*"],
"priority": 500,
"data_stream": {},
"template": {
"settings": {
"index.lifecycle.name": "logs-30d",
"number_of_shards": 1,
"number_of_replicas": 1
}
}
}
JSON
1ノードでreplicas=1なら割り当て先がないためhealthはyellowです。検証限定で0にするか、本番は少なくとも2データノード以上を構成します。保持期間とrolloverは実日次量と復旧目標で計算します。
ILM状態の確認
curl --fail --silent --show-error --cacert /etc/elastic-client/http_ca.crt -u "elastic:${ELASTIC_PASSWORD}" 'https://127.0.0.1:9200/_ilm/status?pretty'
curl --fail --silent --show-error --cacert /etc/elastic-client/http_ca.crt -u "elastic:${ELASTIC_PASSWORD}" 'https://127.0.0.1:9200/.ds-logs-app-*/_ilm/explain?pretty'
LogstashとFilebeatの収集pipeline
例はFilebeatからloopbackのLogstashへ送り、LogstashがCAでElasticsearchを検証します。遠隔Beatsが5044へ接続するなら、別途サーバー・クライアント証明書を配布しsourceを限定します。
最小権限の収集アカウント
read -rsp 'elastic password: ' ELASTIC_PASSWORD
echo
read -rsp 'logstash_writer password: ' LOGSTASH_WRITER_PASSWORD
echo
curl --fail --silent --show-error --cacert /etc/elastic-client/http_ca.crt -u "elastic:${ELASTIC_PASSWORD}" -X PUT https://127.0.0.1:9200/_security/role/logstash_writer_role -H 'Content-Type: application/json' -d @- <<'JSON'
{
"cluster": ["monitor"],
"indices": [
{
"names": ["logs-app-*"],
"privileges": ["auto_configure", "create_doc"]
}
]
}
JSON
jq -n --arg password "$LOGSTASH_WRITER_PASSWORD" '{password:$password,roles:["logstash_writer_role"]}' | curl --fail --silent --show-error --cacert /etc/elastic-client/http_ca.crt -u "elastic:${ELASTIC_PASSWORD}" -X PUT https://127.0.0.1:9200/_security/user/logstash_writer -H 'Content-Type: application/json' --data-binary @-
unset LOGSTASH_WRITER_PASSWORD
Logstash構文と接続
sudo install -d -o logstash -g logstash -m 0750 /etc/logstash/certs
sudo install -o logstash -g logstash -m 0640 /etc/elasticsearch/certs/http_ca.crt /etc/logstash/certs/http_ca.crt
sudo /usr/share/logstash/bin/logstash-keystore --path.settings /etc/logstash create
sudo /usr/share/logstash/bin/logstash-keystore --path.settings /etc/logstash add ES_PASSWORD
ES_PASSWORDは演習用elasticではなく、専用の最小権限収集アカウントの値を使います。設定はkeystore変数を参照するため、秘密値が設定ファイルに残りません。
sudo tee /etc/logstash/conf.d/beats-to-elasticsearch.conf >/dev/null <<'EOF'
input {
beats { host => "127.0.0.1" port => 5044 }
}
filter {
if ![@metadata][pipeline] { mutate { add_tag => ["pipeline-default"] } }
}
output {
elasticsearch {
hosts => ["https://127.0.0.1:9200"]
user => "logstash_writer"
password => "${ES_PASSWORD}"
ssl_enabled => true
ssl_certificate_authorities => ["/etc/logstash/certs/http_ca.crt"]
ecs_compatibility => "v8"
manage_template => false
ilm_enabled => false
data_stream => "true"
data_stream_type => "logs"
data_stream_dataset => "app"
data_stream_namespace => "default"
}
}
EOF
sudo chown logstash:logstash /etc/logstash/logstash.keystore
sudo chmod 0600 /etc/logstash/logstash.keystore
sudo -u logstash /usr/share/logstash/bin/logstash --path.settings /etc/logstash --config.test_and_exit
sudo systemctl enable --now logstash
Filebeat filestreamの入力と出力
sudo tee /etc/filebeat/filebeat.yml >/dev/null <<'EOF'
filebeat.inputs:
- type: filestream
id: system-messages
enabled: true
paths:
- /var/log/messages
output.elasticsearch:
enabled: false
output.logstash:
hosts: ["127.0.0.1:5044"]
logging.level: info
EOF
sudo filebeat test config -e
sudo filebeat test output -e
sudo systemctl enable --now filebeat
sudo journalctl -u filebeat --since '-10 min' --no-pager
/var/log/messagesを読めるか確認します。無条件にrootで実行せず、パッケージの実行アカウントと権限を調べます。機密、個人情報、トークンは収集前にdrop・redactし、解析失敗は専用tagとダッシュボードで観測します。
本番3ノードへの移行
3つのmaster-eligibleを別障害領域へ置きます。2台だけでは1台喪失時に過半数を維持できません。新ノードをtokenで加入させ、形成後にdiscoveryを整理します。
ノード登録とクラスター確認
loopback・single-nodeのまま追加はできません。既存ノードのdiscovery.type: single-nodeを除き、transport.hostを相互到達できる専用網へ変えます。登録はHTTPS APIも使うためhttp.hostに専用網とloopbackを含め、SANを確認します。9200は加入を行う管理ホスト、9300はノードだけに許可し、TLSは保持します。
cluster.name: logs-lab
node.name: es01
network.host: 10.20.30.11
http.host: ["127.0.0.1", "10.20.30.11"]
transport.host: 10.20.30.11
discovery.seed_hosts: ["10.20.30.11:9300", "10.20.30.12:9300", "10.20.30.13:9300"]
実際の3台のアドレスへ変え、既存キーを編集します。既存クラスターのcluster.initial_master_nodesは削除したままにします。bootstrap checkと再起動成功後、新ノードごとにtokenを発行します。新ノードも初回前に同じcluster.name、自分のnode.nameとアドレス、seed hostsを確認します。
sudo systemctl restart elasticsearch
sudo systemctl status elasticsearch --no-pager
sudo ss -lntp | grep -E ":(9200|9300)\b"
# 既存クラスターのノードで実行
sudo /usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s node
# 新ノードでパッケージ導入直後に実行
sudo /usr/share/elasticsearch/bin/elasticsearch-reconfigure-node --enrollment-token '<ONE_TIME_NODE_TOKEN>'
sudo systemctl enable --now elasticsearch
# 既存クラスターでノードと役割を確認
curl --cacert /etc/elastic-client/http_ca.crt -u "elastic:${ELASTIC_PASSWORD}" 'https://127.0.0.1:9200/_cat/nodes?v&h=name,ip,node.role,master,heap.percent,disk.avail'
cluster.initial_master_nodesは最初の選出専用です。形成後は各ノードから除き、再設定しません。再起動時に残すと別クラスターを誤bootstrapする危険があります。discovery.seed_hostsには再起動後も見つけられるmaster-eligibleを指定します。
スナップショットと復旧訓練
replicaはノード障害用のコピーで、バックアップではありません。誤削除も反映されます。完了条件には外部snapshot repository、自動SLM、実復旧試験を含めます。
共有FS repository登録例
全master/dataノードで、同じ実共有FSを同じパスへmountし、実行アカウントの読み書きを検証します。別々のローカルディレクトリに同じ名前を付けても共有にはなりません。
# 全master/dataノードのelasticsearch.ymlにある既存path.repoを、
# path.repo: ["/mnt/elastic-backup"] に設定します。
# rolling restart後にrepositoryを登録します。
curl --fail --silent --show-error --cacert /etc/elastic-client/http_ca.crt -u "elastic:${ELASTIC_PASSWORD}" -X PUT https://127.0.0.1:9200/_snapshot/prod_backup -H 'Content-Type: application/json' -d @- <<'JSON'
{
"type": "fs",
"settings": {
"location": "/mnt/elastic-backup",
"compress": true
}
}
JSON
repository検証と試験snapshot
curl --fail --silent --show-error --cacert /etc/elastic-client/http_ca.crt -u "elastic:${ELASTIC_PASSWORD}" -X POST 'https://127.0.0.1:9200/_snapshot/prod_backup/_verify?pretty'
curl --fail --silent --show-error --cacert /etc/elastic-client/http_ca.crt -u "elastic:${ELASTIC_PASSWORD}" -X PUT 'https://127.0.0.1:9200/_snapshot/prod_backup/smoke-test?wait_for_completion=true&pretty'
curl --fail --silent --show-error --cacert /etc/elastic-client/http_ca.crt -u "elastic:${ELASTIC_PASSWORD}" 'https://127.0.0.1:9200/_snapshot/prod_backup/smoke-test?pretty'
/var/lib/elasticsearchをrsyncやFS snapshotでコピーするのは対応するバックアップ方式ではありません。repositoryも外部プロセスが変更すると壊れる場合があります。別の試験クラスターへ定期復元し、index、feature state、ダッシュボードを確認します。
検証と障害診断
サービス、TLS、クラスターの一括確認
systemctl --no-pager --full status elasticsearch kibana logstash filebeat
sudo ss -lntp | grep -E ':(9200|9300|5601|5044)\b'
curl --fail --silent --show-error --cacert /etc/elastic-client/http_ca.crt -u "elastic:${ELASTIC_PASSWORD}" 'https://127.0.0.1:9200/_cluster/health?pretty'
curl --fail --silent --show-error --cacert /etc/elastic-client/http_ca.crt -u "elastic:${ELASTIC_PASSWORD}" 'https://127.0.0.1:9200/_cat/shards?v&s=state,index'
unset ELASTIC_PASSWORD
診断順序
- systemd終了コードとjournalから最初のエラー時点を特定する。
- 予定したアドレスだけでlistenしているか確認する。
- CA、hostname、期限、クライアントの信頼を確認する。
- healthで未割当shardの理由とdisk watermarkを見る。
- Logstash config test、persistent queue、rejected event、Filebeat output testを見る。
- ILM explainとsnapshot verifyで保持・バックアップ自動化を確認する。
- 同入力・設定で隔離環境に再現して変更を記録する。
よくある誤り
| 誤り | 問題 | 安全な方法 |
|---|---|---|
| securityをfalseへ変更 | 認証、権限、TLS境界の喪失 | 自動security維持とCA配布 |
| curlの証明書検証省略 | 偽証明書を検出できない | –cacertかfingerprint確認 |
| elasticで収集 | 過大なsuperuser権限 | サービス別role・userかAPI key |
| 9200・5601の全公開 | 管理APIとUIの直接露出 | loopback、管理網、reverse proxy |
| 無制限の単一index | 巨大shardと長い復旧 | data stream、rollover、ILM |
| dataディレクトリのコピー | 不整合で復旧不能なバックアップ | snapshot repositoryと復旧試験 |
| 2台master | 障害時に過半数喪失 | 3つのmaster-eligible |
更新と運用チェックリスト
- 目標版をそろえ、release noteとbreaking changeを確認する。
- 事前にhealth、未割当shard、watermark、snapshot成功を確認する。
- 1台ずつrolling upgradeし、各段階で再加入とshard復旧を待つ。
- Kibana saved object、Logstash plugin、Beats module、独自templateの対応を検証する。
- 証明書期限、keystore、API鍵ローテーション、最小権限を定期確認する。
- 収集遅延、JVM pressure、rejected request、shardサイズ、ILM・SLM失敗を警報化する。
関連資料
- Elastic公式RPM導入
- Elastic self-managedセキュリティ
- Elastic ILM
- Elastic Snapshot and Restore
- Linuxディスク・inode不足の診断
- Ansibleとローリング配布
- Linuxコンテナとrootless Podman
まとめ
重要なのは成功画面ではなく、安全に収集し、予測できる大きさで保持し、障害後に復旧できる運用です。同じ版の公式パッケージから始め、自動TLSと認証を維持します。最小権限、loopbackとfirewall、data streamとILM、外部snapshotを順に検証することで、単一演習を運用可能な3ノードへ拡張できます。