多环境管理与 Vault 加密
核心问题:开发、测试、生产三套环境配置差异很大——怎样用同一套 Playbook 管理它们?数据库密码等敏感信息怎样安全存储?
多环境 Inventory 结构
推荐将每个环境放在独立目录,而不是在同一 Inventory 文件里用变量区分:
inventory/
├── staging/
│ ├── hosts.yml # Staging 主机清单
│ └── group_vars/
│ ├── all.yml # Staging 公共变量
│ ├── webservers.yml # Staging Web 服务器变量
│ └── webservers/
│ ├── vars.yml # 明文变量
│ └── vault.yml # Vault 加密文件(密钥等)
├── production/
│ ├── hosts.yml
│ └── group_vars/
│ ├── all.yml
│ ├── webservers.yml
│ └── webservers/
│ ├── vars.yml
│ └── vault.yml
└── dev/
├── hosts.yml
└── group_vars/
└── all.yml
环境变量分层设计
# inventory/staging/group_vars/all.yml
# 所有主机公共变量(Staging 版本)
target_env: staging
aws_region: ap-southeast-1
log_level: debug
monitoring_enabled: false
# inventory/production/group_vars/all.yml
target_env: production
aws_region: ap-southeast-1
log_level: warn
monitoring_enabled: true
backup_retention_days: 30
# inventory/staging/group_vars/webservers/vars.yml
app_port: 3000
db_host: db-staging.internal
db_name: myapp_staging
db_user: app_staging
redis_host: redis-staging.internal
ssl_enabled: true
domain_name: staging.example.com
# db_password 不在这里 — 在 vault.yml 里
# inventory/production/group_vars/webservers/vars.yml
app_port: 3000
db_host: db-prod-primary.internal
db_name: myapp_production
db_user: app_prod
redis_host: redis-prod.internal
ssl_enabled: true
domain_name: api.example.com
ansible-vault:加密敏感变量
ansible-vault 对变量文件(或整个文件)进行 AES-256 加密,加密后文件可以安全提交到 Git。
基本操作
# 创建加密文件
ansible-vault create inventory/production/group_vars/webservers/vault.yml
# 编辑加密文件(自动解密→编辑→加密保存)
ansible-vault edit inventory/production/group_vars/webservers/vault.yml
# 加密已有明文文件
ansible-vault encrypt secrets.yml
# 解密查看(不保存,仅输出到终端)
ansible-vault view inventory/production/group_vars/webservers/vault.yml
# 修改 Vault 密码
ansible-vault rekey inventory/production/group_vars/webservers/vault.yml
# 解密并覆盖文件(危险操作,谨慎使用)
ansible-vault decrypt secrets.yml
vault.yml 内容(加密前)
# inventory/production/group_vars/webservers/vault.yml(明文,加密后不可读)
vault_db_password: "S3cur3P@ssw0rd_prod"
vault_app_secret: "xK9mL2nP8qR5vT7w"
vault_aws_access_key: "AKIAIOSFODNN7EXAMPLE"
vault_aws_secret_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
vault_slack_webhook: "https://hooks.slack.com/services/T.../B.../..."
加密后文件内容类似:
$ANSIBLE_VAULT;1.1;AES256
33613438343061643736353065313163393138313663333237623566623139313633336334303561
3934646562313332363537393664383064373636656231360a616336363733643730346538383663
...
命名约定:vault_ 前缀
# vars.yml — 明文,引用 vault 变量
db_password: "{{ vault_db_password }}"
app_secret: "{{ vault_app_secret }}"
调用者只需要知道 db_password,不需要知道它来自 vault。
运行时提供 Vault 密码
# 方式 1:交互式输入(适合手动执行)
ansible-playbook -i inventory/production/ site.yml --ask-vault-pass
# 方式 2:密码文件(适合 CI,文件不提交到 Git)
ansible-playbook -i inventory/production/ site.yml --vault-password-file ~/.vault_pass
# 方式 3:环境变量(CI 最常用)
export ANSIBLE_VAULT_PASSWORD_FILE=~/.vault_pass
ansible-playbook -i inventory/production/ site.yml
CI/CD 中管理 Vault 密码
# GitHub Actions 示例
- name: Deploy to Production
env:
ANSIBLE_VAULT_PASSWORD: ${{ secrets.ANSIBLE_VAULT_PASSWORD }}
run: |
echo "$ANSIBLE_VAULT_PASSWORD" > /tmp/vault_pass
ansible-playbook -i inventory/production/ site.yml \
--vault-password-file /tmp/vault_pass
rm /tmp/vault_pass
多 Vault ID:不同环境用不同密码
# 创建时指定 vault-id
ansible-vault create --vault-id staging@prompt staging_secrets.yml
ansible-vault create --vault-id prod@prompt prod_secrets.yml
# 运行时提供多个密码
ansible-playbook site.yml \
--vault-id staging@~/.vault_staging \
--vault-id prod@~/.vault_prod
最佳实践:完整多环境工作流
目录结构
project/
├── ansible.cfg
├── site.yml
├── roles/
│ └── app/
├── inventory/
│ ├── staging/
│ │ ├── hosts.yml
│ │ └── group_vars/
│ │ └── all/
│ │ ├── vars.yml # 明文
│ │ └── vault.yml # ansible-vault 加密
│ └── production/
│ ├── hosts.yml
│ └── group_vars/
│ └── all/
│ ├── vars.yml
│ └── vault.yml
└── .gitignore # 忽略 .vault_pass 文件!
.gitignore
.vault_pass
*.vault_pass
.env
*.pem
*.key
部署命令约定
# Staging 部署(CI 自动触发)
ansible-playbook -i inventory/staging/ site.yml \
--vault-password-file ~/.vault_staging
# Production 部署(需要人工审批后手动执行或 CD 触发)
ansible-playbook -i inventory/production/ site.yml \
--vault-password-file ~/.vault_prod \
--extra-vars "confirm_env=production"
对比:各种密钥管理方案
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| ansible-vault | 中小团队、简单项目 | 零依赖、随 Ansible 自带 | 密码轮换麻烦、不支持动态密钥 |
| AWS Secrets Manager | AWS 环境 | 自动轮换、细粒度 IAM 权限 | 费用(每个密钥 $0.40/月) |
| HashiCorp Vault | 多云、大型企业 | 功能最强、动态密钥生成 | 维护复杂度高 |
| SOPS + Age/GPG | 现代 GitOps | 文件级加密,与 Git 深度集成 | 学习成本较高 |
常见错误
| 错误 | 原因 | 解决 |
|---|---|---|
Decryption failed | Vault 密码错误 | 确认密码文件或 --ask-vault-pass 输入正确 |
| 明文密码被提交到 Git | 忘记加密 vault.yml | 安装 git-secrets 或 pre-commit 钩子检测 |
vault.yml 被 edit 后变为明文 | ansible-vault edit 失败没有重新加密 | 检查文件开头是否有 $ANSIBLE_VAULT; |
| Staging 误用了 Production 密钥 | 多环境 vault 密码混用 | 严格区分 --vault-id staging@... 和 prod@... |