Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9730105
feat: Publish private DNS names for compute instances
scotwells Oct 7, 2026
e957824
fix: Resolve internal DNS publisher lint findings
scotwells Oct 7, 2026
fc8f8b4
Align instance DNS publishing with VPC resolver contexts
scotwells Oct 10, 2026
dac1156
Share DNS protocol constants for CI lint checks
scotwells Oct 10, 2026
ade5d45
Scope runtime observations to the instance slot
scotwells Oct 10, 2026
850ca82
Publish instance names using the allocated DNS namespace
scotwells Oct 11, 2026
55018ab
test: capture multi-location dependency readiness failures
scotwells Oct 12, 2026
05c0d7e
fix: share the DNS readiness condition name
scotwells Oct 12, 2026
0327f40
fix: Preserve DNS through observation outages
scotwells Oct 12, 2026
164eef0
chore: Generate API schemas with pinned tools
scotwells Oct 12, 2026
46784c6
test: Align DNS observation contract checks
scotwells Oct 12, 2026
b4b5659
test: Remove unused DNS observation import
scotwells Oct 12, 2026
071d9b9
refactor: Remove unused DNS expiry clock
scotwells Oct 12, 2026
8e66037
test: Preserve published endpoints during observation failures
scotwells Oct 12, 2026
4a5697c
refactor: Share DNS observation field names
scotwells Oct 13, 2026
8797cb5
fix: Preserve DNS records when interface programming health is unknown
scotwells Oct 13, 2026
8cfe258
refactor: Share DNS record field name
scotwells Oct 13, 2026
01165bd
api: identify instance DNS status by network reference
scotwells Oct 13, 2026
3480084
test: use shared network status fixture name
scotwells Oct 13, 2026
dcb0f1d
fix: Publish DNS registrations using zone names
scotwells Oct 13, 2026
6ebf0ae
fix: Preserve read-only interface DNS status
scotwells Oct 13, 2026
a7df477
test: Preserve interface DNS availability status
scotwells Oct 13, 2026
05e3e27
fix: Patch only interface availability status
scotwells Oct 13, 2026
85900ff
test: Reuse the managed DNS domain fixture
scotwells Oct 13, 2026
b7df134
refactor: Publish instance DNS through project RBAC
scotwells Oct 13, 2026
9586fdc
refactor: Read DNS reference names directly
scotwells Oct 13, 2026
fcf3a4e
fix: Read publication zone references consistently
scotwells Oct 13, 2026
330826e
feat: Publish DNS addresses and health together
scotwells Oct 13, 2026
f6b6506
test: Share the DNS project identity
scotwells Oct 13, 2026
de54496
Discover internal DNS project identities through Milo
scotwells Oct 13, 2026
afc1cf9
Name the project discovery test fixture
scotwells Oct 13, 2026
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
18 changes: 18 additions & 0 deletions api/v1alpha/instance_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,12 @@ type InstanceStatus struct {
// Network interface information
NetworkInterfaces []InstanceNetworkInterfaceStatus `json:"networkInterfaces,omitempty"`

// DNS reports assigned private names and publication readiness per network.
// Resolver access and record eligibility are managed by platform services.
// +kubebuilder:validation:Optional
// +listType=atomic
DNS []InstanceDNSStatus `json:"dns,omitempty"`

// Controller contains status information about the controller managing the instance.
//
// +kubebuilder:validation:Optional
Expand All @@ -826,6 +832,18 @@ type InstanceStatus struct {
Suspended bool `json:"suspended,omitempty"`
}

// InstanceDNSStatus describes private name publication within one network.
type InstanceDNSStatus struct {
// Network identifies the attached network in the owning project.
Network networkingv1alpha.NetworkRef `json:"network"`
// Hostnames contains the fully qualified names allocated for this Instance.
Hostnames []string `json:"hostnames,omitempty"`
// Conditions reports DNS readiness independently of runtime readiness.
// +listType=map
// +listMapKey=type
Conditions []metav1.Condition `json:"conditions,omitempty"`
}

type InstanceControllerStatus struct {
// ObservedTemplateHash is the hash of the instance template applied for this instance.
//
Expand Down
2 changes: 2 additions & 0 deletions api/v1alpha/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ const (
WorkloadDeploymentUIDLabel = LabelNamespace + "/workload-deployment-uid"

InstanceIndexLabel = LabelNamespace + "/instance-index"
// InstanceSourceUIDLabel pins the edge Instance lifetime on its projections.
InstanceSourceUIDLabel = LabelNamespace + "/source-instance-uid"

// WorkloadDeploymentNameLabel carries the name of the WorkloadDeployment
// that owns an Instance. Stamped at creation and kept current on updates.
Expand Down
35 changes: 35 additions & 0 deletions api/v1alpha/zz_generated.deepcopy.go

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

65 changes: 64 additions & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,8 @@ func main() {
flag.StringVar(&featureGatesFlag, "feature-gates", "",
"A set of key=value pairs that describe feature gates for the compute operator. "+
"Example: --feature-gates=NetworkingIntegration=true. "+
"Available features: NetworkingIntegration (default=false).")
"Available features: NetworkingIntegration, RuntimeClasses, InstanceTypes, InstanceConsoleSessions, "+
"InternalDNSPublishing (all default=false).")

opts := zap.Options{
Development: true,
Expand All @@ -181,6 +182,7 @@ func main() {
"RuntimeClasses", features.FeatureGate.Enabled(features.RuntimeClasses),
"InstanceTypes", features.FeatureGate.Enabled(features.InstanceTypes),
"InstanceConsoleSessions", features.FeatureGate.Enabled(features.InstanceConsoleSessions),
"InternalDNSPublishing", features.FeatureGate.Enabled(features.InternalDNSPublishing),
)

ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
Expand Down Expand Up @@ -444,6 +446,39 @@ func main() {
}
}

// DNS publication is its own asynchronous management-plane controller. It is
// not registered at all while its default-off feature gate is disabled.
if features.FeatureGate.Enabled(features.InternalDNSPublishing) {
if serverConfig.Discovery.Mode != multiclusterproviders.ProviderMilo || !enableManagementControllers {
setupLog.Error(nil,
"InternalDNSPublishing requires Milo discovery mode and management controllers")
os.Exit(1)
}
identityConfig, identityErr := serverConfig.Discovery.DiscoveryRestConfig()
if identityErr != nil {
setupLog.Error(identityErr, "unable to load internal DNS project discovery credentials")
os.Exit(1)
}
identityClient, identityErr := client.New(identityConfig, client.Options{Scheme: scheme})
if identityErr != nil {
setupLog.Error(identityErr, "unable to create internal DNS project discovery client")
os.Exit(1)
}
observers, observationErr := internalDNSObservationSources(serverConfig.InternalDNS)
if observationErr != nil {
setupLog.Error(observationErr, "invalid internal DNS network observation configuration")
os.Exit(1)
}
dnsPublisher := &controller.InternalDNSPublisherReconciler{
ResolveProjectIdentity: controller.InternalDNSProjectIdentityResolver(identityClient),
ObservationSources: observers,
}
if err = dnsPublisher.SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "InternalDNSPublisher")
os.Exit(1)
}
}

// Instance types live in project control planes, which only the management
// manager engages; cells read instance types but don't maintain them.
if enableManagementControllers && features.FeatureGate.Enabled(features.InstanceTypes) {
Expand Down Expand Up @@ -941,3 +976,31 @@ func setupManagementControllers(mgr mcmanager.Manager, federationClient client.C
func computeWatchProviderClaims(mode multiclusterproviders.Provider) bool {
return mode == multiclusterproviders.ProviderMilo
}

func internalDNSObservationSources(
cfg config.InternalDNSConfig,
) (map[string]controller.InternalDNSObservationSource, error) {
sources := make(map[string]controller.InternalDNSObservationSource, len(cfg.ObservationSources))
for i, source := range cfg.ObservationSources {
if source.Location == "" || source.KubeconfigPath == "" {
return nil, fmt.Errorf("internalDNS.observationSources[%d] requires location and kubeconfigPath", i)
}
if _, exists := sources[source.Location]; exists {
return nil, fmt.Errorf("duplicate internal DNS observation location %q", source.Location)
}
cfg, err := clientcmd.BuildConfigFromFlags("", source.KubeconfigPath)
if err != nil {
return nil, fmt.Errorf("load network observation credentials for %q: %w", source.Location, err)
}
cfg.Timeout = 5 * time.Second
reader, err := client.New(cfg, client.Options{Scheme: scheme})
if err != nil {
return nil, fmt.Errorf("create network observation client for %q: %w", source.Location, err)
}
sources[source.Location] = controller.InternalDNSObservationSource{Reader: reader}
}
if len(sources) == 0 {
return nil, errors.New("internalDNS.observationSources must contain at least one authenticated edge API")
}
return sources, nil
}
33 changes: 33 additions & 0 deletions cmd/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,42 @@ import (
locationsv1alpha1 "go.miloapis.com/locations/api/v1alpha1"
multiclusterproviders "go.miloapis.com/milo/pkg/multicluster-runtime"

"go.datum.net/compute/internal/config"
"go.datum.net/compute/internal/locations"
)

func TestInternalDNSObservationSourcesRequireExplicitCredentials(t *testing.T) {
_, err := internalDNSObservationSources(config.InternalDNSConfig{})
require.Error(t, err)
_, err = internalDNSObservationSources(config.InternalDNSConfig{
ObservationSources: []config.InternalDNSObservationSource{{Location: "dfw"}},
})
require.Error(t, err)
path := filepath.Join(t.TempDir(), "edge.kubeconfig")
require.NoError(t, os.WriteFile(path, []byte(`apiVersion: v1
kind: Config
clusters:
- name: edge
cluster:
server: https://127.0.0.1:6443
contexts:
- name: edge
context:
cluster: edge
current-context: edge
`), 0o600))
source := config.InternalDNSObservationSource{Location: "dfw", KubeconfigPath: path}
sources, err := internalDNSObservationSources(config.InternalDNSConfig{
ObservationSources: []config.InternalDNSObservationSource{source},
})
require.NoError(t, err)
require.NotNil(t, sources["dfw"].Reader)
_, err = internalDNSObservationSources(config.InternalDNSConfig{
ObservationSources: []config.InternalDNSObservationSource{source, source},
})
require.Error(t, err)
}

// TestComputeWatchProviderClaims is the #171 guard: quota enforcement (and thus
// the ResourceClaim watch) is wired only in Milo mode. Single/cluster mode must
// stay false, so the manager never engages a ResourceClaim watch against a cell
Expand Down
97 changes: 97 additions & 0 deletions config/base/crd/bases/compute.datumapis.com_instances.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1260,6 +1260,103 @@ spec:
required:
- observedTemplateHash
type: object
dns:
description: |-
DNS reports assigned private names and publication readiness per network.
Resolver access and record eligibility are managed by platform services.
items:
description: InstanceDNSStatus describes private name publication
within one network.
properties:
conditions:
description: Conditions reports DNS readiness independently
of runtime readiness.
items:
description: Condition contains details for one aspect of
the current state of this API Resource.
properties:
lastTransitionTime:
description: |-
lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: |-
message is a human readable message indicating details about the transition.
This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: |-
observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: |-
reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False,
Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
hostnames:
description: Hostnames contains the fully qualified names allocated
for this Instance.
items:
type: string
type: array
network:
description: Network identifies the attached network in the
owning project.
properties:
name:
description: The network name
type: string
namespace:
description: |-
The network namespace.

Defaults to the namespace for the type the reference is embedded in.
type: string
required:
- name
type: object
required:
- network
type: object
type: array
x-kubernetes-list-type: atomic
networkInterfaces:
description: Network interface information
items:
Expand Down
5 changes: 5 additions & 0 deletions config/components/internal-dns-rbac/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component
resources:
- role.yaml
- role_binding.yaml
17 changes: 17 additions & 0 deletions config/components/internal-dns-rbac/role.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: compute-internal-dns-publisher
rules:
- apiGroups: ["dns.networking.miloapis.com"]
resources: ["dnsresolvercontexts"]
verbs: ["get", "list"]
- apiGroups: ["dns.networking.miloapis.com"]
resources: ["dnsregistrations", "dnsrecordcontributions"]
verbs: ["get", "list", "create", "update", "patch", "delete"]
- apiGroups: ["dns.networking.miloapis.com"]
resources: ["dnsrecordcontributions/status"]
verbs: ["get", "update", "patch"]
- apiGroups: ["compute.datumapis.com"]
resources: ["instances/status"]
verbs: ["get", "patch"]
12 changes: 12 additions & 0 deletions config/components/internal-dns-rbac/role_binding.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: compute-internal-dns-publisher
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: compute-internal-dns-publisher
subjects:
- kind: ServiceAccount
name: compute-manager
namespace: compute-system
Loading
Loading