在AWS Elastic Beanstalk(EB)环境中管理EC2实例时,环境变量的动态配置一直是个痛点。传统做法是通过EB控制台界面手动添加,这在需要频繁变更或批量部署时效率极低。更麻烦的是,每次环境变量更新都会触发实例重建,导致服务中断。
我最近接手的一个微服务项目就遇到这个问题:需要根据部署环境(dev/staging/prod)动态注入不同的数据库连接参数和第三方API密钥。手动操作不仅容易出错,在CI/CD流程中更是无法实现自动化。经过多次踩坑,最终摸索出一套完全通过代码管理EB环境变量的方案。
这套方案的核心价值在于:
先看几种常见的EB环境变量管理方式:
| 方案 | 优点 | 缺点 |
|---|---|---|
| EB控制台手动配置 | 操作简单直观 | 无法版本控制,更新触发实例重建 |
| .ebextensions配置 | 可版本化 | 仍需部署生效,部分变量类型不支持 |
| 第三方参数存储服务 | 支持动态读取 | 增加架构复杂度,有额外成本 |
| 本文的代码化方案 | 全自动化,支持热更新 | 需要编写脚本,对IAM权限有要求 |
最终方案基于以下AWS服务构建:
.platform/hooks在部署阶段注入变量重要提示:选择SSM而非Secrets Manager主要出于成本考虑。SSM标准层参数完全免费,而Secrets Manager每个密钥每月收费$0.4。对于非金融级敏感数据,SSM已足够安全。
首先需要配置SSM参数存储,建议按以下命名规范创建:
code复制/env/[environment_name]/[application_name]/[variable_name]
例如生产环境的数据库连接字符串:
code复制/env/prod/user-service/DB_CONNECTION
通过AWS CLI创建示例:
bash复制aws ssm put-parameter \
--name "/env/prod/user-service/DB_CONNECTION" \
--value "postgres://user:pass@host:5432/db" \
--type SecureString \
--tags "Key=env,Value=prod"
EC2实例需要通过Instance Profile获取读取SSM的权限。在EB环境配置中添加以下托管策略:
AmazonSSMReadOnlyAccessjson复制{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ssm:GetParameters",
"ssm:GetParameter"
],
"Resource": [
"arn:aws:ssm:region:account-id:parameter/env/prod/user-service/*"
]
}
]
}
在项目根目录创建.platform/confighooks/predeploy文件夹,添加01_load_env_vars.sh:
bash复制#!/bin/bash
# 获取当前环境名称
ENV_NAME=$(/opt/elasticbeanstalk/bin/get-config environment -k EnvironmentName)
# 从SSM批量获取参数
PARAMS=$(aws ssm get-parameters-by-path \
--path "/env/${ENV_NAME}/user-service/" \
--with-decryption \
--query "Parameters[*].{Name:Name,Value:Value}")
# 转换为环境变量格式
for row in $(echo "${PARAMS}" | jq -r '.[] | @base64'); do
_jq() {
echo ${row} | base64 --decode | jq -r ${1}
}
# 提取变量名最后部分作为KEY
FULL_NAME=$(_jq '.Name')
KEY=$(basename "${FULL_NAME}")
VALUE=$(_jq '.Value')
echo "Exporting ${KEY}=${VALUE}"
echo "export ${KEY}=${VALUE}" >> /etc/profile.d/eb_env_vars.sh
done
# 使环境变量立即生效
source /etc/profile.d/eb_env_vars.sh
记得给脚本添加执行权限:
bash复制chmod +x .platform/confighooks/predeploy/01_load_env_vars.sh
对于需要提前加载变量的场景(如PM2启动前),在.platform/hooks/prestart添加:
bash复制#!/bin/bash
source /etc/profile.d/eb_env_vars.sh
# 示例:传递给Node.js应用
pm2 start ecosystem.config.js --update-env
通过环境名称自动加载不同配置,在CI/CD管道中动态设置:
yaml复制# .github/workflows/deploy.yml
jobs:
deploy:
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Set env vars from SSM
run: |
ENV_NAME=${{ github.event_name == 'push' && 'prod' || 'staging' }}
aws ssm get-parameters-by-path \
--path "/env/${ENV_NAME}/user-service/" \
--with-decryption \
--output json | jq -r '.Parameters[] | "\(.Name)=\(.Value)"' >> $GITHUB_ENV
对数据库密码等敏感信息建议:
SecureString类型存储轮换示例:
bash复制# rotate_secret.sh
NEW_PASSWORD=$(openssl rand -base64 16)
aws ssm put-parameter \
--name "/env/prod/user-service/DB_PASSWORD" \
--value "${NEW_PASSWORD}" \
--type SecureString \
--overwrite
# 触发应用重新加载(无需重启)
aws elasticbeanstalk restart-app-server --environment-name prod-env
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 变量未生效 | Hook执行顺序问题 | 确保脚本前缀数字正确(如01_, 02_)控制执行顺序 |
| PermissionDenied异常 | IAM权限不足 | 检查Instance Profile是否附加SSM读取策略,参数路径是否匹配 |
| 变量值被截断 | 命令行长度限制 | 改用get-parameters-by-path分批获取,或存储为JSON字符串再解析 |
| 部署时卡住 | Hook脚本未设置可执行权限 | chmod +x .platform/**/*.sh |
| 中文乱码 | 编码格式问题 | 在脚本开头添加export LANG=en_US.UTF-8 |
bash复制/var/log/eb-hooks.log
bash复制# 假设EC2已有权限
aws ssm get-parameter \
--name "/env/prod/user-service/DB_CONNECTION" \
--with-decryption \
--query "Parameter.Value" \
--output text
bash复制cat /etc/profile.d/eb_env_vars.sh
env | grep DB_
bash复制# 一次获取所有参数(最多10个)
aws ssm get-parameters \
--names "/env/prod/db_url" "/env/prod/api_key" \
--with-decryption
/tmp并设置TTLbash复制CACHE_FILE="/tmp/env_vars.cache"
if [ ! -f "$CACHE_FILE" ] || [ $(stat -c %Y "$CACHE_FILE") -lt $(date -d '1 hour ago' +%s) ]; then
aws ssm get-parameters-by-path --path "/env/prod/" > "$CACHE_FILE"
fi
bash复制# 先加载数据库等关键配置
CRITICAL_VARS=("DB_HOST" "DB_PORT" "REDIS_URL")
for var in "${CRITICAL_VARS[@]}"; do
value=$(aws ssm get-parameter --name "/env/prod/$var" --with-decryption --query "Parameter.Value" --output text)
export "$var=$value"
done
# 其他变量后台加载
(nohup load_other_vars.sh &)
这套方案在日部署频率超过20次的生产环境中验证,环境变量更新耗时从原来的8-10分钟(EB环境重建)降低到10秒内完成。对于需要动态调整配置参数的场景,比如临时切换日志级别或功能开关,效果尤为显著。