356 lines
14 KiB
YAML
356 lines
14 KiB
YAML
name: Deploy Environment & Migrate Data (Data Sync)
|
||
|
||
# =============================================================================
|
||
# IaC ↔ 数据流单向迁移流水线 (Site Recovery)
|
||
# 1. 动态 Provision (基于 Terraform 创建新环境 site-migration-toolkit)
|
||
# 2. 部署基础服务 (Bootstrap 部署,跑空环境)
|
||
# 3. 生产环境克隆与单向同步 (通过 site_migration Ansible 角色)
|
||
# =============================================================================
|
||
|
||
on:
|
||
workflow_dispatch:
|
||
inputs:
|
||
infra_ref:
|
||
description: "ai-workspace-infra git ref"
|
||
required: false
|
||
default: "main"
|
||
type: string
|
||
source_host:
|
||
description: "源环境 IP 或域名 (用于导出数据)"
|
||
required: true
|
||
default: "console.svc.plus"
|
||
type: string
|
||
source_domain_base:
|
||
description: "源环境域名后缀 (例如 svc.plus)"
|
||
required: true
|
||
default: "svc.plus"
|
||
type: string
|
||
target_domain_base:
|
||
description: "新环境域名后缀 (用于参数化替换,如 onwalk.net)"
|
||
required: true
|
||
default: "onwalk.net"
|
||
type: string
|
||
run_provision_and_deploy:
|
||
description: "是否执行 Provision 和基础环境 Deploy? (如果只迁移数据则取消勾选)"
|
||
required: false
|
||
default: true
|
||
type: boolean
|
||
toolkit_action:
|
||
description: "Toolkit 执行动作"
|
||
required: false
|
||
default: "migrate"
|
||
type: choice
|
||
options: [migrate, backup, restore]
|
||
terraform_action:
|
||
description: "apply 创建/更新,destroy 销毁"
|
||
required: false
|
||
default: "apply"
|
||
type: choice
|
||
options: [apply, destroy]
|
||
confirm_dns_switch:
|
||
description: "危险: 勾选此项将在最后一步接管原域名的生产流量 (仅真实灾备启用)"
|
||
required: false
|
||
default: false
|
||
type: boolean
|
||
|
||
permissions:
|
||
contents: read
|
||
id-token: write
|
||
|
||
concurrency:
|
||
group: deploy-env-migration
|
||
cancel-in-progress: false
|
||
|
||
env:
|
||
VAULT_ADDR: https://vault.svc.plus
|
||
VAULT_ROLE: github-actions-site-migration-toolkit
|
||
VAULT_KV: kv/data/CICD
|
||
VAULT_KV_OPENCLAW: kv/data/openclaw
|
||
VPS_ROOT: infra/iac_modules/terraform-hcl-standard/vultr-vps
|
||
# 指向 site-migration-toolkit 的 Terraform 目录
|
||
ENV_DIR: infra/iac_modules/terraform-hcl-standard/vultr-vps/envs/site-migration-toolkit
|
||
PLAYBOOKS_DIR: infra/playbooks
|
||
|
||
jobs:
|
||
# ---------------------------------------------------------------------------
|
||
# 步骤 1: 基础设施配置与启动 (Provision)
|
||
# ---------------------------------------------------------------------------
|
||
provision:
|
||
name: Provision New Environment (Terraform)
|
||
if: ${{ github.event.inputs.run_provision_and_deploy == 'true' }}
|
||
runs-on: ubuntu-latest
|
||
outputs:
|
||
hosts: ${{ steps.matrix.outputs.hosts }}
|
||
count: ${{ steps.matrix.outputs.count }}
|
||
steps:
|
||
- name: Load Vault secrets
|
||
id: vault
|
||
uses: hashicorp/vault-action@v4
|
||
with:
|
||
url: ${{ env.VAULT_ADDR }}
|
||
method: jwt
|
||
role: ${{ env.VAULT_ROLE }}
|
||
jwtGithubAudience: vault
|
||
ignoreNotFound: true
|
||
secrets: |
|
||
${{ env.VAULT_KV }} VULTR_API_KEY | VULTR_API_KEY ;
|
||
${{ env.VAULT_KV }} TF_STATE_ENDPOINT | TF_STATE_ENDPOINT ;
|
||
${{ env.VAULT_KV }} TF_STATE_BUCKET | TF_STATE_BUCKET ;
|
||
${{ env.VAULT_KV }} TF_STATE_ACCESS_KEY | TF_STATE_ACCESS_KEY ;
|
||
${{ env.VAULT_KV }} TF_STATE_SECRET_KEY | TF_STATE_SECRET_KEY ;
|
||
${{ env.VAULT_KV }} TF_STATE_REGION | TF_STATE_REGION
|
||
|
||
- name: Checkout iac_modules
|
||
uses: actions/checkout@v7
|
||
with:
|
||
repository: ai-workspace-infra/iac_modules
|
||
ref: ${{ github.event.inputs.infra_ref }}
|
||
path: infra/iac_modules
|
||
|
||
- uses: hashicorp/setup-terraform@v3
|
||
with:
|
||
terraform_version: "1.9.8"
|
||
|
||
- uses: actions/setup-python@v6
|
||
with:
|
||
python-version: "3.12"
|
||
|
||
- name: Install render deps
|
||
run: pip install --quiet pyyaml jinja2
|
||
|
||
- name: Configure remote backend
|
||
working-directory: ${{ env.ENV_DIR }}
|
||
env:
|
||
TF_STATE_ENDPOINT: ${{ steps.vault.outputs.TF_STATE_ENDPOINT }}
|
||
TF_STATE_REGION: ${{ steps.vault.outputs.TF_STATE_REGION }}
|
||
run: python3 $GITHUB_WORKSPACE/${{ env.VPS_ROOT }}/scripts/render_backend_tf.py backend.tf
|
||
|
||
- name: generate.py render
|
||
working-directory: ${{ env.VPS_ROOT }}
|
||
# 强制指定 envs/site-migration-toolkit 生成 tfvars 和显式 HCL
|
||
run: python3 scripts/generate.py render --resources config/resources/site-migration-toolkit.yaml --workdir envs/site-migration-toolkit
|
||
|
||
- name: Terraform init
|
||
working-directory: ${{ env.ENV_DIR }}
|
||
env:
|
||
AWS_ACCESS_KEY_ID: ${{ steps.vault.outputs.TF_STATE_ACCESS_KEY }}
|
||
AWS_SECRET_ACCESS_KEY: ${{ steps.vault.outputs.TF_STATE_SECRET_KEY }}
|
||
TF_STATE_ENDPOINT: ${{ steps.vault.outputs.TF_STATE_ENDPOINT }}
|
||
TF_STATE_BUCKET: ${{ steps.vault.outputs.TF_STATE_BUCKET }}
|
||
TF_STATE_REGION: ${{ steps.vault.outputs.TF_STATE_REGION }}
|
||
run: |
|
||
terraform init -input=false \
|
||
-backend-config="bucket=${TF_STATE_BUCKET}" \
|
||
-backend-config="key=site-migration-toolkit/terraform.tfstate" \
|
||
-backend-config="region=${TF_STATE_REGION}"
|
||
|
||
- name: Terraform Apply / Destroy
|
||
working-directory: ${{ env.ENV_DIR }}
|
||
env:
|
||
AWS_ACCESS_KEY_ID: ${{ steps.vault.outputs.TF_STATE_ACCESS_KEY }}
|
||
AWS_SECRET_ACCESS_KEY: ${{ steps.vault.outputs.TF_STATE_SECRET_KEY }}
|
||
TF_VAR_vultr_api_key: ${{ steps.vault.outputs.VULTR_API_KEY }}
|
||
run: terraform ${{ github.event.inputs.terraform_action }} -auto-approve -input=false
|
||
|
||
- name: generate.py inventory
|
||
if: ${{ github.event.inputs.terraform_action == 'apply' }}
|
||
working-directory: ${{ env.VPS_ROOT }}
|
||
env:
|
||
AWS_ACCESS_KEY_ID: ${{ steps.vault.outputs.TF_STATE_ACCESS_KEY }}
|
||
AWS_SECRET_ACCESS_KEY: ${{ steps.vault.outputs.TF_STATE_SECRET_KEY }}
|
||
# 同样指定 envs/site-migration-toolkit
|
||
run: python3 scripts/generate.py inventory --resources config/resources/site-migration-toolkit.yaml --workdir envs/site-migration-toolkit
|
||
|
||
- name: Build deploy matrix
|
||
id: matrix
|
||
if: ${{ github.event.inputs.terraform_action == 'apply' }}
|
||
working-directory: ${{ env.ENV_DIR }}
|
||
run: |
|
||
hosts="$(jq -c 'keys' cmdb.json)"
|
||
echo "hosts=${hosts}" >> "$GITHUB_OUTPUT"
|
||
echo "count=$(jq 'length' cmdb.json)" >> "$GITHUB_OUTPUT"
|
||
|
||
- name: Upload CMDB artifact
|
||
if: ${{ github.event.inputs.terraform_action == 'apply' }}
|
||
uses: actions/upload-artifact@v7
|
||
with:
|
||
name: site-migration-toolkit-cmdb
|
||
path: |
|
||
${{ env.ENV_DIR }}/cmdb.json
|
||
${{ env.ENV_DIR }}/inventory.ini
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 步骤 2: 基础环境安装 (Bootstrap Deploy)
|
||
# ---------------------------------------------------------------------------
|
||
deploy_base:
|
||
name: Deploy Base Services on ${{ matrix.host }}
|
||
needs: provision
|
||
if: ${{ github.event.inputs.run_provision_and_deploy == 'true' && github.event.inputs.terraform_action == 'apply' && needs.provision.outputs.count != '0' }}
|
||
runs-on: ubuntu-latest
|
||
strategy:
|
||
fail-fast: false
|
||
matrix:
|
||
host: ${{ fromJSON(needs.provision.outputs.hosts) }}
|
||
steps:
|
||
- name: Checkout xworkspace-console helpers
|
||
uses: actions/checkout@v7
|
||
with:
|
||
repository: ai-workspace-lab/xworkspace-console
|
||
ref: main
|
||
|
||
- name: Download CMDB
|
||
uses: actions/download-artifact@v8
|
||
with:
|
||
name: site-migration-toolkit-cmdb
|
||
path: cmdb
|
||
|
||
- name: Load Vault secrets
|
||
id: vault
|
||
uses: hashicorp/vault-action@v4
|
||
with:
|
||
url: ${{ env.VAULT_ADDR }}
|
||
method: jwt
|
||
role: ${{ env.VAULT_ROLE }}
|
||
jwtGithubAudience: vault
|
||
ignoreNotFound: true
|
||
secrets: |
|
||
${{ env.VAULT_KV }} SSH_PRIVATE_DEPLOY_KEY_B64 | ANSIBLE_SSH_KEY_B64 ;
|
||
${{ env.VAULT_KV }} AI_WORKSPACE_AUTH_TOKEN | AI_WORKSPACE_AUTH_TOKEN
|
||
|
||
- name: Configure SSH Key
|
||
run: |
|
||
mkdir -p ~/.ssh
|
||
printf '%s' "${{ steps.vault.outputs.ANSIBLE_SSH_KEY_B64 }}" | base64 -d > ~/.ssh/id_deploy
|
||
chmod 600 ~/.ssh/id_deploy
|
||
ssh-keygen -y -f ~/.ssh/id_deploy >/dev/null
|
||
|
||
- name: Wait for host SSH
|
||
run: |
|
||
ip="$(jq -r '.["${{ matrix.host }}"].ip' cmdb/cmdb.json)"
|
||
for _ in $(seq 1 60); do
|
||
if nc -z -w 5 "$ip" 22; then exit 0; fi
|
||
sleep 10
|
||
done
|
||
exit 1
|
||
|
||
- name: Run on-host bootstrap
|
||
env:
|
||
MATRIX_HOST: ${{ matrix.host }}
|
||
CMDB_PATH: cmdb/cmdb.json
|
||
SSH_KEY_PATH: ~/.ssh/id_deploy
|
||
AI_WORKSPACE_OFFLINE_MODE: 'off'
|
||
AI_WORKSPACE_AUTH_TOKEN: ${{ steps.vault.outputs.AI_WORKSPACE_AUTH_TOKEN }}
|
||
run: bash scripts/run-on-host-bootstrap.sh
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 步骤 3: 全站单向数据流迁移与环境恢复 (Data Migration)
|
||
# ---------------------------------------------------------------------------
|
||
data_migration:
|
||
name: Migrate Site Data (Source -> Target)
|
||
needs: deploy_base
|
||
if: ${{ always() && github.event.inputs.terraform_action == 'apply' }}
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- name: Download CMDB
|
||
# 只在 provision 运行的情况下下载
|
||
if: ${{ github.event.inputs.run_provision_and_deploy == 'true' }}
|
||
uses: actions/download-artifact@v8
|
||
with:
|
||
name: site-migration-toolkit-cmdb
|
||
path: cmdb
|
||
|
||
- name: Checkout site-migration-toolkit
|
||
uses: actions/checkout@v7
|
||
with:
|
||
path: site-migration-toolkit
|
||
|
||
- name: Checkout playbooks
|
||
uses: actions/checkout@v7
|
||
with:
|
||
repository: ai-workspace-infra/playbooks
|
||
ref: ${{ github.event.inputs.infra_ref }}
|
||
path: playbooks
|
||
|
||
- name: Install Ansible
|
||
run: pip install --quiet ansible
|
||
|
||
- name: Load Vault secrets for SSH
|
||
id: vault
|
||
uses: hashicorp/vault-action@v4
|
||
with:
|
||
url: ${{ env.VAULT_ADDR }}
|
||
method: jwt
|
||
role: ${{ env.VAULT_ROLE }}
|
||
jwtGithubAudience: vault
|
||
ignoreNotFound: true
|
||
secrets: |
|
||
${{ env.VAULT_KV }} SSH_PRIVATE_DEPLOY_KEY_B64 | ANSIBLE_SSH_KEY_B64
|
||
|
||
- name: Configure SSH Key
|
||
run: |
|
||
mkdir -p ~/.ssh
|
||
printf '%s' "${{ steps.vault.outputs.ANSIBLE_SSH_KEY_B64 }}" | base64 -d > ~/.ssh/id_deploy
|
||
chmod 600 ~/.ssh/id_deploy
|
||
|
||
- name: Generate Migration Inventory
|
||
run: |
|
||
# 动态解析 target IP,如果是纯数据迁移,则可能需要手动指定,这里以 jq 解析 cmdb 为主
|
||
if [ -f "cmdb/cmdb.json" ]; then
|
||
TARGET_IP=$(jq -r 'to_entries | .[0].value.ip' cmdb/cmdb.json)
|
||
else
|
||
TARGET_IP="new_env_ip_placeholder" # 需要补充处理纯迁移模式下的 Target IP
|
||
fi
|
||
|
||
echo "[migration_source]" > inventory_migration.ini
|
||
echo "${{ github.event.inputs.source_host }} ansible_user=root ansible_ssh_private_key_file=~/.ssh/id_deploy" >> inventory_migration.ini
|
||
echo "" >> inventory_migration.ini
|
||
echo "[migration_target]" >> inventory_migration.ini
|
||
echo "${TARGET_IP} ansible_user=root ansible_ssh_private_key_file=~/.ssh/id_deploy" >> inventory_migration.ini
|
||
|
||
- name: Run Toolkit Action (${{ github.event.inputs.toolkit_action }})
|
||
working-directory: site-migration-toolkit
|
||
run: |
|
||
make ${{ github.event.inputs.toolkit_action }} RUN_ARGS="-i $GITHUB_WORKSPACE/inventory_migration.ini -e target_domain=${{ github.event.inputs.target_domain_base }} -e migration_flow.source.domain_base=${{ github.event.inputs.source_domain_base }}"
|
||
|
||
switch_dns:
|
||
name: Switch DNS Traffic (Manual Approval)
|
||
needs: data_migration
|
||
if: ${{ always() && github.event.inputs.terraform_action == 'apply' && github.event.inputs.confirm_dns_switch == 'true' }}
|
||
runs-on: ubuntu-latest
|
||
environment: production
|
||
steps:
|
||
- name: Download CMDB
|
||
uses: actions/download-artifact@v8
|
||
with:
|
||
name: site-migration-toolkit-cmdb
|
||
path: cmdb
|
||
|
||
- name: Checkout playbooks
|
||
uses: actions/checkout@v7
|
||
with:
|
||
repository: ai-workspace-infra/playbooks
|
||
ref: ${{ github.event.inputs.infra_ref }}
|
||
path: playbooks
|
||
|
||
- name: Install Ansible
|
||
run: pip install --quiet ansible
|
||
|
||
- name: Load Vault secrets for DNS
|
||
id: vault
|
||
uses: hashicorp/vault-action@v4
|
||
with:
|
||
url: ${{ env.VAULT_ADDR }}
|
||
method: jwt
|
||
role: ${{ env.VAULT_ROLE }}
|
||
jwtGithubAudience: vault
|
||
ignoreNotFound: true
|
||
secrets: |
|
||
${{ env.VAULT_KV }} CLOUDFLARE_DNS_API_TOKEN | CLOUDFLARE_DNS_API_TOKEN
|
||
|
||
- name: Switch Cloudflare DNS Records
|
||
env:
|
||
CLOUDFLARE_DNS_API_TOKEN: ${{ steps.vault.outputs.CLOUDFLARE_DNS_API_TOKEN }}
|
||
run: |
|
||
cd playbooks
|
||
ansible-playbook -i ../cmdb/inventory.ini update_site_dns.yml -e "target_domain=${{ github.event.inputs.target_domain_base }}" -e "source_domain=${{ github.event.inputs.source_domain_base }}"
|