17、配置管理:配置文件设计(YAML/JSON)、环境变量管理、多环境切换、敏感信息加密
做市系统跑起来,第一件事不是写策略,而是配参数。
我见过太多团队,代码写得漂漂亮亮,结果配置文件一团糟。测试环境连生产库,生产环境打印调试日志……这种事故,说白了就是配置管理没做好。
今天咱们聊聊,怎么把配置这件事,做得像瑞士钟表一样精准。
17.1 配置文件格式:YAML vs JSON
做市系统的配置,我首选YAML。为什么?
你看,一个典型的做市策略配置,包含嵌套结构、列表、键值对。JSON写出来满眼都是花括号和引号,人眼扫过去容易疲劳。YAML用缩进表示层级,清爽多了。
我的经验法则:
- YAML:适合人工编写和阅读的配置文件
- JSON:适合程序生成、跨语言传递的配置
- TOML:如果你喜欢INI风格的简洁,也可以考虑
来看个实际例子。这是做市系统的核心配置:
# config.yaml
exchange:
name: binance
api_endpoint: https://api.binance.com
ws_endpoint: wss://stream.binance.com:9443
timeout_ms: 5000
retry_count: 3
strategy:
name: grid_market_making
symbol: BTCUSDT
spread_bps: 5 # 价差,单位基点
order_size_btc: 0.1 # 每单数量
max_position_btc: 2.0 # 最大持仓
rebalance_interval_s: 60
risk:
max_drawdown_pct: 5.0
daily_loss_limit_usdt: 1000
circuit_breaker: true
logging:
level: INFO
file: /var/log/market_maker.log
rotation: daily
换成JSON,你感受一下:
{
"exchange": {
"name": "binance",
"api_endpoint": "https://api.binance.com",
"ws_endpoint": "wss://stream.binance.com:9443",
"timeout_ms": 5000,
"retry_count": 3
},
"strategy": {
"name": "grid_market_making",
"symbol": "BTCUSDT",
"spread_bps": 5,
"order_size_btc": 0.1,
"max_position_btc": 2.0,
"rebalance_interval_s": 60
}
}
嗯,JSON也不是不能用。但如果你需要写注释,JSON就尴尬了——它原生不支持注释。YAML可以用#随意加注释,这对运维同学太友好了。
小技巧:我习惯在YAML里用锚点(&)和别名(*)来复用配置。比如多个币种共享相同的交易所参数,写一次就够了。
17.2 环境变量管理
配置文件里有些东西不能写死。比如API密钥、数据库密码、不同环境的IP地址。
这些我统统交给环境变量。
为什么?因为环境变量是操作系统级别的配置,不会被误提交到Git仓库。你想想看,要是把生产环境的API密钥写进配置文件,一不小心push到GitHub……那画面太美我不敢看。
我的做法是这样的:
- 默认值从配置文件读取:开发环境用默认值,省事
- 环境变量覆盖:生产环境通过环境变量传入敏感信息
- 优先级:环境变量 > 配置文件 > 代码默认值
代码实现很简单:
import os
import yaml
def load_config():
# 1. 加载YAML配置文件
with open('config.yaml', 'r') as f:
config = yaml.safe_load(f)
# 2. 环境变量覆盖
config['exchange']['api_key'] = os.getenv(
'EXCHANGE_API_KEY',
config.get('exchange', {}).get('api_key', '')
)
config['exchange']['api_secret'] = os.getenv(
'EXCHANGE_API_SECRET',
config.get('exchange', {}).get('api_secret', '')
)
# 3. 数据库连接串
config['database']['url'] = os.getenv(
'DATABASE_URL',
'sqlite:///dev.db' # 开发环境默认用SQLite
)
return config
注意:环境变量也不是绝对安全。在共享服务器上,ps aux 可能会泄露环境变量。更敏感的信息,请看后面的加密方案。
17.3 多环境切换
做市系统至少需要三个环境:
| 环境 | 用途 | 特点 |
|---|---|---|
| dev | 本地开发调试 | 模拟盘、SQLite、DEBUG日志 |
| staging | 集成测试 | 测试网、小资金、INFO日志 |
| production | 实盘运行 | 主网、大资金、WARNING日志 |
我习惯用APP_ENV这个环境变量来切换。代码里这样写:
import os
ENV = os.getenv('APP_ENV', 'dev')
# 根据环境加载不同配置文件
config_file = f'config.{ENV}.yaml'
with open(config_file, 'r') as f:
config = yaml.safe_load(f)
目录结构大概是:
config/
├── config.dev.yaml # 开发环境
├── config.staging.yaml # 测试环境
├── config.production.yaml # 生产环境
└── config.schema.yaml # 配置模板(带注释)
我曾经犯过一个错:三个环境的配置文件内容差异很大,导致切换环境时总有些参数忘记改。后来我学乖了——用config.schema.yaml作为模板,每个环境只覆盖差异部分。
推荐做法:用deep_merge函数,把环境配置和基础配置合并。基础配置放公共参数,环境配置只放差异项。
17.4 敏感信息加密
API密钥、私钥、数据库密码……这些是做市系统的命根子。
明文存储?绝对不行。环境变量?比明文好,但还不够。
我推荐的做法是:加密存储 + 运行时解密。
方案一:使用Python的cryptography库
from cryptography.fernet import Fernet
# 生成密钥(仅一次)
key = Fernet.generate_key()
cipher = Fernet(key)
# 加密
encrypted = cipher.encrypt(b"my_api_secret_123")
print(encrypted) # 保存这个加密后的字符串
# 解密
decrypted = cipher.decrypt(encrypted)
print(decrypted.decode()) # 输出: my_api_secret_123
密钥本身存在哪里?我通常放在环境变量CONFIG_ENCRYPTION_KEY里。或者用KMS(密钥管理服务),比如AWS KMS、HashiCorp Vault。
方案二:使用Hashicorp Vault
如果你的团队规模比较大,直接上Vault吧。它提供:
- 动态密钥(密钥定期轮换)
- 访问审计(谁什么时候读取了哪个密钥)
- 租约机制(密钥过期自动失效)
代码集成也很简单:
import hvac
client = hvac.Client(url='https://vault.example.com', token=os.getenv('VAULT_TOKEN'))
secret = client.secrets.kv.v2.read_secret_version(path='market-maker/api-key')
api_key = secret['data']['data']['api_key']
避坑指南:我曾经把加密密钥和加密后的密文放在同一个配置文件里……这相当于把钥匙和锁放在一起。密钥必须独立存储,最好由不同的人管理。
17.5 配置校验
配置加载进来,不能直接用。万一有人把spread_bps写成了负数呢?
我习惯用Pydantic做配置校验:
from pydantic import BaseModel, Field, validator
from typing import Optional
class ExchangeConfig(BaseModel):
name: str
api_endpoint: str
timeout_ms: int = Field(ge=100, le=30000) # 100ms ~ 30s
retry_count: int = Field(ge=0, le=10)
class StrategyConfig(BaseModel):
name: str
symbol: str
spread_bps: int = Field(ge=1, le=100) # 1~100基点
order_size_btc: float = Field(gt=0)
max_position_btc: float = Field(gt=0)
class AppConfig(BaseModel):
exchange: ExchangeConfig
strategy: StrategyConfig
logging: dict
# 使用
raw_config = load_config()
validated_config = AppConfig(**raw_config)
print("配置校验通过!")
这样,任何非法配置在启动时就会报错,而不是等到运行时炸了才发现。
17.6 知识体系总览
下面这张图,概括了配置管理的核心逻辑:
配置管理这件事,说难不难,说简单也不简单。核心就三点:分层(不同环境不同配置)、加密(敏感信息不裸奔)、校验(错误尽早暴露)。
把这三点做好了,你的做市系统至少不会因为配置问题在半夜炸掉。嗯,我当年可是被这种问题折腾过好几回,现在想想都心疼那几杯咖啡钱。
总结一下今天的要点:
- YAML适合人工编写,JSON适合程序处理
- 环境变量覆盖配置文件,优先级明确
- 多环境用
APP_ENV切换,配置模板化 - 敏感信息加密存储,密钥独立管理
- 配置加载后必须校验,用Pydantic最省心