资讯动态

Flink部署模式选型:Session、Application与Kubernetes/YARN实践

发布时间:2026/10/29 23:54:33 来源:尧图企业网站定制
1. 部署模式选的不是安装方式而是作业的生命周期归属有人问我你们 Flink 用的什么部署模式我第一反应不是回答 Session 还是 Application而是反问一句你这个作业是长期跑的还是隔几天换一版的这个问题答不上来模式选哪个都是拍脑袋。Flink 集群部署模式这个词听起来像安装选项实际上它决定的是一整套运行时契约——资源什么时候申请、谁负责解析作业图、集群活多久、故障了从哪恢复、日志去哪儿找。这五件事里任意一件想错后面调试的时间就会成倍往上翻。我见过太多团队在这个问题上走弯路本地用 Session 模式跑通了 Demo上线直接照搬结果三个作业挤在一个集群里互相抢 slot一个作业背压把整个集群的心跳都拖慢也有人听说 Per-Job 模式隔离性好翻着两年前的博客配了一通结果在新版本上参数直接不生效卡了半天才发现这条路官方已经标记废弃。这些坑的共同点是大家把部署模式当成了一个配置项而不是一个需要跟业务形态绑定的架构决策。这篇内容想解决的问题很具体——把 Flink 的几种部署模式拆到它到底改变了什么这个层面然后给出可落地的选择逻辑和配置样例。适合两类人看一类是刚开始负责 Flink 平台建设、需要在 Session 和 Application 之间做决定的工程师另一类是已经在跑但总觉得集群状态不对劲、想搞清楚底层发生了什么的人。我会尽量少讲概念定义多讲链路和账目因为部署模式这个问题看懂资源是怎么流动的比背三种模式的名字有用得多。1.1 三个正交维度谁申请资源、集群活多久、代码在哪解析想把部署模式理解透最有效的方式是把它拆成三个互不干扰的维度来看而不是死记 Session、Per-Job、Application 三个名词。这三个维度分别是资源由谁申请、集群生命周期多长、作业代码在哪一端解析成执行图。任何一个具体的部署方案都是这三个维度上各取一个值组合出来的。资源由谁申请指的是 TaskManager 这些干活的计算资源是提前起好等着作业来用还是作业提交时才按需拉起。集群生命周期指的是一个 JobManager 是服务一个作业还是服务一批作业作业结束后集群是留着还是销毁。代码在哪解析指的是main()方法里的那些逻辑——读配置、拼算子、算并行度——是在提交作业的客户端机器上执行还是在集群内部的 JobManager 上执行。这一条最容易被忽略但它恰恰是 Application 模式存在的全部理由。把这三个维度摆开之后你会发现所谓三种部署模式其实是三组典型组合Session 模式 资源提前起 集群常驻 客户端解析Per-Job 模式 资源按需起 一个作业一个集群 客户端解析Application 模式 资源按需起 一个应用一个集群 集群端解析。理解了这个映射关系后面遇到任何组合比如 Standalone 上的 Application 模式都不会懵。提示很多资料把部署模式和资源底座混在一起讲导致读者以为 YARN 就是 Per-Job、Kubernetes 就是 Application。实际上两者是正交的——YARN 支持 Session、Per-Job、Application 三种Kubernetes 也支持 Session 和 Application 两种Standalone 主要配 Session。底座决定资源从哪来模式决定资源怎么用。1.2 为什么换个模式就要重写一堆提交参数真正让人头疼的不是理解概念而是概念切换之后参数全线失效。同一个作业从 Session 模式切到 Application 模式flink run变成flink run-applicationjar 的路径从本地路径变成local://前缀并行度的设置位置可能从命令行挪到代码里连日志的输出位置都会变。如果不清楚每个参数背后的归属就只能靠试错。我一般用一条经验来判断某个参数该写在哪跟作业逻辑相关的写进代码或配置文件跟运行时环境相关的写进提交参数。比如算子的并行度上限、状态后端的类型、Checkpoint 的间隔这些是作业自身的属性写在代码里换环境不用改而镜像地址、命名空间、资源请求量、slot 数量这些是环境属性写在提交命令或平台的部署模板里同一份代码在不同环境用不同参数提交。这条经验之所以重要是因为 Application 模式下所有-D参数最终会合并进集群的配置覆盖顺序是代码里的配置 提交时的 -D 参数 集群 conf 文件。搞错覆盖顺序就会出现我明明在命令行传了并行度怎么不生效这种问题。实际操作中我习惯把关键参数显式打印出来在作业启动的第一行日志里把生效的并行度、Checkpoint 目录、slot 数量全打一遍排查时省下大量时间。2. Session 模式一个常驻集群养多作业省事背后是资源账算不清Session 模式是绝大多数人接触 Flink 的第一种形态因为它启动最快——集群提前起好作业来了直接提交秒级就能跑起来。它的核心特征是 JobManager 常驻所有作业共享同一批 TaskManager 的 slot。这种共享带来的便利是实打实的不用每次等集群拉起多个小作业可以挤在一批资源上运维只需要管一个集群实例。但共享的另一面是账目模糊。当一个集群里同时跑着五个作业某个作业突然背压你很难第一时间判断是它自己的问题还是被别人挤的。slot 被谁占着、内存被谁吃了、GC 是谁引起的这些信息在 Session 模式下都是混在一起的。我见过一个典型案例某团队在一个 Session 集群里跑着实时清洗和离线回刷两类作业回刷作业一启动就把 slot 占满实时作业被迫等待监控上看是作业启动慢实际原因是资源竞争。2.1 从客户端提交到 TaskManager 分配 slot 的完整链路理解 Session 模式的运行机制最好的方式是跟着一次提交走完整条链路。客户端执行flink run时先在本地把代码里的main()跑一遍生成 JobGraph——这一步很关键意味着所有算子的并行度、连接关系、序列化器都已经在客户端确定下来了。然后客户端把 JobGraph 和依赖 jar 打包通过 REST 接口提交给已经运行的 JobManager。JobManager 收到后交给 DispatcherDispatcher 会为这个作业创建一个 JobMasterJobMaster 拿着 JobGraph 去找 ResourceManager 申请 slot。ResourceManager 手里有一份 TaskManager 注册表知道每个 TM 还有几个空闲 slot够就分配不够就让作业排队等着。这里有个容易误解的点Session 模式下 ResourceManager 一般不会主动去拉起新的 TaskManager除非你配了底座的动态资源分配比如 YARN 的 session 模式支持按需申请容器Kubernetes 的 session 模式也支持动态拉起 TM。在纯 Standalone 的 Session 集群里slot 用完了就是排队一点办法都没有。所以 Standalone Session 模式下的容量规划是道纯算术题集群总 slot 数 TaskManager 数量 × 每个 TM 的taskmanager.numberOfTaskSlots所有作业在同一时刻需要的 slot 总和不能超过这个数。而单个作业需要的 slot 数在默认的 slot sharing 策略下等于该作业所有算子中最大的那个并行度——因为不同算子可以共享同一个 slot只要它们在同一个 slot sharing group 里。这个算法很简单但很多人算的时候会忘记加上作业内部的各种 source 和 sink导致规划出来的容量偏小。2.2 资源抢占与故障放大Session 模式最真实的两类问题资源抢占的表现形式往往不是报错而是变慢。作业 A 和作业 B 共享一个 TM 的 slot 配额A 的流量高峰来了它占用的 CPU 和内存上升B 的处理延迟跟着上涨但因为 CPU 是共享的、内存是 JVM 内共享的监控上你看到的是两个作业的延迟都在抖很难归因。如果两个作业跑在同一个 JVM 里共享 TM 的多个 slot 就是这种情况一个作业的 GC 停顿会直接影响另一个作业的吞吐这种耦合在排查时非常隐蔽。故障放大的问题更严重。Session 模式下 JobManager 是所有作业的单点一旦 JobManager 所在节点出问题整个集群上的所有作业一起受影响。虽然现在有 HA 机制基于 ZooKeeper 或 Kubernetes 的 ConfigMap/Lease主备切换通常能在几十秒内完成但这几十秒里所有作业都在重启。相比之下一个作业一个集群的模式下这种故障的影响面只有一个作业。还有一个常被忽略的问题版本升级和依赖冲突。Session 集群的 classpath 是固定的所有作业共享。如果作业 A 依赖某个库的 1.x 版本作业 B 依赖 2.x在同一个集群里跑就会出问题——要么靠 shade 重定位要么就只能拆集群。我在实际项目里遇到过因为两个作业用了不同版本的某个序列化库导致其中一个作业反序列化时抛异常的情况最后是靠给每个作业单独打 shade 包解决的。2.3 什么样的场景值得继续用 Session说了这么多问题Session 模式并不是不能用关键是用在对的场景。它的甜区是多个小作业、作业之间没有强隔离要求、资源需求稳定且可预估、对启动延迟敏感。典型例子是一批轻量的实时 ETL 任务每个作业的并行度只有个位数加起来也不到集群容量的一半这种情况下 Session 模式的资源利用率确实比一作业一集群高。另一个适合的场景是开发和调试环境。每次改代码都要等集群拉起几十秒这个体验很差Session 集群常驻着提交验证就快得多。但我要强调这个便利只应该出现在开发环境生产环境图这个方便是要还债的。注意如果你的作业里有任何一个属于大状态 频繁 Checkpoint 长时间运行的组合不要把它放进共享的 Session 集群。大状态作业的 Checkpoint 会占用大量网络带宽和磁盘 IO对同集群其他作业的影响非常明显而这种影响在监控上几乎不可见。3. Application 模式main() 搬进集群之后客户端终于不用背锅Per-Job 模式在 Flink 1.15 被标记为废弃之后Application 模式基本成了一作业一集群场景下的标准答案。但很多人只是把flink run换成了flink run-application并不清楚这个切换到底改变了什么于是遇到jar 找不到、配置不生效、依赖下载失败这类问题时毫无头绪。Application 模式最本质的改变是把main()的执行位置从客户端搬到了集群的 JobManager 上。这个改变听上去只是换了个执行位置实际带来的差异非常大。客户端在传统模式下要做的活包括加载用户代码、解析算子链、生成 JobGraph、收集所有依赖 jar、打包上传。对于一个依赖复杂的作业客户端可能要下载几百兆的依赖用户的笔记本或者跳板机上还得装对版本的 Java 和 Flink 客户端。Application 模式下客户端只负责把 jar 和配置交出去剩下的解析工作全在集群里做客户端轻到可以用一条 curl 命令完成提交。3.1 客户端解析与集群解析的本质区别客户端解析的问题在于环境不一致。你在本地跑通的作业提交到集群后可能因为客户端机器的 JDK 版本、依赖缓存、环境变量不同而表现不一样。更麻烦的是当作业依赖的 jar 很多时客户端的解析耗时会变得很长而且这个过程占用的是提交机器的资源如果是 CI/CD 流水线自动提交每次都要重新走一遍依赖解析效率很低。集群端解析则把这件事固化了。JobManager 所在的镜像里有什么依赖、什么版本、什么配置都是确定的一次构建反复使用。作业的解析过程和执行过程在同一个环境里不会出现客户端能解析、集群跑不起来的错位。这就是为什么 Application 模式在容器化部署里几乎是默认选择——镜像本身就是一个不可变的环境快照你把这个快照跑起来行为就是可复现的。但要注意Application 模式并不是没有客户端参与。flink run-application这个命令仍然需要客户端它负责把 jar 上传到集群、把配置翻译成集群能理解的参数、启动集群。区别在于客户端不再需要解析用户代码也就不需要用户代码的全部依赖都在客户端存在。这个边界要划清楚否则会误以为 Application 模式可以完全脱离客户端。3.2 提交全流程逐段拆解以 Kubernetes 上的 Application 模式为例一次完整提交大致分这么几步。客户端读取命令行参数和配置文件把 jar 文件、-D形式的配置项整理好通过 Kubernetes API 创建 JobManager 的 Deployment或者直接创建 Pod取决于版本和实现。这个 Pod 的镜像里包含 Flink 发行版、作业 jar如果用local://方式以及入口类信息。JobManager 启动后ClusterEntrypoint会读取配置初始化 ResourceManager、Dispatcher 等组件。关键的一步是它要执行用户的main()方法。这时候main()里读配置、建执行环境、定义算子这些逻辑全在集群里跑。跑完之后得到一个 JobGraphDispatcher 拿着它创建 JobMasterJobMaster 再向 ResourceManager 申请 slot。ResourceManager 这时会向 Kubernetes 请求创建 TaskManager Pod等 Pod 就绪并注册上来之后作业才真正开始执行。这条链路里有两个地方最容易出问题。一是 JobManager 执行main()时需要的类必须都能找到——如果你的main()里用到了某个工具类而这个类没打进镜像作业在解析阶段就会抛ClassNotFoundException而且这个异常出现得很早日志可能在 JobManager 的启动日志里不在常规的作业日志里。二是 TaskManager 拉起的时机——如果资源申请配得太大K8s 集群没有足够资源Pod 会一直处于 Pending作业就一直卡在等待资源状态表现是 REST 接口显示作业在运行但没有 TaskManager。3.3 用户 jar 与依赖分发这条最容易断的链路Application 模式下用户 jar 的位置是个绕不开的问题。目前主流做法有三种各有适用场景。第一种是把 jar 打进镜像用local:///opt/flink/usrlib/job.jar这样的路径引用。这是最稳的方式因为 jar 和 Flink 运行时在同一个文件系统里不涉及任何网络传输启动最快行为最可预测。代价是每次作业更新都要重新构建镜像镜像层会变大而且如果多个作业共用一个基础镜像各自打各自的 jar镜像数量会膨胀。第二种是把 jar 放到远端存储通过 Flink 的 artifact 分发机制让 JobManager 去拉取。这种方式的好处是镜像可以复用作业更新只需要重新上传 jar。但要注意版本差异——artifact 分发相关配置在不同版本里的行为不完全一样配置项名称也调整过跨版本迁移时务必查对应版本的官方文档不要照抄别人的配置。第三种是 YARN 上的yarn.provided.lib.dirs思路把公共依赖和用户 jar 放到分布式文件系统上由集群统一加载。这种做法能显著缩短提交时间但对目录结构和权限管理有要求。我个人的选择倾向是生产环境用第一种把稳定性放在第一位如果作业迭代非常频繁、镜像构建成为瓶颈再考虑第二种但一定要在测试环境把上传、校验、清理这几步都跑通。4. Standalone、YARN、Kubernetes 三块底座的能力边界聊完模式还得聊底座。同样一个 Application 模式跑在 Kubernetes 上和跑在 YARN 上配置项、资源申请方式、故障恢复行为都不一样。选底座本质上是在选你的组织已经有什么——已有的调度平台、已有的运维习惯、已有的监控体系比纯粹的技木优劣重要得多。4.1 Standalone静态资源与手工扩缩容的代价Standalone 是最朴素的形态几台机器手动起 JobManager 和 TaskManager配置文件写死。它的优点是简单没有额外依赖不依赖任何调度器出问题的时候排查路径最短——你能直接看到进程在不在、端口通不通、日志在哪。对于自建机房、资源固定、作业数量可控的团队这套方案完全够用。缺点也很明确资源是静态的扩缩容靠改配置文件加手工重启。TaskManager 挂了一台需要人工或者外部脚本来补。而且 Standalone 的 Session 模式下没有动态资源分配slot 用完了就是排队不会自动扩容。如果你的业务有明显的波峰波谷Standalone 会造成大量的资源闲置或者高峰期排队。4.2 YARN队列、容器与资源回收的老牌组合YARN 上的部署在资源管理上是成熟的。资源申请通过 YARN 的容器机制完成作业结束后容器释放资源回收到队列里给别人用这个机制经过多年验证稳定性没什么可挑剔的。Application 模式下整个 Flink 集群JobManager 加 TaskManager都在 YARN 的容器里跑作业结束集群销毁资源干净释放。配置上主要关注几个点yarn.application.name用来标识作业方便在 YARN 的界面里找yarn.queue指定提交到哪个队列队列容量决定了你能申请到多少资源yarn.application-attempts控制失败重试次数资源相关的yarn.taskmanager.memory在新的内存模型下已经统一到taskmanager.memory.process.size。还有一个常被忽略的yarn.ship-files如果你有额外的配置文件要一起分发需要显式声明。日志收集在 YARN 上是另一个需要提前安排的环节。容器结束后日志会被聚合到 HDFS如果没配日志聚合容器一销毁日志就没了排查历史问题会非常被动。这个配置在 YARN 侧开启不在 Flink 侧。4.3 Kubernetes原生提交与 Operator 两条路线Kubernetes 上的 Flink 部署有两条路线。一条是 Flink 自带的原生 Kubernetes 部署直接通过flink run-application -t kubernetes-application提交Flink 自己调用 K8s API 创建 JobManager 的 Deployment 和 TaskManager 的 Pod自己管理生命周期。另一条是 Flink Kubernetes Operator用一个自定义资源CRD描述作业由 Operator 监听并负责创建、升级、恢复、扩缩容。原生路线的优势是轻不需要额外部署 Operator适合简单场景和快速验证。缺点是没有声明式的作业管理升级、暂停、恢复都得自己写脚本或者手工操作。Operator 路线把作业当成 K8s 的一等公民支持 savepoint 触发的升级、状态监控、自动恢复适合作业数量多、需要统一管理的场景代价是引入了额外的组件和一层抽象出问题时排查链路会变长。选择上我的建议是作业数量在个位数、变更不频繁用原生就够作业数量上去了、需要自动化和统一的运维视图上 Operator。不要因为 Operator 听起来更云原生就盲目引入多一层组件就多一层故障点。4.4 一张表把三种底座的关键差异摊开维度StandaloneYARNKubernetes资源来源静态机器集群队列容器编排平台扩缩容手工改配置重启作业级自动申请释放声明式可自动扩缩支持的模式主要 SessionSession / ApplicationSession / Application作业结束资源回收不回收集群常驻自动回收自动回收日志排查直接看本地文件依赖日志聚合到分布式存储依赖容器日志收集依赖分发各节点预置分布式文件系统分发打进镜像或制品仓库运维复杂度低但人力成本高中高需要 K8s 运维能力适合团队资源固定、作业少已有 Hadoop 体系已有容器平台这张表只是给人一个整体印象实际选型时还要看团队的运维能力。我见过有团队为了统一技术栈把 Flink 迁到 K8s结果团队里没人熟悉 K8s 的网络模型和存储挂载一个 DNS 解析问题拖了三天。技术选型的成本里学习成本往往比硬件成本高。5. 在 Kubernetes 上跑 Application 模式的落地配置假设底座已经确定是 Kubernetes模式确定是 Application接下来就是具体配置。这一节我按实战顺序走一遍把每个参数的作用和取值理由说清楚。5.1 镜像分层与 jar 位置的取舍镜像分层的思路是基础层放 Flink 发行版和公共依赖应用层放具体的作业 jar 和它独有的依赖。这样多个作业可以共享基础层镜像仓库的存储压力和构建时间都能降下来。基础层的 Dockerfile 大致是这样组织的FROM flink:1.18-scala_2.12-java11 # 放入公共连接器与工具库 COPY lib/flink-connector-*.jar /opt/flink/lib/ # 放入作业 jar COPY job/app-job.jar /opt/flink/usrlib/app-job.jar # 放入集群配置 COPY conf/flink-conf.yaml /opt/flink/conf/flink-conf.yaml这里有个细节要注意/opt/flink/lib下的 jar 会被加到 Flink 的系统类加载器路径上/opt/flink/usrlib下的 jar 是通过用户类加载器加载的。把作业 jar 放到lib下会污染系统 classpath可能导致版本冲突正确的位置是usrlib。另外如果用 Flink 自带的一些插件机制比如文件系统插件需要放到/opt/flink/plugins下不同版本的目录约定略有差异以对应版本的镜像文档为准。配置文件的处理也有两种思路。一种是把配置打进去优点是环境固定缺点是同一份镜像没法在不同环境测试/生产复用。另一种是镜像里不放配置提交时全用-D传。我倾向于折中镜像里放与环境和作业都无关的默认配置跟环境相关的参数集群 ID、对象存储地址、资源量全部在提交时传入。5.2 并行度、slot 与内存的账怎么算平这是最容易出错的一节因为三个数字互相牵制作业的最大并行度、TaskManager 的数量、每个 TaskManager 的 slot 数。先算作业需要多少 slot。默认所有算子共享 slot所以作业的 slot 需求等于所有算子中最大的并行度。假设你的作业里 source 并行度 12中间的 keyBy 聚合并行度 12sink 并行度 6那这个作业需要 12 个 slot。再算 TaskManager 的数量。taskmanager.numberOfTaskSlots设成多少合适这个值决定了一个 TM 里跑几个 slot也就是几个 slot 共享一个 JVM。设得大JVM 复用率高资源利用率好但隔离性差一个 slot 里的算子 GC 会波及其他 slot设得小隔离性好但每个 TM 都有独立的 JVM 开销堆外内存、metaspace、JVM 自身资源浪费。实践中的惯例是 2 到 4 之间Flink 的默认值是 1。我一般设成 2 或 4配合合理的并行度让 TM 数量是整数。以 12 并行度、每 TM 4 个 slot 为例需要的 TM 数量是 3。这个数字要和资源申请对上kubernetes.taskmanager.cpu设成多少如果每个 TM 分 2 个 CPU 核心3 个 TM 就是 6 核加上 JobManager 的 1 核集群总共需要 7 核的配额。这个账要在提交前算清楚否则会出现 Pod Pending。内存的账更细。Flink 1.10 之后用的是统一内存模型taskmanager.memory.process.size是整个容器的内存上限包括 JVM 堆、堆外内存、metaspace、JVM 自身开销、以及 Flink 管理的网络缓冲和托管内存。设定这个值时要保证它和kubernetes.taskmanager.memory的取值一致否则可能被 K8s 的 OOM Killer 干掉。一个常见的配置是这样taskmanager.memory.process.size: 4096m taskmanager.memory.managed.fraction: 0.4 taskmanager.numberOfTaskSlots: 4 kubernetes.taskmanager.cpu: 2 kubernetes.taskmanager.memory: 4096mmanaged.fraction控制托管内存占总内存的比例默认 0.4这个部分主要给 RocksDB 状态后端和批量算子用。如果你的作业用 RocksDB 存大状态这个比例可能要往上调如果是纯堆内状态可以调低把内存让给任务堆。提示内存配置算完后一定要在测试环境用真实数据量压一遍观察 TaskManager 的实际内存使用和 GC 情况。纸面计算只能保证不报错实际的内存需求跟数据倾斜、状态大小强相关压测是唯一可靠的验证手段。5.3 HA、Checkpoint 与重启策略的配套设置Application 模式下一个作业一个集群JobManager 挂掉意味着整个作业停摆所以 HA 配置不能省。Kubernetes 上推荐用基于 ConfigMap 和 Lease 的 HA 实现不需要额外部署 ZooKeeperhigh-availability.type: kubernetes high-availability.storageDir: s3://your-bucket/flink/ha kubernetes.jobmanager.replicas: 2high-availability.storageDir指向一个所有副本都能访问的共享存储用来存 JobGraph 和恢复元数据。kubernetes.jobmanager.replicas设成 2 表示跑两个 JobManager 副本一个主一个备主挂了备自动接管。这里的前提是你的main()逻辑必须能重复执行而不产生副作用——比如不要在main()里写数据库、不要生成随机文件名否则主备切换时可能会重复执行。Checkpoint 的配置跟 HA 是配套的state.checkpoints.dir必须指向一个所有节点都能访问的持久化存储。间隔的设置要看作业的状态大小和可接受的恢复时间状态大、恢复慢的作业间隔可以适当放长避免 Checkpoint 成为常态开销状态小、要求低延迟恢复的间隔可以短一些。同时记得配execution.checkpointing.timeout超时时间要比正常完成时间长一些否则会因为偶发的网络抖动频繁失败。重启策略在 Application 模式下尤其重要因为默认的固定延迟重启次数用完之后集群就直接退出了表现出来就是作业莫名其妙消失。我的习惯是配成固定延迟加重试上限的组合同时配合外部监控作业如果真的到了重启上限需要告警而不是静默退出。restart-strategy.type: fixed-delay restart-strategy.fixed-delay.attempts: 10 restart-strategy.fixed-delay.delay: 30s5.4 提交命令与验证步骤参数理清楚之后提交命令本身不长flink run-application \ --target kubernetes-application \ -Dkubernetes.cluster-idorder-etl-prod \ -Dkubernetes.namespaceflink-jobs \ -Dkubernetes.service-accountflink-sa \ -Dkubernetes.container.imageregistry.local/flink-job:1.0.3 \ -Dkubernetes.jobmanager.cpu1 \ -Dkubernetes.taskmanager.cpu2 \ -Dtaskmanager.numberOfTaskSlots4 \ -Dtaskmanager.memory.process.size4096m \ -Dparallelism.default12 \ -Dstate.checkpoints.dirs3://your-bucket/flink/checkpoints/order-etl \ -Dhigh-availability.typekubernetes \ -Dhigh-availability.storageDirs3://your-bucket/flink/ha \ local:///opt/flink/usrlib/app-job.jar提交后的验证我一般分四步走。第一步看 JobManager Pod 是否 Runningkubectl get pods -n flink-jobs看状态。第二步看 JobManager 日志里有没有Recovered/Started cluster之类的字样确认集群启动成功。第三步看 TaskManager Pod 有没有按预期数量起来数量不对说明资源申请和并行度的账没算平。第四步通过 REST 接口查作业状态curl http://jobmanager:8081/jobs/overview确认作业处于 RUNNING 且所有 subtask 都在运行。如果某一步卡住基本可以按这个顺序定位Pod 没起来就查调度和镜像拉取Pod 起来了但集群没启动就查 JobManager 日志的异常集群起来了但作业没提交就查main()执行阶段的类加载问题作业提交了但没跑满就查 slot 数量和资源申请。6. 模式迁移过程中反复出现的几个坑这一节讲的是我在实际切换部署模式时反复遇到的几类问题按照现象—排查—根因—处理的顺序写希望你在遇到类似现象时能少走弯路。6.1 slot 数量对不上导致的无限等待现象是作业处于 RUNNING 状态但部分 subtask 没有分配到资源界面上一直显示等待。排查的第一步是看 TaskManager 的实际数量和预期数量是否一致。如果 TM 数量少于预期说明资源申请环节有问题去看 K8s 的事件或者 YARN 的容器日志通常是集群资源不足导致 Pod Pending。如果 TM 数量符合预期那就要检查 slot 数。常见的情况是taskmanager.numberOfTaskSlots和parallelism.default不匹配——比如并行度 12、每 TM 4 个 slot、但只起了 2 个 TM总共 8 个 slot差 4 个作业就永远差那么一点跑不满。另一种更隐蔽的情况是作业内部设置了不同的 slot sharing group导致 slot 需求大于最大并行度这个在代码层面不容易看出来需要在提交后看 JobGraph 里每个 slot sharing group 的资源需求。处理办法是把 TM 数量和并行度的关系做成显式约束。我在提交脚本里通常会加一段校验从配置里读出并行度和 slot 数算出需要的 TM 数量再和kubernetes.taskmanager.replicas或者 YARN 的容器数量对比不一致就直接报错退出不让它带着错误的配置跑到集群里。6.2 state 恢复路径错位与版本不兼容从旧模式迁移到新模式时最怕的是 checkpoint 和 savepoint 的状态恢复不了。表现是作业启动时报状态不兼容或者找不到状态文件。原因通常有三种存储路径变了但配置没改状态后端类型变了比如从堆内改成了 RocksDB作业的算子拓扑发生了变化导致状态无法对位。排查这类问题的第一步是确认 savepoint 的路径和作业配置里的state.savepoints.dir一致并且新集群的所有节点都能访问这个路径。第二步是确认状态后端类型与生成 savepoint 时一致。第三步是看 savepoint 里记录的算子 UID 是否和当前代码一致——如果代码里改了算子的结构但没保留uid()状态就对不上。我的经验是给每个关键算子显式设置uid()这样即使重命名算子或者调整代码结构只要 uid 不变状态就能恢复。另外迁移前一定要在测试环境用真实 savepoint 完整走一遍恢复流程验证数据是否连续别等到生产切换时才发现问题。6.3 日志分散造成的排查断层Application 模式下每个组件都是独立的 PodJobManager 的日志、TaskManager 的日志、K8s 的事件分散在不同的地方。作业出问题时如果只盯着作业运行日志看很容易漏掉 JobManager 启动阶段的异常或者 K8s 层面的调度失败。我的做法是把日志收集前置不依赖出了问题再去翻。K8s 环境下用统一的日志采集侧车或者节点级采集把 Flink 容器的 stdout 和文件日志都收进去按 cluster-id 打标签。这样排查时能按集群 ID 过滤出所有相关日志按时间排序看完整的上下文。同时在提交时统一设置日志级别把 JobManager 和 ResourceManager 的日志级别调到 INFO 以上确保资源申请和分配的关键信息都在日志里。还有一点容易被忽略作业重启之后日志会覆盖如果问题的现场被覆盖掉了就只能等下次复现。所以关键作业的日志要落到持久化存储或者至少在重启前把旧日志归档。6.4 类加载冲突与依赖重复打包这个坑在从 Session 模式切到 Application 模式时特别容易出现。Session 模式下所有作业共享集群 classpath依赖冲突往往一启动就暴露Application 模式下一个作业一个环境冲突反而更隐蔽——比如某个连接器的 jar 在镜像里放了一份旧版本作业的 shade 包里又带了一份新版本实际加载哪个取决于类加载顺序。排查方式是看启动日志里的类加载信息确认关键类是从哪个 jar 加载的。处理方式有两个方向一是保证镜像里的公共依赖版本统一作业的 shade 包里排除这些公共依赖用 provided scope二是彻底隔离把可能有冲突的依赖都 shade 重定位。我倾向于第一种因为镜像本身就是受控环境把公共依赖放在镜像里统一管理比每个作业自己带更清晰。6.5 资源请求与调度器的隐性冲突最后说一个偏运维的坑。Application 模式下 JobManager 和 TaskManager 都是 K8s 的 Pod资源请求requests和限制limits的设置会直接影响调度行为。如果 requests 设得过高Pod 可能一直 Pending如果 limits 设得过高而 requests 设得低可能出现节点超卖导致的实际资源不足。Flink 的配置项kubernetes.jobmanager.cpu和kubernetes.taskmanager.cpu对应的是 CPU 的 requests。内存方面kubernetes.taskmanager.memory要和taskmanager.memory.process.size保持一致。我见过因为这两处不一致TaskManager 被 K8s 判定超限直接杀掉表现是作业反复重启但日志里没有任何异常信息——因为进程是被外部杀掉的。处理这类问题要养成看 K8s 事件和 Pod 状态的习惯。kubectl describe pod里能看到 OOMKilled 或者调度失败的原因这些信息比 Flink 自己的日志更早暴露出问题。7. 把团队现状代入表格做选型理论说完了落到具体决策时我一般让人回答几个问题然后对号入座。第一个问题作业数量是多少未来半年会不会翻倍作业少于五个且稳定一作业一集群的管理成本可以接受Application 模式配原生提交就够。作业几十个且持续增加Session 模式或者 Operator 管理的 Application 模式更合适否则光是作业清单的维护就够呛。第二个问题团队里有没有人能处理 K8s 层面的问题如果没有即便公司的基础设施是 K8s也要慎重——Flink 在 K8s 上出问题一半以上的概率根因在 K8s 侧网络策略、存储挂载、RBAC 权限这些不懂 K8s 根本查不下去。这种情况下选 YARN 或者 Standalone 反而更务实。第三个问题作业的状态有多大恢复时间的要求是什么大状态作业对 Checkpoint 和恢复的要求高一作业一集群的模式能提供更干净的恢复环境共享集群里恢复一个大状态作业会影响所有人。第四个问题变更频率如何每天多次变更的作业需要的是快速提交和快速回滚的能力镜像构建和推送的时间会成为瓶颈这时候要考虑依赖分发方案或者 Operator 的 savepoint 升级能力。团队现状推荐模式推荐底座主要理由作业少、变更少、资源固定SessionStandalone运维简单无额外依赖已有 Hadoop 体系、作业中等规模ApplicationYARN复用现有资源池与权限体系已有容器平台、需要弹性伸缩ApplicationKubernetes资源按需、环境一致作业多、需要统一管理ApplicationKubernetes Operator声明式管理、自动化升级本地开发调试SessionStandalone 或本地启动快、迭代快选完之后还有一件事要做把配置模板固化下来。不要每次提交都手写一遍参数把环境相关的参数抽成变量作业相关的参数写进代码中间用一份模板串起来。这样下次换模式或者换环境时改动面会小很多。最后分享一个我在实际操作中的体会部署模式这个决定做错了一次之后往往要花几周时间才能改回来因为涉及配置、脚本、监控、告警、运维流程的全套调整。所以宁可前期多花两天把资源账算清楚、把链路走通也不要抱着先跑起来再说的心态。跑起来容易跑稳很难。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑