mirror of
https://github.com/tailscale/tailscale.git
synced 2025-07-31 00:03:47 +00:00
{,cmd/}k8s-operator: improve docs and validation for image
Change-Id: Ifa9397b4e9dd474a02a8ed3fd56b7d2616738211 Signed-off-by: Tom Proctor <tomhjp@users.noreply.github.com>
This commit is contained in:
parent
dc1a5cd004
commit
e6394e2713
@ -88,6 +88,13 @@ ingressClass:
|
|||||||
# If you need more configuration options, take a look at ProxyClass:
|
# If you need more configuration options, take a look at ProxyClass:
|
||||||
# https://tailscale.com/kb/1445/kubernetes-operator-customization#cluster-resource-customization-using-proxyclass-custom-resource
|
# https://tailscale.com/kb/1445/kubernetes-operator-customization#cluster-resource-customization-using-proxyclass-custom-resource
|
||||||
proxyConfig:
|
proxyConfig:
|
||||||
|
# Configure the proxy image to use instead of the default tailscale/tailscale:latest.
|
||||||
|
# Applying a ProxyClass with `spec.statefulSet.pod.tailscaleContainer.image`
|
||||||
|
# set will override any defaults here.
|
||||||
|
#
|
||||||
|
# Note that ProxyGroups of type "kube-apiserver" use a different default image,
|
||||||
|
# tailscale/k8s-proxy:latest, and it is currently only possible to override
|
||||||
|
# that image via the same ProxyClass field.
|
||||||
image:
|
image:
|
||||||
# Repository defaults to DockerHub, but images are also synced to ghcr.io/tailscale/tailscale.
|
# Repository defaults to DockerHub, but images are also synced to ghcr.io/tailscale/tailscale.
|
||||||
repository: tailscale/tailscale
|
repository: tailscale/tailscale
|
||||||
|
@ -1379,12 +1379,21 @@ spec:
|
|||||||
type: string
|
type: string
|
||||||
image:
|
image:
|
||||||
description: |-
|
description: |-
|
||||||
Container image name. By default images are pulled from
|
Container image name. By default images are pulled from docker.io/tailscale,
|
||||||
docker.io/tailscale/tailscale, but the official images are also
|
but the official images are also available at ghcr.io/tailscale.
|
||||||
available at ghcr.io/tailscale/tailscale. Specifying image name here
|
|
||||||
will override any proxy image values specified via the Kubernetes
|
For all uses except on ProxyGroups of type "kube-apiserver", this image must
|
||||||
operator's Helm chart values or PROXY_IMAGE env var in the operator
|
be either tailscale/tailscale, or an equivalent mirror of that image.
|
||||||
Deployment.
|
To apply to ProxyGroups of type "kube-apiserver", this image must be
|
||||||
|
tailscale/k8s-proxy or a mirror of that image.
|
||||||
|
|
||||||
|
For "tailscale/tailscale"-based proxies, specifying image name here will
|
||||||
|
override any proxy image values specified via the Kubernetes operator's
|
||||||
|
Helm chart values or PROXY_IMAGE env var in the operator Deployment.
|
||||||
|
For "tailscale/k8s-proxy"-based proxies, there is currently no way to
|
||||||
|
configure your own default, and this field is the only way to use a
|
||||||
|
custom image.
|
||||||
|
|
||||||
https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#image
|
https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#image
|
||||||
type: string
|
type: string
|
||||||
imagePullPolicy:
|
imagePullPolicy:
|
||||||
@ -1655,7 +1664,9 @@ spec:
|
|||||||
PodSecurityContext, the value specified in SecurityContext takes precedence.
|
PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||||
type: string
|
type: string
|
||||||
tailscaleInitContainer:
|
tailscaleInitContainer:
|
||||||
description: Configuration for the proxy init container that enables forwarding.
|
description: |-
|
||||||
|
Configuration for the proxy init container that enables forwarding.
|
||||||
|
Not valid to apply to ProxyGroups of type "kube-apiserver".
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
debug:
|
debug:
|
||||||
@ -1709,12 +1720,21 @@ spec:
|
|||||||
type: string
|
type: string
|
||||||
image:
|
image:
|
||||||
description: |-
|
description: |-
|
||||||
Container image name. By default images are pulled from
|
Container image name. By default images are pulled from docker.io/tailscale,
|
||||||
docker.io/tailscale/tailscale, but the official images are also
|
but the official images are also available at ghcr.io/tailscale.
|
||||||
available at ghcr.io/tailscale/tailscale. Specifying image name here
|
|
||||||
will override any proxy image values specified via the Kubernetes
|
For all uses except on ProxyGroups of type "kube-apiserver", this image must
|
||||||
operator's Helm chart values or PROXY_IMAGE env var in the operator
|
be either tailscale/tailscale, or an equivalent mirror of that image.
|
||||||
Deployment.
|
To apply to ProxyGroups of type "kube-apiserver", this image must be
|
||||||
|
tailscale/k8s-proxy or a mirror of that image.
|
||||||
|
|
||||||
|
For "tailscale/tailscale"-based proxies, specifying image name here will
|
||||||
|
override any proxy image values specified via the Kubernetes operator's
|
||||||
|
Helm chart values or PROXY_IMAGE env var in the operator Deployment.
|
||||||
|
For "tailscale/k8s-proxy"-based proxies, there is currently no way to
|
||||||
|
configure your own default, and this field is the only way to use a
|
||||||
|
custom image.
|
||||||
|
|
||||||
https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#image
|
https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#image
|
||||||
type: string
|
type: string
|
||||||
imagePullPolicy:
|
imagePullPolicy:
|
||||||
|
@ -1852,12 +1852,21 @@ spec:
|
|||||||
type: array
|
type: array
|
||||||
image:
|
image:
|
||||||
description: |-
|
description: |-
|
||||||
Container image name. By default images are pulled from
|
Container image name. By default images are pulled from docker.io/tailscale,
|
||||||
docker.io/tailscale/tailscale, but the official images are also
|
but the official images are also available at ghcr.io/tailscale.
|
||||||
available at ghcr.io/tailscale/tailscale. Specifying image name here
|
|
||||||
will override any proxy image values specified via the Kubernetes
|
For all uses except on ProxyGroups of type "kube-apiserver", this image must
|
||||||
operator's Helm chart values or PROXY_IMAGE env var in the operator
|
be either tailscale/tailscale, or an equivalent mirror of that image.
|
||||||
Deployment.
|
To apply to ProxyGroups of type "kube-apiserver", this image must be
|
||||||
|
tailscale/k8s-proxy or a mirror of that image.
|
||||||
|
|
||||||
|
For "tailscale/tailscale"-based proxies, specifying image name here will
|
||||||
|
override any proxy image values specified via the Kubernetes operator's
|
||||||
|
Helm chart values or PROXY_IMAGE env var in the operator Deployment.
|
||||||
|
For "tailscale/k8s-proxy"-based proxies, there is currently no way to
|
||||||
|
configure your own default, and this field is the only way to use a
|
||||||
|
custom image.
|
||||||
|
|
||||||
https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#image
|
https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#image
|
||||||
type: string
|
type: string
|
||||||
imagePullPolicy:
|
imagePullPolicy:
|
||||||
@ -2129,7 +2138,9 @@ spec:
|
|||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
tailscaleInitContainer:
|
tailscaleInitContainer:
|
||||||
description: Configuration for the proxy init container that enables forwarding.
|
description: |-
|
||||||
|
Configuration for the proxy init container that enables forwarding.
|
||||||
|
Not valid to apply to ProxyGroups of type "kube-apiserver".
|
||||||
properties:
|
properties:
|
||||||
debug:
|
debug:
|
||||||
description: |-
|
description: |-
|
||||||
@ -2182,12 +2193,21 @@ spec:
|
|||||||
type: array
|
type: array
|
||||||
image:
|
image:
|
||||||
description: |-
|
description: |-
|
||||||
Container image name. By default images are pulled from
|
Container image name. By default images are pulled from docker.io/tailscale,
|
||||||
docker.io/tailscale/tailscale, but the official images are also
|
but the official images are also available at ghcr.io/tailscale.
|
||||||
available at ghcr.io/tailscale/tailscale. Specifying image name here
|
|
||||||
will override any proxy image values specified via the Kubernetes
|
For all uses except on ProxyGroups of type "kube-apiserver", this image must
|
||||||
operator's Helm chart values or PROXY_IMAGE env var in the operator
|
be either tailscale/tailscale, or an equivalent mirror of that image.
|
||||||
Deployment.
|
To apply to ProxyGroups of type "kube-apiserver", this image must be
|
||||||
|
tailscale/k8s-proxy or a mirror of that image.
|
||||||
|
|
||||||
|
For "tailscale/tailscale"-based proxies, specifying image name here will
|
||||||
|
override any proxy image values specified via the Kubernetes operator's
|
||||||
|
Helm chart values or PROXY_IMAGE env var in the operator Deployment.
|
||||||
|
For "tailscale/k8s-proxy"-based proxies, there is currently no way to
|
||||||
|
configure your own default, and this field is the only way to use a
|
||||||
|
custom image.
|
||||||
|
|
||||||
https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#image
|
https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#image
|
||||||
type: string
|
type: string
|
||||||
imagePullPolicy:
|
imagePullPolicy:
|
||||||
|
@ -17,6 +17,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
dockerref "github.com/distribution/reference"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
xslices "golang.org/x/exp/slices"
|
xslices "golang.org/x/exp/slices"
|
||||||
appsv1 "k8s.io/api/apps/v1"
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
@ -161,10 +162,6 @@ func (r *ProxyGroupReconciler) reconcilePG(ctx context.Context, pg *tsapi.ProxyG
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.validate(ctx, pg); err != nil {
|
|
||||||
return r.notReady(reasonProxyGroupInvalid, fmt.Sprintf("invalid ProxyGroup spec: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyClassName := r.defaultProxyClass
|
proxyClassName := r.defaultProxyClass
|
||||||
if pg.Spec.ProxyClass != "" {
|
if pg.Spec.ProxyClass != "" {
|
||||||
proxyClassName = pg.Spec.ProxyClass
|
proxyClassName = pg.Spec.ProxyClass
|
||||||
@ -182,7 +179,6 @@ func (r *ProxyGroupReconciler) reconcilePG(ctx context.Context, pg *tsapi.ProxyG
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return r.notReadyErrf(pg, "error getting ProxyGroup's ProxyClass %q: %w", proxyClassName, err)
|
return r.notReadyErrf(pg, "error getting ProxyGroup's ProxyClass %q: %w", proxyClassName, err)
|
||||||
}
|
}
|
||||||
validateProxyClassForPG(logger, pg, proxyClass)
|
|
||||||
if !tsoperator.ProxyClassIsReady(proxyClass) {
|
if !tsoperator.ProxyClassIsReady(proxyClass) {
|
||||||
msg := fmt.Sprintf("the ProxyGroup's ProxyClass %q is not yet in a ready state, waiting...", proxyClassName)
|
msg := fmt.Sprintf("the ProxyGroup's ProxyClass %q is not yet in a ready state, waiting...", proxyClassName)
|
||||||
logger.Info(msg)
|
logger.Info(msg)
|
||||||
@ -190,6 +186,10 @@ func (r *ProxyGroupReconciler) reconcilePG(ctx context.Context, pg *tsapi.ProxyG
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := r.validate(ctx, pg, proxyClass, logger); err != nil {
|
||||||
|
return r.notReady(reasonProxyGroupInvalid, fmt.Sprintf("invalid ProxyGroup spec: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
staticEndpoints, nrr, err := r.maybeProvision(ctx, pg, proxyClass)
|
staticEndpoints, nrr, err := r.maybeProvision(ctx, pg, proxyClass)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), optimisticLockErrorMsg) {
|
if strings.Contains(err.Error(), optimisticLockErrorMsg) {
|
||||||
@ -204,39 +204,7 @@ func (r *ProxyGroupReconciler) reconcilePG(ctx context.Context, pg *tsapi.ProxyG
|
|||||||
return staticEndpoints, nrr, nil
|
return staticEndpoints, nrr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ProxyGroupReconciler) validate(ctx context.Context, pg *tsapi.ProxyGroup) error {
|
func (r *ProxyGroupReconciler) validate(ctx context.Context, pg *tsapi.ProxyGroup, pc *tsapi.ProxyClass, logger *zap.SugaredLogger) error {
|
||||||
if isAuthAPIServerProxy(pg) {
|
|
||||||
// Validate that the static ServiceAccount already exists.
|
|
||||||
sa := &corev1.ServiceAccount{}
|
|
||||||
if err := r.Get(ctx, types.NamespacedName{Namespace: r.tsNamespace, Name: authAPIServerProxySAName}, sa); err != nil {
|
|
||||||
if apierrors.IsNotFound(err) {
|
|
||||||
return fmt.Errorf("the ServiceAccount %q used for the API server proxy in auth mode does not exist but "+
|
|
||||||
"should have been created during operator installation; use apiServerProxyConfig.allowImpersonation=true "+
|
|
||||||
"in the helm chart, or authproxy-rbac.yaml from the static manifests", authAPIServerProxySAName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("error validating that ServiceAccount %q exists: %w", authAPIServerProxySAName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate that the ServiceAccount we create won't overwrite the static one.
|
|
||||||
// TODO(tomhjp): This doesn't cover other controllers that could create a
|
|
||||||
// ServiceAccount. Perhaps should have some guards to ensure that an update
|
|
||||||
// would never change the ownership of a resource we expect to already be owned.
|
|
||||||
if pgServiceAccountName(pg) == authAPIServerProxySAName {
|
|
||||||
return fmt.Errorf("the name of the ProxyGroup %q conflicts with the static ServiceAccount used for the API server proxy in auth mode", pg.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateProxyClassForPG applies custom validation logic for ProxyClass applied to ProxyGroup.
|
|
||||||
func validateProxyClassForPG(logger *zap.SugaredLogger, pg *tsapi.ProxyGroup, pc *tsapi.ProxyClass) {
|
|
||||||
if pg.Spec.Type == tsapi.ProxyGroupTypeIngress {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Our custom logic for ensuring minimum downtime ProxyGroup update rollouts relies on the local health check
|
// Our custom logic for ensuring minimum downtime ProxyGroup update rollouts relies on the local health check
|
||||||
// beig accessible on the replica Pod IP:9002. This address can also be modified by users, via
|
// beig accessible on the replica Pod IP:9002. This address can also be modified by users, via
|
||||||
// TS_LOCAL_ADDR_PORT env var.
|
// TS_LOCAL_ADDR_PORT env var.
|
||||||
@ -248,13 +216,69 @@ func validateProxyClassForPG(logger *zap.SugaredLogger, pg *tsapi.ProxyGroup, pc
|
|||||||
// shouldn't need to set their own).
|
// shouldn't need to set their own).
|
||||||
//
|
//
|
||||||
// TODO(irbekrm): maybe disallow configuring this env var in future (in Tailscale 1.84 or later).
|
// TODO(irbekrm): maybe disallow configuring this env var in future (in Tailscale 1.84 or later).
|
||||||
if hasLocalAddrPortSet(pc) {
|
if pg.Spec.Type == tsapi.ProxyGroupTypeEgress && hasLocalAddrPortSet(pc) {
|
||||||
msg := fmt.Sprintf("ProxyClass %s applied to an egress ProxyGroup has TS_LOCAL_ADDR_PORT env var set to a custom value."+
|
msg := fmt.Sprintf("ProxyClass %s applied to an egress ProxyGroup has TS_LOCAL_ADDR_PORT env var set to a custom value."+
|
||||||
"This will disable the ProxyGroup graceful failover mechanism, so you might experience downtime when ProxyGroup pods are restarted."+
|
"This will disable the ProxyGroup graceful failover mechanism, so you might experience downtime when ProxyGroup pods are restarted."+
|
||||||
"In future we will remove the ability to set custom TS_LOCAL_ADDR_PORT for egress ProxyGroups."+
|
"In future we will remove the ability to set custom TS_LOCAL_ADDR_PORT for egress ProxyGroups."+
|
||||||
"Please raise an issue if you expect that this will cause issues for your workflow.", pc.Name)
|
"Please raise an issue if you expect that this will cause issues for your workflow.", pc.Name)
|
||||||
logger.Warn(msg)
|
logger.Warn(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// image is the value of pc.Spec.StatefulSet.Pod.TailscaleContainer.Image or ""
|
||||||
|
// imagePath is a slash-delimited path ending with the image name, e.g.
|
||||||
|
// "tailscale/tailscale" or maybe "k8s-proxy" if hosted at example.com/k8s-proxy.
|
||||||
|
var image, imagePath string
|
||||||
|
if pc != nil &&
|
||||||
|
pc.Spec.StatefulSet != nil &&
|
||||||
|
pc.Spec.StatefulSet.Pod != nil &&
|
||||||
|
pc.Spec.StatefulSet.Pod.TailscaleContainer != nil {
|
||||||
|
image, err := dockerref.ParseNormalizedNamed(pc.Spec.StatefulSet.Pod.TailscaleContainer.Image)
|
||||||
|
if err != nil {
|
||||||
|
// Shouldn't be possible as the ProxyClass won't be marked ready
|
||||||
|
// without successfully parsing the image.
|
||||||
|
return fmt.Errorf("error parsing %q as a container image reference: %w", pc.Spec.StatefulSet.Pod.TailscaleContainer.Image, err)
|
||||||
|
}
|
||||||
|
imagePath = dockerref.Path(image)
|
||||||
|
}
|
||||||
|
|
||||||
|
var errs []error
|
||||||
|
if isAuthAPIServerProxy(pg) {
|
||||||
|
// Validate that the static ServiceAccount already exists.
|
||||||
|
sa := &corev1.ServiceAccount{}
|
||||||
|
if err := r.Get(ctx, types.NamespacedName{Namespace: r.tsNamespace, Name: authAPIServerProxySAName}, sa); err != nil {
|
||||||
|
if !apierrors.IsNotFound(err) {
|
||||||
|
return fmt.Errorf("error validating that ServiceAccount %q exists: %w", authAPIServerProxySAName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
errs = append(errs, fmt.Errorf("the ServiceAccount %q used for the API server proxy in auth mode does not exist but "+
|
||||||
|
"should have been created during operator installation; use apiServerProxyConfig.allowImpersonation=true "+
|
||||||
|
"in the helm chart, or authproxy-rbac.yaml from the static manifests", authAPIServerProxySAName))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Validate that the ServiceAccount we create won't overwrite the static one.
|
||||||
|
// TODO(tomhjp): This doesn't cover other controllers that could create a
|
||||||
|
// ServiceAccount. Perhaps should have some guards to ensure that an update
|
||||||
|
// would never change the ownership of a resource we expect to already be owned.
|
||||||
|
if pgServiceAccountName(pg) == authAPIServerProxySAName {
|
||||||
|
errs = append(errs, fmt.Errorf("the name of the ProxyGroup %q conflicts with the static ServiceAccount used for the API server proxy in auth mode", pg.Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if pg.Spec.Type == tsapi.ProxyGroupTypeKubernetesAPIServer {
|
||||||
|
if strings.HasSuffix(imagePath, "tailscale") {
|
||||||
|
errs = append(errs, fmt.Errorf("the configured ProxyClass %q specifies to use image %q but expected a %q image for ProxyGroup of type %q", pc.Name, image, "k8s-proxy", pg.Spec.Type))
|
||||||
|
}
|
||||||
|
|
||||||
|
if pc.Spec.StatefulSet != nil && pc.Spec.StatefulSet.Pod != nil && pc.Spec.StatefulSet.Pod.TailscaleInitContainer != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("the configured ProxyClass %q specifies Tailscale init container config, but ProxyGroups of type %q do not use init containers", pc.Name, pg.Spec.Type))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if strings.HasSuffix(imagePath, "k8s-proxy") {
|
||||||
|
errs = append(errs, fmt.Errorf("the configured ProxyClass %q specifies to use image %q but expected a %q image for ProxyGroup of type %q", pc.Name, image, "tailscale", pg.Spec.Type))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Join(errs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ProxyGroupReconciler) maybeProvision(ctx context.Context, pg *tsapi.ProxyGroup, proxyClass *tsapi.ProxyClass) (map[string][]netip.AddrPort, *notReadyReason, error) {
|
func (r *ProxyGroupReconciler) maybeProvision(ctx context.Context, pg *tsapi.ProxyGroup, proxyClass *tsapi.ProxyClass) (map[string][]netip.AddrPort, *notReadyReason, error) {
|
||||||
|
@ -300,7 +300,7 @@ func kubeAPIServerStatefulSet(pg *tsapi.ProxyGroup, namespace, image string) (*a
|
|||||||
ServiceAccountName: pgServiceAccountName(pg),
|
ServiceAccountName: pgServiceAccountName(pg),
|
||||||
Containers: []corev1.Container{
|
Containers: []corev1.Container{
|
||||||
{
|
{
|
||||||
Name: "k8s-proxy",
|
Name: mainContainerName,
|
||||||
Image: image,
|
Image: image,
|
||||||
Env: []corev1.EnvVar{
|
Env: []corev1.EnvVar{
|
||||||
{
|
{
|
||||||
|
@ -1310,6 +1310,170 @@ func TestIngressAdvertiseServicesConfigPreserved(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidateProxyGroup(t *testing.T) {
|
||||||
|
type testCase struct {
|
||||||
|
typ tsapi.ProxyGroupType
|
||||||
|
pgName string
|
||||||
|
image string
|
||||||
|
noauth bool
|
||||||
|
initContainer bool
|
||||||
|
staticSAExists bool
|
||||||
|
expectedErrs int
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range map[string]testCase{
|
||||||
|
"default_ingress": {
|
||||||
|
typ: tsapi.ProxyGroupTypeIngress,
|
||||||
|
},
|
||||||
|
"default_kube": {
|
||||||
|
typ: tsapi.ProxyGroupTypeKubernetesAPIServer,
|
||||||
|
staticSAExists: true,
|
||||||
|
},
|
||||||
|
"default_kube_noauth": {
|
||||||
|
typ: tsapi.ProxyGroupTypeKubernetesAPIServer,
|
||||||
|
noauth: true,
|
||||||
|
// Does not require the static ServiceAccount to exist.
|
||||||
|
},
|
||||||
|
"kube_static_sa_missing": {
|
||||||
|
typ: tsapi.ProxyGroupTypeKubernetesAPIServer,
|
||||||
|
staticSAExists: false,
|
||||||
|
expectedErrs: 1,
|
||||||
|
},
|
||||||
|
"kube_noauth_would_overwrite_static_sa": {
|
||||||
|
typ: tsapi.ProxyGroupTypeKubernetesAPIServer,
|
||||||
|
staticSAExists: true,
|
||||||
|
noauth: true,
|
||||||
|
pgName: authAPIServerProxySAName,
|
||||||
|
expectedErrs: 1,
|
||||||
|
},
|
||||||
|
"ingress_would_overwrite_static_sa": {
|
||||||
|
typ: tsapi.ProxyGroupTypeIngress,
|
||||||
|
staticSAExists: true,
|
||||||
|
pgName: authAPIServerProxySAName,
|
||||||
|
expectedErrs: 1,
|
||||||
|
},
|
||||||
|
"tailscale_image_for_kube_pg_1": {
|
||||||
|
typ: tsapi.ProxyGroupTypeKubernetesAPIServer,
|
||||||
|
staticSAExists: true,
|
||||||
|
image: "example.com/tailscale/tailscale",
|
||||||
|
expectedErrs: 1,
|
||||||
|
},
|
||||||
|
"tailscale_image_for_kube_pg_2": {
|
||||||
|
typ: tsapi.ProxyGroupTypeKubernetesAPIServer,
|
||||||
|
staticSAExists: true,
|
||||||
|
image: "example.com/tailscale",
|
||||||
|
expectedErrs: 1,
|
||||||
|
},
|
||||||
|
"tailscale_image_for_kube_pg_3": {
|
||||||
|
typ: tsapi.ProxyGroupTypeKubernetesAPIServer,
|
||||||
|
staticSAExists: true,
|
||||||
|
image: "example.com/tailscale/tailscale:latest",
|
||||||
|
expectedErrs: 1,
|
||||||
|
},
|
||||||
|
"tailscale_image_for_kube_pg_4": {
|
||||||
|
typ: tsapi.ProxyGroupTypeKubernetesAPIServer,
|
||||||
|
staticSAExists: true,
|
||||||
|
image: "tailscale/tailscale",
|
||||||
|
expectedErrs: 1,
|
||||||
|
},
|
||||||
|
"k8s_proxy_image_for_ingress_pg": {
|
||||||
|
typ: tsapi.ProxyGroupTypeIngress,
|
||||||
|
image: "example.com/k8s-proxy",
|
||||||
|
expectedErrs: 1,
|
||||||
|
},
|
||||||
|
"init_container_for_kube_pg": {
|
||||||
|
typ: tsapi.ProxyGroupTypeKubernetesAPIServer,
|
||||||
|
staticSAExists: true,
|
||||||
|
initContainer: true,
|
||||||
|
expectedErrs: 1,
|
||||||
|
},
|
||||||
|
"init_container_for_ingress_pg": {
|
||||||
|
typ: tsapi.ProxyGroupTypeIngress,
|
||||||
|
initContainer: true,
|
||||||
|
},
|
||||||
|
"init_container_for_egress_pg": {
|
||||||
|
typ: tsapi.ProxyGroupTypeEgress,
|
||||||
|
initContainer: true,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
pc := &tsapi.ProxyClass{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "some-pc",
|
||||||
|
},
|
||||||
|
Spec: tsapi.ProxyClassSpec{
|
||||||
|
StatefulSet: &tsapi.StatefulSet{
|
||||||
|
Pod: &tsapi.Pod{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if tc.image != "" {
|
||||||
|
pc.Spec.StatefulSet.Pod.TailscaleContainer = &tsapi.Container{
|
||||||
|
Image: tc.image,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tc.initContainer {
|
||||||
|
pc.Spec.StatefulSet.Pod.TailscaleInitContainer = &tsapi.Container{}
|
||||||
|
}
|
||||||
|
pgName := "some-pg"
|
||||||
|
if tc.pgName != "" {
|
||||||
|
pgName = tc.pgName
|
||||||
|
}
|
||||||
|
pg := &tsapi.ProxyGroup{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: pgName,
|
||||||
|
},
|
||||||
|
Spec: tsapi.ProxyGroupSpec{
|
||||||
|
Type: tc.typ,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if tc.noauth {
|
||||||
|
pg.Spec.KubeAPIServer = &tsapi.KubeAPIServerConfig{
|
||||||
|
Mode: ptr.To(tsapi.APIServerProxyModeNoAuth),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var objs []client.Object
|
||||||
|
if tc.staticSAExists {
|
||||||
|
objs = append(objs, &corev1.ServiceAccount{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: authAPIServerProxySAName,
|
||||||
|
Namespace: tsNamespace,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
r := ProxyGroupReconciler{
|
||||||
|
tsNamespace: tsNamespace,
|
||||||
|
Client: fake.NewClientBuilder().
|
||||||
|
WithObjects(objs...).
|
||||||
|
Build(),
|
||||||
|
}
|
||||||
|
|
||||||
|
logger, _ := zap.NewDevelopment()
|
||||||
|
err := r.validate(t.Context(), pg, pc, logger.Sugar())
|
||||||
|
if tc.expectedErrs == 0 {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no errors, got: %v", err)
|
||||||
|
}
|
||||||
|
// Test finished.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected %d errors, got none", tc.expectedErrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
type unwrapper interface {
|
||||||
|
Unwrap() []error
|
||||||
|
}
|
||||||
|
errs := err.(unwrapper)
|
||||||
|
if len(errs.Unwrap()) != tc.expectedErrs {
|
||||||
|
t.Fatalf("expected %d errors, got %d: %v", tc.expectedErrs, len(errs.Unwrap()), err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func proxyClassesForLEStagingTest() (*tsapi.ProxyClass, *tsapi.ProxyClass, *tsapi.ProxyClass) {
|
func proxyClassesForLEStagingTest() (*tsapi.ProxyClass, *tsapi.ProxyClass, *tsapi.ProxyClass) {
|
||||||
pcLEStaging := &tsapi.ProxyClass{
|
pcLEStaging := &tsapi.ProxyClass{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
@ -102,6 +102,8 @@ const (
|
|||||||
defaultLocalAddrPort = 9002 // metrics and health check port
|
defaultLocalAddrPort = 9002 // metrics and health check port
|
||||||
|
|
||||||
letsEncryptStagingEndpoint = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
letsEncryptStagingEndpoint = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||||
|
|
||||||
|
mainContainerName = "tailscale"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -903,7 +905,7 @@ func enableEndpoints(ss *appsv1.StatefulSet, metrics, debug bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isMainContainer(c *corev1.Container) bool {
|
func isMainContainer(c *corev1.Container) bool {
|
||||||
return c.Name == "tailscale" || c.Name == "k8s-proxy"
|
return c.Name == mainContainerName
|
||||||
}
|
}
|
||||||
|
|
||||||
// tailscaledConfig takes a proxy config, a newly generated auth key if generated and a Secret with the previous proxy
|
// tailscaledConfig takes a proxy config, a newly generated auth key if generated and a Secret with the previous proxy
|
||||||
|
@ -157,7 +157,7 @@ _Appears in:_
|
|||||||
| Field | Description | Default | Validation |
|
| Field | Description | Default | Validation |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `env` _[Env](#env) array_ | List of environment variables to set in the container.<br />https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#environment-variables<br />Note that environment variables provided here will take precedence<br />over Tailscale-specific environment variables set by the operator,<br />however running proxies with custom values for Tailscale environment<br />variables (i.e TS_USERSPACE) is not recommended and might break in<br />the future. | | |
|
| `env` _[Env](#env) array_ | List of environment variables to set in the container.<br />https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#environment-variables<br />Note that environment variables provided here will take precedence<br />over Tailscale-specific environment variables set by the operator,<br />however running proxies with custom values for Tailscale environment<br />variables (i.e TS_USERSPACE) is not recommended and might break in<br />the future. | | |
|
||||||
| `image` _string_ | Container image name. By default images are pulled from<br />docker.io/tailscale/tailscale, but the official images are also<br />available at ghcr.io/tailscale/tailscale. Specifying image name here<br />will override any proxy image values specified via the Kubernetes<br />operator's Helm chart values or PROXY_IMAGE env var in the operator<br />Deployment.<br />https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#image | | |
|
| `image` _string_ | Container image name. By default images are pulled from docker.io/tailscale,<br />but the official images are also available at ghcr.io/tailscale.<br />For all uses except on ProxyGroups of type "kube-apiserver", this image must<br />be either tailscale/tailscale, or an equivalent mirror of that image.<br />To apply to ProxyGroups of type "kube-apiserver", this image must be<br />tailscale/k8s-proxy or a mirror of that image.<br />For "tailscale/tailscale"-based proxies, specifying image name here will<br />override any proxy image values specified via the Kubernetes operator's<br />Helm chart values or PROXY_IMAGE env var in the operator Deployment.<br />For "tailscale/k8s-proxy"-based proxies, there is currently no way to<br />configure your own default, and this field is the only way to use a<br />custom image.<br />https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#image | | |
|
||||||
| `imagePullPolicy` _[PullPolicy](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#pullpolicy-v1-core)_ | Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always.<br />https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#image | | Enum: [Always Never IfNotPresent] <br /> |
|
| `imagePullPolicy` _[PullPolicy](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#pullpolicy-v1-core)_ | Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always.<br />https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#image | | Enum: [Always Never IfNotPresent] <br /> |
|
||||||
| `resources` _[ResourceRequirements](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#resourcerequirements-v1-core)_ | Container resource requirements.<br />By default Tailscale Kubernetes operator does not apply any resource<br />requirements. The amount of resources required wil depend on the<br />amount of resources the operator needs to parse, usage patterns and<br />cluster size.<br />https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#resources | | |
|
| `resources` _[ResourceRequirements](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#resourcerequirements-v1-core)_ | Container resource requirements.<br />By default Tailscale Kubernetes operator does not apply any resource<br />requirements. The amount of resources required wil depend on the<br />amount of resources the operator needs to parse, usage patterns and<br />cluster size.<br />https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#resources | | |
|
||||||
| `securityContext` _[SecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#securitycontext-v1-core)_ | Container security context.<br />Security context specified here will override the security context set by the operator.<br />By default the operator sets the Tailscale container and the Tailscale init container to privileged<br />for proxies created for Tailscale ingress and egress Service, Connector and ProxyGroup.<br />You can reduce the permissions of the Tailscale container to cap NET_ADMIN by<br />installing device plugin in your cluster and configuring the proxies tun device to be created<br />by the device plugin, see https://github.com/tailscale/tailscale/issues/10814#issuecomment-2479977752<br />https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#security-context | | |
|
| `securityContext` _[SecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#securitycontext-v1-core)_ | Container security context.<br />Security context specified here will override the security context set by the operator.<br />By default the operator sets the Tailscale container and the Tailscale init container to privileged<br />for proxies created for Tailscale ingress and egress Service, Connector and ProxyGroup.<br />You can reduce the permissions of the Tailscale container to cap NET_ADMIN by<br />installing device plugin in your cluster and configuring the proxies tun device to be created<br />by the device plugin, see https://github.com/tailscale/tailscale/issues/10814#issuecomment-2479977752<br />https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#security-context | | |
|
||||||
@ -490,7 +490,7 @@ _Appears in:_
|
|||||||
| `annotations` _object (keys:string, values:string)_ | Annotations that will be added to the proxy Pod.<br />Any annotations specified here will be merged with the default<br />annotations applied to the Pod by the Tailscale Kubernetes operator.<br />Annotations must be valid Kubernetes annotations.<br />https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/#syntax-and-character-set | | |
|
| `annotations` _object (keys:string, values:string)_ | Annotations that will be added to the proxy Pod.<br />Any annotations specified here will be merged with the default<br />annotations applied to the Pod by the Tailscale Kubernetes operator.<br />Annotations must be valid Kubernetes annotations.<br />https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/#syntax-and-character-set | | |
|
||||||
| `affinity` _[Affinity](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#affinity-v1-core)_ | Proxy Pod's affinity rules.<br />By default, the Tailscale Kubernetes operator does not apply any affinity rules.<br />https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#affinity | | |
|
| `affinity` _[Affinity](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#affinity-v1-core)_ | Proxy Pod's affinity rules.<br />By default, the Tailscale Kubernetes operator does not apply any affinity rules.<br />https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#affinity | | |
|
||||||
| `tailscaleContainer` _[Container](#container)_ | Configuration for the proxy container running tailscale. | | |
|
| `tailscaleContainer` _[Container](#container)_ | Configuration for the proxy container running tailscale. | | |
|
||||||
| `tailscaleInitContainer` _[Container](#container)_ | Configuration for the proxy init container that enables forwarding. | | |
|
| `tailscaleInitContainer` _[Container](#container)_ | Configuration for the proxy init container that enables forwarding.<br />Not valid to apply to ProxyGroups of type "kube-apiserver". | | |
|
||||||
| `securityContext` _[PodSecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#podsecuritycontext-v1-core)_ | Proxy Pod's security context.<br />By default Tailscale Kubernetes operator does not apply any Pod<br />security context.<br />https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#security-context-2 | | |
|
| `securityContext` _[PodSecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#podsecuritycontext-v1-core)_ | Proxy Pod's security context.<br />By default Tailscale Kubernetes operator does not apply any Pod<br />security context.<br />https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#security-context-2 | | |
|
||||||
| `imagePullSecrets` _[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#localobjectreference-v1-core) array_ | Proxy Pod's image pull Secrets.<br />https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodSpec | | |
|
| `imagePullSecrets` _[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#localobjectreference-v1-core) array_ | Proxy Pod's image pull Secrets.<br />https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodSpec | | |
|
||||||
| `nodeName` _string_ | Proxy Pod's node name.<br />https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#scheduling | | |
|
| `nodeName` _string_ | Proxy Pod's node name.<br />https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#scheduling | | |
|
||||||
|
@ -264,6 +264,7 @@ type Pod struct {
|
|||||||
// +optional
|
// +optional
|
||||||
TailscaleContainer *Container `json:"tailscaleContainer,omitempty"`
|
TailscaleContainer *Container `json:"tailscaleContainer,omitempty"`
|
||||||
// Configuration for the proxy init container that enables forwarding.
|
// Configuration for the proxy init container that enables forwarding.
|
||||||
|
// Not valid to apply to ProxyGroups of type "kube-apiserver".
|
||||||
// +optional
|
// +optional
|
||||||
TailscaleInitContainer *Container `json:"tailscaleInitContainer,omitempty"`
|
TailscaleInitContainer *Container `json:"tailscaleInitContainer,omitempty"`
|
||||||
// Proxy Pod's security context.
|
// Proxy Pod's security context.
|
||||||
@ -364,12 +365,21 @@ type Container struct {
|
|||||||
// the future.
|
// the future.
|
||||||
// +optional
|
// +optional
|
||||||
Env []Env `json:"env,omitempty"`
|
Env []Env `json:"env,omitempty"`
|
||||||
// Container image name. By default images are pulled from
|
// Container image name. By default images are pulled from docker.io/tailscale,
|
||||||
// docker.io/tailscale/tailscale, but the official images are also
|
// but the official images are also available at ghcr.io/tailscale.
|
||||||
// available at ghcr.io/tailscale/tailscale. Specifying image name here
|
//
|
||||||
// will override any proxy image values specified via the Kubernetes
|
// For all uses except on ProxyGroups of type "kube-apiserver", this image must
|
||||||
// operator's Helm chart values or PROXY_IMAGE env var in the operator
|
// be either tailscale/tailscale, or an equivalent mirror of that image.
|
||||||
// Deployment.
|
// To apply to ProxyGroups of type "kube-apiserver", this image must be
|
||||||
|
// tailscale/k8s-proxy or a mirror of that image.
|
||||||
|
//
|
||||||
|
// For "tailscale/tailscale"-based proxies, specifying image name here will
|
||||||
|
// override any proxy image values specified via the Kubernetes operator's
|
||||||
|
// Helm chart values or PROXY_IMAGE env var in the operator Deployment.
|
||||||
|
// For "tailscale/k8s-proxy"-based proxies, there is currently no way to
|
||||||
|
// configure your own default, and this field is the only way to use a
|
||||||
|
// custom image.
|
||||||
|
//
|
||||||
// https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#image
|
// https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#image
|
||||||
// +optional
|
// +optional
|
||||||
Image string `json:"image,omitempty"`
|
Image string `json:"image,omitempty"`
|
||||||
|
Loading…
x
Reference in New Issue
Block a user