Files
2026-06-01 13:54:52 +08:00

10 KiB

Tasks: K8s Runtime Support Fix

Branch: dev-k8s | Date: 2026-04-20 Plan: plan.md | Spec: spec.md

Task List

Phase 1: Critical Fixes (P0)


Task 1: Fix global::init_global_docker_manager_with_config() K8s path

File: crates/docker_manager/src/lib.rs
Priority: P0
Estimated Time: 30 min
Status: TODO

Problem: When CONTAINER_RUNTIME=kubernetes, the function calls RuntimeManager::init() but then still tries to set GLOBAL_DOCKER_MANAGER which fails silently or causes issues.

Changes:

#[cfg(feature = "kubernetes")]
pub async fn init_global_docker_manager_with_config(
    config: DockerManagerConfig,
) -> DockerResult<()> {
    let runtime_type = RuntimeType::from_env();
    crate::runtime::RuntimeManager::init(config.clone())
        .await
        .map_err(|e| DockerError::ConfigurationError(e.to_string()))?;
    info!("Runtime initialized with config");

    if runtime_type == RuntimeType::Docker {
        let manager = Arc::new(DockerManager::new(config).await?);
        GLOBAL_DOCKER_MANAGER.set(manager).map_err(|_| {
            DockerError::IoError(std::io::Error::new(
                std::io::ErrorKind::AlreadyExists,
                "global DockerManager already initialized",
            ))
        })?;
        info!("DockerManager initialized with config");
    }
    // K8s mode: RuntimeManager is initialized, GLOBAL_DOCKER_MANAGER stays empty (ok)

    Ok(())
}

Verification:

  • cargo check -p docker_manager --features kubernetes passes
  • Unit test test_runtime_type_from_env_kubernetes passes

Task 2: Verify main.rs runtime initialization flow

File: crates/rcoder/src/main.rs
Priority: P0
Estimated Time: 30 min
Status: TODO

Problem: Need to verify that after calling init_global_docker_manager_with_config(), RuntimeManager::get() works correctly for K8s mode.

Current Code (lines 181-186):

if let Err(e) =
    docker_manager::global::init_global_docker_manager_with_config(docker_manager_config).await
{
    error!("Docker Manager initializefailed: {}", e);
    return Err(anyhow::anyhow!("Docker Manager initialization failed: {}", e));
}

Verification:

  • K8s mode: RuntimeManager::get().await returns Arc<dyn ContainerRuntime>
  • Docker mode: get_global_docker_manager().await returns Arc<DockerManager>
  • Both modes can call cleanup_all() without error

Task 3: Fix stop_container in KubernetesRuntime to handle service_type

File: crates/docker_manager/src/runtime/kubernetes_runtime.rs
Priority: P0
Estimated Time: 45 min
Status: TODO

Problem: stop_container(&self, project_id: &str) only takes project_id, but K8s creates different pod types (RCoder vs ComputerAgentRunner) with different prefixes.

Current:

async fn stop_container(&self, project_id: &str) -> ContainerRuntimeResult<()> {
    let pod_name = self.pod_name(project_id, &ServiceType::RCoder);  // Always RCoder!
    // ...
}

Fix Options:

Option A - Add service_type parameter to stop_container:

async fn stop_container(&self, project_id: &str, service_type: ServiceType) -> ContainerRuntimeResult<()> {
    let pod_name = self.pod_name(project_id, &service_type);
    // ...
}

⚠️ This breaks the trait signature.

Option B - Cache service_type in pod_cache:

// Store (identifier, service_type) in cache when creating container
self.pod_cache.write().await.insert(identifier.to_string(), (pod_info, service_type.clone()));

// In stop_container, lookup cached service_type or default to RCoder
let service_type = self.get_cached_service_type(identifier).await.unwrap_or(ServiceType::RCoder);

Option C - Use stop_container_by_identifier which already has service_type:

async fn stop_container(&self, project_id: &str) -> ContainerRuntimeResult<()> {
    // Try RCoder first, then ComputerAgentRunner
    match self.stop_container_by_identifier(project_id, &ServiceType::RCoder).await {
        Ok(()) => Ok(()),
        Err(ContainerRuntimeError::ContainerStopError(_)) => {
            // Try ComputerAgentRunner if RCoder not found
            self.stop_container_by_identifier(project_id, &ServiceType::ComputerAgentRunner).await
        }
        Err(e) => Err(e),
    }
}

Recommended - Uses existing trait method, handles both types

Implementation: Use Option C

Verification:

  • stop_container("user123") correctly deletes ComputerAgentRunner pod when it exists
  • stop_container("project123") correctly deletes RCoder pod when it exists

Task 4: Fix Makefile k8s commands

File: Makefile
Priority: P1
Estimated Time: 20 min
Status: TODO

Problem:

  1. dev-up-k8s and dev-restart-k8s do kubectl apply followed by kubectl set image, but the image in deployment.yaml is hardcoded to rcoder:test
  2. dev-restart-k8s uses rollout restart which doesn't pick up the new image from set image

Current (broken):

dev-up-k8s:
    kubectl apply -f k8s/manifests/rcoder-deployment.yaml  # Uses image: rcoder:test
    kubectl set image deployment/rcoder rcoder=$(IMAGE)    # But this updates it
    ...

dev-restart-k8s: dev-build-k8s
    kubectl apply -f k8s/manifests/rcoder-deployment.yaml
    kubectl set image deployment/rcoder rcoder=$(IMAGE)    # Sets new image
    kubectl rollout restart deploy/rcoder                 # But restart uses deployment.yaml, not set image!

Fix:

# Use sed to replace image in deployment.yaml before applying
define apply_with_image
    kubectl apply -f k8s/manifests/namespace.yaml
    kubectl apply -f k8s/manifests/serviceaccount.yaml
    sed "s|image: rcoder:test|image: $(IMAGE)|" k8s/manifests/rcoder-deployment.yaml | kubectl apply -f -
    kubectl apply -f k8s/manifests/rcoder-service.yaml
endef

dev-up-k8s:
    $(call apply_with_image)
    kubectl rollout status deploy/rcoder -n $(K8S_NAMESPACE) --timeout=$(ROLLOUT_TIMEOUT)

dev-restart-k8s: dev-build-k8s
    kubectl delete pods -n $(K8S_NAMESPACE) -l app=rcoder --ignore-not-found
    kubectl rollout status deploy/rcoder -n $(K8S_NAMESPACE) --timeout=$(ROLLOUT_TIMEOUT)

Key changes:

  1. Use sed to replace image tag at apply time
  2. dev-restart-k8s uses kubectl delete pods instead of rollout restart (simpler, faster)
  3. dev-down-k8s stays the same (already correct)

Verification:

  • make dev-up-k8s IMAGE=rcoder:test-k8s deploys with correct image
  • make dev-restart-k8s IMAGE=rcoder:test-k8s rebuilds and redeploys
  • make dev-down-k8s cleans up properly

Task 5: Add K8s mode to Cargo features in rcoder

File: crates/rcoder/Cargo.toml
Priority: P1
Estimated Time: 10 min
Status: TODO

Problem: The kubernetes feature in rcoder passes to docker_manager, but need to verify it's properly configured.

Current:

[features]
# Kubernetes 支持:启用 Kubernetes 运行时模式
# 启用后可通过 CONTAINER_RUNTIME=kubernetes 环境变量切换到 K8s 模式
kubernetes = ["docker_manager/kubernetes"]

Verification:

  • cargo build --features kubernetes works
  • cargo build --features kubernetes --package rcoder produces binary with K8s support

Task 6: Test K8s mode end-to-end

Priority: P0
Estimated Time: 60 min
Status: TODO

Prerequisites: Kind cluster or real K8s cluster available

Test Steps:

# 1. Build K8s image
make dev-build-k8s IMAGE=rcoder:test-k8s

# 2. Deploy to K8s
make dev-up-k8s IMAGE=rcoder:test-k8s

# 3. Check rcoder pod is running
kubectl get pods -n rcoder
kubectl logs -n rcoder -l app=rcoder

# 4. Test /health endpoint
NODE_PORT=$(kubectl get svc rcoder -n rcoder -o jsonpath='{.spec.ports[0].nodePort}')
curl http://localhost:$NODE_PORT/health

# 5. Test /chat endpoint (creates RCoder agent pod)
curl -X POST http://localhost:$NODE_PORT/chat \
  -H "Content-Type: application/json" \
  -d '{"prompt": "hello"}'

# 6. Check agent pod was created
kubectl get pods -n rcoder

# 7. Test /computer/chat endpoint (creates ComputerAgentRunner pod)
curl -X POST http://localhost:$NODE_PORT/computer/chat \
  -H "Content-Type: application/json" \
  -d '{"user_id": "test-user", "prompt": "hello"}'

# 8. Check computer agent pod was created
kubectl get pods -n rcoder -l user_id=test-user

# 9. Test cleanup (delete chat session)
# ... verify pods are deleted

# 10. Cleanup
make dev-down-k8s

Expected Results:

  • /health returns healthy
  • /chat creates rcoder-agent-{project_id} pod
  • /computer/chat creates computer-agent-runner-{user_id} pod
  • Both pods are reachable via gRPC from rcoder main pod
  • Cleanup properly deletes pods

Task 7: Verify Docker mode still works (regression test)

Priority: P1
Estimated Time: 30 min
Status: TODO

Test Steps:

# 1. Build Docker image
make dev-build

# 2. Start Docker Compose
make dev-up

# 3. Test /health
curl http://localhost:8087/health

# 4. Test /chat
curl -X POST http://localhost:8087/chat \
  -H "Content-Type: application/json" \
  -d '{"prompt": "hello"}'

# 5. Verify container created
docker ps | grep rcoder-agent

# 6. Cleanup
make dev-down

Expected Results:

  • All existing functionality works as before
  • No regression in Docker mode

Task Summary

# Task Priority Status Dependencies
1 Fix init_global_docker_manager_with_config() P0 TODO -
2 Verify main.rs runtime init P0 TODO Task 1
3 Fix stop_container service_type P0 TODO -
4 Fix Makefile k8s commands P1 TODO -
5 Verify Cargo features P1 TODO -
6 Test K8s mode end-to-end P0 TODO Tasks 1-5
7 Regression test Docker mode P1 TODO -

Completion Criteria

  • All P0 tasks complete
  • K8s mode can create/manage RCoder agent pods
  • K8s mode can create/manage ComputerAgentRunner pods
  • Docker mode regression tests pass
  • Makefile k8s commands work correctly
  • Code compiles with both kubernetes feature enabled and disabled