Fullmoon System

Elastic Stack構築:Rocky Linux 9・TLS・ILMの運用ガイド

EdwardMoon

Elastic Stackは4パッケージを入れるだけでは完成しません。収集経路の認証、ElasticsearchのTLSと権限、Kibanaの公開範囲、保持方針、スナップショット復旧までを1つの運用境界として設計します。Rocky Linux 9とElastic Stack 9.4.2で、再現可能な導入と検証を説明します。

演習は単一ホストですが、本番では役割と障害領域を分離します。各コマンドの目的と確認方法を示します。パスワードとAPI鍵を本文や履歴へ直接書かず、既定で有効なセキュリティを無効にしません。

Elastic Stack:TLS収集経路、3ノードElasticsearch、Kibana、ILM、外部スナップショット
TLSで保護する収集・検索・可視化と3ノード、外部バックアップ、データライフサイクルの構成

対象範囲と構成要素

要素役割運用の要点
Elasticsearch文書の索引、検索、集計、shard管理3ノードの合意、TLS、容量、shard数
Kibana検索、ダッシュボード、管理UIloopback、HTTPSプロキシ、RBAC
Logstash解析、変換、ルーティングback pressure、persistent queue、秘密情報
Filebeat/Elastic Agentログとメトリクス収集TLS信頼、フィールド方針、再送
ILMrolloverと保持期間の自動化shardサイズ、保持要件、実行状態
Snapshot/SLM外部バックアップと復旧保存先検証、復旧訓練、アクセス分離
ELKは歴史的なElasticsearch・Logstash・Kibanaの組み合わせ、Elastic StackはBeats、Agent、セキュリティ、ライフサイクル、バックアップも含む運用単位を指します。単純な収集ならLogstashを省き、AgentやFilebeatから直接送ることもできます。

事前設計と要件

Rocky Linux 9はRHEL互換ですが、商用サポートが必要ならElasticのマトリックスで正確なOS対応を確認します。各要素は同じ版へそろえます。ここでの9.4.2は例の基準で、現在の最新版という意味ではありません。導入時に公式RPMの提供とサポートを確認してください。

演習と本番を分ける

項目単一ノード演習本番の設計方針
Elasticsearch1台、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

診断順序

  1. systemd終了コードとjournalから最初のエラー時点を特定する。
  2. 予定したアドレスだけでlistenしているか確認する。
  3. CA、hostname、期限、クライアントの信頼を確認する。
  4. healthで未割当shardの理由とdisk watermarkを見る。
  5. Logstash config test、persistent queue、rejected event、Filebeat output testを見る。
  6. ILM explainとsnapshot verifyで保持・バックアップ自動化を確認する。
  7. 同入力・設定で隔離環境に再現して変更を記録する。

よくある誤り

誤り問題安全な方法
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失敗を警報化する。

関連資料

まとめ

重要なのは成功画面ではなく、安全に収集し、予測できる大きさで保持し、障害後に復旧できる運用です。同じ版の公式パッケージから始め、自動TLSと認証を維持します。最小権限、loopbackとfirewall、data streamとILM、外部snapshotを順に検証することで、単一演習を運用可能な3ノードへ拡張できます。