Skip to content

Commit 9fcda94

Browse files
dilyevskyclaude
andcommitted
[tunnel] add agent connection labels for cluster liveness
Add a Labels field to AgentStatus and TunnelConfig so tunnel agent connections carry metadata (e.g. "apoxy.dev/cluster") that the GC controller can use to verify tunnel liveness before cleaning up mirrored resources. - Client serializes labels as label.* query params on connect - Server extracts label.* params and stores them in AgentStatus - Mirror controller sets mirror.apoxy.dev/cluster label on leases - Runtime tunnel component threads labels from TunnelConfig Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0b319d9 commit 9fcda94

File tree

9 files changed

+58
-2
lines changed

9 files changed

+58
-2
lines changed

api/config/v1alpha1/config_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ type TunnelConfig struct {
137137
// Defaults to "127.0.0.1:8053". Set to empty string to disable.
138138
// +optional
139139
DNSAddr string `json:"dnsAddr,omitempty"`
140+
// Labels are metadata key-value pairs attached to agent connections.
141+
// Convention: set "apoxy.dev/cluster" to identify cluster membership.
142+
// +optional
143+
Labels map[string]string `json:"labels,omitempty"`
140144
}
141145

142146
// TunnelMode is the mode of the tunnel.

api/config/v1alpha1/zz_generated.deepcopy.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/core/v1alpha/tunnel_types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ type AgentStatus struct {
6767
// Extra addresses of the agent (for additional v4/v6 overlays, if configured).
6868
// +optional
6969
AgentAddresses []string `json:"agentAddresses,omitempty"`
70+
71+
// Labels are metadata key-value pairs associated with this agent connection.
72+
// Used to identify cluster membership, failure domains, etc.
73+
// +optional
74+
Labels map[string]string `json:"labels,omitempty"`
7075
}
7176

7277
type TunnelNodeCredentials struct {

api/core/v1alpha/zz_generated.deepcopy.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/cmd/run/tunnel.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,9 @@ func (r *runtimeTunnelReconciler) reconcile(ctx context.Context, req ctrl.Reques
228228
return ctrl.Result{RequeueAfter: time.Second}, nil
229229
}
230230
cOpts = append(cOpts, tunnel.WithAuthToken(tunnelNode.Status.Credentials.Token))
231+
if len(r.tunCfg.Labels) > 0 {
232+
cOpts = append(cOpts, tunnel.WithLabels(r.tunCfg.Labels))
233+
}
231234

232235
var srvAddr string
233236
if !r.cfg.IsLocalMode {

pkg/kube-controller/controllers/mirror.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,9 @@ func (r *MirrorReconciler) renewLease(ctx context.Context, namespace, leaseName
656656
ObjectMeta: metav1.ObjectMeta{
657657
Name: leaseName,
658658
Namespace: namespace,
659+
Labels: map[string]string{
660+
labelCluster: r.clusterName,
661+
},
659662
},
660663
Spec: k8scoordinationv1.LeaseSpec{
661664
HolderIdentity: ptr.To(r.clusterName),
@@ -674,6 +677,10 @@ func (r *MirrorReconciler) renewLease(ctx context.Context, namespace, leaseName
674677
return fmt.Errorf("getting lease: %w", err)
675678
}
676679

680+
if existing.Labels == nil {
681+
existing.Labels = make(map[string]string)
682+
}
683+
existing.Labels[labelCluster] = r.clusterName
677684
existing.Spec.RenewTime = &now
678685
existing.Spec.HolderIdentity = ptr.To(r.clusterName)
679686
existing.Spec.LeaseDurationSeconds = ptr.To(durationSecs)

pkg/kube-controller/controllers/mirror_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,8 @@ func TestRenewLease_CreatesNew(t *testing.T) {
517517
assert.Equal(t, int32(30), *lease.Spec.LeaseDurationSeconds)
518518
assert.NotNil(t, lease.Spec.AcquireTime)
519519
assert.NotNil(t, lease.Spec.RenewTime)
520+
// Cluster label.
521+
assert.Equal(t, "test-cluster", lease.Labels[labelCluster])
520522
}
521523

522524
func TestRenewLease_RenewsExisting(t *testing.T) {
@@ -551,6 +553,8 @@ func TestRenewLease_RenewsExisting(t *testing.T) {
551553
// RenewTime should be updated to approximately now.
552554
assert.True(t, lease.Spec.RenewTime.Time.After(before.Add(-time.Second)))
553555
assert.Equal(t, "test-cluster", *lease.Spec.HolderIdentity)
556+
// Cluster label set on update.
557+
assert.Equal(t, "test-cluster", lease.Labels[labelCluster])
554558
}
555559

556560
// --- reconcileTCPRoute (v1alpha2 round-trip) ---

pkg/tunnel/client.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ type tunnelClientOptions struct {
6969
preserveDefaultGwDsts []netip.Prefix
7070
// Packet observer for TUI
7171
packetObserver tunnelconn.PacketObserver
72+
// Labels to send on tunnel connections.
73+
labels map[string]string
7274
}
7375

7476
func defaultClientOptions() *tunnelClientOptions {
@@ -152,6 +154,13 @@ func WithPacketObserver(obs tunnelconn.PacketObserver) TunnelClientOption {
152154
}
153155
}
154156

157+
// WithLabels sets metadata labels to send on tunnel connections.
158+
func WithLabels(labels map[string]string) TunnelClientOption {
159+
return func(o *tunnelClientOptions) {
160+
o.labels = labels
161+
}
162+
}
163+
155164
// BuildClientRouter builds a router for the client tunnel side using provided
156165
// options and sane defaults.
157166
func BuildClientRouter(opts ...TunnelClientOption) (router.Router, error) {
@@ -273,9 +282,11 @@ func (d *TunnelDialer) Dial(
273282
q := addrUrl.Query()
274283
if options.authToken != "" {
275284
q.Add("token", options.authToken)
276-
addrUrl.RawQuery = q.Encode()
277285
}
278-
addrUrl.RawQuery = addrUrl.Query().Encode()
286+
for k, v := range options.labels {
287+
q.Add("label."+k, v)
288+
}
289+
addrUrl.RawQuery = q.Encode()
279290

280291
tmpl, err := uritemplate.New(addrUrl.String())
281292
if err != nil {

pkg/tunnel/server.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,13 @@ func (t *TunnelServer) makeSingleConnectHandler(ctx context.Context, qConn quic.
508508
return
509509
}
510510

511+
labels := make(map[string]string)
512+
for key, values := range r.URL.Query() {
513+
if strings.HasPrefix(key, "label.") && len(values) > 0 {
514+
labels[strings.TrimPrefix(key, "label.")] = values[0]
515+
}
516+
}
517+
511518
connID := uuid.NewString()
512519
// Sends connection ID information to the client so that it can
513520
// track its connection status. This must be done before initializing the proxy.
@@ -538,6 +545,7 @@ func (t *TunnelServer) makeSingleConnectHandler(ctx context.Context, qConn quic.
538545
agent := &corev1alpha.AgentStatus{
539546
Name: connID,
540547
ConnectedAt: ptr.To(metav1.Now()),
548+
Labels: labels,
541549
}
542550
// TODO(dilyevsky): Support multiple external addresses in the Status.
543551
if len(t.options.extAddrs) > 0 && t.options.extAddrs[0].IsValid() {

0 commit comments

Comments
 (0)