Files
qiming/qiming-rcoder/specs/k8s-runtime-support/tasks.md
2026-06-01 13:54:52 +08:00

326 lines
10 KiB
Markdown

# Tasks: K8s Runtime Support Fix
**Branch**: `dev-k8s` | **Date**: 2026-04-20
**Plan**: [plan.md](./plan.md) | **Spec**: [spec.md](./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**:
```rust
#[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):
```rust
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**:
```rust
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`:
```rust
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:
```rust
// 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:
```rust
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)**:
```makefile
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**:
```makefile
# 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**:
```toml
[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**:
```bash
# 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**:
```bash
# 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