CI/CD for Ilum Interactive Services with GitHub Actions
This guide demonstrates how to automate the deployment of Ilum Interactive ServicesUtilisant GitHub Actions with self-hosted runners running directly on your Kubernetes cluster. By combining the Ilum REST API with a CI/CD pipeline, you can deploy and update services automatically on every push or merge.
- Self-Hosted Runners: Deploy GitHub Actions Runner Controller (ARC) on Kubernetes so workflows execute inside your cluster with direct access to Ilum.
- Service Code in Git: Store your Ilum service implementation (
service.py) in a GitHub repository. - Automated Deployment: A GitHub Actions workflow creates/updates Ilum service groups via the REST API on every push to
principal. - Update via Pull Requests: Modify service logic in a feature branch, open a PR, merge — and the service is automatically redeployed with the new code.
Conditions préalables
- A running Ilum instance on Kubernetes (with all pods healthy)
kubectlethelmconfigured for your cluster- A GitHub account with permissions to create repositories and personal access tokens
cert-managerinstalled on the cluster
Step 1: Install cert-manager
Actions Runner Controller requires cert-manager for TLS certificate management. Install it if not already present:
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.16.2/cert-manager.yaml
Wait for all cert-manager pods to be ready:
kubectl wait --for=condition=Ready pods --all -n cert-manager --timeout=120s
Step 2: Create a GitHub Personal Access Token
The self-hosted runner needs a GitHub token to register itself with your repository.
- Navigate to GitHub → Settings → Developer Settings → Personal access tokens → Tokens (classic).
- Cliquer Generate new token (classic).
- Set a descriptive name (e.g.,
ServiceManagementToken). - Select the
reposcope (full control of private repositories). - Cliquer Generate token and copy the value immediately — you won't see it again.
Step 3: Install Actions Runner Controller (ARC)
3.1 Add the Helm Repository
helm repo add actions-runner-controller \
https://actions-runner-controller.github.io/actions-runner-controller
3.2 Create the Namespace and Secret
# Create namespace for runners
kubectl create ns arc-runners
# Store the GitHub token as a Kubernetes secret
kubectl create secret generic github-token \
--namespace arc-runners \
--from-literal=github_token=<YOUR_GITHUB_TOKEN>
3.3 Install ARC via Helm
helm install arc \
actions-runner-controller/actions-runner-controller \
--namespace arc-runners \
--set authSecret.create=false \
--set authSecret.name=github-token
Step 4: Create a Runner Deployment
Créez un runner-deployment.yaml file that tells ARC to spawn a self-hosted runner for your repository:
apiVersion : actions.summerwind.dev/v1alpha1
gentil : RunnerDeployment
métadonnées :
nom : ilum - demo- runner
Namespace : arc- runners
Spec :
Répliques : 1
modèle :
Spec :
dépôt : <votre - org> /IlumServiceManagment
Étiquettes :
- même - hosted
- linux
Apply it:
kubectl apply -f runner-deployment.yaml
The runner pod will register itself with GitHub and appear in your repository's Settings → Actions → Runners as an available self-hosted runner.
Step 5: Create the Service Repository
Create a new GitHub repository (e.g., IlumServiceManagment) with the following structure:
IlumServiceManagment/
├── service.py # Ilum service implementation
└── .github/
└── workflows/
└── deploy.yml # GitHub Actions workflow
5.1 Service Implementation (service.py)
This file contains the Ilum Interactive Service code. The service extends IlumJob and implements the Courir method:
De ilum . API importation IlumJob
De Pyspark . SQL . functions importation col, sum comme spark_sum
classe SparkInteractiveExample( IlumJob ) :
Def Courir ( même , étincelle , Configuration ) - > str:
table_name = Configuration . Avoir ( 'Table' )
database_name = Configuration . Avoir ( 'database')
report_lines = [ ]
si non table_name :
raise ValueError( "Config must provide a 'table' key")
si database_name:
étincelle . catalogue . setCurrentDatabase( database_name)
report_lines. append( f"Using database: { database_name} " )
si table_name non dans [ t . nom pour t dans étincelle . catalogue . listTables( ) ] :
raise ValueError( f"Table '{ table_name } ' not found in catalog")
Df = étincelle . table ( table_name )
report_lines. append( f"=== Details for table: { table_name } ===")
total_rows = Df . compter ( )
report_lines. append( f"Total rows: { total_rows} " )
total_columns = len( Df . columns)
report_lines. append( f"Total columns: { total_columns} " )
report_lines. append( "Distinct values per column:")
pour c dans Df . columns:
distinct_count = Df . select( c ) . distinct( ) . compter ( )
report_lines. append( f" { c } : { distinct_count} " )
report_lines. append( "Schema:")
pour f dans Df . schéma . fields:
report_lines. append( f" { f . nom } : { f . dataType} " )
report_lines. append( "Sample data (first 5 rows):")
sample_rows = Df . take( 5 )
pour ramer dans sample_rows:
report_lines. append( str( ramer . asDict( ) ) )
report_lines. append( "Null counts per column:")
null_counts_df = Df . select(
[ spark_sum( col( c ) . isNull( ) . cast( "int") ) . alias( c ) pour c dans Df . columns]
)
null_counts = null_counts_df. collect( ) [ 0 ] . asDict( )
pour c , v dans null_counts. items( ) :
report_lines. append( f" { c } : { v} " )
rendre "\n". join( report_lines)
5.2 GitHub Actions Workflow (.github/workflows/deploy.yml)
This workflow runs on every push to principal and uses the Ilum REST API to deploy the service:
nom : Deploy ILUM Group
on:
pousser :
branches: [ principal ]
Env :
ILUM_DEMO_SERVICE: ILUM_DEMO_SERVICE
jobs:
deploy:
runs-on: même - hosted
steps:
- nom : Checkout code
Utilise : actions/checkout@v4
- nom : Check if group ILUM_DEMO_SERVICE exists
id : check_group
Courir : |
echo "Checking if group ILUM_DEMO_SERVICE exists..."
RESPONSE=$(curl - s - X GET \
- w "\nHTTP_STATUS: % { http_code} " \
"http: ilum - core.ilum.svc.cluster.local: 9888/api/v1/group?name=$ILUM_DEMO_SERVICE")
HTTP_STATUS=$(echo "$RESPONSE" | grep HTTP_STATUS | cut - d': ' - f2)
BODY=$(echo "$RESPONSE" | sed '/HTTP_STATUS/d')
GROUP_ID=$(echo "$BODY" | jq - r '.content[ 0 ] .groupId // empty')
si [ - n "$GROUP_ID" ] && [ "$GROUP_ID" != "null" ] ; then
echo "Group ILUM_DEMO_SERVICE found with ID: $GROUP_ID"
echo "GROUP_ID=$GROUP_ID" > > $GITHUB_OUTPUT
echo "GROUP_EXISTS=true" > > $GITHUB_OUTPUT
autre
echo "Group ILUM_DEMO_SERVICE does not exist."
echo "GROUP_EXISTS=false" > > $GITHUB_OUTPUT
fi
- nom : Delete existing group
si : steps.check_group.outputs.GROUP_EXISTS == 'true'
Courir : |
GROUP_ID="${{ steps.check_group.outputs.GROUP_ID }}"
echo "Stopping and deleting group ILUM_DEMO_SERVICE (ID: $GROUP_ID)..."
friser - s - X POST \
http : ilum - core.ilum.svc.cluster.local: 9888/api/v1/group/$GROUP_ID/stop
RESPONSE=$(curl - s - X DELETE \
- w "\nHTTP_STATUS: % { http_code} " \
http : ilum - core.ilum.svc.cluster.local: 9888/api/v1/group/$GROUP_ID)
HTTP_STATUS=$(echo "$RESPONSE" | grep HTTP_STATUS | cut - d': ' - f2)
si [ "$HTTP_STATUS" - ne 200 ] ; then
echo "Error: Failed to delete group (Status: $HTTP_STATUS)"
exit 1
fi
echo "Group ILUM_DEMO_SERVICE deleted successfully."
- nom : Create new group
Courir : |
echo "Creating group ILUM_DEMO_SERVICE with service.py..."
RESPONSE=$(curl - s - X POST \
- F "name=ILUM_DEMO_SERVICE" \
- F "[email protected] " \
- F "clusterName=default" \
- F "language=PYTHON" \
- w "\nHTTP_STATUS: % { http_code} " \
http : ilum - core.ilum.svc.cluster.local: 9888/api/v1/group)
HTTP_STATUS=$(echo "$RESPONSE" | grep HTTP_STATUS | cut - d': ' - f2)
BODY=$(echo "$RESPONSE" | sed '/HTTP_STATUS/d')
echo "HTTP Status: $HTTP_STATUS"
echo "Response Body: $BODY"
si [ "$HTTP_STATUS" - ne 200 ] ; then
echo "Error: Failed to create group (Status: $HTTP_STATUS)"
exit 1
fi
GROUP_ID=$(echo "$BODY" | jq - r '.groupId // empty')
echo "Group ILUM_DEMO_SERVICE created successfully with ID: $GROUP_ID"
Commit both files to the repository.
Since the runner pod runs inside the Kubernetes cluster, it can directly access the Ilum Core API via the internal service DNS (ilum-core.ilum.svc.cluster.local:9888). The workflow:
- Checks if a service group named
ILUM_DEMO_SERVICEalready exists. - Deletes the existing group (stops it first, then removes it).
- Creates a new group by uploading
service.pyvia the REST API.
Step 6: Verify the Deployment
Once the workflow completes successfully (visible in the Actions tab on GitHub), navigate to the Ilum UI:
- Atteindre Services in the sidebar.
- You should see the
ILUM_DEMO_SERVICEservice group with status Active. - Click on it and go to the Execute Jobonglet.
- Set the Classe À
service.SparkInteractiveExample. - Provide parameters as JSON:
{
"database": "ilum_example_product_sales",
"table": "products"
}
- Cliquer Exécuter . The result will display table details including row count, schema, sample data, and null counts.
Step 7: Update the Service via Pull Request
The key benefit of this CI/CD approach is that updating your service is as simple as modifying code and merging a pull request.
Example: Adding Table Listing Feature
- Create a new branch (e.g.,
feature/list-tables) fromprincipal. - Éditer
service.pyto add functionality — for example, listing other tables in the database:
# List other tables in the current database
current_db = étincelle . catalogue . currentDatabase( )
report_lines. append( f"Tables in database '{ current_db} ':")
tables = étincelle . catalogue . listTables( current_db)
other_tables = [ t . nom pour t dans tables si t . nom !=table_name ]
si other_tables:
pour tname dans other_tables:
report_lines. append( f" { tname} " )
autre :
report_lines. append( " (no other tables)")
rendre "\n". join( report_lines)
- Commit the change and open a Pull RequestDe
feature/list-tablesintoprincipal. - Merge the PR.
- The GitHub Actions workflow triggers automatically, stops the old service, and deploys the updated version.
- Execute the job again in Ilum — the result now includes the new "Tables in database" section.
Dépannage
Runner pod not registering with GitHub
- Verify the GitHub token secret is correct:
kubectl get secret github-token -n arc-runners -o yaml - Check the runner pod logs:
kubectl logs -n arc-runners -l app=ilum-demo-runner - Assurez-vous que le
dépôtfield inrunner-deployment.yamlmatches your GitHub repository exactly (including organization/user).
Workflow fails with connection refused to ilum-core
The self-hosted runner must be in the same Kubernetes cluster as Ilum. Verify:
- The Ilum namespace and service name are correct. Use
kubectl get svc -n ilumto find the correct service DNS. - The runner pod has network access to the Ilum namespace. Check for NetworkPolicies that may block cross-namespace traffic.
Service group creation returns HTTP 400
- Assurez-vous que le
service.pyfile contains a valid class extendingIlumJob. - Check that
clusterNamematches an existing Ilum cluster (usuallyfaire défaut). - Verify the file is being uploaded correctly with
-F "[email protected] ".
Job execution returns "Table not found in catalog"
Make sure the databaseet table parameters match existing data in your Ilum environment. You can check available databases and tables via the SQL module or a Jupyter notebook.
Frequently Asked Questions (FAQ)
Why use self-hosted runners instead of GitHub-hosted runners?
GitHub-hosted runners run in GitHub's cloud and cannot access your Kubernetes cluster's internal services. Self-hosted runners (via ARC) run as pods inside your cluster, giving them direct network access to Ilum's internal API (ilum-core.ilum.svc.cluster.local).
Can I use this approach with GitLab CI or other CI/CD systems?
Yes. The core concept — calling the Ilum REST API from a CI/CD pipeline — works with any CI/CD system. You would need to use a GitLab Runner on Kubernetes (or similar) and adapt the workflow syntax to your CI/CD platform.
How do I scale the number of runners?
Increase the Répliques field in runner-deployment.yaml. ARC also supports HorizontalRunnerAutoscaler for auto-scaling based on workflow queue depth.