cmd/k8s-operator: include peer relay CRD in generation (#20593)

This commit modifies the generation command for the kubernetes operator
to include the CRD for peer relays in the helm chart and static
manifests

Updates: #fixup

Signed-off-by: David Bond <davidsbond93@gmail.com>
This commit is contained in:
David Bond
2026-07-24 14:01:41 +01:00
committed by GitHub
parent f6fa294635
commit 9535e3b99b
3 changed files with 270 additions and 0 deletions
@@ -10,3 +10,4 @@
/recorder.yaml /recorder.yaml
/tailnet.yaml /tailnet.yaml
/proxygrouppolicy.yaml /proxygrouppolicy.yaml
/peerrelay.yaml
@@ -1463,6 +1463,271 @@ spec:
--- ---
apiVersion: apiextensions.k8s.io/v1 apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.17.0
name: peerrelays.tailscale.com
spec:
group: tailscale.com
names:
kind: PeerRelay
listKind: PeerRelayList
plural: peerrelays
shortNames:
- pr
singular: peerrelay
scope: Cluster
versions:
- additionalPrinterColumns:
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
- description: Status of the deployed PeerRelay resources.
jsonPath: .status.conditions[?(@.type == "PeerRelayReady")].reason
name: Status
type: string
- description: Public addresses the peer relay replicas are reachable on.
jsonPath: .status.endpoints[*].address
name: Endpoints
type: string
name: v1alpha1
schema:
openAPIV3Schema:
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: |-
Spec describes the desired state of the PeerRelay.
More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
properties:
aws:
description: |-
AWS contains configuration for pinning each replica to a specific AWS Elastic IP and subnet. Only meaningful
when running on EKS with the AWS Load Balancer Controller. When set, the per-replica values override any
aws-load-balancer-eip-allocations or aws-load-balancer-subnets values supplied via spec.service.annotations.
properties:
elasticIPs:
description: |-
ElasticIPs pins each replica to a specific AWS EIP allocation and subnet. Only meaningful when Network Load
Balancers are provisioned by the AWS Load Balancer Controller. ElasticIPs supplies one allocation-subnet pair
per replica: replica N uses ElasticIPs[N]. The list must be at least as long as spec.replicas so every replica
has a distinct EIP; extra entries are permitted so that scale-up doesn't immediately trip validation.
When set, the reconciler stamps
service.beta.kubernetes.io/aws-load-balancer-eip-allocations and
service.beta.kubernetes.io/aws-load-balancer-subnets on each per-replica Service, overriding any values in
spec.service.annotations.
items:
description: PeerRelayAWSElasticIP pairs an EIP allocation with the subnet in the same AZ.
properties:
allocationID:
description: |-
AllocationID is the AWS EIP allocation ID (e.g. eipalloc-0123abcd) whose public IP this replica is reachable
on. Stamped as service.beta.kubernetes.io/aws-load-balancer-eip-allocations on the replica's Service.
pattern: ^eipalloc-[0-9a-f]+$
type: string
subnetID:
description: |-
SubnetID is the AWS subnet in the same availability zone as AllocationID (e.g. subnet-0123abcd). Stamped as
service.beta.kubernetes.io/aws-load-balancer-subnets on the replica's Service so the NLB is provisioned in
the same AZ as the EIP.
pattern: ^subnet-[0-9a-f]+$
type: string
required:
- allocationID
- subnetID
type: object
minItems: 1
type: array
x-kubernetes-list-type: atomic
required:
- elasticIPs
type: object
hostnamePrefix:
description: |-
HostnamePrefix specifies the hostname prefix for each
replica. Each device will have the integer number
from its StatefulSet pod appended to this prefix to form the full hostname.
HostnamePrefix can contain lower case letters, numbers and dashes, it
must not start with a dash and must be between 1 and 62 characters long.
pattern: ^[a-z0-9][a-z0-9-]{0,61}$
type: string
proxyClass:
description: |-
ProxyClass is the name of the ProxyClass custom resource that
contains configuration options that should be applied to the
resources created for this PeerRelay. If unset, the operator will
create resources with the default configuration.
type: string
replicas:
default: 1
description: |-
Replicas specifies how many devices to create. Set this to enable
high availability for peer relays.
https://tailscale.com/kb/1115/high-availability. Defaults to 1.
format: int32
minimum: 0
type: integer
service:
description: Service contains configuration values to modify the LoadBalancer service used to expose the peer relay.
properties:
annotations:
additionalProperties:
type: string
description: |-
Annotations to apply to the LoadBalancer service. Any annotations that conflict with those used by known
cloud providers to ensure IP addresses rather than DNS names are ignored.
type: object
type: object
tags:
description: |-
Tags that the Tailscale node will be tagged with.
Defaults to [tag:k8s].
To autoapprove the device defined by a PeerRelay,
you can configure Tailscale ACLs to give these tags the necessary
permissions.
See https://tailscale.com/kb/1337/acl-syntax#autoapprovers.
If you specify custom tags here, you must also make the operator an owner of these tags.
See https://tailscale.com/kb/1236/kubernetes-operator/#setting-up-the-kubernetes-operator.
Tags cannot be changed once a PeerRelay node has been created.
Tag values must be in form ^tag:[a-zA-Z][a-zA-Z0-9-]*$.
items:
pattern: ^tag:[a-zA-Z][a-zA-Z0-9-]*$
type: string
type: array
tailnet:
description: |-
Tailnet specifies the tailnet this PeerRelay should join. If blank, the default tailnet is used. When set, this
name must match that of a valid Tailnet resource. This field is immutable and cannot be changed once set.
type: string
x-kubernetes-validations:
- message: PeerRelay tailnet is immutable
rule: self == oldSelf
type: object
x-kubernetes-validations:
- message: spec.aws.elasticIPs must contain at least one entry per replica
rule: '!has(self.aws) || !has(self.aws.elasticIPs) || self.aws.elasticIPs.size() >= self.replicas'
status:
description: |-
Status describes the status of the PeerRelay. This is set
and managed by the Tailscale operator.
properties:
conditions:
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
endpoints:
description: |-
Endpoints lists the public address:port pairs each peer relay replica is reachable on. There is one entry
per replica whose LoadBalancer Service has been assigned a public address; entries appear as the underlying
cloud provisions each Service.
items:
properties:
address:
description: |-
Address is the public IP or hostname the cloud has allocated for this replica's LoadBalancer Service.
Peers reach this relay by connecting to Address:Port over UDP.
type: string
port:
description: Port is the UDP port the peer relay listens on.
format: int32
type: integer
replica:
description: Replica is the zero-based index of the peer relay replica this endpoint targets.
format: int32
type: integer
required:
- address
- port
- replica
type: object
type: array
x-kubernetes-list-map-keys:
- replica
x-kubernetes-list-type: map
type: object
required:
- metadata
- spec
type: object
served: true
storage: true
subresources:
status: {}
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata: metadata:
annotations: annotations:
controller-gen.kubebuilder.io/version: v0.17.0 controller-gen.kubebuilder.io/version: v0.17.0
+4
View File
@@ -28,6 +28,7 @@ const (
proxyGroupCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_proxygroups.yaml" proxyGroupCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_proxygroups.yaml"
tailnetCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_tailnets.yaml" tailnetCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_tailnets.yaml"
proxyGroupPolicyCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_proxygrouppolicies.yaml" proxyGroupPolicyCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_proxygrouppolicies.yaml"
peerRelayCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_peerrelays.yaml"
helmTemplatesPath = operatorDeploymentFilesPath + "/chart/templates" helmTemplatesPath = operatorDeploymentFilesPath + "/chart/templates"
connectorCRDHelmTemplatePath = helmTemplatesPath + "/connector.yaml" connectorCRDHelmTemplatePath = helmTemplatesPath + "/connector.yaml"
proxyClassCRDHelmTemplatePath = helmTemplatesPath + "/proxyclass.yaml" proxyClassCRDHelmTemplatePath = helmTemplatesPath + "/proxyclass.yaml"
@@ -36,6 +37,7 @@ const (
proxyGroupCRDHelmTemplatePath = helmTemplatesPath + "/proxygroup.yaml" proxyGroupCRDHelmTemplatePath = helmTemplatesPath + "/proxygroup.yaml"
tailnetCRDHelmTemplatePath = helmTemplatesPath + "/tailnet.yaml" tailnetCRDHelmTemplatePath = helmTemplatesPath + "/tailnet.yaml"
proxyGroupPolicyCRDHelmTemplatePath = helmTemplatesPath + "/proxygrouppolicy.yaml" proxyGroupPolicyCRDHelmTemplatePath = helmTemplatesPath + "/proxygrouppolicy.yaml"
peerRelayCRDHelmTemplatePath = helmTemplatesPath + "/peerrelay.yaml"
helmConditionalStart = "{{ if .Values.installCRDs -}}\n" helmConditionalStart = "{{ if .Values.installCRDs -}}\n"
helmConditionalEnd = "{{- end -}}" helmConditionalEnd = "{{- end -}}"
@@ -160,6 +162,7 @@ func generate(baseDir string) error {
{proxyGroupCRDPath, proxyGroupCRDHelmTemplatePath}, {proxyGroupCRDPath, proxyGroupCRDHelmTemplatePath},
{tailnetCRDPath, tailnetCRDHelmTemplatePath}, {tailnetCRDPath, tailnetCRDHelmTemplatePath},
{proxyGroupPolicyCRDPath, proxyGroupPolicyCRDHelmTemplatePath}, {proxyGroupPolicyCRDPath, proxyGroupPolicyCRDHelmTemplatePath},
{peerRelayCRDPath, peerRelayCRDHelmTemplatePath},
} { } {
if err := addCRDToHelm(crd.crdPath, crd.templatePath); err != nil { if err := addCRDToHelm(crd.crdPath, crd.templatePath); err != nil {
return fmt.Errorf("error adding %s CRD to Helm templates: %w", crd.crdPath, err) return fmt.Errorf("error adding %s CRD to Helm templates: %w", crd.crdPath, err)
@@ -178,6 +181,7 @@ func cleanup(baseDir string) error {
proxyGroupCRDHelmTemplatePath, proxyGroupCRDHelmTemplatePath,
tailnetCRDHelmTemplatePath, tailnetCRDHelmTemplatePath,
proxyGroupPolicyCRDHelmTemplatePath, proxyGroupPolicyCRDHelmTemplatePath,
peerRelayCRDHelmTemplatePath,
} { } {
if err := os.Remove(filepath.Join(baseDir, path)); err != nil && !os.IsNotExist(err) { if err := os.Remove(filepath.Join(baseDir, path)); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("error cleaning up %s: %w", path, err) return fmt.Errorf("error cleaning up %s: %w", path, err)