翼flink特点
翼flink是基于flink进行二次开发的应用平台,将flink繁琐的交互方式简化为只通过用户书写sql命令、上传udf包即可完成。翼flink目前基于云原生体系进行任务调度,使用k8s application的方式进行任务生成,减去了繁琐的集群管理步骤。
因此,本文的重点将介绍翼flink是如何在k8s application中将一个flink sql或用户自定义jar包生成任务的过程,在此过程中将涉及java、scala源码、linux操作
云原生基础环境
-
我们要提供一个docker,在这里省略安装过程。
-
在这里,我们需要搭建一个K8S环境用于提供flink任务的运行时环境。在这里推荐使用kubeadm或者一些脚本工具搭建https://gitlab.ctyuncdn.cn/liuxy28/kube-minite/-/tree/x86_64。具体过程在这里省略,可以参考上述链接中的文档进行操作。
-
需要注意的是,我们需要在相应用户的目录下提供一个kubeconfig文件,如下图所示,通过该文件,StreamPark才能顺利地调用K8S客户端提交任务,该config的内容为与K8S的ApiServer进行连接时需要使用的信息。
-
安装flink官方客户端,在这里我们使用flink官方1.14-scala2.12的客户端进行安装,如下所示
wget https://archive.apache.org/dist/flink/flink-1.14.3/flink-1.14.3-bin-scala_2.12.tgz tar zxvf ./flink-1.14.3-bin-scala_2.12.tgz
Flink流程(对比项)
和翼flink-StreamPark相比,传统的flink界面的功能性较少,并且对于k8s application模式必须要经过大数据开发工程师将代码进行打包、制作镜像、上传后才能成功的启动flink任务。其流程如下:
-
将flink代码打包成jar包
-
制作独特的任务镜像
-
将镜像推送至镜像仓供flink进行拉取,生成任务
-
通过flink脚本生成任务
./bin/flink run-application \ --target kubernetes-application \ -Dkubernetes.cluster-id=flink-cluster \ # 指定容器启动的镜像(与之前提交的保持一致) -Dkubernetes.container.image=registry.cn.hangzhou.aliyuncs.com/dockerxiahu/flink:1.15.1-app-test-05 \ -Dkubernetes.jobmanager.replicas=1 \ # 指定容器运行的命名空间 -Dkubernetes.namespace=flink \ -Dkubernetes.jobmanager.service-account=flink-service-account \ -Dkubernetes.taskmanager.cpu=1 \ -Dtaskmanager.memory.process.size=4096mb \ -Dkubernetes.jobmanager.cpu=1 \ -Djobmanager.memory.process.size=4096mb \ -Dkubernetes.rest-service.exposed.type=NodePort \ -Dclassloader.resolve-order=parent-first \ # yaml 模板,为解决hosts映射,后续可以通过编排此yaml文件,实现动态替换启动jar包和配置文件 -Dkubernetes.pod-template-file=/opt/flink-1.14.2/flink-templeta.yaml \ # Main方法 -c com.clb.hadoop.hub.flink.realtime.launch.FlinkConsumeKafkaToHdfs \ # 启动Jar包和启动配置文件的绝对路径(容器内部,不是宿主机) local:///opt/flink/lib/flink-realtime-1.0-SNAPSHOT.jar /opt/flink/usrlib/flink-realtime-hdfs.properties
通过如上几个步骤,才能顺利地将一个flink任务成功的执行。
翼flink-StreamPark流程
将用户的sql、UDF包快捷便利地上传,快速的仅仅通过翼flink-StreamPark端启动用户的flinksql任务,将以上几个步骤一键式的完成便是翼flink-StreamPark的主要工作。关于翼flink-StreamPark的使用在这里将不再过多介绍。如下,将对翼flink-StreamPark的代码进行介绍。
任务初始化构建
任务的初始化在翼flink-StreamPark端一般要求将我们的sql打包成k8s环境能够运行的镜像,整体流程的起点为sql,终点为生成k8s任务镜像并上传至镜像仓供后续调用。详细代码请见,以下将对各个步骤分步讲解org/apache/streampark/flink/packer/pipeline/impl/FlinkK8sApplicationBuildPipeline.scala
-
在StreamPark本机环境中构建工作空间环境,这里主要就是新建一个目录,用于存储用户数据,简单地通过目录进行隔离。在这里不再详解
// Step-1: init build workspace of flink job // the sub workspace dir like: APP_WORKSPACE/k8s-clusterId@k8s-namespace/ val buildWorkspace = execStep(1) { val buildWorkspace = s"${request.workspace}/${request.clusterId}@${request.k8sNamespace}" LfsOperator.mkCleanDirs(buildWorkspace) logInfo(s"recreate building workspace: $buildWorkspace") buildWorkspace }.getOrElse(throw getError.exception)
-
将用户的k8s Podtemplate导出至翼flink-StreamPark端的相应存储目录
在这里,先对k8s template进行介绍,一般情况下flink原生k8s调度系统将会提供一个默认的k8s资源文件来运行我们的任务,如果不指定podtemplate的话将使用默认的pod启动任务。拓展地,Flink 允许用户通过模板文件定义 JobManager 和 TaskManager pod。 这允许直接支持 Flink Kubernetes 配置选项不支持的高级功能。 使用 kubernetes.pod-template-file 指定包含 pod 定义的本地文件。 它将用于初始化 JobManager 和 TaskManager。
在StreamPark端,同样地可以指定podTemplate,而在这一步主要仍是将podTemplate导出至本地,供后续使用。
// Step-2: export k8s pod template files val podTemplatePaths = request.flinkPodTemplate match { case podTemplate if podTemplate.isEmpty => skipStep(2) Map[String, String]() case podTemplate => execStep(2) { val podTemplateFiles = PodTemplateTool.preparePodTemplateFiles(buildWorkspace, podTemplate).tmplFiles logInfo(s"export flink podTemplates: ${podTemplateFiles.values.mkString(",")}") podTemplateFiles }.getOrElse(throw getError.exception) }
-
打包生成任务的运行时jar包
在这里,是一个复杂的过程,打包运行时jar包主要将如下一些要素打包成一个fatjar(所有依赖打包在一起,防止依赖缺失,直接通过java -jar即可调用)
- 必要的基础组件(如logback、json解析组件)
- 用户的作业依赖(UDF)
- 翼flink-StreamPark自研的Sql语句翻译器(SqlClient.scala所在的包,主要将用户的sql动态的输入进任务中,直接翻译成算子任务,免去了sql语句仍需要打包成flink Jar包的繁琐操作)
最终地,将生成能够动态解析用户sql的flink运行jar包,如下本jar包的入口信息(MANIFEST.MF),可见其标记了入口的主类,如此Flink加载此jar包时候将优先使用sqlclient作为flink任务的入口。
Manifest-Version: 1.0 Implementation-Title: StreamPark : Flink Shims 1.14 Implementation-Version: 2.0.0 Specification-Vendor: Apache Software Foundation Specification-Title: StreamPark : Flink Shims 1.14 Build-Jdk-Spec: 1.8 Created-By: Maven JAR Plugin 3.2.2 Specification-Version: 2.0 Implementation-Vendor: Apache Software Foundation Main-Class: org.apache.streampark.flink.cli.SqlClient
-
任务镜像的生成
目前而言,任务的镜像主要作用为将上述第3步的运行时jar包加入到镜像目录中,详细地请看如下的dockerfile,主要使用flink官方提供的基础镜像基础上将用户的jar包添加到
/usr/local/flink/usrlib
目录下。之后此镜像将作为我们任务的运行时镜像,目前阶段通过镜像仓保存。FROM apache/flink:1.14.3-scala_2.12 RUN mkdir -p $FLINK_HOME/usrlib COPY lib $FLINK_HOME/lib/ COPY streampark-flinkjob_test-local.jar $FLINK_HOME/usrlib/streampark-flinkjob_test-local.jar
对于翼flink而言,最终的任务执行端将是上述jar包中的org.apache.streampark.flink.cli.SqlClient
类的主函数。
-
任务镜像的推送
镜像的推送主要地将通过翼flink-StreamPark所在的宿主机上的docker将上一步生成的镜像推送到目标镜像仓,在这里相关的镜像仓的设定应该提前设定好,并且需要注意的,该任务镜像包含了用户自定义的UDF可能存在一定的隐私性,最好能够提供一个私有的镜像仓(如自建harbor)。当镜像推送成功后,云原生底座才能进行调用
一般地,在翼flink-StreamPark启动前,应该在其相应的数据库中初始化相应的docker信息,如下所示。
-
Ingress的生成(可选)
任务启动
StreamPark的实现
目前地,对于flink任务启动,主要是用K8s Application模式对任务进行启动。主要地,其步骤和本文中介绍的手动启动相似,主要则是通过flink官方原生java任务客户端。对于StreamPark中的代码,唯一做的事就是把用户的相关信息解析成flink支持的参数,与K8S交互部分完全使用flink端原生的方法。
最终地与手动启动类似,通过参数进行控制。
与K8S交互
StreamPark中,主要调用相关依赖org.apache.flink.kubernetes.KubernetesClusterDescriptor
的deployApplicationCluster
方法启动flink任务,所有的和云原生底座的交互将在这里进行。详情参见org.apache.streampark.flink.client.impl.KubernetesNativeApplicationSubmit
。对于StreamPark端所做的工作主要为通过flink源码中通过命令行模式执行的客户端org.apache.flink.client.cli.CliFrontend
转化为自动执行。
所有的一切和k8s操作相关的代码请看org.apache.flink.kubernetes.kubeclient.Fabric8FlinkKubeClient
中,其中所有和k8s操作的细节都有介绍。由于本文作者关于K8S的操作技术不是很熟练。后续将对此加强解读。
对于翼flink-StreamPark而言主要是调用此方法启动一个K8s的Deployment资源,并调用一些PodTemplate进行初始化,这里给出一个样例文件如下,此为StreamPark端调用原生flink-kubernetes依赖生成的结果。其底层调用的是fabric系列的k8s操作sdk进行操作。
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "1"
creationTimestamp: "2023-04-03T08:54:11Z"
generation: 1
labels:
app: ceshiaa
component: jobmanager
type: flink-native-kubernetes
name: ceshiaa
namespace: flink-dev
resourceVersion: "41858779"
uid: 1d5e5467-f430-4e28-91ef-d03146b52854
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: ceshiaa
component: jobmanager
type: flink-native-kubernetes
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
app: ceshiaa
component: jobmanager
type: flink-native-kubernetes
spec:
containers:
- args:
- bash
- -c
- kubernetes-jobmanager.sh kubernetes-application
command:
- /docker-entrypoint.sh
env:
- name: _POD_IP_ADDRESS
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: status.podIP
image: lvlin241/streamparkflinkjob-flink-dev-ceshiaa
imagePullPolicy: Always
name: flink-main-container
ports:
- containerPort: 8081
name: rest
protocol: TCP
- containerPort: 6123
name: jobmanager-rpc
protocol: TCP
- containerPort: 6124
name: blobserver
protocol: TCP
resources:
limits:
cpu: "1"
memory: 4Gi
requests:
cpu: "1"
memory: 4Gi
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /opt/flink/conf
name: flink-config-volume
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
serviceAccount: default
serviceAccountName: default
terminationGracePeriodSeconds: 30
volumes:
- configMap:
defaultMode: 420
items:
- key: logback-console.xml
path: logback-console.xml
- key: log4j-console.properties
path: log4j-console.properties
- key: flink-conf.yaml
path: flink-conf.yaml
name: flink-config-ceshiaa
name: flink-config-volume
其中,关于flink任务的K8S配置项,请参考org.apache.flink.kubernetes.configuration.KubernetesConfigOptions
中的设定,详情可查看官方文档https://nightlies.apache.org/flink/flink-docs-release-1.13/zh/docs/deployment/config/#kubernetes。对于StreamPark唯一能做的就是修改这些参数的值,将K8s信息传递给flink进相应的资源生成。
UI&任务日志查询
启动flink任务日志后,允许通过界面查询日志。由于目前模式均为K8S Application模式部署。flink web ui默认通过nodeport方式获取。一般地当任务成功启动后,相应的web ui将提供出来,但目前翼flink-StreamPark暂不支持该功能。
对于任务日志,翼flink-StreamPark则是通过访问底层云底座的deployment资源获得,详细代码请见org.apache.streampark.flink.kubernetes.helper.KubernetesDeploymentHelper
,其方法如下所示:
def watchDeploymentLog(nameSpace: String, jobName: String, jobId: String): String = {
tryWithResource(KubernetesRetriever.newK8sClient()) { client =>
val path = KubernetesDeploymentHelper.getJobLog(jobId)
val file = new File(path)
val log = client.apps.deployments.inNamespace(nameSpace).withName(jobName).getLog
Files.asCharSink(file, Charsets.UTF_8).write(log)
path
}
}
之后,StreamPark端将会把查询到的日志保存在其工作目录下,并以该任务的job_id作为标识进行保存,保存至fl一般地,将会把用户日志保存在安装目录下的temp目录中,提供持久化存储,供后续使用。