From 17ebf9cfdec7a800f81d24e4dfdfaf0dcac62e9b Mon Sep 17 00:00:00 2001 From: Yuto Nishida Date: Sat, 6 Jun 2026 20:09:42 -0700 Subject: [PATCH] [milky-way][orion-system] Fix tailscale operator --- .../stage00/orion-system/main.jsonnet | 5 + milky-way/lib/tailscale-operator.libsonnet | 30 ++++- .../test-tailscale-operator-ingress.libsonnet | 115 ++++++++++++++++++ 3 files changed, 147 insertions(+), 3 deletions(-) create mode 100644 milky-way/lib/test-tailscale-operator-ingress.libsonnet diff --git a/milky-way/environments/stage00/orion-system/main.jsonnet b/milky-way/environments/stage00/orion-system/main.jsonnet index 3f9858f..15f3d0f 100644 --- a/milky-way/environments/stage00/orion-system/main.jsonnet +++ b/milky-way/environments/stage00/orion-system/main.jsonnet @@ -9,6 +9,7 @@ local calibreWebAuto = import 'milky-way/lib/calibre-web-automated.libsonnet'; local ddnsUpdater = import 'milky-way/lib/ddns-updater.libsonnet'; local traefik = import 'milky-way/lib/traefik.libsonnet'; local tailscaleOperator = import 'milky-way/lib/tailscale-operator.libsonnet'; +local testTailscaleIngress = import 'milky-way/lib/test-tailscale-operator-ingress.libsonnet'; local secrets = import 'milky-way/secrets/k8s-secret-values.jsonnet'; { local this = self, @@ -84,8 +85,12 @@ local secrets = import 'milky-way/secrets/k8s-secret-values.jsonnet'; tailscaleOperator: tailscaleOperator.new( client_id = secrets.tailscaleOperatorTrustCredentials.orionSystem.client_id, client_secret = secrets.tailscaleOperatorTrustCredentials.orionSystem.client_secret, + operatorTags = 'tag:k8s-orion-system-operator', + proxyTags = 'tag:k8s-orion-system', ), + testTailscaleIngress: testTailscaleIngress.new(), + cilium: charts.cilium, traefikConfig: traefik.reconfigForCilium(), diff --git a/milky-way/lib/tailscale-operator.libsonnet b/milky-way/lib/tailscale-operator.libsonnet index 3dcffe4..ff0c5c1 100644 --- a/milky-way/lib/tailscale-operator.libsonnet +++ b/milky-way/lib/tailscale-operator.libsonnet @@ -4,7 +4,13 @@ local vendoredOperatorManifest = importstr 'milky-way/lib/tailscale-operator/ope new( client_id, client_secret, + operatorTags, // tag the operator authenticates itself with + proxyTags, // tag applied to the resources the operator manages ):: ( + local envOverrides = { + OPERATOR_INITIAL_TAGS: operatorTags, + PROXY_TAGS: proxyTags, + }; local resources = [ if resource.kind == 'Secret' && resource.metadata.name == 'operator-oauth' then resource { @@ -13,16 +19,34 @@ local vendoredOperatorManifest = importstr 'milky-way/lib/tailscale-operator/ope client_secret: client_secret, }, } + else if resource.kind == 'Deployment' && resource.metadata.name == 'operator' then + resource { + spec+: { template+: { spec+: { containers: std.map( + function(container) container { + env: std.map( + function(e) if std.objectHas(envOverrides, e.name) then e { value: envOverrides[e.name] } else e, + container.env, + ), + }, + super.containers, + ) } } }, + } else resource for resource in std.parseYaml(vendoredOperatorManifest) ]; utils.assertAndReturn( - resources, + utils.assertAndReturn( + resources, + function(rs) std.length(std.filter( + function(r) r.kind == 'Secret' && r.metadata.name == 'operator-oauth', rs + )) == 1, + 'expected exactly one operator-oauth Secret in the vendored manifest', + ), function(rs) std.length(std.filter( - function(r) r.kind == 'Secret' && r.metadata.name == 'operator-oauth', rs + function(r) r.kind == 'Deployment' && r.metadata.name == 'operator', rs )) == 1, - 'expected exactly one operator-oauth Secret in the vendored manifest', + 'expected exactly one operator Deployment in the vendored manifest', ) ), } diff --git a/milky-way/lib/test-tailscale-operator-ingress.libsonnet b/milky-way/lib/test-tailscale-operator-ingress.libsonnet new file mode 100644 index 0000000..f51353f --- /dev/null +++ b/milky-way/lib/test-tailscale-operator-ingress.libsonnet @@ -0,0 +1,115 @@ +local utils = import 'milky-way/lib/utils.libsonnet'; + +// A minimal smoke test for the Tailscale operator's L7 Ingress path +// (`ingressClassName: tailscale`). It deploys a single whoami pod, a ClusterIP +// Service, and a tailscale Ingress so we can confirm the operator provisions a +// proxy device and serves the backend over HTTPS on the tailnet. +// +// This deliberately exercises ONLY the Ingress controller path -- it does not +// use the `tailscale.com/expose` Service annotation (that is the separate L4 +// service-exposure mechanism). +{ + new( + tailscaleHostname='test-ts-ingress', // becomes the tailnet device name; reachable at https://..ts.net + name='test-ts-ingress', + namespace='test-k8s', + // whoami echoes the request + headers, making a successful proxy hop self-evident. + image='traefik/whoami@sha256:200689790a0a0ea48ca45992e0450bc26ccab5307375b41c84dfc4f2475937ab', + ):: { + local this = self, + + deployment: { + apiVersion: 'apps/v1', + kind: 'Deployment', + metadata: { + name: name, + namespace: namespace, + }, + spec: { + replicas: 1, + selector: { + matchLabels: { + app: name, + }, + }, + template: { + metadata: { + labels: {} + this.deployment.spec.selector.matchLabels, + }, + spec: { + tolerations: [ + { + key: 'ephemeral', + operator: 'Exists', + effect: 'NoSchedule', + }, + ], + containers: [{ + name: 'whoami', + image: image, + ports: [{ + name: 'main-http', + containerPort: 80, + }], + }], + }, + }, + }, + }, + + service: { + apiVersion: 'v1', + kind: 'Service', + metadata: { + name: name, + namespace: namespace, + }, + spec: { + selector: {} + this.deployment.spec.template.metadata.labels, + ports: [{ + port: 80, + targetPort: utils.assertEqualAndReturn(this.deployment.spec.template.spec.containers[0].ports[0].name, 'main-http'), + }], + type: 'ClusterIP', + }, + }, + + ingress: { + apiVersion: 'networking.k8s.io/v1', + kind: 'Ingress', + metadata: { + name: name, + namespace: namespace, + annotations: { + 'tailscale.com/funnel': 'false', // tailnet-only, no public funnel + }, + }, + spec: { + ingressClassName: 'tailscale', + tls: [ + { + hosts: [tailscaleHostname], + }, + ], + rules: [ + { + http: { + paths: [{ + path: '/', + pathType: 'Prefix', + backend: { + service: { + name: this.service.metadata.name, + port: { + number: utils.assertEqualAndReturn(this.service.spec.ports[0].port, 80), + }, + }, + }, + }], + }, + }, + ], + }, + }, + }, +} -- 2.51.2