未记录事实核验 · 状态:维护中
记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。
warning 适用范围与待维护事项
本文适用于已掌握 Airflow DAG、Operator 和基础连接概念的读者,用于比较配置存放方式并完成练习,不是可直接复制的生产密钥部署方案。Vault 与 AWS Secrets Manager 示例须按所用 Airflow、Provider 版本及实际认证方式验证;示例主机、账号、密码和 token 均为占位材料,不代表已配置安全的凭据注入。生产使用前还须检查日志与环境变量的暴露范围、SQL 中动态表名的可信度,以及数据库写入语义。
变量连接与配置管理
abstract 本文核心内容
- Variable:管理全局配置参数
- Connection:安全管理数据源连接
- Secrets Backend:对接 HashiCorp Vault、AWS Secrets Manager 等密钥管理系统
- 环境变量:最轻量的配置方式
- 配置管理最佳实践
为什么需要集中化的配置管理
不好的做法(硬编码在 DAG 文件里):
# 危险!密码硬写在代码里,会被 git 记录,有安全风险
conn = mysql.connect(
host="192.168.1.100",
user="etl_user",
password="my_secret_password_123", # 绝对不要这样做
database="business_db"
)
好的做法(通过 Airflow 的配置管理):
# 安全:Connection 信息存在 MetaDB 中(可加密),代码里只有连接 ID
from airflow.hooks.base import BaseHook
conn_info = BaseHook.get_connection("mysql_prod")
Variable:全局配置参数
Variable 适合存储非敏感的配置信息,可以在 Web UI 中直接修改,不需要改代码。
创建和管理 Variable
方式 1:Web UI 创建
Admin → Variables → 点击 "+" 按钮,填写 Key 和 Value
方式 2:命令行创建
# 创建简单字符串变量
airflow variables set target_env "production"
# 创建 JSON 变量
airflow variables set report_config '{"to_email": ["alice@company.com"], "cc": ["bob@company.com"]}'
# 从文件导入变量(批量导入)
airflow variables import variables.json
# 查看变量
airflow variables get target_env
airflow variables list
variables.json 示例:
{
"target_env": "production",
"dw_database": "bigdata_dw",
"ods_table_prefix": "ods_",
"dws_table_prefix": "dws_",
"alert_email_list": ["data-team@company.com", "manager@company.com"],
"data_retention_days": 90,
"spark_queue": "etl_high_priority"
}
在 DAG 代码中读取 Variable
from airflow.models import Variable
# 读取字符串变量
env = Variable.get("target_env")
print(f"运行环境:{env}")
# 读取 JSON 变量(自动反序列化)
email_list = Variable.get("alert_email_list", deserialize_json=True)
# email_list = ["data-team@company.com", "manager@company.com"]
# 读取带默认值(变量不存在时不报错)
retention_days = Variable.get("data_retention_days", default_var=30)
# 读取 JSON 的某个字段
config = Variable.get("report_config", deserialize_json=True)
to_email = config.get("to_email", [])
在 Operator 参数的模板中使用 Variable
# 在 bash_command 的 Jinja 模板中读取 Variable
extract_task = BashOperator(
task_id="extract_data",
bash_command="""
python extract.py \
--env {{ var.value.target_env }} \
--database {{ var.value.dw_database }} \
--date {{ ds }}
""",
)
# 读取 JSON 变量的字段
query_task = SQLExecuteQueryOperator(
task_id="query_data",
conn_id="mysql_prod",
sql="SELECT * FROM {{ var.value.ods_table_prefix }}orders WHERE dt = '{{ ds }}'",
)
warning Variable 的性能注意事项
在 DAG 顶层(文件解析时)直接调用 Variable.get(),会在每次解析时触发变量查找;具体是否访问 MetaDB,取决于环境变量、Secrets Backend 和缓存等配置。
建议:在函数内部(Task 执行时)读取 Variable,而不是在 DAG 文件的顶层。
# 不推荐:在文件顶层读取(解析时会触发变量查找,是否访问 MetaDB 取决于后端与缓存配置)
TARGET_TABLE = Variable.get("target_table") # 不要这样做
# 正确:在函数内部读取(只在 Task 执行时才查询)
def run_etl(**context):
target_table = Variable.get("target_table") # 在这里读取
# ...
Connection:数据源连接管理
Connection 用于存储数据库、API、存储系统的连接信息,包含主机、端口、用户名、密码等。
创建 Connection
方式 1:Web UI 创建
Admin → Connections → 点击 "+"
| 字段 | MySQL 示例 | PostgreSQL 示例 |
|---|---|---|
| Conn Id | mysql_prod |
postgres_dw |
| Conn Type | MySQL | Postgres |
| Host | 192.168.1.100 | 192.168.1.101 |
| Schema | business_db | data_warehouse |
| Login | etl_user | dw_reader |
| Password | ******** | ******** |
| Port | 3306 | 5432 |
方式 2:环境变量创建(推荐生产环境)
# 格式:AIRFLOW_CONN_{CONN_ID}=conn_type://user:password@host:port/schema
# 环境变量名通常将 conn_id 转为大写,并保留下划线
export AIRFLOW_CONN_MYSQL_PROD="mysql://etl_user:my_password@192.168.1.100:3306/business_db"
export AIRFLOW_CONN_POSTGRES_DW="postgresql://dw_reader:password@192.168.1.101:5432/data_warehouse"
export AIRFLOW_CONN_HIVE_DEFAULT="hive://user@hiveserver2:10000/default"
# JSON 格式(更灵活,支持 extra 参数)
export AIRFLOW_CONN_SPARK_DEFAULT='{
"conn_type": "spark",
"host": "spark://spark-master:7077",
"extra": "{\"deploy-mode\": \"cluster\", \"spark-home\": \"/opt/spark\"}"
}'
方式 3:命令行创建
airflow connections add mysql_prod \
--conn-type mysql \
--conn-host 192.168.1.100 \
--conn-login etl_user \
--conn-password "my_password" \
--conn-schema business_db \
--conn-port 3306
# 查看连接列表
airflow connections list
# 查看某个连接;输出可能涉及敏感信息,勿直接写入共享日志
airflow connections get mysql_prod
在 DAG 中使用 Connection
方式 1:在 Operator 中直接引用 conn_id
# 大多数 Provider Operator 直接接受 conn_id
from airflow.providers.common.sql.operators.sql import SQLExecuteQueryOperator
from airflow.providers.apache.hive.operators.hive import HiveOperator
sql_task = SQLExecuteQueryOperator(
task_id="query_mysql",
conn_id="mysql_prod", # 直接引用 Connection ID
sql="SELECT COUNT(*) FROM orders WHERE dt = '{{ ds }}'",
)
hive_task = HiveOperator(
task_id="run_hql",
hive_cli_conn_id="hive_default",
hql="SELECT ...",
)
方式 2:通过 Hook 获取连接对象
from airflow.hooks.base import BaseHook
from sqlalchemy import create_engine
def run_custom_query(**context):
# 获取 Connection 对象
conn = BaseHook.get_connection("mysql_prod")
# 用 Hook 构建引擎,避免自行拼接含密码的 URL
from airflow.providers.mysql.hooks.mysql import MySqlHook
from sqlalchemy import text
engine = MySqlHook(mysql_conn_id="mysql_prod").get_sqlalchemy_engine()
with engine.connect() as connection:
result = connection.execute(text("SELECT COUNT(*) FROM orders"))
count = result.scalar()
print(f"订单数:{count}")
return count
方式 3:使用 Hook(推荐,封装了连接细节)
from airflow.providers.mysql.hooks.mysql import MySqlHook
from airflow.providers.postgres.hooks.postgres import PostgresHook
def query_with_hook(**context):
# MySqlHook 封装了连接管理
mysql_hook = MySqlHook(mysql_conn_id="mysql_prod")
# 直接执行查询,返回 pandas DataFrame
df = mysql_hook.get_pandas_df(
sql="SELECT * FROM orders WHERE dt = %(date)s",
parameters={"date": context["ds"]}
)
print(f"查询到 {len(df)} 行数据")
# 向 PostgreSQL 写入数据
pg_hook = PostgresHook(postgres_conn_id="postgres_dw")
pg_hook.insert_rows(
table="ods_orders",
rows=df.values.tolist(),
target_fields=df.columns.tolist(),
replace=False # 示例仅演示插入;需要更新已有记录时须另行设计写入语义
)
Secrets Backend:对接企业密钥管理
在企业环境中,通常有统一的密钥管理系统(如 HashiCorp Vault、AWS Secrets Manager),Airflow 支持从这些系统读取 Variable 和 Connection。
配置 HashiCorp Vault
# airflow.cfg
[secrets]
backend = airflow.providers.hashicorp.secrets.vault.VaultBackend
backend_kwargs = {
"connections_path": "airflow/connections",
"variables_path": "airflow/variables",
"url": "http://vault:8200",
"auth_type": "token",
"token": "仅作示例的占位值,勿在配置文件中填写真实 token"
}
在 Vault 中存储 Connection:
# Vault 中的 KV 结构
vault kv put airflow/connections/mysql_prod \
conn_type="mysql" \
host="192.168.1.100" \
login="etl_user" \
password="my_secret" \
schema="business_db" \
port=3306
配置 AWS Secrets Manager
# airflow.cfg
[secrets]
backend = airflow.providers.amazon.aws.secrets.secrets_manager.SecretsManagerBackend
backend_kwargs = {
"connections_prefix": "airflow/connections",
"variables_prefix": "airflow/variables",
"profile_name": "default"
}
Secrets 查找优先级
在未启用 worker 专用 Secrets Backend 的常见配置中,Variable 和 Connection 的查找顺序如下;支持并配置 worker 专用 Secrets Backend 的版本,在 worker 中还会优先查找该后端,须按实际版本与运行组件核对:
1. 配置的自定义 Secrets Backend(Vault、AWS SM 等)
2. 环境变量(AIRFLOW_CONN_xxx / AIRFLOW_VAR_xxx)
3. MetaDB(Web UI 中配置的)
tip 生产环境密钥管理建议
对于不同类型的配置采用不同策略:
- 非敏感业务配置(表名、路径等)→ Variable(MetaDB 存储,Web UI 可见)
- 数据库连接(含密码)→ Secrets Backend 或环境变量
- 永远不要把密码写在 DAG 代码里,也不要 commit 到 git
环境变量:最轻量的配置方式
对于简单场景,直接用 OS 环境变量也是一种方式:
# 在 docker-compose.yaml 或系统环境中设置
export DB_HOST="192.168.1.100"
export DB_PASSWORD="my_password"
import os
def run_task(**context):
# 从环境变量读取
db_host = os.environ.get("DB_HOST", "localhost")
db_password = os.environ.get("DB_PASSWORD")
if not db_password:
raise ValueError("DB_PASSWORD 环境变量未设置")
在 Docker Compose 中注入环境变量:
# docker-compose.override.yaml
services:
airflow-worker:
environment:
- DB_HOST=192.168.1.100
- DB_PASSWORD=${DB_PASSWORD} # 从 .env 文件读取
配置管理最佳实践总结
| 配置类型 | 推荐方案 | 原因 |
|---|---|---|
| 非敏感业务配置 | Airflow Variable | Web UI 可修改,不用改代码 |
| 数据库连接(含密码) | Connection + Secrets Backend | 凭据不进代码;具体加密与访问控制取决于存储后端及其配置 |
| 环境区分(dev/prod) | 环境变量或不同的 conn_id | 避免测试和生产混用 |
| API Key、Token | Secrets Backend | 便于集中管理密钥;合规性仍取决于具体配置和组织要求 |
| Spark 配置参数 | Variable | 调优时不需要改代码 |
| 邮件地址列表 | Variable(JSON 格式) | 灵活调整,不需要发版 |
小结
- Variable:非敏感配置的首选,Web UI 可管理;注意不要在 DAG 文件顶层读取(影响解析性能)
- Connection:数据源连接的标准方式;MetaDB 中密码的加密依赖正确配置 Fernet,其他后端的保护机制取决于其配置;生产环境可通过环境变量或 Secrets Backend 注入,并检查凭据暴露范围
- Secrets Backend:企业级密钥管理,对接 Vault/AWS SM;中大型团队的正确方案
- 永远不要把密码硬编码在 DAG 文件或 git 仓库中
下一篇:09-监控告警与故障处理 — Web UI 使用、任务重试、告警通知和故障排查
所属系列:00-Airflow学习路线图 | 上一篇:07-调度策略与时间管理 | 下一篇:09-监控告警与故障处理
#Airflow #Variable #Connection #Secrets #配置管理 #安全
info 前置知识
- 理解 Airflow Connection 和 Variable 的基本概念,参考 Airflow 核心概念
- 了解环境变量(
os.environ)在 Python 中的使用方式 - 知道 SQLExecuteQueryOperator 通过
conn_id引用连接,参考 常用 Operator 详解
掌握检查
- [ ] 能在 Web UI 中创建一个 MySQL Connection,并在 PythonOperator 中通过
BaseHook.get_connection()读取它 - [ ] 能解释为什么不应该在 DAG 文件顶层调用
Variable.get(),而应该在 Task 函数内部调用 - [ ] 知道未启用 worker 专用 Secrets Backend 时的查找优先级:自定义 Secrets Backend > 环境变量 > MetaDB;启用 worker 专用后端时,能按实际版本核对 worker 中的查找顺序
- [ ] 能用环境变量覆盖
airflow.cfg配置(AIRFLOW__SECTION__KEY格式),举出至少 2 个实际例子 - [ ] 理解 Fernet Key 的作用——它用于加密 MetaDB 中受支持的敏感字段,如 Connection 密码,而不是加密 DAG 文件
实践练习
基础题:在 Airflow Web UI 中创建一个 Variable batch_size,值为 1000;然后在 DAG 的 PythonOperator 中读取这个变量,并打印出来。修改 Variable 值后不改代码重新触发 DAG,确认读取到了新值。
进阶题:如果你的公司要求数据库密码不能存在 Airflow MetaDB 中(避免被数据库管理员直接查到),应该怎么处理?列出至少两种方案,并说明各自的适用场景(提示:环境变量 vs Secrets Backend)。