Flink External Resource Framework让作业“原生”申请 GPU/FPGA 等外部资源

1. 外部资源框架到底做了什么

整体就两件事:

1)改写资源请求(Resource Request)

  • 你在 Flink 配置里声明要什么资源、要多少
  • Flink 会把这些外部资源需求映射进底层资源管理系统(Kubernetes/YARN)的容器或 Pod 资源请求中
  • 最终确保 TaskManager 所在的容器/Pod 真的带着你要的外部资源启动

2)把"可用资源信息"提供给算子(Operator)

  • TaskManager 启动后,由"外部资源驱动(driver)"生成 ExternalResourceInfo(资源信息集合)
  • 算子通过 RuntimeContext.getExternalResourceInfos(resourceName) 拿到资源的关键属性(比如 GPU index),然后就可以在算子里绑定对应设备去用

一句话:框架负责"申请 + 告知",至于"怎么用"取决于具体插件。

2. 适用场景与边界

适合的典型场景

  • GPU 推理(TensorRT / ONNX Runtime / PyTorch inference)作为 RichFunction/AsyncFunction 的一部分
  • GPU 加速特征工程或向量计算
  • 需要 FPGA、专用加速卡的自定义计算
  • 同一套作业在 Kubernetes/YARN 上希望"按需申请资源"

当前边界(很关键)

  • 外部资源是"机器级/进程级共享"的:同一 TaskManager 上运行的所有算子拿到的 ExternalResourceInfos 目前是同一份集合
  • 也就是说:没有 operator 级别的资源隔离(同 TM 内算子理论上能看到同一批 GPU)

如果你希望"每个算子/每个 subtask 独占一张卡",需要你在算子内部做更严格的绑定策略,或用脚本协调模式避免同机多 TM 抢同一 GPU(后面讲)。

3. 启用流程:三步走

3.1 准备外部资源插件(plugins/)

外部资源通过 Flink 插件机制加载,你需要把对应 jar 放到 Flink 的 plugins/ 目录下的某个子目录中,例如:

  • GPU 插件:放到 plugins/external-resource-gpu/(或你自定义目录,但要保证 jar 能被加载)
  • 自定义资源插件:创建一个目录,比如 plugins/fpga/,把你打出来的 jar 放进去

插件隔离非常重要:每个 plugin 目录是独立 classloader,避免依赖冲突;同时 SPI(ServiceLoader)文件必须保留(META-INF/services)。

3.2 配置 external-resources 与每个资源的参数

核心配置有两层:

A)先声明启用哪些资源(白名单)

yaml 复制代码
external-resources: gpu;fpga

只有这里列出来的 <resource_name> 才会生效。

B)为每个资源配置 amount、k8s/yarn 映射、driver、driver 参数

常见配置项含义:

  • external-resource.<name>.amount:每个 TaskManager 需要的资源数量

  • external-resource.<name>.yarn.config-key:YARN 容器资源 profile 的映射 key(可选)

  • external-resource.<name>.kubernetes.config-key:K8s 容器 resources.requests/limits 的 key(可选)

  • external-resource.<name>.driver-factory.class:驱动工厂(可选但强烈建议)

    • 不配置也能"申请到资源",但算子拿不到 ExternalResourceInfo(RuntimeContext 里会没有信息)
  • external-resource.<name>.param.<param>:传给驱动工厂的自定义参数(插件自定义解释)

一个包含 GPU+FPGA 的示例:

yaml 复制代码
external-resources: gpu;fpga

external-resource.gpu.driver-factory.class: org.apache.flink.externalresource.gpu.GPUDriverFactory
external-resource.gpu.amount: 2
external-resource.gpu.param.discovery-script.args: --enable-coordination-mode

external-resource.fpga.driver-factory.class: org.apache.flink.externalresource.fpga.FPGADriverFactory
external-resource.fpga.amount: 1
external-resource.fpga.yarn.config-key: yarn.io/fpga

3.3 在算子里使用 RuntimeContext 获取资源信息

算子侧用法非常直接:

java 复制代码
public class ExternalResourceMapFunction extends RichMapFunction<String, String> {
    private static final String RESOURCE_NAME = "gpu";

    @Override
    public String map(String value) throws Exception {
        Set<ExternalResourceInfo> infos =
                getRuntimeContext().getExternalResourceInfos(RESOURCE_NAME);

        List<String> indices = new ArrayList<>();
        for (ExternalResourceInfo info : infos) {
            info.getProperty("index").ifPresent(indices::add); // GPU 插件常用属性 key:index
        }

        // 这里用 indices 做设备绑定,比如选择一张卡 set CUDA_VISIBLE_DEVICES 或初始化推理引擎
        return value;
    }
}

每个 ExternalResourceInfo 里有哪些 key,取决于插件实现。你可以用 ExternalResourceInfo#getKeys() 获取完整键集合。

4. Kubernetes / YARN / Standalone:三种环境的差异

4.1 Kubernetes

  • K8s 原生通过 Device Plugin 机制提供 GPU/FPGA 等资源(Kubernetes v1.10+ 支持)

  • Flink 会把你配置的 kubernetes.config-key 写入 TaskManager 主容器的:

    • resources.requests.<config-key>
    • resources.limits.<config-key>

GPU 的常见 key:

  • NVIDIA:nvidia.com/gpu
  • AMD:amd.com/gpu(但 Flink 默认 discovery 脚本是 NVIDIA 的,AMD 需要你自己写脚本)

4.2 YARN

  • YARN 2.10+ / 3.1+ 开始支持 GPU/FPGA 资源
  • Flink 通过 external-resource.<name>.yarn.config-key 把 amount 写进 container resource profile

GPU(YARN)常见 key:

  • yarn.io/gpu(注意:YARN 当前通常仅支持 NVIDIA GPU 的调度)

4.3 Standalone

  • Standalone 模式没有底层 RM 帮你"保证资源",你需要管理员在节点上确保外部资源可用(驱动安装、权限、设备可见性等)
  • 如果同一台机器上跑多个 TaskManager,GPU 可见性很容易冲突,需要配合 discovery script 的协调模式

5. GPU 插件:最常用也最值得踩坑的一块

Flink 目前官方提供的一方外部资源插件就是 GPU 插件。

5.1 必要配置(GPU)

yaml 复制代码
external-resources: gpu
external-resource.gpu.driver-factory.class: org.apache.flink.externalresource.gpu.GPUDriverFactory
external-resource.gpu.amount: 2

# Kubernetes
external-resource.gpu.kubernetes.config-key: nvidia.com/gpu

# YARN
external-resource.gpu.yarn.config-key: yarn.io/gpu

5.2 discovery script(GPU 发现脚本)

GPUDriver 会调用 discovery script 来发现可用 GPU,并生成 ExternalResourceInfo,其中关键属性是:

  • key = index(GPU 设备 index)

默认脚本路径(NVIDIA):

yaml 复制代码
external-resource.gpu.param.discovery-script.path: plugins/external-resource-gpu/nvidia-gpu-discovery.sh

自定义脚本路径(例如 AMD)也可以配置同一个 key。

脚本参数:

yaml 复制代码
external-resource.gpu.param.discovery-script.args: --enable-coordination-mode

5.3 脚本契约(你写自定义脚本时必须遵守)

  • Flink 先把 amount 作为第一个参数传给脚本
  • 你配置的 discovery-script.args 会拼在后面
  • 脚本输出:用逗号分隔的 GPU index 列表,例如 0,1
  • 输出里纯空白 index 会被忽略
  • 如果发现失败或数量不足:脚本返回非 0 exit code,Flink 将不会向算子提供 gpu 信息(RuntimeContext 拿不到)

5.4 协调模式:解决"同机多 TM 抢同一 GPU"

Standalone 下经常同机部署多个 TaskManager,此时所有 TM 默认都能看到同一批 GPU(nvidia-smi 可见),很容易"多进程抢同一张卡"。

默认脚本提供 coordination mode:

  • --enable-coordination-mode:启用协调
  • --coordination-file <path>:协调文件路径(默认 /var/tmp/flink-gpu-coordination

它能保证:同一个 Flink 集群内,同机多个 TaskManager 不会分到同一张 GPU。

但要注意两点:

  • 只在"同一协调文件范围内"有效:另一个 Flink 集群如果用不同 coordination file,仍可能抢同一 GPU
  • 非 Flink 应用也可能用同一 GPU,这个模式无法防住

6. 自定义资源插件:你要支持 FPGA/自研加速卡怎么做

你需要实现三件套:

1)ExternalResourceDriver

  • retrieveResourceInfo(long amount):返回 ExternalResourceInfo 集合(你定义的资源维度)

2)ExternalResourceDriverFactory

  • createExternalResourceDriver(Configuration config):从配置创建 driver

3)SPI 服务声明(非常关键)

  • 在 jar 内创建文件:

    • META-INF/services/org.apache.flink.api.common.externalresource.ExternalResourceDriverFactory
  • 文件内容写你的 factory 全类名,例如:

    • your.domain.FPGADriverFactory

示例骨架:

java 复制代码
public class FPGADriver implements ExternalResourceDriver {
    @Override
    public Set<ExternalResourceInfo> retrieveResourceInfo(long amount) {
        // 发现并返回 FPGA 信息集合
        return Set.of(/* ... */);
    }
}

public class FPGADriverFactory implements ExternalResourceDriverFactory {
    @Override
    public ExternalResourceDriver createExternalResourceDriver(Configuration config) {
        return new FPGADriver();
    }
}

public class FPGAInfo implements ExternalResourceInfo {
    @Override
    public Optional<String> getProperty(String key) {
        // 根据 key 返回属性,比如 "device", "pci", "address" 等
        return Optional.empty();
    }

    @Override
    public Collection<String> getKeys() {
        return List.of("device", "pci", "address");
    }
}

打包成 jar 丢到 plugins/fpga/,然后在 flink-conf.yaml 里按 <resource_name> 配置启用即可。

7. 排障清单:最常见的 6 个"为什么拿不到 GPU"

1)external-resources 没写 gpu(或资源名拼错)

2)插件 jar 没放到 plugins/ 下正确目录(或目录里缺依赖)

3)没配置 driver-factory.class,导致算子侧拿不到 ExternalResourceInfo

4)K8s 没装 NVIDIA device plugin(Pod 根本拿不到 GPU)

5)discovery script 不可执行 / 路径不对 / 返回非 0

6)Standalone 同机多 TM 没开协调模式,导致资源冲突看似"有卡但不可用"

8. 最佳实践建议

  • 先明确"资源申请"和"资源绑定"是两步:申请解决"容器是否带卡",绑定解决"算子用哪张卡"
  • GPU 推理算子里要做设备亲和:基于 index 决定 CUDA_VISIBLE_DEVICES 或引擎初始化参数
  • Standalone 同机多 TM 建议默认开 coordination mode
  • 生产上尽量用 Kubernetes/YARN 去做资源保证,Standalone 只适合可控环境
  • 由于没有 operator 级隔离,最好避免在同一 TM 内多个算子"各自随便挑卡",要统一策略(例如只由一个算子管理 GPU worker)
相关推荐
AC赳赳老秦2 小时前
轻量化模型浪潮下的关键技术突破:DeepSeek INT4量化优化引领2026端侧算力新纪元
网络·安全·mongodb·web安全·flink·prometheus·deepseek
菜鸟小芯2 小时前
从“会聊天”到“能做事”:AI Agent(AI 智能体)的技术革命与落地实践
大数据·人工智能
龙山云仓2 小时前
No155:AI中国故事-对话宋应星——天工开物与AI造物:格物穷理与经世致用
大数据·人工智能·深度学习
Hello.Reader2 小时前
Flink History Server 集群停了也能看已完成作业的 Web UI 与 REST 数据
大数据·flink
赵谨言2 小时前
基于Python和ArcPy的不动产数据入库技术与运用
大数据·开发语言·经验分享·python
一条咸鱼_SaltyFish3 小时前
Elasticsearch索引规划:从字段类型到分片策略的实战思考
大数据·elasticsearch·搜索引擎·全文检索·后端开发·分片策略·索引规划
海兰3 小时前
Elasticsearch 9.3.0 系统日志采集详解
大数据·elasticsearch·搜索引擎
计算机编程-吉哥3 小时前
大数据毕业设计 基于大数据的计算机岗位招聘数据可视化分析系统 计算机毕业设计【项目+论文+安装调试】
大数据·机器学习·信息可视化·数据分析·毕业设计·计算机毕业设计选题·大数据毕业设计选题推荐
说私域3 小时前
链动2+1模式AI智能名片S2B2C商城小程序在微商信任重建中的创新应用与价值实现
大数据·人工智能·小程序·私域运营