mirror of
https://github.com/tailscale/tailscale.git
synced 2024-12-04 23:45:34 +00:00
64776a0277
Implements the controller for the new ProxyGroup CRD, designed for running proxies in a high availability configuration. Each proxy gets its own config and state Secret, and its own tailscale node ID. We are currently mounting all of the config secrets into the container, but will stop mounting them and instead read them directly from the kube API once #13578 is implemented. Updates #13406 Signed-off-by: Tom Proctor <tomhjp@users.noreply.github.com>
243 lines
6.2 KiB
Go
243 lines
6.2 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
//go:build !plan9
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
rbacv1 "k8s.io/api/rbac/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
tsapi "tailscale.com/k8s-operator/apis/v1alpha1"
|
|
"tailscale.com/types/ptr"
|
|
)
|
|
|
|
const labelSecretType = "tailscale.com/secret-type"
|
|
|
|
// Returns the base StatefulSet definition for a ProxyGroup. A ProxyClass may be
|
|
// applied over the top after.
|
|
func pgStatefulSet(pg *tsapi.ProxyGroup, namespace, cfgHash string) *appsv1.StatefulSet {
|
|
return &appsv1.StatefulSet{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: pg.Name,
|
|
Namespace: namespace,
|
|
Labels: labels("proxygroup", pg.Name, nil),
|
|
OwnerReferences: pgOwnerReference(pg),
|
|
},
|
|
Spec: appsv1.StatefulSetSpec{
|
|
Replicas: ptr.To(pgReplicas(pg)),
|
|
Selector: &metav1.LabelSelector{
|
|
MatchLabels: labels("proxygroup", pg.Name, nil),
|
|
},
|
|
Template: corev1.PodTemplateSpec{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: pg.Name,
|
|
Namespace: namespace,
|
|
Labels: labels("proxygroup", pg.Name, nil),
|
|
DeletionGracePeriodSeconds: ptr.To[int64](10),
|
|
Annotations: map[string]string{
|
|
podAnnotationLastSetConfigFileHash: cfgHash,
|
|
},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
ServiceAccountName: pg.Name,
|
|
InitContainers: []corev1.Container{
|
|
{
|
|
Name: "sysctler",
|
|
Image: fmt.Sprintf("tailscale/tailscale:%s", selfVersionImageTag()),
|
|
SecurityContext: &corev1.SecurityContext{
|
|
Privileged: ptr.To(true),
|
|
},
|
|
Command: []string{
|
|
"/bin/sh",
|
|
"-c",
|
|
},
|
|
Args: []string{
|
|
"sysctl -w net.ipv4.ip_forward=1 && if sysctl net.ipv6.conf.all.forwarding; then sysctl -w net.ipv6.conf.all.forwarding=1; fi",
|
|
},
|
|
},
|
|
},
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "tailscale",
|
|
Image: fmt.Sprintf("tailscale/tailscale:%s", selfVersionImageTag()),
|
|
Env: pgEnv(pg),
|
|
SecurityContext: &corev1.SecurityContext{
|
|
Capabilities: &corev1.Capabilities{
|
|
Add: []corev1.Capability{
|
|
"NET_ADMIN",
|
|
},
|
|
},
|
|
},
|
|
VolumeMounts: func() []corev1.VolumeMount {
|
|
var mounts []corev1.VolumeMount
|
|
for i := range pgReplicas(pg) {
|
|
mounts = append(mounts, corev1.VolumeMount{
|
|
Name: fmt.Sprintf("tailscaledconfig-%d", i),
|
|
ReadOnly: true,
|
|
MountPath: fmt.Sprintf("/etc/tsconfig/%s-%d", pg.Name, i),
|
|
})
|
|
}
|
|
|
|
return mounts
|
|
}(),
|
|
},
|
|
},
|
|
Volumes: func() []corev1.Volume {
|
|
var volumes []corev1.Volume
|
|
for i := range pgReplicas(pg) {
|
|
volumes = append(volumes, corev1.Volume{
|
|
Name: fmt.Sprintf("tailscaledconfig-%d", i),
|
|
VolumeSource: corev1.VolumeSource{
|
|
Secret: &corev1.SecretVolumeSource{
|
|
SecretName: fmt.Sprintf("%s-%d-config", pg.Name, i),
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
return volumes
|
|
}(),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func pgServiceAccount(pg *tsapi.ProxyGroup, namespace string) *corev1.ServiceAccount {
|
|
return &corev1.ServiceAccount{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: pg.Name,
|
|
Namespace: namespace,
|
|
Labels: labels("proxygroup", pg.Name, nil),
|
|
OwnerReferences: pgOwnerReference(pg),
|
|
},
|
|
}
|
|
}
|
|
|
|
func pgRole(pg *tsapi.ProxyGroup, namespace string) *rbacv1.Role {
|
|
return &rbacv1.Role{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: pg.Name,
|
|
Namespace: namespace,
|
|
Labels: labels("proxygroup", pg.Name, nil),
|
|
OwnerReferences: pgOwnerReference(pg),
|
|
},
|
|
Rules: []rbacv1.PolicyRule{
|
|
{
|
|
APIGroups: []string{""},
|
|
Resources: []string{"secrets"},
|
|
Verbs: []string{
|
|
"get",
|
|
"patch",
|
|
"update",
|
|
},
|
|
ResourceNames: func() (secrets []string) {
|
|
for i := range pgReplicas(pg) {
|
|
secrets = append(secrets,
|
|
fmt.Sprintf("%s-%d-config", pg.Name, i), // Config with auth key.
|
|
fmt.Sprintf("%s-%d", pg.Name, i), // State.
|
|
)
|
|
}
|
|
return secrets
|
|
}(),
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func pgRoleBinding(pg *tsapi.ProxyGroup, namespace string) *rbacv1.RoleBinding {
|
|
return &rbacv1.RoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: pg.Name,
|
|
Namespace: namespace,
|
|
Labels: labels("proxygroup", pg.Name, nil),
|
|
OwnerReferences: pgOwnerReference(pg),
|
|
},
|
|
Subjects: []rbacv1.Subject{
|
|
{
|
|
Kind: "ServiceAccount",
|
|
Name: pg.Name,
|
|
Namespace: namespace,
|
|
},
|
|
},
|
|
RoleRef: rbacv1.RoleRef{
|
|
Kind: "Role",
|
|
Name: pg.Name,
|
|
},
|
|
}
|
|
}
|
|
|
|
func pgStateSecrets(pg *tsapi.ProxyGroup, namespace string) (secrets []*corev1.Secret) {
|
|
for i := range pgReplicas(pg) {
|
|
secrets = append(secrets, &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: fmt.Sprintf("%s-%d", pg.Name, i),
|
|
Namespace: namespace,
|
|
Labels: secretLabels("proxygroup", pg.Name, "state"),
|
|
OwnerReferences: pgOwnerReference(pg),
|
|
},
|
|
})
|
|
}
|
|
|
|
return secrets
|
|
}
|
|
|
|
func secretLabels(app, instance, typ string) map[string]string {
|
|
return labels(app, instance, map[string]string{
|
|
labelSecretType: typ, // "config" or "state".
|
|
})
|
|
}
|
|
|
|
func pgEnv(_ *tsapi.ProxyGroup) []corev1.EnvVar {
|
|
envs := []corev1.EnvVar{
|
|
{
|
|
Name: "POD_IP",
|
|
ValueFrom: &corev1.EnvVarSource{
|
|
FieldRef: &corev1.ObjectFieldSelector{
|
|
FieldPath: "status.podIP",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "POD_NAME",
|
|
ValueFrom: &corev1.EnvVarSource{
|
|
FieldRef: &corev1.ObjectFieldSelector{
|
|
// Secret is named after the pod.
|
|
FieldPath: "metadata.name",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "TS_KUBE_SECRET",
|
|
Value: "$(POD_NAME)",
|
|
},
|
|
{
|
|
Name: "TS_STATE",
|
|
Value: "kube:$(POD_NAME)",
|
|
},
|
|
{
|
|
Name: "TS_EXPERIMENTAL_VERSIONED_CONFIG_DIR",
|
|
Value: "/etc/tsconfig/$(POD_NAME)",
|
|
},
|
|
}
|
|
|
|
return envs
|
|
}
|
|
|
|
func pgOwnerReference(owner *tsapi.ProxyGroup) []metav1.OwnerReference {
|
|
return []metav1.OwnerReference{*metav1.NewControllerRef(owner, tsapi.SchemeGroupVersion.WithKind("ProxyGroup"))}
|
|
}
|
|
|
|
func pgReplicas(pg *tsapi.ProxyGroup) int32 {
|
|
if pg.Spec.Replicas != nil {
|
|
return *pg.Spec.Replicas
|
|
}
|
|
|
|
return 2
|
|
}
|