Настройка Elasticsearch кластера для веб-приложения
Elasticsearch — распределённая поисковая система на базе Apache Lucene. Одиночный узел подходит для разработки, но в продакшене нужен кластер: для отказоустойчивости, горизонтального масштабирования и изоляции нагрузок. Настройка кластера — это не просто запуск нескольких инстансов, а понимание ролей узлов, стратегии шардирования и процедуры обновления без простоя.
Роли узлов кластера
В Elasticsearch 8.x каждый узел может выполнять несколько ролей. Для небольшого кластера (3–5 узлов) все узлы обычно выполняют все роли. Для кластера от 10 узлов — разделение обязательно.
Master-eligible node — участвует в выборах мастера, управляет состоянием кластера (cluster state): список индексов, маппинги, настройки шардов. Мастер не обрабатывает поисковые запросы. Минимум 3 master-eligible узла для кворума.
Data node — хранит шарды, выполняет поиск и агрегации. Самые ресурсоёмкие: нужны быстрые диски (NVMe) и много RAM для JVM heap и файлового кэша OS.
Coordinating node (client node) — принимает запросы от клиентов, распределяет по data-узлам, собирает результаты. Снимает нагрузку с data-узлов на фазе scatter-gather.
Ingest node — обрабатывает документы перед индексацией через ingest pipelines (парсинг, обогащение, трансформация).
Конфигурация ролей в elasticsearch.yml:
# Master-only node
node.roles: [ master ]
# Data node
node.roles: [ data, data_content, data_hot, data_warm, data_cold ]
# Coordinating only
node.roles: []
Минимальная продакшен конфигурация: 3 узла
Все три узла — master-eligible + data. Это даёт кворум (2 из 3) и хранение данных.
elasticsearch.yml для узла 1:
cluster.name: myapp-prod
node.name: es-node-01
node.roles: [master, data, ingest]
network.host: 0.0.0.0
http.port: 9200
transport.port: 9300
discovery.seed_hosts:
- es-node-01:9300
- es-node-02:9300
- es-node-03:9300
cluster.initial_master_nodes:
- es-node-01
- es-node-02
- es-node-03
path.data: /var/lib/elasticsearch
path.logs: /var/log/elasticsearch
xpack.security.enabled: true
xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.keystore.path: elastic-certificates.p12
xpack.security.transport.ssl.truststore.path: elastic-certificates.p12
На узлах 2 и 3 меняется только node.name.
cluster.initial_master_nodes указывается только при первом запуске кластера. После формирования кластера эту строку нужно убрать или закомментировать — иначе при перезапуске узла возможно split-brain.
JVM и память
Elasticsearch по умолчанию берёт 1 GB heap — для продакшена критически мало. Правило: heap = половина доступной RAM, но не более 31 GB (выше 32 GB JVM теряет compressed oops).
В /etc/elasticsearch/jvm.options.d/heap.options:
-Xms16g
-Xmx16g
Оставшаяся память уходит на файловый кэш OS — Elasticsearch активно использует mmap для чтения сегментов Lucene. На сервере с 64 GB RAM: 31 GB heap + 30+ GB под OS cache = хорошо.
Настройка системы:
# /etc/sysctl.conf
vm.max_map_count=262144
vm.swappiness=1
# /etc/security/limits.conf
elasticsearch soft memlock unlimited
elasticsearch hard memlock unlimited
elasticsearch soft nofile 65536
elasticsearch hard nofile 65536
Генерация TLS сертификатов и настройка безопасности
# Генерация CA и сертификатов для кластера
/usr/share/elasticsearch/bin/elasticsearch-certutil ca --out /etc/elasticsearch/elastic-ca.p12
/usr/share/elasticsearch/bin/elasticsearch-certutil cert \
--ca /etc/elasticsearch/elastic-ca.p12 \
--out /etc/elasticsearch/elastic-certificates.p12
# Установка пароля elastic пользователя
/usr/share/elasticsearch/bin/elasticsearch-setup-passwords auto
HTTP TLS (для клиентских подключений) — отдельный сертификат:
xpack.security.http.ssl.enabled: true
xpack.security.http.ssl.keystore.path: http.p12
Docker Compose для разработки
version: '3.8'
services:
es01:
image: docker.elastic.co/elasticsearch/elasticsearch:8.13.0
environment:
- node.name=es01
- cluster.name=dev-cluster
- discovery.seed_hosts=es02,es03
- cluster.initial_master_nodes=es01,es02,es03
- ELASTIC_PASSWORD=changeme
- xpack.security.enabled=true
- xpack.security.http.ssl.enabled=false
- xpack.security.transport.ssl.enabled=false
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
volumes:
- es01_data:/usr/share/elasticsearch/data
ports:
- "9200:9200"
es02:
image: docker.elastic.co/elasticsearch/elasticsearch:8.13.0
environment:
- node.name=es02
- cluster.name=dev-cluster
- discovery.seed_hosts=es01,es03
- cluster.initial_master_nodes=es01,es02,es03
- ELASTIC_PASSWORD=changeme
- xpack.security.enabled=true
- xpack.security.http.ssl.enabled=false
- xpack.security.transport.ssl.enabled=false
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
volumes:
- es02_data:/usr/share/elasticsearch/data
es03:
image: docker.elastic.co/elasticsearch/elasticsearch:8.13.0
environment:
- node.name=es03
- cluster.name=dev-cluster
- discovery.seed_hosts=es01,es02
- cluster.initial_master_nodes=es01,es02,es03
- ELASTIC_PASSWORD=changeme
- xpack.security.enabled=true
- xpack.security.http.ssl.enabled=false
- xpack.security.transport.ssl.enabled=false
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
volumes:
- es03_data:/usr/share/elasticsearch/data
kibana:
image: docker.elastic.co/kibana/kibana:8.13.0
environment:
- ELASTICSEARCH_HOSTS=http://es01:9200
- ELASTICSEARCH_USERNAME=kibana_system
- ELASTICSEARCH_PASSWORD=changeme
ports:
- "5601:5601"
volumes:
es01_data:
es02_data:
es03_data:
Проверка состояния кластера
# Статус кластера (green/yellow/red)
curl -u elastic:changeme http://localhost:9200/_cluster/health?pretty
# Список узлов
curl -u elastic:changeme http://localhost:9200/_cat/nodes?v
# Распределение шардов
curl -u elastic:changeme http://localhost:9200/_cat/shards?v&h=index,shard,prirep,state,node
# Неназначенные шарды и причины
curl -u elastic:changeme "http://localhost:9200/_cluster/allocation/explain?pretty"
Статус yellow означает, что все первичные шарды назначены, но часть реплик — нет. На кластере из 1 узла это нормально. На 3-узловом кластере yellow — сигнал проблемы.
ILM (Index Lifecycle Management)
Для логов и данных с временным горизонтом — обязательно настроить ILM. Без него индексы растут бесконечно.
PUT _ilm/policy/logs-policy
{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_age": "7d",
"max_size": "50gb"
},
"set_priority": { "priority": 100 }
}
},
"warm": {
"min_age": "7d",
"actions": {
"shrink": { "number_of_shards": 1 },
"forcemerge": { "max_num_segments": 1 },
"set_priority": { "priority": 50 }
}
},
"cold": {
"min_age": "30d",
"actions": {
"freeze": {},
"set_priority": { "priority": 0 }
}
},
"delete": {
"min_age": "90d",
"actions": {
"delete": {}
}
}
}
}
}
Подключение из приложения
PHP (laravel-elasticsearch / elastic/elasticsearch-php):
use Elastic\Elasticsearch\ClientBuilder;
$client = ClientBuilder::create()
->setHosts(['https://es-node-01:9200', 'https://es-node-02:9200', 'https://es-node-03:9200'])
->setBasicAuthentication('elastic', 'changeme')
->setCABundle('/path/to/ca.crt')
->build();
Клиент автоматически выполняет sniffing — обнаруживает все узлы кластера и балансирует запросы. При падении узла переключается на оставшиеся.
Python (elasticsearch-py):
from elasticsearch import Elasticsearch
es = Elasticsearch(
['https://es-node-01:9200', 'https://es-node-02:9200'],
basic_auth=('elastic', 'changeme'),
ca_certs='/path/to/ca.crt',
retry_on_timeout=True,
max_retries=3,
)
Сроки
Развёртывание 3-узлового кластера в облаке (AWS/GCP/Hetzner) с TLS, базовым ILM и мониторингом через Kibana — 3–5 рабочих дней. Миграция существующих данных из одиночного инстанса — ещё 1–2 дня в зависимости от объёма. Настройка hot-warm-cold архитектуры с настройкой ролей — дополнительный день.







