Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This file provides guidance to AI assistants when working with code in this repo

## What this project is

Cloud defines Kubernetes CRDs and API types for virtual networking. It is **API-only** — no controllers, no binaries, no runtime. Implementations consume these APIs; this repo just defines the contract.
Cloud defines Kubernetes CRDs and API types for virtual networking, and ships `vpc-controller` (`cmd/main.go`), which reconciles them in a POP cell against the galactic data plane. API types live in `api/v1alpha1/`; controller code lives in `internal/`.

Module: `go.datum.net/cloud`

Expand Down
22 changes: 22 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Build the manager binary
FROM --platform=$BUILDPLATFORM golang:1.26 AS builder
ARG TARGETOS
ARG TARGETARCH

WORKDIR /workspace
COPY go.mod go.mod
COPY go.sum go.sum
RUN go mod download

COPY cmd/ cmd/
COPY api/ api/
COPY internal/ internal/

RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -ldflags "-s -w" -o vpc-controller cmd/main.go

FROM gcr.io/distroless/static-debian12:nonroot
WORKDIR /
COPY --from=builder /workspace/vpc-controller .
USER 65532:65532

ENTRYPOINT ["/vpc-controller"]
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Services

Kubernetes CRDs for virtual networking — API-only, no controller.
Kubernetes CRDs for virtual networking, and the controller that reconciles them in a POP cell.

**API group:** `cloud.datumapis.com/v1alpha1`
**Stability:** Alpha
Expand All @@ -10,7 +10,13 @@ Kubernetes CRDs for virtual networking — API-only, no controller.

## What it is

Services defines Kubernetes Custom Resource Definitions for virtual tenant networking. It ships type definitions, validation rules, and CRD manifests — no controller, no runtime, no binaries. External implementations import this module to register these types and reconcile the resources.
Services defines Kubernetes Custom Resource Definitions for virtual tenant networking, plus `vpc-controller`, which realizes them against the galactic data plane.

The controller runs in a POP cell beside network-services-operator, compute and the workload providers. It turns a `NetworkContext` into a `VPC` identity; when a `NetworkInterface` claim is fulfilled it creates the `VPCAttachment` and the `NetworkAttachmentDefinition`, allocates the attachment identifier, and publishes the annotations a workload must carry; and it projects what the data plane reported back onto `VPCAttachment` and `NetworkInterface` status.

It also serves a mutating admission webhook that injects the Multus annotation into Pods labelled `networking.datumapis.com/inject-interfaces: "true"`, so Multus knowledge stays inside the one component that writes NetworkAttachmentDefinitions.

It requires `--attachment-mode` (`Netns` or `Hypervisor`) — how guests in the cell consume an interface. There is no default, because defaulting would hand a microVM an interface it cannot use.

## Resources

Expand Down Expand Up @@ -50,7 +56,8 @@ spec:
## Quick start

```bash
kubectl apply -k config/crd
kubectl apply -k config/crd # types only
kubectl apply -k config/default # types, RBAC and the controller
```

## Development
Expand Down
9 changes: 8 additions & 1 deletion Taskfile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ tasks:
desc: Run unit tests
deps: [fmt, vet]
cmds:
- GOOS=linux go test $(go list ./... | grep -v /e2e) -coverprofile cover.out
- go test $(go list ./... | grep -v /e2e) -coverprofile cover.out

lint:
desc: Run golangci-lint, yamlfmt, and yaml extension check
Expand Down Expand Up @@ -95,6 +95,7 @@ tasks:
cmds:
- task: generate:methods
- task: generate:manifests
- task: generate:rbac
- task: generate:docs

generate:methods:
Expand All @@ -109,6 +110,12 @@ tasks:
cmds:
- '{{.CONTROLLER_GEN}} crd paths="./api/..." output:crd:artifacts:config=config/crd'

generate:rbac:
desc: Generate RBAC manifests from controller markers
deps: [install:controller-gen]
cmds:
- '{{.CONTROLLER_GEN}} rbac:roleName=vpc-controller paths="./internal/..." output:rbac:artifacts:config=config/rbac'

generate:docs:
desc: Generate API reference documentation
deps: [install:crd-ref-docs]
Expand Down
1 change: 1 addition & 0 deletions api/v1alpha1/groupversion_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ var (
GroupVersion = schema.GroupVersion{Group: "cloud.datumapis.com", Version: "v1alpha1"}

// SchemeBuilder is used to add go types to the GroupVersionKind scheme.
//nolint:staticcheck // scheme.Builder is what gives the generated types a Register().
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}

// AddToScheme adds the types in this group-version to the given scheme.
Expand Down
96 changes: 77 additions & 19 deletions api/v1alpha1/vpcattachment_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,25 @@ import (

const VPCAttachmentAnnotation = "k8s.v1alpha1.cloud.datumapis.com/vpc-attachment"

const (
// ConditionTypeReady reports that identifiers are allocated and the
// NetworkAttachmentDefinition is written.
ConditionTypeReady = "Ready"

// ConditionTypeProgrammed reports that the data plane realized the attachment.
ConditionTypeProgrammed = "Programmed"
)

// VPCAttachmentSpec defines the desired state of VPCAttachment
//
// +kubebuilder:validation:XValidation:rule="has(self.vpc) && self.vpc.name != ”",message="vpc reference is required"
type VPCAttachmentSpec struct {
// VPC this attachment belongs to.
// +required
VPC VPCRef `json:"vpc"`

// NetworkInterface this attachment realizes.
// +optional
InterfaceRef *NetworkInterfaceRef `json:"interfaceRef,omitempty"`

// Interface defines the network interface configuration.
// +required
Interface VPCAttachmentInterface `json:"interface"`
Expand All @@ -43,27 +54,61 @@ type VPCRef struct {
Name string `json:"name"`
}

// NetworkInterfaceRef references a networking.datumapis.com NetworkInterface in
// the same namespace.
type NetworkInterfaceRef struct {
// Name of the NetworkInterface.
// +kubebuilder:validation:MinLength=1
// +required
Name string `json:"name"`
}

// IPAddress is an IPv4 or IPv6 address with CIDR notation.
// +kubebuilder:validation:MaxLength=64
type IPAddress string

// VPCAttachmentInterfaceMode is how the workload consumes the interface. It
// describes the guest, not the data plane, so a change of implementation on the
// data plane side does not move this API.
// +kubebuilder:validation:Enum=Netns;Hypervisor
type VPCAttachmentInterfaceMode string

const (
// VPCAttachmentInterfaceModeNetns moves the interface into the workload's
// network namespace, which is what a container consumes.
VPCAttachmentInterfaceModeNetns VPCAttachmentInterfaceMode = "Netns"

// VPCAttachmentInterfaceModeHypervisor hands the interface to a hypervisor as
// a device, which is what a virtual machine guest consumes.
VPCAttachmentInterfaceModeHypervisor VPCAttachmentInterfaceMode = "Hypervisor"
)

// VPCAttachmentInterface defines the network interface details.
//
// +kubebuilder:validation:XValidation:rule="self.addresses.all(a, isCIDR(a))",message="each address must be a valid IPv4 or IPv6 CIDR"
// +kubebuilder:validation:XValidation:rule="!has(self.addresses) || self.addresses.all(a, isCIDR(a))",message="each address must be a valid IPv4 or IPv6 CIDR"
type VPCAttachmentInterface struct {
// Name of the interface (e.g., eth0).
// +required
// +default:value="eth0"
Name string `json:"name"`

// A list of IPv4 or IPv6 addresses associated with the interface.
// +kubebuilder:validation:MinItems=1
// Mode is how the workload consumes the interface, resolved and written by
// the attachment controller rather than by whoever runs the workload.
// +kubebuilder:default=Netns
// +optional
Mode VPCAttachmentInterfaceMode `json:"mode,omitempty"`

// A list of IPv4 or IPv6 addresses associated with the interface. Empty when
// the guest manages its own addressing.
// +kubebuilder:validation:MaxItems=16
// +required
Addresses []IPAddress `json:"addresses"`
// +optional
Addresses []IPAddress `json:"addresses,omitempty"`
}

// VPCAttachmentStatus defines the observed state of VPCAttachment.
//
// Every field but Conditions is optional: an identifier is recorded before a pod
// attaches, and a guest managing its own addressing never reports a subnet.
type VPCAttachmentStatus struct {
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
Expand All @@ -76,44 +121,57 @@ type VPCAttachmentStatus struct {
// Base62-encoded VPC identifier.
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=16
VPC string `json:"vpc"`
// +optional
VPC string `json:"vpc,omitempty"`

// Base62-encoded VPCAttachment identifier.
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=16
VPCAttachment string `json:"vpcAttachment"`
// +optional
VPCAttachment string `json:"vpcAttachment,omitempty"`

// Kubernetes node name where the attachment lives.
// +kubebuilder:validation:MinLength=1
Node string `json:"node"`
// +optional
Node string `json:"node,omitempty"`

// Full container ID (46 hex characters).
// +kubebuilder:validation:MinLength=46
// +kubebuilder:validation:MaxLength=46
ContainerID string `json:"containerID"`
// +optional
ContainerID string `json:"containerID,omitempty"`

// Pod name.
// +kubebuilder:validation:MinLength=1
PodName string `json:"podName"`
// +optional
PodName string `json:"podName,omitempty"`

// Host-side veth device name (e.g., "G000000010010H").
// Host-side veth or tap device name (e.g., "G000000010013H").
// +kubebuilder:validation:MinLength=1
HostInterface string `json:"hostInterface"`
// +optional
HostInterface string `json:"hostInterface,omitempty"`

// VRF device name (e.g., "G000000010010V").
// VRF device name, which is per-VPC (e.g., "G000000010V").
// +kubebuilder:validation:MinLength=1
VRFInterface string `json:"vrfInterface"`
// +optional
VRFInterface string `json:"vrfInterface,omitempty"`

// Guest-side veth device name (e.g., "G000000010010G").
// Guest-side veth device name (e.g., "G000000010013G").
// +kubebuilder:validation:MinLength=1
// +optional
GuestInterface string `json:"guestInterface,omitempty"`

// Allocated /80 subnet in CIDR notation (e.g., "fd00:10:ff01:0:1::/80").
// Allocated subnet in CIDR notation (e.g., "fd00:10:ff01:0:1::/80").
// +kubebuilder:validation:MinLength=1
// +optional
//
// +kubebuilder:validation:XValidation:rule="isCIDR(self)",message="podSubnet must be a valid IPv6 CIDR"
PodSubnet string `json:"podSubnet"`
PodSubnet string `json:"podSubnet,omitempty"`

// NetworkAttachmentDefinition rendered for this attachment.
// +kubebuilder:validation:MinLength=1
// +optional
NetworkAttachmentDefinition string `json:"networkAttachmentDefinition,omitempty"`
}

// +kubebuilder:object:root=true
Expand Down
20 changes: 20 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading