环境管理指南
本指南介绍如何在 Trae IDE 中有效管理开发环境,包括环境变量、配置管理和部署环境设置。
概述
环境管理是现代软件开发的重要组成部分。本指南涵盖:
- 环境变量管理
- 配置文件管理
- 多环境部署
- 环境隔离策略
- 安全最佳实践
环境变量管理
基本概念
环境变量是在操作系统级别定义的动态值,可以被运行的程序访问。它们用于:
- 存储配置信息
- 管理敏感数据
- 区分不同环境
- 控制应用程序行为
环境变量类型
开发环境变量
bash
# .env.development
NODE_ENV=development
API_URL=http://localhost:3001
DEBUG=true
LOG_LEVEL=debug
DATABASE_URL=postgresql://localhost:5432/myapp_dev
REDIS_URL=redis://localhost:6379测试环境变量
bash
# .env.test
NODE_ENV=test
API_URL=http://localhost:3001
DEBUG=false
LOG_LEVEL=error
DATABASE_URL=postgresql://localhost:5432/myapp_test
REDIS_URL=redis://localhost:6379生产环境变量
bash
# .env.production
NODE_ENV=production
API_URL=https://api.myapp.com
DEBUG=false
LOG_LEVEL=info
DATABASE_URL=${DATABASE_URL}
REDIS_URL=${REDIS_URL}环境变量加载
使用 dotenv
javascript
// 安装 dotenv
npm install dotenv
// 在应用程序入口点加载
require('dotenv').config();
// 或指定环境文件
require('dotenv').config({
path: `.env.${process.env.NODE_ENV || 'development'}`
});
// 使用环境变量
const config = {
port: process.env.PORT || 3000,
apiUrl: process.env.API_URL,
dbUrl: process.env.DATABASE_URL,
logLevel: process.env.LOG_LEVEL || 'info'
};环境变量验证
javascript
// config/env.js
const joi = require('joi');
const envSchema = joi.object({
NODE_ENV: joi.string()
.valid('development', 'test', 'production')
.default('development'),
PORT: joi.number().default(3000),
API_URL: joi.string().uri().required(),
DATABASE_URL: joi.string().required(),
JWT_SECRET: joi.string().min(32).required(),
LOG_LEVEL: joi.string()
.valid('error', 'warn', 'info', 'debug')
.default('info')
}).unknown();
const { error, value: envVars } = envSchema.validate(process.env);
if (error) {
throw new Error(`配置验证错误: ${error.message}`);
}
module.exports = {
env: envVars.NODE_ENV,
port: envVars.PORT,
apiUrl: envVars.API_URL,
database: {
url: envVars.DATABASE_URL
},
jwt: {
secret: envVars.JWT_SECRET
},
logging: {
level: envVars.LOG_LEVEL
}
};Trae IDE 环境变量配置
项目级环境变量
json
// .trae/environment.json
{
"environments": {
"development": {
"variables": {
"API_URL": "http://localhost:3001",
"DEBUG": "true",
"LOG_LEVEL": "debug"
}
},
"staging": {
"variables": {
"API_URL": "https://staging-api.myapp.com",
"DEBUG": "false",
"LOG_LEVEL": "info"
}
},
"production": {
"variables": {
"API_URL": "https://api.myapp.com",
"DEBUG": "false",
"LOG_LEVEL": "warn"
}
}
},
"secrets": [
"DATABASE_URL",
"JWT_SECRET",
"API_KEY"
]
}全局环境变量
json
// ~/.trae/global-env.json
{
"variables": {
"EDITOR": "trae",
"BROWSER": "chrome",
"TERM": "xterm-256color"
},
"paths": [
"/usr/local/bin",
"~/.local/bin",
"./node_modules/.bin"
]
}配置文件管理
配置文件结构
分层配置
config/
├── default.json # 默认配置
├── development.json # 开发环境配置
├── test.json # 测试环境配置
├── staging.json # 预发布环境配置
├── production.json # 生产环境配置
└── local.json # 本地覆盖配置(不提交到版本控制)默认配置
json
// config/default.json
{
"app": {
"name": "MyApp",
"version": "1.0.0",
"port": 3000
},
"database": {
"host": "localhost",
"port": 5432,
"name": "myapp",
"pool": {
"min": 2,
"max": 10
}
},
"redis": {
"host": "localhost",
"port": 6379,
"db": 0
},
"logging": {
"level": "info",
"format": "json"
},
"security": {
"jwt": {
"expiresIn": "24h"
},
"bcrypt": {
"rounds": 12
}
}
}环境特定配置
json
// config/development.json
{
"app": {
"port": 3000
},
"database": {
"name": "myapp_dev",
"logging": true
},
"logging": {
"level": "debug",
"format": "pretty"
},
"security": {
"bcrypt": {
"rounds": 1
}
}
}json
// config/production.json
{
"app": {
"port": 80
},
"database": {
"pool": {
"min": 5,
"max": 20
},
"ssl": true
},
"logging": {
"level": "warn"
},
"security": {
"jwt": {
"expiresIn": "1h"
}
}
}配置加载器
javascript
// config/index.js
const config = require('config');
const path = require('path');
class ConfigManager {
constructor() {
this.config = config;
this.environment = process.env.NODE_ENV || 'development';
}
get(key, defaultValue = null) {
try {
return this.config.get(key);
} catch (error) {
if (defaultValue !== null) {
return defaultValue;
}
throw error;
}
}
has(key) {
return this.config.has(key);
}
getAll() {
return this.config.util.toObject();
}
validate() {
const requiredKeys = [
'app.name',
'app.port',
'database.host',
'database.name'
];
const missing = requiredKeys.filter(key => !this.has(key));
if (missing.length > 0) {
throw new Error(`缺少必需的配置键: ${missing.join(', ')}`);
}
}
isDevelopment() {
return this.environment === 'development';
}
isProduction() {
return this.environment === 'production';
}
isTest() {
return this.environment === 'test';
}
}
module.exports = new ConfigManager();多环境部署
环境定义
开发环境
yaml
# docker-compose.dev.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
environment:
- NODE_ENV=development
- DEBUG=true
volumes:
- .:/app
- /app/node_modules
depends_on:
- postgres
- redis
postgres:
image: postgres:13
environment:
- POSTGRES_DB=myapp_dev
- POSTGRES_USER=dev
- POSTGRES_PASSWORD=dev123
ports:
- "5432:5432"
volumes:
- postgres_dev_data:/var/lib/postgresql/data
redis:
image: redis:6-alpine
ports:
- "6379:6379"
volumes:
postgres_dev_data:测试环境
yaml
# docker-compose.test.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile.test
environment:
- NODE_ENV=test
- DATABASE_URL=postgresql://test:test123@postgres:5432/myapp_test
depends_on:
- postgres
postgres:
image: postgres:13
environment:
- POSTGRES_DB=myapp_test
- POSTGRES_USER=test
- POSTGRES_PASSWORD=test123
tmpfs:
- /var/lib/postgresql/data生产环境
yaml
# docker-compose.prod.yml
version: '3.8'
services:
app:
image: myapp:latest
ports:
- "80:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=${DATABASE_URL}
- REDIS_URL=${REDIS_URL}
- JWT_SECRET=${JWT_SECRET}
restart: unless-stopped
depends_on:
- postgres
- redis
postgres:
image: postgres:13
environment:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- postgres_prod_data:/var/lib/postgresql/data
restart: unless-stopped
redis:
image: redis:6-alpine
restart: unless-stopped
volumes:
postgres_prod_data:部署脚本
环境部署脚本
bash
#!/bin/bash
# deploy.sh
set -e
ENVIRONMENT=${1:-development}
VERSION=${2:-latest}
echo "正在部署到 $ENVIRONMENT 环境..."
case $ENVIRONMENT in
"development")
echo "启动开发环境..."
docker-compose -f docker-compose.dev.yml up -d
;;
"test")
echo "运行测试..."
docker-compose -f docker-compose.test.yml up --abort-on-container-exit
docker-compose -f docker-compose.test.yml down
;;
"staging")
echo "部署到预发布环境..."
docker-compose -f docker-compose.staging.yml up -d
;;
"production")
echo "部署到生产环境..."
docker-compose -f docker-compose.prod.yml up -d
;;
*)
echo "未知环境: $ENVIRONMENT"
echo "用法: $0 [development|test|staging|production] [version]"
exit 1
;;
esac
echo "部署到 $ENVIRONMENT 完成!"环境切换脚本
bash
#!/bin/bash
# switch-env.sh
ENVIRONMENT=$1
if [ -z "$ENVIRONMENT" ]; then
echo "用法: $0 <environment>"
echo "可用环境: development, test, staging, production"
exit 1
fi
# 检查环境文件是否存在
if [ ! -f ".env.$ENVIRONMENT" ]; then
echo "环境文件 .env.$ENVIRONMENT 未找到!"
exit 1
fi
# 备份当前环境文件
if [ -f ".env" ]; then
cp .env .env.backup
fi
# 切换到新环境
cp ".env.$ENVIRONMENT" .env
echo "已切换到 $ENVIRONMENT 环境"
echo "当前环境变量:"
cat .env环境隔离策略
数据库隔离
数据库命名约定
javascript
// config/database.js
const environments = {
development: {
database: 'myapp_development',
username: 'dev_user',
password: 'dev_password',
host: 'localhost',
port: 5432,
dialect: 'postgres',
logging: console.log
},
test: {
database: 'myapp_test',
username: 'test_user',
password: 'test_password',
host: 'localhost',
port: 5432,
dialect: 'postgres',
logging: false
},
staging: {
database: 'myapp_staging',
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
host: process.env.DB_HOST,
port: process.env.DB_PORT,
dialect: 'postgres',
logging: false
},
production: {
database: 'myapp_production',
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
host: process.env.DB_HOST,
port: process.env.DB_PORT,
dialect: 'postgres',
logging: false,
pool: {
max: 20,
min: 5,
acquire: 30000,
idle: 10000
}
}
};
module.exports = environments[process.env.NODE_ENV || 'development'];服务隔离
Docker 网络隔离
yaml
# docker-compose.yml
version: '3.8'
networks:
development:
driver: bridge
staging:
driver: bridge
production:
driver: bridge
services:
app-dev:
build: .
networks:
- development
environment:
- NODE_ENV=development
app-staging:
build: .
networks:
- staging
environment:
- NODE_ENV=staging
app-prod:
build: .
networks:
- production
environment:
- NODE_ENV=production资源隔离
Kubernetes 命名空间
yaml
# k8s/namespaces.yml
apiVersion: v1
kind: Namespace
metadata:
name: myapp-development
---
apiVersion: v1
kind: Namespace
metadata:
name: myapp-staging
---
apiVersion: v1
kind: Namespace
metadata:
name: myapp-productionyaml
# k8s/deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: myapp-production
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:latest
env:
- name: NODE_ENV
value: "production"
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: myapp-secrets
key: database-url
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"安全最佳实践
敏感信息管理
使用密钥管理服务
javascript
// utils/secrets.js
const AWS = require('aws-sdk');
const secretsManager = new AWS.SecretsManager({
region: process.env.AWS_REGION || 'us-east-1'
});
class SecretsManager {
async getSecret(secretName) {
try {
const result = await secretsManager.getSecretValue({
SecretId: secretName
}).promise();
return JSON.parse(result.SecretString);
} catch (error) {
console.error(`获取密钥 ${secretName} 失败:`, error);
throw error;
}
}
async getDatabaseCredentials() {
const secrets = await this.getSecret('myapp/database');
return {
host: secrets.host,
port: secrets.port,
username: secrets.username,
password: secrets.password,
database: secrets.database
};
}
async getJWTSecret() {
const secrets = await this.getSecret('myapp/jwt');
return secrets.secret;
}
}
module.exports = new SecretsManager();环境变量加密
bash
# 使用 sops 加密环境变量
# 安装 sops
brew install sops
# 创建加密的环境文件
sops -e .env.production > .env.production.encrypted
# 解密环境文件
sops -d .env.production.encrypted > .env.productionyaml
# .sops.yaml
creation_rules:
- path_regex: \.env\.production$
kms: 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012'
- path_regex: \.env\.staging$
kms: 'arn:aws:kms:us-east-1:123456789012:key/87654321-4321-4321-4321-210987654321'访问控制
基于角色的访问控制
javascript
// middleware/rbac.js
const roles = {
developer: {
environments: ['development', 'test'],
permissions: ['read', 'write', 'deploy']
},
tester: {
environments: ['test', 'staging'],
permissions: ['read', 'deploy']
},
devops: {
environments: ['development', 'test', 'staging', 'production'],
permissions: ['read', 'write', 'deploy', 'admin']
},
admin: {
environments: ['development', 'test', 'staging', 'production'],
permissions: ['read', 'write', 'deploy', 'admin', 'manage_users']
}
};
function checkEnvironmentAccess(userRole, environment) {
const role = roles[userRole];
if (!role) {
throw new Error('无效的用户角色');
}
return role.environments.includes(environment);
}
function checkPermission(userRole, permission) {
const role = roles[userRole];
if (!role) {
throw new Error('无效的用户角色');
}
return role.permissions.includes(permission);
}
module.exports = {
checkEnvironmentAccess,
checkPermission
};审计日志
javascript
// utils/audit.js
const winston = require('winston');
const auditLogger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({
filename: 'logs/audit.log',
maxsize: 10485760, // 10MB
maxFiles: 5
}),
new winston.transports.Console()
]
});
function logEnvironmentAccess(user, environment, action, result) {
auditLogger.info('环境访问', {
user: user.id,
username: user.username,
environment,
action,
result,
timestamp: new Date().toISOString(),
ip: user.ip,
userAgent: user.userAgent
});
}
function logConfigurationChange(user, environment, changes) {
auditLogger.info('配置变更', {
user: user.id,
username: user.username,
environment,
changes,
timestamp: new Date().toISOString()
});
}
module.exports = {
logEnvironmentAccess,
logConfigurationChange
};环境监控
健康检查
javascript
// routes/health.js
const express = require('express');
const router = express.Router();
const config = require('../config');
// 基本健康检查
router.get('/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
environment: config.get('app.env'),
version: config.get('app.version')
});
});
// 详细健康检查
router.get('/health/detailed', async (req, res) => {
const checks = {
database: await checkDatabase(),
redis: await checkRedis(),
external_api: await checkExternalAPI(),
disk_space: await checkDiskSpace(),
memory: checkMemoryUsage()
};
const allHealthy = Object.values(checks).every(check => check.status === 'healthy');
res.status(allHealthy ? 200 : 503).json({
status: allHealthy ? 'healthy' : 'unhealthy',
timestamp: new Date().toISOString(),
environment: config.get('app.env'),
checks
});
});
async function checkDatabase() {
try {
// 数据库连接检查
await db.raw('SELECT 1');
return { status: 'healthy', response_time: '< 100ms' };
} catch (error) {
return { status: 'unhealthy', error: error.message };
}
}
async function checkRedis() {
try {
// Redis 连接检查
await redis.ping();
return { status: 'healthy', response_time: '< 50ms' };
} catch (error) {
return { status: 'unhealthy', error: error.message };
}
}
function checkMemoryUsage() {
const used = process.memoryUsage();
const total = used.heapTotal;
const usage = (used.heapUsed / total) * 100;
return {
status: usage < 90 ? 'healthy' : 'warning',
heap_used: `${Math.round(used.heapUsed / 1024 / 1024)} MB`,
heap_total: `${Math.round(total / 1024 / 1024)} MB`,
usage_percentage: `${Math.round(usage)}%`
};
}
module.exports = router;环境指标收集
javascript
// utils/metrics.js
const prometheus = require('prom-client');
// 创建指标收集器
const register = new prometheus.Registry();
// 环境特定指标
const environmentInfo = new prometheus.Gauge({
name: 'app_environment_info',
help: 'Application environment information',
labelNames: ['environment', 'version', 'node_version'],
registers: [register]
});
// 请求计数器
const httpRequestsTotal = new prometheus.Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status_code', 'environment'],
registers: [register]
});
// 响应时间直方图
const httpRequestDuration = new prometheus.Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'environment'],
buckets: [0.1, 0.5, 1, 2, 5],
registers: [register]
});
// 数据库连接池指标
const dbConnectionsActive = new prometheus.Gauge({
name: 'db_connections_active',
help: 'Number of active database connections',
labelNames: ['environment'],
registers: [register]
});
// 初始化环境信息
environmentInfo.set(
{
environment: process.env.NODE_ENV,
version: process.env.APP_VERSION,
node_version: process.version
},
1
);
// 中间件:记录 HTTP 请求指标
function metricsMiddleware(req, res, next) {
const start = Date.now();
res.on('finish', () => {
const duration = (Date.now() - start) / 1000;
const environment = process.env.NODE_ENV;
httpRequestsTotal.inc({
method: req.method,
route: req.route?.path || req.path,
status_code: res.statusCode,
environment
});
httpRequestDuration.observe(
{
method: req.method,
route: req.route?.path || req.path,
environment
},
duration
);
});
next();
}
// 导出指标
function getMetrics() {
return register.metrics();
}
module.exports = {
register,
metricsMiddleware,
getMetrics,
dbConnectionsActive
};故障排除
常见问题
环境变量未加载
javascript
// 调试环境变量加载
console.log('NODE_ENV:', process.env.NODE_ENV);
console.log('All environment variables:', process.env);
// 检查 .env 文件是否存在
const fs = require('fs');
const path = require('path');
const envFile = path.join(process.cwd(), '.env');
if (fs.existsSync(envFile)) {
console.log('.env 文件存在');
console.log('内容:', fs.readFileSync(envFile, 'utf8'));
} else {
console.log('.env 文件未找到');
}配置文件冲突
javascript
// 调试配置加载顺序
const config = require('config');
console.log('Config sources:', config.util.getConfigSources());
console.log('Final config:', config.util.toObject());数据库连接问题
javascript
// 数据库连接诊断
async function diagnoseDatabaseConnection() {
const config = require('./config/database');
console.log('Database config:', {
host: config.host,
port: config.port,
database: config.database,
username: config.username
// 不要打印密码
});
try {
const { Client } = require('pg');
const client = new Client(config);
await client.connect();
console.log('数据库连接成功');
const result = await client.query('SELECT version()');
console.log('Database version:', result.rows[0].version);
await client.end();
} catch (error) {
console.error('数据库连接失败:', error.message);
console.error('Error details:', error);
}
}
diagnoseDatabaseConnection();环境同步工具
bash
#!/bin/bash
# sync-env.sh - 环境同步脚本
SOURCE_ENV=$1
TARGET_ENV=$2
if [ -z "$SOURCE_ENV" ] || [ -z "$TARGET_ENV" ]; then
echo "用法: $0 <source-env> <target-env>"
echo "示例: $0 staging production"
exit 1
fi
echo "正在从 $SOURCE_ENV 同步环境到 $TARGET_ENV..."
# 备份目标环境
cp ".env.$TARGET_ENV" ".env.$TARGET_ENV.backup.$(date +%Y%m%d_%H%M%S)"
# 复制源环境到目标环境
cp ".env.$SOURCE_ENV" ".env.$TARGET_ENV"
# 更新环境特定的变量
sed -i "s/NODE_ENV=$SOURCE_ENV/NODE_ENV=$TARGET_ENV/g" ".env.$TARGET_ENV"
echo "环境同步完成!"
echo "请检查 .env.$TARGET_ENV 并更新环境特定的值。"最佳实践总结
环境变量管理
- 使用 .env 文件:为每个环境创建单独的 .env 文件
- 验证配置:在应用启动时验证必需的环境变量
- 敏感信息保护:使用密钥管理服务存储敏感信息
- 文档化:维护环境变量的文档和示例
配置管理
- 分层配置:使用默认配置和环境特定覆盖
- 配置验证:实施配置模式验证
- 版本控制:将配置文件纳入版本控制(除敏感信息外)
- 配置即代码:将基础设施配置作为代码管理
环境隔离
- 物理隔离:使用不同的服务器或容器
- 网络隔离:实施网络分段和访问控制
- 数据隔离:为每个环境使用独立的数据库
- 资源隔离:设置适当的资源限制和配额
安全实践
- 最小权限原则:只授予必要的访问权限
- 定期轮换:定期更新密钥和凭证
- 审计日志:记录所有环境访问和更改
- 加密传输:使用 HTTPS/TLS 加密所有通信