flink-operator 实战:Flink on Kubernetes 从部署到稳定运行
前阵子刚在一个客户的实时计算集群上把 Flink on K8s 这套流程完整落地踩了不少坑也把 flink-operator 的部署方式彻底捋了一遍。说实话现在再让我用传统方式手动去管理 Flink 集群我是一万个不愿意。在 Kubernetes 上跑 Flink 本身不复杂但真正把“跑起来”变成“稳定地跑、可声明地管理、能跟随业务自由伸缩”没有一个趁手的 Operator 是不行的。这篇文章就围绕 flink-operator 的部署全过程展开把版本选型、环境准备、实际安装、任务提交到问题排查一条线讲清楚适合正在规划 Flink 上容器或者已经被手动部署搞得焦头烂额的工程师参考。1. 为什么 Flink on K8s 需要一个 Operator1.1 手动部署 Flink 集群的痛苦如果只用原生 Kubernetes 资源去部署 Flink通常你会创建 Deployment、Service、ConfigMap甚至 StatefulSet把 JobManager 和 TaskManager 分别定义好。第一次这么折腾确实能跑通但很快问题就来了TaskManager 扩缩容的时候新 Pod 要能正确加入集群JobManager 和 TaskManager 之间的服务发现得稳定作业失败要自动重启不能每次都靠人去 kubectl delete版本升级的时候要保证 JobManager 和 TaskManager 一起平滑升级而不是各跑各的版本。这些事不是不能用脚本做但脚本只能解决“一次性操作”解决不了“状态保持”。Flink 集群是有状态的JobManager 里存着作业的元数据、Checkpoint 信息TaskManager 在运行期会持续上报心跳。手动管理的时候任何一次 Pod 重建都可能留下孤儿节点或者注册信息错乱。做过一次线上事故排查的人对这类问题都会心有余悸。1.2 Operator 模式到底解决了什么Operator 的核心思路是把“运维 Flink 集群”这件事固化成程序逻辑。flink-operator 会 watch 一个名为 FlinkDeployment 的 CRD 资源你只需要提交一份 YAML 声明“我要一个两核四内存的 JobManager、五个 TaskManager、跑某某镜像”剩下的创建工作、健康检查、扩缩容、滚动更新都由 Operator 完成。它跟手工部署最大的区别不只是“自动创建 Pod”而是“持续维持期望状态”。比如你把并行度从 5 改成 8Operator 会计算需要的 TaskManager 数量再执行一次受控的扩缩容你升级镜像版本它会用滚动更新的方式替换 Pod避免 JobManager 和 TaskManager 版本不匹配导致的作业恢复失败。这个过程是声明式的你可以像管理 Deployment 一样管理 Flink 集群而不是每天对着 kubectl 敲命令。1.3 官方三种部署方式对比Flink on K8s 有三种主流方式Session 模式先启动一个常驻的 Flink 集群再用客户端往里面提交作业。适合短任务、交互式开发但多个作业共享资源隔离性差。Application 模式每个作业都有自己的 Flink 集群作业的 main 方法打包在镜像里启动集群就是提交作业。适合生产环境长跑任务资源隔离好出问题只影响单个作业。Standalone 模式完全手动部署 Flink 组件不依赖资源管理器这也是过去最常被拿来“硬怼”K8s 的方式但维护成本极高。flink-operator 主要管理 Application 模式和 Session 模式。我强烈建议生产环境一律用 Application 模式原因后面写任务部署的时候会细说。2. 部署前的版本选型与环境准备2.1 版本兼容性不要想当然在部署前第一件事是确认 flink-operator 版本、Flink 版本和 Kubernetes 版本的兼容关系。有一段时间我没看兼容矩阵直接把新版本 Operator 配到老集群上结果 webhook 证书一直报错折腾半天才发现是版本不匹配。这里列一个我当时实践验证过的版本组合参考组件版本选择Kubernetes1.24 及以上建议 1.26 左右Apache Flink1.17.2 或 1.18.1flink-operator1.7.0 或 1.8.0Helm3.8 以上cert-manager1.12 以上如果走 helm 内置 webhook需要注意的是flink-operator 的 1.8 版本对 Flink 1.18 支持更完善如果你用的是 Flink 1.17不必强求最新 Operator。反过来如果你已经在生产环境跑了 Flink 1.15那 Operator 版本最好就固定在某个经过验证的区间别为了追新随便升。大数据组件最重要的是稳定不是新。2.2 集群资源与命名空间规划Flink 作业跑起来JobManager 和 TaskManager 都是吃资源的。在部署 Operator 本身之前建议先规划好命名空间flink-operator存放 Operator 自身的 Deployment、RBAC、Webhook 配置。flink存放业务 FlinkDeployment 资源及运行中的 Flink 集群 Pod。这样管理起来很清晰Operator 和业务资源分开后续如果要做多租户再按团队拆分命名空间也方便。资源上至少保证集群剩余 2C4G 给 Operator 和示例作业否则部署完 Operator 发现没有资源跑 Flink 作业又得临时扩容。2.3 提前安装 Helm 和 cert-managerflink-operator 官方推荐用 Helm 安装好处是所有资源打包在一个 Chart 里卸载、升级都方便。Helm 安装很简单几行命令的事这里不多说。cert-manager 是关键依赖。flink-operator 的 Admission Webhook 需要 TLS 证书如果集群里没有 cert-managerOperator 的 webhook 会因为证书缺失而启动失败。我见过不少人卡在这一步Operator Pod 明明 Running但创建 FlinkDeployment 资源时请求直接被 API Server 拦截报Internal error occurred: failed calling webhook。这就是 cert-manager 没装或者证书没签发导致的。安装 cert-manager 的方式可以直接用官方 Helm Charthelm repo add jetstack https://charts.jetstack.io helm repo update helm install cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ --set installCRDstrue安装完成后可以检查 CertManager 的 Pod 是否全部 Readykubectl get pods -n cert-manager注意如果公司内网环境拉取镜像受限把 cert-manager 相关镜像提前同步到内部镜像仓库不然 helm install 会一直卡在 ImagePullBackOff。3. 一步步部署 flink-operator3.1 添加 Helm 仓库并准备配置项flink-operator 的 Helm Chart 在官方仓库里发布。先添加仓库helm repo add flink-operator https://flink.apache.org添加完仓库后不要急着 install先看下这个 Chart 支持哪些配置项helm show values flink-operator/flink-operator这里有一个比较容易忽略的点如果你是用 cert-manager 签发证书要在 Helm values 里把webhook.create打开并设置webhook.certManager相关配置。不同版本的 Chart 字段名可能不一样以实际情况为准。我通常习惯先看一遍默认 values再决定要不要覆盖。3.2 创建命名空间并安装 Operator创建命名空间并执行安装kubectl create namespace flink-operator helm install flink-operator flink-operator/flink-operator \ --namespace flink-operator \ --set webhook.createtrue执行完成后用下面命令确认相关资源kubectl get all -n flink-operator正常情况下你会看到一个名为flink-operator-controller-manager-xxx的 Pod 处于 Running 状态这个名称前缀是 Controller 模式的典型特征。如果 Pod 启动失败一般集中在镜像拉不下来、RBAC 配置错误、webhook 证书问题这几类可以先看日志定位kubectl logs -n flink-operator deploy/flink-operator-controller-manager --tail2003.3 CRD 资源确认Operator 安装成功的一个关键标志是集群里出现了 FlinkDeployment 这个 CRD。可以通过以下命令验证kubectl get crd | grep flink如果看到flinkdeployments.flink.apache.org说明 Operator 已经成功注册了自定义资源。接下来你就能用 kubectl 直接管理 Flink 集群了。到这里一个基础的 flink-operator 就装完了。但别急装完不等于能用真正跑起第一个 FlinkDeployment 的时候还有不少细节要注意。4. 用 Operator 提交第一个 Flink 作业4.1 编写 FlinkDeployment 清单FlinkDeployment 是 flink-operator 管理的核心资源。一个最简单的 Application 模式清单如下apiVersion: flink.apache.org/v1beta1 kind: FlinkDeployment metadata: name: etl-demo-job namespace: flink spec: flinkVersion: v1_18 flinkConfiguration: taskmanager.numberOfTaskSlots: 2 high-availability.type: kubernetes jobManager: resource: cpu: 1 memory: 1g taskManager: resource: cpu: 1 memory: 2g podTemplate: apiVersion: v1 kind: Pod metadata: labels: app: flink-etl spec: containers: - name: flink-main-container image: myrepo/flink-etl-job:1.0.0 imagePullPolicy: Always env: - name: KAFKA_BOOTSTRAP_SERVERS value: kafka-broker:9092 job: jarURI: local:///opt/flink/usrlib/etl-job.jar entryClass: com.demo.streaming.EtlJob args: --kafka.topic input_events --sink.type clickhouse parallelism: 2这里需要解释几个关键点flinkVersion字段的值格式是v1_18下划线而不是点号别写错了。podTemplate是可选的但实际生产中几乎必用。因为镜像里有环境变量、挂载卷、自定义 DNS 配置都需要通过这个字段注入。jobManager.resource.memory是给 JVM 的总体内存Flink 会自动按比例分配堆内和堆外内存你不用手动去算 JVM 参数。job.jarURI在 Application 模式下通常指向镜像里的本地路径我这里是local:///opt/flink/usrlib/前提是打包镜像时把作业 jar 放进去。4.2 JobManager 和 TaskManager 资源如何定资源定多少不能拍脑袋。我一般按这个思路估算先确定作业的并行度。比如你要处理 12 个分区的 Kafka Topic并行度至少是 12 才能保证每个分区都被消费。TaskManager 数量 并行度 / 单 TaskManager 的 Slot 数。taskmanager.numberOfTaskSlots一般设为 2 到 4不要太大否则单点故障影响面太广。单 Slot 内存 每条记录大小 × 单算子状态大小 × 预估堆积量再加 OS 和网络开销。实际经验值生产环境的 Slot 内存不要低于 1G否则状态稍大就容易 OOM。举个例子24 个并行度每 Slot 给 1G 堆上内存堆外加 1G总内存约 2G。TaskManager 数量 24 / 4 6 个每个 TaskManager 内存 8G。这个配置相对均衡故障恢复时丢失的计算范围可控。4.3 提交作业与状态查看编写好 YAML 后直接应用即可kubectl apply -f flink-deployment.yaml查看 FlinkDeployment 状态kubectl get flinkdeployment -n flink正常时状态列会从Creating变为Running。如果变成Failed说明作业启动过程中出了异常这时要查两处Operator 日志和 Flink 作业日志。查看具体日志kubectl logs -n flink deploy/etl-demo-job --tail300这里有个命令行技巧通过deploy/[FlinkDeployment名]能直接看 JobManager 的日志而 TaskManager 日志要看具体的 Podkubectl get pods -n flink -l flink.apache.org/applicationetl-demo-job kubectl logs -n flink taskmanager-pod-name --tail200我第一次用 Operator 时找日志找了好久后来才发现 Operator 把 Flink 集群的组件都打上了统一的 label用 selector 过滤一下就很清晰。5. 生产环境高频问题与排查实录5.1 Operator 和 Webhook 的证书问题这个必须放在最前面说因为遇到的人最多。症状提交 FlinkDeployment 时报failed calling webhook mutating.flink.apache.org或者 API Server 拒绝创建资源。原因webhook 的 TLS 证书没有正确签发通常是 cert-manager 没装或者 Operator 启动时找不到证书。还有一种是 Helm 安装时 webhook 配置没打开。排查步骤# 查看 webhook 配置 kubectl get validatingwebhookconfiguration | grep flink kubectl get mutatingwebhookconfiguration | grep flink # 查看 webhook 引用的 secret 是否存在 kubectl get secret -n flink-operator | grep webhook如果确认是证书问题最干净的办法是把 Operator 卸载重装同时确保 cert-manager 在它之前安装好。不要尝试手动生成自签证书去填坑后续证书轮换会折磨死你。5.2 FlinkDeployment 卡在 Creating 状态症状kubectl get flinkdeployment一直显示Creating但不论等多久都不变。原因Operator 在创建 Flink 集群时大概率卡在状态同步上。常见原因包括镜像名写错了Pod 创建失败。拉取镜像的 Secret 没有配置导致 ImagePullBackOff。TaskManager 一直无法注册到 JobManager通常是网络问题或者服务发现配置错误。排查要分两端先看 Operator 日志里有没有报 watch 资源的错误再看 Flink JobManager 日志里有没有报 TaskManager 连接拒绝。在这里我特别提醒一下私有镜像仓库的坑很多公司内部仓库是 HTTPS 自签证书Flink 镜像里如果没有把我们的 CA 证书加进去Pod 启动后从仓库拉镜像会一直失败。解决办法是把自签证书打进基础镜像或者把内部仓库配置为insecure-registries后者更适合内网环境。5.3 Web UI 访问不了Flink Web UI 默认监听在 JobManager 的 8081 端口但 Operator 创建的 Service 类型通常是 ClusterIP外部访问不方便。我建议的做法是用 Ingress 暴露配上域名和 TLS。FlinkDeployment 的 Service 命名规则一般是flinkdeployment名-rest比如etl-demo-job-rest。Ingress 这样写apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: flink-web-ui namespace: flink spec: rules: - host: flink.etl.internal http: paths: - path: / pathType: Prefix backend: service: name: etl-demo-job-rest port: number: 8081这类问题通常不是因为 Ingress 配置而是因为端口没对。有个坑点是 Flink 开启 Web 提交作业的功能时可能还会需要额外的端口暴露不过新版本默认关闭了 web 提交问题不大。5.4 作业自动重启与 Checkpoint 恢复生产环境作业总会有失败的时候关键是失败后能不能自动恢复。flink-operator 里restartNonce是一个很有用的字段。作业失败后你修改spec.restartNonce的值随便改个字符串Operator 会触发一次作业重启。这比kubectl delete pod优雅得多因为它会先尝试从最近一次 Checkpoint 恢复。spec: restartNonce: 20240617-01我遇到过一种情况作业因为上游数据延迟持续失败Checkpoint 一直在生成但恢复时总是报错。后来发现是 Checkpoint 路径的权限问题。Flink 使用 S3 做 Checkpoint 时如果 IAM 权限没配好看起来作业在正常跑但一触发重启就起不来。这类问题平时不会暴露但一定要在前期的演练里测到。5.5 Operator 版本升级注意事项如果后续要升级 flink-operator建议先在一个非生产命名空间里做验证。因为 Operator 升级可能会更新 CRD 的 schema旧的 FlinkDeployment 资源如果缺少新字段Controller 会直接报错。升级时先用 helm 管理保留好 values 文件。升级后把 FlinkDeployment 资源重新 apply 一遍触发 reconcile确保新版本 Operator 能满意地看到每个资源状态。6. 从部署到稳定运行的实践经验6.1 把 Flink 打包进镜像的几个细节说到镜像很多人在这一步翻车。Flink 镜像不能光把作业 jar 塞进去还需要处理lib目录里的依赖冲突。我的经验是使用 Flink 官方基础镜像然后用flink-containment这个思路把作业依赖和 Flink 框架依赖分开。作业用到的 Kafka connector、JDBC driver 等都放进/opt/flink/usrlib这样它们会自动进入 classpath而不会污染 Flink 自身依赖。合并 fat jar 的做法虽然简单但在依赖版本冲突时排错非常痛苦。还需要注意在 Application 模式下镜像的启动命令不能被覆盖。PodTemplate 里可以覆盖环境变量和资源但不能改args否则 Operator 注入的启动命令会失效作业压根起不来。这块建议通过 FlinkDeployment 的job.entryClass和job.args来表达别在 PodTemplate 里写自定义 entrypoint。6.2 监控面板要一起搭起来Flink on K8s 部署完了监控是必须跟上的。Operator 之下每个 TaskManager 都会暴露 metrics 端口但要把这些指标对接进 Prometheus还是要做一些工作。我建议在 FlinkConfiguration 里开启 Prometheus 插件spec: flinkConfiguration: metrics.reporter.prometheus.factory.class: org.apache.flink.metrics.prometheus.PrometheusReporterFactory metrics.reporter.prometheus.port: 9249然后配置 Prometheus 的 PodMonitor按 FlinkDeployment 相关的 label 自动发现目标。这里有一个细节TaskManager 的指标端口是动态分配的必须依赖服务发现来自动抓取不能写死 target。用 PodMonitor 选好 label才能把flink_jobmanager_和flink_taskmanager_各项指标捞出来。6.3 资源配额和租户规划如果集群里同时跑多个团队的任务一定要在命名空间级别设置资源配额。否则某个团队的 Flink 集群突然扩容可能把集群的资源打满影响其他团队。可以为每个 FlinkDeployment 设置nodeSelector或者资源请求把不同的业务隔离到不同节点池。这个在编写podTemplate时配置不需要改 Operator 代码。本质上是把 K8s 的资源管理能力通过声明式配置转递给 Flink 集群。有了配额之后还要设置 Flink 作业的 CPU 和内存限制。这里有一个容易出问题的点resources.memory给的是 Flink 控制的运维内存而 K8s 层面的limits.memory是容器级限制。两者必须留出余量否则 TaskManager 可能会在 K8s 层被 OOMKilled而 Flink 自己一点日志都没有。比较稳妥的做法是K8s 的limits.memory比 Flink 的resource.memory大 10% 到 20%。例如 Flink 侧配 4gK8s 侧配 5g给 JVM 之外的 off-heap 和 native 内存留出空间。6.4 我建议生产环境直接上 Application 模式虽然 flink-operator 同时支持 Session 和 Application但我个人强烈建议生产任务全部用 Application 模式。Session 模式的优点是省事一个集群跑多个小作业资源利用率高。但代价是某个任务状态异常或者内存泄漏可能拖垮整个 Session升级任务的 jar 包也要小心不能影响同一个 Session 里的其他任务。我见过不止一次因为 Session 里跑了一个问题任务导致所有任务一起重启的事故。Application 模式每个作业一个集群隔离性是最好的。虽然多消耗一点 JobManager 的资源但是在稳定性面前这点成本不值一提。加上 flink-operator 本身支持快速创建和销毁集群Application 模式的运维成本已经被降到了可以接受的程度。写在最后的一些体会这条路走下来我最大的感受是flink-operator 本身部署并不难难的是配套体系。版本兼容、证书、镜像仓库、监控告警、权限规划任何一个环节掉链子都会在后续使用中放大成生产事故。如果你是从零开始建议不要直接在生产环境动手。先在测试集群上把整个链路跑一遍部署 Operator、提交一个从 Kafka 读到 ClickHouse 的测试作业手动杀掉 TaskManager 验证自动恢复最后再配好监控。这套流程走顺了生产环境上基本就是换个镜像、改一下资源配置的事。另外一个小经验把部署依赖的 Helm Chart、values 文件和 FlinkDeployment 模板全部放到代码仓库里跟着版本走。不要靠人在终端里手敲命令维护环境否则半年之后没人能说清楚现在线上这套 Flink 集群到底是怎么来的。声明式管理不只是 K8s 的理念它也应该是我们团队运维流程的底线。