Merge pull request #115 from svc-design/codex/add-vultr-module-support
feat(pulumi): add vultr support and local tooling
This commit is contained in:
commit
576a1c963d
90
iac_modules/pulumi/README.md
Normal file
90
iac_modules/pulumi/README.md
Normal file
@ -0,0 +1,90 @@
|
||||
# Pulumi IaC 本地调试指南
|
||||
|
||||
该目录包含基于 Pulumi Python 的多云基础设施模板,支持阿里云、AWS 以及 Vultr。本文档介绍本地调试所需的前置条件、环境变量以及辅助脚本的使用方式,便于与 GitHub Actions 流水线保持一致。
|
||||
|
||||
## 1. 前置条件
|
||||
|
||||
在开始之前,请确保本地环境已经安装以下工具:
|
||||
|
||||
- Python 3.9 及以上版本
|
||||
- Pulumi CLI(推荐使用最新版,可通过 `curl -fsSL https://get.pulumi.com | sh` 安装)
|
||||
- AWS CLI(用于访问 S3 backend 与 AWS 资源)
|
||||
- 已准备好仓库根目录的依赖:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
> 建议使用虚拟环境(如 `python -m venv .venv`)隔离依赖,并在执行 Pulumi 命令前激活它。
|
||||
|
||||
## 2. 必需环境变量
|
||||
|
||||
GitHub Actions 与本地调试共享相同的一组环境变量。根据目标云厂商选择性地设置凭据,但 **Pulumi 状态管理必须使用 S3 backend**。
|
||||
|
||||
| 变量名 | 说明 |
|
||||
| --- | --- |
|
||||
| `PULUMI_ACCESS_TOKEN` | 使用 Pulumi Service 时需要的访问令牌。若仅使用 S3 backend,可留空。 |
|
||||
| `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` | 用于访问 AWS 资源和 S3 状态桶的凭据。 |
|
||||
| `ALICLOUD_ACCESS_KEY` / `ALICLOUD_SECRET_KEY` | 部署阿里云资源时所需的访问密钥。 |
|
||||
| `VULTR_API_KEY` | 部署 Vultr 资源时使用的 API Key。 |
|
||||
| `IAC_STATE_BACKEND` | Pulumi 后端地址,**必须**为 `s3://<bucket>/<path>` 形式,以确保状态文件全部存储在 S3。 |
|
||||
| `PULUMI_STACK` | 当前操作的 Stack 名称,例如 `dev`、`prod`。 |
|
||||
| `CONFIG_PATH` | (可选)指定配置目录,默认根据云厂商选择 `config/<provider>`。 |
|
||||
|
||||
> 兼容变量:脚本同样支持 `IAC_State_backend` 形式的环境变量,以便与现有流水线兼容。
|
||||
|
||||
S3 backend 的 Bucket 需提前创建,并为 Pulumi 访问角色授予读写权限。例如:
|
||||
|
||||
```bash
|
||||
export IAC_STATE_BACKEND="s3://my-pulumi-state-bucket/modern-app"
|
||||
```
|
||||
|
||||
## 3. `iac_run.sh` 辅助脚本
|
||||
|
||||
为方便本地调试,目录下新增 `iac_run.sh`,与 GitHub Actions 的命令约定保持一致。执行前请确保以上环境变量均已配置。
|
||||
|
||||
```bash
|
||||
cd iac_modules/pulumi
|
||||
./iac_run.sh <命令>
|
||||
```
|
||||
|
||||
支持的命令如下:
|
||||
|
||||
| 命令 | 对应操作 |
|
||||
| --- | --- |
|
||||
| `init` | 登录 S3 backend 并初始化(或选择)指定的 Stack。 |
|
||||
| `create` | 使用 `pulumi up --yes --skip-preview` 创建/更新资源。 |
|
||||
| `migrate` | 调用 `pulumi refresh --yes`,同步实际资源状态到后端。 |
|
||||
| `upgrade` | 执行常规 `pulumi up --yes` 以滚动更新。 |
|
||||
| `backup` | 通过 `pulumi stack export` 导出状态文件到 `backups/` 目录。可通过 `PULUMI_BACKUP_DIR` 覆盖输出路径。 |
|
||||
| `restore <文件>` | 从指定备份文件执行 `pulumi stack import`。也可设置 `BACKUP_FILE` 环境变量。 |
|
||||
| `destroy` | 运行 `pulumi destroy --yes` 清理当前 Stack 资源。 |
|
||||
|
||||
查看帮助信息:
|
||||
|
||||
```bash
|
||||
./iac_run.sh --help
|
||||
```
|
||||
|
||||
## 4. 配置目录与多云支持
|
||||
|
||||
Pulumi 入口脚本会根据配置文件中的根节点自动选择部署目标:
|
||||
|
||||
- `alicloud`:执行阿里云 Landing Zone 模块。
|
||||
- `aws`:执行 AWS 基线模块。
|
||||
- `vultr`:执行 Vultr Landing Zone 模块。
|
||||
|
||||
可通过设置 `CONFIG_PATH` 指向自定义目录,例如:
|
||||
|
||||
```bash
|
||||
export CONFIG_PATH="config/vultr/dev"
|
||||
```
|
||||
|
||||
随后运行 `./iac_run.sh init` 与 `./iac_run.sh create` 即可完成 Vultr 基线的部署与更新。
|
||||
|
||||
## 5. 常见问题
|
||||
|
||||
1. **Pulumi 提示未登录**:确认是否正确设置了 `IAC_STATE_BACKEND`(或 `IAC_State_backend`)并具备相应的 S3 访问权限。
|
||||
2. **凭据不足**:依赖的云厂商访问密钥需具备创建/查询基础资源的权限,建议在 IAM 中专门为 IaC 运行创建角色或子账号。
|
||||
3. **Python 依赖缺失**:确保已经在仓库根目录执行 `pip install -r requirements.txt`,Pulumi 才能正确加载各个云厂商的 SDK。
|
||||
|
||||
如需更多关于资源结构与模块的细节,请查阅 `modules/` 目录或仓库中的云基线设计文档。
|
||||
@ -6,9 +6,11 @@ from typing import Dict
|
||||
import pulumi
|
||||
import pulumi_alicloud as alicloud
|
||||
import pulumi_aws as aws
|
||||
import ediri_vultr as vultr
|
||||
|
||||
from modules import alicloud as alicloud_modules
|
||||
from modules import aws as aws_modules
|
||||
from modules import vultr as vultr_modules
|
||||
from utils.config_loader import load_merged_config
|
||||
|
||||
|
||||
@ -95,6 +97,39 @@ def deploy_aws(config: Dict[str, object]) -> None:
|
||||
pulumi.export("s3_bucket_count", len(buckets))
|
||||
|
||||
|
||||
def deploy_vultr(config: Dict[str, object]) -> None:
|
||||
vultr_conf: Dict[str, object] = config.get("vultr", {}) # type: ignore[assignment]
|
||||
region = vultr_conf.get("region")
|
||||
default_tags = vultr_conf.get("default_tags", {})
|
||||
|
||||
if region:
|
||||
vultr.config.region = region # type: ignore[assignment]
|
||||
pulumi.export("region", region)
|
||||
|
||||
pulumi.log.info("Loaded Vultr configuration")
|
||||
|
||||
network_results = vultr_modules.create_vpcs(
|
||||
config.get("network", {}),
|
||||
region,
|
||||
)
|
||||
|
||||
firewall_results = vultr_modules.create_firewall_groups(
|
||||
config.get("security", {}),
|
||||
)
|
||||
|
||||
instance_results = vultr_modules.create_instances(
|
||||
config.get("compute", {}),
|
||||
region,
|
||||
default_tags,
|
||||
firewall_results,
|
||||
network_results,
|
||||
)
|
||||
|
||||
pulumi.export("vpc_count", len(network_results))
|
||||
pulumi.export("firewall_group_count", len(firewall_results))
|
||||
pulumi.export("instance_count", len(instance_results))
|
||||
|
||||
|
||||
def main() -> None:
|
||||
config_dir = os.environ.get("CONFIG_PATH", "config/alicloud")
|
||||
config = load_merged_config(config_dir)
|
||||
@ -103,9 +138,11 @@ def main() -> None:
|
||||
deploy_alicloud(config)
|
||||
elif "aws" in config:
|
||||
deploy_aws(config)
|
||||
elif "vultr" in config:
|
||||
deploy_vultr(config)
|
||||
else:
|
||||
raise ValueError(
|
||||
"Unsupported landing zone configuration. Expected either 'alicloud' or 'aws' section."
|
||||
"Unsupported landing zone configuration. Expected a 'alicloud', 'aws', or 'vultr' section."
|
||||
)
|
||||
|
||||
|
||||
|
||||
155
iac_modules/pulumi/iac_run.sh
Executable file
155
iac_modules/pulumi/iac_run.sh
Executable file
@ -0,0 +1,155 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
PULUMI_BIN=${PULUMI_BIN:-pulumi}
|
||||
STACK_NAME=${PULUMI_STACK:-${STACK_NAME:-${STACK:-}}}
|
||||
BACKEND_URL=${IAC_STATE_BACKEND:-${IAC_State_backend:-}}
|
||||
BACKUPS_DIR=${PULUMI_BACKUP_DIR:-backups}
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
用法: ./iac_run.sh <命令> [参数]
|
||||
|
||||
可用命令:
|
||||
init 初始化 Pulumi 后端与 Stack(需要 IAC_STATE_BACKEND 和 PULUMI_STACK)
|
||||
create 创建或更新资源(pulumi up --yes --skip-preview)
|
||||
migrate 同步当前资源状态到 S3 后端(pulumi refresh --yes)
|
||||
upgrade 执行常规的基础设施更新(pulumi up --yes)
|
||||
backup 导出 Stack 状态到本地文件(默认保存到 backups/ 目录)
|
||||
restore <文件路径> 从指定备份文件恢复 Stack 状态
|
||||
destroy 销毁当前 Stack 中的所有资源
|
||||
|
||||
环境变量:
|
||||
PULUMI_STACK Pulumi Stack 名称(必需)
|
||||
IAC_STATE_BACKEND Pulumi backend 地址(必须为 s3:// 前缀)
|
||||
CONFIG_PATH 自定义配置目录,可选
|
||||
PULUMI_BACKUP_DIR 备份输出目录,默认为 ./backups
|
||||
|
||||
若使用 GitHub Actions 对齐流水线,请确保预先设置云厂商访问密钥与 Pulumi 访问令牌。
|
||||
USAGE
|
||||
}
|
||||
|
||||
require_backend() {
|
||||
if [[ -z "${BACKEND_URL}" ]]; then
|
||||
echo "[错误] 未设置 IAC_STATE_BACKEND(或 IAC_State_backend)环境变量,无法连接到 S3 backend." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${BACKEND_URL}" != s3://* ]]; then
|
||||
echo "[错误] IAC_STATE_BACKEND 必须为 s3:// 开头的 Pulumi 后端地址." >&2
|
||||
exit 1
|
||||
fi
|
||||
"${PULUMI_BIN}" login "${BACKEND_URL}" >/dev/null
|
||||
}
|
||||
|
||||
require_stack() {
|
||||
if [[ -z "${STACK_NAME}" ]]; then
|
||||
echo "[错误] 未设置 PULUMI_STACK 环境变量." >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! "${PULUMI_BIN}" stack select "${STACK_NAME}" >/dev/null 2>&1; then
|
||||
"${PULUMI_BIN}" stack init "${STACK_NAME}"
|
||||
fi
|
||||
}
|
||||
|
||||
command_init() {
|
||||
require_backend
|
||||
require_stack
|
||||
echo "Pulumi backend 已配置: ${BACKEND_URL}"
|
||||
echo "Pulumi stack 已就绪: ${STACK_NAME}"
|
||||
}
|
||||
|
||||
command_create() {
|
||||
require_backend
|
||||
require_stack
|
||||
"${PULUMI_BIN}" up --stack "${STACK_NAME}" --yes --skip-preview
|
||||
}
|
||||
|
||||
command_migrate() {
|
||||
require_backend
|
||||
require_stack
|
||||
"${PULUMI_BIN}" refresh --stack "${STACK_NAME}" --yes
|
||||
}
|
||||
|
||||
command_upgrade() {
|
||||
require_backend
|
||||
require_stack
|
||||
"${PULUMI_BIN}" up --stack "${STACK_NAME}" --yes
|
||||
}
|
||||
|
||||
command_backup() {
|
||||
require_backend
|
||||
require_stack
|
||||
mkdir -p "${BACKUPS_DIR}"
|
||||
timestamp=$(date +"%Y%m%d%H%M%S")
|
||||
backup_file="${BACKUPS_DIR}/${STACK_NAME}-${timestamp}.json"
|
||||
"${PULUMI_BIN}" stack export --stack "${STACK_NAME}" >"${backup_file}"
|
||||
echo "Pulumi stack 已备份到 ${backup_file}"
|
||||
}
|
||||
|
||||
command_restore() {
|
||||
require_backend
|
||||
require_stack
|
||||
local backup_file=${1:-${BACKUP_FILE:-}}
|
||||
if [[ -z "${backup_file}" ]]; then
|
||||
echo "[错误] restore 命令需要提供备份文件路径作为参数或通过 BACKUP_FILE 环境变量传入." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f "${backup_file}" ]]; then
|
||||
echo "[错误] 找不到备份文件 ${backup_file}." >&2
|
||||
exit 1
|
||||
fi
|
||||
"${PULUMI_BIN}" stack import --stack "${STACK_NAME}" <"${backup_file}"
|
||||
}
|
||||
|
||||
command_destroy() {
|
||||
require_backend
|
||||
require_stack
|
||||
"${PULUMI_BIN}" destroy --stack "${STACK_NAME}" --yes
|
||||
}
|
||||
|
||||
main() {
|
||||
if [[ $# -lt 1 ]]; then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local command=$1
|
||||
shift
|
||||
|
||||
case "${command}" in
|
||||
init)
|
||||
command_init "$@"
|
||||
;;
|
||||
create)
|
||||
command_create "$@"
|
||||
;;
|
||||
migrate)
|
||||
command_migrate "$@"
|
||||
;;
|
||||
upgrade)
|
||||
command_upgrade "$@"
|
||||
;;
|
||||
backup)
|
||||
command_backup "$@"
|
||||
;;
|
||||
restore)
|
||||
command_restore "$@"
|
||||
;;
|
||||
destroy)
|
||||
command_destroy "$@"
|
||||
;;
|
||||
-h|--help|help)
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
echo "[错误] 未知命令: ${command}" >&2
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Loading…
Reference in New Issue
Block a user