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:
shenlan 2025-09-30 11:18:32 +08:00 committed by GitHub
commit 576a1c963d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 283 additions and 1 deletions

View 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/` 目录或仓库中的云基线设计文档。

View File

@ -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
View 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 "$@"