From 830e265162827c9605f290264bc289254ff25689 Mon Sep 17 00:00:00 2001 From: Mohammadreza Khani Date: Sat, 5 Apr 2025 01:39:02 +0330 Subject: [PATCH 1/2] feat: apply new helm structure use minio s3 for savepoint and checkpoint path separate task-manager, job-manager and operator use statefulset for task-manager to handle replication support basic credential for download jar request update to flink 1.20.1 --- .vscode/settings.json | 1 + Dockerfile | 3 +- Dockerfile.flink | 9 +- crds.yaml | 4 + example-job.yaml | 13 +- helm/chart/Chart.lock | 6 + helm/chart/Chart.yaml | 4 + helm/chart/charts/minio-16.0.2.tgz | Bin 0 -> 57792 bytes helm/chart/templates/NOTES.txt | 4 +- helm/chart/templates/flink/config.yaml | 33 ++++ helm/chart/templates/flink/data.pvc.yaml | 10 -- helm/chart/templates/flink/deploy.yaml | 165 ------------------ helm/chart/templates/flink/ha.pvc.yaml | 2 +- .../templates/flink/job-manager-deploy.yaml | 84 +++++++++ .../templates/flink/job-manager-service.yaml | 28 +++ helm/chart/templates/flink/savepoint.pvc.yaml | 10 -- helm/chart/templates/flink/service.yaml | 19 -- .../flink/task-manager-statefulset.yaml | 58 ++++++ helm/chart/templates/operator/deployment.yaml | 0 helm/chart/templates/operator/service.yaml | 8 +- .../chart/templates/operator/statefulset.yaml | 66 +++++++ helm/chart/values.yaml | 23 +-- helm/index.yaml | 20 +-- internal/crd/v1alpha1/flink_job.go | 16 +- internal/jar/jar.go | 54 +++++- internal/managed_job/jar.go | 2 +- 26 files changed, 386 insertions(+), 256 deletions(-) create mode 100644 helm/chart/Chart.lock create mode 100644 helm/chart/charts/minio-16.0.2.tgz create mode 100644 helm/chart/templates/flink/config.yaml delete mode 100644 helm/chart/templates/flink/data.pvc.yaml delete mode 100644 helm/chart/templates/flink/deploy.yaml create mode 100644 helm/chart/templates/flink/job-manager-deploy.yaml create mode 100644 helm/chart/templates/flink/job-manager-service.yaml delete mode 100644 helm/chart/templates/flink/savepoint.pvc.yaml delete mode 100644 helm/chart/templates/flink/service.yaml create mode 100644 helm/chart/templates/flink/task-manager-statefulset.yaml delete mode 100644 helm/chart/templates/operator/deployment.yaml create mode 100644 helm/chart/templates/operator/statefulset.yaml diff --git a/.vscode/settings.json b/.vscode/settings.json index 37077b5..54a8d12 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,6 +14,7 @@ "nindent", "reactivex", "repsert", + "taskmanager", "tolerations" ] } \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index beb8f96..3ad486a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM public.ecr.aws/docker/library/golang:1.23.4-bookworm AS build ARG upx_version=4.2.4 -RUN apt-get update && apt-get install -y --no-install-recommends xz-utils && \ +RUN apt-get update && apt-get install -y --no-install-recommends xz-utils ca-certificates && \ curl -Ls https://github.com/upx/upx/releases/download/v${upx_version}/upx-${upx_version}-amd64_linux.tar.xz -o - | tar xvJf - -C /tmp && \ cp /tmp/upx-${upx_version}-amd64_linux/upx /usr/local/bin/ && \ chmod +x /usr/local/bin/upx && \ @@ -27,6 +27,7 @@ FROM public.ecr.aws/docker/library/busybox:1.37.0 AS final COPY --from=build /flink-kube-operator /flink-kube-operator +COPY --from=build /etc/ssl/certs /etc/ssl/certs EXPOSE 8083 diff --git a/Dockerfile.flink b/Dockerfile.flink index 584fb83..8a10ef3 100644 --- a/Dockerfile.flink +++ b/Dockerfile.flink @@ -1,4 +1,4 @@ -FROM public.ecr.aws/docker/library/flink:1.20.0-scala_2.12-java17 +FROM public.ecr.aws/docker/library/flink:1.20.1-scala_2.12-java17 # Set working directory WORKDIR /opt/flink @@ -15,17 +15,18 @@ RUN chmod +x /opt/flink/bin/start-cluster.sh RUN wget -q https://repo1.maven.org/maven2/org/apache/flink/flink-connector-kafka/3.4.0-1.20/flink-connector-kafka-3.4.0-1.20.jar -P /opt/flink/lib/ RUN wget -q https://repo1.maven.org/maven2/org/apache/kafka/kafka-clients/3.9.0/kafka-clients-3.9.0.jar -P /opt/flink/lib/ -RUN wget -q https://repo1.maven.org/maven2/org/apache/flink/flink-avro/1.20.0/flink-avro-1.20.0.jar -P /opt/flink/lib/ -RUN wget -q https://repo1.maven.org/maven2/org/apache/flink/flink-avro-confluent-registry/1.20.0/flink-avro-confluent-registry-1.20.0.jar -P /opt/flink/lib/ +RUN wget -q https://repo1.maven.org/maven2/org/apache/flink/flink-avro/1.20.1/flink-avro-1.20.1.jar -P /opt/flink/lib/ +RUN wget -q https://repo1.maven.org/maven2/org/apache/flink/flink-avro-confluent-registry/1.20.1/flink-avro-confluent-registry-1.20.1.jar -P /opt/flink/lib/ RUN wget -q https://repo1.maven.org/maven2/name/nkonev/flink/flink-sql-connector-clickhouse/1.17.1-8/flink-sql-connector-clickhouse-1.17.1-8.jar -P /opt/flink/lib/ RUN wget -q https://repo1.maven.org/maven2/org/apache/flink/flink-sql-connector-postgres-cdc/3.2.1/flink-sql-connector-postgres-cdc-3.2.1.jar -P /opt/flink/lib/ RUN wget -q https://repo1.maven.org/maven2/org/apache/avro/avro/1.8.2/avro-1.8.2.jar -P /opt/flink/lib/ RUN wget -q https://repo1.maven.org/maven2/net/objecthunter/exp4j/0.4.5/exp4j-0.4.5.jar -P /opt/flink/lib/ RUN wget -q https://jdbc.postgresql.org/download/postgresql-42.7.4.jar -P /opt/flink/lib/ -RUN wget -q https://repo1.maven.org/maven2/org/apache/flink/flink-sql-jdbc-driver/1.20.0/flink-sql-jdbc-driver-1.20.0.jar -P /opt/flink/lib/ +RUN wget -q https://repo1.maven.org/maven2/org/apache/flink/flink-sql-jdbc-driver/1.20.1/flink-sql-jdbc-driver-1.20.1.jar -P /opt/flink/lib/ RUN wget -q https://repo1.maven.org/maven2/org/apache/flink/flink-jdbc_2.12/1.10.3/flink-jdbc_2.12-1.10.3.jar -P /opt/flink/lib/ RUN wget -q https://repo1.maven.org/maven2/com/aventrix/jnanoid/jnanoid/2.0.0/jnanoid-2.0.0.jar -P /opt/flink/lib/ RUN wget -q https://repo1.maven.org/maven2/org/apache/flink/flink-connector-jdbc/3.2.0-1.19/flink-connector-jdbc-3.2.0-1.19.jar -P /opt/flink/lib/ +RUN wget -q https://repo1.maven.org/maven2/org/apache/flink/flink-s3-fs-presto/1.20.1/flink-s3-fs-presto-1.20.1.jar -P /opt/flink/lib/ # Command to start Flink JobManager and TaskManager in a mini-cluster setup CMD ["bin/start-cluster.sh"] \ No newline at end of file diff --git a/crds.yaml b/crds.yaml index c044377..5de3b21 100644 --- a/crds.yaml +++ b/crds.yaml @@ -36,6 +36,10 @@ spec: type: integer jarUri: type: string + jarURIBasicAuthUsername: + type: string + jarURIBasicAuthPassword: + type: string args: type: array items: diff --git a/example-job.yaml b/example-job.yaml index ebd74a2..a1b3068 100644 --- a/example-job.yaml +++ b/example-job.yaml @@ -3,13 +3,14 @@ apiVersion: flink.logicamp.tech/v1alpha1 kind: FlinkJob metadata: name: my-flink-job - namespace: default spec: key: word-count name: "Word Count Example" - entryClass: "org.apache.flink.examples.java.wordcount.WordCount" - parallelism: 2 - jarUri: "http://192.168.7.7:8080/product-enrichment-processor.jar" + entryClass: "tech.logicamp.logiline.FacilityEnrichment" + parallelism: 1 + jarUri: "https://git.logicamp.tech/api/packages/logiline/generic/facility-enrichment/1.0.0/facility-enrichment.jar" + jarURIBasicAuthUsername: logiline-actrunner + jarURIBasicAuthPassword: daeweeb7ohpaiw3oojiCoong flinkConfiguration: - taskmanager.numberOfTaskSlots: "2" - parallelism.default: "2" \ No newline at end of file + taskmanager.numberOfTaskSlots: "1" + parallelism.default: "1" \ No newline at end of file diff --git a/helm/chart/Chart.lock b/helm/chart/Chart.lock new file mode 100644 index 0000000..f6368ff --- /dev/null +++ b/helm/chart/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: minio + repository: https://charts.bitnami.com/bitnami + version: 16.0.2 +digest: sha256:9a822e9c5a4eee1b6515c143150c1dd6f84ceb080a7be4573e09396c5916f7d3 +generated: "2025-04-04T14:42:09.771390014+03:30" diff --git a/helm/chart/Chart.yaml b/helm/chart/Chart.yaml index 9fc1a17..7bf5006 100644 --- a/helm/chart/Chart.yaml +++ b/helm/chart/Chart.yaml @@ -4,3 +4,7 @@ description: Helm chart for flink kube operator type: application version: 0.1.14 appVersion: "0.1.0" +dependencies: + - name: minio + repository: https://charts.bitnami.com/bitnami + version: 16.0.2 \ No newline at end of file diff --git a/helm/chart/charts/minio-16.0.2.tgz b/helm/chart/charts/minio-16.0.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..624d25b9aab91a620c87730427984c93387cfeb0 GIT binary patch literal 57792 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�F37GNcwUb%=DPO%xix;y`0FXyotRl{6E_AsRc)~Ka5nWVc!1~D6}+00B* zNM71wL#RF5Q%ZaGHfEIeSfh_p+RO0lC*A81G8~PQp#GXtD&4EBcTdYp1AqE;r8G0q zLMo^?rx}HJRL&u=UTe=N$nH$&`r>9Y1HED`DfRs$7T)IYOd|hrUHZflV1@km zoJxJB{I|W;TFQTmcpi)VC+|>R0hJxTAc^VflgeoSQ``_yHf`JlX{qTf7eX1&W67aP zSzhkqCA;G(ym%1<ph0vLvM`#%e${aDd(@+I=E>b`1daEv!qI%PC5r@cvJ4rZ!vijSOJn9!0lp=T@f zRI(S<1tnk3oF((B**=|xSLotq+Jz}ruAD1iR4x6@Z+1}i(IAj2p-iJP%Mo1RwWaR< zz+*aVFj&%mnIkL3_5_&DBY9RtYe+Wl1=ZmrIcnU;QPzSbM_@LW_C-}peU|byh%l>l z4`{N%RF1J4+qq#Kle2}g0AwRmh2zXkTQPxhs>TjDKlRl7gn>G6Pji&!&3@TRDn5t_ z=6za4RY5DWs;YRie-&j_nNWA13Xc0*ZZoT_#5()Jl~zS!_7^#;^;K62mz-I7HG%$T z7^kDFB$)gXutNW{v(?Jk|8H(C?f)0@JQn?rokR43E`$&f785+Hl=P0{E2oS{C`8Lk z(`D<4Om6D22L9{Dm#+9$#w*Z2jB*?AP!6SC`1NwM(DgF#3;*%=7b?4;s6Qz#CI zh!r^xeFYY~*xb~IQz9}H_AzP0bC%APAx=m!)5+*14b3Q~tCRJ(C3PXs35Zj1B%g2! z(5BFA<6fmbg}co`;Hy2Nd*L2g(Kk{fT+KmH^_pLrC3H*FHJ1G;MwjghdX~Dfl2IXP z=XN1`kN3byRAu&btVC_$i>6}hc;?65R76k6>GC$$rA^J5->JO~r)PQATaz!CTnfij zK{5p-F*KIBN)Kh_{@QjOb)N~&|6PheKk6m^q!u7m{y#fyGymJx#`CS^`F|15<39h- z;p~@D?LYi8 zOxA-TJ&4eqjzwJdDP&4^$Nke|k%zV19z+l{??rI}N^aHeB>%>L znX%w>w;%*P0;dE_`t zNjXaVDDC?^-DNMlybLfR76g>znKc0lnefQx8Igcd`;ek!6kG+lWJiaXCEEyRCq4`D zK_)Hd)-ZTmR>bIYlOx#a%KzWq)$BA3!{9qlfy#v)N~wgjr`YsJX_m+t03@w6)FZ!uGsH-Oc!=L(P^HL1n>(pe5DnS2I`F>bOD-8Ib4FBTbnm;Sgg(o$K=E=<3;}`WClnj-4sSvql);0xwfca#Fx4+257Q`sv`tdk^D%QZ?|_RlP8Q`iL#oNf!vZB_VmEo1ryu t^CvAkLSDK}35exJq-<3o`!H~Cu0GVCJvIzu7{mAn;}g|fn27+K1pwz1dUyZ; literal 0 HcmV?d00001 diff --git a/helm/chart/templates/NOTES.txt b/helm/chart/templates/NOTES.txt index 8236e73..6d4a21d 100644 --- a/helm/chart/templates/NOTES.txt +++ b/helm/chart/templates/NOTES.txt @@ -17,6 +17,6 @@ {{- else if contains "ClusterIP" .Values.service.type }} export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "flink-kube-operator.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") - echo "Visit http://127.0.0.1:8080 to use your application" - kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT + echo "Visit http://127.0.0.1:8081 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8081:$CONTAINER_PORT {{- end }} diff --git a/helm/chart/templates/flink/config.yaml b/helm/chart/templates/flink/config.yaml new file mode 100644 index 0000000..d7d77dc --- /dev/null +++ b/helm/chart/templates/flink/config.yaml @@ -0,0 +1,33 @@ +{{- define "flink.env" -}} +- name: JOB_MANAGER_RPC_ADDRESS + value: "localhost" +- name: NAMESPACE + value: {{ .Release.Namespace }} +- name: FLINK_PROPERTIES + value: | + jobmanager.rpc.address: {{ .Release.Name }}-flink-job-manager + jobmanager.memory.process.size: {{ .Values.flink.jobManager.processMemory }} + taskmanager.memory.process.size: {{ .Values.flink.taskManager.processMemory }} + taskmanager.data.port: 6125 + taskmanager.numberOfTaskSlots: {{ .Values.flink.taskManager.numberOfTaskSlots }} + parallelism.default: {{ .Values.flink.parallelism.default }} + state.backend: {{ .Values.flink.state.backend }} + rest.port: 8081 + rootLogger.level = DEBUG + rootLogger.appenderRef.console.ref = ConsoleAppender + high-availability.type: kubernetes + kubernetes.namespace: {{ .Release.Namespace }} + kubernetes.cluster-id: {{ .Values.clusterId | default (print .Release.Name "-cluster") }} + execution.checkpointing.interval: {{ .Values.flink.checkpoint.interval }} + execution.checkpointing.mode: {{ .Values.flink.checkpoint.mode }} + state.checkpoints.dir: s3://{{ .Release.Name }}-minio:9000/checkpoints + state.backend.rocksdb.localdir: /opt/flink/rocksdb + high-availability.storageDir: /opt/flink/ha + state.savepoints.dir: s3://{{ .Release.Name }}-minio:9000/savepoints + state.backend.incremental: {{ .Values.flink.state.incremental }} + rest.profiling.enabled: true + s3.endpoint: http://{{ .Release.Name }}-minio:9000 # Use Kubernetes service name + s3.path.style.access: true + s3.fs.hadoop.impl: org.apache.hadoop.fs.s3a.S3AFileSystem # Keep for compatibility + fs.s3a.aws.credentials.provider: com.amazonaws.auth.DefaultAWSCredentialsProviderChain +{{- end }} diff --git a/helm/chart/templates/flink/data.pvc.yaml b/helm/chart/templates/flink/data.pvc.yaml deleted file mode 100644 index e09bdea..0000000 --- a/helm/chart/templates/flink/data.pvc.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: {{ .Values.flink.state.data.pvcName }} -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: {{ .Values.flink.state.data.size }} # Use size defined in values.yaml diff --git a/helm/chart/templates/flink/deploy.yaml b/helm/chart/templates/flink/deploy.yaml deleted file mode 100644 index 5a7c74e..0000000 --- a/helm/chart/templates/flink/deploy.yaml +++ /dev/null @@ -1,165 +0,0 @@ -{{- define "flink.env" -}} -- name: JOB_MANAGER_RPC_ADDRESS - value: "localhost" -- name: NAMESPACE - value: {{ .Release.Namespace }} -- name: FLINK_PROPERTIES - value: | - jobmanager.rpc.address: localhost - jobmanager.memory.process.size: {{ .Values.flink.jobManager.processMemory }} - taskmanager.memory.process.size: {{ .Values.flink.taskManager.processMemory }} - taskmanager.data.port: 6125 - taskmanager.numberOfTaskSlots: {{ .Values.flink.taskManager.numberOfTaskSlots }} - parallelism.default: {{ .Values.flink.parallelism.default }} - state.backend: {{ .Values.flink.state.backend }} - rest.port: 8081 - rootLogger.level = DEBUG - rootLogger.appenderRef.console.ref = ConsoleAppender - high-availability.type: kubernetes - kubernetes.namespace: {{ .Release.Namespace }} - kubernetes.cluster-id: {{ .Values.clusterId | default (print .Release.Name "-cluster") }} - execution.checkpointing.interval: {{ .Values.flink.checkpoint.interval }} - execution.checkpointing.mode: {{ .Values.flink.checkpoint.mode }} - web.upload.dir: {{ .Values.flink.state.data.dir }}/web-upload - state.checkpoints.dir: file://{{ .Values.flink.state.data.dir }}/checkpoints - state.backend.rocksdb.localdir: file://{{ .Values.flink.state.data.dir }}/rocksdb - high-availability.storageDir: file://{{ .Values.flink.state.ha.dir }} - state.savepoints.dir: file://{{ .Values.flink.state.savepoints.dir }} - state.backend.incremental: {{ .Values.flink.state.incremental }} - rest.profiling.enabled: true -{{- end }} - -{{- define "flink.volumeMounts" -}} -- name: flink-data - mountPath: {{ .Values.flink.state.data.dir }}/data -- name: flink-data - mountPath: {{ .Values.flink.state.data.dir }}/rocksdb - subPath: rocksdb -- name: flink-data - mountPath: {{ .Values.flink.state.data.dir }}/checkpoints - subPath: checkpoints -- name: flink-data - mountPath: {{ .Values.flink.state.data.dir }}/web-upload - subPath: web-upload -- name: flink-ha - mountPath: {{ .Values.flink.state.ha.dir }} -- name: flink-savepoints - mountPath: {{ .Values.flink.state.savepoints.dir }} -{{- end }} - -{{- define "flink.volumes" -}} -- name: flink-data - persistentVolumeClaim: - claimName: {{ .Values.flink.state.data.pvcName }} -- name: flink-savepoints - persistentVolumeClaim: - claimName: {{ .Values.flink.state.savepoints.pvcName }} -- name: flink-ha - persistentVolumeClaim: - claimName: {{ .Values.flink.state.ha.pvcName }} -{{- end }} - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ .Release.Name }}-flink - labels: - app.kubernetes.io/name: {{ .Release.Name }}-flink - app.kubernetes.io/instance: {{ .Release.Name }} -spec: - replicas: 1 - strategy: - type: Recreate - selector: - matchLabels: - app.kubernetes.io/name: {{ .Release.Name }}-flink - app.kubernetes.io/instance: {{ .Release.Name }} - template: - metadata: - labels: - app.kubernetes.io/name: {{ .Release.Name }}-flink - app.kubernetes.io/instance: {{ .Release.Name }} - spec: - serviceAccountName: {{ include "flink-kube-operator.serviceAccountName" . }} - initContainers: - - name: volume-mount-hack - image: {{ .Values.flink.image.repository }}:{{ .Values.flink.image.tag }} - runAsUser: 0 - command: ["sh", "-c", "chown -R flink {{ .Values.flink.state.data.dir }}/data {{ .Values.flink.state.data.dir }}/rocksdb {{ .Values.flink.state.data.dir }}/checkpoints {{ .Values.flink.state.data.dir }}/web-upload {{ .Values.flink.state.ha.dir }} {{ .Values.flink.state.savepoints.dir }}"] - volumeMounts: - {{- include "flink.volumeMounts" . | nindent 12 }} - containers: - - name: jobmanager - image: {{ .Values.flink.image.repository }}:{{ .Values.flink.image.tag }} - imagePullPolicy: Always - args: ["jobmanager"] - ports: - - containerPort: 6123 # JobManager RPC port - name: rpc - - containerPort: 6124 # JobManager blob server port - name: blob - - containerPort: 6125 # JobManager queryable state port - name: query - - containerPort: 8081 # JobManager Web UI port - name: ui - env: - {{- include "flink.env" . | nindent 12 }} - - name: POD_IP - valueFrom: - fieldRef: - fieldPath: status.podIP - volumeMounts: - {{- include "flink.volumeMounts" . | nindent 12 }} - - name: taskmanager - image: {{ .Values.flink.image.repository }}:{{ .Values.flink.image.tag }} - imagePullPolicy: Always - args: ["taskmanager"] - ports: - - containerPort: 6121 # TaskManager data port - name: data - - containerPort: 6122 # TaskManager RPC port - name: rpc - env: - {{- include "flink.env" . | nindent 12 }} - - name: POD_IP - valueFrom: - fieldRef: - fieldPath: status.podIP - volumeMounts: - {{- include "flink.volumeMounts" . | nindent 12 }} - - name: operator - securityContext: - {{- toYaml .Values.securityContext | nindent 12 }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - ports: - - name: http - containerPort: {{ .Values.service.port }} - protocol: TCP - env: - - name: FLINK_API_URL - value: localhost:8081 - - name: SAVEPOINT_PATH - value: file://{{ .Values.flink.state.savepoints.dir }} - - name: NAMESPACE - value: "{{ .Release.Namespace }}" - - resources: - {{- toYaml .Values.resources | nindent 12 }} - volumeMounts: - {{- include "flink.volumeMounts" . | nindent 12 }} - volumes: - {{- include "flink.volumes" . | nindent 8 }} - - {{- with .Values.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.affinity }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} diff --git a/helm/chart/templates/flink/ha.pvc.yaml b/helm/chart/templates/flink/ha.pvc.yaml index fcff631..ee3b672 100644 --- a/helm/chart/templates/flink/ha.pvc.yaml +++ b/helm/chart/templates/flink/ha.pvc.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: PersistentVolumeClaim metadata: - name: {{ .Values.flink.state.ha.pvcName }} + name: {{ .Release.Name }}-{{ .Values.flink.state.ha.pvcName }} spec: accessModes: - ReadWriteOnce diff --git a/helm/chart/templates/flink/job-manager-deploy.yaml b/helm/chart/templates/flink/job-manager-deploy.yaml new file mode 100644 index 0000000..017bef4 --- /dev/null +++ b/helm/chart/templates/flink/job-manager-deploy.yaml @@ -0,0 +1,84 @@ + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-flink-job-manager + labels: + app: {{ .Release.Name }}-flink-operator + component: {{ .Release.Name }}-flink-job-manager +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: {{ .Release.Name }}-flink-operator + component: {{ .Release.Name }}-flink-job-manager + template: + metadata: + labels: + app: {{ .Release.Name }}-flink-operator + component: {{ .Release.Name }}-flink-job-manager + spec: + serviceAccountName: {{ include "flink-kube-operator.serviceAccountName" . }} + initContainers: + - name: volume-mount-hack + image: {{ .Values.flink.image.repository }}:{{ .Values.flink.image.tag }} + runAsUser: 0 + command: ["sh", "-c", "chown -R flink {{ .Values.flink.state.ha.dir }}"] + volumeMounts: + - name: flink-ha + mountPath: {{ .Values.flink.state.ha.dir }} + containers: + - name: jobmanager + image: {{ .Values.flink.image.repository }}:{{ .Values.flink.image.tag }} + imagePullPolicy: Always + args: ["jobmanager"] + ports: + - containerPort: 6123 # JobManager RPC port + name: rpc + - containerPort: 6124 # JobManager blob server port + name: blob + - containerPort: 6125 # JobManager queryable state port + name: query + - containerPort: 8081 # JobManager Web UI port + name: ui + env: + {{- include "flink.env" . | nindent 12 }} + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: S3_ENDPOINT + value: "http://minio-service:9000" + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-flink-secrets + key: minio_access_key + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-flink-secrets + key: minio_secret_key + volumeMounts: + - name: flink-ha + mountPath: {{ .Values.flink.state.ha.dir }} + + volumes: + - name: flink-ha + persistentVolumeClaim: + claimName: {{ .Release.Name }}-{{ .Values.flink.state.ha.pvcName }} + + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm/chart/templates/flink/job-manager-service.yaml b/helm/chart/templates/flink/job-manager-service.yaml new file mode 100644 index 0000000..c7d3af3 --- /dev/null +++ b/helm/chart/templates/flink/job-manager-service.yaml @@ -0,0 +1,28 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-flink-job-manager + labels: + app.kubernetes.io/name: {{ .Release.Name }}-flink-job-manager + app.kubernetes.io/instance: {{ .Release.Name }} +spec: + ports: + - name: flink-web-ui + port: 8081 + targetPort: 8081 + - name: rpc + port: 6123 + targetPort: 6123 + - name: blob + port: 6124 + targetPort: 6124 + - name: query + port: 6125 + targetPort: 6125 + - name: operator + port: 3000 + targetPort: 3000 + selector: + app: {{ .Release.Name }}-flink-operator + component: {{ .Release.Name }}-flink-job-manager + type: ClusterIP # Change to LoadBalancer if you want external access diff --git a/helm/chart/templates/flink/savepoint.pvc.yaml b/helm/chart/templates/flink/savepoint.pvc.yaml deleted file mode 100644 index 6906af1..0000000 --- a/helm/chart/templates/flink/savepoint.pvc.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: {{ .Values.flink.state.savepoints.pvcName }} -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: {{ .Values.flink.state.savepoints.size }} # Use size defined in values.yaml diff --git a/helm/chart/templates/flink/service.yaml b/helm/chart/templates/flink/service.yaml deleted file mode 100644 index 5a7cc1b..0000000 --- a/helm/chart/templates/flink/service.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: flink - labels: - app.kubernetes.io/name: {{ .Release.Name }}-flink - app.kubernetes.io/instance: {{ .Release.Name }} -spec: - ports: - - port: 8081 - name: flink-web-ui - targetPort: 8081 - - port: 3000 - name: operator - targetPort: 3000 - selector: - app.kubernetes.io/name: {{ .Release.Name }}-flink - app.kubernetes.io/instance: {{ .Release.Name }} - type: ClusterIP # Change to LoadBalancer if you want external access diff --git a/helm/chart/templates/flink/task-manager-statefulset.yaml b/helm/chart/templates/flink/task-manager-statefulset.yaml new file mode 100644 index 0000000..cd260a9 --- /dev/null +++ b/helm/chart/templates/flink/task-manager-statefulset.yaml @@ -0,0 +1,58 @@ + +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ .Release.Name }}-flink-task-manager + labels: + app: {{ .Release.Name }}-flink-operator + component: taskmanager +spec: + serviceName: {{ .Release.Name }}-flink-task-manager + replicas: {{ .Values.flink.taskManager.replicas }} + selector: + matchLabels: + app: {{ .Release.Name }}-flink-operator + component: {{ .Release.Name }}-flink-task-manager + template: + metadata: + labels: + app: {{ .Release.Name }}-flink-operator + component: {{ .Release.Name }}-flink-task-manager + spec: + serviceAccountName: {{ include "flink-kube-operator.serviceAccountName" . }} + containers: + - name: task-manager + image: {{ .Values.flink.image.repository }}:{{ .Values.flink.image.tag }} + imagePullPolicy: Always + args: ["taskmanager"] + env: + {{- include "flink.env" . | nindent 8 }} + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: S3_ENDPOINT + value: "http://minio-service:9000" + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-flink-secrets + key: minio_access_key + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-flink-secrets + key: minio_secret_key + volumeMounts: + - name: rocksdb-storage + mountPath: /opt/flink/rocksdb + resources: + {{- toYaml .Values.flink.taskManager.resources | nindent 10 }} + volumeClaimTemplates: + - metadata: + name: rocksdb-storage + spec: + accessModes: [ ReadWriteOnce ] + resources: + requests: + storage: {{ .Values.flink.taskManager.storage.rocksDb.size }} diff --git a/helm/chart/templates/operator/deployment.yaml b/helm/chart/templates/operator/deployment.yaml deleted file mode 100644 index e69de29..0000000 diff --git a/helm/chart/templates/operator/service.yaml b/helm/chart/templates/operator/service.yaml index 887cfb0..cef229a 100644 --- a/helm/chart/templates/operator/service.yaml +++ b/helm/chart/templates/operator/service.yaml @@ -1,9 +1,10 @@ apiVersion: v1 kind: Service metadata: - name: {{ include "flink-kube-operator.fullname" . }} + name: {{ .Release.Name }}-flink-operator labels: - {{- include "flink-kube-operator.labels" . | nindent 4 }} + app: {{ .Release.Name }}-flink-operator + component: {{ .Release.Name }}-flink-operator spec: type: {{ .Values.service.type }} ports: @@ -12,4 +13,5 @@ spec: protocol: TCP name: http selector: - {{- include "flink-kube-operator.selectorLabels" . | nindent 4 }} + app: {{ .Release.Name }}-flink-operator + component: {{ .Release.Name }}-flink-operator diff --git a/helm/chart/templates/operator/statefulset.yaml b/helm/chart/templates/operator/statefulset.yaml new file mode 100644 index 0000000..4b90465 --- /dev/null +++ b/helm/chart/templates/operator/statefulset.yaml @@ -0,0 +1,66 @@ + +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ .Release.Name }}-flink-operator + labels: + app: {{ .Release.Name }}-flink-operator + component: {{ .Release.Name }}-flink-operator +spec: + serviceName: {{ .Release.Name }}-flink-operator + replicas: 1 + selector: + matchLabels: + app: {{ .Release.Name }}-flink-operator + component: {{ .Release.Name }}-flink-operator + template: + metadata: + labels: + app: {{ .Release.Name }}-flink-operator + component: {{ .Release.Name }}-flink-operator + spec: + serviceAccountName: {{ include "flink-kube-operator.serviceAccountName" . }} + initContainers: + - name: wait-for-jobmanager + image: curlimages/curl:8.5.0 # Lightweight curl image + command: + - sh + - -c + - | + echo "Waiting for Flink JobManager to be ready..." + until curl -sSf "http://{{ .Release.Name }}-flink-job-manager:8081/taskmanagers"; do + echo "JobManager not ready yet - retrying in 5s..." + sleep 5 + done + echo "JobManager is ready!" + containers: + - name: operator + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + env: + - name: FLINK_API_URL + value: {{ .Release.Name }}-flink-job-manager:8081 + - name: SAVEPOINT_PATH + value: s3://{{ .Release.Name }}-minio:9000/savepoints + - name: NAMESPACE + value: "{{ .Release.Namespace }}" + - name: S3_ENDPOINT + value: "http://{{ .Release.Name }}-minio:9000" + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-minio + key: root-user + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-minio + key: root-password + + diff --git a/helm/chart/values.yaml b/helm/chart/values.yaml index a9ec791..0bf2e4a 100644 --- a/helm/chart/values.yaml +++ b/helm/chart/values.yaml @@ -117,7 +117,7 @@ affinity: {} flink: image: repository: lcr.logicamp.tech/library/flink - tag: 1.20.0-scala_2.12-java17-minicluster + tag: 1.20.1-scala_2.12-java17-minicluster parallelism: default: 1 # Default parallelism for Flink jobs @@ -129,14 +129,6 @@ flink: state: backend: rocksdb # Use RocksDB for state backend incremental: true - savepoints: - dir: "/opt/flink/savepoints" # Directory to store savepoints - pvcName: flink-savepoints-pvc # PVC for savepoints persistence - size: 10Gi # PVC size for savepoints storage - data: - dir: "/opt/flink/data" # Directory to store checkpoints/web-upload/rocksdb - pvcName: flink-data-pvc # PVC for checkpoints/web-upload/rocksdb - size: 10Gi # PVC size for checkpoints/web-upload/rocksdb ha: dir: "/opt/flink/ha" # Directory to store ha data pvcName: flink-ha-pvc # PVC for ha @@ -149,5 +141,14 @@ flink: taskManager: numberOfTaskSlots: 12 # Number of task slots for TaskManager processMemory: 4096m # Size of task manager process memory - -# clusterId: some-id \ No newline at end of file + replicas: 1 + storage: + rocksDb: + size: 4Gi + resources: + limits: + cpu: 3 + memory: 4Gi + requests: + cpu: 1 + memory: 2Gi \ No newline at end of file diff --git a/helm/index.yaml b/helm/index.yaml index 678c5d9..f07333c 100644 --- a/helm/index.yaml +++ b/helm/index.yaml @@ -53,7 +53,7 @@ entries: version: 0.1.10 - apiVersion: v2 appVersion: 0.1.0 - created: "2025-04-04T13:50:27.975218534+03:30" + created: "2025-03-04T18:04:35.495842696+03:30" description: Helm chart for flink kube operator digest: abc08853c65ba36ff3485f182555522408e150f2508d4cac672d588972ddca3c name: flink-kube-operator @@ -63,7 +63,7 @@ entries: version: 0.1.9 - apiVersion: v2 appVersion: 0.1.0 - created: "2025-04-04T13:50:27.974750898+03:30" + created: "2025-03-04T18:04:35.495392608+03:30" description: Helm chart for flink kube operator digest: 3986a0a2348db1e17a1524eb0d87eabf6d64050d4007c5b393f723393cc4b675 name: flink-kube-operator @@ -73,7 +73,7 @@ entries: version: 0.1.8 - apiVersion: v2 appVersion: 0.1.0 - created: "2025-04-04T13:50:27.974306458+03:30" + created: "2025-03-04T18:04:35.494948853+03:30" description: Helm chart for flink kube operator digest: 1bbeb92ecd10e36fa7d742a61cced0d842139ada0cfeff6fa1b0cf8718189235 name: flink-kube-operator @@ -83,7 +83,7 @@ entries: version: 0.1.7 - apiVersion: v2 appVersion: 0.1.0 - created: "2025-04-04T13:50:27.973833587+03:30" + created: "2025-03-04T18:04:35.49450822+03:30" description: Helm chart for flink kube operator digest: 4031f4a79e65f6c5e60b6ebf9dd7e2a663b1fb6f893056ad81ca33660f94406e name: flink-kube-operator @@ -93,7 +93,7 @@ entries: version: 0.1.6 - apiVersion: v2 appVersion: 0.1.0 - created: "2025-04-04T13:50:27.972800097+03:30" + created: "2025-03-04T18:04:35.494040193+03:30" description: Helm chart for flink kube operator digest: 22ed155c8538ca5e7dc26863304eb9f76b09c454edbf709a891d7ccc440f35f6 name: flink-kube-operator @@ -103,7 +103,7 @@ entries: version: 0.1.5 - apiVersion: v2 appVersion: 0.1.0 - created: "2025-04-04T13:50:27.972374168+03:30" + created: "2025-03-04T18:04:35.493584927+03:30" description: Helm chart for flink kube operator digest: b548a64ef89bbcd12d92fefffd1fd37758e8fccda02aecd97c7519a08f10fa4a name: flink-kube-operator @@ -113,7 +113,7 @@ entries: version: 0.1.4 - apiVersion: v2 appVersion: 0.1.0 - created: "2025-04-04T13:50:27.971952322+03:30" + created: "2025-03-04T18:04:35.493138547+03:30" description: Helm chart for flink kube operator digest: 05a9664f574e2d5d1cca764efb6481ad21b9176663b907973a8ef5264f15a91f name: flink-kube-operator @@ -123,7 +123,7 @@ entries: version: 0.1.3 - apiVersion: v2 appVersion: 0.1.0 - created: "2025-04-04T13:50:27.971461428+03:30" + created: "2025-03-04T18:04:35.492696005+03:30" description: Helm chart for flink kube operator digest: 89345b1a9a79aa18b646705aeb8cfdc547629600cb8a00708a3f64d188f296f2 name: flink-kube-operator @@ -133,7 +133,7 @@ entries: version: 0.1.2 - apiVersion: v2 appVersion: 0.1.0 - created: "2025-04-04T13:50:27.968770748+03:30" + created: "2025-03-04T18:04:35.490170385+03:30" description: Helm chart for flink kube operator digest: 1d2af9af6b9889cc2962d627946464766f1b65b05629073b7fffb9a98cd957e2 name: flink-kube-operator @@ -143,7 +143,7 @@ entries: version: 0.1.1 - apiVersion: v2 appVersion: 0.1.0 - created: "2025-04-04T13:50:27.968266924+03:30" + created: "2025-03-04T18:04:35.489734651+03:30" description: Helm chart for flink kube operator digest: 0890d955904e6a3b2155c086a933b27e45266d896fb69eaad0e811dea40414da name: flink-kube-operator diff --git a/internal/crd/v1alpha1/flink_job.go b/internal/crd/v1alpha1/flink_job.go index 930da8c..e0dfe8a 100644 --- a/internal/crd/v1alpha1/flink_job.go +++ b/internal/crd/v1alpha1/flink_job.go @@ -10,13 +10,15 @@ import ( //go:generate go run sigs.k8s.io/controller-tools/cmd/controller-gen object paths=$GOFILE type FlinkJobSpec struct { - Key string `json:"key"` - Name string `json:"name"` - Parallelism int `json:"parallelism"` - JarURI string `json:"jarUri"` - SavepointInterval metaV1.Duration `json:"savepointInterval"` - EntryClass string `json:"entryClass"` - Args []string `json:"args"` + Key string `json:"key"` + Name string `json:"name"` + Parallelism int `json:"parallelism"` + JarURI string `json:"jarUri"` + JarURIBasicAuthUsername *string `json:"jarURIBasicAuthUsername"` + JarURIBasicAuthPassword *string `json:"jarURIBasicAuthPassword"` + SavepointInterval metaV1.Duration `json:"savepointInterval"` + EntryClass string `json:"entryClass"` + Args []string `json:"args"` } type FlinkJobStatus struct { diff --git a/internal/jar/jar.go b/internal/jar/jar.go index 2fafc9b..78de3d4 100644 --- a/internal/jar/jar.go +++ b/internal/jar/jar.go @@ -2,10 +2,12 @@ package jar import ( "crypto/rand" + "encoding/base64" "encoding/hex" "errors" "io" "net/http" + "net/http/cookiejar" "os" "strings" @@ -16,13 +18,17 @@ import ( ) type JarFile struct { - uri string - filePath string + uri string + filePath string + basicAuthUsername *string + basicAuthPassword *string } -func NewJarFile(URI string) (*JarFile, error) { +func NewJarFile(URI string, basicAuthUsername *string, basicAuthPassword *string) (*JarFile, error) { jarFile := &JarFile{ - uri: URI, + uri: URI, + basicAuthUsername: basicAuthUsername, + basicAuthPassword: basicAuthPassword, } err := jarFile.Download() if err != nil { @@ -57,9 +63,45 @@ func (jarFile *JarFile) Download() error { } defer out.Close() - resp, err := http.Get(jarFile.uri) - if err != nil || resp.StatusCode > 299 { + + var resp *http.Response + if jarFile.basicAuthPassword != nil && jarFile.basicAuthUsername != nil { + + basicAuth := func(username, password string) string { + auth := username + ":" + password + return base64.StdEncoding.EncodeToString([]byte(auth)) + } + + redirectPolicyFunc := func(req *http.Request, via []*http.Request) error { + req.Header.Add("Authorization", "Basic "+basicAuth(*jarFile.basicAuthUsername, *jarFile.basicAuthPassword)) + return nil + } + + client := &http.Client{ + Jar: &cookiejar.Jar{}, + CheckRedirect: redirectPolicyFunc, + } + + req, err := http.NewRequest("GET", jarFile.uri, nil) + if err != nil { + jarFile.delete() + return err + } + req.Header.Add("Authorization", "Basic "+basicAuth(*jarFile.basicAuthUsername, *jarFile.basicAuthPassword)) + resp, err = client.Do(req) + } else { + resp, err = http.Get(jarFile.uri) + } + if err != nil { jarFile.delete() + pkg.Logger.Error("error in downloading jar", zap.Error(err)) + return err + } + if resp.StatusCode > 299 { + respBody := []byte{} + resp.Body.Read(respBody) + err = errors.New(string(respBody) + " status:" + resp.Status) + pkg.Logger.Error("error in downloading jar", zap.Error(err)) return err } diff --git a/internal/managed_job/jar.go b/internal/managed_job/jar.go index 515081e..0f376b4 100644 --- a/internal/managed_job/jar.go +++ b/internal/managed_job/jar.go @@ -9,7 +9,7 @@ import ( // upload jar file and set the jarId for later usages func (job *ManagedJob) upload() error { - jarFile, err := jar.NewJarFile(job.def.Spec.JarURI) + jarFile, err := jar.NewJarFile(job.def.Spec.JarURI, job.def.Spec.JarURIBasicAuthUsername, job.def.Spec.JarURIBasicAuthPassword) if err != nil { pkg.Logger.Debug("[manage-job] [upload] error on download jar", zap.Error(err)) return err From 54008669cbdcf7a27cdabc3ccec3b93ec5897df8 Mon Sep 17 00:00:00 2001 From: Mohammadreza Khani Date: Sun, 6 Apr 2025 01:49:00 +0330 Subject: [PATCH 2/2] fix(helm): wrong savepoint and checkpoint s3 configs --- helm/chart/templates/flink/config.yaml | 8 +++----- helm/chart/templates/operator/statefulset.yaml | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/helm/chart/templates/flink/config.yaml b/helm/chart/templates/flink/config.yaml index d7d77dc..d128807 100644 --- a/helm/chart/templates/flink/config.yaml +++ b/helm/chart/templates/flink/config.yaml @@ -20,14 +20,12 @@ kubernetes.cluster-id: {{ .Values.clusterId | default (print .Release.Name "-cluster") }} execution.checkpointing.interval: {{ .Values.flink.checkpoint.interval }} execution.checkpointing.mode: {{ .Values.flink.checkpoint.mode }} - state.checkpoints.dir: s3://{{ .Release.Name }}-minio:9000/checkpoints + state.checkpoints.dir: s3://flink/checkpoints/ state.backend.rocksdb.localdir: /opt/flink/rocksdb high-availability.storageDir: /opt/flink/ha - state.savepoints.dir: s3://{{ .Release.Name }}-minio:9000/savepoints + state.savepoints.dir: s3://flink/savepoints/ state.backend.incremental: {{ .Values.flink.state.incremental }} rest.profiling.enabled: true - s3.endpoint: http://{{ .Release.Name }}-minio:9000 # Use Kubernetes service name + s3.endpoint: http://{{ .Release.Name }}-minio:9000 s3.path.style.access: true - s3.fs.hadoop.impl: org.apache.hadoop.fs.s3a.S3AFileSystem # Keep for compatibility - fs.s3a.aws.credentials.provider: com.amazonaws.auth.DefaultAWSCredentialsProviderChain {{- end }} diff --git a/helm/chart/templates/operator/statefulset.yaml b/helm/chart/templates/operator/statefulset.yaml index 4b90465..c50f097 100644 --- a/helm/chart/templates/operator/statefulset.yaml +++ b/helm/chart/templates/operator/statefulset.yaml @@ -47,7 +47,7 @@ spec: - name: FLINK_API_URL value: {{ .Release.Name }}-flink-job-manager:8081 - name: SAVEPOINT_PATH - value: s3://{{ .Release.Name }}-minio:9000/savepoints + value: s3://flink/savepoints/ - name: NAMESPACE value: "{{ .Release.Namespace }}" - name: S3_ENDPOINT