This commit contains the Kubernetes implementation of peer relays via the new `PeerRelay` CRD. It's a mega branch consisting of the commits of other PRs gone into this work: 1. https://github.com/tailscale/tailscale/pull/20211 2. https://github.com/tailscale/tailscale/pull/20329 3. https://github.com/tailscale/tailscale/pull/20423 4. https://github.com/tailscale/tailscale/pull/20503 An instance of the `PeerRelay` CRD deploys a `StatefulSet` of containerboot instances configured to advertise themselves as peer relays using the IP addresses configured via `LoadBalancer` services on each cloud provider (with some AWS specifics as it's less automatic than its competing cloud providers). Per replica, a `LoadBalancer` type `Service` resource is provisioned and its IP address is used to configure the respective relay. This has been tested with success in AWS, GCP & Azure and provides additional modification to `Service` resources via the CRD for any other kinds of deployment environments. It also contains some work that may appear to be duplication of what already exists within `cmd/k8s-operator` so we can start building an appropriate migration path for `Connector`, `ProxyGroup` etc into respective `k8s-operator/reconciler/*` packages. Closes https://github.com/tailscale/corp/issues/34524
102 lines
2.9 KiB
Go
102 lines
2.9 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
//go:build !plan9
|
|
|
|
package peerrelay
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/netip"
|
|
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
|
|
"tailscale.com/ipn"
|
|
tsapi "tailscale.com/k8s-operator/apis/v1alpha1"
|
|
"tailscale.com/k8s-operator/reconciler"
|
|
"tailscale.com/k8s-operator/reconciler/tailscaled"
|
|
"tailscale.com/kube/kubetypes"
|
|
)
|
|
|
|
func configSecretName(prName string, idx int32) string {
|
|
return replicaName(prName, idx) + "-config"
|
|
}
|
|
|
|
func peerRelayHostname(pr *tsapi.PeerRelay, idx int32) string {
|
|
prefix := string(pr.Spec.HostnamePrefix)
|
|
if prefix == "" {
|
|
prefix = pr.Name
|
|
}
|
|
return fmt.Sprintf("%s-%d", prefix, idx)
|
|
}
|
|
|
|
func peerRelayTailscaledConfig(pr *tsapi.PeerRelay, idx int32, endpoint *tsapi.PeerRelayEndpoint, authKey *string) ipn.ConfigVAlpha {
|
|
conf := ipn.ConfigVAlpha{
|
|
Version: "alpha0",
|
|
AcceptDNS: "false",
|
|
AcceptRoutes: "false",
|
|
Locked: "false",
|
|
Hostname: new(peerRelayHostname(pr, idx)),
|
|
RelayServerPort: new(uint16(servicePort)),
|
|
AuthKey: authKey,
|
|
}
|
|
|
|
if endpoint != nil {
|
|
if addr, err := netip.ParseAddr(endpoint.Address); err == nil {
|
|
conf.RelayServerStaticEndpoints = []netip.AddrPort{
|
|
netip.AddrPortFrom(addr, uint16(endpoint.Port)),
|
|
}
|
|
}
|
|
}
|
|
|
|
return conf
|
|
}
|
|
|
|
func (r *Reconciler) peerRelayConfigSecret(pr *tsapi.PeerRelay, idx int32, endpoint *tsapi.PeerRelayEndpoint, authKey *string) (*corev1.Secret, error) {
|
|
labels := peerRelayServiceLabels(pr.Name, idx)
|
|
return tailscaled.NewConfigSecret(tailscaled.ConfigSecretOptions{
|
|
Name: configSecretName(pr.Name, idx),
|
|
Namespace: r.tailscaleNamespace,
|
|
Labels: labels,
|
|
Config: peerRelayTailscaledConfig(pr, idx, endpoint, authKey),
|
|
})
|
|
}
|
|
|
|
func (r *Reconciler) peerRelayStatefulSet(pr *tsapi.PeerRelay, replicas int32, pc *tsapi.ProxyClass) *appsv1.StatefulSet {
|
|
labels := peerRelayLabels(pr.Name)
|
|
ss := tailscaled.NewStatefulSet(tailscaled.StatefulSetOptions{
|
|
Name: resourceName(pr.Name),
|
|
Namespace: r.tailscaleNamespace,
|
|
Labels: labels,
|
|
Image: r.proxyImage,
|
|
Replicas: replicas,
|
|
ServiceAccountName: "proxies",
|
|
ConfigSecretNameFunc: func(idx int32) string {
|
|
return configSecretName(pr.Name, idx)
|
|
},
|
|
})
|
|
|
|
return tailscaled.ApplyProxyClass(ss, pc, managedLabelKeys, nil)
|
|
}
|
|
|
|
var managedLabelKeys = []string{
|
|
kubetypes.LabelManaged,
|
|
reconciler.LabelParentType,
|
|
reconciler.LabelParentName,
|
|
}
|
|
|
|
func (r *Reconciler) getProxyClass(ctx context.Context, pr *tsapi.PeerRelay) (*tsapi.ProxyClass, error) {
|
|
if pr.Spec.ProxyClass == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
var pc tsapi.ProxyClass
|
|
if err := r.Get(ctx, types.NamespacedName{Name: pr.Spec.ProxyClass}, &pc); err != nil {
|
|
return nil, fmt.Errorf("failed to get ProxyClass %q: %w", pr.Spec.ProxyClass, err)
|
|
}
|
|
return &pc, nil
|
|
}
|