Skip to main content

Connect a Kubernetes environment

This how-to connects an existing Kubernetes cluster to AkôFlow for real container execution. It is for an operator who controls a namespace and its service account. The Kind real-execution Showcase is the reproducible local reference implementation; use it before adapting these steps to a shared cluster.

Use this runtime for container workloads that must become Kubernetes Jobs. Do not use it to simulate a cluster: use SimGrid for modeled infrastructure. Do not put a bearer token in a workflow, plan, repository, or screenshot.

Prerequisites

  • A reachable Kubernetes API endpoint from the machine that runs the AkôFlow daemon.
  • kubectl access as a cluster administrator only for the initial namespace and service-account setup.
  • A namespace dedicated to AkôFlow-managed workloads.
  • Images that every intended node can pull. The built-in runtime passes the activity image directly to Kubernetes; it does not configure registry credentials on your behalf.
  • A storage class or shared storage mechanism when activities exchange files or need persisted artifacts.

1. Create a namespace and a least-privilege service account

The local Kind bundle uses cluster-admin only because it is disposable. In a shared cluster, create a namespace-scoped service account and grant only the API actions that AkôFlow uses for that namespace. The runtime creates, lists, gets, and deletes Jobs, Pods, Services, and PersistentVolumeClaims; it reads Pod logs. Workspace transfer and the interactive console also create Pods and use the Pod exec subresource. Discovery additionally lists cluster-scoped Nodes.

Start with this namespace-scoped baseline, then add only the cluster-scoped nodes read permission if you will use discovery:

apiVersion: v1
kind: Namespace
metadata:
name: akoflow
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: akoflow-runtime
namespace: akoflow
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: akoflow-runtime
namespace: akoflow
rules:
- apiGroups: ["batch"]
resources: ["jobs"]
verbs: ["create", "get", "list", "delete"]
- apiGroups: [""]
resources: ["pods", "services", "persistentvolumeclaims"]
verbs: ["create", "get", "list", "delete"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: akoflow-runtime
namespace: akoflow
subjects:
- kind: ServiceAccount
name: akoflow-runtime
namespace: akoflow
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: akoflow-runtime

Apply it, then verify the namespace permissions before registering the connection:

kubectl apply -f akoflow-runtime-rbac.yaml
kubectl auth can-i --as=system:serviceaccount:akoflow:akoflow-runtime create jobs -n akoflow
kubectl auth can-i --as=system:serviceaccount:akoflow:akoflow-runtime get pods/log -n akoflow

For node discovery, a separate ClusterRole and ClusterRoleBinding granting get,list on nodes is required. Keep it separate from the namespace role so an execution-only token does not automatically gain cluster inventory access.

2. Store the API credential outside the environment definition

Generate a short-lived token and send it to the daemon's local Kubernetes-token endpoint. The example below deliberately avoids printing the token after it has been assigned to the shell variable.

export AKOFLOW_API_URL='http://127.0.0.1:8080/akoflow-api'
export AKOFLOW_API_TOKEN='<daemon API token>'

KUBE_TOKEN="$(kubectl -n akoflow create token akoflow-runtime --duration=1h)"
curl --fail-with-body \
-H "Authorization: Bearer $AKOFLOW_API_TOKEN" \
-H 'Content-Type: application/json' \
--data "{\"id\":\"research-kubernetes\",\"token\":\"$KUBE_TOKEN\"}" \
"$AKOFLOW_API_URL/kubernetes-tokens/"
unset KUBE_TOKEN

The Kubernetes client accepts a credentialRef using file:<path> or env:<variable>, or a bearerToken in connection configuration. Prefer a daemon-managed local file reference such as file:storage/credentials/kubernetes/research-kubernetes.token; it keeps the token out of the versioned environment YAML. See credentials and SSH service keys for the daemon-side credential flow.

For a production cluster, provide the API server certificate through configuration.caFile. insecureSkipTlsVerify: true is appropriate for the disposable Kind example only; do not copy it to a trusted cluster configuration.

3. Register the connection, runtime, and resources

In Desktop, open Infrastructure → Environments, create a real Kubernetes environment, add the connection, test it, and run discovery. Create or confirm a Kubernetes runtime whose configuration names the connection and namespace. Bind that runtime to every schedulable Kubernetes machine.

The relevant part of examples/kind/environment.yaml is:

examples/kind/environment.yaml
connections:
- id: kind-akoflow-connection
type: kubernetes
endpoint: https://host.docker.internal:63395
credentialRef: file:storage/credentials/kubernetes/kind-akoflow.token
configuration:
namespace: akoflow
insecureSkipTlsVerify: true

runtimes:
- id: kind-kubernetes
driver: kubernetes
mode: execution
configuration:
namespace: akoflow
connectionId: kind-akoflow-connection
capabilities:
batch: true
container: true
cancellation: true

resources:
- id: kind-worker
type: kubernetes_machine
providerId: akoflow-worker
cpuCores: 4
cpuCapacity: 4
schedulable: true
metadata:
kubernetesNode: akoflow-worker

resourceRuntimeBindings:
- resourceId: kind-worker
runtimeId: kind-kubernetes
enabled: true

namespace on the runtime takes precedence over the connection namespace. The runtime requires both an endpoint and a bearer token. Its connection health check lists Pods in the selected namespace; discovery lists Nodes and reports their readiness, allocatable CPU/memory, architecture, and container runtime information.

For a manually maintained resource, set metadata.kubernetesNode or providerId to the node's exact kubernetes.io/hostname value. AkôFlow emits that value as a Pod nodeSelector, so a plan that selects the resource is actually placed on the modeled node. An API URL is not a node name and must not be used as providerId for a Kubernetes-machine resource.

4. Attach storage and make images available

Use a PVC or NFS storage resource when the workflow needs a shared workspace. The Kind example defines a PVC and its runtime mount:

examples/kind/environment.yaml
storages:
- id: kind-akoflow-data
type: pvc
endpoint: pvc://akoflow/akoflow-data
shared: true
readOnly: false
configuration:
namespace: akoflow
claimName: akoflow-data
mountPath: /data
runtimeBindings:
- runtimeId: kind-kubernetes
default: true
containerPath: /data

The activity-level storage metadata identifies the claim and mount path. At run time, AkôFlow mounts the volume and records artifacts under a run/activity path. A PVC with ReadWriteOnce can only mount on one node at a time; when a plan pins activities or transfer pods to different nodes, select a storage class/access mode that supports that topology, or keep the relevant placement co-located.

Before launching a production workflow, verify image pull access from the selected namespace and nodes. The current runtime does not add imagePullSecrets to generated Pods. Configure image access at the cluster, service-account, or admission-policy layer before registering private images with AkôFlow.

5. Validate the connection and run a minimal workflow

In Desktop, test the connection, run discovery, inspect the resource inventory, then import a small workflow. Create a scope containing the environment version, generate or create a plan, choose Real execution, and inspect the completed run's activity logs and artifacts.

For an equivalent API validation, follow the complete Kind README. It applies the cluster access and PVC, stores a short-lived token, and submits the environment, scope, topology, workflow, plan, and execution request in that order.

The exact Kind bundle completed on 2026-09-11 as kind-dag-run-v8. It created two Kubernetes Jobs, transferred 9 bytes through its workspace, and produced matching result.txt and consumed.txt files with checksum sha256:cb064c1339ffa3d7777bcb0459de3dceddb9146156dde58065a4ac826b029aa7.

Troubleshoot and clean up

SymptomCheck and recover
Health check says the namespace is unreachableVerify the endpoint is reachable from the daemon—not merely from your laptop—and confirm the token can list Pods in the configured namespace.
Discovery cannot find nodesGrant the separate cluster-scoped nodes read permission, then run discovery again.
A Job remains PendingRun kubectl describe pod <pod> and check image pulling, requests, taints, the selected node, and PVC/node affinity.
Job creation returns forbiddenCompare the denied Kubernetes resource and verb with the namespace role above. Add only that missing permission.
Files are absent from the consumerCheck the storage binding, the producer's artifact path, and the storage access mode. For a PVC, confirm the node chosen by the plan is compatible with the claim.
Interactive console cannot startThe runtime must be able to create/get/delete Pods, and the daemon host must have kubectl available for the current interactive-console implementation.

AkôFlow labels generated Jobs, Services, PVCs, and console Pods with app.kubernetes.io/managed-by: akoflow. Use that label to inspect resources before cleanup:

kubectl -n akoflow get jobs,pods,services,pvc \
-l app.kubernetes.io/managed-by=akoflow

Delete only the run resources you intend to remove. For the disposable Kind environment, use the Showcase cleanup command: kind delete cluster --name akoflow.

Related material: Kubernetes real execution, execution scopes, storage, and interactive console.