feat: 代码标准化和文件重命名
## 新增功能 - 创建统一的公共函数库 (common/logging.sh, common/error_handler.sh) - 添加功能演示脚本 (common/demo_usage.sh) - 完善的使用文档 (common/README.md) ## 代码重构 - 重构所有脚本使用统一的公共库 - 为所有函数添加完整的文档注释 - 统一代码格式(4空格缩进、严格模式) - 标准化错误处理和日志输出 ## 文件重命名 - gcp/create_ai_project.sh → gcp/create_ai_projects.sh (单复数统一) - gcp/delete_all_project.sh → gcp/delete_all_projects.sh (单复数统一) - linux/install_ohmyzsh.sh → linux/install_oh_my_zsh.sh (专有名词规范) - linux/create_raid0_with_ext4.sh → linux/create_raid0_array.sh (简化命名) - common/example.sh → common/demo_usage.sh (更具描述性) ## 技术改进 - 使用 readonly 声明常量 - 启用 set -euo pipefail 严格模式 - 统一的 ANSI 颜色日志输出 - 完善的命令重试机制 - 栈追踪支持
This commit is contained in:
389
oci/create_instance.sh
Normal file
389
oci/create_instance.sh
Normal file
@@ -0,0 +1,389 @@
|
||||
#!/bin/bash
|
||||
# ============================================================================
|
||||
# 文件名: create_instance.sh
|
||||
# 描述: Oracle Cloud Infrastructure 虚拟机批量部署工具
|
||||
# 作者: Cloud Tools Project
|
||||
# 版本: 2.0.0
|
||||
# ============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# 获取脚本目录
|
||||
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
readonly PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
# 加载公共库
|
||||
source "${PROJECT_ROOT}/common/logging.sh"
|
||||
source "${PROJECT_ROOT}/common/error_handler.sh"
|
||||
|
||||
# 默认配置参数
|
||||
readonly DEFAULT_NUMBER=1
|
||||
readonly DEFAULT_SHAPE="VM.Standard.A1.Flex"
|
||||
readonly DEFAULT_SHAPE_CONFIG="1+6"
|
||||
readonly DEFAULT_IMAGE_NAME="Canonical-Ubuntu-20.04-aarch64-2025.05.20-0"
|
||||
readonly DEFAULT_BOOT_VOLUME_SIZE=0
|
||||
readonly DEFAULT_VPU=120
|
||||
readonly DEFAULT_DOMAIN=0
|
||||
|
||||
#
|
||||
# 检查必要的依赖工具是否安装
|
||||
#
|
||||
check_dependencies() {
|
||||
local deps=("oci" "jq" "base64" "mkfifo")
|
||||
local missing=()
|
||||
|
||||
for cmd in "${deps[@]}"; do
|
||||
if ! check_command "$cmd"; then
|
||||
missing+=("$cmd")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#missing[@]} -gt 0 ]]; then
|
||||
log_error "缺少必要依赖: ${missing[*]}"
|
||||
exit 1
|
||||
fi
|
||||
log_success "运行环境检查通过"
|
||||
}
|
||||
|
||||
#
|
||||
# 生成随机安全密码
|
||||
#
|
||||
# 返回:
|
||||
# 16位随机密码
|
||||
#
|
||||
generate_password() {
|
||||
LC_ALL=C tr -dc 'A-Za-z0-9!#%&()*+,-.:;<=>?@[]^_~' </dev/urandom | head -c 16
|
||||
}
|
||||
|
||||
#
|
||||
# 生成实例初始化的 cloud-init 用户数据
|
||||
#
|
||||
# 参数:
|
||||
# $1 - root 密码
|
||||
#
|
||||
# 返回:
|
||||
# Base64 编码的 user_data
|
||||
#
|
||||
generate_user_data() {
|
||||
local password="$1"
|
||||
cat <<EOF | base64 -w 0
|
||||
#!/bin/bash
|
||||
echo root:"$password" | chpasswd
|
||||
sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/g' /etc/ssh/sshd_config
|
||||
sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/g' /etc/ssh/sshd_config
|
||||
rm -f /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*
|
||||
systemctl restart sshd
|
||||
EOF
|
||||
}
|
||||
|
||||
#
|
||||
# 生成实例规格配置
|
||||
#
|
||||
# 参数:
|
||||
# $1 - 规格配置字符串 (格式: CPU+内存 或 CPU+内存+基线)
|
||||
#
|
||||
# 返回:
|
||||
# JSON 格式的规格配置
|
||||
#
|
||||
generate_shape_config() {
|
||||
local config_str="$1"
|
||||
IFS='+' read -ra parts <<< "$config_str"
|
||||
|
||||
case "${#parts[@]}" in
|
||||
2)
|
||||
echo "{\"ocpus\":${parts[0]},\"memoryInGBs\":${parts[1]}}"
|
||||
;;
|
||||
3)
|
||||
case "${parts[2]}" in
|
||||
0.125)
|
||||
echo "{\"ocpus\":${parts[0]},\"memoryInGBs\":${parts[1]},\"baselineOcpuUtilization\":\"BASELINE_1_8\"}"
|
||||
;;
|
||||
0.5)
|
||||
echo "{\"ocpus\":${parts[0]},\"memoryInGBs\":${parts[1]},\"baselineOcpuUtilization\":\"BASELINE_1_2\"}"
|
||||
;;
|
||||
*)
|
||||
log_error "无效的 baseline 配置: ${parts[2]}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
log_error "无效的 shape 配置格式: $config_str"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
#
|
||||
# 配置 OCI 网络资源
|
||||
#
|
||||
# 参数:
|
||||
# $1 - Compartment OCID
|
||||
# $2 - FIFO 文件路径(用于返回子网ID)
|
||||
#
|
||||
configure_network() {
|
||||
local ocid="$1"
|
||||
local fifo="$2"
|
||||
local vcn_dns="vcn$(shuf -i 100000-999999 -n 1)"
|
||||
local subnet_dns="subnet$(shuf -i 100000-999999 -n 1)"
|
||||
|
||||
# 创建 VCN
|
||||
log_info "正在创建虚拟网络 (DNS: $vcn_dns)"
|
||||
local vcn_id
|
||||
vcn_id=$(oci network vcn create \
|
||||
--compartment-id "$ocid" \
|
||||
--cidr-blocks '["10.0.0.0/16"]' \
|
||||
--is-ipv6-enabled true \
|
||||
--dns-label "$vcn_dns" \
|
||||
--query 'data.id' \
|
||||
--raw-output 2>/dev/null)
|
||||
|
||||
check_not_empty "$vcn_id" "vcn_id"
|
||||
log_success "VCN 创建成功 (ID: $vcn_id)"
|
||||
|
||||
# 生成 IPv6 CIDR
|
||||
log_info "正在生成 IPv6 CIDR..."
|
||||
local ipv6_cidr
|
||||
ipv6_cidr=$(oci network vcn get --vcn-id "$vcn_id" --query 'data."ipv6-cidr-blocks"[0]' --raw-output)
|
||||
ipv6_cidr="${ipv6_cidr%00::*}$((RANDOM%90+10))::/64"
|
||||
log_success "IPv6 CIDR 生成成功: $ipv6_cidr"
|
||||
|
||||
# 创建子网
|
||||
log_info "正在创建子网 (DNS: $subnet_dns)"
|
||||
local subnet_id
|
||||
subnet_id=$(OCI_CLI_SUPPRESS_JSON_OUTPUT=true oci network subnet create \
|
||||
--compartment-id "$ocid" \
|
||||
--vcn-id "$vcn_id" \
|
||||
--cidr-block "10.0.0.0/16" \
|
||||
--ipv6-cidr-block "$ipv6_cidr" \
|
||||
--dns-label "$subnet_dns" \
|
||||
--query 'data.id' \
|
||||
--raw-output 2>/dev/null)
|
||||
|
||||
check_not_empty "$subnet_id" "subnet_id"
|
||||
log_success "子网创建成功 (ID: $subnet_id)"
|
||||
|
||||
# 配置网关
|
||||
log_info "正在配置网络网关..."
|
||||
local gateway_id
|
||||
gateway_id=$(oci network internet-gateway create \
|
||||
--compartment-id "$ocid" \
|
||||
--vcn-id "$vcn_id" \
|
||||
--is-enabled true \
|
||||
--query 'data.id' \
|
||||
--raw-output 2>/dev/null)
|
||||
|
||||
check_return $? "网关创建失败"
|
||||
log_success "网关创建成功 (ID: $gateway_id)"
|
||||
|
||||
# 配置路由表
|
||||
log_info "正在配置路由表..."
|
||||
local rt_id
|
||||
rt_id=$(oci network route-table list \
|
||||
--compartment-id "$ocid" \
|
||||
--vcn-id "$vcn_id" \
|
||||
--query 'data[0].id' \
|
||||
--raw-output 2>/dev/null)
|
||||
|
||||
check_not_empty "$rt_id" "rt_id"
|
||||
|
||||
oci network route-table update --rt-id "$rt_id" \
|
||||
--route-rules "[
|
||||
{\"cidrBlock\":\"0.0.0.0/0\",\"networkEntityId\":\"$gateway_id\"},
|
||||
{\"cidrBlock\":\"0:0:0:0:0:0:0:0/0\",\"networkEntityId\":\"$gateway_id\"}
|
||||
]" --force >/dev/null
|
||||
|
||||
check_return $? "路由配置失败"
|
||||
log_success "路由表配置成功"
|
||||
|
||||
# 配置安全组
|
||||
log_info "正在配置安全组规则..."
|
||||
local sg_id
|
||||
sg_id=$(oci network security-list list \
|
||||
--compartment-id "$ocid" \
|
||||
--vcn-id "$vcn_id" \
|
||||
--query 'data[0].id' \
|
||||
--raw-output 2>/dev/null)
|
||||
|
||||
oci network security-list update --security-list-id "$sg_id" \
|
||||
--ingress-security-rules '[]' --force >/dev/null
|
||||
check_return $? "安全组清空失败"
|
||||
|
||||
oci network security-list update --security-list-id "$sg_id" \
|
||||
--ingress-security-rules "[
|
||||
{\"protocol\":\"all\",\"source\":\"0.0.0.0/0\",\"sourceType\":\"CIDR_BLOCK\"},
|
||||
{\"protocol\":\"all\",\"source\":\"::/0\",\"sourceType\":\"CIDR_BLOCK\"}
|
||||
]" --force >/dev/null
|
||||
|
||||
check_return $? "安全组规则更新失败"
|
||||
log_success "安全组规则配置成功"
|
||||
|
||||
# 返回子网ID通过 FIFO
|
||||
echo "$subnet_id" > "$fifo"
|
||||
}
|
||||
|
||||
#
|
||||
# 显示帮助信息
|
||||
#
|
||||
show_help() {
|
||||
cat <<EOF
|
||||
用法: ${0##*/} [选项]
|
||||
|
||||
选项:
|
||||
-n, --number <数量> 创建实例数量 (默认: $DEFAULT_NUMBER)
|
||||
--ocid <OCID> 区域ID (自动获取)
|
||||
--shape <形状> 实例规格 (默认: $DEFAULT_SHAPE)
|
||||
--shape_config <配置> 资源配置格式: CPU+内存[+基线] (默认: $DEFAULT_SHAPE_CONFIG)
|
||||
--subnet_id <子网ID> 指定现有子网ID (自动创建)
|
||||
--image_name <镜像名> 系统镜像名称 (默认: $DEFAULT_IMAGE_NAME)
|
||||
--vpu <数值> 启动卷性能单位 (默认: $DEFAULT_VPU)
|
||||
--boot_volume_size <GB> 启动卷大小 (默认: 自动)
|
||||
--domain <索引> 可用域序号 (默认: $DEFAULT_DOMAIN)
|
||||
--password <密码> 指定 root 密码 (默认自动生成)
|
||||
-h, --help 显示本帮助信息
|
||||
|
||||
示例:
|
||||
$0 -n 3 --shape VM.Standard.E4.Flex --shape_config 2+16
|
||||
EOF
|
||||
}
|
||||
|
||||
#
|
||||
# 主函数 - 批量创建 OCI 实例
|
||||
#
|
||||
main() {
|
||||
log_info "============ OCI 实例批量部署工具 ============"
|
||||
|
||||
# 参数初始化
|
||||
local number=$DEFAULT_NUMBER
|
||||
local shape=$DEFAULT_SHAPE
|
||||
local shape_config=$DEFAULT_SHAPE_CONFIG
|
||||
local image_name=$DEFAULT_IMAGE_NAME
|
||||
local boot_size=$DEFAULT_BOOT_VOLUME_SIZE
|
||||
local vpu=$DEFAULT_VPU
|
||||
local domain=$DEFAULT_DOMAIN
|
||||
local ocid password subnet_id
|
||||
|
||||
# 参数解析
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-n|--number) number="$2"; shift 2 ;;
|
||||
--ocid) ocid="$2"; shift 2 ;;
|
||||
--shape) shape="$2"; shift 2 ;;
|
||||
--shape_config) shape_config="$2"; shift 2 ;;
|
||||
--subnet_id) subnet_id="$2"; shift 2 ;;
|
||||
--image_name) image_name="$2"; shift 2 ;;
|
||||
--vpu) vpu="$2"; shift 2 ;;
|
||||
--boot_volume_size) boot_size="$2"; shift 2 ;;
|
||||
--domain) domain="$2"; shift 2 ;;
|
||||
--password) password="$2"; shift 2 ;;
|
||||
-h|--help) show_help; exit 0 ;;
|
||||
*) log_error "无效参数: $1"; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 环境检查
|
||||
check_dependencies
|
||||
|
||||
# 自动生成密码
|
||||
if [[ -z "${password:-}" ]]; then
|
||||
password=$(generate_password)
|
||||
fi
|
||||
log_warning "使用密码: $password"
|
||||
|
||||
# OCID 自动获取
|
||||
if [[ -z "${ocid:-}" ]]; then
|
||||
log_info "正在获取 OCID..."
|
||||
ocid=$(oci iam availability-domain list --query 'data[0]."compartment-id"' --raw-output 2>/dev/null)
|
||||
check_not_empty "$ocid" "ocid"
|
||||
fi
|
||||
log_success "OCID 获取成功: $ocid"
|
||||
|
||||
# 网络配置
|
||||
if [[ -z "${subnet_id:-}" ]]; then
|
||||
log_info "正在配置网络..."
|
||||
local fifo
|
||||
fifo=$(mktemp -u)
|
||||
mkfifo "$fifo"
|
||||
trap 'rm -f "$fifo"' EXIT
|
||||
configure_network "$ocid" "$fifo" &
|
||||
read subnet_id < "$fifo"
|
||||
log_success "网络配置完成 (子网ID: $subnet_id)"
|
||||
else
|
||||
log_info "使用指定的子网ID: $subnet_id"
|
||||
fi
|
||||
|
||||
# 生成配置
|
||||
local shape_cfg
|
||||
shape_cfg=$(generate_shape_config "$shape_config")
|
||||
|
||||
local image_id
|
||||
image_id=$(oci compute image list \
|
||||
--compartment-id "$ocid" \
|
||||
--display-name "$image_name" \
|
||||
--query 'data[0].id' \
|
||||
--raw-output 2>/dev/null)
|
||||
check_return $? "镜像查询失败"
|
||||
|
||||
local source_cfg
|
||||
if [[ $boot_size -eq 0 ]] || [[ $boot_size -lt 50 ]]; then
|
||||
source_cfg="{\"sourceType\":\"image\",\"imageId\":\"$image_id\",\"bootVolumeVpusPerGB\":$vpu}"
|
||||
else
|
||||
source_cfg="{\"sourceType\":\"image\",\"imageId\":\"$image_id\",\"bootVolumeVpusPerGB\":$vpu,\"bootVolumeSizeInGBs\":$boot_size}"
|
||||
fi
|
||||
|
||||
local metadata="{\"user_data\":\"$(generate_user_data "$password")\"}"
|
||||
local tags="{\"RootPassword\":\"$password\"}"
|
||||
|
||||
# 获取可用域
|
||||
log_info "正在获取可用域列表..."
|
||||
local domains
|
||||
domains=$(oci iam availability-domain list \
|
||||
--compartment-id "$ocid" \
|
||||
--query 'data[*].name' \
|
||||
--raw-output 2>/dev/null)
|
||||
check_return $? "无法获取可用域"
|
||||
|
||||
domains=$(echo "$domains" | jq -c | tr -d '[]"' | tr ',' ' ')
|
||||
read -ra domains <<< "$domains"
|
||||
|
||||
if [[ ${#domains[@]} -eq 0 ]]; then
|
||||
log_error "未找到可用域"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local target_domain="${domains[$domain]}"
|
||||
log_success "选择可用域: $target_domain"
|
||||
|
||||
# 批量创建实例
|
||||
log_info "开始批量创建实例 (数量: $number)..."
|
||||
local instance_id
|
||||
|
||||
for ((i=1; i<=number; i++)); do
|
||||
log_info "正在创建第 $i/$number 个实例..."
|
||||
|
||||
instance_id=$(oci compute instance launch \
|
||||
--compartment-id "$ocid" \
|
||||
--display-name "instance-$(date +%Y%m%d-%H%M%S)" \
|
||||
--shape "$shape" \
|
||||
--shape-config "$shape_cfg" \
|
||||
--subnet-id "$subnet_id" \
|
||||
--assign-ipv6-ip true \
|
||||
--source-details "$source_cfg" \
|
||||
--availability-domain "$target_domain" \
|
||||
--metadata "$metadata" \
|
||||
--freeform-tags "$tags" \
|
||||
--query 'data."id"' \
|
||||
--raw-output)
|
||||
|
||||
check_return $? "实例创建失败"
|
||||
log_success "实例 $i/$number 创建成功 (ID: $instance_id)"
|
||||
|
||||
# 随机延迟避免 API 限制
|
||||
sleep $((RANDOM % 5 + 1))
|
||||
done
|
||||
|
||||
log_success "所有实例部署完成!"
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user