This commit adds a `replicas` field to the `Connector` custom resource that
allows users to specify the number of desired replicas deployed for their
connectors.
This allows users to deploy exit nodes, subnet routers and app connectors
in a highly available fashion.
Fixes#14020
Signed-off-by: David Bond <davidsbond93@gmail.com>
// StatefulSet exists, so we have already created the secret.
secretNames[i]=secret.Name
// If the secret is missing, they should delete the StatefulSet.
logger.Errorf("Tailscale proxy secret doesn't exist, but the corresponding StatefulSet %s/%s already does. Something is wrong, please delete the StatefulSet.",sts.GetNamespace(),sts.GetName())
ifsts.ServeConfig!=nil&&sts.ForwardClusterTrafficViaL7IngressProxy!=true{// If forwarding cluster traffic via is required we need non-userspace + NET_ADMIN + forwarding
ifsts.ServeConfig!=nil&&sts.ForwardClusterTrafficViaL7IngressProxy!=true{// If forwarding cluster traffic via is required we need non-userspace + NET_ADMIN + forwarding
| `status`_[ConnectorStatus](#connectorstatus)_ | ConnectorStatus describes the status of the Connector. This is set<br/>and managed by the Tailscale operator. | | |
| `status`_[ConnectorStatus](#connectorstatus)_ | ConnectorStatus describes the status of the Connector. This is set<br/>and managed by the Tailscale operator. | | |
#### ConnectorDevice
_Appears in:_
- [ConnectorStatus](#connectorstatus)
| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `hostname`_string_ | Hostname is the fully qualified domain name of the Connector replica.<br/>If MagicDNS is enabled in your tailnet, it is the MagicDNS name of the<br/>node. | | |
| `tailnetIPs`_string array_ | TailnetIPs is the set of tailnet IP addresses (both IPv4 and IPv6)<br/>assigned to the Connector replica. | | |
#### ConnectorList
#### ConnectorList
@ -115,11 +132,13 @@ _Appears in:_
| Field | Description | Default | Validation |
| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| --- | --- | --- | --- |
| `tags`_[Tags](#tags)_ | Tags that the Tailscale node will be tagged with.<br/>Defaults to [tag:k8s].<br/>To autoapprove the subnet routes or exit node defined by a Connector,<br/>you can configure Tailscale ACLs to give these tags the necessary<br/>permissions.<br/>See https://tailscale.com/kb/1337/acl-syntax#autoapprovers.<br/>If you specify custom tags here, you must also make the operator an owner of these tags.<br/>See https://tailscale.com/kb/1236/kubernetes-operator/#setting-up-the-kubernetes-operator.<br/>Tags cannot be changed once a Connector node has been created.<br/>Tag values must be in form ^tag:[a-zA-Z][a-zA-Z0-9-]*$. | | Pattern: `^tag:[a-zA-Z][a-zA-Z0-9-]*$`<br/>Type: string <br/> |
| `tags`_[Tags](#tags)_ | Tags that the Tailscale node will be tagged with.<br/>Defaults to [tag:k8s].<br/>To autoapprove the subnet routes or exit node defined by a Connector,<br/>you can configure Tailscale ACLs to give these tags the necessary<br/>permissions.<br/>See https://tailscale.com/kb/1337/acl-syntax#autoapprovers.<br/>If you specify custom tags here, you must also make the operator an owner of these tags.<br/>See https://tailscale.com/kb/1236/kubernetes-operator/#setting-up-the-kubernetes-operator.<br/>Tags cannot be changed once a Connector node has been created.<br/>Tag values must be in form ^tag:[a-zA-Z][a-zA-Z0-9-]*$. | | Pattern: `^tag:[a-zA-Z][a-zA-Z0-9-]*$`<br/>Type: string <br/> |
| `hostname`_[Hostname](#hostname)_ | Hostname is the tailnet hostname that should be assigned to the<br/>Connector node. If unset, hostname defaults to <connector<br/>name>-connector. Hostname can contain lower case letters, numbers and<br/>dashes, it must not start or end with a dash and must be between 2<br/>and 63 characters long. | | Pattern: `^[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$`<br/>Type: string <br/> |
| `hostname`_[Hostname](#hostname)_ | Hostname is the tailnet hostname that should be assigned to the<br/>Connector node. If unset, hostname defaults to <connector<br/>name>-connector. Hostname can contain lower case letters, numbers and<br/>dashes, it must not start or end with a dash and must be between 2<br/>and 63 characters long. This field should only be used when creating a connector<br/>with an unspecified number of replicas, or a single replica. | | Pattern: `^[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$`<br/>Type: string <br/> |
| `hostnamePrefix`_[HostnamePrefix](#hostnameprefix)_ | HostnamePrefix specifies the hostname prefix for each<br/>replica. Each device will have the integer number<br/>from its StatefulSet pod appended to this prefix to form the full hostname.<br/>HostnamePrefix can contain lower case letters, numbers and dashes, it<br/>must not start with a dash and must be between 1 and 62 characters long. | | Pattern: `^[a-z0-9][a-z0-9-]{0,61}$`<br/>Type: string <br/> |
| `proxyClass`_string_ | ProxyClass is the name of the ProxyClass custom resource that<br/>contains configuration options that should be applied to the<br/>resources created for this Connector. If unset, the operator will<br/>create resources with the default configuration. | | |
| `proxyClass`_string_ | ProxyClass is the name of the ProxyClass custom resource that<br/>contains configuration options that should be applied to the<br/>resources created for this Connector. If unset, the operator will<br/>create resources with the default configuration. | | |
| `subnetRouter`_[SubnetRouter](#subnetrouter)_ | SubnetRouter defines subnet routes that the Connector device should<br/>expose to tailnet as a Tailscale subnet router.<br/>https://tailscale.com/kb/1019/subnets/<br/>If this field is unset, the device does not get configured as a Tailscale subnet router.<br/>This field is mutually exclusive with the appConnector field. | | |
| `subnetRouter`_[SubnetRouter](#subnetrouter)_ | SubnetRouter defines subnet routes that the Connector device should<br/>expose to tailnet as a Tailscale subnet router.<br/>https://tailscale.com/kb/1019/subnets/<br/>If this field is unset, the device does not get configured as a Tailscale subnet router.<br/>This field is mutually exclusive with the appConnector field. | | |
| `appConnector`_[AppConnector](#appconnector)_ | AppConnector defines whether the Connector device should act as a Tailscale app connector. A Connector that is<br/>configured as an app connector cannot be a subnet router or an exit node. If this field is unset, the<br/>Connector does not act as an app connector.<br/>Note that you will need to manually configure the permissions and the domains for the app connector via the<br/>Admin panel.<br/>Note also that the main tested and supported use case of this config option is to deploy an app connector on<br/>Kubernetes to access SaaS applications available on the public internet. Using the app connector to expose<br/>cluster workloads or other internal workloads to tailnet might work, but this is not a use case that we have<br/>tested or optimised for.<br/>If you are using the app connector to access SaaS applications because you need a predictable egress IP that<br/>can be whitelisted, it is also your responsibility to ensure that cluster traffic from the connector flows<br/>via that predictable IP, for example by enforcing that cluster egress traffic is routed via an egress NAT<br/>device with a static IP address.<br/>https://tailscale.com/kb/1281/app-connectors | | |
| `appConnector`_[AppConnector](#appconnector)_ | AppConnector defines whether the Connector device should act as a Tailscale app connector. A Connector that is<br/>configured as an app connector cannot be a subnet router or an exit node. If this field is unset, the<br/>Connector does not act as an app connector.<br/>Note that you will need to manually configure the permissions and the domains for the app connector via the<br/>Admin panel.<br/>Note also that the main tested and supported use case of this config option is to deploy an app connector on<br/>Kubernetes to access SaaS applications available on the public internet. Using the app connector to expose<br/>cluster workloads or other internal workloads to tailnet might work, but this is not a use case that we have<br/>tested or optimised for.<br/>If you are using the app connector to access SaaS applications because you need a predictable egress IP that<br/>can be whitelisted, it is also your responsibility to ensure that cluster traffic from the connector flows<br/>via that predictable IP, for example by enforcing that cluster egress traffic is routed via an egress NAT<br/>device with a static IP address.<br/>https://tailscale.com/kb/1281/app-connectors | | |
| `exitNode`_boolean_ | ExitNode defines whether the Connector device should act as a Tailscale exit node. Defaults to false.<br/>This field is mutually exclusive with the appConnector field.<br/>https://tailscale.com/kb/1103/exit-nodes | | |
| `exitNode`_boolean_ | ExitNode defines whether the Connector device should act as a Tailscale exit node. Defaults to false.<br/>This field is mutually exclusive with the appConnector field.<br/>https://tailscale.com/kb/1103/exit-nodes | | |
| `replicas`_integer_ | Replicas specifies how many devices to create. Set this to enable<br/>high availability for app connectors, subnet routers, or exit nodes.<br/>https://tailscale.com/kb/1115/high-availability. Defaults to 1. | | Minimum: 0 <br/> |
#### ConnectorStatus
#### ConnectorStatus
@ -140,7 +159,8 @@ _Appears in:_
| `isExitNode`_boolean_ | IsExitNode is set to true if the Connector acts as an exit node. | | |
| `isExitNode`_boolean_ | IsExitNode is set to true if the Connector acts as an exit node. | | |
| `isAppConnector`_boolean_ | IsAppConnector is set to true if the Connector acts as an app connector. | | |
| `isAppConnector`_boolean_ | IsAppConnector is set to true if the Connector acts as an app connector. | | |
| `tailnetIPs`_string array_ | TailnetIPs is the set of tailnet IP addresses (both IPv4 and IPv6)<br/>assigned to the Connector node. | | |
| `tailnetIPs`_string array_ | TailnetIPs is the set of tailnet IP addresses (both IPv4 and IPv6)<br/>assigned to the Connector node. | | |
| `hostname`_string_ | Hostname is the fully qualified domain name of the Connector node.<br/>If MagicDNS is enabled in your tailnet, it is the MagicDNS name of the<br/>node. | | |
| `hostname`_string_ | Hostname is the fully qualified domain name of the Connector node.<br/>If MagicDNS is enabled in your tailnet, it is the MagicDNS name of the<br/>node. When using multiple replicas, this field will be populated with the<br/>first replica's hostname. Use the Hostnames field for the full list<br/>of hostnames. | | |
| `devices`_[ConnectorDevice](#connectordevice) array_ | Devices contains information on each device managed by the Connector resource. | | |
// ConnectorSpec describes a Tailscale node to be deployed in the cluster.
// ConnectorSpec describes a Tailscale node to be deployed in the cluster.
// +kubebuilder:validation:XValidation:rule="has(self.subnetRouter) || (has(self.exitNode) && self.exitNode == true) || has(self.appConnector)",message="A Connector needs to have at least one of exit node, subnet router or app connector configured."
// +kubebuilder:validation:XValidation:rule="has(self.subnetRouter) || (has(self.exitNode) && self.exitNode == true) || has(self.appConnector)",message="A Connector needs to have at least one of exit node, subnet router or app connector configured."
// +kubebuilder:validation:XValidation:rule="!((has(self.subnetRouter) || (has(self.exitNode) && self.exitNode == true)) && has(self.appConnector))",message="The appConnector field is mutually exclusive with exitNode and subnetRouter fields."
// +kubebuilder:validation:XValidation:rule="!((has(self.subnetRouter) || (has(self.exitNode) && self.exitNode == true)) && has(self.appConnector))",message="The appConnector field is mutually exclusive with exitNode and subnetRouter fields."
// +kubebuilder:validation:XValidation:rule="!(has(self.hostname) && has(self.replicas) && self.replicas > 1)",message="The hostname field cannot be specified when replicas is greater than 1."
// +kubebuilder:validation:XValidation:rule="!(has(self.hostname) && has(self.hostnamePrefix))",message="The hostname and hostnamePrefix fields are mutually exclusive."
typeConnectorSpecstruct{
typeConnectorSpecstruct{
// Tags that the Tailscale node will be tagged with.
// Tags that the Tailscale node will be tagged with.
// Defaults to [tag:k8s].
// Defaults to [tag:k8s].
@ -76,9 +78,19 @@ type ConnectorSpec struct {
// Connector node. If unset, hostname defaults to <connector
// Connector node. If unset, hostname defaults to <connector
// name>-connector. Hostname can contain lower case letters, numbers and
// name>-connector. Hostname can contain lower case letters, numbers and
// dashes, it must not start or end with a dash and must be between 2
// dashes, it must not start or end with a dash and must be between 2
// and 63 characters long.
// and 63 characters long. This field should only be used when creating a connector
// with an unspecified number of replicas, or a single replica.
// +optional
// +optional
HostnameHostname`json:"hostname,omitempty"`
HostnameHostname`json:"hostname,omitempty"`
// 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.