很多团队在向云迁移的过程中,都会碰到一个现实问题:本地训练脚本、调度系统、监控面板已经围绕 MLflow 搭好了整套流程,但一旦把实验追踪服务搬到 Amazon SageMaker MLflow,外部系统就得装 MLflow SDK、配 VPC 网络才能连上——这对还在过渡期的组织来说改动面太大。AWS 最近给出的方案是:在 SageMaker MLflow 前面架一层 Flask REST 代理,外部调用方只需要一个 HTTPS endpoint,不再依赖任何 SDK。
为什么不能直接暴露 SageMaker MLflow
SageMaker MLflow Server 运行在 SageMaker 管控的 VPC 内,默认只对同一 VPC 里的资源开放访问。外部要连进来,常规做法有两条路:
- 装 MLflow SDK,走
mlflow.set_tracking_uri()——这意味着每台要上报实验的机器都得装 Python 依赖、配 credential,非 Python 环境(比如 Java 微服务、Go 调度器)直接被排除。 - 搭 VPN 或 VPC Peering——网络层面的工程量不小,而且一旦打通,暴露的是整个 MLflow Server 的全部 API,权限粒度粗。
代理方案的好处在于:你只需要暴露一个 HTTPS endpoint,可以按路由做细粒度鉴权,调用方用任何语言发 HTTP 请求就行。
代理的核心设计思路
Flask 代理要解决三个问题:
- 身份认证——外部请求进来,代理负责校验 token / IAM 签名,再以合法身份转发给 SageMaker MLflow。
- 协议适配——MLflow REST API 本身就是 HTTP,代理做的是路径映射和必要的 header 改写,不需要重写业务逻辑。
- HTTPS 终结——在代理层做 TLS,内部到 SageMaker 的调用走 VPC 内网。
整体数据流:
外部调用方 → HTTPS → API Gateway / ALB → Flask Proxy → SageMaker MLflow Server
实战:搭建一个最小可用的 Flask 代理
下面给出一个可以直接改造运行的 Flask 代理示例。假设你已经有一个运行中的 SageMaker MLflow Server,它的 VPC 内地址是 https://mlflow-server.internal:5000。
代理服务代码
# app.py — Flask MLflow Proxy
import os
import requests
from flask import Flask, request, jsonify, Response
app = Flask(__name__)
# SageMaker MLflow 内网地址,从环境变量读取
MLFLOW_INTERNAL_URL = os.environ.get(
"MLFLOW_INTERNAL_URL", "https://mlflow-server.internal:5000"
)
# 简易 token 校验(生产环境建议换成 IAM 签名或 OAuth)
API_TOKEN = os.environ.get("PROXY_API_TOKEN", "change-me-in-production")
def verify_token():
"""校验请求头中的 Authorization token"""
auth = request.headers.get("Authorization", "")
if auth != f"Bearer {API_TOKEN}":
return jsonify({"error": "unauthorized"}), 401
return None
@app.route("/api/2.0/mlflow/<path:subpath>", methods=["GET", "POST", "PUT", "DELETE"])
def proxy_mlflow(subpath):
# 1. 鉴权
err = verify_token()
if err is not None:
return err
# 2. 构造转发目标 URL
target_url = f"{MLFLOW_INTERNAL_URL}/api/2.0/mlflow/{subpath}"
# 3. 复制请求头,去掉 host 和 authorization(避免泄露到内网)
fwd_headers = {
k: v for k, v in request.headers
if k.lower() not in ("host", "authorization")
}
# 4. 转发请求
resp = requests.request(
method=request.method,
url=target_url,
headers=fwd_headers,
data=request.get_data(),
params=request.args,
timeout=30,
verify=False, # 内网自签证书场景;生产环境应配 CA
)
# 5. 透传响应
excluded_headers = {"content-encoding", "transfer-encoding"}
response_headers = {
k: v for k, v in resp.headers.items() if k.lower() not in excluded_headers
}
return Response(resp.content, status=resp.status_code, headers=response_headers)
@app.route("/health", methods=["GET"])
def health():
return jsonify({"status": "ok"})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)
关键点说明:
- 路径
/api/2.0/mlflow/<path:subpath>覆盖了 MLflow REST API 的全部路由,包括runs/create、experiments/search、artifacts/list等,不需要逐个硬编码。 verify=False是内网自签证书的妥协做法,生产部署应该把 CA 证书挂进容器,改成verify="/path/to/ca-bundle.crt"。- token 校验是最简版本;在 AWS 环境下,更推荐用 API Gateway + IAM Authorizer 或者 Lambda Authorizer 做鉴权,代理层只做转发。
本地快速验证
# 安装依赖
pip install flask requests
# 设置环境变量
export MLFLOW_INTERNAL_URL="https://your-sagemaker-mlflow.internal:5000"
export PROXY_API_TOKEN="my-secret-token"
# 启动代理
python app.py
测试创建实验:
curl -X POST \
http://localhost:8080/api/2.0/mlflow/experiments/create \
-H "Authorization: Bearer my-secret-token" \
-H "Content-Type: application/json" \
-d '{"name": "proxy-test-experiment"}'
搜索实验:
curl -X GET \
"http://localhost:8080/api/2.0/mlflow/experiments/search?max_results=10" \
-H "Authorization: Bearer my-secret-token"
如果返回 JSON 和直接访问 MLflow Server 一致,代理就通了。
部署到 AWS:从本地到生产
本地跑通了,下一步是把代理放进 AWS 网络里,让外部能通过 HTTPS 安全访问。推荐架构:
外部 → Amazon API Gateway (IAM/Auth) → ALB → ECS/Fargate (Flask Proxy) → SageMaker MLflow
用 Fargate 部署代理的 Terraform 片段
# ecs_service.tf — 核心部分
resource "aws_ecs_task_definition" "mlflow_proxy" {
family = "mlflow-proxy"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = "256"
memory = "512"
container_definitions = jsonencode([
{
name = "proxy"
image = "your-registry/mlflow-proxy:latest"
essential = true
portMappings = [{ containerPort = 8080, protocol = "tcp" }]
environment = [
{ name = "MLFLOW_INTERNAL_URL", value = var.mlflow_internal_url },
{ name = "PROXY_API_TOKEN", value = var.proxy_api_token },
]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = aws_cloudwatch_log_group.mlflow_proxy.name
"awslogs-region" = var.region
}
}
}
])
}
resource "aws_ecs_service" "mlflow_proxy" {
name = "mlflow-proxy"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.mlflow_proxy.arn
launch_type = "FARGATE"
desired_count = 2
network_configuration {
subnets = var.private_subnet_ids # 和 MLflow Server 同 VPC
security_groups = [aws_security_group.proxy.id]
}
load_balancer {
target_group_arn = aws_lb_target_group.proxy.arn
container_name = "proxy"
container_port = 8080
}
}
安全组规则要严格控制:
- 入站:只允许 ALB 的安全组访问 8080 端口。
- 出站:只允许访问 SageMaker MLflow Server 的安全组(端口 5000),以及 ECR 拉镜像的端口。
API Gateway 配置要点
在 API Gateway 上挂 IAM Authorizer,这样外部调用方用 AWS SigV4 签名即可鉴权,代理层可以去掉 token 校验逻辑,只做纯转发。API Gateway 还自带 HTTPS,省掉自己管证书的负担。
需要注意的边界和取舍
| 维度 | 说明 |
|---|---|
| 延迟 | 代理多了一跳,典型增加 5-15ms。对高频上报指标的流水线要评估是否可接受 |
| 大文件上传 | MLflow artifact 上传可能涉及几百 MB 的模型文件,Flask 默认内存处理会撑爆。需要改成流式转发或直接走 S3 presigned URL |
| 超时 | MLflow 的 search_runs 在实验量大时可能返回慢,代理的 timeout 要留够余量,建议 60s+ |
| 路由覆盖度 | MLflow REST API 有几十个端点,<path:subpath> 通配能兜住大部分,但 /mlflow-artifacts/ 这类静态文件路径需要单独映射 |
| 并发 | Flask 单进程并发有限,生产部署用 gunicorn + 多 worker,或者直接换 FastAPI + uvicorn |
迁移过渡期的实操建议
如果你正在从本地 MLflow 向 SageMaker MLflow 迁移,可以按这个节奏走:
- 先搭代理,不改调用方——所有本地脚本把
tracking_uri从http://local-mlflow:5000换成代理的 HTTPS 地址,其余代码不动。 - 逐步收紧鉴权——初期用 token 快速验证连通性,稳定后切换到 IAM Authorizer。
- artifact 上传单独处理——大模型文件不走代理转发,改用 S3 presigned URL 直传,代理只负责元数据 API。
- 监控代理层——在
/health之外加上对 MLflow Server 的连通性探测,一旦内网 MLflow 异常,代理层主动返回 503 而不是超时等待。
这个方案的核心价值不是技术复杂度,而是让迁移过程中的改动面最小:外部系统不需要装 SDK、不需要改语言栈、不需要配 VPC 网络,一个 HTTPS 地址就够了。等迁移完成、所有系统都跑在 AWS 内网后,代理可以逐步退役,调用方直接切到 SageMaker MLflow 的内网地址。