feat: 添加 OpenStack 基础虚拟化环境和控制节点初始化脚本
This commit is contained in:
commit
a376b574bb
9 files changed
+2984
No files matched your search
@@ -0,0 +1,128 @@
|
||||
#!/bin/bash
|
||||
# OpenStack 控制节点(Rocky) KeyStone 服务部署检查脚本
|
||||
# 适用于: 3.2 KeyStone服务部署
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m' # 恢复默认颜色
|
||||
|
||||
PASS_CNT=0
|
||||
FAIL_CNT=0
|
||||
|
||||
pass() { echo -e "${GREEN}[✔ 通过] $1${NC}"; ((PASS_CNT++)); }
|
||||
fail() { echo -e "${RED}[✘ 失败] $1${NC}"; ((FAIL_CNT++)); }
|
||||
info() { echo -e "${CYAN}[➜] $1${NC}"; }
|
||||
|
||||
echo -e "${YELLOW}====================================================${NC}"
|
||||
echo -e "${YELLOW} OpenStack KeyStone 身份认证部署自测脚本 ${NC}"
|
||||
echo -e "${YELLOW}====================================================${NC}"
|
||||
|
||||
# 0. 检查是否为 root 用户
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo -e "${RED}请使用 root 权限执行此脚本!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 1. 检查数据库配置与授权
|
||||
info "1. 检查 MariaDB 数据库创建与授权"
|
||||
if mysql -ukeystone -pkeystone -e "SHOW DATABASES;" 2>/dev/null | grep -q "keystone"; then
|
||||
pass "keystone 数据库已创建"
|
||||
else
|
||||
fail "keystone 数据库未创建"
|
||||
fi
|
||||
|
||||
if mysql -ukeystone -pkeystone -e "SHOW DATABASES;" &>/dev/null; then
|
||||
pass "keystone 数据库用户授权正确 (可正常登录)"
|
||||
|
||||
# 顺便检查 DB_SYNC 是否成功
|
||||
if [ $(mysql -ukeystone -pkeystone -e "USE keystone; SHOW TABLES;" 2>/dev/null | wc -l) -gt 2 ]; then
|
||||
pass "keystone 数据库表结构已成功同步 (db_sync)"
|
||||
else
|
||||
fail "keystone 数据库表为空,未执行同步或同步失败"
|
||||
fi
|
||||
else
|
||||
fail "keystone 数据库用户授权失败或密码不匹配"
|
||||
fi
|
||||
|
||||
# 2. 检查软件包安装
|
||||
info "2. 检查依赖软件包安装"
|
||||
if rpm -q openstack-keystone httpd mod_wsgi &>/dev/null; then
|
||||
pass "相关软件包 (keystone, httpd, mod_wsgi) 已全部安装"
|
||||
else
|
||||
fail "部分软件包未安装,请检查是否遗漏"
|
||||
fi
|
||||
|
||||
# 3. 检查 KeyStone 配置文件
|
||||
info "3. 检查 keystone.conf 配置文件"
|
||||
if grep -q "^connection.*=.*mysql+pymysql://keystone:keystone@controller/keystone" /etc/keystone/keystone.conf 2>/dev/null; then
|
||||
pass "数据库连接 [database] 配置正确"
|
||||
else
|
||||
fail "数据库连接 [database] 配置不匹配"
|
||||
fi
|
||||
|
||||
if grep -q "^provider.*=.*fernet" /etc/keystone/keystone.conf 2>/dev/null; then
|
||||
pass "令牌 [token] 认证类型已按要求设置为 fernet"
|
||||
else
|
||||
fail "令牌 [token] 的 provider 未设置为 fernet"
|
||||
fi
|
||||
|
||||
# 4. 检查 Fernet 密钥
|
||||
info "4. 检查 Fernet 密钥库初始化"
|
||||
if [ "$(ls -A /etc/keystone/fernet-keys 2>/dev/null)" ] && [ "$(ls -A /etc/keystone/credential-keys 2>/dev/null)" ]; then
|
||||
pass "Fernet 与 Credential 密钥库已初始化"
|
||||
else
|
||||
fail "Fernet 密钥目录为空,未执行 fernet_setup"
|
||||
fi
|
||||
|
||||
# 5. 检查 Apache HTTP 状态
|
||||
info "5. 检查 Apache (HTTPD) 服务状态"
|
||||
if grep -iq "^ServerName controller" /etc/httpd/conf/httpd.conf 2>/dev/null; then
|
||||
pass "Apache ServerName 已配置为 controller"
|
||||
else
|
||||
fail "Apache ServerName 未配置正确"
|
||||
fi
|
||||
|
||||
if [ -L /etc/httpd/conf.d/wsgi-keystone.conf ]; then
|
||||
pass "WSGI 虚拟主机链接已创建"
|
||||
else
|
||||
fail "wsgi-keystone.conf 的软链接不存在"
|
||||
fi
|
||||
|
||||
if systemctl is-active httpd &>/dev/null; then pass "httpd 服务正在运行"; else fail "httpd 服务未运行"; fi
|
||||
if systemctl is-enabled httpd &>/dev/null; then pass "httpd 服务已允许开机自启"; else fail "httpd 未设置开机自启"; fi
|
||||
|
||||
# 导入临时管理员环境变量用于后续 API 测试
|
||||
export OS_USERNAME=admin
|
||||
export OS_PASSWORD=admin
|
||||
export OS_PROJECT_NAME=admin
|
||||
export OS_USER_DOMAIN_NAME=Default
|
||||
export OS_PROJECT_DOMAIN_NAME=Default
|
||||
export OS_AUTH_URL=http://controller:5000/v3
|
||||
export OS_IDENTITY_API_VERSION=3
|
||||
export NO_PROXY=localhost,127.0.0.1,controller
|
||||
|
||||
# 6. Admin 身份令牌签发测试 (Bootstrap 验证)
|
||||
info "6. 检查 KeyStone Bootstrap 及管理员权限"
|
||||
if openstack token issue >/dev/null 2>&1; then
|
||||
pass "管理员 (admin) 成功签发 Token (Bootstrap 初始化成功)"
|
||||
else
|
||||
fail "管理员 (admin) 无法签发 Token,Bootstrap 可能失败或 Apache 未响应"
|
||||
echo -e "${RED}后续针对域、项目、用户的检查由于无法认证,将跳过!${NC}"
|
||||
FAIL_CNT=$((FAIL_CNT+5))
|
||||
goto_end=1
|
||||
fi
|
||||
|
||||
|
||||
|
||||
echo ""
|
||||
echo -e "${YELLOW}====================================================${NC}"
|
||||
echo -e "检查完毕!通过项: ${GREEN}${PASS_CNT}${NC},失败项: ${RED}${FAIL_CNT}${NC}"
|
||||
if [ ${FAIL_CNT} -eq 0 ]; then
|
||||
echo -e "${GREEN}太棒了!KeyStone 服务已完美搭建且工作正常!${NC}"
|
||||
else
|
||||
echo -e "${RED}你有 ${FAIL_CNT} 处配置未达标,请对照 [✘ 失败] 的提示,参考手册检查数据库或相关配置文件。${NC}"
|
||||
fi
|
||||
echo -e "${YELLOW}====================================================${NC}"
|
||||
@@ -0,0 +1,139 @@
|
||||
|
||||
#!/bin/bash
|
||||
# OpenStack 控制节点(Rocky) Glance 镜像服务部署检查脚本
|
||||
# 适用于: 3.3 Glance镜像服务部署
|
||||
# 强制设置字符集为 UTF-8,防止 OpenStack 客户端因为读取到中文/全角标点而崩溃报错 ascii codec
|
||||
export LANG=en_US.UTF-8
|
||||
export LC_ALL=en_US.UTF-8
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m' # 恢复默认颜色
|
||||
|
||||
PASS_CNT=0
|
||||
FAIL_CNT=0
|
||||
|
||||
pass() { echo -e "${GREEN}[✔ 通过] $1${NC}"; ((PASS_CNT++)); }
|
||||
fail() { echo -e "${RED}[✘ 失败] $1${NC}"; ((FAIL_CNT++)); }
|
||||
info() { echo -e "${CYAN}[➜] $1${NC}"; }
|
||||
|
||||
echo -e "${YELLOW}====================================================${NC}"
|
||||
echo -e "${YELLOW} OpenStack Glance 镜像服务部署自测脚本 ${NC}"
|
||||
echo -e "${YELLOW}====================================================${NC}"
|
||||
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo -e "${RED}请使用 root 权限执行此脚本!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 1. 检查数据库
|
||||
info "1. 检查 MariaDB 数据库创建与授权"
|
||||
if mysql -uglance -pglance -e "SHOW DATABASES;" 2>/dev/null | grep -qw "glance"; then
|
||||
pass "glance 数据库已创建且用户授权正确"
|
||||
|
||||
if [ $(mysql -uglance -pglance -e "USE glance; SHOW TABLES;" 2>/dev/null | wc -l) -gt 2 ]; then
|
||||
pass "glance 数据库表结构同步 (db_sync) 已完成"
|
||||
else
|
||||
fail "glance 数据库表为空,未执行同步命令"
|
||||
fi
|
||||
else
|
||||
fail "glance 数据库未创建,或 glance 用户授权失败/密码不正确"
|
||||
fi
|
||||
|
||||
# 2. 检查软件包
|
||||
info "2. 检查依赖软件包安装"
|
||||
if rpm -q openstack-glance &>/dev/null; then
|
||||
pass "相关软件包 (openstack-glance) 已安装"
|
||||
else
|
||||
fail "软件包 openstack-glance 未安装"
|
||||
fi
|
||||
|
||||
# 导入临时管理员环境变量用于 API 测试
|
||||
export OS_USERNAME=admin
|
||||
export OS_PASSWORD=admin
|
||||
export OS_PROJECT_NAME=admin
|
||||
export OS_USER_DOMAIN_NAME=Default
|
||||
export OS_PROJECT_DOMAIN_NAME=Default
|
||||
export OS_AUTH_URL=http://controller:5000/v3
|
||||
export OS_IDENTITY_API_VERSION=3
|
||||
export NO_PROXY=localhost,127.0.0.1,controller
|
||||
|
||||
# 验证 Keystone 连通性
|
||||
if ! openstack token issue >/dev/null 2>&1; then
|
||||
fail "当前无法与 Keystone 认证服务通信,请检查上节内容是否运行正常!"
|
||||
echo -e "${RED}后续关于 Keystone 的依赖测试将被跳过!${NC}"
|
||||
FAIL_CNT=$((FAIL_CNT+7))
|
||||
goto_end=1
|
||||
fi
|
||||
|
||||
if [ -z "$goto_end" ]; then
|
||||
# 3. 检查 Glance 凭证注册
|
||||
info "3. 检查 KeyStone 中的 Glance 账号与权限注册"
|
||||
if openstack user show glance >/dev/null 2>&1; then pass "用户: glance 已在 KeyStone 中创建"; else fail "用户: glance 未在 KeyStone 中找到"; fi
|
||||
|
||||
if openstack role assignment list --user glance --project service --role admin -f value --names 2>/dev/null | grep -q "glance"; then
|
||||
pass "用户 glance 已成功加入 service 项目并绑定 admin 角色"
|
||||
else
|
||||
fail "未将 glance 用户绑定到 service 项目的 admin 角色"
|
||||
fi
|
||||
|
||||
if openstack service show image >/dev/null 2>&1; then pass "服务实体: image(glance) 已创建"; else fail "服务实体: image(glance) 未创建"; fi
|
||||
|
||||
# 4. 检查端点 Endpoint 注册
|
||||
info "4. 检查 Endpoint API 端点注册"
|
||||
endpoints=$(openstack endpoint list --service image -f value -c Interface)
|
||||
if echo "$endpoints" | grep -qw "public" && echo "$endpoints" | grep -qw "internal" && echo "$endpoints" | grep -qw "admin"; then
|
||||
pass "镜像服务的 public, internal, admin 端点已全部注册成功"
|
||||
else
|
||||
fail "镜像服务的端点注册不全,请检查 openstack endpoint list"
|
||||
fi
|
||||
|
||||
# 5. 检查 Glance 配置文件
|
||||
info "5. 检查 Glance 服务配置文件"
|
||||
if grep -q "^connection.*=.*glance:glance@controller/glance" /etc/glance/glance-api.conf 2>/dev/null; then
|
||||
pass "glance-api.conf 数据库配置正确"
|
||||
else
|
||||
fail "glance-api.conf 数据库连接 [database] 未配置或错误"
|
||||
fi
|
||||
|
||||
if grep -q "^auth_url.*=.*http://controller:5000" /etc/glance/glance-api.conf 2>/dev/null; then
|
||||
pass "glance-api.conf 身份认证 [keystone_authtoken] 配置正确"
|
||||
else
|
||||
fail "glance-api.conf 身份认证 [keystone_authtoken] 配置有误"
|
||||
fi
|
||||
|
||||
if grep -q "^filesystem_store_datadir.*=.*?/var/lib/glance/images/" /etc/glance/glance-api.conf 2>/dev/null || grep -q "^stores.*=.*file" /etc/glance/glance-api.conf; then
|
||||
pass "glance-api.conf 本地存储目录 [glance_store] 配置完成"
|
||||
else
|
||||
fail "glance-api.conf 存储方式 [glance_store] 配置有误"
|
||||
fi
|
||||
|
||||
# 6. 检查系统服务进程
|
||||
info "6. 检查 Glance 服务启动状态"
|
||||
if systemctl is-active openstack-glance-api &>/dev/null; then pass "openstack-glance-api 服务正在运行"; else fail "openstack-glance-api 服务未运行 (手动补全系统命令了吗?)"; fi
|
||||
if systemctl is-enabled openstack-glance-api &>/dev/null; then pass "openstack-glance-api 服务已设为开机自启"; else fail "openstack-glance-api 服务未设置开启自启"; fi
|
||||
|
||||
if systemctl is-active openstack-glance-registry &>/dev/null; then pass "openstack-glance-registry 服务正在运行"; else fail "openstack-glance-registry 服务未运行"; fi
|
||||
if systemctl is-enabled openstack-glance-registry &>/dev/null; then pass "openstack-glance-registry 服务已设为开机自启"; else fail "openstack-glance-registry 未设置开机自启"; fi
|
||||
|
||||
# 7. 检查镜像上传测试结果
|
||||
info "7. 检查 CirrOS 镜像上传测试结果"
|
||||
img_status=$(openstack image show cirros -f value -c status 2>/dev/null)
|
||||
if [ "$img_status" == "active" ]; then
|
||||
pass "测试镜像 cirros 已成功上传,且目前处于 active 状态!"
|
||||
else
|
||||
fail "未找到处于 active 状态的测试镜像 cirros,请检查上传步骤"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${YELLOW}====================================================${NC}"
|
||||
echo -e "检查完毕!通过项: ${GREEN}${PASS_CNT}${NC},失败项: ${RED}${FAIL_CNT}${NC}"
|
||||
if [ ${FAIL_CNT} -eq 0 ]; then
|
||||
echo -e "${GREEN}完美通过!Glance 镜像服务部署成功,已具备为虚拟机分发操作系统镜像的能力。${NC}"
|
||||
else
|
||||
echo -e "${RED}你有 ${FAIL_CNT} 处配置未达标,请对比报错提示与实验手册,仔细排查修改后再运行!${NC}"
|
||||
fi
|
||||
echo -e "${YELLOW}====================================================${NC}"
|
||||
@@ -0,0 +1,413 @@
|
||||
#!/bin/bash
|
||||
# OpenStack 控制节点(CentOS 7.9 / Rocky)Nova 计算服务部署检测
|
||||
# 参考 3.2.sh、3.3.sh;按手册章节汇总,异常提示对应章内步骤。
|
||||
# 用法:以 root 执行 bash 3.4.sh。仅检查,不安装、同步数据库或重启服务。
|
||||
# 默认实验密码:数据库 root=123456,管理员 admin=admin。
|
||||
# 可通过 CHECK_DB_ROOT_PASSWORD、CHECK_ADMIN_PASSWORD 覆盖以上密码。
|
||||
# 退出码:0=全部通过,1=有失败,2=无失败但存在未验证项。
|
||||
|
||||
export LANG=en_US.UTF-8
|
||||
export LC_ALL=en_US.UTF-8
|
||||
set -o pipefail
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
PASS_CNT=0
|
||||
FAIL_CNT=0
|
||||
SKIP_CNT=0
|
||||
SECTION_TITLE=''
|
||||
SECTION_PASS=0
|
||||
SECTION_FAIL=0
|
||||
SECTION_SKIP=0
|
||||
SECTION_DETAILS=()
|
||||
|
||||
# 正常检查仅计数;每章输出一行汇总,异常才展开具体步骤。
|
||||
pass() { PASS_CNT=$((PASS_CNT+1)); }
|
||||
detail() {
|
||||
if [ -n "$SECTION_TITLE" ]; then
|
||||
SECTION_DETAILS+=("$1")
|
||||
else
|
||||
printf '%s\n' "$1"
|
||||
fi
|
||||
}
|
||||
fail() { FAIL_CNT=$((FAIL_CNT+1)); detail " [✘ 失败] $1"; }
|
||||
skip() { SKIP_CNT=$((SKIP_CNT+1)); detail " [− 跳过] $1"; }
|
||||
section_summary() {
|
||||
[ -n "$SECTION_TITLE" ] || return 0
|
||||
local passed=$((PASS_CNT-SECTION_PASS)) failed=$((FAIL_CNT-SECTION_FAIL)) skipped=$((SKIP_CNT-SECTION_SKIP))
|
||||
if [ "$failed" -gt 0 ]; then
|
||||
printf '%b[✘] %s:通过 %s,失败 %s,跳过 %s%b\n' "$RED" "$SECTION_TITLE" "$passed" "$failed" "$skipped" "$NC"
|
||||
elif [ "$skipped" -gt 0 ]; then
|
||||
printf '%b[−] %s:通过 %s,跳过 %s%b\n' "$YELLOW" "$SECTION_TITLE" "$passed" "$skipped" "$NC"
|
||||
else
|
||||
printf '%b[✔] %s:%s 项通过%b\n' "$GREEN" "$SECTION_TITLE" "$passed" "$NC"
|
||||
fi
|
||||
if [ "${#SECTION_DETAILS[@]}" -gt 0 ]; then
|
||||
printf '%s\n' "${SECTION_DETAILS[@]}"
|
||||
fi
|
||||
}
|
||||
info() {
|
||||
section_summary
|
||||
SECTION_TITLE=$1
|
||||
SECTION_PASS=$PASS_CNT
|
||||
SECTION_FAIL=$FAIL_CNT
|
||||
SECTION_SKIP=$SKIP_CNT
|
||||
SECTION_DETAILS=()
|
||||
}
|
||||
|
||||
printf '%bNova 控制节点部署检测%b\n' "$CYAN" "$NC"
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
fail '[准备工作] 请在控制节点上使用 root 权限执行此脚本。'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CONF=/etc/nova/nova.conf
|
||||
APACHE_CONF=/etc/httpd/conf.d/00-nova-placement-api.conf
|
||||
DB_ROOT_PASSWORD=${CHECK_DB_ROOT_PASSWORD-123456}
|
||||
ADMIN_PASSWORD=${CHECK_ADMIN_PASSWORD-admin}
|
||||
|
||||
# 远程查询均设置超时,避免服务不可达时一直等待。
|
||||
for cmd in timeout mysql openstack rpm ip systemctl httpd nova-manage curl; do
|
||||
if ! command -v "$cmd" >/dev/null 2>&1; then
|
||||
fail "[准备工作第 3 步 / 第三部分第 1 步] 缺少命令 ${cmd},请检查实验环境及软件包安装;相关检查可能失败或跳过。"
|
||||
fi
|
||||
done
|
||||
|
||||
db_root() {
|
||||
MYSQL_PWD="$DB_ROOT_PASSWORD" timeout 15 mysql --connect-timeout=5 \
|
||||
-uroot -N -B -e "$1" 2>/dev/null
|
||||
}
|
||||
db_query() {
|
||||
local database=$1 user=nova password=nova
|
||||
[ "$database" != placement ] || { user=placement; password=placement; }
|
||||
MYSQL_PWD="$password" timeout 15 mysql --connect-timeout=5 --protocol=TCP \
|
||||
-h controller -u "$user" -N -B "$database" -e "$2" 2>/dev/null
|
||||
}
|
||||
|
||||
# 隔离调用者旧的 OS_TOKEN、OS_CLOUD、OS_*_ID 等变量,避免它们覆盖本次凭证。
|
||||
# 与参考脚本一样使用实验管理员凭证,不执行凭证文件里的任意命令。
|
||||
os_cli() (
|
||||
for variable in ${!OS_@}; do unset "$variable"; done
|
||||
export OS_AUTH_URL=http://controller:5000/v3 OS_IDENTITY_API_VERSION=3
|
||||
export OS_AUTH_TYPE=password OS_USERNAME=admin OS_PASSWORD="$ADMIN_PASSWORD"
|
||||
export OS_PROJECT_NAME=admin OS_USER_DOMAIN_NAME=Default OS_PROJECT_DOMAIN_NAME=Default
|
||||
export NO_PROXY=localhost,127.0.0.1,controller no_proxy=localhost,127.0.0.1,controller
|
||||
timeout 30 openstack "$@" 2>/dev/null
|
||||
)
|
||||
|
||||
# 按节读取最后一个有效值,避免注释、其他节中的同名键造成误判。
|
||||
ini_value() {
|
||||
awk -v section="$1" -v key="$2" '
|
||||
/^[[:space:]]*[#;]/ { next }
|
||||
{ sub(/\r$/, "") }
|
||||
/^[[:space:]]*\[/ {
|
||||
s=$0; sub(/^[[:space:]]*\[/,"",s); sub(/\].*$/,"",s)
|
||||
gsub(/^[[:space:]]+|[[:space:]]+$/,"",s); next
|
||||
}
|
||||
s == section && index($0,"=") {
|
||||
k=substr($0,1,index($0,"=")-1)
|
||||
gsub(/^[[:space:]]+|[[:space:]]+$/,"",k)
|
||||
if (k == key) {
|
||||
v=substr($0,index($0,"=")+1)
|
||||
gsub(/^[[:space:]]+|[[:space:]]+$/,"",v)
|
||||
}
|
||||
}
|
||||
END { print v }
|
||||
' "$CONF" 2>/dev/null
|
||||
}
|
||||
check_config() {
|
||||
local section=$1 key=$2 expected=$3 actual
|
||||
actual=$(ini_value "$section" "$key")
|
||||
case "$key" in
|
||||
enabled|use_neutron|project_domain_name|user_domain_name)
|
||||
actual=$(printf '%s' "$actual" | tr '[:upper:]' '[:lower:]')
|
||||
expected=$(printf '%s' "$expected" | tr '[:upper:]' '[:lower:]');;
|
||||
enabled_apis) actual=$(printf '%s' "$actual" | tr -d '[:space:]');;
|
||||
esac
|
||||
if [ "$actual" = "$expected" ]; then
|
||||
pass "[第 2 步] [$section] $key 配置正确"
|
||||
else
|
||||
# 不回显实际值,避免泄露连接串或口令。
|
||||
fail "[第 2 步] [$section] $key 缺失或与手册不符,请检查 $CONF"
|
||||
fi
|
||||
}
|
||||
|
||||
info '一、数据库与授权'
|
||||
ROOT_OK=0
|
||||
if db_root 'SELECT 1;' >/dev/null; then
|
||||
ROOT_OK=1
|
||||
pass '[第 1 步] 数据库 root 可登录'
|
||||
else
|
||||
fail '[第 1 步] 数据库 root 登录失败,请检查 MariaDB、密码(默认 123456)或 CHECK_DB_ROOT_PASSWORD'
|
||||
fi
|
||||
for database in nova_api nova nova_cell0 placement; do
|
||||
if [ "$ROOT_OK" -eq 1 ]; then
|
||||
if [ "$(db_root "SELECT COUNT(*) FROM information_schema.SCHEMATA WHERE SCHEMA_NAME='$database';")" = 1 ]; then
|
||||
pass "[第 2 步] $database 数据库已创建"
|
||||
else
|
||||
fail "[第 2 步] $database 数据库不存在或查询失败"
|
||||
fi
|
||||
user=nova
|
||||
[ "$database" != placement ] || user=placement
|
||||
grants=$(db_root "SHOW GRANTS FOR '$user'@'%';")
|
||||
if printf '%s\n' "$grants" | tr -d '\140' | grep -Fq "GRANT ALL PRIVILEGES ON $database.* TO "; then
|
||||
pass "[第 3 步] '$user'@'%' 已获得 $database 的所有权限"
|
||||
else
|
||||
fail "[第 3 步] '$user'@'%' 缺少 $database 的 ALL PRIVILEGES 授权,或授权查询失败"
|
||||
fi
|
||||
else
|
||||
skip "[第 2、3 步] 无法独立核查 $database 是否创建及 '%' 账号授权;继续测试实际连接"
|
||||
fi
|
||||
if db_query "$database" 'SELECT 1;' >/dev/null; then
|
||||
pass "[第 3 步] 使用手册服务账号经 controller 的 TCP 连接可访问 $database"
|
||||
else
|
||||
fail "[第 2、3 步] 无法访问 ${database},请检查建库、账号密码、授权及 controller 解析"
|
||||
fi
|
||||
done
|
||||
|
||||
info '二、服务凭证与端点'
|
||||
AUTH_OK=0
|
||||
if os_cli token issue >/dev/null; then
|
||||
AUTH_OK=1
|
||||
pass '[第 1 步] 管理员凭证可获取 Token'
|
||||
else
|
||||
fail '[第 1 步] 管理员认证失败,请检查 Keystone、admin 密码及 admin-openstack.sh;继续检查本地配置'
|
||||
fi
|
||||
|
||||
check_identity() {
|
||||
local user=$1 type=$2 user_step=$3 role_step=$4 service_step=$5 endpoint_step=$6 url=$7
|
||||
local user_id service_id service_type roles endpoints interface
|
||||
if [ "$AUTH_OK" -ne 1 ]; then
|
||||
skip "[第 ${user_step}、${role_step}、${service_step}、${endpoint_step} 步] 管理员无法认证,暂不检查 $user 用户、角色、服务及端点"
|
||||
return
|
||||
fi
|
||||
user_id=$(os_cli user show --domain default "$user" -f value -c id)
|
||||
if [ -n "$user_id" ]; then
|
||||
pass "[第 ${user_step} 步] default 域内的 $user 用户已创建"
|
||||
# 用实际服务密码签发 service 项目的令牌,同时确认账号可用。
|
||||
if os_cli --os-username "$user" --os-password "$user" --os-project-name service token issue >/dev/null; then
|
||||
pass "[第 ${user_step}、${role_step} 步] $user 可使用手册密码在 service 项目完成认证"
|
||||
else
|
||||
fail "[第 ${user_step}、${role_step} 步] $user 认证失败,请检查密码、启用状态及 service 项目角色"
|
||||
fi
|
||||
if roles=$(os_cli role assignment list --user "$user_id" --project service --role admin -f value) && [ -n "$roles" ]; then
|
||||
pass "[第 ${role_step} 步] $user 已绑定 service 项目的 admin 角色"
|
||||
else
|
||||
fail "[第 ${role_step} 步] $user 未绑定 service 项目的 admin 角色,或查询失败"
|
||||
fi
|
||||
else
|
||||
fail "[第 ${user_step} 步] 未找到 default 域的 $user 用户,或查询失败"
|
||||
skip "[第 ${role_step} 步] 无法确认 $user 用户,暂不检查其项目角色"
|
||||
fi
|
||||
service_id=$(os_cli service show "$user" -f value -c id)
|
||||
service_type=$(os_cli service show "$user" -f value -c type)
|
||||
if [ -n "$service_id" ] && [ "$service_type" = "$type" ]; then
|
||||
pass "[第 ${service_step} 步] $user 服务实体存在,类型为 $type"
|
||||
else
|
||||
fail "[第 ${service_step} 步] $user 服务实体不存在、重名或类型不是 $type"
|
||||
skip "[第 ${endpoint_step} 步] 服务实体未确认,暂不检查端点"
|
||||
return
|
||||
fi
|
||||
for interface in public internal admin; do
|
||||
if endpoints=$(os_cli endpoint list --service "$service_id" --region RegionOne \
|
||||
--interface "$interface" -f value -c Enabled -c URL) && \
|
||||
printf '%s\n' "$endpoints" | awk -v url="$url" '
|
||||
NF { count++; sub(/\/$/, "", $2)
|
||||
if (NF == 2 && tolower($1) == "true" && $2 == url) valid++ }
|
||||
END { exit !(count == 1 && valid == 1) }
|
||||
'; then
|
||||
pass "[第 ${endpoint_step} 步] $user 的 RegionOne/$interface 端点为 $url"
|
||||
else
|
||||
fail "[第 ${endpoint_step} 步] $user 的 RegionOne/$interface 端点缺失、重复、未启用或 URL 错误,应为 $url"
|
||||
fi
|
||||
done
|
||||
}
|
||||
check_identity nova compute 2 3 4 5 http://controller:8774/v2.1
|
||||
check_identity placement placement 6 7 8 9 http://controller:8778
|
||||
|
||||
info '三、软件包与配置'
|
||||
for package in openstack-nova-api openstack-nova-conductor openstack-nova-novncproxy \
|
||||
openstack-nova-scheduler openstack-nova-placement-api; do
|
||||
if rpm -q "$package" >/dev/null 2>&1; then
|
||||
pass "[第 1 步] $package 已安装"
|
||||
else
|
||||
fail "[第 1 步] $package 未安装"
|
||||
fi
|
||||
done
|
||||
if [ -r "$CONF" ]; then
|
||||
while IFS='|' read -r section key expected; do
|
||||
check_config "$section" "$key" "$expected"
|
||||
done <<'CONFIG'
|
||||
DEFAULT|use_neutron|true
|
||||
DEFAULT|firewall_driver|nova.virt.firewall.NoopFirewallDriver
|
||||
DEFAULT|enabled_apis|osapi_compute,metadata
|
||||
DEFAULT|transport_url|rabbit://openstack:openstack@controller
|
||||
api_database|connection|mysql+pymysql://nova:nova@controller/nova_api
|
||||
database|connection|mysql+pymysql://nova:nova@controller/nova
|
||||
placement_database|connection|mysql+pymysql://placement:placement@controller/placement
|
||||
api|auth_strategy|keystone
|
||||
keystone_authtoken|auth_url|http://controller:5000/v3
|
||||
keystone_authtoken|memcached_servers|controller:11211
|
||||
keystone_authtoken|auth_type|password
|
||||
keystone_authtoken|project_domain_name|Default
|
||||
keystone_authtoken|user_domain_name|Default
|
||||
keystone_authtoken|project_name|service
|
||||
keystone_authtoken|username|nova
|
||||
keystone_authtoken|password|nova
|
||||
vnc|enabled|true
|
||||
glance|api_servers|http://controller:9292
|
||||
oslo_concurrency|lock_path|/var/lib/nova/tmp
|
||||
placement|region_name|RegionOne
|
||||
placement|project_domain_name|Default
|
||||
placement|project_name|service
|
||||
placement|auth_type|password
|
||||
placement|user_domain_name|Default
|
||||
placement|auth_url|http://controller:5000/v3
|
||||
placement|username|placement
|
||||
placement|password|placement
|
||||
CONFIG
|
||||
my_ip=$(ini_value DEFAULT my_ip)
|
||||
if [ -n "$my_ip" ] && ip -o -4 addr show scope global 2>/dev/null | \
|
||||
awk '{split($4,a,"/"); print a[1]}' | grep -Fxq "$my_ip"; then
|
||||
pass '[第 2 步] [DEFAULT] my_ip 是本机已配置的 IPv4 地址'
|
||||
else
|
||||
fail '[第 2 步] [DEFAULT] my_ip 为空或不是本机 IPv4 地址,应填写自己的控制节点管理 IP'
|
||||
fi
|
||||
for key in server_listen server_proxyclient_address; do
|
||||
value=$(ini_value vnc "$key")
|
||||
if [ -n "$my_ip" ] && { [ "$value" = '$my_ip' ] || [ "$value" = "\${my_ip}" ] || [ "$value" = "$my_ip" ]; }; then
|
||||
pass "[第 2 步] [vnc] $key 引用 my_ip 或使用相同地址"
|
||||
else
|
||||
fail "[第 2 步] [vnc] $key 应引用 \$my_ip 或填写相同的管理 IP"
|
||||
fi
|
||||
done
|
||||
else
|
||||
fail "[第 2 步] $CONF 不存在或不可读"
|
||||
fi
|
||||
|
||||
# 仅在指定 Directory 块内检查,注释及其他目录的授权不能算作通过。
|
||||
if [ -r "$APACHE_CONF" ] && awk '
|
||||
/^[[:space:]]*#/ { next }
|
||||
{ line=tolower($0); gsub(/^[[:space:]]+|[[:space:]]+$/,"",line) }
|
||||
line ~ /^<directory[[:space:]]+"?\/usr\/bin"?[[:space:]]*>$/ { inside=1; req=ord=allow=0; next }
|
||||
inside && line == "require all granted" { req=1 }
|
||||
inside && line == "order allow,deny" { ord=1 }
|
||||
inside && line == "allow from all" { allow=1 }
|
||||
inside && line == "</directory>" { if(req && ord && allow) found=1; inside=0 }
|
||||
END { exit !found }
|
||||
' "$APACHE_CONF"; then
|
||||
pass '[第 3 步] /usr/bin 的 Directory 块包含手册要求的访问授权指令'
|
||||
else
|
||||
fail "[第 3 步] $APACHE_CONF 缺少手册中的 /usr/bin Directory 授权配置"
|
||||
fi
|
||||
if timeout 15 httpd -t >/dev/null 2>&1; then
|
||||
pass '[第 3、4 步] Apache 配置语法检查通过'
|
||||
else
|
||||
fail '[第 3、4 步] Apache 配置语法检查失败,请执行 httpd -t 查看具体错误'
|
||||
fi
|
||||
if systemctl is-active --quiet httpd; then
|
||||
pass '[第 4 步] httpd 正在运行'
|
||||
else
|
||||
fail '[第 4 步] httpd 未运行,请检查配置并按手册重启'
|
||||
fi
|
||||
http_code=$(curl --noproxy '*' -s -o /dev/null -w '%{http_code}' \
|
||||
--connect-timeout 5 --max-time 10 http://controller:8778/ 2>/dev/null)
|
||||
case "$http_code" in
|
||||
200) pass '[第 3、4 步] Placement API 根路径返回 HTTP 200';;
|
||||
401) skip '[第 3、4 步] Placement API 返回 HTTP 401,仅确认服务可达,未验证认证后的访问';;
|
||||
*) fail "[第 3、4 步] Placement API 返回 HTTP ${http_code:-000},请检查授权配置、httpd 日志及 8778 端口";;
|
||||
esac
|
||||
|
||||
info '四、数据库同步与 Cells'
|
||||
check_tables() {
|
||||
local database=$1 step=$2 tables table missing=''
|
||||
shift 2
|
||||
if tables=$(db_query "$database" 'SHOW TABLES;'); then
|
||||
for table in "$@"; do
|
||||
if ! printf '%s\n' "$tables" | grep -Fxq "$table"; then missing="$missing $table"; fi
|
||||
done
|
||||
if [ -z "$missing" ]; then
|
||||
pass "[第 ${step}、6 步] $database 已生成关键数据表"
|
||||
else
|
||||
fail "[第 ${step}、6 步] $database 缺少表:${missing},请检查该步骤数据库同步是否成功"
|
||||
fi
|
||||
else
|
||||
skip "[第 ${step}、6 步] 无法连接 ${database},先完成第一部分第 2、3 步,再验证数据表"
|
||||
fi
|
||||
}
|
||||
check_tables nova_api 1 cell_mappings instance_mappings
|
||||
check_tables placement 1 resource_providers inventories allocations
|
||||
check_tables nova_cell0 4 instances instance_extra
|
||||
check_tables nova 4 instances compute_nodes services
|
||||
|
||||
# 查询数据库中的实际映射;不执行 map_cell0/create_cell/db sync 等写操作。
|
||||
if cells=$(db_query nova_api 'SELECT name,uuid,transport_url,database_connection FROM cell_mappings;'); then
|
||||
for cell in cell0 cell1; do
|
||||
if [ "$cell" = cell0 ]; then
|
||||
step=2
|
||||
expected_db=mysql+pymysql://nova:nova@controller/nova_cell0
|
||||
expected_transport=none:///
|
||||
else
|
||||
step=3
|
||||
expected_db=mysql+pymysql://nova:nova@controller/nova
|
||||
expected_transport=$(ini_value DEFAULT transport_url)
|
||||
fi
|
||||
if printf '%s\n' "$cells" | awk -F '\t' -v name="$cell" -v db="$expected_db" -v transport="$expected_transport" '
|
||||
$1 == name { count++; if($4 == db && $3 == transport && $2 != "" &&
|
||||
(name != "cell0" || $2 == "00000000-0000-0000-0000-000000000000")) valid++ }
|
||||
END { exit !(count == 1 && valid == 1) }
|
||||
'; then
|
||||
pass "[第 ${step}、5 步] $cell 唯一存在,数据库及消息队列映射正确"
|
||||
else
|
||||
fail "[第 ${step}、5 步] $cell 缺失、重复或映射错误,请检查 list_cells(不要盲目重复创建 cell1)"
|
||||
fi
|
||||
done
|
||||
else
|
||||
skip '[第 2、3、5 步] 无法读取 cell_mappings,请先检查数据库权限及第 1 步的同步结果'
|
||||
fi
|
||||
if timeout 30 su -s /bin/sh -c 'nova-manage cell_v2 list_cells' nova >/dev/null 2>&1; then
|
||||
pass '[第 5 步] nova 用户可成功执行 nova-manage cell_v2 list_cells'
|
||||
else
|
||||
fail '[第 5 步] nova 用户执行 list_cells 失败,请检查 nova.conf、文件权限及数据库连接'
|
||||
fi
|
||||
|
||||
info '五、服务运行与自启'
|
||||
for service in openstack-nova-api openstack-nova-scheduler openstack-nova-conductor openstack-nova-novncproxy; do
|
||||
if systemctl is-active --quiet "$service"; then
|
||||
pass "[第 1、3 步] $service 正在运行"
|
||||
else
|
||||
fail "[第 1、3 步] $service 未正常运行"
|
||||
fi
|
||||
if [ "$(systemctl is-enabled "$service" 2>/dev/null)" = enabled ]; then
|
||||
pass "[第 2 步] $service 已设置开机自启"
|
||||
else
|
||||
fail "[第 2 步] $service 未设置持久的开机自启"
|
||||
fi
|
||||
done
|
||||
|
||||
info '结果自测:Compute API'
|
||||
if [ "$AUTH_OK" -eq 1 ]; then
|
||||
if os_cli compute service list >/dev/null; then
|
||||
pass '管理员可查询计算服务列表'
|
||||
else
|
||||
fail '[第二部分第 5 步、第三部分第 2 步、第五部分第 3 步] 计算服务列表查询失败,请检查 API、端点和认证配置'
|
||||
fi
|
||||
else
|
||||
skip '[第二部分第 1 步] 管理员认证失败,暂不验证 Compute API'
|
||||
fi
|
||||
|
||||
section_summary
|
||||
printf '\n合计:通过 %b%s%b,失败 %b%s%b,跳过 %b%s%b\n' \
|
||||
"$GREEN" "$PASS_CNT" "$NC" "$RED" "$FAIL_CNT" "$NC" "$YELLOW" "$SKIP_CNT" "$NC"
|
||||
if [ "$FAIL_CNT" -gt 0 ]; then
|
||||
printf '%b请按异常项的手册编号修正后重试。%b\n' "$RED" "$NC"
|
||||
exit 1
|
||||
elif [ "$SKIP_CNT" -gt 0 ]; then
|
||||
printf '%b存在未验证项,请解决跳过原因后重试。%b\n' "$YELLOW" "$NC"
|
||||
exit 2
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
@@ -0,0 +1,116 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_err() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 检查 root 权限
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
log_err "请使用 root 权限执行此脚本(如: sudo bash $0)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "=========================================="
|
||||
log_info "开始初始化实验环境..."
|
||||
log_info "=========================================="
|
||||
|
||||
REPO_DIR="/etc/yum.repos.d"
|
||||
BACKUP_DIR="/etc/yum.repos.d.bak"
|
||||
|
||||
# 首次运行时备份原有软件源
|
||||
if [ ! -d "$BACKUP_DIR" ]; then
|
||||
log_info "备份原有 yum 软件源..."
|
||||
cp -a "$REPO_DIR" "$BACKUP_DIR"
|
||||
fi
|
||||
|
||||
# 配置 CentOS 7.9 基础源
|
||||
log_info "配置 CentOS 7.9 阿里云软件源..."
|
||||
|
||||
cat > "$REPO_DIR/CentOS-Base.repo" <<'EOF'
|
||||
[base]
|
||||
name=CentOS-7 - Base - Aliyun
|
||||
baseurl=http://mirrors.aliyun.com/centos-vault/7.9.2009/os/$basearch/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
|
||||
[updates]
|
||||
name=CentOS-7 - Updates - Aliyun
|
||||
baseurl=http://mirrors.aliyun.com/centos-vault/7.9.2009/updates/$basearch/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
|
||||
[extras]
|
||||
name=CentOS-7 - Extras - Aliyun
|
||||
baseurl=http://mirrors.aliyun.com/centos-vault/7.9.2009/extras/$basearch/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
EOF
|
||||
|
||||
# 配置 QEMU EV 源
|
||||
cat > "$REPO_DIR/CentOS-QEMU-EV.repo" <<'EOF'
|
||||
[centos-qemu-ev]
|
||||
name=CentOS-7 - QEMU EV - Aliyun
|
||||
baseurl=http://mirrors.aliyun.com/centos-vault/7.9.2009/virt/$basearch/kvm-common/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
EOF
|
||||
|
||||
# 配置 OpenStack Rocky 源
|
||||
cat > "$REPO_DIR/CentOS-OpenStack-rocky.repo" <<'EOF'
|
||||
[centos-openstack-rocky]
|
||||
name=CentOS-7 - OpenStack Rocky - Aliyun
|
||||
baseurl=http://mirrors.aliyun.com/centos-vault/7.9.2009/cloud/$basearch/openstack-rocky/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
exclude=sip,PyQt4
|
||||
EOF
|
||||
|
||||
# 删除不再需要的 Ceph 源,避免 yum 读取其元数据
|
||||
rm -f "$REPO_DIR/CentOS-Ceph-Luminous.repo"
|
||||
|
||||
|
||||
# 清除旧仓库元数据,保留已下载的软件包缓存
|
||||
log_info "刷新软件源元数据..."
|
||||
yum clean metadata > /dev/null 2>&1
|
||||
|
||||
# 安装 KVM / libvirt
|
||||
log_info "安装/检查虚拟化软件包..."
|
||||
|
||||
yum install -y \
|
||||
qemu-kvm \
|
||||
libvirt \
|
||||
virt-install \
|
||||
bridge-utils \
|
||||
qemu-img
|
||||
|
||||
# 启动 libvirtd
|
||||
log_info "启动 libvirtd 服务..."
|
||||
|
||||
systemctl enable --now libvirtd > /dev/null
|
||||
|
||||
# 验证服务状态
|
||||
if systemctl is-active --quiet libvirtd; then
|
||||
log_info "libvirtd 状态正常。"
|
||||
else
|
||||
log_err "libvirtd 启动失败!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "=========================================="
|
||||
log_info "实验环境初始化完成!"
|
||||
log_info "=========================================="
|
||||
Executable
+307
@@ -0,0 +1,307 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_err() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 检查 root 权限
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
log_err "请使用 root 权限执行此脚本(如: sudo bash $0)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "=========================================="
|
||||
log_info "开始初始化实验环境..."
|
||||
log_info "=========================================="
|
||||
|
||||
# 直接连接终端,保留 yum 原生下载进度条;不要通过 tee 管道转存输出。
|
||||
run_yum() {
|
||||
local status
|
||||
log_info "执行 yum $*(下载速度取决于网络和镜像源)..."
|
||||
# 仅启用本地仓库,避免系统中其他已启用的公网仓库参与下载。
|
||||
if yum --disablerepo='*' \
|
||||
--enablerepo=local-base,local-updates,local-extras,local-qemu-ev,local-openstack-rocky \
|
||||
"$@"; then
|
||||
log_info "yum 操作完成。"
|
||||
else
|
||||
status=$?
|
||||
log_err "yum 操作失败(退出码 $status),请查看上方错误信息。"
|
||||
return "$status"
|
||||
fi
|
||||
}
|
||||
|
||||
REPO_DIR="/etc/yum.repos.d"
|
||||
BACKUP_DIR="/etc/yum.repos.d.bak"
|
||||
|
||||
# 首次运行时备份原有软件源
|
||||
if [ ! -d "$BACKUP_DIR" ]; then
|
||||
log_info "备份原有 yum 软件源..."
|
||||
cp -a "$REPO_DIR" "$BACKUP_DIR"
|
||||
fi
|
||||
|
||||
# 配置 CentOS 7.9 基础源
|
||||
log_info "配置 CentOS 7.9 本地软件源(192.168.192.205:3080)..."
|
||||
|
||||
cat > "$REPO_DIR/CentOS-Base.repo" <<'EOF'
|
||||
[local-base]
|
||||
name=Local - Base
|
||||
baseurl=http://192.168.192.205:3080/vault-base/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
|
||||
[local-updates]
|
||||
name=Local - Updates
|
||||
baseurl=http://192.168.192.205:3080/vault-updates/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
|
||||
[local-extras]
|
||||
name=Local - Extras
|
||||
baseurl=http://192.168.192.205:3080/vault-extras/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
EOF
|
||||
|
||||
# 配置 QEMU EV 源
|
||||
cat > "$REPO_DIR/CentOS-QEMU-EV.repo" <<'EOF'
|
||||
[local-qemu-ev]
|
||||
name=Local - QEMU EV
|
||||
baseurl=http://192.168.192.205:3080/vault-centos-qemu-ev/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
EOF
|
||||
|
||||
# 配置 OpenStack Rocky 源
|
||||
cat > "$REPO_DIR/CentOS-OpenStack-rocky.repo" <<'EOF'
|
||||
[local-openstack-rocky]
|
||||
name=Local - OpenStack Rocky
|
||||
baseurl=http://192.168.192.205:3080/vault-centos-openstack-rocky/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
exclude=sip,PyQt4
|
||||
EOF
|
||||
|
||||
# 删除不再需要的 Ceph 源,避免 yum 读取其元数据
|
||||
rm -f "$REPO_DIR/CentOS-Ceph-Luminous.repo"
|
||||
|
||||
|
||||
# 清除旧仓库元数据,保留已下载的软件包缓存
|
||||
log_info "刷新软件源元数据..."
|
||||
run_yum clean metadata
|
||||
|
||||
# 安装 KVM / libvirt
|
||||
log_info "安装/检查虚拟化软件包..."
|
||||
|
||||
run_yum install -y \
|
||||
qemu-kvm \
|
||||
libvirt \
|
||||
virt-install \
|
||||
bridge-utils \
|
||||
qemu-img
|
||||
|
||||
# 启动 libvirtd
|
||||
log_info "启动 libvirtd 服务..."
|
||||
|
||||
systemctl enable --now libvirtd > /dev/null
|
||||
|
||||
# 验证服务状态
|
||||
if systemctl is-active --quiet libvirtd; then
|
||||
log_info "libvirtd 状态正常。"
|
||||
else
|
||||
log_err "libvirtd 启动失败!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "=========================================="
|
||||
log_info "基础虚拟化环境初始化完成,开始配置控制节点..."
|
||||
log_info "=========================================="
|
||||
|
||||
CONTROLLER_IP="192.168.30.129"
|
||||
CONTROLLER_HOSTNAME="controller"
|
||||
|
||||
# 首次修改时备份配置,重复运行不覆盖原始备份。
|
||||
backup_config() {
|
||||
if [ -f "$1" ] && [ ! -e "$1.env3-1.bak" ]; then
|
||||
cp -a "$1" "$1.env3-1.bak"
|
||||
fi
|
||||
}
|
||||
|
||||
log_info "关闭防火墙和 SELinux..."
|
||||
if systemctl list-unit-files firewalld.service --no-legend | grep -q '^firewalld.service'; then
|
||||
systemctl stop firewalld
|
||||
systemctl disable firewalld
|
||||
fi
|
||||
if command -v getenforce > /dev/null 2>&1; then
|
||||
if [ "$(getenforce)" = "Enforcing" ]; then
|
||||
setenforce 0
|
||||
fi
|
||||
fi
|
||||
if [ -f /etc/selinux/config ]; then
|
||||
backup_config /etc/selinux/config
|
||||
sed -i '/^[[:space:]]*SELINUX=/d' /etc/selinux/config
|
||||
echo 'SELINUX=disabled' >> /etc/selinux/config
|
||||
fi
|
||||
|
||||
log_info "配置主机名和 hosts 解析..."
|
||||
hostnamectl set-hostname "$CONTROLLER_HOSTNAME"
|
||||
backup_config /etc/hosts
|
||||
# 移除旧 controller 映射,保留同一行的其他别名及注释。
|
||||
HOSTS_TMP=$(mktemp)
|
||||
awk -v host="$CONTROLLER_HOSTNAME" '
|
||||
/^[[:space:]]*#/ || NF == 0 { print; next }
|
||||
{
|
||||
comment = ""
|
||||
pos = index($0, "#")
|
||||
if (pos) {
|
||||
comment = substr($0, pos)
|
||||
$0 = substr($0, 1, pos - 1)
|
||||
}
|
||||
line = $1
|
||||
aliases = 0
|
||||
for (i = 2; i <= NF; i++) {
|
||||
if ($i != host) {
|
||||
line = line " " $i
|
||||
aliases++
|
||||
}
|
||||
}
|
||||
if (aliases) print line (comment == "" ? "" : " " comment)
|
||||
else if (comment != "") print comment
|
||||
}
|
||||
' /etc/hosts > "$HOSTS_TMP"
|
||||
cat "$HOSTS_TMP" > /etc/hosts
|
||||
rm -f "$HOSTS_TMP"
|
||||
printf '%s %s\n' "$CONTROLLER_IP" "$CONTROLLER_HOSTNAME" >> /etc/hosts
|
||||
|
||||
log_info "升级系统软件包并安装 OpenStack 基础服务..."
|
||||
run_yum -y upgrade
|
||||
run_yum install -y chrony python2-openstackclient \
|
||||
mariadb mariadb-server python2-PyMySQL \
|
||||
rabbitmq-server memcached python-memcached etcd
|
||||
|
||||
log_info "配置 chrony 时间服务器..."
|
||||
backup_config /etc/chrony.conf
|
||||
if ! grep -Eq '^[[:space:]]*allow[[:space:]]+all([[:space:]]*(#.*)?)?$' /etc/chrony.conf; then
|
||||
printf '\n# 允许所有网段的机器访问同步时间\nallow all\n' >> /etc/chrony.conf
|
||||
fi
|
||||
systemctl enable chronyd
|
||||
systemctl restart chronyd
|
||||
|
||||
log_info "配置 MariaDB..."
|
||||
mkdir -p /etc/my.cnf.d
|
||||
backup_config /etc/my.cnf.d/openstack.cnf
|
||||
cat > /etc/my.cnf.d/openstack.cnf <<'EOF'
|
||||
[mysqld]
|
||||
bind-address = 0.0.0.0
|
||||
# 默认使用 InnoDB,每个表的数据独立保存成文件。
|
||||
default-storage-engine = innodb
|
||||
innodb_file_per_table = on
|
||||
# 调高最大连接数,并使用 UTF-8 字符集。
|
||||
max_connections = 4096
|
||||
collation-server = utf8_general_ci
|
||||
character-set-server = utf8
|
||||
EOF
|
||||
systemctl enable mariadb
|
||||
systemctl restart mariadb
|
||||
|
||||
log_info "初始化 MariaDB 安全配置及 root 远程登录权限..."
|
||||
# CentOS 7 的 MariaDB 5.5:自动执行 mysql_secure_installation 的等效操作。
|
||||
# 使用权限为 600 的临时客户端配置,避免把密码放进进程参数。
|
||||
MYSQL_AUTH_FILE=$(mktemp)
|
||||
trap 'rm -f "$MYSQL_AUTH_FILE"' EXIT
|
||||
chmod 600 "$MYSQL_AUTH_FILE"
|
||||
cat > "$MYSQL_AUTH_FILE" <<'EOF'
|
||||
[client]
|
||||
user=root
|
||||
password=123456
|
||||
protocol=socket
|
||||
EOF
|
||||
MYSQL_CMD=(mysql --defaults-extra-file="$MYSQL_AUTH_FILE")
|
||||
if ! "${MYSQL_CMD[@]}" -e 'SELECT 1' > /dev/null 2>&1; then
|
||||
MYSQL_CMD=(mysql --no-defaults --user=root --protocol=socket)
|
||||
if ! "${MYSQL_CMD[@]}" -e 'SELECT 1' > /dev/null 2>&1; then
|
||||
log_err "无法使用空密码或预设密码连接 MariaDB root,请检查已有数据库凭据。"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
"${MYSQL_CMD[@]}" <<'SQL'
|
||||
DELETE FROM mysql.user WHERE User = '';
|
||||
DELETE FROM mysql.user
|
||||
WHERE User = 'root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');
|
||||
UPDATE mysql.user SET Password = PASSWORD('123456') WHERE User = 'root';
|
||||
DROP DATABASE IF EXISTS test;
|
||||
DELETE FROM mysql.db WHERE Db = 'test' OR Db = 'test\_%';
|
||||
FLUSH PRIVILEGES;
|
||||
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456' WITH GRANT OPTION;
|
||||
FLUSH PRIVILEGES;
|
||||
SQL
|
||||
mysql --defaults-extra-file="$MYSQL_AUTH_FILE" -e 'SELECT 1' > /dev/null
|
||||
rm -f "$MYSQL_AUTH_FILE"
|
||||
trap - EXIT
|
||||
|
||||
log_info "配置 RabbitMQ 用户及权限..."
|
||||
systemctl enable rabbitmq-server
|
||||
systemctl start rabbitmq-server
|
||||
RABBITMQ_USERS=$(rabbitmqctl list_users -q)
|
||||
if awk '$1 == "openstack" { found = 1 } END { exit !found }' <<< "$RABBITMQ_USERS"; then
|
||||
rabbitmqctl change_password openstack openstack
|
||||
else
|
||||
rabbitmqctl add_user openstack openstack
|
||||
fi
|
||||
rabbitmqctl set_permissions -p / openstack '.*' '.*' '.*'
|
||||
|
||||
log_info "配置 Memcached..."
|
||||
backup_config /etc/sysconfig/memcached
|
||||
sed -i '/^[[:space:]]*OPTIONS=/d' /etc/sysconfig/memcached
|
||||
echo 'OPTIONS="-l 127.0.0.1,::1,controller"' >> /etc/sysconfig/memcached
|
||||
systemctl enable memcached
|
||||
systemctl restart memcached
|
||||
|
||||
log_info "配置 Etcd..."
|
||||
backup_config /etc/etcd/etcd.conf
|
||||
cat > /etc/etcd/etcd.conf <<'EOF'
|
||||
#[Member]
|
||||
ETCD_LISTEN_PEER_URLS="http://0.0.0.0:2380"
|
||||
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"
|
||||
ETCD_NAME="controller"
|
||||
|
||||
#[Clustering]
|
||||
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://controller:2380"
|
||||
ETCD_ADVERTISE_CLIENT_URLS="http://controller:2379"
|
||||
ETCD_INITIAL_CLUSTER="controller=http://controller:2380"
|
||||
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster-01"
|
||||
ETCD_INITIAL_CLUSTER_STATE="new"
|
||||
EOF
|
||||
systemctl enable etcd
|
||||
systemctl restart etcd
|
||||
|
||||
log_info "检查各项服务状态..."
|
||||
for service in libvirtd chronyd mariadb rabbitmq-server memcached etcd; do
|
||||
if systemctl is-active --quiet "$service"; then
|
||||
log_info "$service 状态正常。"
|
||||
else
|
||||
log_err "$service 未正常运行,请使用 journalctl -u $service 查看日志。"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
log_info "=========================================="
|
||||
log_info "OpenStack 控制节点初始化完成!"
|
||||
log_info "控制节点:$CONTROLLER_HOSTNAME ($CONTROLLER_IP)"
|
||||
log_info "MariaDB root 密码:123456,已允许远程登录。"
|
||||
log_info "RabbitMQ 用户/密码:openstack / openstack"
|
||||
log_info "=========================================="
|
||||
@@ -0,0 +1,304 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_err() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 检查 root 权限
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
log_err "请使用 root 权限执行此脚本(如: sudo bash $0)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "=========================================="
|
||||
log_info "开始初始化实验环境..."
|
||||
log_info "=========================================="
|
||||
|
||||
# 直接连接终端,保留 yum 原生下载进度条;不要通过 tee 管道转存输出。
|
||||
run_yum() {
|
||||
local status
|
||||
log_info "执行 yum $*(下载速度取决于网络和镜像源)..."
|
||||
if yum "$@"; then
|
||||
log_info "yum 操作完成。"
|
||||
else
|
||||
status=$?
|
||||
log_err "yum 操作失败(退出码 $status),请查看上方错误信息。"
|
||||
return "$status"
|
||||
fi
|
||||
}
|
||||
|
||||
REPO_DIR="/etc/yum.repos.d"
|
||||
BACKUP_DIR="/etc/yum.repos.d.bak"
|
||||
|
||||
# 首次运行时备份原有软件源
|
||||
if [ ! -d "$BACKUP_DIR" ]; then
|
||||
log_info "备份原有 yum 软件源..."
|
||||
cp -a "$REPO_DIR" "$BACKUP_DIR"
|
||||
fi
|
||||
|
||||
# 配置 CentOS 7.9 基础源
|
||||
log_info "配置 CentOS 7.9 腾讯云软件源..."
|
||||
|
||||
cat > "$REPO_DIR/CentOS-Base.repo" <<'EOF'
|
||||
[base]
|
||||
name=CentOS-7 - Base - Tencent
|
||||
baseurl=https://mirrors.cloud.tencent.com/centos-vault/7.9.2009/os/$basearch/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
|
||||
[updates]
|
||||
name=CentOS-7 - Updates - Tencent
|
||||
baseurl=https://mirrors.cloud.tencent.com/centos-vault/7.9.2009/updates/$basearch/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
|
||||
[extras]
|
||||
name=CentOS-7 - Extras - Tencent
|
||||
baseurl=https://mirrors.cloud.tencent.com/centos-vault/7.9.2009/extras/$basearch/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
EOF
|
||||
|
||||
# 配置 QEMU EV 源
|
||||
cat > "$REPO_DIR/CentOS-QEMU-EV.repo" <<'EOF'
|
||||
[centos-qemu-ev]
|
||||
name=CentOS-7 - QEMU EV - Tencent
|
||||
baseurl=https://mirrors.cloud.tencent.com/centos-vault/7.9.2009/virt/$basearch/kvm-common/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
EOF
|
||||
|
||||
# 配置 OpenStack Rocky 源
|
||||
cat > "$REPO_DIR/CentOS-OpenStack-rocky.repo" <<'EOF'
|
||||
[centos-openstack-rocky]
|
||||
name=CentOS-7 - OpenStack Rocky - Tencent
|
||||
baseurl=https://mirrors.cloud.tencent.com/centos-vault/7.9.2009/cloud/$basearch/openstack-rocky/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
exclude=sip,PyQt4
|
||||
EOF
|
||||
|
||||
# 删除不再需要的 Ceph 源,避免 yum 读取其元数据
|
||||
rm -f "$REPO_DIR/CentOS-Ceph-Luminous.repo"
|
||||
|
||||
|
||||
# 清除旧仓库元数据,保留已下载的软件包缓存
|
||||
log_info "刷新软件源元数据..."
|
||||
run_yum clean metadata
|
||||
|
||||
# 安装 KVM / libvirt
|
||||
log_info "安装/检查虚拟化软件包..."
|
||||
|
||||
run_yum install -y \
|
||||
qemu-kvm \
|
||||
libvirt \
|
||||
virt-install \
|
||||
bridge-utils \
|
||||
qemu-img
|
||||
|
||||
# 启动 libvirtd
|
||||
log_info "启动 libvirtd 服务..."
|
||||
|
||||
systemctl enable --now libvirtd > /dev/null
|
||||
|
||||
# 验证服务状态
|
||||
if systemctl is-active --quiet libvirtd; then
|
||||
log_info "libvirtd 状态正常。"
|
||||
else
|
||||
log_err "libvirtd 启动失败!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "=========================================="
|
||||
log_info "基础虚拟化环境初始化完成,开始配置控制节点..."
|
||||
log_info "=========================================="
|
||||
|
||||
CONTROLLER_IP="192.168.30.129"
|
||||
CONTROLLER_HOSTNAME="controller"
|
||||
|
||||
# 首次修改时备份配置,重复运行不覆盖原始备份。
|
||||
backup_config() {
|
||||
if [ -f "$1" ] && [ ! -e "$1.env3-1.bak" ]; then
|
||||
cp -a "$1" "$1.env3-1.bak"
|
||||
fi
|
||||
}
|
||||
|
||||
log_info "关闭防火墙和 SELinux..."
|
||||
if systemctl list-unit-files firewalld.service --no-legend | grep -q '^firewalld.service'; then
|
||||
systemctl stop firewalld
|
||||
systemctl disable firewalld
|
||||
fi
|
||||
if command -v getenforce > /dev/null 2>&1; then
|
||||
if [ "$(getenforce)" = "Enforcing" ]; then
|
||||
setenforce 0
|
||||
fi
|
||||
fi
|
||||
if [ -f /etc/selinux/config ]; then
|
||||
backup_config /etc/selinux/config
|
||||
sed -i '/^[[:space:]]*SELINUX=/d' /etc/selinux/config
|
||||
echo 'SELINUX=disabled' >> /etc/selinux/config
|
||||
fi
|
||||
|
||||
log_info "配置主机名和 hosts 解析..."
|
||||
hostnamectl set-hostname "$CONTROLLER_HOSTNAME"
|
||||
backup_config /etc/hosts
|
||||
# 移除旧 controller 映射,保留同一行的其他别名及注释。
|
||||
HOSTS_TMP=$(mktemp)
|
||||
awk -v host="$CONTROLLER_HOSTNAME" '
|
||||
/^[[:space:]]*#/ || NF == 0 { print; next }
|
||||
{
|
||||
comment = ""
|
||||
pos = index($0, "#")
|
||||
if (pos) {
|
||||
comment = substr($0, pos)
|
||||
$0 = substr($0, 1, pos - 1)
|
||||
}
|
||||
line = $1
|
||||
aliases = 0
|
||||
for (i = 2; i <= NF; i++) {
|
||||
if ($i != host) {
|
||||
line = line " " $i
|
||||
aliases++
|
||||
}
|
||||
}
|
||||
if (aliases) print line (comment == "" ? "" : " " comment)
|
||||
else if (comment != "") print comment
|
||||
}
|
||||
' /etc/hosts > "$HOSTS_TMP"
|
||||
cat "$HOSTS_TMP" > /etc/hosts
|
||||
rm -f "$HOSTS_TMP"
|
||||
printf '%s %s\n' "$CONTROLLER_IP" "$CONTROLLER_HOSTNAME" >> /etc/hosts
|
||||
|
||||
log_info "升级系统软件包并安装 OpenStack 基础服务..."
|
||||
run_yum -y upgrade
|
||||
run_yum install -y chrony python2-openstackclient \
|
||||
mariadb mariadb-server python2-PyMySQL \
|
||||
rabbitmq-server memcached python-memcached etcd
|
||||
|
||||
log_info "配置 chrony 时间服务器..."
|
||||
backup_config /etc/chrony.conf
|
||||
if ! grep -Eq '^[[:space:]]*allow[[:space:]]+all([[:space:]]*(#.*)?)?$' /etc/chrony.conf; then
|
||||
printf '\n# 允许所有网段的机器访问同步时间\nallow all\n' >> /etc/chrony.conf
|
||||
fi
|
||||
systemctl enable chronyd
|
||||
systemctl restart chronyd
|
||||
|
||||
log_info "配置 MariaDB..."
|
||||
mkdir -p /etc/my.cnf.d
|
||||
backup_config /etc/my.cnf.d/openstack.cnf
|
||||
cat > /etc/my.cnf.d/openstack.cnf <<'EOF'
|
||||
[mysqld]
|
||||
bind-address = 0.0.0.0
|
||||
# 默认使用 InnoDB,每个表的数据独立保存成文件。
|
||||
default-storage-engine = innodb
|
||||
innodb_file_per_table = on
|
||||
# 调高最大连接数,并使用 UTF-8 字符集。
|
||||
max_connections = 4096
|
||||
collation-server = utf8_general_ci
|
||||
character-set-server = utf8
|
||||
EOF
|
||||
systemctl enable mariadb
|
||||
systemctl restart mariadb
|
||||
|
||||
log_info "初始化 MariaDB 安全配置及 root 远程登录权限..."
|
||||
# CentOS 7 的 MariaDB 5.5:自动执行 mysql_secure_installation 的等效操作。
|
||||
# 使用权限为 600 的临时客户端配置,避免把密码放进进程参数。
|
||||
MYSQL_AUTH_FILE=$(mktemp)
|
||||
trap 'rm -f "$MYSQL_AUTH_FILE"' EXIT
|
||||
chmod 600 "$MYSQL_AUTH_FILE"
|
||||
cat > "$MYSQL_AUTH_FILE" <<'EOF'
|
||||
[client]
|
||||
user=root
|
||||
password=123456
|
||||
protocol=socket
|
||||
EOF
|
||||
MYSQL_CMD=(mysql --defaults-extra-file="$MYSQL_AUTH_FILE")
|
||||
if ! "${MYSQL_CMD[@]}" -e 'SELECT 1' > /dev/null 2>&1; then
|
||||
MYSQL_CMD=(mysql --no-defaults --user=root --protocol=socket)
|
||||
if ! "${MYSQL_CMD[@]}" -e 'SELECT 1' > /dev/null 2>&1; then
|
||||
log_err "无法使用空密码或预设密码连接 MariaDB root,请检查已有数据库凭据。"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
"${MYSQL_CMD[@]}" <<'SQL'
|
||||
DELETE FROM mysql.user WHERE User = '';
|
||||
DELETE FROM mysql.user
|
||||
WHERE User = 'root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');
|
||||
UPDATE mysql.user SET Password = PASSWORD('123456') WHERE User = 'root';
|
||||
DROP DATABASE IF EXISTS test;
|
||||
DELETE FROM mysql.db WHERE Db = 'test' OR Db = 'test\_%';
|
||||
FLUSH PRIVILEGES;
|
||||
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456' WITH GRANT OPTION;
|
||||
FLUSH PRIVILEGES;
|
||||
SQL
|
||||
mysql --defaults-extra-file="$MYSQL_AUTH_FILE" -e 'SELECT 1' > /dev/null
|
||||
rm -f "$MYSQL_AUTH_FILE"
|
||||
trap - EXIT
|
||||
|
||||
log_info "配置 RabbitMQ 用户及权限..."
|
||||
systemctl enable rabbitmq-server
|
||||
systemctl start rabbitmq-server
|
||||
RABBITMQ_USERS=$(rabbitmqctl list_users -q)
|
||||
if awk '$1 == "openstack" { found = 1 } END { exit !found }' <<< "$RABBITMQ_USERS"; then
|
||||
rabbitmqctl change_password openstack openstack
|
||||
else
|
||||
rabbitmqctl add_user openstack openstack
|
||||
fi
|
||||
rabbitmqctl set_permissions -p / openstack '.*' '.*' '.*'
|
||||
|
||||
log_info "配置 Memcached..."
|
||||
backup_config /etc/sysconfig/memcached
|
||||
sed -i '/^[[:space:]]*OPTIONS=/d' /etc/sysconfig/memcached
|
||||
echo 'OPTIONS="-l 127.0.0.1,::1,controller"' >> /etc/sysconfig/memcached
|
||||
systemctl enable memcached
|
||||
systemctl restart memcached
|
||||
|
||||
log_info "配置 Etcd..."
|
||||
backup_config /etc/etcd/etcd.conf
|
||||
cat > /etc/etcd/etcd.conf <<'EOF'
|
||||
#[Member]
|
||||
ETCD_LISTEN_PEER_URLS="http://0.0.0.0:2380"
|
||||
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"
|
||||
ETCD_NAME="controller"
|
||||
|
||||
#[Clustering]
|
||||
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://controller:2380"
|
||||
ETCD_ADVERTISE_CLIENT_URLS="http://controller:2379"
|
||||
ETCD_INITIAL_CLUSTER="controller=http://controller:2380"
|
||||
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster-01"
|
||||
ETCD_INITIAL_CLUSTER_STATE="new"
|
||||
EOF
|
||||
systemctl enable etcd
|
||||
systemctl restart etcd
|
||||
|
||||
log_info "检查各项服务状态..."
|
||||
for service in libvirtd chronyd mariadb rabbitmq-server memcached etcd; do
|
||||
if systemctl is-active --quiet "$service"; then
|
||||
log_info "$service 状态正常。"
|
||||
else
|
||||
log_err "$service 未正常运行,请使用 journalctl -u $service 查看日志。"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
log_info "=========================================="
|
||||
log_info "OpenStack 控制节点初始化完成!"
|
||||
log_info "控制节点:$CONTROLLER_HOSTNAME ($CONTROLLER_IP)"
|
||||
log_info "MariaDB root 密码:123456,已允许远程登录。"
|
||||
log_info "RabbitMQ 用户/密码:openstack / openstack"
|
||||
log_info "=========================================="
|
||||
Executable
+353
@@ -0,0 +1,353 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_err() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 检查 root 权限
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
log_err "请使用 root 权限执行此脚本(如: sudo bash $0)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "=========================================="
|
||||
log_info "开始初始化实验环境..."
|
||||
log_info "=========================================="
|
||||
|
||||
# 直接连接终端,保留 yum 原生下载进度条;不要通过 tee 管道转存输出。
|
||||
run_yum() {
|
||||
local status
|
||||
log_info "执行 yum $*(下载速度取决于网络和镜像源)..."
|
||||
# 仅启用本地仓库,避免系统中其他已启用的公网仓库参与下载。
|
||||
if yum --disablerepo='*' \
|
||||
--enablerepo=local-base,local-updates,local-extras,local-qemu-ev,local-openstack-rocky \
|
||||
"$@"; then
|
||||
log_info "yum 操作完成。"
|
||||
else
|
||||
status=$?
|
||||
log_err "yum 操作失败(退出码 $status),请查看上方错误信息。"
|
||||
return "$status"
|
||||
fi
|
||||
}
|
||||
|
||||
REPO_DIR="/etc/yum.repos.d"
|
||||
BACKUP_DIR="/etc/yum.repos.d.bak"
|
||||
|
||||
# 首次运行时备份原有软件源
|
||||
if [ ! -d "$BACKUP_DIR" ]; then
|
||||
log_info "备份原有 yum 软件源..."
|
||||
cp -a "$REPO_DIR" "$BACKUP_DIR"
|
||||
fi
|
||||
|
||||
# 配置 CentOS 7.9 基础源
|
||||
log_info "配置 CentOS 7.9 本地软件源(192.168.192.205:3080)..."
|
||||
|
||||
cat > "$REPO_DIR/CentOS-Base.repo" <<'EOF'
|
||||
[local-base]
|
||||
name=Local - Base
|
||||
baseurl=http://192.168.192.205:3080/vault-base/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
|
||||
[local-updates]
|
||||
name=Local - Updates
|
||||
baseurl=http://192.168.192.205:3080/vault-updates/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
|
||||
[local-extras]
|
||||
name=Local - Extras
|
||||
baseurl=http://192.168.192.205:3080/vault-extras/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
EOF
|
||||
|
||||
# 配置 QEMU EV 源
|
||||
cat > "$REPO_DIR/CentOS-QEMU-EV.repo" <<'EOF'
|
||||
[local-qemu-ev]
|
||||
name=Local - QEMU EV
|
||||
baseurl=http://192.168.192.205:3080/vault-centos-qemu-ev/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
EOF
|
||||
|
||||
# 配置 OpenStack Rocky 源
|
||||
cat > "$REPO_DIR/CentOS-OpenStack-rocky.repo" <<'EOF'
|
||||
[local-openstack-rocky]
|
||||
name=Local - OpenStack Rocky
|
||||
baseurl=http://192.168.192.205:3080/vault-centos-openstack-rocky/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
exclude=sip,PyQt4
|
||||
EOF
|
||||
|
||||
# 删除不再需要的 Ceph 源,避免 yum 读取其元数据
|
||||
rm -f "$REPO_DIR/CentOS-Ceph-Luminous.repo"
|
||||
|
||||
|
||||
# 清除旧仓库元数据,保留已下载的软件包缓存
|
||||
log_info "刷新软件源元数据..."
|
||||
run_yum clean metadata
|
||||
|
||||
CONTROLLER_IP="192.168.30.129"
|
||||
CONTROLLER_HOSTNAME="controller"
|
||||
|
||||
# 首次修改时备份配置,重复运行不覆盖原始备份。
|
||||
backup_config() {
|
||||
if [ -f "$1" ] && [ ! -e "$1.env3-2.bak" ]; then
|
||||
cp -a "$1" "$1.env3-2.bak"
|
||||
fi
|
||||
}
|
||||
|
||||
log_info "关闭防火墙和 SELinux..."
|
||||
if systemctl list-unit-files firewalld.service --no-legend | grep -q '^firewalld.service'; then
|
||||
systemctl stop firewalld
|
||||
systemctl disable firewalld
|
||||
fi
|
||||
if command -v getenforce > /dev/null 2>&1; then
|
||||
if [ "$(getenforce)" = "Enforcing" ]; then
|
||||
setenforce 0
|
||||
fi
|
||||
fi
|
||||
if [ -f /etc/selinux/config ]; then
|
||||
backup_config /etc/selinux/config
|
||||
sed -i '/^[[:space:]]*SELINUX=/d' /etc/selinux/config
|
||||
echo 'SELINUX=disabled' >> /etc/selinux/config
|
||||
fi
|
||||
|
||||
log_info "配置主机名和 hosts 解析..."
|
||||
hostnamectl set-hostname "$CONTROLLER_HOSTNAME"
|
||||
backup_config /etc/hosts
|
||||
# 移除旧 controller 映射,保留同一行的其他别名及注释。
|
||||
HOSTS_TMP=$(mktemp)
|
||||
awk -v host="$CONTROLLER_HOSTNAME" '
|
||||
/^[[:space:]]*#/ || NF == 0 { print; next }
|
||||
{
|
||||
comment = ""
|
||||
pos = index($0, "#")
|
||||
if (pos) {
|
||||
comment = substr($0, pos)
|
||||
$0 = substr($0, 1, pos - 1)
|
||||
}
|
||||
line = $1
|
||||
aliases = 0
|
||||
for (i = 2; i <= NF; i++) {
|
||||
if ($i != host) {
|
||||
line = line " " $i
|
||||
aliases++
|
||||
}
|
||||
}
|
||||
if (aliases) print line (comment == "" ? "" : " " comment)
|
||||
else if (comment != "") print comment
|
||||
}
|
||||
' /etc/hosts > "$HOSTS_TMP"
|
||||
cat "$HOSTS_TMP" > /etc/hosts
|
||||
rm -f "$HOSTS_TMP"
|
||||
printf '%s %s\n' "$CONTROLLER_IP" "$CONTROLLER_HOSTNAME" >> /etc/hosts
|
||||
|
||||
log_info "升级系统软件包,合并安装虚拟化、OpenStack 基础服务和 Keystone..."
|
||||
run_yum -y upgrade
|
||||
run_yum install -y chrony python2-openstackclient \
|
||||
mariadb mariadb-server python2-PyMySQL \
|
||||
rabbitmq-server memcached python-memcached etcd \
|
||||
qemu-kvm libvirt virt-install bridge-utils qemu-img \
|
||||
openstack-keystone httpd mod_wsgi
|
||||
|
||||
# 启动 libvirtd
|
||||
log_info "启动 libvirtd 服务..."
|
||||
|
||||
systemctl enable --now libvirtd > /dev/null
|
||||
|
||||
# 验证服务状态
|
||||
if systemctl is-active --quiet libvirtd; then
|
||||
log_info "libvirtd 状态正常。"
|
||||
else
|
||||
log_err "libvirtd 启动失败!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
log_info "配置 chrony 时间服务器..."
|
||||
backup_config /etc/chrony.conf
|
||||
if ! grep -Eq '^[[:space:]]*allow[[:space:]]+all([[:space:]]*(#.*)?)?$' /etc/chrony.conf; then
|
||||
printf '\n# 允许所有网段的机器访问同步时间\nallow all\n' >> /etc/chrony.conf
|
||||
fi
|
||||
systemctl enable chronyd
|
||||
systemctl restart chronyd
|
||||
|
||||
log_info "配置 MariaDB..."
|
||||
mkdir -p /etc/my.cnf.d
|
||||
backup_config /etc/my.cnf.d/openstack.cnf
|
||||
cat > /etc/my.cnf.d/openstack.cnf <<'EOF'
|
||||
[mysqld]
|
||||
bind-address = 0.0.0.0
|
||||
# 默认使用 InnoDB,每个表的数据独立保存成文件。
|
||||
default-storage-engine = innodb
|
||||
innodb_file_per_table = on
|
||||
# 调高最大连接数,并使用 UTF-8 字符集。
|
||||
max_connections = 4096
|
||||
collation-server = utf8_general_ci
|
||||
character-set-server = utf8
|
||||
EOF
|
||||
systemctl enable mariadb
|
||||
systemctl restart mariadb
|
||||
|
||||
log_info "初始化 MariaDB 安全配置及 root 远程登录权限..."
|
||||
# CentOS 7 的 MariaDB 5.5:自动执行 mysql_secure_installation 的等效操作。
|
||||
# 使用权限为 600 的临时客户端配置,避免把密码放进进程参数。
|
||||
MYSQL_AUTH_FILE=$(mktemp)
|
||||
trap 'rm -f "$MYSQL_AUTH_FILE"' EXIT
|
||||
chmod 600 "$MYSQL_AUTH_FILE"
|
||||
cat > "$MYSQL_AUTH_FILE" <<'EOF'
|
||||
[client]
|
||||
user=root
|
||||
password=123456
|
||||
protocol=socket
|
||||
EOF
|
||||
MYSQL_CMD=(mysql --defaults-extra-file="$MYSQL_AUTH_FILE")
|
||||
if ! "${MYSQL_CMD[@]}" -e 'SELECT 1' > /dev/null 2>&1; then
|
||||
MYSQL_CMD=(mysql --no-defaults --user=root --protocol=socket)
|
||||
if ! "${MYSQL_CMD[@]}" -e 'SELECT 1' > /dev/null 2>&1; then
|
||||
log_err "无法使用空密码或预设密码连接 MariaDB root,请检查已有数据库凭据。"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
"${MYSQL_CMD[@]}" <<'SQL'
|
||||
DELETE FROM mysql.user WHERE User = '';
|
||||
DELETE FROM mysql.user
|
||||
WHERE User = 'root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');
|
||||
UPDATE mysql.user SET Password = PASSWORD('123456') WHERE User = 'root';
|
||||
DROP DATABASE IF EXISTS test;
|
||||
DELETE FROM mysql.db WHERE Db = 'test' OR Db = 'test\_%';
|
||||
FLUSH PRIVILEGES;
|
||||
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456' WITH GRANT OPTION;
|
||||
FLUSH PRIVILEGES;
|
||||
SQL
|
||||
log_info "创建 Keystone 数据库并授权..."
|
||||
mysql --defaults-extra-file="$MYSQL_AUTH_FILE" <<'SQL'
|
||||
CREATE DATABASE IF NOT EXISTS keystone;
|
||||
GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'localhost' IDENTIFIED BY 'keystone';
|
||||
GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'%' IDENTIFIED BY 'keystone';
|
||||
FLUSH PRIVILEGES;
|
||||
SQL
|
||||
rm -f "$MYSQL_AUTH_FILE"
|
||||
trap - EXIT
|
||||
|
||||
log_info "配置 RabbitMQ 用户及权限..."
|
||||
systemctl enable rabbitmq-server
|
||||
systemctl start rabbitmq-server
|
||||
RABBITMQ_USERS=$(rabbitmqctl list_users -q)
|
||||
if awk '$1 == "openstack" { found = 1 } END { exit !found }' <<< "$RABBITMQ_USERS"; then
|
||||
rabbitmqctl change_password openstack openstack
|
||||
else
|
||||
rabbitmqctl add_user openstack openstack
|
||||
fi
|
||||
rabbitmqctl set_permissions -p / openstack '.*' '.*' '.*'
|
||||
|
||||
log_info "配置 Memcached..."
|
||||
backup_config /etc/sysconfig/memcached
|
||||
sed -i '/^[[:space:]]*OPTIONS=/d' /etc/sysconfig/memcached
|
||||
echo 'OPTIONS="-l 127.0.0.1,::1,controller"' >> /etc/sysconfig/memcached
|
||||
systemctl enable memcached
|
||||
systemctl restart memcached
|
||||
|
||||
log_info "配置 Etcd..."
|
||||
backup_config /etc/etcd/etcd.conf
|
||||
cat > /etc/etcd/etcd.conf <<'EOF'
|
||||
#[Member]
|
||||
ETCD_LISTEN_PEER_URLS="http://0.0.0.0:2380"
|
||||
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"
|
||||
ETCD_NAME="controller"
|
||||
|
||||
#[Clustering]
|
||||
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://controller:2380"
|
||||
ETCD_ADVERTISE_CLIENT_URLS="http://controller:2379"
|
||||
ETCD_INITIAL_CLUSTER="controller=http://controller:2380"
|
||||
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster-01"
|
||||
ETCD_INITIAL_CLUSTER_STATE="new"
|
||||
EOF
|
||||
systemctl enable etcd
|
||||
systemctl restart etcd
|
||||
|
||||
log_info "配置 Keystone 数据库连接及 Fernet 令牌..."
|
||||
backup_config /etc/keystone/keystone.conf
|
||||
cat > /etc/keystone/keystone.conf <<'EOF'
|
||||
[database]
|
||||
connection = mysql+pymysql://keystone:keystone@controller/keystone
|
||||
[token]
|
||||
provider = fernet
|
||||
EOF
|
||||
chown root:keystone /etc/keystone/keystone.conf
|
||||
chmod 640 /etc/keystone/keystone.conf
|
||||
|
||||
log_info "同步 Keystone 数据库表结构..."
|
||||
su -s /bin/sh -c "keystone-manage db_sync" keystone
|
||||
|
||||
# 使用 setup 命令初始化密钥库,不删除已有密钥。
|
||||
log_info "初始化 Fernet 和 credential 密钥库..."
|
||||
keystone-manage fernet_setup --keystone-user keystone --keystone-group keystone
|
||||
keystone-manage credential_setup --keystone-user keystone --keystone-group keystone
|
||||
|
||||
log_info "初始化 Keystone 管理员及服务端点..."
|
||||
keystone-manage bootstrap --bootstrap-password admin \
|
||||
--bootstrap-admin-url http://controller:5000/v3 \
|
||||
--bootstrap-public-url http://controller:5000/v3 \
|
||||
--bootstrap-internal-url http://controller:5000/v3 \
|
||||
--bootstrap-region-id RegionOne
|
||||
|
||||
log_info "配置 Apache HTTP 服务..."
|
||||
backup_config /etc/httpd/conf/httpd.conf
|
||||
# 删除已有生效的 ServerName,避免重复追加。
|
||||
sed -i '/^[[:space:]]*ServerName[[:space:]]/d' /etc/httpd/conf/httpd.conf
|
||||
printf '\nServerName controller\n' >> /etc/httpd/conf/httpd.conf
|
||||
|
||||
WSGI_SOURCE="/usr/share/keystone/wsgi-keystone.conf"
|
||||
WSGI_LINK="/etc/httpd/conf.d/wsgi-keystone.conf"
|
||||
if [ ! -f "$WSGI_SOURCE" ]; then
|
||||
log_err "缺少 Keystone WSGI 配置:$WSGI_SOURCE"
|
||||
exit 1
|
||||
fi
|
||||
# 备份已有普通配置文件;重复运行时更新软链接。
|
||||
if [ ! -L "$WSGI_LINK" ]; then
|
||||
backup_config "$WSGI_LINK"
|
||||
fi
|
||||
ln -sfnT "$WSGI_SOURCE" "$WSGI_LINK"
|
||||
|
||||
log_info "检查 Apache 配置并启动服务..."
|
||||
httpd -t
|
||||
systemctl enable httpd
|
||||
systemctl restart httpd
|
||||
|
||||
log_info "检查各项服务状态..."
|
||||
for service in libvirtd chronyd mariadb rabbitmq-server memcached etcd httpd; do
|
||||
if systemctl is-active --quiet "$service"; then
|
||||
log_info "$service 状态正常。"
|
||||
else
|
||||
log_err "$service 未正常运行,请使用 journalctl -u $service 查看日志。"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
log_info "=========================================="
|
||||
log_info "OpenStack 控制节点及 Keystone 初始化完成!"
|
||||
log_info "控制节点:$CONTROLLER_HOSTNAME ($CONTROLLER_IP)"
|
||||
log_info "MariaDB root 密码:123456,已允许远程登录。"
|
||||
log_info "RabbitMQ 用户/密码:openstack / openstack"
|
||||
log_info "Keystone 管理员/密码:admin / admin,区域:RegionOne"
|
||||
log_info "Keystone API:http://controller:5000/v3"
|
||||
log_info "=========================================="
|
||||
Executable
+517
@@ -0,0 +1,517 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_err() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 检查 root 权限
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
log_err "请使用 root 权限执行此脚本(如: sudo bash $0)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "=========================================="
|
||||
log_info "开始初始化实验环境..."
|
||||
log_info "=========================================="
|
||||
|
||||
# 直接连接终端,保留 yum 原生下载进度条;不要通过 tee 管道转存输出。
|
||||
run_yum() {
|
||||
local status
|
||||
log_info "执行 yum $*(下载速度取决于网络和镜像源)..."
|
||||
# 仅启用本地仓库,避免系统中其他已启用的公网仓库参与下载。
|
||||
if yum --disablerepo='*' \
|
||||
--enablerepo=local-base,local-updates,local-extras,local-qemu-ev,local-openstack-rocky \
|
||||
"$@"; then
|
||||
log_info "yum 操作完成。"
|
||||
else
|
||||
status=$?
|
||||
log_err "yum 操作失败(退出码 $status),请查看上方错误信息。"
|
||||
return "$status"
|
||||
fi
|
||||
}
|
||||
|
||||
REPO_DIR="/etc/yum.repos.d"
|
||||
BACKUP_DIR="/etc/yum.repos.d.bak"
|
||||
|
||||
# 首次运行时备份原有软件源
|
||||
if [ ! -d "$BACKUP_DIR" ]; then
|
||||
log_info "备份原有 yum 软件源..."
|
||||
cp -a "$REPO_DIR" "$BACKUP_DIR"
|
||||
fi
|
||||
|
||||
# 配置 CentOS 7.9 基础源
|
||||
log_info "配置 CentOS 7.9 本地软件源(192.168.192.205:3080)..."
|
||||
|
||||
cat > "$REPO_DIR/CentOS-Base.repo" <<'EOF'
|
||||
[local-base]
|
||||
name=Local - Base
|
||||
baseurl=http://192.168.192.205:3080/vault-base/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
|
||||
[local-updates]
|
||||
name=Local - Updates
|
||||
baseurl=http://192.168.192.205:3080/vault-updates/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
|
||||
[local-extras]
|
||||
name=Local - Extras
|
||||
baseurl=http://192.168.192.205:3080/vault-extras/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
EOF
|
||||
|
||||
# 配置 QEMU EV 源
|
||||
cat > "$REPO_DIR/CentOS-QEMU-EV.repo" <<'EOF'
|
||||
[local-qemu-ev]
|
||||
name=Local - QEMU EV
|
||||
baseurl=http://192.168.192.205:3080/vault-centos-qemu-ev/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
EOF
|
||||
|
||||
# 配置 OpenStack Rocky 源
|
||||
cat > "$REPO_DIR/CentOS-OpenStack-rocky.repo" <<'EOF'
|
||||
[local-openstack-rocky]
|
||||
name=Local - OpenStack Rocky
|
||||
baseurl=http://192.168.192.205:3080/vault-centos-openstack-rocky/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
exclude=sip,PyQt4
|
||||
EOF
|
||||
|
||||
# 删除不再需要的 Ceph 源,避免 yum 读取其元数据
|
||||
rm -f "$REPO_DIR/CentOS-Ceph-Luminous.repo"
|
||||
|
||||
|
||||
# 清除旧仓库元数据,保留已下载的软件包缓存
|
||||
log_info "刷新软件源元数据..."
|
||||
run_yum clean metadata
|
||||
|
||||
CONTROLLER_IP="192.168.30.129"
|
||||
CONTROLLER_HOSTNAME="controller"
|
||||
|
||||
# 首次修改时备份配置,重复运行不覆盖原始备份。
|
||||
backup_config() {
|
||||
if [ -f "$1" ] && [ ! -e "$1.env3-3.bak" ]; then
|
||||
cp -a "$1" "$1.env3-3.bak"
|
||||
fi
|
||||
}
|
||||
|
||||
log_info "关闭防火墙和 SELinux..."
|
||||
if systemctl list-unit-files firewalld.service --no-legend | grep -q '^firewalld.service'; then
|
||||
systemctl stop firewalld
|
||||
systemctl disable firewalld
|
||||
fi
|
||||
if command -v getenforce > /dev/null 2>&1; then
|
||||
if [ "$(getenforce)" = "Enforcing" ]; then
|
||||
setenforce 0
|
||||
fi
|
||||
fi
|
||||
if [ -f /etc/selinux/config ]; then
|
||||
backup_config /etc/selinux/config
|
||||
sed -i '/^[[:space:]]*SELINUX=/d' /etc/selinux/config
|
||||
echo 'SELINUX=disabled' >> /etc/selinux/config
|
||||
fi
|
||||
|
||||
log_info "配置主机名和 hosts 解析..."
|
||||
hostnamectl set-hostname "$CONTROLLER_HOSTNAME"
|
||||
backup_config /etc/hosts
|
||||
# 移除旧 controller 映射,保留同一行的其他别名及注释。
|
||||
HOSTS_TMP=$(mktemp)
|
||||
awk -v host="$CONTROLLER_HOSTNAME" '
|
||||
/^[[:space:]]*#/ || NF == 0 { print; next }
|
||||
{
|
||||
comment = ""
|
||||
pos = index($0, "#")
|
||||
if (pos) {
|
||||
comment = substr($0, pos)
|
||||
$0 = substr($0, 1, pos - 1)
|
||||
}
|
||||
line = $1
|
||||
aliases = 0
|
||||
for (i = 2; i <= NF; i++) {
|
||||
if ($i != host) {
|
||||
line = line " " $i
|
||||
aliases++
|
||||
}
|
||||
}
|
||||
if (aliases) print line (comment == "" ? "" : " " comment)
|
||||
else if (comment != "") print comment
|
||||
}
|
||||
' /etc/hosts > "$HOSTS_TMP"
|
||||
cat "$HOSTS_TMP" > /etc/hosts
|
||||
rm -f "$HOSTS_TMP"
|
||||
printf '%s %s\n' "$CONTROLLER_IP" "$CONTROLLER_HOSTNAME" >> /etc/hosts
|
||||
|
||||
log_info "升级系统软件包,合并安装虚拟化、OpenStack 基础服务、Keystone 和 Glance..."
|
||||
run_yum -y upgrade
|
||||
run_yum install -y chrony python2-openstackclient \
|
||||
mariadb mariadb-server python2-PyMySQL \
|
||||
rabbitmq-server memcached python-memcached etcd \
|
||||
qemu-kvm libvirt virt-install bridge-utils qemu-img \
|
||||
openstack-keystone httpd mod_wsgi openstack-glance
|
||||
|
||||
# 启动 libvirtd
|
||||
log_info "启动 libvirtd 服务..."
|
||||
|
||||
systemctl enable --now libvirtd > /dev/null
|
||||
|
||||
# 验证服务状态
|
||||
if systemctl is-active --quiet libvirtd; then
|
||||
log_info "libvirtd 状态正常。"
|
||||
else
|
||||
log_err "libvirtd 启动失败!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
log_info "配置 chrony 时间服务器..."
|
||||
backup_config /etc/chrony.conf
|
||||
if ! grep -Eq '^[[:space:]]*allow[[:space:]]+all([[:space:]]*(#.*)?)?$' /etc/chrony.conf; then
|
||||
printf '\n# 允许所有网段的机器访问同步时间\nallow all\n' >> /etc/chrony.conf
|
||||
fi
|
||||
systemctl enable chronyd
|
||||
systemctl restart chronyd
|
||||
|
||||
log_info "配置 MariaDB..."
|
||||
mkdir -p /etc/my.cnf.d
|
||||
backup_config /etc/my.cnf.d/openstack.cnf
|
||||
cat > /etc/my.cnf.d/openstack.cnf <<'EOF'
|
||||
[mysqld]
|
||||
bind-address = 0.0.0.0
|
||||
# 默认使用 InnoDB,每个表的数据独立保存成文件。
|
||||
default-storage-engine = innodb
|
||||
innodb_file_per_table = on
|
||||
# 调高最大连接数,并使用 UTF-8 字符集。
|
||||
max_connections = 4096
|
||||
collation-server = utf8_general_ci
|
||||
character-set-server = utf8
|
||||
EOF
|
||||
systemctl enable mariadb
|
||||
systemctl restart mariadb
|
||||
|
||||
log_info "初始化 MariaDB 安全配置及 root 远程登录权限..."
|
||||
# CentOS 7 的 MariaDB 5.5:自动执行 mysql_secure_installation 的等效操作。
|
||||
# 使用权限为 600 的临时客户端配置,避免把密码放进进程参数。
|
||||
MYSQL_AUTH_FILE=$(mktemp)
|
||||
trap 'rm -f "$MYSQL_AUTH_FILE"' EXIT
|
||||
chmod 600 "$MYSQL_AUTH_FILE"
|
||||
cat > "$MYSQL_AUTH_FILE" <<'EOF'
|
||||
[client]
|
||||
user=root
|
||||
password=123456
|
||||
protocol=socket
|
||||
EOF
|
||||
MYSQL_CMD=(mysql --defaults-extra-file="$MYSQL_AUTH_FILE")
|
||||
if ! "${MYSQL_CMD[@]}" -e 'SELECT 1' > /dev/null 2>&1; then
|
||||
MYSQL_CMD=(mysql --no-defaults --user=root --protocol=socket)
|
||||
if ! "${MYSQL_CMD[@]}" -e 'SELECT 1' > /dev/null 2>&1; then
|
||||
log_err "无法使用空密码或预设密码连接 MariaDB root,请检查已有数据库凭据。"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
"${MYSQL_CMD[@]}" <<'SQL'
|
||||
DELETE FROM mysql.user WHERE User = '';
|
||||
DELETE FROM mysql.user
|
||||
WHERE User = 'root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');
|
||||
UPDATE mysql.user SET Password = PASSWORD('123456') WHERE User = 'root';
|
||||
DROP DATABASE IF EXISTS test;
|
||||
DELETE FROM mysql.db WHERE Db = 'test' OR Db = 'test\_%';
|
||||
FLUSH PRIVILEGES;
|
||||
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456' WITH GRANT OPTION;
|
||||
FLUSH PRIVILEGES;
|
||||
SQL
|
||||
log_info "创建 Keystone 和 Glance 数据库并授权..."
|
||||
mysql --defaults-extra-file="$MYSQL_AUTH_FILE" <<'SQL'
|
||||
CREATE DATABASE IF NOT EXISTS keystone;
|
||||
GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'localhost' IDENTIFIED BY 'keystone';
|
||||
GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'%' IDENTIFIED BY 'keystone';
|
||||
CREATE DATABASE IF NOT EXISTS glance;
|
||||
GRANT ALL PRIVILEGES ON glance.* TO 'glance'@'localhost' IDENTIFIED BY 'glance';
|
||||
GRANT ALL PRIVILEGES ON glance.* TO 'glance'@'%' IDENTIFIED BY 'glance';
|
||||
FLUSH PRIVILEGES;
|
||||
SQL
|
||||
rm -f "$MYSQL_AUTH_FILE"
|
||||
trap - EXIT
|
||||
|
||||
log_info "配置 RabbitMQ 用户及权限..."
|
||||
systemctl enable rabbitmq-server
|
||||
systemctl start rabbitmq-server
|
||||
RABBITMQ_USERS=$(rabbitmqctl list_users -q)
|
||||
if awk '$1 == "openstack" { found = 1 } END { exit !found }' <<< "$RABBITMQ_USERS"; then
|
||||
rabbitmqctl change_password openstack openstack
|
||||
else
|
||||
rabbitmqctl add_user openstack openstack
|
||||
fi
|
||||
rabbitmqctl set_permissions -p / openstack '.*' '.*' '.*'
|
||||
|
||||
log_info "配置 Memcached..."
|
||||
backup_config /etc/sysconfig/memcached
|
||||
sed -i '/^[[:space:]]*OPTIONS=/d' /etc/sysconfig/memcached
|
||||
echo 'OPTIONS="-l 127.0.0.1,::1,controller"' >> /etc/sysconfig/memcached
|
||||
systemctl enable memcached
|
||||
systemctl restart memcached
|
||||
|
||||
log_info "配置 Etcd..."
|
||||
backup_config /etc/etcd/etcd.conf
|
||||
cat > /etc/etcd/etcd.conf <<'EOF'
|
||||
#[Member]
|
||||
ETCD_LISTEN_PEER_URLS="http://0.0.0.0:2380"
|
||||
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"
|
||||
ETCD_NAME="controller"
|
||||
|
||||
#[Clustering]
|
||||
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://controller:2380"
|
||||
ETCD_ADVERTISE_CLIENT_URLS="http://controller:2379"
|
||||
ETCD_INITIAL_CLUSTER="controller=http://controller:2380"
|
||||
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster-01"
|
||||
ETCD_INITIAL_CLUSTER_STATE="new"
|
||||
EOF
|
||||
systemctl enable etcd
|
||||
systemctl restart etcd
|
||||
|
||||
log_info "配置 Keystone 数据库连接及 Fernet 令牌..."
|
||||
backup_config /etc/keystone/keystone.conf
|
||||
cat > /etc/keystone/keystone.conf <<'EOF'
|
||||
[database]
|
||||
connection = mysql+pymysql://keystone:keystone@controller/keystone
|
||||
[token]
|
||||
provider = fernet
|
||||
EOF
|
||||
chown root:keystone /etc/keystone/keystone.conf
|
||||
chmod 640 /etc/keystone/keystone.conf
|
||||
|
||||
log_info "同步 Keystone 数据库表结构..."
|
||||
su -s /bin/sh -c "keystone-manage db_sync" keystone
|
||||
|
||||
# 使用 setup 命令初始化密钥库,不删除已有密钥。
|
||||
log_info "初始化 Fernet 和 credential 密钥库..."
|
||||
keystone-manage fernet_setup --keystone-user keystone --keystone-group keystone
|
||||
keystone-manage credential_setup --keystone-user keystone --keystone-group keystone
|
||||
|
||||
log_info "初始化 Keystone 管理员及服务端点..."
|
||||
keystone-manage bootstrap --bootstrap-password admin \
|
||||
--bootstrap-admin-url http://controller:5000/v3 \
|
||||
--bootstrap-public-url http://controller:5000/v3 \
|
||||
--bootstrap-internal-url http://controller:5000/v3 \
|
||||
--bootstrap-region-id RegionOne
|
||||
|
||||
log_info "配置 Apache HTTP 服务..."
|
||||
backup_config /etc/httpd/conf/httpd.conf
|
||||
# 删除已有生效的 ServerName,避免重复追加。
|
||||
sed -i '/^[[:space:]]*ServerName[[:space:]]/d' /etc/httpd/conf/httpd.conf
|
||||
printf '\nServerName controller\n' >> /etc/httpd/conf/httpd.conf
|
||||
|
||||
WSGI_SOURCE="/usr/share/keystone/wsgi-keystone.conf"
|
||||
WSGI_LINK="/etc/httpd/conf.d/wsgi-keystone.conf"
|
||||
if [ ! -f "$WSGI_SOURCE" ]; then
|
||||
log_err "缺少 Keystone WSGI 配置:$WSGI_SOURCE"
|
||||
exit 1
|
||||
fi
|
||||
# 备份已有普通配置文件;重复运行时更新软链接。
|
||||
if [ ! -L "$WSGI_LINK" ]; then
|
||||
backup_config "$WSGI_LINK"
|
||||
fi
|
||||
ln -sfnT "$WSGI_SOURCE" "$WSGI_LINK"
|
||||
|
||||
log_info "检查 Apache 配置并启动服务..."
|
||||
httpd -t
|
||||
systemctl enable httpd
|
||||
systemctl restart httpd
|
||||
|
||||
log_info "创建并加载管理员登录脚本..."
|
||||
ROOT_HOME=$(getent passwd root | cut -d: -f6)
|
||||
if [ -z "$ROOT_HOME" ] || [ ! -d "$ROOT_HOME" ]; then
|
||||
log_err "无法确定 root 用户的家目录。"
|
||||
exit 1
|
||||
fi
|
||||
ADMIN_OPENRC="$ROOT_HOME/admin-openstack.sh"
|
||||
backup_config "$ADMIN_OPENRC"
|
||||
cat > "$ADMIN_OPENRC" <<'EOF'
|
||||
export OS_USERNAME=admin
|
||||
export OS_PASSWORD=admin
|
||||
export OS_PROJECT_NAME=admin
|
||||
export OS_USER_DOMAIN_NAME=Default
|
||||
export OS_PROJECT_DOMAIN_NAME=Default
|
||||
export OS_AUTH_URL=http://controller:5000/v3
|
||||
export OS_IDENTITY_API_VERSION=3
|
||||
EOF
|
||||
chmod 600 "$ADMIN_OPENRC"
|
||||
chown root:root "$ADMIN_OPENRC"
|
||||
# 清除继承的其他 OS_* 认证设置,避免学生之前的环境变量干扰本次配置。
|
||||
for variable in ${!OS_@}; do
|
||||
unset "$variable"
|
||||
done
|
||||
source "$ADMIN_OPENRC"
|
||||
|
||||
# Apache 启动后,等待 Keystone 实际可以签发令牌(不输出令牌内容)。
|
||||
log_info "等待 Keystone 管理员认证就绪..."
|
||||
for attempt in {1..12}; do
|
||||
if openstack --os-auth-type password --os-api-timeout 10 token issue > /dev/null; then
|
||||
break
|
||||
fi
|
||||
if [ "$attempt" -eq 12 ]; then
|
||||
log_err "Keystone 认证失败,请检查 httpd、Keystone 配置和管理员密码。"
|
||||
exit 1
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# 查询失败直接停止;只有成功查询且没有匹配项时才创建。
|
||||
# 对已有重复资源报错,避免任意选择或继续制造重复项。
|
||||
unique_resource_id() {
|
||||
python -c '
|
||||
import json, sys
|
||||
field, expected = sys.argv[1:3]
|
||||
rows = json.load(sys.stdin)
|
||||
ids = [str(row["ID"]) for row in rows
|
||||
if field == "*" or row.get(field) == expected]
|
||||
if len(ids) > 1:
|
||||
sys.stderr.write("Found duplicate resources: %s\n" % ", ".join(ids))
|
||||
sys.exit(1)
|
||||
if ids:
|
||||
print(ids[0])
|
||||
' "$1" "$2"
|
||||
}
|
||||
|
||||
log_info "创建或复用 service 项目、glance 用户及服务..."
|
||||
SERVICE_PROJECT_ID=$(openstack project list --domain default -f json | unique_resource_id Name service)
|
||||
if [ -z "$SERVICE_PROJECT_ID" ]; then
|
||||
SERVICE_PROJECT_ID=$(openstack project create --domain default service -f value -c id)
|
||||
fi
|
||||
openstack project set --enable "$SERVICE_PROJECT_ID"
|
||||
|
||||
GLANCE_USER_ID=$(openstack user list --domain default -f json | unique_resource_id Name glance)
|
||||
if [ -z "$GLANCE_USER_ID" ]; then
|
||||
GLANCE_USER_ID=$(openstack user create --domain default --password glance glance -f value -c id)
|
||||
else
|
||||
openstack user set --password glance --enable "$GLANCE_USER_ID"
|
||||
fi
|
||||
# Keystone 的角色授权可重复提交,不会生成重复授权。
|
||||
openstack role add --project "$SERVICE_PROJECT_ID" --user "$GLANCE_USER_ID" admin
|
||||
|
||||
GLANCE_SERVICE_ID=$(openstack service list -f json | unique_resource_id Name glance)
|
||||
if [ -z "$GLANCE_SERVICE_ID" ]; then
|
||||
GLANCE_SERVICE_ID=$(openstack service create --name glance --description "OpenStack Image" image -f value -c id)
|
||||
else
|
||||
openstack service set --type image --description "OpenStack Image" --enable "$GLANCE_SERVICE_ID"
|
||||
fi
|
||||
|
||||
log_info "创建或更新 Glance API 端点..."
|
||||
for interface in public internal admin; do
|
||||
ENDPOINT_ID=$(openstack endpoint list --service "$GLANCE_SERVICE_ID" \
|
||||
--region RegionOne --interface "$interface" -f json | unique_resource_id '*' '')
|
||||
if [ -z "$ENDPOINT_ID" ]; then
|
||||
openstack endpoint create --region RegionOne \
|
||||
"$GLANCE_SERVICE_ID" "$interface" http://controller:9292
|
||||
else
|
||||
openstack endpoint set --url http://controller:9292 --enable "$ENDPOINT_ID"
|
||||
fi
|
||||
done
|
||||
|
||||
log_info "配置 Glance API 和 Registry..."
|
||||
backup_config /etc/glance/glance-api.conf
|
||||
backup_config /etc/glance/glance-registry.conf
|
||||
# 两个服务共用数据库和认证设置,一次生成后复用。
|
||||
cat > /etc/glance/glance-registry.conf <<'EOF'
|
||||
[database]
|
||||
connection = mysql+pymysql://glance:glance@controller/glance
|
||||
|
||||
[keystone_authtoken]
|
||||
www_authenticate_uri = http://controller:5000
|
||||
auth_url = http://controller:5000
|
||||
memcached_servers = controller:11211
|
||||
auth_type = password
|
||||
project_domain_name = Default
|
||||
user_domain_name = Default
|
||||
project_name = service
|
||||
username = glance
|
||||
password = glance
|
||||
|
||||
[paste_deploy]
|
||||
flavor = keystone
|
||||
EOF
|
||||
cat /etc/glance/glance-registry.conf > /etc/glance/glance-api.conf
|
||||
cat >> /etc/glance/glance-api.conf <<'EOF'
|
||||
|
||||
[glance_store]
|
||||
stores = file,http
|
||||
default_store = file
|
||||
filesystem_store_datadir = /var/lib/glance/images/
|
||||
EOF
|
||||
chown root:glance /etc/glance/glance-api.conf /etc/glance/glance-registry.conf
|
||||
chmod 640 /etc/glance/glance-api.conf /etc/glance/glance-registry.conf
|
||||
# 保留已有镜像文件,只确保目录存在且 Glance 可写。
|
||||
install -d -o glance -g glance -m 0750 /var/lib/glance/images
|
||||
|
||||
log_info "同步 Glance 数据库并验证表结构..."
|
||||
su -s /bin/sh -c "glance-manage db_sync" glance
|
||||
# 以 glance 用户连接数据库,验证同步后的核心表,无需再次创建 root 凭据文件。
|
||||
python - <<'PYDB'
|
||||
import pymysql
|
||||
conn = pymysql.connect(host='controller', user='glance', password='glance', database='glance')
|
||||
try:
|
||||
with conn.cursor() as cursor:
|
||||
cursor.execute('SHOW TABLES')
|
||||
tables = [row[0] for row in cursor.fetchall()]
|
||||
if 'images' not in tables:
|
||||
raise RuntimeError('Glance images table is missing')
|
||||
print('Glance tables: ' + ', '.join(tables))
|
||||
finally:
|
||||
conn.close()
|
||||
PYDB
|
||||
|
||||
log_info "启动 Glance 服务并设置开机自启..."
|
||||
systemctl daemon-reload
|
||||
systemctl enable openstack-glance-api openstack-glance-registry
|
||||
systemctl restart openstack-glance-api openstack-glance-registry
|
||||
|
||||
log_info "验证 Glance API 和管理员访问..."
|
||||
for attempt in {1..12}; do
|
||||
if openstack --os-api-timeout 10 --os-image-api-version 2 image list; then
|
||||
break
|
||||
fi
|
||||
if [ "$attempt" -eq 12 ]; then
|
||||
log_err "Glance API 验证失败,请检查 /var/log/glance/ 中的日志。"
|
||||
exit 1
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
log_info "检查各项服务状态..."
|
||||
for service in libvirtd chronyd mariadb rabbitmq-server memcached etcd httpd openstack-glance-api openstack-glance-registry; do
|
||||
if systemctl is-active --quiet "$service"; then
|
||||
log_info "$service 状态正常。"
|
||||
else
|
||||
log_err "$service 未正常运行,请使用 journalctl -u $service 查看日志。"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
log_info "=========================================="
|
||||
log_info "OpenStack 控制节点、Keystone 和 Glance 初始化完成!"
|
||||
log_info "控制节点:$CONTROLLER_HOSTNAME ($CONTROLLER_IP)"
|
||||
log_info "MariaDB root 密码:123456,已允许远程登录。"
|
||||
log_info "RabbitMQ 用户/密码:openstack / openstack"
|
||||
log_info "Keystone 管理员/密码:admin / admin,区域:RegionOne"
|
||||
log_info "Keystone API:http://controller:5000/v3"
|
||||
log_info "Glance API:http://controller:9292"
|
||||
log_info "后续在当前终端使用 OpenStack 命令前,请执行:source $ADMIN_OPENRC"
|
||||
log_info "=========================================="
|
||||
Executable
+707
@@ -0,0 +1,707 @@
|
||||
#!/bin/bash
|
||||
|
||||
# CentOS 7.9 / OpenStack Rocky 控制节点完整初始化脚本。
|
||||
# 包含 env3-3 的基础环境、Keystone、Glance,并增加 Nova / Placement。
|
||||
# 在控制节点以 root 执行;运行前请核对 CONTROLLER_IP 和本地仓库地址。
|
||||
set -euo pipefail
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_err() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 检查 root 权限
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
log_err "请使用 root 权限执行此脚本(如: sudo bash $0)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "=========================================="
|
||||
log_info "开始初始化实验环境..."
|
||||
log_info "=========================================="
|
||||
|
||||
# 直接连接终端,保留 yum 原生下载进度条;不要通过 tee 管道转存输出。
|
||||
run_yum() {
|
||||
local status
|
||||
log_info "执行 yum $*(下载速度取决于网络和镜像源)..."
|
||||
# 仅启用本地仓库,避免系统中其他已启用的公网仓库参与下载。
|
||||
if yum --disablerepo='*' \
|
||||
--enablerepo=local-base,local-updates,local-extras,local-qemu-ev,local-openstack-rocky \
|
||||
"$@"; then
|
||||
log_info "yum 操作完成。"
|
||||
else
|
||||
status=$?
|
||||
log_err "yum 操作失败(退出码 $status),请查看上方错误信息。"
|
||||
return "$status"
|
||||
fi
|
||||
}
|
||||
|
||||
REPO_DIR="/etc/yum.repos.d"
|
||||
BACKUP_DIR="/etc/yum.repos.d.bak"
|
||||
|
||||
# 首次运行时备份原有软件源
|
||||
if [ ! -d "$BACKUP_DIR" ]; then
|
||||
log_info "备份原有 yum 软件源..."
|
||||
cp -a "$REPO_DIR" "$BACKUP_DIR"
|
||||
fi
|
||||
|
||||
# 配置 CentOS 7.9 基础源
|
||||
log_info "配置 CentOS 7.9 本地软件源(192.168.192.205:3080)..."
|
||||
|
||||
cat > "$REPO_DIR/CentOS-Base.repo" <<'EOF'
|
||||
[local-base]
|
||||
name=Local - Base
|
||||
baseurl=http://192.168.192.205:3080/vault-base/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
|
||||
[local-updates]
|
||||
name=Local - Updates
|
||||
baseurl=http://192.168.192.205:3080/vault-updates/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
|
||||
[local-extras]
|
||||
name=Local - Extras
|
||||
baseurl=http://192.168.192.205:3080/vault-extras/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
EOF
|
||||
|
||||
# 配置 QEMU EV 源
|
||||
cat > "$REPO_DIR/CentOS-QEMU-EV.repo" <<'EOF'
|
||||
[local-qemu-ev]
|
||||
name=Local - QEMU EV
|
||||
baseurl=http://192.168.192.205:3080/vault-centos-qemu-ev/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
EOF
|
||||
|
||||
# 配置 OpenStack Rocky 源
|
||||
cat > "$REPO_DIR/CentOS-OpenStack-rocky.repo" <<'EOF'
|
||||
[local-openstack-rocky]
|
||||
name=Local - OpenStack Rocky
|
||||
baseurl=http://192.168.192.205:3080/vault-centos-openstack-rocky/
|
||||
gpgcheck=0
|
||||
enabled=1
|
||||
exclude=sip,PyQt4
|
||||
EOF
|
||||
|
||||
# 删除不再需要的 Ceph 源,避免 yum 读取其元数据
|
||||
rm -f "$REPO_DIR/CentOS-Ceph-Luminous.repo"
|
||||
|
||||
|
||||
# 清除旧仓库元数据,保留已下载的软件包缓存
|
||||
log_info "刷新软件源元数据..."
|
||||
run_yum clean metadata
|
||||
|
||||
CONTROLLER_IP="192.168.30.129"
|
||||
CONTROLLER_HOSTNAME="controller"
|
||||
|
||||
# 首次修改时备份配置,重复运行不覆盖原始备份。
|
||||
backup_config() {
|
||||
if [ -f "$1" ] && [ ! -e "$1.env3-4.bak" ]; then
|
||||
cp -a "$1" "$1.env3-4.bak"
|
||||
fi
|
||||
}
|
||||
|
||||
log_info "关闭防火墙和 SELinux..."
|
||||
if systemctl list-unit-files firewalld.service --no-legend | grep -q '^firewalld.service'; then
|
||||
systemctl stop firewalld
|
||||
systemctl disable firewalld
|
||||
fi
|
||||
if command -v getenforce > /dev/null 2>&1; then
|
||||
if [ "$(getenforce)" = "Enforcing" ]; then
|
||||
setenforce 0
|
||||
fi
|
||||
fi
|
||||
if [ -f /etc/selinux/config ]; then
|
||||
backup_config /etc/selinux/config
|
||||
sed -i '/^[[:space:]]*SELINUX=/d' /etc/selinux/config
|
||||
echo 'SELINUX=disabled' >> /etc/selinux/config
|
||||
fi
|
||||
|
||||
log_info "配置主机名和 hosts 解析..."
|
||||
hostnamectl set-hostname "$CONTROLLER_HOSTNAME"
|
||||
backup_config /etc/hosts
|
||||
# 移除旧 controller 映射,保留同一行的其他别名及注释。
|
||||
HOSTS_TMP=$(mktemp)
|
||||
awk -v host="$CONTROLLER_HOSTNAME" '
|
||||
/^[[:space:]]*#/ || NF == 0 { print; next }
|
||||
{
|
||||
comment = ""
|
||||
pos = index($0, "#")
|
||||
if (pos) {
|
||||
comment = substr($0, pos)
|
||||
$0 = substr($0, 1, pos - 1)
|
||||
}
|
||||
line = $1
|
||||
aliases = 0
|
||||
for (i = 2; i <= NF; i++) {
|
||||
if ($i != host) {
|
||||
line = line " " $i
|
||||
aliases++
|
||||
}
|
||||
}
|
||||
if (aliases) print line (comment == "" ? "" : " " comment)
|
||||
else if (comment != "") print comment
|
||||
}
|
||||
' /etc/hosts > "$HOSTS_TMP"
|
||||
cat "$HOSTS_TMP" > /etc/hosts
|
||||
rm -f "$HOSTS_TMP"
|
||||
printf '%s %s\n' "$CONTROLLER_IP" "$CONTROLLER_HOSTNAME" >> /etc/hosts
|
||||
|
||||
log_info "升级系统软件包,合并安装基础服务、Keystone、Glance、Nova 和 Placement..."
|
||||
run_yum -y upgrade
|
||||
run_yum install -y chrony python2-openstackclient \
|
||||
mariadb mariadb-server python2-PyMySQL \
|
||||
rabbitmq-server memcached python-memcached etcd \
|
||||
qemu-kvm libvirt virt-install bridge-utils qemu-img \
|
||||
openstack-keystone httpd mod_wsgi openstack-glance \
|
||||
openstack-nova-api openstack-nova-conductor openstack-nova-console \
|
||||
openstack-nova-novncproxy openstack-nova-scheduler openstack-nova-placement-api
|
||||
|
||||
# 启动 libvirtd
|
||||
log_info "启动 libvirtd 服务..."
|
||||
|
||||
systemctl enable --now libvirtd > /dev/null
|
||||
|
||||
# 验证服务状态
|
||||
if systemctl is-active --quiet libvirtd; then
|
||||
log_info "libvirtd 状态正常。"
|
||||
else
|
||||
log_err "libvirtd 启动失败!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
log_info "配置 chrony 时间服务器..."
|
||||
backup_config /etc/chrony.conf
|
||||
if ! grep -Eq '^[[:space:]]*allow[[:space:]]+all([[:space:]]*(#.*)?)?$' /etc/chrony.conf; then
|
||||
printf '\n# 允许所有网段的机器访问同步时间\nallow all\n' >> /etc/chrony.conf
|
||||
fi
|
||||
systemctl enable chronyd
|
||||
systemctl restart chronyd
|
||||
|
||||
log_info "配置 MariaDB..."
|
||||
mkdir -p /etc/my.cnf.d
|
||||
backup_config /etc/my.cnf.d/openstack.cnf
|
||||
cat > /etc/my.cnf.d/openstack.cnf <<'EOF'
|
||||
[mysqld]
|
||||
bind-address = 0.0.0.0
|
||||
# 默认使用 InnoDB,每个表的数据独立保存成文件。
|
||||
default-storage-engine = innodb
|
||||
innodb_file_per_table = on
|
||||
# 调高最大连接数,并使用 UTF-8 字符集。
|
||||
max_connections = 4096
|
||||
collation-server = utf8_general_ci
|
||||
character-set-server = utf8
|
||||
EOF
|
||||
systemctl enable mariadb
|
||||
systemctl restart mariadb
|
||||
|
||||
log_info "初始化 MariaDB 安全配置及 root 远程登录权限..."
|
||||
# CentOS 7 的 MariaDB 5.5:自动执行 mysql_secure_installation 的等效操作。
|
||||
# 使用权限为 600 的临时客户端配置,避免把密码放进进程参数。
|
||||
MYSQL_AUTH_FILE=$(mktemp)
|
||||
trap 'rm -f "$MYSQL_AUTH_FILE"' EXIT
|
||||
chmod 600 "$MYSQL_AUTH_FILE"
|
||||
cat > "$MYSQL_AUTH_FILE" <<'EOF'
|
||||
[client]
|
||||
user=root
|
||||
password=123456
|
||||
protocol=socket
|
||||
EOF
|
||||
MYSQL_CMD=(mysql --defaults-extra-file="$MYSQL_AUTH_FILE")
|
||||
if ! "${MYSQL_CMD[@]}" -e 'SELECT 1' > /dev/null 2>&1; then
|
||||
MYSQL_CMD=(mysql --no-defaults --user=root --protocol=socket)
|
||||
if ! "${MYSQL_CMD[@]}" -e 'SELECT 1' > /dev/null 2>&1; then
|
||||
log_err "无法使用空密码或预设密码连接 MariaDB root,请检查已有数据库凭据。"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
"${MYSQL_CMD[@]}" <<'SQL'
|
||||
DELETE FROM mysql.user WHERE User = '';
|
||||
DELETE FROM mysql.user
|
||||
WHERE User = 'root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');
|
||||
UPDATE mysql.user SET Password = PASSWORD('123456') WHERE User = 'root';
|
||||
DROP DATABASE IF EXISTS test;
|
||||
DELETE FROM mysql.db WHERE Db = 'test' OR Db = 'test\_%';
|
||||
FLUSH PRIVILEGES;
|
||||
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456' WITH GRANT OPTION;
|
||||
FLUSH PRIVILEGES;
|
||||
SQL
|
||||
log_info "批量创建 Keystone、Glance、Nova 和 Placement 数据库并授权..."
|
||||
mysql --defaults-extra-file="$MYSQL_AUTH_FILE" <<'SQL'
|
||||
CREATE DATABASE IF NOT EXISTS keystone;
|
||||
GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'localhost' IDENTIFIED BY 'keystone';
|
||||
GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'%' IDENTIFIED BY 'keystone';
|
||||
CREATE DATABASE IF NOT EXISTS glance;
|
||||
GRANT ALL PRIVILEGES ON glance.* TO 'glance'@'localhost' IDENTIFIED BY 'glance';
|
||||
GRANT ALL PRIVILEGES ON glance.* TO 'glance'@'%' IDENTIFIED BY 'glance';
|
||||
CREATE DATABASE IF NOT EXISTS nova_api;
|
||||
CREATE DATABASE IF NOT EXISTS nova;
|
||||
CREATE DATABASE IF NOT EXISTS nova_cell0;
|
||||
CREATE DATABASE IF NOT EXISTS placement;
|
||||
GRANT ALL PRIVILEGES ON nova_api.* TO 'nova'@'localhost' IDENTIFIED BY 'nova';
|
||||
GRANT ALL PRIVILEGES ON nova_api.* TO 'nova'@'%' IDENTIFIED BY 'nova';
|
||||
GRANT ALL PRIVILEGES ON nova.* TO 'nova'@'localhost' IDENTIFIED BY 'nova';
|
||||
GRANT ALL PRIVILEGES ON nova.* TO 'nova'@'%' IDENTIFIED BY 'nova';
|
||||
GRANT ALL PRIVILEGES ON nova_cell0.* TO 'nova'@'localhost' IDENTIFIED BY 'nova';
|
||||
GRANT ALL PRIVILEGES ON nova_cell0.* TO 'nova'@'%' IDENTIFIED BY 'nova';
|
||||
GRANT ALL PRIVILEGES ON placement.* TO 'placement'@'localhost' IDENTIFIED BY 'placement';
|
||||
GRANT ALL PRIVILEGES ON placement.* TO 'placement'@'%' IDENTIFIED BY 'placement';
|
||||
FLUSH PRIVILEGES;
|
||||
SQL
|
||||
rm -f "$MYSQL_AUTH_FILE"
|
||||
trap - EXIT
|
||||
|
||||
log_info "配置 RabbitMQ 用户及权限..."
|
||||
systemctl enable rabbitmq-server
|
||||
systemctl start rabbitmq-server
|
||||
RABBITMQ_USERS=$(rabbitmqctl list_users -q)
|
||||
if awk '$1 == "openstack" { found = 1 } END { exit !found }' <<< "$RABBITMQ_USERS"; then
|
||||
rabbitmqctl change_password openstack openstack
|
||||
else
|
||||
rabbitmqctl add_user openstack openstack
|
||||
fi
|
||||
rabbitmqctl set_permissions -p / openstack '.*' '.*' '.*'
|
||||
|
||||
log_info "配置 Memcached..."
|
||||
backup_config /etc/sysconfig/memcached
|
||||
sed -i '/^[[:space:]]*OPTIONS=/d' /etc/sysconfig/memcached
|
||||
echo 'OPTIONS="-l 127.0.0.1,::1,controller"' >> /etc/sysconfig/memcached
|
||||
systemctl enable memcached
|
||||
systemctl restart memcached
|
||||
|
||||
log_info "配置 Etcd..."
|
||||
backup_config /etc/etcd/etcd.conf
|
||||
cat > /etc/etcd/etcd.conf <<'EOF'
|
||||
#[Member]
|
||||
ETCD_LISTEN_PEER_URLS="http://0.0.0.0:2380"
|
||||
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"
|
||||
ETCD_NAME="controller"
|
||||
|
||||
#[Clustering]
|
||||
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://controller:2380"
|
||||
ETCD_ADVERTISE_CLIENT_URLS="http://controller:2379"
|
||||
ETCD_INITIAL_CLUSTER="controller=http://controller:2380"
|
||||
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster-01"
|
||||
ETCD_INITIAL_CLUSTER_STATE="new"
|
||||
EOF
|
||||
systemctl enable etcd
|
||||
systemctl restart etcd
|
||||
|
||||
log_info "配置 Keystone 数据库连接及 Fernet 令牌..."
|
||||
backup_config /etc/keystone/keystone.conf
|
||||
cat > /etc/keystone/keystone.conf <<'EOF'
|
||||
[database]
|
||||
connection = mysql+pymysql://keystone:keystone@controller/keystone
|
||||
[token]
|
||||
provider = fernet
|
||||
EOF
|
||||
chown root:keystone /etc/keystone/keystone.conf
|
||||
chmod 640 /etc/keystone/keystone.conf
|
||||
|
||||
log_info "同步 Keystone 数据库表结构..."
|
||||
su -s /bin/sh -c "keystone-manage db_sync" keystone
|
||||
|
||||
# 使用 setup 命令初始化密钥库,不删除已有密钥。
|
||||
log_info "初始化 Fernet 和 credential 密钥库..."
|
||||
keystone-manage fernet_setup --keystone-user keystone --keystone-group keystone
|
||||
keystone-manage credential_setup --keystone-user keystone --keystone-group keystone
|
||||
|
||||
log_info "初始化 Keystone 管理员及服务端点..."
|
||||
keystone-manage bootstrap --bootstrap-password admin \
|
||||
--bootstrap-admin-url http://controller:5000/v3 \
|
||||
--bootstrap-public-url http://controller:5000/v3 \
|
||||
--bootstrap-internal-url http://controller:5000/v3 \
|
||||
--bootstrap-region-id RegionOne
|
||||
|
||||
log_info "配置 Apache HTTP 服务..."
|
||||
backup_config /etc/httpd/conf/httpd.conf
|
||||
# 删除已有生效的 ServerName,避免重复追加。
|
||||
sed -i '/^[[:space:]]*ServerName[[:space:]]/d' /etc/httpd/conf/httpd.conf
|
||||
printf '\nServerName controller\n' >> /etc/httpd/conf/httpd.conf
|
||||
|
||||
WSGI_SOURCE="/usr/share/keystone/wsgi-keystone.conf"
|
||||
WSGI_LINK="/etc/httpd/conf.d/wsgi-keystone.conf"
|
||||
if [ ! -f "$WSGI_SOURCE" ]; then
|
||||
log_err "缺少 Keystone WSGI 配置:$WSGI_SOURCE"
|
||||
exit 1
|
||||
fi
|
||||
# 备份已有普通配置文件;重复运行时更新软链接。
|
||||
if [ ! -L "$WSGI_LINK" ]; then
|
||||
backup_config "$WSGI_LINK"
|
||||
fi
|
||||
ln -sfnT "$WSGI_SOURCE" "$WSGI_LINK"
|
||||
|
||||
log_info "配置 Nova 和 Placement..."
|
||||
backup_config /etc/nova/nova.conf
|
||||
# 仅展开控制节点 IP;保留 $my_ip 供 Nova 配置系统引用。
|
||||
cat > /etc/nova/nova.conf <<EOF
|
||||
[DEFAULT]
|
||||
my_ip = $CONTROLLER_IP
|
||||
use_neutron = true
|
||||
firewall_driver = nova.virt.firewall.NoopFirewallDriver
|
||||
enabled_apis = osapi_compute,metadata
|
||||
transport_url = rabbit://openstack:openstack@controller
|
||||
|
||||
[api_database]
|
||||
connection = mysql+pymysql://nova:nova@controller/nova_api
|
||||
|
||||
[database]
|
||||
connection = mysql+pymysql://nova:nova@controller/nova
|
||||
|
||||
[placement_database]
|
||||
connection = mysql+pymysql://placement:placement@controller/placement
|
||||
|
||||
[api]
|
||||
auth_strategy = keystone
|
||||
|
||||
[keystone_authtoken]
|
||||
auth_url = http://controller:5000/v3
|
||||
memcached_servers = controller:11211
|
||||
auth_type = password
|
||||
project_domain_name = Default
|
||||
user_domain_name = Default
|
||||
project_name = service
|
||||
username = nova
|
||||
password = nova
|
||||
|
||||
[vnc]
|
||||
enabled = true
|
||||
server_listen = \$my_ip
|
||||
server_proxyclient_address = \$my_ip
|
||||
|
||||
[glance]
|
||||
api_servers = http://controller:9292
|
||||
|
||||
[oslo_concurrency]
|
||||
lock_path = /var/lib/nova/tmp
|
||||
|
||||
[placement]
|
||||
region_name = RegionOne
|
||||
project_domain_name = Default
|
||||
project_name = service
|
||||
auth_type = password
|
||||
user_domain_name = Default
|
||||
auth_url = http://controller:5000/v3
|
||||
username = placement
|
||||
password = placement
|
||||
EOF
|
||||
chown root:nova /etc/nova/nova.conf
|
||||
chmod 640 /etc/nova/nova.conf
|
||||
install -d -o nova -g nova -m 0750 /var/lib/nova/tmp
|
||||
|
||||
log_info "修复 Placement API 的 Apache 目录访问权限..."
|
||||
PLACEMENT_HTTPD_CONF=/etc/httpd/conf.d/00-nova-placement-api.conf
|
||||
if [ ! -f "$PLACEMENT_HTTPD_CONF" ]; then
|
||||
log_err "缺少 Placement WSGI 配置:$PLACEMENT_HTTPD_CONF,请检查软件包安装。"
|
||||
exit 1
|
||||
fi
|
||||
backup_config "$PLACEMENT_HTTPD_CONF"
|
||||
# 只替换本脚本管理的配置块,保留软件包提供的 VirtualHost / WSGI 配置。
|
||||
sed -i '/^# BEGIN env3-4 placement access$/,/^# END env3-4 placement access$/d' "$PLACEMENT_HTTPD_CONF"
|
||||
cat >> "$PLACEMENT_HTTPD_CONF" <<'EOF'
|
||||
# BEGIN env3-4 placement access
|
||||
<Directory /usr/bin>
|
||||
<IfVersion >= 2.4>
|
||||
Require all granted
|
||||
</IfVersion>
|
||||
<IfVersion < 2.4>
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
</IfVersion>
|
||||
</Directory>
|
||||
# END env3-4 placement access
|
||||
EOF
|
||||
|
||||
log_info "检查 Apache 配置,合并应用 Keystone 和 Placement 配置..."
|
||||
httpd -t
|
||||
systemctl enable httpd
|
||||
systemctl restart httpd
|
||||
|
||||
log_info "创建并加载管理员登录脚本..."
|
||||
ROOT_HOME=$(getent passwd root | cut -d: -f6)
|
||||
if [ -z "$ROOT_HOME" ] || [ ! -d "$ROOT_HOME" ]; then
|
||||
log_err "无法确定 root 用户的家目录。"
|
||||
exit 1
|
||||
fi
|
||||
ADMIN_OPENRC="$ROOT_HOME/admin-openstack.sh"
|
||||
backup_config "$ADMIN_OPENRC"
|
||||
cat > "$ADMIN_OPENRC" <<'EOF'
|
||||
export OS_USERNAME=admin
|
||||
export OS_PASSWORD=admin
|
||||
export OS_PROJECT_NAME=admin
|
||||
export OS_USER_DOMAIN_NAME=Default
|
||||
export OS_PROJECT_DOMAIN_NAME=Default
|
||||
export OS_AUTH_URL=http://controller:5000/v3
|
||||
export OS_IDENTITY_API_VERSION=3
|
||||
EOF
|
||||
chmod 600 "$ADMIN_OPENRC"
|
||||
chown root:root "$ADMIN_OPENRC"
|
||||
# 清除继承的其他 OS_* 认证设置,避免学生之前的环境变量干扰本次配置。
|
||||
for variable in ${!OS_@}; do
|
||||
unset "$variable"
|
||||
done
|
||||
source "$ADMIN_OPENRC"
|
||||
|
||||
# Apache 启动后,等待 Keystone 实际可以签发令牌(不输出令牌内容)。
|
||||
log_info "等待 Keystone 管理员认证就绪..."
|
||||
for attempt in {1..12}; do
|
||||
if openstack --os-auth-type password --os-api-timeout 10 token issue > /dev/null; then
|
||||
break
|
||||
fi
|
||||
if [ "$attempt" -eq 12 ]; then
|
||||
log_err "Keystone 认证失败,请检查 httpd、Keystone 配置和管理员密码。"
|
||||
exit 1
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# 查询失败直接停止;只有成功查询且没有匹配项时才创建。
|
||||
# 对已有重复资源报错,避免任意选择或继续制造重复项。
|
||||
unique_resource_id() {
|
||||
python -c '
|
||||
import json, sys
|
||||
field, expected = sys.argv[1:3]
|
||||
rows = json.load(sys.stdin)
|
||||
ids = [str(row["ID"]) for row in rows
|
||||
if field == "*" or row.get(field) == expected]
|
||||
if len(ids) > 1:
|
||||
sys.stderr.write("Found duplicate resources: %s\n" % ", ".join(ids))
|
||||
sys.exit(1)
|
||||
if ids:
|
||||
print(ids[0])
|
||||
' "$1" "$2"
|
||||
}
|
||||
|
||||
log_info "创建或复用 service 项目及 Glance、Nova、Placement 服务凭证..."
|
||||
SERVICE_PROJECT_ID=$(openstack project list --domain default -f json | unique_resource_id Name service)
|
||||
if [ -z "$SERVICE_PROJECT_ID" ]; then
|
||||
SERVICE_PROJECT_ID=$(openstack project create --domain default service -f value -c id)
|
||||
fi
|
||||
openstack project set --enable "$SERVICE_PROJECT_ID"
|
||||
|
||||
# 用户和服务清单各查询一次;三个不同名称无需重复获取完整清单。
|
||||
SERVICE_USERS=$(openstack user list --domain default -f json)
|
||||
SERVICE_CATALOG=$(openstack service list -f json)
|
||||
for service_name in glance nova placement; do
|
||||
case "$service_name" in
|
||||
glance)
|
||||
service_type=image
|
||||
service_description="OpenStack Image"
|
||||
service_url=http://controller:9292
|
||||
;;
|
||||
nova)
|
||||
service_type=compute
|
||||
service_description="OpenStack Compute"
|
||||
service_url=http://controller:8774/v2.1
|
||||
;;
|
||||
placement)
|
||||
service_type=placement
|
||||
service_description="Placement API"
|
||||
service_url=http://controller:8778
|
||||
;;
|
||||
esac
|
||||
USER_ID=$(unique_resource_id Name "$service_name" <<< "$SERVICE_USERS")
|
||||
if [ -z "$USER_ID" ]; then
|
||||
USER_ID=$(openstack user create --domain default --password "$service_name" \
|
||||
"$service_name" -f value -c id)
|
||||
else
|
||||
openstack user set --password "$service_name" --enable "$USER_ID"
|
||||
fi
|
||||
# Keystone 的角色授权可重复提交,不会生成重复授权。
|
||||
openstack role add --project "$SERVICE_PROJECT_ID" --user "$USER_ID" admin
|
||||
|
||||
SERVICE_ID=$(unique_resource_id Name "$service_name" <<< "$SERVICE_CATALOG")
|
||||
if [ -z "$SERVICE_ID" ]; then
|
||||
SERVICE_ID=$(openstack service create --name "$service_name" \
|
||||
--description "$service_description" "$service_type" -f value -c id)
|
||||
else
|
||||
openstack service set --type "$service_type" --description "$service_description" \
|
||||
--enable "$SERVICE_ID"
|
||||
fi
|
||||
|
||||
log_info "创建或更新 $service_name API 的三个端点..."
|
||||
# 每个服务只查询一次端点列表,按接口筛选,避免反复连接 Keystone。
|
||||
SERVICE_ENDPOINTS=$(openstack endpoint list --service "$SERVICE_ID" --region RegionOne -f json)
|
||||
for interface in public internal admin; do
|
||||
ENDPOINT_ID=$(unique_resource_id Interface "$interface" <<< "$SERVICE_ENDPOINTS")
|
||||
if [ -z "$ENDPOINT_ID" ]; then
|
||||
openstack endpoint create --region RegionOne "$SERVICE_ID" "$interface" "$service_url"
|
||||
else
|
||||
openstack endpoint set --url "$service_url" --enable "$ENDPOINT_ID"
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
log_info "配置 Glance API 和 Registry..."
|
||||
backup_config /etc/glance/glance-api.conf
|
||||
backup_config /etc/glance/glance-registry.conf
|
||||
# 两个服务共用数据库和认证设置,一次生成后复用。
|
||||
cat > /etc/glance/glance-registry.conf <<'EOF'
|
||||
[database]
|
||||
connection = mysql+pymysql://glance:glance@controller/glance
|
||||
|
||||
[keystone_authtoken]
|
||||
www_authenticate_uri = http://controller:5000
|
||||
auth_url = http://controller:5000
|
||||
memcached_servers = controller:11211
|
||||
auth_type = password
|
||||
project_domain_name = Default
|
||||
user_domain_name = Default
|
||||
project_name = service
|
||||
username = glance
|
||||
password = glance
|
||||
|
||||
[paste_deploy]
|
||||
flavor = keystone
|
||||
EOF
|
||||
cat /etc/glance/glance-registry.conf > /etc/glance/glance-api.conf
|
||||
cat >> /etc/glance/glance-api.conf <<'EOF'
|
||||
|
||||
[glance_store]
|
||||
stores = file,http
|
||||
default_store = file
|
||||
filesystem_store_datadir = /var/lib/glance/images/
|
||||
EOF
|
||||
chown root:glance /etc/glance/glance-api.conf /etc/glance/glance-registry.conf
|
||||
chmod 640 /etc/glance/glance-api.conf /etc/glance/glance-registry.conf
|
||||
# 保留已有镜像文件,只确保目录存在且 Glance 可写。
|
||||
install -d -o glance -g glance -m 0750 /var/lib/glance/images
|
||||
|
||||
log_info "同步 Glance 数据库并验证表结构..."
|
||||
su -s /bin/sh -c "glance-manage db_sync" glance
|
||||
# 以 glance 用户连接数据库,验证同步后的核心表,无需再次创建 root 凭据文件。
|
||||
python - <<'PYDB'
|
||||
import pymysql
|
||||
conn = pymysql.connect(host='controller', user='glance', password='glance', database='glance')
|
||||
try:
|
||||
with conn.cursor() as cursor:
|
||||
cursor.execute('SHOW TABLES')
|
||||
tables = [row[0] for row in cursor.fetchall()]
|
||||
if 'images' not in tables:
|
||||
raise RuntimeError('Glance images table is missing')
|
||||
print('Glance tables: ' + ', '.join(tables))
|
||||
finally:
|
||||
conn.close()
|
||||
PYDB
|
||||
|
||||
log_info "同步 Nova API / Placement 数据库并注册 Cells v2..."
|
||||
# Rocky 的 api_db sync 同时同步 [placement_database] 指定的数据库。
|
||||
su -s /bin/sh -c "nova-manage api_db sync" nova
|
||||
su -s /bin/sh -c "nova-manage cell_v2 map_cell0" nova
|
||||
CELL_LIST=$(su -s /bin/sh -c "nova-manage cell_v2 list_cells" nova)
|
||||
CELL1_COUNT=$(awk -F '|' '$2 ~ /^[[:space:]]*cell1[[:space:]]*$/ { count++ } END { print count+0 }' <<< "$CELL_LIST")
|
||||
case "$CELL1_COUNT" in
|
||||
0) su -s /bin/sh -c "nova-manage cell_v2 create_cell --name=cell1 --verbose" nova ;;
|
||||
1) log_info "cell1 已存在,复用已有映射。" ;;
|
||||
*) log_err "发现多个 cell1,请先检查 cell 映射,避免重复注册。"; exit 1 ;;
|
||||
esac
|
||||
su -s /bin/sh -c "nova-manage db sync" nova
|
||||
su -s /bin/sh -c "nova-manage cell_v2 list_cells" nova
|
||||
|
||||
log_info "验证 Nova / Placement 四个数据库的表结构和 cell 映射..."
|
||||
python - <<'PYDB'
|
||||
import pymysql
|
||||
|
||||
databases = [
|
||||
('nova_api', 'nova', 'cell_mappings'),
|
||||
('nova', 'nova', 'instances'),
|
||||
('nova_cell0', 'nova', 'instances'),
|
||||
('placement', 'placement', 'resource_providers'),
|
||||
]
|
||||
for database, user, required_table in databases:
|
||||
conn = pymysql.connect(host='controller', user=user, password=user, database=database)
|
||||
try:
|
||||
with conn.cursor() as cursor:
|
||||
cursor.execute('SHOW TABLES')
|
||||
tables = [row[0] for row in cursor.fetchall()]
|
||||
if required_table not in tables:
|
||||
raise RuntimeError('%s: missing table %s' % (database, required_table))
|
||||
print('%s tables: %s' % (database, ', '.join(tables)))
|
||||
if database == 'nova_api':
|
||||
cursor.execute('SELECT name, uuid, database_connection, transport_url, disabled '
|
||||
'FROM cell_mappings')
|
||||
cells = cursor.fetchall()
|
||||
expected = {
|
||||
'cell0': ('mysql+pymysql://nova:nova@controller/nova_cell0', 'none:///'),
|
||||
'cell1': ('mysql+pymysql://nova:nova@controller/nova',
|
||||
'rabbit://openstack:openstack@controller'),
|
||||
}
|
||||
for name, (db_url, transport) in expected.items():
|
||||
matches = [row for row in cells if row[0] == name]
|
||||
if len(matches) != 1:
|
||||
raise RuntimeError('Expected exactly one %s mapping' % name)
|
||||
row = matches[0]
|
||||
if row[2] != db_url or row[3] != transport or row[4]:
|
||||
raise RuntimeError('%s mapping differs from the lab configuration' % name)
|
||||
if name == 'cell0' and row[1] != '00000000-0000-0000-0000-000000000000':
|
||||
raise RuntimeError('Invalid cell0 UUID')
|
||||
print('%s mapping OK: %s' % (name, row[1]))
|
||||
finally:
|
||||
conn.close()
|
||||
PYDB
|
||||
|
||||
log_info "批量启动 Glance、Nova 服务并设置开机自启..."
|
||||
NOVA_SERVICES=(
|
||||
openstack-nova-api
|
||||
openstack-nova-consoleauth
|
||||
openstack-nova-scheduler
|
||||
openstack-nova-conductor
|
||||
openstack-nova-novncproxy
|
||||
)
|
||||
systemctl daemon-reload
|
||||
systemctl enable openstack-glance-api openstack-glance-registry "${NOVA_SERVICES[@]}"
|
||||
# restart 同时支持首次启动和重跑后加载新配置。
|
||||
systemctl restart openstack-glance-api openstack-glance-registry "${NOVA_SERVICES[@]}"
|
||||
|
||||
log_info "验证 Glance API 和管理员访问..."
|
||||
for attempt in {1..12}; do
|
||||
if openstack --os-api-timeout 10 --os-image-api-version 2 image list; then
|
||||
break
|
||||
fi
|
||||
if [ "$attempt" -eq 12 ]; then
|
||||
log_err "Glance API 验证失败,请检查 /var/log/glance/ 中的日志。"
|
||||
exit 1
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
log_info "检查各项服务状态..."
|
||||
for service in libvirtd chronyd mariadb rabbitmq-server memcached etcd httpd \
|
||||
openstack-glance-api openstack-glance-registry "${NOVA_SERVICES[@]}"; do
|
||||
if systemctl is-active --quiet "$service"; then
|
||||
log_info "$service 状态正常。"
|
||||
else
|
||||
log_err "$service 未正常运行,请使用 journalctl -u $service 查看日志。"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
log_info "=========================================="
|
||||
log_info "OpenStack 控制节点、Keystone、Glance、Nova 和 Placement 初始化完成!"
|
||||
log_info "控制节点:$CONTROLLER_HOSTNAME ($CONTROLLER_IP)"
|
||||
log_info "MariaDB root 密码:123456,已允许远程登录。"
|
||||
log_info "RabbitMQ 用户/密码:openstack / openstack"
|
||||
log_info "Keystone 管理员/密码:admin / admin,区域:RegionOne"
|
||||
log_info "Keystone API:http://controller:5000/v3"
|
||||
log_info "Glance API:http://controller:9292"
|
||||
log_info "Nova API:http://controller:8774/v2.1,用户/密码:nova / nova"
|
||||
log_info "Placement API:http://controller:8778,用户/密码:placement / placement"
|
||||
log_info "后续在当前终端使用 OpenStack 命令前,请执行:source $ADMIN_OPENRC"
|
||||
log_info "=========================================="
|
||||
Reference in new issue
Block a user