drivers

package
v0.0.0-...-a0319ed Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 2, 2026 License: AGPL-3.0 Imports: 33 Imported by: 0

Documentation

Overview

Package drivers orchestrates the docker-side state of `clrk dev`: the shared bridge network the k3d cluster + local registry sit on, and the k3d-library-driven cluster controller that brings them up. The k3d node and the (optional) local registry both attach to the `clrk` bridge so in-cluster pods reach the registry via docker DNS.

Index

Constants

View Source
const (
	RegistryDataVolume = "clrk-registry-data"
	RegistryDataMount  = "/var/lib/registry"
)

RegistryDataVolume backs the local registry's /var/lib/registry so pushed images survive `clrk dev` stop/start cycles. Attached via k3d's Registry.Volumes field at RegistryRun time.

View Source
const (
	// ClusterName is the k3d cluster identifier. Distinct from
	// NetworkName ("clrk") and the in-cluster "clrk" namespace so the
	// three layers aren't confusable in `docker ps` or `kubectl` output.
	ClusterName = "clrk-dev"

	// RegistryName is the docker container name for the local OCI
	// registry on the shared bridge. In-cluster pods resolve it as
	// `clrk-registry:5000` via docker DNS.
	RegistryName = "clrk-registry"

	// DefaultK3sImage pins a minor version whose k8s.io/api release matches
	// clrk and apoxy-cloud's module resolution (v0.34.x). Override via
	// ClusterDriver.K3sImage for CI pinning.
	DefaultK3sImage = "rancher/k3s:v1.34.1-k3s1"

	// DefaultRegistryImage is the OCI distribution image k3d boots when
	// we hand it a Registry with Image=="" — we pin explicitly so a k3d
	// upgrade can't silently swap the binary.
	DefaultRegistryImage = "docker.io/library/registry:2"

	// KubeconfigFileName is written into the shared data dir so other
	// drivers (and the user) can find the rewritten in-network kubeconfig.
	KubeconfigFileName = "kubeconfig"
)
View Source
const (
	V4Subnet = "192.168.231.0/24"
	V6Subnet = "fd00:dead:beef::/64"
)

V4Subnet and V6Subnet are the explicit subnets declared on the shared bridge. Both must be present: k3d scans IPAM.Config to decide which address families k3s should enable, and an --ipv6 network with only a v6 subnet declared causes k3s inside k3d to crash-loop with no log output. Declaring v4 as well makes k3d see a dual-stack network (matching how plain `docker run` sees one regardless of IPAM config).

View Source
const (
	ClrkControllerManagerImagePath = "us-west1-docker.pkg.dev/apoxy-dev/public/clrk-controller-manager"
	ClrkWorkerImagePath            = "us-west1-docker.pkg.dev/apoxy-dev/public/clrk-worker"

	// LocalRegistryControllerManagerImage is the in-network ref clrk
	// dev's controller-manager container uses when launched with
	// --registry-image=controller-manager=<…>. ClusterDriver brings up
	// a docker registry container at this exact name on the shared
	// `clrk` network and wires it into the k3d cluster via the
	// SimpleConfig's Registries.Use field, so docker resolves the host
	// part via docker DNS and k3s's containerd pulls from it without
	// a registries.yaml override. The host pushes against
	// localhost:<RegistryHostPort()> for the same content.
	LocalRegistryControllerManagerImage = "clrk-registry:5000/clrk/controller-manager:dev"
	// LocalRegistryWorkerImage is the matching ref for the worker.
	LocalRegistryWorkerImage = "clrk-registry:5000/clrk/worker:dev"
)

Public-GAR repo paths where every clrk main commit lands. The .github/workflows/clrk.yml workflow in apoxy-cloud (triggered by a repository_dispatch from this repo) builds the bazel OCI tarballs and pushes them with the first 8 hex chars of the clrk SHA as the tag. ImageTag() below mirrors that derivation so a `clrk` binary built from a given SHA defaults to the matching pushed image.

View Source
const NetworkName = "clrk"

NetworkName is the shared docker network k3d and the local registry attach to. Pods inside the cluster reach the registry as `clrk-registry:5000` over this network.

Variables

View Source
var (
	DefaultControllerManagerImage = ClrkControllerManagerImagePath + ":" + ImageTag()
	DefaultWorkerImage            = ClrkWorkerImagePath + ":" + ImageTag()
)

Default image refs are package vars (not consts) because the tag is derived from the binary's embedded VCS info at process start. Cobra reads these as flag defaults; the drivers fall back to them when no override is supplied.

View Source
var ClusterServerContainerName = k3dclient.GenerateNodeName(ClusterName, k3d.ServerRole, 0)

ClusterServerContainerName is the docker container name of the single-server node, sourced from k3d's own naming helper so any future change to k3d's prefix scheme is picked up automatically.

Functions

func EnsureNetwork

func EnsureNetwork(ctx context.Context) error

EnsureNetwork creates the shared docker network if it does not already exist. Safe to call concurrently; the docker daemon serializes creates.

IPv6 is enabled on the bridge so Envoy's DFP cluster can reach AAAA-only hops. If we find a pre-existing `clrk` network whose IPAM does not declare both subnets (e.g. created by an older clrk dev that only set --subnet for v6, or a v4-only network from before IPv6 landed), tear it down and recreate — the alternative is silent ENETUNREACH on every v6 upstream or k3s crash-looping under k3d.

func HostGatewayIP

func HostGatewayIP(ctx context.Context, network string) (string, error)

HostGatewayIP returns the IP that docker resolves the magic "host-gateway" alias to for a container attached to the given network. On Linux native docker this is the bridge gateway (which is the host). On Docker Desktop for Mac/Windows it's the VPN-kit address that bridges container traffic back to the host process. We discover it by spawning a one-shot container on the same network with `--add-host host.docker.internal:host-gateway` and parsing the /etc/hosts entry docker writes — the only portable way to get the same IP a k8s HostAlias entry needs.

network must be the same docker network k3d's node is attached to — otherwise on Linux the resolved IP is the gateway of a *different* bridge and may not be on the routing path the in-cluster Pod uses. On Docker Desktop the network choice doesn't affect the result.

func ImageTag

func ImageTag() string

ImageTag returns the GAR tag matching this clrk binary, derived from the binary's embedded VCS info. Two cases:

  1. Local build (`go install ./cmd/clrk` from a clean checkout): debug.BuildInfo.Settings carries `vcs.revision` = full SHA. We truncate to 8 hex chars to match the publish workflow.
  2. Remote install (`go install github.com/apoxy-dev/clrk/cmd/clrk@<rev>`): debug.BuildInfo.Main.Version is a Go pseudo-version of the form `v0.0.0-<timestamp>-<sha-12>`. We pull the last 12 hex chars and truncate to 8.

Falls back to "latest" if neither is reachable — the publish workflow doesn't push a `:latest` tag, so that fallback surfaces a clear `manifest unknown` from `docker pull` instead of silently running stale code.

func LocalRegistryHostRef

func LocalRegistryHostRef(component string, port int) string

LocalRegistryHostRef returns the host-side image ref for the dev session's local registry on the supplied port. The pods see the same content via LocalRegistry{ControllerManager,Worker}Image (resolved via docker DNS on the shared clrk network); the host has to address the registry as localhost:<port>. Callers like `clrk dev push-image` use this to push bytes the pods will pull.

Types

type ClusterDriver

type ClusterDriver struct {
	// DataDir is the host path where rewritten kubeconfigs are written.
	// Must be the same path mounted into the cm/worker drivers so they
	// can read the in-network kubeconfig.
	DataDir string
	// K3sImage overrides the k3s image k3d boots. Empty = DefaultK3sImage.
	K3sImage string
	// EnableRegistry brings up the local OCI registry container alongside
	// the cluster and wires it into k3d's containerd registries.yaml. Only
	// useful for the inner dev loop where you `clrk dev push-image`
	// locally-built images; leave false when pulling published refs.
	EnableRegistry bool
	// RegistryPort is the host port the local registry publishes. Zero
	// means auto-pick a free port; the actual port is available via
	// RegistryHostPort() after Start. Ignored when EnableRegistry is false.
	RegistryPort int
	// contains filtered or unexported fields
}

ClusterDriver brings up a k3d cluster (and optionally a colocated local OCI registry) by calling k3d v5 as a Go library — no `k3d` binary on PATH. Both attach to the shared `clrk` docker network (EnsureNetwork). When EnableRegistry is true, pods reach the registry as `clrk-registry:5000` and the host pushes to `localhost:<RegistryHostPort()>`.

func NewClusterDriver

func NewClusterDriver(dataDir, k3sImage string, registryPort int) *ClusterDriver

NewClusterDriver constructs a driver with sane defaults. dataDir is required because the kubeconfig has to land somewhere other drivers can see. k3sImage may be empty to accept the default.

func (*ClusterDriver) ApplyObjects

func (d *ClusterDriver) ApplyObjects(ctx context.Context, objs ...client.Object) error

ApplyObjects server-side-applies one or more typed objects through the host's controller-runtime client. Force ownership so re-applies from `clrk dev reload` reclaim fields from any previous manager.

func (*ClusterDriver) EnsureNamespace

func (d *ClusterDriver) EnsureNamespace(ctx context.Context, ns string) error

EnsureNamespace creates ns if it doesn't exist. Idempotent. Called before starting the controller-manager so the supervised envoy-gateway certgen can write its TLS Secret into the runtime namespace. The controller binary doesn't install its own Namespace, so whoever brings up the controller has to materialize it.

func (*ClusterDriver) HostKubeconfigPath

func (d *ClusterDriver) HostKubeconfigPath() string

HostKubeconfigPath is the host path to the kubeconfig pointing at the kube API's host-published port. Use this for `kubectl` from the host, integration tests, and `clrk dev wait-ready`.

func (*ClusterDriver) KubeClient

func (d *ClusterDriver) KubeClient(ctx context.Context) (client.Client, error)

KubeClient returns the lazy controller-runtime client. Useful for callers that need to Get/Watch/Patch outside the SSA-only path ApplyObjects offers.

func (*ClusterDriver) KubeconfigPath

func (d *ClusterDriver) KubeconfigPath() string

KubeconfigPath is the host path to the kubeconfig with the server URL rewritten to the in-network k3d node hostname. Bind-mount into cm / worker containers so they reach k3s via the shared docker network.

func (*ClusterDriver) NodeContainerName

func (d *ClusterDriver) NodeContainerName() string

NodeContainerName is k3d's deterministic name for the server node container. Used by dev_status / dev_logs to tail the right container.

func (*ClusterDriver) NodeHostname

func (d *ClusterDriver) NodeHostname() string

NodeHostname is the in-network DNS name for the apiserver. Same as NodeContainerName because docker resolves container names as DNS labels on the shared bridge network.

func (*ClusterDriver) RegistryHostPort

func (d *ClusterDriver) RegistryHostPort() int

RegistryHostPort returns the host port the local registry is published on. Zero before Start succeeds.

func (*ClusterDriver) Reset

func (d *ClusterDriver) Reset(ctx context.Context) error

Reset is Stop plus the shared docker network. Used by the drift-gate recreate path in `clrk dev`, which has to evict any container still pinning the `clrk` bridge (otherwise the next EnsureNetwork-recreate fails). The network is left in place by Stop because a normal clrk-dev shutdown doesn't need to disturb it.

func (*ClusterDriver) Rollout

func (d *ClusterDriver) Rollout(ctx context.Context, ns, name string) error

Rollout bumps a `clrk.apoxy.dev/restartedAt` annotation on the Deployment's pod template, triggering the same rolling-restart behavior as `kubectl rollout restart`. Uses a strategic-merge patch so concurrent reconciles (e.g. envoy-gateway, the WorkerPool controller) can't lose the rollout to a 409 — no Get + Update race.

func (*ClusterDriver) RolloutWorkerPool

func (d *ClusterDriver) RolloutWorkerPool(ctx context.Context, ns, name string) (int64, error)

RolloutWorkerPool triggers a rolling restart of a WorkerPool's worker Deployment by bumping RestartedAtAnnotation on the WorkerPool's spec.template.metadata.annotations — NOT on the Deployment. The Deployment is a controller-owned child: WorkerPoolDeploymentReconciler rebuilds its pod template from wp.spec.template on every reconcile (via internal/workerpod, which propagates spec.template.metadata.annotations into the pod template), so an annotation patched straight onto the Deployment is wiped on the next pass and the freshly-created ReplicaSet is scaled back to zero (the rollout silently no-ops). Patching the WorkerPool makes the controller propagate the annotation into the Deployment template itself — it can't revert a change it's the source of. Uses a JSON merge patch: no Get+Update, so it can't lose the rollout to a 409, and on an annotations map its merge semantics match strategic merge anyway.

Returns the WorkerPool's post-patch metadata.generation so the caller can wait for status.observedGeneration to catch up (see WaitWorkerPoolConverged) — the race-free signal that the rollout this patch triggered has completed.

func (*ClusterDriver) Start

func (d *ClusterDriver) Start(ctx context.Context) error

Start brings up the k3d cluster (and the registry when EnableRegistry is set) on the shared `clrk` network. Idempotent: if a cluster/registry with our names already exists (left over from a previous `clrk dev` that wasn't stopped), we reuse it instead of failing.

func (*ClusterDriver) Stop

func (d *ClusterDriver) Stop(ctx context.Context) error

Stop deletes the k3d cluster and the registry. Idempotent: a missing cluster or registry is not an error.

func (*ClusterDriver) WaitDeploymentAvailable

func (d *ClusterDriver) WaitDeploymentAvailable(ctx context.Context, ns, name string, timeout time.Duration) error

WaitDeploymentAvailable blocks until ns/name reports DeploymentAvailable=True or timeout elapses.

func (*ClusterDriver) WaitRolloutComplete

func (d *ClusterDriver) WaitRolloutComplete(ctx context.Context, ns, name string, timeout time.Duration, wantDigests ...string) error

WaitRolloutComplete blocks until ns/name has fully rolled out — observed generation caught up, every replica updated and available, none unavailable — and at least one running, Ready pod reports one of wantDigests as a container image. This turns the fire-and-forget Rollout into a trustworthy gate: instead of returning the instant a rollout is triggered, it returns only once the new pod is actually serving, and (when digests are supplied) fails loudly rather than silently testing stale code when the node served a cached `:dev` tag. Pass no wantDigests to wait for rollout completion only — e.g. `clrk dev reload`, which has no pushed digest to assert against.

func (*ClusterDriver) WaitWorkerPoolConverged

func (d *ClusterDriver) WaitWorkerPoolConverged(ctx context.Context, ns, name string, wantGeneration int64, timeout time.Duration) error

WaitWorkerPoolConverged blocks until the WorkerPool has reconciled at least wantGeneration and reports its workers rolled out and ready — Available=True and Progressing=False. This is the race-free counterpart to triggering a rollout via the WorkerPool: the WorkerPool's status is what the controller derives from the Deployment, so waiting on it (rather than polling the Deployment directly) can't observe the pre-reconcile converged state. The observedGeneration floor ensures we're reading the controller's verdict on THIS rollout, not a stale status from before the trigger.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL