如果你正在做实时计算,迟早要面对一个问题:Flink 集群怎么在 Kubernetes 上稳定地跑起来。自己手写 YAML 一条条部署当然也能跑,但当作业数量上来之后,升级、扩缩容、故障恢复全都变成手工活,稍不注意就把线上搞挂。flink-operator 的出现,就是用 Kubernetes 声明式的方式统一管理 Flink 集群,你只需要提交一个 FlinkDeployment 对象,创建、更新、销毁一套 Flink 集群的活都交给 Operator 控制器去处理。这篇文章面向正在调研或准备落地的开发和运维同学,讲清楚 operator 的安装、第一个 Flink 集群的部署、核心参数怎么配,以及一堆实际部署中才会遇到的坑。
1. 为什么选择 flink-operator 而不是自己写 YAML
1.1 手动部署 Flink 到 Kubernetes 的真实痛点
先说说我自己最早是怎么干的。一个 Flink 集群在 Kubernetes 上跑起来,表面上只需要拉起 JobManager 和 TaskManager 两组 Pod,但真正要交付生产环境时,需要准备的东西远不止两个 Deployment。
JobManager 要暴露 REST 和 Web UI 端口,TaskManager 之间要互相通信,这意味着至少三个 Service;Flink 的配置文件、日志配置、高可用元数据存储,通常要拆成 ConfigMap、PVC 等对象;如果你要把 Dashboard 暴露给外部访问,还得再补 Ingress。一个最小可用的集群,YAML 加在一起轻松超过两百行。这还只是初始创建,真正痛苦的是后面的变更。
升级 Flink 版本的时候,你要改镜像 tag,还要担心 JobManager 和 TaskManager 镜像不一致导致的兼容问题;调整并行度的时候,你得手动算 TaskManager 副本数,改完 Deployment 的 replicas 之后还要盯着滚动更新;JobManager 单点挂掉的时候,Kubernetes 只会帮你重新拉起一个 Pod,但状态、Checkpoint 元数据、作业恢复这些事它一概不管。做过一轮之后你就会意识到,这种方式适合尝鲜,不适合长期运维。
1.2 Operator 模式带来的变化
Operator 模式本质上解决的是“应用生命周期管理自动化”的问题。它把运维人员对 Flink 集群的认知固化到一段控制逻辑里,通过自定义资源(CRD)暴露给上层用户。你只需要提交一个 FlinkDeployment 对象,声明“我要一个 Flink 1.17 的集群,2 个 TaskManager slot,作业并行度 4”,Operator 会负责生成底层所有 Kubernetes 资源,并持续保证实际状态向期望状态收敛。
用一个生活化的类比,传统手动部署就像你每次开店都要自己进货、摆货架、算库存;而 Operator 模式就像一个店长,你只需要告诉它“我要开一家什么样的店”,它自己去搞定货架、补货、盘点,甚至打折促销。省下来的时间,你可以真正去关心业务代码和作业稳定性。
在实际项目中,我最大的体感是:以前需要写脚本去完成的发布动作,现在变成了一条kubectl apply -f xxx.yaml;以前升级需要人工编排的“先起新 JobManager、再做 Savepoint、再切换流量”流程,变成了 Operator 内部的一个内置步骤。它更适合多团队、多作业、需要频繁发布和回滚的中大型实时计算场景。
2. flink-kubernetes-operator 的原理与版本选型
2.1 一次调谐流程是怎么跑起来的
flink-kubernetes-operator 安装到集群后,核心是一组控制器 Pod,它们监听 FlinkDeployment 资源的变化。当用户执行kubectl apply提交一个 FlinkDeployment 时,请求会先经过准入 Webhook 做参数校验和默认值填充,然后写入 etcd。
控制器通过 List-Watch 机制拿到这个新对象,进入调谐循环。它把期望状态(CR 里的 spec)和当前状态(集群中已存在的 Deployment、Service、ConfigMap)做对比,然后执行一系列操作:创建 JobManager Deployment、创建 REST Service、计算 TaskManager 副本数并创建对应 Deployment、写入 Flink 配置文件、配置 Ingress 等。操作完成后,控制器把结果写回 FlinkDeployment 的 status 字段。
所以你会看到 FlinkDeployment 对象上有类似jobManagerDeploymentStatus: READY、jobStatus.state: RUNNING这样的状态信息。这些状态不是人工维护的,而是控制器每次调谐后自动更新的。理解了这一点,排障的时候就会很自然地去看 status 和 events,而不是一头扎进 YAML 里瞎改。
2.2 CRD 版本选择:FlinkCluster 还是 FlinkDeployment
这是很多人第一次接触时会踩的坑。早期版本的 flink-kubernetes-operator 使用FlinkCluster作为核心 CRD,API 组是flink.apache.org/v1alpha1。后来社区为了更好地区分“集群管理”和“作业管理”,引入了FlinkDeployment,API 版本升级为v1beta1,把集群和应用的生命周期统一到一个对象里。
如果你是从网上翻到的一些老教程,里面很可能是FlinkCluster的写法,直接套用到新版 Operator 上通常会报no matches for kind "FlinkCluster"。我建议你以官方文档当前版本为准,新项目一律使用FlinkDeployment。它不仅能描述 JobManager 和 TaskManager 的拓扑,还能描述作业的 jar 包、并行度、升级模式,一个 CR 覆盖所有诉求。
如果你在集群里已经存在旧的 FlinkCluster 资源,升级 Operator 之前需要先确认兼容性。社区通常会在 release notes 里说明哪些旧 API 仍然可用,但长期维护的角度看,还是尽早迁移到 FlinkDeployment 更省心。
2.3 版本组合怎么选
Operator、Flink、Kubernetes 三者之间存在版本匹配关系,不能随意组合。根据我目前的经验,以下几个组合比较稳:
| 组件 | 建议版本 | 说明 |
|---|---|---|
| Kubernetes | 1.25 及以上 | 1.27 / 1.28 实测正常,旧版本需要确认 CRD 兼容性 |
| Helm | 3.8 及以上 | 安装和升级 Operator 的主要方式 |
| flink-kubernetes-operator | 1.7.x 系列 | API 稳定,支持 Flink 1.17 / 1.18 |
| Flink | 1.17 或 1.18 | 与 Operator 1.7.x 配合成熟,社区支持度好 |
| 共享存储 | 支持 ReadWriteMany | 生产环境做高可用和 Checkpoint 的硬性要求 |
选版本时最怕的是“看着差不多就上了”。比如拿一个较新的 Operator 去管理旧版 Flink 集群,API 里新增的字段会被忽略,但某些默认行为可能直接导致集群起不来。我自己的习惯是先到官方文档查看版本兼容矩阵,再在小集群上做一次冒烟测试,确认没问题后再推到生产。
3. 环境准备与 Operator 安装
3.1 动手前先过一遍环境清单
安装 Operator 之前,先把基础环境确认好。Kubernetes 集群要保持可用状态,kubectl能正确连接集群;Helm 版本不要太老,我建议 3.8 以上;集群内部需要允许创建和更新 CRD 资源,也就是你要具备足够的 RBAC 权限。
另外要确认集群是否启用了 admission webhook 支持。flink-kubernetes-operator 安装时会注册一个 ValidatingWebhookConfiguration 用于校验 FlinkDeployment 参数。如果你的集群环境比较特殊,比如某些受管集群默认禁用了 webhook 扩展能力,安装后 Operator 可能无法正常工作。遇到这种情况,需要先查询集群是否支持admissionregistration.k8s.io/v1这个 API 组。
存储方面,如果只是本地测试,可以暂时不准备共享存储;但如果你打算跑生产集群,尤其是要开启高可用模式,一定要提前准备一个 ReadWriteMany 的 StorageClass。这个点后面讲 HA 时会详细说,现在先记在心里。
3.2 Helm 方式安装 Operator
安装 flink-kubernetes-operator 最主流的方式是 Helm。把官方 Helm 仓库添加到本地后,直接安装即可。由于仓库地址在不同版本中可能会调整,我建议你在动手前到项目官方文档里复制最新的仓库地址,别凭记忆拼写。
helm repo add flink-operator <FLINK_OPERATOR_REPO_URL> helm repo update然后创建命名空间并安装:
kubectl create namespace flink helm install flink-kubernetes-operator flink-operator/flink-kubernetes-operator \ --namespace flink \ --create-namespace安装过程会创建 FlinkDeployment 相关的 CRD、服务账号、RBAC 权限、Operator Deployment 和 Webhook 配置。默认情况下,Operator 会监听所有命名空间中的 FlinkDeployment 资源,如果你希望限制监听范围,可以设置watchNamespaces参数。比如只允许在default和flink-demo两个命名空间里创建 Flink 集群:
helm install flink-kubernetes-operator flink-operator/flink-kubernetes-operator \ --namespace flink \ --set watchNamespaces="{default,flink-demo}"这个参数在生产环境很有用,可以避免 Operator 的服务账号权限被滥用。但要注意,设置之后其他命名空间里的 FlinkDeployment 就不会被处理,如果你后续有新增命名空间的需求,需要重新升级 Helm release。
3.3 验证安装是否正常
安装完成后,先确认 Pod 状态和 CRD 是否注册成功。
kubectl -n flink get pods kubectl get crd | grep flink.apache.org正常情况下,Opeartor 的 Pod 应该处于 Running 状态,CRD 列表里能看到flinkdeployments.flink.apache.org等条目。接着查看 Operator 日志,确认没有证书相关的报错:
kubectl -n flink logs deployment/flink-kubernetes-operator这里我想提醒一句:Operator 的启动日志里如果出现了 x509 证书过期或 webhook 注册失败的提示,多半是 Helm chart 自带的证书生成逻辑出了问题。比较常见的解决办法是删除相关 Secret 后重新执行helm upgrade,让 chart 重新生成证书。如果集群里已经安装了 cert-manager,也可以配置 chart 使用 cert-manager 管理证书,但这需要额外修改 values 文件,建议在正式使用前多做一次验证。
4. 部署第一个 Flink 集群
4.1 准备命名空间和服务账号
Operator 安装好之后,先创建一个专门跑 Flink 集群的命名空间,并准备一个服务账号。FlinkDeployment 里可以指定serviceAccount,这个服务账号会被 JobManager 和 TaskManager 使用。如果你的作业需要访问 Kubernetes API(比如使用 Kubernetes 原生 HA),这个服务账号还需要绑定相应 RBAC 权限。
kubectl create namespace flink-demo kubectl -n flink-demo create serviceaccount flink如果暂时不需要作业访问集群内的其他资源,使用默认服务账号也可以,但我还是建议显式创建一个专用服务账号,方便后续做权限隔离和审计。
4.2 写一个最小 FlinkDeployment
创建一个basic-example.yaml,内容如下:
apiVersion: flink.apache.org/v1beta1 kind: FlinkDeployment metadata: name: basic-example namespace: flink-demo spec: image: flink:1.17.2 flinkVersion: v1_17 flinkConfiguration: taskmanager.numberOfTaskSlots: "2" serviceAccount: flink jobManager: replicas: 1 resource: memory: "1024m" cpu: 1 taskManager: resource: memory: "1024m" cpu: 1注意几个关键点。image使用的是官方 Flink 镜像,flinkVersion的格式是v1_17,下划线而不是点。这个字段必须和镜像里的 Flink 大版本保持一致,否则 Operator 在生成配置时可能计算出错误的内存参数。taskmanager.numberOfTaskSlots表示每个 TaskManager 提供的 slot 数量,这里设置了 2,意味着后续作业如果并行度是 4,Operator 会拉起 2 个 TaskManager。
4.3 查看状态与访问 Dashboard
用kubectl apply提交 CR 后,观察资源创建情况:
kubectl apply -f basic-example.yaml kubectl -n flink-demo get flinkdeployments kubectl -n flink-demo get pods第一次创建时,JobManager 和 TaskManager 的 Pod 会依次启动。你可以用kubectl describe flinkdeployment basic-example查看详细状态,status 里如果出现jobManagerDeploymentStatus: READY,说明集群已经正常拉起。
访问 Web UI 最简单的方式是端口转发:
kubectl -n flink-demo port-forward svc/basic-example-rest 8081:8081浏览器打开localhost:8081就能看到 Flink Dashboard。这里要注意 Service 的名称规则:如果 FlinkDeployment 叫basic-example,对应的 REST Service 一般是basic-example-rest,不是basic-example。我见过不少人在这里卡住,反复检查 Service 配置,其实只是名字没找对。
4.4 用示例作业跑通全链路
集群起来了,接着验证作业提交能力。官方 Flink 镜像自带了示例 jar,可以在 FlinkDeployment 里直接声明要运行的作业。修改 YAML,加上job部分:
spec: job: jarURI: local:///opt/flink/examples/streaming/WindowWordCount.jar parallelism: 2 upgradeMode: savepoint state: running重新 apply 之后,Operator 会在集群启动完成后自动提交该作业。查看作业状态:
kubectl -n flink-demo get flinkdeployments basic-example -o jsonpath='{.status.jobStatus.state}'如果输出是RUNNING,说明从集群创建到作业提交的全链路已经跑通。upgradeMode: savepoint这个配置很关键,它决定了后续你更新作业或升级版本时,Operator 是否会先做 Savepoint 再优雅重启。对于生产环境,我强烈建议配置成savepoint,不要图省事用stateless。
5. 核心配置深度解析:资源、高可用与监控
5.1 内存和 CPU 配置的正确姿势
资源配额是部署 FlinkDeployment 时最容易出问题的地方。很多人的第一反应是给 Pod 设置一个很大的内存,然后发现 JobManager 反复重启。原因很简单:Flink 的 JVM 内存分为堆内、堆外、JVM Overhead、网络缓冲区等多个部分,仅设置 Pod 总内存并不能决定各个部分如何分配。
Operator 在生成 Flink 配置时,会根据jobManager.resource.memory和taskManager.resource.memory自动推导 JVM 参数。如果你同时在flinkConfiguration里手动写了jobmanager.memory.process.size、taskmanager.memory.process.size等参数,就很可能和 Pod 资源产生冲突。我的经验是:二选一。要么只设置 Pod 的resource.memory和resource.cpu,让 Operator 和官方镜像的 entrypoint 脚本自动计算;要么你把flinkConfiguration里的内存参数填得明明白白,同时把 Pod 内存也对应调整。
内存大小的选择也没有绝对标准。JobManager 主要承担调度和协调工作,一般 1-2G 足够;TaskManager 是真正干活的地方,建议至少给到 2-4G,slot 数量根据自己的作业并发情况设置。注意,slot 数量并不是越多越好,一个 slot 对应一个执行线程,如果 CPU 和内存不足,slot 再多也只是把线程切换开销变大。
5.2 高可用:从单副本到多副本
默认情况下,jobManager.replicas是 1,也就是只有一个 JobManager。这个配置在测试环境没问题,生产环境却是绝对不可接受的。JobManager 一旦异常宕机,集群会失去调度能力,所有运行中的作业都会失败。
Flink 在 Kubernetes 上支持原生 HA 模式,原理是利用 Kubernetes 的 ConfigMap 和 Leader Election 机制,让多个 JobManager 实例选举出一个 Leader,其余实例作为 Standby。配置方式如下:
spec: jobManager: replicas: 2 flinkConfiguration: high-availability: kubernetes high-availability.storageDir: <共享存储路径>high-availability.storageDir是 HA 元数据的存放位置,JobManager 会把作业图、Checkpoint 元数据等信息写到这里。Standby JobManager 启动时会从这个目录恢复状态,接管后无缝继续运行。
这里有个非常容易踩的坑:storageDir必须是所有 JobManager 副本都能访问的共享存储,而且存储必须支持多节点同时挂载。如果你选了普通的单节点读写 PVC,第二个 JobManager 在另一台机器上启动时根本读不到数据。生产环境请务必准备 ReadWriteMany 的存储方案,比如共享文件系统或对象存储。
5.3 监控与日志的接入思路
Flink 自身暴露了大量的 Metrics,包括吞吐量、延迟、Checkpoint 耗时、背压情况等。要采集这些数据,最常用的方案是 Prometheus。在flinkConfiguration里配置 Prometheus Reporter:
spec: flinkConfiguration: metrics.reporter.prom.factory.class: org.apache.flink.metrics.prometheus.PrometheusReporterFactory metrics.reporter.prom.port: "9249"然后通过 Prometheus 的 ServiceMonitor 或手工配置抓取任务来收集指标。如果你还没有 Prometheus,也可以先在本地端口转发 9249 端口看一眼指标是否正常输出。日志方面,Flink 默认会输出到 stdout,容器运行时可以收集这些日志。如果你需要更精细的日志配置,可以通过 FlinkDeployment 的spec.logConfig字段覆盖默认的 log4j 配置。实际项目中,我通常是把日志输出到 stdout,再由日志采集组件统一收集和检索,这样省心且不会丢日志。
6. 日常运维:升级、扩缩容与资源回收
6.1 Flink 版本和作业升级
使用 Operator 之后,升级 Flink 版本变成了一件“改一行配置”的事。你只需要修改 FlinkDeployment 中的image为新的 Flink 镜像版本,然后kubectl apply。Operator 检测到字段变化后,会执行滚动升级:先处理 JobManager,再根据计算结果调整 TaskManager。
如果作业配置了upgradeMode: savepoint,升级过程中 Operator 会先让作业停止并生成 Savepoint,然后用新镜像重新启动作业,并从该 Savepoint 恢复状态。这个过程通常不用人工干预,是 Operator 最有价值的能力之一。
但升级过程中有一个细节需要注意:新版本镜像里必须存在jarURI指向的文件。如果你把作业 jar 放在自定义镜像里,升级前一定要确认新镜像已经包含新的 jar。否则,作业会因为没有提交物而启动失败,然后一直处于 RESTARTING 状态。我踩过一次这个坑,排查了半天才发现是 jar 路径在新镜像里不存在。
6.2 并行度与 TaskManager 数量调整
很多人会误以为 TaskManager 数量是手动设置的,其实在 Operator 模式下,TaskManager 的数量是根据作业并行度和每个 TaskManager 的 slot 数自动计算的。并行度 4、每个 TaskManager 2 个 slot,就会自动拉起 2 个 TaskManager。这个设计让用户只需要关心“作业要多少并行度”,而不需要关心底层 Pod 数量。
当你调整spec.job.parallelism时,Operator 会触发一次带 Savepoint 的作业重启,并根据新的并行度和 slot 数自动调整 TaskManager 数量。如果你只调整taskmanager.numberOfTaskSlots,同样会触发资源拓扑变化。生产环境中调整并行度前,最好先评估一下状态大小和状态后端,因为并行度变化可能会影响状态分布,Savepoint 恢复速度慢会直接影响作业可用性。
6.3 删除集群后如何处理残留资源
删除 FlinkDeployment 时,Operator 会清理对应的 Deployment、Service、ConfigMap 等资源,但 PVC 和 Savepoint 数据可能不会自动删除,这是很多运维同学容易忽略的地方。
如果你创建集群时配置了 PVC 用于存储,删除 CR 后 PVC 依然存在。这是有意设计的,为了防止误删数据。想彻底清理时,需要手动删除 PVC:
kubectl -n flink-demo get pvc kubectl -n flink-demo delete pvc <PVC名称>同时,如果你用了对象存储保存 Savepoint,那部分数据也需要根据清理策略单独处置。我的建议是,在删除测试集群前,先确认没有需要保留的作业状态,再一次性清理 PVC 和存储目录。否则重建同名集群时,可能会因为旧 PVC 被占用或元数据残留,导致新集群起不来。
7. 常见问题排查速查表与实战心得
7.1 高频问题排查速查表
部署和维护过程中,我积累了一些高频问题的排查经验,整理成表格供你参考:
| 现象 | 常见原因 | 排查思路 |
|---|---|---|
| Operator Pod CrashLoopBackOff | Webhook 证书生成异常或过期 | 查看 Operator 日志,删除证书 Secret 后重新执行helm upgrade |
| FlinkDeployment 创建后一直 Pending | 资源配额不足、StorageClass 不存在 | kubectl describe pod查看 Events,确认 PVC 和节点资源 |
| JobManager 反复 CrashLoopBackOff | 内存配置自相矛盾 | 删掉flinkConfiguration里手动设置的 memory 参数,只保留 Pod 资源 |
| 作业一直 RESTARTING | 用户代码异常、Checkpoint 存储不可用 | 查看 JobManager 日志,检查 Savepoint 和 Checkpoint 路径的连通性 |
| Dashboard 无法访问 | Service 名称不对、端口转发目标错误 | 用kubectl get svc确认 Service 名称,REST 服务通常以-rest结尾 |
这套速查表覆盖了大部分初级问题。归根结底,排障的顺序一定是先看事件,再看日志,最后才去怀疑 Operator 本身。大多数问题并不是 Operator 的 bug,而是配置不当或环境不满足预期。
7.2 两个容易被忽略的坑
第一个坑是证书过期。flink-kubernetes-operator 安装时生成的 Webhook 证书通常有有效期限制。证书过期后,你再向集群提交 FlinkDeployment 时,请求会被 Webhook 拦截,报出类似failed calling webhook的错误。这时候最直接的处理方法是重新执行一次helm upgrade让证书重新生成,或者按官方文档配置证书自动续期。
第二个坑是删除集群后立即重建同名集群。由于旧集群的 PVC 和服务资源可能还处于 Terminating 状态,新集群如果同名,控制器在创建时可能因为资源冲突而一直等待。最简单的方法是把kubectl delete flinkdeployment等你确定资源完全清理后再重新创建。
7.3 最后的实操心得
我个人在实际操作中有几条心得。第一,版本一定要成套升级,不要只升级 Operator 或只升级 Flink,两者对不上是最难排查的隐形问题。第二,任何新方案先在一个小集群上跑通全流程再引入生产环境。我见过不少团队在 UAT 环境验证都没问题,一上生产就翻车,差距往往就在存储、RBAC 这类容易被忽视的基建上。第三,生产环境必须开启 HA,哪怕业务规模再小,也不要用单副本 JobManager 赌稳定性。这四个问题处理完之后,flink on k8s 这条路基本就比较顺畅了。