Evaluate agents on ACK with the Harbor CLI
Agent evaluation requires running many tasks in isolation and collecting their results. The Container Service for Kubernetes (ACK) environment of the Harbor CLI schedules each task as a separate pod in your cluster, and supports mounting datasets from Object Storage Service (OSS), pre-building images, and collecting results automatically.
Harbor (an open-source framework for agent evaluation, not the container image registry) uses theharbor runcommand to create a separate trial pod for each evaluation task in an ACK cluster. Datasets are mounted to the pod in advance, and tasks are dispatched and run one at a time. The-nparameter controls the number of concurrent trials. With-n 1(a concurrency of 1), Trial pods are created one at a time, and the next task starts only after the previous one finishes. For in-cluster scenarios such as a CI/CD pipeline, wrap theharbor runcommand in a Kubernetes Job. For more information, see Use a Kubernetes Job to orchestrate evaluations.
Prepare the environment
Install the Harbor CLI
Clone the repository from GitHub and install it using pip:
git clone https://github.com/alibaba/harbor.git
cd harbor
pip install -e .
harbor --versionConfigure kubeconfig
The Harbor ACK environment uses kubeconfig to connect to the cluster. Run the following command to verify the connection:
kubectl cluster-infoTo specify a context or a kubeconfig file, pass the corresponding parameters to the harbor run command:
--ek context=my-ack-context
# or
--ek kubeconfig=/path/to/kubeconfigCreate an evaluation namespace
Use a dedicated namespace to store all evaluation-related resources, such as trial pods, Secrets, and PVs/PVCs:
kubectl create namespace harbor-evalOther tools (optional)
crane: Used to verify images pushed to Container Registry (ACR), as described in Build and push images in bulk.
ossutil: Used to view evaluation result files in OSS from the command line, as described in View the evaluation result files.
Mount datasets
Because trial pods are ephemeral, you must mount datasets and result directories from external storage. For production evaluations, using an OSS CSI static volume is recommended. For development and debugging, specify a local directory path directly.
Option 1: OSS CSI static volume (recommended)
Use ACK's OSS CSI plugin to mount a directory from an OSS bucket to a pod. Create two sets of PVs/PVCs: one for the dataset, mounted as read-only, and another for the results, mounted as read-write. Both PVs use the same Secret containing an AccessKey for authentication.
First, create a Secret containing your OSS AccessKey for PV authentication:
kubectl create secret -n harbor-eval generic oss-secret \
--from-literal=akId=<YOUR-ACCESS-KEY-ID> \
--from-literal=akSecret=<YOUR-ACCESS-KEY-SECRET>Dataset volume (read-only):
apiVersion: v1
kind: PersistentVolume
metadata:
name: harbor-dataset-pv
spec:
capacity:
storage: 100Gi
accessModes:
- ReadOnlyMany
persistentVolumeReclaimPolicy: Retain
csi:
driver: ossplugin.csi.alibabacloud.com
volumeHandle: harbor-dataset-pv
volumeAttributes:
bucket: my-harbor-bucket
url: oss-cn-hangzhou-internal.aliyuncs.com
otherOpts: "-o ro"
path: "dataset"
nodePublishSecretRef:
name: oss-secret
namespace: harbor-eval
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: harbor-dataset-pvc
namespace: harbor-eval
spec:
accessModes:
- ReadOnlyMany
resources:
requests:
storage: 100Gi
volumeName: harbor-dataset-pv
storageClassName: ""Results volume (writable):
apiVersion: v1
kind: PersistentVolume
metadata:
name: harbor-results-pv
spec:
capacity:
storage: 100Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
csi:
driver: ossplugin.csi.alibabacloud.com
volumeHandle: harbor-results-pv
volumeAttributes:
bucket: my-harbor-bucket
url: oss-cn-hangzhou-internal.aliyuncs.com
path: "results"
nodePublishSecretRef:
name: oss-secret
namespace: harbor-eval
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: harbor-results-pvc
namespace: harbor-eval
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 100Gi
volumeName: harbor-results-pv
storageClassName: ""Writing to an OSS CSI FUSE mount point requires root permissions, so the Job pod's securityContext.runAsUser must be set to 0.
Option 2: Local directory (for development and debugging)
Store your dataset locally and specify the path using the -p parameter. Harbor uses the Kubernetes exec API to upload task files to the trial pod, so no additional configuration is required.
Pre-build images
Each evaluation task has its own environment/Dockerfile and task.toml. If an image is not pre-built, harbor run builds it inside the trial pod. This process is slow and depends on the build environment in the pod. Pre-build your images and push them to ACR instead.
Deploy BuildKit in the cluster
Because clusters typically lack a Docker daemon, deploy BuildKit to serve as a remote build backend:
apiVersion: apps/v1
kind: Deployment
metadata:
name: buildkitd
namespace: harbor-eval
spec:
replicas: 1
selector:
matchLabels:
app: buildkitd
template:
metadata:
labels:
app: buildkitd
spec:
containers:
- name: buildkitd
image: moby/buildkit:v0.15.0
args:
- --addr
- tcp://0.0.0.0:1234
- --config
- /etc/buildkit/buildkitd.toml
securityContext:
privileged: true
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: "2"
memory: 4Gi
volumeMounts:
- name: buildkitd-config
mountPath: /etc/buildkit/buildkitd.toml
subPath: buildkitd.toml
- name: registry-auth
mountPath: /registry-auth/
volumes:
- name: buildkitd-config
configMap:
name: buildkitd-config
- name: registry-auth
secret:
secretName: buildkitd-registry-auth
---
apiVersion: v1
kind: Service
metadata:
name: buildkit-service
namespace: harbor-eval
spec:
type: ClusterIP
selector:
app: buildkitd
ports:
- port: 1234
targetPort: 1234Configure buildkitd authentication and registry mirror
Because BuildKit v0.15.0 does not support registry authentication through CLI parameters, you must configure it in a configuration file. Create a buildkitd.toml ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: buildkitd-config
namespace: harbor-eval
data:
buildkitd.toml: |
[registry."docker.io"]
mirrors = ["mirror.gcr.io"]
[registry."<YOUR-ACR-ADDRESS>"]
config = "/registry-auth/"In this example, the Docker Hub image source is set to mirror.gcr.io. The Alibaba Cloud registry mirror does not proxy third-party repositories such as swebench, and pulling from them returns an insufficient_scope error.
Create an ACR authentication Secret (in Docker config format):
kubectl create secret generic buildkitd-registry-auth \
--namespace=harbor-eval \
--from-file=config.json=$HOME/.docker/config.jsonBuild and push images in bulk
The harbor admin upload-images command scans all tasks in the dataset, builds the Dockerfile of each task, and pushes the images to ACR:
harbor admin upload-images \
-t /path/to/dataset \
-r <YOUR-ACR-ADDRESS> \
--remote-buildkit tcp://buildkit-service:1234 \
--update-config \
--sanitize-image-names \
--skip-unchanged \
-n 4Parameter | Description |
| Path to the dataset directory |
| ACR address |
| BuildKit service address |
| Updates |
| Sanitizes image names for ACR compatibility |
| Skips tasks whose content has not changed, allowing you to resume interrupted jobs |
| Number of parallel builds |
Command execution flow:
Reads the Dockerfile for each task.
Builds the image on the remote BuildKit service by using buildctl.
Pushes the image to ACR.
Writes
docker_image,built_content_hash, andimage_sha256back totask.toml.
Docker Hub has a rate limit of 100 anonymous pulls per 6 hours (200 for authenticated users). Large-scale builds can easily trigger this limit, causing a failed to resolve source metadata error. Ensure you have configured the mirror.gcr.io registry mirror as described previously.
With -n 4 parallelism, building 500 tasks takes approximately 2 to 3 hours. Run the command with nohup ... & disown to prevent the process from being terminated if your terminal disconnects.
After the build is complete, verify the image:
crane digest <YOUR-ACR-ADDRESS>/<image-name>:<tag>docker_image field in task.toml
After upload-images runs, the following fields are added to the [environment] section of task.toml:
[environment]
docker_image = "registry.cn-hangzhou.aliyuncs.com/<ns>/harbor/<task-name>@sha256:..."
built_content_hash = "abc123..."
image_sha256 = "sha256:..."When harbor run starts, the ACK environment reads docker_image first and pulls the image directly. This takes seconds, whereas building the image in the cluster takes minutes.
Run the evaluation
Configure ACR pull credentials
The trial pod requires authentication to pull pre-built images from ACR. Create a docker-registry Secret:
kubectl create secret docker-registry acr-pull-secret \
--namespace=harbor-eval \
--docker-server=registry.cn-hangzhou.aliyuncs.com \
--docker-username=<username> \
--docker-password=<password>Passing --docker-password directly on the command line writes the password to your shell history. Perform this operation in a secure environment or use a credential file instead.
Run evaluation tasks
The following command starts an evaluation in an ACK cluster. Harbor creates a trial pod for each task in the dataset and runs the evaluation. Replace -a and -m with the agent and model you use.
harbor run \
-p ./my-dataset \
--env ack \
-n 1 \
--ek namespace=harbor-eval \
--ek registry=registry.cn-hangzhou.aliyuncs.com/<ns>/harbor \
--ek image_pull_secret=acr-pull-secret \
-a qwen-coder \
-m qwen3.7-max \
--no-delete \
-yCommon parameters:
Parameter | Description |
| Path to the local dataset |
| Dataset registry reference, such as |
| Use the ACK environment |
| Number of concurrent trials. With |
| Namespace for the trial pods |
| The ACR address. This can be omitted if |
| Name of the Secret used to pull the image |
| The agent to use |
| The model to use |
| Keeps the trial pod after the evaluation is complete for troubleshooting |
| Skips confirmation prompts |
Schedule pods to specific nodes
Use a node selector to schedule trial pods to specific instance types and override resource configurations:
harbor run \
-p ./my-dataset \
--env ack \
-n 1 \
--ek namespace=harbor-eval \
--ek image_pull_secret=acr-pull-secret \
--ek 'node_selector={"node.kubernetes.io/instance-type":"ecs.gn7i-c8g1.2xlarge"}' \
--override-cpus 4 \
--override-memory-mb 8192 \
--override-gpus 1 \
-a qwen-coder \
-m qwen3.7-max \
-yFilter tasks
Use the following parameters to filter which tasks to run:
-i "task-prefix-*" # Only run tasks with matching names
-x "skip-*" # Exclude tasks with matching names
-l 10 # Run a maximum of 10 tasksPass environment variables to the agent
Use the --ae parameter to pass environment variables, such as an API Key, to the trial pod for the agent to use when it calls the model:
--ae ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \
--ae DASHSCOPE_API_KEY=$DASHSCOPE_API_KEYUse a YAML configuration file
To manage numerous parameters, use a YAML configuration file:
# eval-config.yaml
dataset: ./my-dataset # Or a registry reference
agent: qwen-coder
model: qwen3.7-max
environment:
type: ack
kwargs:
namespace: harbor-eval
registry: registry.cn-hangzhou.aliyuncs.com/<ns>/harbor
image_pull_secret: acr-pull-secret
service_account: harbor-eval-sa
n_concurrent_trials: 1
agent_env:
- name: ANTHROPIC_API_KEY
value: "${ANTHROPIC_API_KEY}"harbor run -c eval-config.yaml -yUse ACS sandbox mode (optional)
Enable sandbox mode to run evaluations on Alibaba Cloud Serverless Container (ACS) or to use OpenKruise SandboxSet to pre-warm pods and accelerate trial startup.
The SandboxClaim mode relies on the OpenKruise CRD (agents.kruise.io/v1alpha1). Before you enable this mode, ensure that OpenKruise is installed in the cluster and that the ServiceAccount has the required CRD permissions (for more information, see Configure RBAC).
Core parameters:
Parameter | Description |
| Enables SandboxClaim mode. Harbor automatically creates a SandboxSet warm pool. |
| Specifies the image used by the SandboxSet (defaults to the |
| Size of the warm pool. Recommended to set this to the same value as |
| Timeout in seconds to wait for the SandboxClaim to become ready |
| Adds annotations to the sandbox pod (JSON format) |
| Adds labels to the sandbox pod (JSON format) |
| Injects environment variables into the sandbox pod (JSON format) |
Example of running on ACS:
harbor run \
-p ./dataset \
--env ack \
-n 4 \
--ek namespace=harbor-eval \
--ek image_pull_secret=acr-pull-secret \
--ek use_sandbox_claim=true \
--ek sandboxset_replicas=4 \
--ek 'sandbox_annotations={"k8s.aliyun.com/product-on-demand":"acs"}' \
-a qwen-coder \
-m qwen3.7-max \
-yTo schedule standard trial pods to virtual-kubelet nodes without sandbox mode, use the following parameters:
--ek 'node_selector={"type":"virtual-kubelet"}' \
--ek 'tolerations=[{"key":"virtual-kubelet.io/provider","operator":"Exists"}]'Orchestrate evaluations with a Kubernetes Job
In a CI/CD pipeline, wrap the harbor run command in a Kubernetes Job to trigger an evaluation from within the cluster:
apiVersion: batch/v1
kind: Job
metadata:
name: harbor-eval-job
namespace: harbor-eval
spec:
parallelism: 1
completions: 1
backoffLimit: 0
template:
spec:
serviceAccountName: harbor-eval-sa
restartPolicy: Never
securityContext:
runAsUser: 0
containers:
- name: harbor-eval
image: <harbor-cli-image>
command:
- harbor
- run
- -p
- /mnt/oss/dataset
- --env
- ack
- -n
- "1"
- --ek
- namespace=harbor-eval
- --ek
- image_pull_secret=acr-pull-secret
- -a
- qwen-coder
- -m
- qwen3.7-max
- -o
- /mnt/oss/results/harbor-eval
- -y
env:
- name: ANTHROPIC_API_KEY
valueFrom:
secretKeyRef:
name: api-keys
key: anthropic-api-key
volumeMounts:
- name: dataset
mountPath: /mnt/oss/dataset
readOnly: true
- name: results
mountPath: /mnt/oss/results
volumes:
- name: dataset
persistentVolumeClaim:
claimName: harbor-dataset-pvc
readOnly: true
- name: results
persistentVolumeClaim:
claimName: harbor-results-pvcConfigure RBAC
The Job pod needs permissions to perform operations such as creating trial pods. Grant these permissions through a ServiceAccount:
# harbor-eval-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: harbor-eval-sa
namespace: harbor-eval
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: harbor-eval-role
namespace: harbor-eval
rules:
- apiGroups: [""]
resources: ["pods", "pods/exec", "pods/log"]
verbs: ["get", "list", "create", "delete", "watch"]
- apiGroups: [""]
resources: ["configmaps", "secrets"]
verbs: ["get", "list"]
- apiGroups: ["batch"]
resources: ["jobs"]
verbs: ["get", "list", "create", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: harbor-eval-rolebinding
namespace: harbor-eval
subjects:
- kind: ServiceAccount
name: harbor-eval-sa
namespace: harbor-eval
roleRef:
kind: Role
name: harbor-eval-role
apiGroup: rbac.authorization.k8s.iokubectl apply -f harbor-eval-rbac.yamlWhen using sandbox mode, you must also add permissions to the preceding Role for sandboxsets, sandboxclaims, and sandboxes to allow get/list/create/delete/watch operations.
View evaluation results
Check the status of trial pods
# View all trial pods
kubectl get pods -n harbor-eval -l app=sandbox
# View the logs of a specific pod
kubectl logs -n harbor-eval <pod-name> --tail=100Check the Job status
When orchestrating evaluations with a Kubernetes Job, check the execution status and logs of the Job:
kubectl get jobs -n harbor-eval
kubectl describe job harbor-eval-job -n harbor-eval
kubectl logs -l job-name=harbor-eval-job -n harbor-eval --tail=200View the evaluation result files
The result directory structure for each trial is as follows:
harbor-eval/
└── <task-name>/
├── agent/ # Agent execution logs
├── verifier/ # Verifier validation results
├── artifacts/ # Task artifacts
└── results.json # Summary resultsWhen orchestrating a Kubernetes Job, use -o /mnt/oss/results to specify the output path. The results are written directly back to OSS through OSS CSI FUSE, so no additional upload is required. After the evaluation is complete, use ossutil to view the output:
ossutil ls oss://my-harbor-bucket/results/harbor-eval/FAQ
Pod is stuck in Pending state
Check the pod's events:
kubectl describe pod <pod-name> -n harbor-evalCommon causes include:
Insufficient node resources: Adjust
--override-cpusor--override-memory-mb.Image pull failed: Check
image_pull_secretand the ACR address configuration.Node selector mismatch: Confirm that the labels in the
node_selectorexist on the cluster nodes.
Image pull failure
# Confirm that the target image exists in ACR
docker manifest inspect <acr-address>/<image>:<tag>
# Confirm that the Secret and the pod are in the same namespace
kubectl get secret acr-pull-secret -n harbor-evalHarbor CLI cannot connect to the cluster
# Verify the kubeconfig
kubectl cluster-info
# Confirm RBAC permissions from within a pod
kubectl auth can-i create pods -n harbor-eval --as=system:serviceaccount:harbor-eval:harbor-eval-saWhen running in a cluster, the VPC private IP address that the kubeconfig file points to may not be accessible from the pod CIDR. Change the API Server address to the in-cluster Service endpoint: https://kubernetes.default.svc:443.
Evaluation task times out
Task timeout is controlled by the following fields in task.toml:
[agent]
timeout_sec = 300
[verifier]
timeout_sec = 60For global adjustments, use the top-level CLI parameters (not the --ek parameter):
--agent-timeout-multiplier 2.0The docker_image field has no effect
Make sure the Harbor CLI version is 0.6.5 or later. Older versions do not read the docker_image field.
harbor --versionRelated documents
For complete parameter descriptions, environment configuration guides, and more examples, see the Harbor Open Source Repository and the Harbor Official Documentation.
The Harbor container image registry is a different product from the evaluation framework described in this topic. For information about using it with ACR, see Sync Images from Self-Hosted Harbor to ACR Enterprise Edition.