Created on
06-29-2026
03:29 AM
- edited on
06-29-2026
03:33 AM
by
VidyaSargur
This document aims to:
Tools/ Components used in testing the integration:
|
Argo Workflows |
v3.0+ (tested) |
Deployed on a Kubernetes cluster with line-of-sight connectivity to Cloudera. |
|
Cloudera CDP |
Public Cloud/Private Cloud |
Access to DataHub or Data Engineering cluster. |
|
Spark Application |
JAR/Python script |
A sample Spark job to be executed. |
|
Authentication |
Using Cloudera Workload credentials as Kubernetes Secrets for Data Hub & Knox token for CDE |
Necessary credentials for Cloudera API access. |
Argo Workflows is an open-source, container-native workflow engine for orchestrating parallel jobs on Kubernetes. It operates as a Kubernetes Custom Resource Definition (CRD) and runs complex multi-step workflows.
CDP is an integrated data platform combining Cloudera's Data Warehousing, Machine Learning, and Engineering capabilities.
Spark jobs run on Cloudera clusters (DataHub or Data Engineering) via the Livy API on datahub and Job API URL on Cloudera Data Engineering cluster.
The integration uses a custom Kubernetes container in the Argo Workflow, configured to interact with the Cloudera API (e.g., Livy/ CDE job API) & use the spark-submit mechanism via the Cloudera CLI or SDK.
Create a namespace “Argo”
Deploy the Argo Workflows Quickstart template from https://github.com/argoproj/argo-workflows/releases/latest/download/quick-start-minimal.yaml
Once deployed Argo workflow UI will be available for scheduling and orchestration of jobs:
An Argo Workflow Template is a reusable, versioned Kubernetes resource defining a set of tasks (a workflow). Think of it as a function definition in programming: you define the logic once and "call" it multiple times with different parameters.
A workflow template is written in YAML and has four main sections:
|
Section |
Purpose |
|
Metadata |
The name and namespace of the template. |
|
Arguments (Parameters) |
Inputs like table_name, input_path, or spark_memory. |
|
Entrypoint |
Tells Argo which template to execute first. |
|
Templates |
The actual "steps"—can be a Single Container, a Script, or a DAG (Directed Acyclic Graph). |
The metadata contains the unique identifiers assigned by Kubernetes when the job was started.
name vs generateName: The generateName is the prefix (datahub-dispatch-job-). Kubernetes took that prefix and added a random suffix (8l7m7) to create the unique name.
uid & resourceVersion: These are internal Kubernetes tracking IDs. You don't set these; the system generates them to manage the object's lifecycle.
labels: It identifies that the Argo-server system account created this job.
managedFields: This is a history log of who modified the object and when.
The spec defines how the job actually runs. This specific workflow uses a two-step hierarchy.
Argo looks here first. It tells the system to start with the template named main.
YAML
steps:
- name: submit-spark-datahub
template: call-livy-api
This is where the actual work happens. Instead of running Spark directly, it uses a lightweight Curl container to "talk" to the Cloudera cluster.
image: curlimages/curl:latest: A very small Docker image that only contains the curl tool.
The Command (args): It sends a POST request to the Livy API. It passes the Spark configuration (memory, script path) as a JSON payload.
{{workflow.parameters.xxx}}: These are variables. Argo replaces these with the values defined at the bottom of the file (e.g., the URL and the workload credentials).
Secrets (env):
YAML
valueFrom:
secretKeyRef:
name: cloudera-credsThis is a security best practice. It pulls the sensitive password from a Kubernetes Secret named cloudera-creds instead of typing it in plain text in the script.
At the bottom, you see the hardcoded values for this specific run:
livy-api-url: The endpoint for your Spark 3 cluster.
user: The Kerberos/Cloudera username.
script-path: The location of your Python code in HDFS (dispatch_job_ext.py).
Orchestration of Cloudera Spark Jobs Using Argo Workflows
Click on Create a new workflow Template, and add the Manifest file in JSON/YAML format
5. Validation of the successful execution of the Spark job on Datahub
Creating a workflow template for Spark job execution at CDE
Using knox token: https://docs.cloudera.com/runtime/7.3.1/knox-authentication/topics/security-knox-token-api.html.
2. Submitting workflow for CDE Job creation and execution of Job
3. Validation of Job creation & execution on CDE
Sample workflow Yaml Cloudera Data Engineering.
metadata:
name: datahub-dispatch-job-8l7m7
generateName: datahub-dispatch-job-
namespace: argo
uid: 82163cb3-1ed1-4901-a49d-20feb05013eb
resourceVersion: "924865"
generation: 4
creationTimestamp: "2026-01-12T04:45:50Z"
labels:
workflows.argoproj.io/action: Update
workflows.argoproj.io/actor: system-serviceaccount-argo-argo-server
workflows.argoproj.io/creator: system-serviceaccount-argo-argo-server
managedFields:
- manager: argo
operation: Update
apiVersion: argoproj.io/v1alpha1
time: "2026-02-01T04:33:45Z"
fieldsType: FieldsV1
fieldsV1:
f:metadata:
f:generateName: {}
f:labels:
".": {}
f:workflows.argoproj.io/action: {}
f:workflows.argoproj.io/actor: {}
f:workflows.argoproj.io/creator: {}
f:spec: {}
spec:
templates:
- name: main
inputs: {}
outputs: {}
metadata: {}
steps:
- - name: submit-spark-datahub
template: call-livy-api
arguments: {}
- name: call-livy-api
inputs: {}
outputs: {}
metadata: {}
container:
name: ""
image: curlimages/curl:latest
command:
- sh
- -c
args:
- |
echo "Submitting Spark job to DataHub via Proxy API..."
curl -k -i -u "{{workflow.parameters.user}}:$PASSWORD" \
-X POST "{{workflow.parameters.livy-api-url}}" \
-H "Content-Type: application/json" \
-d '{
"file": "{{workflow.parameters.script-path}}",
"name": "Argo_DataHub_Dispatch",
"conf": {
"spark.executor.memory": "2g",
"spark.driver.memory": "1g"
}
}'
env:
- name: PASSWORD
valueFrom:
secretKeyRef:
name: cloudera-creds
key: password
resources: {}
entrypoint: main
arguments:
parameters:
- name: livy-api-url
value: https://clusterdataeng-master0.se-sandb.a465-9q4k.cloudera.site/clusterdataeng/cdp-proxy-api/livy_for_spark3/v1/batches
- name: user
value: sumit
- name: script-path
value: hdfs:///user/sumit/dispatch_job_ext.py
Sample workflow YAML Cloudera Data hub
metadata:
name: cde-vehicle-query-ext-cde8rvw8
generateName: cde-vehicle-query-ext-cde
namespace: argo
uid: 9a35fe46-ebde-4b83-9983-f85b724eaea5
resourceVersion: "946692"
generation: 2
creationTimestamp: "2026-02-01T04:14:14Z"
labels:
workflows.argoproj.io/action: Update
workflows.argoproj.io/actor: system-serviceaccount-argo-argo-server
workflows.argoproj.io/creator: system-serviceaccount-argo-argo-server
managedFields:
- manager: argo
operation: Update
apiVersion: argoproj.io/v1alpha1
time: "2026-02-01T10:31:22Z"
fieldsType: FieldsV1
fieldsV1:
f:metadata:
f:generateName: {}
f:labels:
".": {}
f:workflows.argoproj.io/action: {}
f:workflows.argoproj.io/actor: {}
f:workflows.argoproj.io/creator: {}
f:spec: {}
spec:
templates:
- name: main
inputs: {}
outputs: {}
metadata: {}
container:
name: ""
image: curlimages/curl:latest
command:
- sh
- -c
args:
- >
set -e
# 1. GET AUTHENTICATION TOKEN
echo "--- Authenticating ---"
TOKEN_URL="https://service.cde-56g8qj2f.se-sandb.a465-9q4k.cloudera.site/gateway/authtkn/knoxtoken/api/v1/token"
RESP=$(curl -k -s -u "$USER:$PASS" "$TOKEN_URL")
TKN=$(echo "$RESP" | sed -E 's/.*"access_token":"([^"]+)".*/\1/' |
tr -d '\n')
if [ -z "$TKN" ]; then echo "Token generation failed"; exit 1; fi
# Define Base URLs and Names
API_BASE="https://t4jsfbdj.cde-56g8qj2f.se-sandb.a465-9q4k.cloudera.site/dex/api/v1"
RES_NAME="test01-res"
JOB_NAME="cdedemo"
# 2. ENSURE RESOURCE EXISTS
echo "--- Checking Resource: $RES_NAME ---"
RES_STATUS=$(curl -k -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer $TKN" -H "X-Requested-By: cli" \
"$API_BASE/resources/$RES_NAME")
if [ "$RES_STATUS" -ne "200" ]; then
echo "Creating $RES_NAME..."
curl -k -s -f -X POST "$API_BASE/resources" \
-H "Authorization: Bearer $TKN" -H "X-Requested-By: cli" \
-H "Content-Type: application/json" \
-d "{\"name\": \"$RES_NAME\", \"type\": \"files\"}"
fi
# 3. PREPARE AND UPLOAD PYSPARK SCRIPT
echo "--- Preparing and Uploading script ---"
cat << 'EOF' > vehicle_query.py
from pyspark.sql import SparkSession
spark =
SparkSession.builder.appName("VehicleQueryExt").getOrCreate()
print("Executing query on vehicle_data_ext...")
try:
# Running the query on the updated table name
df = spark.sql("SELECT * FROM default.vehicle_data_ext LIMIT 20")
df.show()
except Exception as e:
print(f"Error: {e}")
spark.stop()
EOF
curl -k -f -X PUT "$API_BASE/resources/$RES_NAME/vehicle_query.py" \
-H "Authorization: Bearer $TKN" -H "X-Requested-By: cli" \
-F "file=@vehicle_query.py"
# 4. CREATE THE JOB (Using POST to the /jobs collection)
echo "--- Ensuring Job Definition: $JOB_NAME exists ---"
curl -k -X POST "$API_BASE/jobs" \
-H "Authorization: Bearer $TKN" \
-H "X-Requested-By: cli" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"$JOB_NAME\",
\"type\": \"spark\",
\"spark\": {
\"file\": \"vehicle_query.py\",
\"driverMemory\": \"1g\",
\"executorMemory\": \"1g\"
},
\"mounts\": [ { \"resourceName\": \"$RES_NAME\" } ]
}" || echo "Job already exists or created."
# 5. TRIGGER THE RUN
echo "--- Triggering Run for $JOB_NAME ---"
curl -k -f -i -X POST "$API_BASE/jobs/$JOB_NAME/run" \
-H "Authorization: Bearer $TKN" \
-H "X-Requested-By: cli" \
-H "Content-Type: application/json" \
-d "{}"
env:
- name: USER
valueFrom:
secretKeyRef:
name: cde-creds
key: username
- name: PASS
valueFrom:
secretKeyRef:
name: cde-creds
key: password
resources: {}
entrypoint: main
arguments: {}