Elasticsearch 8.0.0 集群部署文档

📚 Elasticsearch 8.0.0 三节点集群部署文档

版本:v1.1
编写日期:2025年9月7日
目标:搭建高可用 ES 集群(3节点)
部署方式:Tarball + 自定义脚本管理
环境:Linux(Ubuntu/CentOS 类)


一、操作目标

  • 部署 3 个 Elasticsearch 8.0.0 节点,组成高可用集群
  • 实现节点间自动发现、主节点选举、数据分片分配
  • 提供统一的启停脚本,便于运维管理
  • 验证集群健康状态

二、操作环境

节点 主机名 IP 地址 角色
1 121-es1 172.31.7.121 master/data/ingest
2 122-es2 172.31.7.122 master/data/ingest
3 123-es3 172.31.7.123 master/data/ingest

所有节点配置相同,均为 master-eligible。


三、操作步骤(✅ 你执行的动作)

1. 分发软件包(所有节点)

# 在 121 上执行
scp elasticsearch-8.0.0-linux-x86_64.tar.gz 172.31.7.122:/apps
scp elasticsearch-8.0.0-linux-x86_64.tar.gz 172.31.7.123:/apps

2. 安装与目录准备(所有节点)

cd /apps
tar xf elasticsearch-8.0.0-linux-x86_64.tar.gz
ln -sv elasticsearch-8.0.0 elasticsearch

3. 创建专用用户(所有节点)

groupadd es
useradd es -g es

4. 创建数据与日志目录(所有节点)

mkdir -p /data/elasticsearch/data
mkdir -p /data/elasticsearch/logs
chown -R es:es /apps/elasticsearch
chown -R es:es /data/elasticsearch

5. 配置 elasticsearch.yml(所有节点)

vim /apps/elasticsearch/config/elasticsearch.yml

配置内容(示例:121-es1

# =================================== 集群配置 ===================================
# 集群名称(所有节点必须一致)
# 同一个网络内,相同 cluster.name 的节点会自动组成集群
cluster.name: elasticsearch


# 节点名称(每个节点必须唯一!)
# ❗ 在 node-1、node-2、node-3 上分别设置为:
#    node-1: node.name: node-1
#    node-2: node.name: node-2
#    node-3: node.name: node-3
node.name: node-1


# 节点角色定义(默认已启用 master/data/ingest)
# 可选值:master, data, ingest, remote_cluster_client, ml, voting_only 等
# 当前为通用节点,承担主节点选举、数据存储、数据预处理
node.roles: [master, data, ingest]


# =================================== 网络配置 ===================================
# 绑定的网络接口,0.0.0.0 表示监听所有网卡
network.host: 0.0.0.0

# HTTP 服务端口(默认 9200),用于 REST API
http.port: 9200

# Transport 端口(默认 9300),用于节点间通信
transport.port: 9300


# =================================== 发现与集群形成 ===================================
# 初始主节点候选列表(仅在集群首次启动时生效!)
# ❗ 首次启动后必须注释或删除此行,否则重启可能出错
# ❗ 所有 master-eligible 节点的 node.name 必须在此列表中
cluster.initial_master_nodes: ["node-1", "node-2", "node-3"]

# 集群种子主机列表(所有节点都需配置)
# 格式:["ip:port", "ip:port", ...]
# 用于节点发现和集群组建
# ❗ 所有 master-eligible 节点的 IP:9300 都应在此列出
discovery.seed_hosts: 
  - "172.31.7.121:9300"
  - "172.31.7.122:9300"
  - "172.31.7.123:9300"


# =================================== 数据与日志路径 ===================================
# 数据存储路径(建议独立磁盘)
path.data: /data/elasticsearch/data

# 日志存储路径
path.logs: /data/elasticsearch/logs


# =================================== 安全配置(测试环境关闭) ===================================
# ❌ 测试环境关闭安全功能(生产环境必须开启)
xpack.security.enabled: false

# ❌ 关闭 HTTPS 和传输层 SSL 加密(生产环境必须开启)
xpack.security.http.ssl.enabled: false
xpack.security.transport.ssl.enabled: false

# ❌ 关闭监控数据收集(可选)
xpack.monitoring.collection.enabled: false


# =================================== JVM 与性能调优(可选) ===================================
# 堆内存大小(建议不超过物理内存 50%,且不超过 32GB)
# 修改位置:config/jvm.options
# 示例:-Xms4g
#       -Xmx4g

# 文件描述符限制(需系统配置)
# 确保系统 ulimit -n >= 65536

# 最大映射数量(必须设置)
# sysctl -w vm.max_map_count=262144

🔁 注意:

  • node.name 每台机器唯一(node-1, node-2, node-3
  • cluster.initial_master_nodes 仅在首次启动时配置

6. 调整系统参数(所有节点)

echo "vm.max_map_count=262144" >> /etc/sysctl.conf
sysctl -p

四、为什么要写启动脚本?

❓ 问题

  • Elasticsearch 以 java 进程运行,直接启动不易管理
  • 需要以 es 用户运行,不能用 root 启动
  • 缺少 start / stop / status 统一接口
  • 无法快速判断进程状态和 API 可达性

✅ 解决方案:编写 start_es.sh 脚本

功能 说明
start es 用户后台启动 ES
stop 安全停止进程,支持优雅关闭
status 检查进程 + API + 集群健康
restart 重启服务

五、脚本设计思路

功能 实现方式
启动 su - es -c "nohup ./elasticsearch -d"
停止 pgrep -f elasticsearch + kill -15
状态 检查进程 + curl 测试 API + 集群健康
用户 固定为 es,避免权限问题
日志 输出彩色信息,便于识别

六、启动脚本内容(/root/start_es.sh

#!/bin/bash

# ========================================
# Elasticsearch 管理脚本
# 支持: start | stop | status
# 日志路径: /data/elasticsearch/logs
# ========================================

# 配置变量
ES_USER="es"
ES_HOME="/apps/elasticsearch"
ES_LOG_DIR="/data/elasticsearch/logs"
ES_PID_FILE="/var/run/elasticsearch.pid"  # PID 文件位置
ES_CMD="cd ${ES_HOME} && ./bin/elasticsearch -d"

# 颜色输出
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

# 确保日志目录存在(可选)
ensure_log_dir() {
    if ! su - ${ES_USER} -c "test -d ${ES_LOG_DIR}"; then
        echo -e "${YELLOW}警告: 日志目录 ${ES_LOG_DIR} 不存在,尝试创建...${NC}"
        mkdir -p ${ES_LOG_DIR}
        chown ${ES_USER}:${ES_USER} ${ES_LOG_DIR}
        chmod 755 ${ES_USER} ${ES_LOG_DIR}
    fi
}

# 检查 es 用户家目录
ensure_home_dir() {
    if [ ! -d "/home/es" ]; then
        echo "创建 es 用户家目录 /home/es"
        mkdir -p /home/es
        chown ${ES_USER}:${ES_USER} /home/es
        chmod 755 /home/es
    fi
}

# 启动 Elasticsearch
start() {
    ensure_home_dir
    ensure_log_dir

    if pgrep -u ${ES_USER} elasticsearch > /dev/null; then
        echo -e "${YELLOW}Elasticsearch 已在运行。${NC}"
        status
        exit 1
    fi

    echo "正在以用户 ${ES_USER} 启动 Elasticsearch..."
    if su - ${ES_USER} -c "${ES_CMD}"; then
        # 等待 PID 文件或进程稳定
        sleep 3
        local pid=$(pgrep -u ${ES_USER} elasticsearch)
        if [ -n "$pid" ]; then
            echo $pid > ${ES_PID_FILE}
            chown ${ES_USER}:${ES_USER} ${ES_PID_FILE} 2>/dev/null || true
            echo -e "${GREEN}✅ Elasticsearch 已成功启动,PID: ${pid}${NC}"
        else
            echo -e "${RED}❌ 启动成功但无法获取 PID,请检查日志。${NC}"
        fi
    else
        echo -e "${RED}❌ 启动失败,请检查日志: ${ES_LOG_DIR}${NC}"
        exit 1
    fi
}

# 停止 Elasticsearch
stop() {
    # 使用 -f 匹配命令行中的 elasticsearch
    local pids=($(pgrep -u ${ES_USER} -f elasticsearch))

    if [ ${#pids[@]} -eq 0 ]; then
        echo -e "${YELLOW}Elasticsearch 未运行。${NC}"
        return 0
    fi

    echo -e "${YELLOW}发现 ${#pids[@]} 个 Elasticsearch 进程:${pids[*]}${NC}"

    # 尝试优雅停止
    for pid in "${pids[@]}"; do
        echo "正在停止 PID: $pid"
        kill -15 $pid
    done

    # 等待最多 30 秒
    for i in {1..30}; do
        local running=0
        for pid in "${pids[@]}"; do
            if kill -0 $pid 2>/dev/null; then
                running=1
            fi
        done
        if [ $running -eq 0 ]; then
            echo -e "${GREEN}✅ 所有 Elasticsearch 进程已停止。${NC}"
            rm -f ${ES_PID_FILE}
            return 0
        fi
        sleep 1
    done

    # 超时,强制终止
    echo -e "${RED}超时,正在强制终止...${NC}"
    for pid in "${pids[@]}"; do
        if kill -0 $pid 2>/dev/null; then
            kill -9 $pid
            echo "已强制终止 PID: $pid"
        fi
    done
    rm -f ${ES_PID_FILE}
    echo -e "${GREEN}✅ 强制停止完成。${NC}"
}
# 查看状态:本地进程 + 集群健康
status() {
    echo -e "${YELLOW}🔍 正在检查 Elasticsearch 状态...${NC}"

    # 使用 pgrep -f 匹配命令行,支持 java -cp ... elasticsearch
    local pids=($(pgrep -u ${ES_USER} -f elasticsearch 2>/dev/null))

    # 检查是否有运行中的进程
    if [ ${#pids[@]} -gt 0 ]; then
        local main_pid=${pids[0]}
        if [ ${#pids[@]} -eq 1 ]; then
            echo -e "${GREEN}🟢 Elasticsearch 正在运行${NC}"
            echo -e "   PID: ${main_pid}"
        else
            echo -e "${YELLOW}🟡 检测到多个 Elasticsearch 进程(可能存在残留)${NC}"
            printf "   PID 列表: "
            printf '%s ' "${pids[@]}"
            echo
            echo -e "   主 PID: ${main_pid}"
        fi

        # 检查 REST API 是否响应
        local http_url="http://localhost:9200"
        echo -e "\n${YELLOW}📡 正在连接 REST API: ${http_url}${NC}"

        if ! command -v curl &> /dev/null; then
            echo -e "${RED}❌ 错误: curl 未安装,无法检查 API 状态${NC}"
            return 1
        fi

        # 尝试获取根端点
        if curl -s --connect-timeout 5 --max-time 10 "${http_url}" > /dev/null; then
            echo -e "${GREEN}✅ REST API 可访问${NC}"

            # 尝试获取集群健康状态
            echo -e "\n${YELLOW}📊 集群健康状态:${NC}"
            local health_output=$(curl -s -X GET "${http_url}/_cluster/health?pretty" 2>/dev/null)
            local status_code=$(echo "$health_output" | grep -o '"status" *: *"[^"]*"' | head -1 | cut -d '"' -f4)

            if [ -n "$status_code" ]; then
                echo "$health_output" | sed 's/^/  | /'
            else
                local error_reason=$(echo "$health_output" | grep -o '"reason" *: *[^,}]*' | head -1 | cut -d ':' -f2- | xargs || echo "unknown")
                echo "  | ❌ 集群状态异常: $error_reason"
                echo "  |    建议: 检查 elasticsearch.yml 中的 discovery 配置"
                echo "  |    常见修复: 添加 discovery.type: single-node(单节点模式)"
            fi

            # 获取节点列表
            echo -e "\n${YELLOW}🔍 集群节点信息:${NC}"
            local nodes_output=$(curl -s -X GET "${http_url}/_cat/nodes?v" 2>/dev/null)
            if echo "$nodes_output" | grep -q "master_not_discovered_exception"; then
                echo "  | ❌ 无法获取节点信息: master_not_discovered_exception"
            else
                echo "$nodes_output" | sed 's/^/  | /'
            fi

        else
            echo -e "${RED}❌ REST API 无法访问,请检查服务是否完全启动${NC}"
            echo -e "${YELLOW}💡 提示: 可能正在启动中,或配置了安全认证${NC}"
        fi

        # 显示日志尾部
        if [ -d "${ES_LOG_DIR}" ] && [ -n "$(ls ${ES_LOG_DIR}/*.log 2>/dev/null)" ]; then
            local log_file=$(ls ${ES_LOG_DIR}/*.log | head -n1)
            echo -e "\n${YELLOW}📄 最近日志 (${log_file}):${NC}"
            tail -n 5 "${log_file}" | sed 's/^/  | /'
        else
            echo -e "\n${YELLOW}📄 日志目录: ${ES_LOG_DIR} 不存在或无日志文件${NC}"
        fi

    else
        # 没有找到进程
        if [ -f "${ES_PID_FILE}" ]; then
            local old_pid=$(cat "${ES_PID_FILE}")
            echo -e "${RED}❌ Elasticsearch 未运行,但存在 PID 文件: ${old_pid}${NC}"
            echo -e "${YELLOW}💡 请检查是否残留或手动 kill 进程${NC}"
        else
            echo -e "${YELLOW}⚪ Elasticsearch 未运行${NC}"
        fi
    fi
}
# 主逻辑
case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    status)
        status
        ;;
    restart)
        stop
        sleep 2
        start
        ;;
    *)
        echo "用法: $0 {start|stop|status|restart}"
        exit 1
        ;;
esac

exit 0

赋予权限:

chmod +x /root/start_es.sh

七、启动集群(操作步骤)

1. 清空数据目录(首次启动)

rm -rf /data/elasticsearch/data/*

2. 启动所有节点(同时)

/root/start_es.sh start

八、验证与展示步骤(✅ 查看结果)

1. 检查状态(122-es2 输出)

./start_es.sh status

✅ 执行结果:

🔍 正在检查 Elasticsearch 状态...
🟡 检测到多个 Elasticsearch 进程(可能存在残留)
   PID 列表: 152116 152172
   主 PID: 152116

📡 正在连接 REST API: http://localhost:9200
✅ REST API 可访问

📊 集群健康状态:
  | {
  |   "cluster_name" : "elasticsearch",
  |   "status" : "green",
  |   "timed_out" : false,
  |   "number_of_nodes" : 3,
  |   "number_of_data_nodes" : 3,
  |   "active_primary_shards" : 0,
  |   "active_shards" : 0,
  |   "relocating_shards" : 0,
  |   "initializing_shards" : 0,
  |   "unassigned_shards" : 0,
  |   "delayed_unassigned_shards" : 0,
  |   "number_of_pending_tasks" : 0,
  |   "number_of_in_flight_fetch" : 0,
  |   "task_max_waiting_in_queue_millis" : 0,
  |   "active_shards_percent_as_number" : 100.0
  | }

🔍 集群节点信息:
  | ip           heap.percent ram.percent cpu load_1m load_5m load_15m node.role   master name
  | 172.31.7.123            8          98   1    0.98    0.28     0.11 cdfhilmrstw -      node-3
  | 172.31.7.122            8          98   2    0.75    0.31     0.15 cdfhilmrstw -      node-2
  | 172.31.7.121           17          97   1    0.56    0.22     0.15 cdfhilmrstw *      node-1

📄 最近日志 (/data/elasticsearch/logs/elasticsearch.log):
  |     at org.elasticsearch.common.util.concurrent.ThreadContext$ContextPreservingAbstractRunnable.doRun(ThreadContext.java:776)
  |     at org.elasticsearch.common.util.concurrent.AbstractRunnable.run(AbstractRunnable.java:26)
  |     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
  |     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
  |     at java.lang.Thread.run(Thread.java:833)

九、结果分析

项目 结论
集群名称 elasticsearch
节点数 ✅ 3 个节点正常加入
集群状态 green(健康)
主节点 node-1* 标记)
分片分配 unassigned_shards: 0
进程状态 ✅ 多 PID 为 Java 正常线程
日志 ✅ 无错误,线程运行正常

十、结论

  • Elasticsearch 8.0.0 三节点集群已成功部署
  • ✅ 集群状态为 green,所有节点正常通信
  • ✅ 启停脚本功能完整,支持日常运维
  • ✅ 当前为空集群,可开始写入数据

十一、后续建议

  1. 创建测试索引,验证写入与分片分配
  2. 模拟节点宕机,测试高可用性
  3. 生产环境开启安全功能
  4. 配置 Kibana 或监控系统