Skip to content

Instantly share code, notes, and snippets.

@kangvcar
Last active May 20, 2025 13:30
Show Gist options
  • Select an option

  • Save kangvcar/a589f9e0e7484ffffd6f5439a9cf7641 to your computer and use it in GitHub Desktop.

Select an option

Save kangvcar/a589f9e0e7484ffffd6f5439a9cf7641 to your computer and use it in GitHub Desktop.
Hadoop 3.3.6 集群部署脚本
#!/bin/bash
# ===================================================================
# Hadoop分布式集群自动部署脚本 v2.2
# 适用系统: CentOS 7.9
# 功能: 自动部署任意节点数量的Hadoop分布式集群(HDFS+YARN)
# ===================================================================
# 设置颜色代码
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m' # 无颜色
# 定义进度计数器
TOTAL_STEPS=12
CURRENT_STEP=0
# 显示脚本标题
clear
echo -e "${BOLD}${BLUE}"
echo "=============================================================="
echo " Hadoop分布式集群自动部署脚本 v2.0 "
echo "=============================================================="
echo -e "${NC}"
echo -e "${CYAN}✨ 功能说明:${NC}"
echo " 此脚本用于自动部署Hadoop 3.3.6分布式集群,适用于CentOS 7.9系统。"
echo " 支持任意数量的节点,由用户指定节点数量和角色。"
echo " 脚本具有幂等性,可以多次执行而不会产生问题。"
echo
echo -e "${CYAN}📦 部署内容:${NC}"
echo " 1. 配置主机名和hosts文件 - 确保集群节点可以通过主机名互相访问"
echo " 2. 设置SSH免密登录 - 实现主节点对所有节点的无密码访问"
echo " 3. 安装JDK 1.8环境 - Hadoop运行的基础环境"
echo " 4. 安装Hadoop 3.3.6 - 包括完整的HDFS和YARN组件"
echo " 5. 自动分配节点角色 - 根据用户指定配置分布式环境"
echo " 6. 同步配置到所有节点 - 确保集群配置的一致性"
echo " 7. 启动完整的分布式集群 - 包括HDFS、YARN和JobHistory服务"
echo
echo -e "${YELLOW}⚠️ 注意事项:${NC}"
echo " • 确保所有服务器能够互相访问"
echo " • 确保root用户有权限执行所有操作"
echo " • 确保服务器可以访问外网下载软件包"
echo " • 建议使用内网IP进行集群通信,以提高性能"
echo " • 主节点(master)会自动成为NameNode和ResourceManager"
echo " • 最后一个节点会自动成为SecondaryNameNode"
echo " • 所有节点都会成为DataNode和NodeManager"
echo
echo -e "${BOLD}${GREEN}按Enter键开始部署...${NC}"
read
# 函数定义:显示步骤信息并更新进度
show_step() {
CURRENT_STEP=$((CURRENT_STEP+1))
PROGRESS=$((CURRENT_STEP*100/TOTAL_STEPS))
echo -e "\n${BOLD}${BLUE}┌─────────────────────────────────────────────────────┐${NC}"
echo -e "${BOLD}${BLUE}│ [进度: ${PROGRESS}%] 步骤 ${CURRENT_STEP}/${TOTAL_STEPS}: $1${NC}"
echo -e "${BOLD}${BLUE}└─────────────────────────────────────────────────────┘${NC}"
}
# 函数定义:显示成功信息
show_success() {
echo -e "${GREEN}[✓] $1${NC}"
}
# 函数定义:显示错误信息
show_error() {
echo -e "${RED}[✗] $1${NC}"
exit 1
}
# 函数定义:显示警告信息
show_warning() {
echo -e "${YELLOW}[!] $1${NC}"
}
# 函数定义:显示信息
show_info() {
echo -e "${CYAN}[i] $1${NC}"
}
# 函数定义:检查命令是否成功
check_success() {
if [ $? -ne 0 ]; then
show_error "执行命令失败: $1"
else
show_success "$1"
fi
}
# 函数定义:检查节点数量是否有效
validate_node_count() {
if ! [[ "$1" =~ ^[0-9]+$ ]]; then
show_error "节点数量必须是数字"
fi
if [ "$1" -lt 1 ]; then
show_error "节点数量必须大于等于1"
fi
}
# 函数定义:SSH无密码执行命令
remote_exec() {
local host=$1
local cmd=$2
sshpass -p "${NODE_PASSWORDS[$host]}" ssh -o StrictHostKeyChecking=no root@${NODE_IPS[$host]} "$cmd" > /dev/null 2>&1
return $?
}
# 函数定义:SSH无密码复制文件
remote_copy() {
local src=$1
local host=$2
local dest=$3
sshpass -p "${NODE_PASSWORDS[$host]}" scp -o StrictHostKeyChecking=no $src root@${NODE_IPS[$host]}:$dest > /dev/null 2>&1
return $?
}
# 输入节点数量和信息
show_step "配置集群节点信息"
read -p "请输入集群节点数量(最少1个): " NODE_COUNT
validate_node_count "$NODE_COUNT"
# 初始化数组来存储节点信息
declare -A NODE_IPS
declare -A NODE_PASSWORDS
declare -a NODE_NAMES
# 第一个节点始终是master
NODE_NAMES[0]="master"
# 为其余节点生成名称(slave1, slave2, ...)
for ((i=1; i<NODE_COUNT; i++)); do
NODE_NAMES[$i]="slave$i"
done
# 输入每个节点的IP和密码
echo -e "${CYAN}请为每个节点输入IP地址和root密码${NC}"
echo -e "${YELLOW}注意: 这些信息仅用于自动化部署,不会被保存${NC}"
echo
# 遍历所有节点收集信息
for ((i=0; i<NODE_COUNT; i++)); do
node_name=${NODE_NAMES[$i]}
read -p "请输入${node_name}节点内网IP地址: " ip
if [ -z "$ip" ]; then
show_error "${node_name}的IP地址不能为空"
fi
NODE_IPS[$node_name]=$ip
done
# 询问密码设置方式
read -p "所有节点使用相同密码吗?(y/n): " same_password
if [[ "$same_password" == "y" || "$same_password" == "Y" ]]; then
read -sp "请输入所有节点共用的root密码: " COMMON_PASSWORD
echo ""
# 为所有节点设置相同的密码
for ((j=0; j<NODE_COUNT; j++)); do
node_name=${NODE_NAMES[$j]}
NODE_PASSWORDS[$node_name]=$COMMON_PASSWORD
done
else
# 为每个节点单独设置密码
for ((j=0; j<NODE_COUNT; j++)); do
node_name=${NODE_NAMES[$j]}
read -sp "请输入${node_name}节点的root密码: " password
echo ""
if [ -z "$password" ]; then
show_error "${node_name}的密码不能为空"
fi
NODE_PASSWORDS[$node_name]=$password
done
fi
# 如果使用共同密码,确保所有节点都设置了
if [ -n "${COMMON_PASSWORD}" ]; then
for ((i=0; i<NODE_COUNT; i++)); do
NODE_PASSWORDS[${NODE_NAMES[$i]}]=$COMMON_PASSWORD
done
fi
# 显示集群规划信息
echo
echo -e "${CYAN}集群规划信息:${NC}"
echo -e "┌───────────────┬──────────┬───────────────────────────────────────────┐"
echo -e "│ IP地址 │ 主机名 │ 节点角色 │"
echo -e "├───────────────┼──────────┼───────────────────────────────────────────┤"
# 设置角色信息
MASTER_ROLES="NameNode, DataNode, ResourceManager, NodeManager, JobHistoryServer"
SECONDARY_ROLES="DataNode, NodeManager, SecondaryNameNode"
WORKER_ROLES="DataNode, NodeManager"
# 显示每个节点的角色
for ((i=0; i<NODE_COUNT; i++)); do
node_name=${NODE_NAMES[$i]}
node_ip=${NODE_IPS[$node_name]}
# 根据节点序号设置角色
if [ $i -eq 0 ]; then
# 第一个节点是master
roles=$MASTER_ROLES
elif [ $i -eq $((NODE_COUNT-1)) ]; then
# 最后一个节点是SecondaryNameNode
roles=$SECONDARY_ROLES
else
# 其他节点是普通工作节点
roles=$WORKER_ROLES
fi
# 输出节点信息
printf "│ %-13s │ %-8s │ %-43s │\n" "$node_ip" "$node_name" "$roles"
if [ $i -lt $((NODE_COUNT-1)) ]; then
echo -e "├───────────────┼──────────┼───────────────────────────────────────────┤"
fi
done
echo -e "└───────────────┴──────────┴───────────────────────────────────────────┘"
echo
# 检查是否已安装必要工具
show_step "检查和安装必要工具"
show_info "检查所需的软件包是否已安装..."
# 检查是否已安装curl
if ! command -v curl &> /dev/null; then
show_info "正在安装curl..."
yum install -y curl > /dev/null 2>&1
check_success "安装curl工具"
else
show_success "curl工具已安装"
fi
# 检查是否已安装wget
if ! command -v wget &> /dev/null; then
show_info "正在安装wget..."
yum install -y wget > /dev/null 2>&1
check_success "安装wget工具"
else
show_success "wget工具已安装"
fi
# 检查是否已安装sshpass
if ! command -v sshpass &> /dev/null; then
show_info "正在安装sshpass..."
yum install -y sshpass > /dev/null 2>&1
check_success "安装sshpass工具"
else
show_success "sshpass工具已安装"
fi
# 检查是否已安装expect
if ! command -v expect &> /dev/null; then
show_info "正在安装expect..."
yum install -y expect > /dev/null 2>&1
check_success "安装expect工具"
else
show_success "expect工具已安装"
fi
# 创建hosts文件内容
show_info "创建hosts文件..."
> /tmp/hadoop_hosts # 清空文件
for ((i=0; i<NODE_COUNT; i++)); do
node_name=${NODE_NAMES[$i]}
node_ip=${NODE_IPS[$node_name]}
echo "$node_ip $node_name" >> /tmp/hadoop_hosts
done
# 检查hosts文件内容是否正确
show_info "检查hosts文件格式..."
cat /tmp/hadoop_hosts
check_success "hosts配置文件创建成功"
# 准备所有节点
show_step "配置主机名和hosts文件"
show_info "开始配置所有节点的主机名和hosts文件..."
# 配置本地主机
local_hostname=$(hostname)
local_node_name=""
# 确定本机是集群中的哪个节点
for ((i=0; i<NODE_COUNT; i++)); do
node_name=${NODE_NAMES[$i]}
node_ip=${NODE_IPS[$node_name]}
# 检查本机IP是否与任一节点IP匹配
for local_ip in $(hostname -I); do
if [ "$local_ip" == "$node_ip" ]; then
local_node_name=$node_name
break
fi
done
if [ -n "$local_node_name" ]; then
break
fi
done
# 如果找不到匹配的节点,使用master作为本地节点
if [ -z "$local_node_name" ]; then
local_node_name="master"
show_warning "无法确定本机是集群中的哪个节点,将默认使用master"
fi
# 设置本机主机名
if [[ "$local_hostname" != "$local_node_name" ]]; then
show_info "设置本机主机名为$local_node_name..."
hostnamectl set-hostname $local_node_name > /dev/null 2>&1
check_success "设置本机主机名"
else
show_success "本机主机名已正确配置为$local_node_name"
fi
# 更新本地hosts文件
show_info "更新本地hosts文件..."
# 备份当前hosts文件
cp /etc/hosts /etc/hosts.bak
# 创建新的hosts文件,保留localhost配置
cat > /etc/hosts << EOF
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
# Hadoop集群节点配置
$(cat /tmp/hadoop_hosts)
EOF
# 检查hosts文件内容
echo "本地hosts文件内容:"
cat /etc/hosts
check_success "更新本地hosts文件"
# 配置所有远程节点
for ((i=0; i<NODE_COUNT; i++)); do
node_name=${NODE_NAMES[$i]}
# 跳过本地节点
if [ "$node_name" == "$local_node_name" ]; then
continue
fi
show_info "配置${node_name}节点(${NODE_IPS[$node_name]})..."
# 设置主机名
remote_exec $node_name "hostnamectl set-hostname $node_name"
check_success "设置$node_name主机名"
# 更新hosts文件 - 直接复制master节点的hosts文件
show_info "同步hosts文件到${node_name}节点..."
remote_copy /etc/hosts $node_name /etc/hosts
check_success "更新$node_name hosts文件"
done
# 测试连通性
show_info "测试节点间的网络连通性..."
for ((i=0; i<NODE_COUNT; i++)); do
node_name=${NODE_NAMES[$i]}
if [ "$node_name" != "$local_node_name" ]; then
ping -c 1 $node_name > /dev/null 2>&1
check_success "$local_node_name到$node_name的连通性测试"
fi
done
# 配置SSH免密登录
show_step "配置SSH免密登录"
show_info "配置SSH免密登录可实现Hadoop自动化管理..."
# 检查SSH密钥是否已存在
if [ ! -f ~/.ssh/id_rsa ]; then
show_info "生成SSH密钥对..."
ssh-keygen -t rsa -P "" -f ~/.ssh/id_rsa > /dev/null 2>&1
check_success "生成SSH密钥"
else
show_success "SSH密钥已存在,无需重新生成"
fi
# 创建SSH免密登录脚本
cat > /tmp/ssh_copy_id.exp << EOF
#!/usr/bin/expect
set timeout 30
set host [lindex \$argv 0]
set password [lindex \$argv 1]
spawn ssh-copy-id -o StrictHostKeyChecking=no root@\$host
expect {
"password:" {
send "\$password\r"
exp_continue
}
"Permission denied" {
exit 1
}
"Number of key(s) added" {
exit 0
}
"already exist on the remote system" {
exit 0
}
eof {
exit 1
}
}
exit 0
EOF
chmod +x /tmp/ssh_copy_id.exp
# 分发SSH密钥到所有节点
show_info "分发SSH密钥到所有节点..."
for ((i=0; i<NODE_COUNT; i++)); do
node_name=${NODE_NAMES[$i]}
/tmp/ssh_copy_id.exp $node_name ${NODE_PASSWORDS[$node_name]} > /dev/null 2>&1
check_success "分发SSH密钥到$node_name节点"
done
# 测试SSH免密登录
show_info "测试SSH免密登录配置..."
for ((i=0; i<NODE_COUNT; i++)); do
node_name=${NODE_NAMES[$i]}
ssh -o StrictHostKeyChecking=no $node_name "echo SSH_SUCCESS" > /dev/null 2>&1 || show_error "SSH到$node_name失败"
done
show_success "SSH免密登录配置成功,所有节点可以无密码访问"
# 安装JDK
show_step "安装Java环境"
show_info "Java是Hadoop运行的基础环境..."
# 创建软件目录
if [ ! -d /opt/software ]; then
mkdir -p /opt/software
show_success "创建软件存放目录: /opt/software"
fi
# 切换到软件目录
cd /opt/software
# 下载JDK(如果不存在)
JDK_FILE="openjdk-8u41-b04-linux-x64-14_jan_2020.tar.gz"
JDK_URL="https://download.java.net/openjdk/jdk8u41/ri/$JDK_FILE"
if [ ! -f "$JDK_FILE" ]; then
show_info "下载OpenJDK 8u41安装包..."
echo -e "${YELLOW}下载可能需要几分钟时间,请耐心等待...${NC}"
# 使用wget下载
wget --no-check-certificate --progress=dot:giga $JDK_URL 2>&1
if [ -f "$JDK_FILE" ]; then
check_success "下载JDK安装包完成"
else
show_error "下载JDK安装包失败"
fi
else
show_success "JDK安装包已存在,无需重新下载"
fi
# 如果Java未安装,则安装
if [ ! -d /opt/java8 ]; then
show_info "解压并安装JDK..."
tar -zxf $JDK_FILE -C /opt/ > /dev/null 2>&1
check_success "解压JDK到/opt目录"
# 重命名JDK目录
if [ -d /opt/java-se-8u41-ri ]; then
mv /opt/java-se-8u41-ri /opt/java8
check_success "重命名JDK目录为/opt/java8"
else
show_error "JDK解压目录不存在,请检查安装包是否正确"
fi
else
show_success "Java已安装在/opt/java8目录"
fi
# 检查环境变量
if ! grep -q "JAVA_HOME=/opt/java8" /etc/profile; then
show_info "配置Java环境变量..."
cat >> /etc/profile << EOF
# Java环境变量
export JAVA_HOME=/opt/java8
export PATH=\$PATH:\$JAVA_HOME/bin
EOF
check_success "配置Java环境变量到/etc/profile"
# 使环境变量生效
source /etc/profile
show_info "Java环境变量已生效"
fi
# 验证Java安装
java -version > /dev/null 2>&1
JAVA_VERSION=$(java -version 2>&1 | head -n 1)
check_success "Java环境验证成功: $JAVA_VERSION"
# 安装Hadoop
show_step "安装Hadoop"
show_info "Hadoop是一个分布式计算框架,核心组件包括HDFS和YARN..."
# 下载Hadoop(如果不存在)
HADOOP_VERSION="3.3.6"
HADOOP_FILE="hadoop-$HADOOP_VERSION.tar.gz"
HADOOP_URL="https://mirrors.huaweicloud.com/apache/hadoop/common/hadoop-$HADOOP_VERSION/$HADOOP_FILE"
if [ ! -f "$HADOOP_FILE" ]; then
show_info "下载Hadoop ${HADOOP_VERSION}安装包..."
echo -e "${YELLOW}下载可能需要几分钟时间,请耐心等待...${NC}"
# 使用curl下载
curl -O $HADOOP_URL
if [ -f "$HADOOP_FILE" ]; then
check_success "下载Hadoop安装包完成"
else
show_error "下载Hadoop安装包失败"
fi
else
show_success "Hadoop安装包已存在,无需重新下载"
fi
# 如果Hadoop未安装,则安装
if [ ! -d /opt/hadoop ]; then
show_info "解压并安装Hadoop..."
tar -zxf $HADOOP_FILE -C /opt/ > /dev/null 2>&1
check_success "解压Hadoop到/opt目录"
# 重命名Hadoop目录
if [ -d "/opt/hadoop-$HADOOP_VERSION" ]; then
mv /opt/hadoop-$HADOOP_VERSION /opt/hadoop
check_success "重命名Hadoop目录为/opt/hadoop"
else
show_error "Hadoop解压目录不存在,请检查安装包是否正确"
fi
else
show_success "Hadoop已安装在/opt/hadoop目录"
fi
# 检查环境变量
if ! grep -q "HADOOP_HOME=/opt/hadoop" /etc/profile; then
show_info "配置Hadoop环境变量..."
cat >> /etc/profile << EOF
# Hadoop环境变量
export HADOOP_HOME=/opt/hadoop
export PATH=\$PATH:\$HADOOP_HOME/bin:\$HADOOP_HOME/sbin
EOF
check_success "配置Hadoop环境变量到/etc/profile"
# 使环境变量生效
source /etc/profile
show_info "Hadoop环境变量已生效"
fi
# 验证Hadoop安装
hadoop version > /dev/null 2>&1
HADOOP_VERSION_INFO=$(hadoop version | head -n 1)
check_success "Hadoop环境验证成功: $HADOOP_VERSION_INFO"
# 配置Hadoop
show_step "配置Hadoop集群"
show_info "正在配置Hadoop分布式环境,包括HDFS和YARN组件..."
# 创建所需的目录
show_info "创建HDFS数据目录..."
mkdir -p /opt/hadoop/hdfs/name
mkdir -p /opt/hadoop/hdfs/data
check_success "创建NameNode和DataNode数据目录"
# 获取主节点(master)的IP
MASTER_IP=${NODE_IPS["master"]}
# 获取SecondaryNameNode节点的名称和IP
SECONDARY_NODE_NAME=${NODE_NAMES[$((NODE_COUNT-1))]}
SECONDARY_NODE_IP=${NODE_IPS[$SECONDARY_NODE_NAME]}
# 配置core-site.xml
show_info "配置core-site.xml(核心配置文件)..."
cat > /opt/hadoop/etc/hadoop/core-site.xml << EOF
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<!-- 指定HDFS文件系统主节点地址和端口 -->
<property>
<name>fs.defaultFS</name>
<value>hdfs://master:8020</value>
</property>
<!-- 指定Hadoop临时文件存储目录 -->
<property>
<name>hadoop.tmp.dir</name>
<value>/opt/hadoop/hdfs/tmp</value>
</property>
<!-- 允许root用户通过代理访问任何主机 -->
<property>
<name>hadoop.proxyuser.root.hosts</name>
<value>*</value>
</property>
<!-- 允许root用户通过代理访问任何用户组 -->
<property>
<name>hadoop.proxyuser.root.groups</name>
<value>*</value>
</property>
</configuration>
EOF
check_success "配置core-site.xml完成"
# 配置hdfs-site.xml
show_info "配置hdfs-site.xml(HDFS配置文件)..."
cat > /opt/hadoop/etc/hadoop/hdfs-site.xml << EOF
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<!-- 设置文件块副本数量, 通常设置为节点数量的一半,最少为1 -->
<property>
<name>dfs.replication</name>
<value>$((NODE_COUNT/2 > 0 ? NODE_COUNT/2 : 1))</value>
</property>
<!-- 指定NameNode元数据存储目录 -->
<property>
<name>dfs.namenode.name.dir</name>
<value>/opt/hadoop/hdfs/name</value>
<final>true</final>
</property>
<!-- 指定DataNode数据存储目录 -->
<property>
<name>dfs.datanode.data.dir</name>
<value>/opt/hadoop/hdfs/data</value>
<final>true</final>
</property>
<!-- 设置NameNode Web界面的访问地址和端口 -->
<property>
<name>dfs.namenode.http-address</name>
<value>master:9870</value>
</property>
<!-- 设置SecondaryNameNode Web界面的访问地址和端口 -->
<property>
<name>dfs.namenode.secondary.http-address</name>
<value>${SECONDARY_NODE_NAME}:9868</value>
</property>
<!-- 关闭HDFS权限检查,简化开发环境使用 -->
<property>
<name>dfs.permissions</name>
<value>false</value>
</property>
</configuration>
EOF
check_success "配置hdfs-site.xml完成"
# 配置mapred-site.xml
show_info "配置mapred-site.xml(MapReduce配置文件)..."
cat > /opt/hadoop/etc/hadoop/mapred-site.xml << EOF
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<!-- 指定MapReduce程序运行在YARN上 -->
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
<!-- 设置MapReduce JobHistory Server的RPC地址和端口 -->
<property>
<name>mapreduce.jobhistory.address</name>
<value>master:10020</value>
</property>
<!-- 设置MapReduce JobHistory Server的Web界面地址和端口 -->
<property>
<name>mapreduce.jobhistory.webapp.address</name>
<value>master:19888</value>
</property>
<!-- 设置MapReduce ApplicationMaster的环境变量 -->
<property>
<name>yarn.app.mapreduce.am.env</name>
<value>HADOOP_MAPRED_HOME=\${HADOOP_HOME}</value>
</property>
<!-- 设置Map任务的环境变量 -->
<property>
<name>mapreduce.map.env</name>
<value>HADOOP_MAPRED_HOME=\${HADOOP_HOME}</value>
</property>
<!-- 设置Reduce任务的环境变量 -->
<property>
<name>mapreduce.reduce.env</name>
<value>HADOOP_MAPRED_HOME=\${HADOOP_HOME}</value>
</property>
</configuration>
EOF
check_success "配置mapred-site.xml完成"
# 配置yarn-site.xml
show_info "配置yarn-site.xml(YARN资源管理配置文件)..."
cat > /opt/hadoop/etc/hadoop/yarn-site.xml << EOF
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<!-- 指定ResourceManager所在节点的主机名 -->
<property>
<name>yarn.resourcemanager.hostname</name>
<value>master</value>
</property>
<!-- 指定NodeManager上运行的附属服务,这里配置提供MapReduce的shuffle服务 -->
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
<!-- ResourceManager的RPC通信地址和端口,客户端通过该地址提交作业 -->
<property>
<name>yarn.resourcemanager.address</name>
<value>master:8032</value>
</property>
<!-- ResourceManager调度器地址和端口,ApplicationMaster通过该地址申请资源 -->
<property>
<name>yarn.resourcemanager.scheduler.address</name>
<value>master:8030</value>
</property>
<!-- ResourceManager资源跟踪器地址和端口,NodeManager通过该地址注册并汇报状态 -->
<property>
<name>yarn.resourcemanager.resource-tracker.address</name>
<value>master:8031</value>
</property>
<!-- ResourceManager Web界面的访问地址和端口,用于监控集群和应用程序状态 -->
<property>
<name>yarn.resourcemanager.webapp.address</name>
<value>master:8088</value>
</property>
<!-- 开启日志聚集功能,将分散在各个节点的日志统一收集到HDFS -->
<property>
<name>yarn.log-aggregation-enable</name>
<value>true</value>
</property>
<!-- 设置聚合日志在HDFS上的保留时间,单位为秒,这里设置为7天 -->
<property>
<name>yarn.log-aggregation.retain-seconds</name>
<value>604800</value>
</property>
<!-- 指定聚合日志在HDFS上的存储目录 -->
<property>
<name>yarn.nodemanager.remote-app-log-dir</name>
<value>/tmp/logs</value>
</property>
<!-- 启用ResourceManager状态恢复功能,确保ResourceManager重启后能够恢复之前的应用程序状态 -->
<property>
<name>yarn.resourcemanager.recovery.enabled</name>
<value>true</value>
</property>
<!-- 指定ResourceManager状态存储的实现类,这里使用基于文件系统的状态存储 -->
<property>
<name>yarn.resourcemanager.store.class</name>
<value>org.apache.hadoop.yarn.server.resourcemanager.recovery.FileSystemRMStateStore</value>
</property>
<!-- 指定ResourceManager状态信息在HDFS上的存储路径 -->
<property>
<name>yarn.resourcemanager.fs.state-store.uri</name>
<value>hdfs://master:8020/rmstate</value>
</property>
<!-- 设置YARN历史日志服务器的地址,用于访问应用程序的日志 -->
<property>
<name>yarn.log.server.url</name>
<value>http://master:19888/jobhistory/logs</value>
</property>
</configuration>
EOF
check_success "配置yarn-site.xml完成"
# 配置hadoop-env.sh
show_info "配置hadoop-env.sh(Hadoop环境变量)..."
# 备份原始文件
cp /opt/hadoop/etc/hadoop/hadoop-env.sh /opt/hadoop/etc/hadoop/hadoop-env.sh.bak 2>/dev/null
# 添加环境配置
cat >> /opt/hadoop/etc/hadoop/hadoop-env.sh << EOF
# 设置Java安装目录
export JAVA_HOME=/opt/java8
# 设置Hadoop安装目录
export HADOOP_HOME=/opt/hadoop
# 设置Hadoop服务运行用户
export HDFS_NAMENODE_USER=root
export HDFS_DATANODE_USER=root
export HDFS_SECONDARYNAMENODE_USER=root
export YARN_RESOURCEMANAGER_USER=root
export YARN_NODEMANAGER_USER=root
EOF
check_success "配置hadoop-env.sh完成"
# 配置workers文件
show_info "配置workers文件(指定所有工作节点)..."
# 清空workers文件
> /opt/hadoop/etc/hadoop/workers
# 将所有节点添加到workers文件
for ((i=0; i<NODE_COUNT; i++)); do
echo "${NODE_NAMES[$i]}" >> /opt/hadoop/etc/hadoop/workers
done
check_success "配置workers文件完成,添加了${NODE_COUNT}个节点"
# 修复XML配置文件中的标签问题
show_info "修复XML标签..."
sed -i 's/<n>/<name>/g' /opt/hadoop/etc/hadoop/*.xml
sed -i 's/<\/n>/<\/name>/g' /opt/hadoop/etc/hadoop/*.xml
check_success "修复XML标签完成"
# 将Java和Hadoop复制到从节点
show_step "同步环境到从节点"
show_info "将Java和Hadoop环境同步到所有节点,确保集群环境一致..."
# 遍历所有节点
for ((i=0; i<NODE_COUNT; i++)); do
node_name=${NODE_NAMES[$i]}
# 跳过本地节点
if [ "$node_name" == "$local_node_name" ]; then
continue
fi
# 同步Java环境
show_info "同步Java环境到${node_name}节点..."
if ssh $node_name "[ ! -d /opt/java8 ]" 2>/dev/null; then
show_info "开始传输Java环境到${node_name}..."
ssh $node_name "mkdir -p /opt" 2>/dev/null
echo -ne "${YELLOW}正在传输...${NC}"
scp -r /opt/java8 $node_name:/opt/ > /dev/null 2>&1
echo -e "\r${GREEN}传输完成!${NC}"
check_success "复制Java到${node_name}节点"
else
show_success "Java已在${node_name}节点上安装"
fi
# 同步Hadoop环境
show_info "同步Hadoop环境到${node_name}节点..."
if ssh $node_name "[ ! -d /opt/hadoop ]" 2>/dev/null; then
show_info "开始传输Hadoop环境到${node_name}..."
ssh $node_name "mkdir -p /opt" 2>/dev/null
echo -ne "${YELLOW}正在传输Hadoop(这可能需要几分钟)...${NC}"
scp -r /opt/hadoop $node_name:/opt/ > /dev/null 2>&1
echo -e "\r${GREEN}传输完成! ${NC}"
check_success "复制Hadoop到${node_name}节点"
else
# 仅同步配置文件
show_info "Hadoop已在${node_name}上安装,仅同步配置文件..."
scp -r /opt/hadoop/etc/hadoop/* $node_name:/opt/hadoop/etc/hadoop/ > /dev/null 2>&1
check_success "同步Hadoop配置到${node_name}节点"
fi
# 同步环境变量
show_info "同步环境变量配置到${node_name}节点..."
scp /etc/profile $node_name:/etc/ > /dev/null 2>&1
check_success "复制环境变量配置到${node_name}节点"
# 在从节点上创建必要的目录
show_info "在${node_name}节点上创建必要的数据目录..."
ssh $node_name "mkdir -p /opt/hadoop/hdfs/data" > /dev/null 2>&1
check_success "在${node_name}上创建DataNode数据目录"
# 如果是SecondaryNameNode节点,创建相应目录
if [ "$node_name" == "$SECONDARY_NODE_NAME" ]; then
ssh $node_name "mkdir -p /opt/hadoop/hdfs/secondary" > /dev/null 2>&1
check_success "在${node_name}上创建SecondaryNameNode目录"
fi
# 在从节点上更新环境变量
show_info "在${node_name}节点上更新环境变量..."
ssh $node_name "source /etc/profile" > /dev/null 2>&1
check_success "在${node_name}上更新环境变量"
# 修复XML标签
show_info "修复${node_name}节点上的XML配置文件..."
ssh $node_name "sed -i 's/<n>/<name>/g' /opt/hadoop/etc/hadoop/*.xml" > /dev/null 2>&1
ssh $node_name "sed -i 's/<\/n>/<\/name>/g' /opt/hadoop/etc/hadoop/*.xml" > /dev/null 2>&1
check_success "修复${node_name}上的XML配置文件"
done
# 格式化HDFS
show_step "格式化HDFS文件系统"
show_info "初始化HDFS文件系统,这是首次启动HDFS之前的必要步骤..."
# 检查是否是master节点
if [ "$local_node_name" == "master" ]; then
if [ ! -d /opt/hadoop/hdfs/name/current ]; then
show_info "初始化NameNode..."
echo -e "${YELLOW}格式化HDFS,请稍候...${NC}"
hdfs namenode -format > /dev/null 2>&1
if [ $? -eq 0 ]; then
check_success "格式化NameNode成功"
else
show_error "格式化NameNode失败,请检查配置"
fi
else
show_warning "NameNode已格式化,跳过此步骤"
show_info "如需重新格式化,请先删除目录: /opt/hadoop/hdfs/name/current"
fi
else
show_info "本机不是master节点,跳过格式化HDFS步骤"
fi
# 启动Hadoop集群
show_step "启动Hadoop分布式集群"
show_info "逐步启动HDFS、YARN和JobHistory服务..."
# 检查是否是master节点
if [ "$local_node_name" == "master" ]; then
# 启动HDFS
show_info "启动HDFS文件系统..."
echo -e "${YELLOW}正在启动HDFS服务(NameNode、DataNode和SecondaryNameNode)...${NC}"
start-dfs.sh > /dev/null 2>&1
if [ $? -eq 0 ]; then
check_success "HDFS服务启动成功"
else
show_warning "HDFS服务启动可能存在问题,请检查日志"
fi
# 启动YARN
show_info "启动YARN资源管理系统..."
echo -e "${YELLOW}正在启动YARN服务(ResourceManager和NodeManager)...${NC}"
start-yarn.sh > /dev/null 2>&1
if [ $? -eq 0 ]; then
check_success "YARN服务启动成功"
else
show_warning "YARN服务启动可能存在问题,请检查日志"
fi
# 启动JobHistory服务器
show_info "启动MapReduce作业历史服务器..."
echo -e "${YELLOW}正在启动JobHistory服务器...${NC}"
mapred --daemon start historyserver > /dev/null 2>&1
if [ $? -eq 0 ]; then
check_success "JobHistory服务器启动成功"
else
show_warning "JobHistory服务器启动可能存在问题,请检查日志"
fi
# 等待服务完全启动
show_info "等待服务完全启动(15秒)..."
for i in {1..15}; do
echo -ne "${YELLOW}倒计时: $((16-i)) 秒${NC}\r"
sleep 1
done
echo -e "${GREEN}等待完成! ${NC}"
else
show_info "本机不是master节点,无法启动集群服务"
show_info "请在master节点(${NODE_IPS['master']})上执行此脚本完成集群启动"
fi
# 验证服务是否启动
show_step "验证集群服务状态"
show_info "检查各节点上的Hadoop进程是否正常运行..."
# 初始化节点进程变量
declare -A NODE_PROCESSES
# 获取本地节点进程
jps_output=$(source /etc/profile && jps | grep -v Jps | awk '{print $2}')
NODE_PROCESSES[$local_node_name]=$(echo "$jps_output" | sort | tr '\n' ', ' | sed 's/,$//')
# 如果是master节点,获取所有远程节点的进程
if [ "$local_node_name" == "master" ]; then
for ((i=0; i<NODE_COUNT; i++)); do
node_name=${NODE_NAMES[$i]}
# 跳过本地节点
if [ "$node_name" == "$local_node_name" ]; then
continue
fi
# 在远程节点上执行jps,确保先执行source /etc/profile
remote_jps=$(ssh $node_name "source /etc/profile && jps" 2>/dev/null | grep -v Jps | awk '{print $2}')
NODE_PROCESSES[$node_name]=$(echo "$remote_jps" | sort | tr '\n' ', ' | sed 's/,$//')
done
fi
# 显示进程信息
echo -e "${CYAN}进程状态信息:${NC}"
echo -e "┌───────────────────────────────────────────────────────────────────┐"
# 遍历所有节点,显示进程信息
for ((i=0; i<NODE_COUNT; i++)); do
node_name=${NODE_NAMES[$i]}
node_ip=${NODE_IPS[$node_name]}
# 设置期望的进程
expected_processes=""
if [ "$node_name" == "master" ]; then
expected_processes="NameNode, DataNode, ResourceManager, NodeManager, JobHistoryServer"
elif [ "$node_name" == "$SECONDARY_NODE_NAME" ]; then
expected_processes="DataNode, NodeManager, SecondaryNameNode"
else
expected_processes="DataNode, NodeManager"
fi
# 获取实际进程,如果未获取到则显示"未知"
actual_processes=${NODE_PROCESSES[$node_name]:-"未知(需在$node_name节点上运行脚本)"}
# 显示节点信息
echo -e "│ ${BOLD}${node_name}节点 (${node_ip})${NC}"
echo -e "│ 期望进程: ${expected_processes}"
echo -e "│ 实际进程: ${actual_processes}"
if [ $i -lt $((NODE_COUNT-1)) ]; then
echo -e "├───────────────────────────────────────────────────────────────────┤"
fi
done
echo -e "└───────────────────────────────────────────────────────────────────┘"
echo
# 检查本节点的进程状态
if [ "$local_node_name" == "master" ]; then
# 检查master节点关键进程
if echo "${NODE_PROCESSES[$local_node_name]}" | grep -q "NameNode" &&
echo "${NODE_PROCESSES[$local_node_name]}" | grep -q "ResourceManager" &&
echo "${NODE_PROCESSES[$local_node_name]}" | grep -q "JobHistoryServer"; then
show_success "Master节点核心服务运行正常"
else
show_warning "Master节点部分服务可能未正常启动,请检查日志"
fi
# 检查其他节点
for ((i=1; i<NODE_COUNT; i++)); do
node_name=${NODE_NAMES[$i]}
if [ -n "${NODE_PROCESSES[$node_name]}" ]; then
if [ "$node_name" == "$SECONDARY_NODE_NAME" ]; then
# 检查SecondaryNameNode节点
if echo "${NODE_PROCESSES[$node_name]}" | grep -q "DataNode" &&
echo "${NODE_PROCESSES[$node_name]}" | grep -q "NodeManager" &&
echo "${NODE_PROCESSES[$node_name]}" | grep -q "SecondaryNameNode"; then
show_success "${node_name}节点核心服务运行正常"
else
show_warning "${node_name}节点部分服务可能未正常启动,请检查日志"
fi
else
# 检查普通工作节点
if echo "${NODE_PROCESSES[$node_name]}" | grep -q "DataNode" &&
echo "${NODE_PROCESSES[$node_name]}" | grep -q "NodeManager"; then
show_success "${node_name}节点核心服务运行正常"
else
show_warning "${node_name}节点部分服务可能未正常启动,请检查日志"
fi
fi
else
show_warning "无法获取${node_name}节点的进程信息,需要在该节点上运行脚本"
fi
done
else
# 如果不是master节点,只检查本节点
if [ "$local_node_name" == "$SECONDARY_NODE_NAME" ]; then
# 检查SecondaryNameNode节点
if echo "${NODE_PROCESSES[$local_node_name]}" | grep -q "DataNode" &&
echo "${NODE_PROCESSES[$local_node_name]}" | grep -q "NodeManager" &&
echo "${NODE_PROCESSES[$local_node_name]}" | grep -q "SecondaryNameNode"; then
show_success "${local_node_name}节点核心服务运行正常"
else
show_warning "${local_node_name}节点部分服务可能未正常启动,请检查日志"
fi
else
# 检查普通工作节点
if echo "${NODE_PROCESSES[$local_node_name]}" | grep -q "DataNode" &&
echo "${NODE_PROCESSES[$local_node_name]}" | grep -q "NodeManager"; then
show_success "${local_node_name}节点核心服务运行正常"
else
show_warning "${local_node_name}节点部分服务可能未正常启动,请检查日志"
fi
fi
show_info "要查看完整集群状态,请在master节点上运行此脚本"
fi
# 尝试获取公网IP
if [ "$local_node_name" == "master" ]; then
show_info "获取当前服务器公网IP..."
MASTER_PUBLIC_IP=$(curl -s ifconfig.me 2>/dev/null || echo "无法获取公网IP")
if [[ "$MASTER_PUBLIC_IP" == "无法获取公网IP" ]]; then
show_warning "无法获取公网IP,请确保已开放安全组端口"
MASTER_PUBLIC_IP=${NODE_IPS["master"]}
else
show_success "获取公网IP成功: $MASTER_PUBLIC_IP"
fi
else
MASTER_PUBLIC_IP=${NODE_IPS["master"]}
fi
# 部署完成,输出集群信息
show_step "部署完成,输出集群信息"
if [ "$local_node_name" == "master" ]; then
show_info "恭喜!Hadoop分布式集群已成功部署,以下是集群关键信息..."
else
show_info "Hadoop节点已成功部署,以下是节点信息..."
fi
echo -e "${BOLD}${GREEN}✅ Hadoop分布式集群部署信息${NC}"
echo
echo -e "${BOLD}${CYAN}🖥️ 集群节点信息:${NC}"
echo -e "┌───────────────────────────────────────────────────────────────────┐"
# 显示每个节点的信息
for ((i=0; i<NODE_COUNT; i++)); do
node_name=${NODE_NAMES[$i]}
node_ip=${NODE_IPS[$node_name]}
# 设置角色信息
if [ "$node_name" == "master" ]; then
roles="NameNode, DataNode, ResourceManager, NodeManager, JobHistoryServer"
elif [ "$node_name" == "$SECONDARY_NODE_NAME" ]; then
roles="DataNode, NodeManager, SecondaryNameNode"
else
roles="DataNode, NodeManager"
fi
# 获取进程信息
processes=${NODE_PROCESSES[$node_name]:-"未获取"}
# 显示节点信息
echo -e "│ ${BOLD}${node_name}节点 (${node_ip})${NC}"
echo -e "│ 角色: ${roles}"
echo -e "│ 运行进程: ${processes}"
if [ $i -lt $((NODE_COUNT-1)) ]; then
echo -e "├───────────────────────────────────────────────────────────────────┤"
fi
done
echo -e "└───────────────────────────────────────────────────────────────────┘"
echo
echo -e "${BOLD}${BLUE}🌐 Web管理界面:${NC}"
echo -e "┌───────────────────────────────────────────────────────────────────┐"
echo -e "│ HDFS管理界面: ${BOLD}http://$MASTER_PUBLIC_IP:9870/${NC} │"
echo -e "│ YARN资源管理界面: ${BOLD}http://$MASTER_PUBLIC_IP:8088/${NC} │"
echo -e "│ MapReduce历史服务器: ${BOLD}http://$MASTER_PUBLIC_IP:19888/${NC} │"
echo -e "└───────────────────────────────────────────────────────────────────┘"
echo
echo -e "${BOLD}${YELLOW}📋 常用管理命令:${NC}"
echo -e "┌───────────────────────────────────────────────────────────────────┐"
echo -e "│ 启动集群: ${BOLD}start-all.sh${NC} │"
echo -e "│ 停止集群: ${BOLD}stop-all.sh${NC} │"
echo -e "│ 单独启动HDFS: ${BOLD}start-dfs.sh${NC} │"
echo -e "│ 单独停止HDFS: ${BOLD}stop-dfs.sh${NC} │"
echo -e "│ 单独启动YARN: ${BOLD}start-yarn.sh${NC} │"
echo -e "│ 单独停止YARN: ${BOLD}stop-yarn.sh${NC} │"
echo -e "│ 启动历史服务器: ${BOLD}mapred --daemon start historyserver${NC} │"
echo -e "│ 停止历史服务器: ${BOLD}mapred --daemon stop historyserver${NC} │"
echo -e "│ 查看节点进程: ${BOLD}jps${NC} │"
echo -e "│ 查看HDFS状态: ${BOLD}hdfs dfsadmin -report${NC} │"
echo -e "│ HDFS文件操作: ${BOLD}hdfs dfs -<命令> [参数]${NC} │"
echo -e "│ - 查看文件列表: ${BOLD}hdfs dfs -ls /path${NC} │"
echo -e "│ - 创建目录: ${BOLD}hdfs dfs -mkdir -p /path${NC} │"
echo -e "│ - 上传文件: ${BOLD}hdfs dfs -put localfile /path${NC} │"
echo -e "│ - 下载文件: ${BOLD}hdfs dfs -get /path/file localpath${NC} │"
echo -e "│ - 删除文件: ${BOLD}hdfs dfs -rm /path/file${NC} │"
echo -e "│ - 删除目录: ${BOLD}hdfs dfs -rm -r /path${NC} │"
echo -e "│ │"
echo -e "│ 提交MapReduce作业: ${BOLD}hadoop jar <jar包路径> <主类> [参数]${NC} │"
echo -e "│ WordCount示例: ${BOLD}hadoop jar \$HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar wordcount /input /output${NC} │"
echo -e "└───────────────────────────────────────────────────────────────────┘"
echo
echo -e "${BOLD}${GREEN}🔍 集群维护提示:${NC}"
echo -e "┌───────────────────────────────────────────────────────────────────┐"
echo -e "│ 1. 日志位置: ${BOLD}/opt/hadoop/logs/${NC} │"
echo -e "│ 2. 配置文件位置: ${BOLD}/opt/hadoop/etc/hadoop/${NC} │"
echo -e "│ 3. HDFS数据位置: ${BOLD}/opt/hadoop/hdfs/data/${NC} │"
echo -e "│ 4. NameNode元数据: ${BOLD}/opt/hadoop/hdfs/name/${NC} │"
echo -e "│ 5. 重启集群流程: 先执行${BOLD}stop-all.sh${NC},然后执行${BOLD}start-all.sh${NC} │"
echo -e "│ 6. 检查HDFS健康状态: ${BOLD}hdfs fsck /${NC} │"
echo -e "└───────────────────────────────────────────────────────────────────┘"
echo
# 最后的提示
if [ "$local_node_name" != "master" ]; then
echo -e "${YELLOW}注意:要完成集群部署,请在master节点(${NODE_IPS['master']})上运行此脚本${NC}"
echo -e "${YELLOW} 集群的启动和停止操作只能在master节点上执行${NC}"
echo
fi
echo -e "${BOLD}${BLUE}=============================================================="
echo -e " Hadoop分布式集群自动部署脚本执行完毕 "
echo -e "==============================================================${NC}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment