用 React + Flask 反向代理嵌入 SageMaker MLflow,搭一个带 SigV4 鉴权的自定义实验追踪门户

2026-05-29 25 预计阅读时间:1 分钟
来源:aws.amazon.com AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:11 分钟

SageMaker AI 现在支持托管 MLflow Server,但默认的访问方式要么走 SageMaker Studio 内部,要么依赖 AWS IAM 策略直接暴露端点——两者都不适合直接面向团队内部的非 AWS 用户。这篇文章给出了一套完整方案:React 前端嵌入 MLflow UI,Flask 反向代理自动签注 SigV4 请求,整栈用 CDK 一键部署。下面逐层拆开来看。

整体架构:三层分工

核心思路是把"谁在访问"和"AWS 怎么认证"这两件事拆到不同层:

职责 技术
前端门户 展示 MLflow UI、团队导航、权限提示 React SPA
反向代理 拦截所有 /mlflow/* 请求,补签 SigV4,转发到 SageMaker MLflow 端点 Flask + boto3
SageMaker MLflow 官方托管,存储实验/模型数据 SageMaker AI MLflow App

前端不碰 AWS 凭证,所有签名在代理层完成。这样前端团队只需要关心 UI,后端团队只需要保证代理服务的 IAM 角色有 sagemaker:CreatePresignedMlflowTrackingServerUrl 等权限。

Flask 反向代理:SigV4 签名的关键细节

Flask 层做两件事:

  1. 接收前端请求——路径前缀 /mlflow/ 映射到 SageMaker MLflow 端点。
  2. 签注 SigV4——用 boto3 的 SigV4Auth 对每个出站请求的 header 和 query string 重新签名,然后转发。

下面是一个可改造运行的 Flask 代理核心示例:

# app.py — Flask 反向代理核心逻辑(简化版,生产环境需加错误处理与日志)
import os
import requests
from flask import Flask, request, Response
from botocore.auth import SigV4Auth
from botocore.awsrequest import AWSRequest
from boto3 import session

app = Flask(__name__)

# 从环境变量或 CDK 注入
MLFLOW_ENDPOINT = os.environ["MLFLOW_ENDPOINT"]  # 例如 https://xxxx.sagemaker.aws/mlflow
REGION = os.environ["AWS_REGION"]                # 例如 us-east-1
SERVICE = "sagemaker"

boto_session = session.Session()
credentials = boto_session.get_credentials()
sigv4 = SigV4Auth(credentials, SERVICE, REGION)


def sign_and_forward(method: str, path: str, headers: dict, body: bytes | None) -> Response:
    """对原始请求做 SigV4 签名后转发到 SageMaker MLflow 端点"""
    url = f"{MLFLOW_ENDPOINT}{path}"

    # 构造 botocore AWSRequest 以便 SigV4Auth 签注
    aws_req = AWSRequest(method=method.upper(), url=url, data=body)
    # 把前端带来的关键 header 搬过来(排除 host 等)
    for k, v in headers.items():
        if k.lower() not in ("host", "content-length", "authorization"):
            aws_req.headers[k] = v

    sigv4.add_auth(aws_req)

    # 用签注后的 header 发出真实请求
    signed_headers = dict(aws_req.headers)
    resp = requests.request(
        method=method,
        url=url,
        headers=signed_headers,
        data=body,
        stream=True,       # 流式转发,MLflow UI 有大文件下载
        allow_redirects=False,
    )

    # 逐 header 透传,但去掉 hop-by-hop header
    excluded = {"transfer-encoding", "connection", "content-encoding"}
    resp_headers = {
        k: v for k, v in resp.raw.headers.items()
        if k.lower() not in excluded
    }
    return Response(resp.content, status=resp.status_code, headers=resp_headers)


@app.route("/mlflow/<path:subpath>", methods=["GET", "POST", "PUT", "DELETE", "PATCH"])
def proxy_mlflow(subpath):
    path = f"/mlflow/{subpath}"
    if request.query_string:
        path = f"{path}?{request.query_string.decode()}"

    body = request.get_data()
    return sign_and_forward(request.method, path, dict(request.headers), body)


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8080)

运行前需要改什么:

  • MLFLOW_ENDPOINT:换成你 SageMaker MLflow App 的真实端点 URL,可从 SageMaker 控制台或 aws sagemaker describe-app 获取。
  • IAM 角色:代理服务运行角色需要 sagemaker:CreatePresignedMlflowTrackingServerUrl 和对 MLflow 端点的访问权限。
  • 生产部署不要用 app.run(),放到 ECS/Fargate 或 Lambda + API Gateway 后面。

React 前端:iframe 嵌入与路径拼接

前端门户的核心就是把 MLflow UI 嵌入 iframe,所有 MLflow 路径指向本地代理而非 AWS 直连:

// MlflowPortal.tsx — React 组件,iframe 嵌入 MLflow
import React from "react";

const PROXY_BASE = "/mlflow";  // 指向 Flask 代理的同域路径

export default function MlflowPortal() {
  // MLflow 默认首页是 /mlflow/,通过代理变成 /mlflow/(代理自己再拼上游路径)
  const mlflowUrl = `${PROXY_BASE}/`;

  return (
    <div style={{ width: "100%", height: "calc(100vh - 64px)" }}>
      {/* 顶部导航栏可放团队 logo、项目切换等 */}
      <iframe
        src={mlflowUrl}
        title="MLflow Experiment Tracking"
        style={{ width: "100%", height: "100%", border: "none" }}
        sandbox="allow-same-origin allow-scripts allow-forms"
      />
    </div>
  );
}

关键点:iframe 的 src 指向 /mlflow/,浏览器请求会先到同域 Flask 代理,代理签注后转发到 SageMaker。前端完全不碰 AWS 签名逻辑。

如果 MLflow 内部链接跳转用了绝对路径(比如 /api/2.0/mlflow/runs/search),需要在代理层做路径重写,或者在 MLflow Server 配置 --artifact-root--host 时把 base path 对齐。实际部署中,Flask 代理可以加一层 response.content 的文本替换来修正 MLflow 返回 HTML 里的硬编码路径。

CDK 部署:一栈搞定

整栈用 CDK 定义,核心资源包括:

  • SageMaker MLflow AppCfnApp 或通过 aws sagemaker create-app 创建。
  • Flask 代理:Fargate 服务 + NLB,或者 Lambda + API Gateway(轻量场景)。
  • React 前端:S3 + CloudFront(静态托管),或者同 Fargate 服务用 Nginx 混合部署。
  • IAM 角色:代理角色带 SigV4 签名权限。

下面是 CDK 核心片段(TypeScript):

// cdk-stack.ts — 关键资源定义(简化版)
import * as cdk from "aws-cdk-lib";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as ecs from "aws-cdk-lib/aws-ecs";
import * as iam from "aws-cdk-lib/aws-iam";
import * as s3 from "aws-cdk-lib/aws-s3";
import * as cloudfront from "aws-cdk-lib/aws-cloudfront";

export class MlflowPortalStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const vpc = new ec2.Vpc(this, "Vpc", { maxAzs: 2 });

    // ---- Flask 反向代理:Fargate ----
    const cluster = new ecs.Cluster(this, "Cluster", { vpc });

    const proxyRole = new iam.Role(this, "ProxyRole", {
      assumedBy: new iam.ServicePrincipal("ecs-tasks.amazonaws.com"),
    });
    proxyRole.addToPolicy(
      new iam.PolicyStatement({
        actions: [
          "sagemaker:CreatePresignedMlflowTrackingServerUrl",
          "sagemaker:DescribeMlflowTrackingServer",
        ],
        resources: ["*"],  // 生产环境应限定到具体 MLflow Server ARN
      })
    );

    const proxyTask = new ecs.FargateTaskDefinition(this, "ProxyTask", {
      taskRole: proxyRole,
    });
    proxyTask.addContainer("flask-proxy", {
      image: ecs.ContainerImage.fromAsset("./flask-proxy"),  // 本地 Dockerfile 构建
      environment: {
        MLFLOW_ENDPOINT: "https://your-mlflow-endpoint.sagemaker.aws/mlflow",
        AWS_REGION: this.region,
      },
      portMappings: [{ containerPort: 8080 }],
    });

    const proxyService = new ecs.FargateService(this, "ProxyService", {
      cluster,
      taskDefinition: proxyTask,
      publicLoadBalancer: true,  // NLB 对外暴露,生产建议加 WAF + 认证
    });

    // ---- React 前端:S3 + CloudFront ----
    const frontendBucket = new s3.Bucket(this, "FrontendBucket", {
      websiteIndexDocument: "index.html",
      publicReadAccess: true,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    const distribution = new cloudfront.Distribution(this, "FrontendDist", {
      defaultBehavior: {
        origin: new cloudfront.S3Origin(frontendBucket),
      },
      additionalBehaviors: {
        "/mlflow/*": {
          // MLflow 请求走 NLB → Flask 代理
          origin: new cloudfront.LoadBalancerOrigin(proxyService.loadBalancer),
          allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
          cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED,
        },
      },
    });
  }
}

部署流程:

# 1. 构建 Flask 代理镜像(CDK fromAsset 会自动 docker build)
cd flask-proxy && docker build -t flask-proxy . && cd ..

# 2. 构建 React 前端并上传到 S3
cd react-frontend && npm run build && cd ..
# CDK 部署后用 aws s3 sync dist/ s3://<bucket-name>/  上传静态文件

# 3. CDK 部署整栈
cdk bootstrap   # 首次需要
cdk deploy      # 等约 10-15 分钟(VPC + ECS + CloudFront)

# 4. 验证:浏览器打开 CloudFront 域名,应看到嵌入 MLflow UI 的门户

安全考量与边界

这套架构有几个必须注意的点:

  1. 代理层是唯一的信任边界——前端用户不持有 AWS 凭证,所有签名在代理完成。这意味着代理服务的 IAM 角色权限必须最小化,只给 MLflow 相关操作,不要附加 s3:* 或其他宽泛权限。

  2. CloudFront /mlflow/* 路径必须禁缓存——MLflow 的实验数据实时更新,缓存会导致 UI 显示过期数据。上面 CDK 片段已设 CACHING_DISABLED

  3. iframe sandbox 属性——allow-same-origin 是必须的(否则 MLflow 内部 AJAX 请求会被浏览器拦截),但不要加 allow-top-navigation,防止 MLflow 页面跳转劫持整个门户。

  4. MLflow 内部路径重写——如果 MLflow Server 返回的 HTML/JS 里包含指向自身端点的绝对路径,代理需要做文本替换。一种做法是在 Flask 的 sign_and_forward 里对 text/html 类型的 response body 做 MLFLOW_ENDPOINT/mlflow 的字符串替换。

  5. 清理——CDK 销毁时 cdk destroy 会删除 ECS、VPC、S3 bucket(如果设了 DESTROY removal policy)。但 SageMaker MLflow App 需要单独删除:

aws sagemaker delete-app \
  --domain-id <your-domain-id> \
  --app-type MLflowServer \
  --app-name <your-mlflow-app-name> \
  --user-profile-name <your-user-profile>

什么时候该用这套方案

场景 建议
团队全员在 SageMaker Studio 内工作 直接用 Studio 内置 MLflow,不需要代理
需要给非 AWS 用户(数据科学家、PM)看实验追踪 这套方案正好解决
已有内部门户/运维平台,想嵌入 MLflow Flask 代理可以集成到现有 Nginx/网关后面
多团队多 MLflow Server 代理层按路径前缀路由到不同端点,前端加项目切换下拉框

核心收益只有一个:让不懂 AWS 的人也能用 MLflow,同时不把 AWS 凭证暴露到前端。如果你的团队已经在 Studio 里工作,没必要加这层复杂度;但如果需要跨团队共享实验追踪面板,这套 React + Flask + SigV4 的组合是目前最干净的方案。


相关推荐