Skip to content

Fix P2 provider status semantics#32

Merged
chimerakang merged 8 commits into
mainfrom
feat/p2-vk-controller-wiring
Jun 18, 2026
Merged

Fix P2 provider status semantics#32
chimerakang merged 8 commits into
mainfrom
feat/p2-vk-controller-wiring

Conversation

@chimerakang

Copy link
Copy Markdown
Owner

Summary

  • reject unsupported restart policies in the P2 pod translator and align docs with restartPolicy=Never
  • parse apple/container inspect exit codes so non-zero stopped workloads report Failed
  • preserve prior pod status on transient runtime status errors and only advertise kubelet endpoint when serving TLS is enabled

Verification

  • go test ./...
  • go vet ./...
  • make build

chimerakang and others added 8 commits June 18, 2026 23:38
Start the Virtual Kubelet node controller from cmd/macvz-kubelet around the
existing apple/container runtime driver, without registering Pods yet.

- pkg/config: add Config.RestConfig to build a Kubernetes REST config from an
  explicit kubeconfig path, KUBECONFIG/default rules, or in-cluster config. A
  missing or unparseable kubeconfig fails loudly at startup rather than silently
  falling back.
- cmd/macvz-kubelet: build the kube client, construct the provider over the
  runtime driver, and run a node.NewNodeController with NaiveNodeProvider on a
  minimal Node object. Capacity/conditions (#14), lease/heartbeat (#15), and the
  Pod lifecycle (#16) build on this loop.
- Graceful shutdown via signal.NotifyContext (SIGINT/SIGTERM); runtime readiness
  remains visible on startup.
- Tests cover missing, invalid, and valid kubeconfig wiring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#14)

Advertise a coherent, configurable node shape through Virtual Kubelet so the
scheduler can target the Mac intentionally.

- pkg/config: add a `node:` block (cpu/memory/pods, os/arch, internalIP,
  labels, annotations, taints). Capacity defaults stay conservative (2 CPU /
  4Gi / 20 pods) until P1 density data is recorded. Capacity() and Taints()
  parse to k8s core types and are validated at load time (bad quantity or taint
  effect fails fast). Default taint is the well-known
  virtual-kubelet.io/provider=macvz:NoSchedule so only tolerating Pods land.
- pkg/provider: BuildNode assembles the corev1.Node — capacity == allocatable
  (no MVP reservation), workload-platform OS/arch labels and node info, merged
  user labels/annotations, taints, addresses, and a coherent condition set. The
  Ready condition reflects live runtime readiness via the optional
  runtime.Pinger; pressure conditions report False.
- cmd/macvz-kubelet: resolve the configured node shape, auto-detect the host
  InternalIP when unset, and register the assembled node. Ongoing
  heartbeat/lease updates remain #15.
- config.example.yaml documents the new node block; tests cover capacity/taint
  parsing, validation, label override/merge, readiness mapping, and address
  handling.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Keep the MacVz virtual node alive and surface runtime readiness over time.

- pkg/config: add heartbeat tuning to the node block — enableLease (default
  true), leaseDurationSeconds (40), pingInterval (10s), statusUpdateInterval
  (1m), mirroring Virtual Kubelet's defaults. HeartbeatIntervals() parses and
  validates the durations; Validate() rejects bad intervals and a non-positive
  lease duration when leases are enabled.
- pkg/provider: add NodeStatusProvider implementing node.NodeProvider. Ping
  reports process liveness (driving the lease/heartbeat); NotifyNodeStatus runs
  a watch loop that re-probes the runtime each status interval and pushes a
  refreshed node only when the Ready condition flips, so runtime failures show
  up as NotReady without removing the node. The loop exits on context cancel.
- cmd/macvz-kubelet: wire the status provider plus WithNodePingInterval,
  WithNodeStatusUpdateInterval, WithNodeEnableLeaseV1 (lease in kube-node-lease),
  and a status-update error handler that logs and returns nil so the control
  loop and client-go retry/backoff handle transient API errors.
- config.example.yaml documents the heartbeat block; tests cover interval
  parsing/validation, lease-duration validation, Ping semantics, push-on-change
  (both directions), and clean shutdown (race-clean).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the P0 not-implemented skeleton with a real PodLifecycleHandler backed
by the runtime abstraction and an in-memory pod/workload state store.

- pkg/provider/provider.go: add the podState store mapping namespace/name to the
  runtime workload IDs backing each container, with a sync.RWMutex for concurrent
  reconcile/CRUD access.
- pkg/provider/pod.go: implement CreatePod/UpdatePod/DeletePod/GetPod/
  GetPodStatus/GetPods. CreatePod pulls+creates+starts a workload per container,
  is a no-op for an already-tracked Pod, and rolls back started workloads on
  partial failure so retries start clean. DeletePod stops+destroys and is
  idempotent (unknown Pod -> errdefs.NotFound). Missing Pods return
  errdefs.NotFound so Virtual Kubelet reconciliation behaves correctly.
- pkg/provider/status.go: reconcile observed runtime state into container
  statuses and aggregate the Pod phase (Pending/Running/Succeeded/Failed); a
  runtime ErrNotFound maps to a terminated "Lost" container rather than failing
  the whole status read.
- pkg/provider/translate.go: minimal Pod->ContainerSpec translation (image,
  command/args, literal env, CPU/memory). Richer translation is #17.
- Tests with a recording fake runtime cover create/get/list/status/delete,
  idempotent create+delete retries, rollback, phase mapping, lost workloads,
  and ErrNotReady propagation (race-clean).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Build out the MVP translation layer from Kubernetes Pod/container fields to
types.ContainerSpec, with clear rejection of unsupported shapes.

- pkg/provider/translate.go: translatePod validates a Pod is a supported shape
  and translates its single container. workloadID derives a stable, DNS-safe
  (RFC 1123) name from namespace/name/container, hash-truncated when it would
  exceed 63 chars, so the driver gets a deterministic --name. Image, command,
  args, literal env, and CPU/memory (limit, falling back to request) are
  translated. unsupportedReasons reports multi-container, init/ephemeral
  containers, user volumes (the auto-mounted service-account token is
  tolerated), pod/container securityContext, host namespaces, envFrom, and env
  valueFrom — each with an actionable message.
- pkg/provider/pod.go: CreatePod now translates a single container. Unsupported
  specs and arch-incompatible images (the P1 ErrIncompatibleArch signal) are
  recorded as a sticky terminal Failed status with a clear reason/message and
  return nil, so Virtual Kubelet surfaces it to Kubernetes instead of retrying
  forever. provider/status.go honors the sticky status during reconciliation.
- Table-driven tests cover the kubectl-run shape, command/args/env/resource
  sizing, request fallback, every unsupported feature, SA-token tolerance, and
  workload-ID stability/DNS-safety/truncation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Expose the Virtual Kubelet log and exec paths on the provider, delegating to
runtime.Logs and runtime.Exec for running micro-VM workloads.

- pkg/provider/logs_exec.go: GetContainerLogs maps Virtual Kubelet
  ContainerLogOpts (follow/tail) onto runtime.LogOptions; RunInContainer wires
  AttachIO stdin/stdout/stderr and TTY into runtime.ExecIO, drains the resize
  channel for TTY sessions (the runtime has no resize), and converts a clean
  non-zero command exit into a utilexec.ExitError so kubectl reports the exit
  status. A workloadID helper resolves container -> runtime workload ID, and
  runtime ErrNotFound/ErrNotRunning map to clear Kubernetes-facing errors
  (errdefs.NotFound / "not running"). Compile-time assertions pin the method
  signatures to the VK handler function types.
- Fake-runtime tests cover log streaming + tail propagation, exec stream wiring,
  non-zero exit-code mapping, and unknown pod/container errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…st (#19)

Tie the virtual node together end-to-end and document the P2 acceptance path.

Serving (the code needed for the smoke test to actually work):
- cmd/macvz-kubelet/serve.go: start the Virtual Kubelet pod controller with
  pod (node-filtered) / configmap / secret / service informers and an event
  recorder, so scheduled Pods become micro-VMs via the provider. Start an HTTPS
  kubelet API server (api.PodHandler) routing kubectl logs/exec to the provider;
  it is a no-op with a clear warning when no serving cert is configured, so Pods
  still run without logs/exec.
- main.go starts both after the node becomes Ready and tears them down on
  shutdown.
- pkg/config: add node.kubeletPort (default 10250) and servingTLSCertFile/
  servingTLSKeyFile, validated (port range; cert+key set together).
- pkg/provider: advertise the kubelet endpoint port in node DaemonEndpoints so
  the API server knows where to reach logs/exec.

Manifests and docs:
- deployments/rbac.yaml: ServiceAccount + ClusterRole/Binding scoped to the MVP
  (nodes/status, pods/status, configmaps/secrets/services read, events, leases).
- deployments/alpine-smoke.yaml: single-container Alpine Pod tolerating the
  provider taint and targeting the virtual node.
- docs/P2_SMOKE_TEST.md: build/configure/run, kubectl get nodes + describe,
  run Alpine, verify logs/exec (incl. non-zero exit and error cases), cleanup,
  and troubleshooting. Linked as P2 acceptance evidence from MASTER_TASKS.

Dependency: bump google.golang.org/genproto to resolve an ambiguous-import
conflict pulled in by the kubelet API server packages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant