mirror of
https://github.com/tailscale/tailscale.git
synced 2025-07-30 07:43:42 +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:
|
||||
# https://tailscale.com/kb/1445/kubernetes-operator-customization#cluster-resource-customization-using-proxyclass-custom-resource
|
||||
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:
|
||||
# Repository defaults to DockerHub, but images are also synced to ghcr.io/tailscale/tailscale.
|
||||
repository: tailscale/tailscale
|
||||
|
@ -1379,12 +1379,21 @@ spec:
|
||||
type: string
|
||||
image:
|
||||
description: |-
|
||||
Container image name. By default images are pulled from
|
||||
docker.io/tailscale/tailscale, but the official images are also
|
||||
available at ghcr.io/tailscale/tailscale. 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.
|
||||
Container image name. By default images are pulled from docker.io/tailscale,
|
||||
but the official images are also available at ghcr.io/tailscale.
|
||||
|
||||
For all uses except on ProxyGroups of type "kube-apiserver", this image must
|
||||
be either tailscale/tailscale, or an equivalent mirror of that image.
|
||||
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
|
||||
type: string
|
||||
imagePullPolicy:
|
||||
@ -1655,7 +1664,9 @@ spec:
|
||||
PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
type: string
|
||||
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
|
||||
properties:
|
||||
debug:
|
||||
@ -1709,12 +1720,21 @@ spec:
|
||||
type: string
|
||||
image:
|
||||
description: |-
|
||||
Container image name. By default images are pulled from
|
||||
docker.io/tailscale/tailscale, but the official images are also
|
||||
available at ghcr.io/tailscale/tailscale. 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.
|
||||
Container image name. By default images are pulled from docker.io/tailscale,
|
||||
but the official images are also available at ghcr.io/tailscale.
|
||||
|
||||
For all uses except on ProxyGroups of type "kube-apiserver", this image must
|
||||
be either tailscale/tailscale, or an equivalent mirror of that image.
|
||||
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
|
||||
type: string
|
||||
imagePullPolicy:
|
||||
|
@ -1852,12 +1852,21 @@ spec:
|
||||
type: array
|
||||
image:
|
||||
description: |-
|
||||
Container image name. By default images are pulled from
|
||||
docker.io/tailscale/tailscale, but the official images are also
|
||||
available at ghcr.io/tailscale/tailscale. 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.
|
||||
Container image name. By default images are pulled from docker.io/tailscale,
|
||||
but the official images are also available at ghcr.io/tailscale.
|
||||
|
||||
For all uses except on ProxyGroups of type "kube-apiserver", this image must
|
||||
be either tailscale/tailscale, or an equivalent mirror of that image.
|
||||
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
|
||||
type: string
|
||||
imagePullPolicy:
|
||||
@ -2129,7 +2138,9 @@ spec:
|
||||
type: object
|
||||
type: object
|
||||
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:
|
||||
debug:
|
||||
description: |-
|
||||
@ -2182,12 +2193,21 @@ spec:
|
||||
type: array
|
||||
image:
|
||||
description: |-
|
||||
Container image name. By default images are pulled from
|
||||
docker.io/tailscale/tailscale, but the official images are also
|
||||
available at ghcr.io/tailscale/tailscale. 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.
|
||||
Container image name. By default images are pulled from docker.io/tailscale,
|
||||
but the official images are also available at ghcr.io/tailscale.
|
||||
|
||||
For all uses except on ProxyGroups of type "kube-apiserver", this image must
|
||||
be either tailscale/tailscale, or an equivalent mirror of that image.
|
||||
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
|
||||
type: string
|
||||
imagePullPolicy:
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
dockerref "github.com/distribution/reference"
|
||||
"go.uber.org/zap"
|
||||
xslices "golang.org/x/exp/slices"
|
||||
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
|
||||
if pg.Spec.ProxyClass != "" {
|
||||
proxyClassName = pg.Spec.ProxyClass
|
||||
@ -182,7 +179,6 @@ func (r *ProxyGroupReconciler) reconcilePG(ctx context.Context, pg *tsapi.ProxyG
|
||||
if err != nil {
|
||||
return r.notReadyErrf(pg, "error getting ProxyGroup's ProxyClass %q: %w", proxyClassName, err)
|
||||
}
|
||||
validateProxyClassForPG(logger, pg, proxyClass)
|
||||
if !tsoperator.ProxyClassIsReady(proxyClass) {
|
||||
msg := fmt.Sprintf("the ProxyGroup's ProxyClass %q is not yet in a ready state, waiting...", proxyClassName)
|
||||
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)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), optimisticLockErrorMsg) {
|
||||
@ -204,39 +204,7 @@ func (r *ProxyGroupReconciler) reconcilePG(ctx context.Context, pg *tsapi.ProxyG
|
||||
return staticEndpoints, nrr, nil
|
||||
}
|
||||
|
||||
func (r *ProxyGroupReconciler) validate(ctx context.Context, pg *tsapi.ProxyGroup) 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
|
||||
}
|
||||
func (r *ProxyGroupReconciler) validate(ctx context.Context, pg *tsapi.ProxyGroup, pc *tsapi.ProxyClass, logger *zap.SugaredLogger) error {
|
||||
// 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
|
||||
// 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).
|
||||
//
|
||||
// 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."+
|
||||
"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."+
|
||||
"Please raise an issue if you expect that this will cause issues for your workflow.", pc.Name)
|
||||
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) {
|
||||
|
@ -300,7 +300,7 @@ func kubeAPIServerStatefulSet(pg *tsapi.ProxyGroup, namespace, image string) (*a
|
||||
ServiceAccountName: pgServiceAccountName(pg),
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "k8s-proxy",
|
||||
Name: mainContainerName,
|
||||
Image: image,
|
||||
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) {
|
||||
pcLEStaging := &tsapi.ProxyClass{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
|
@ -102,6 +102,8 @@ const (
|
||||
defaultLocalAddrPort = 9002 // metrics and health check port
|
||||
|
||||
letsEncryptStagingEndpoint = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
|
||||
mainContainerName = "tailscale"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -903,7 +905,7 @@ func enableEndpoints(ss *appsv1.StatefulSet, metrics, debug 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
|
||||
|
@ -157,7 +157,7 @@ _Appears in:_
|
||||
| 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. | | |
|
||||
| `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 /> |
|
||||
| `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 | | |
|
||||
@ -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 | | |
|
||||
| `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. | | |
|
||||
| `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 | | |
|
||||
| `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 | | |
|
||||
|
@ -264,6 +264,7 @@ type Pod struct {
|
||||
// +optional
|
||||
TailscaleContainer *Container `json:"tailscaleContainer,omitempty"`
|
||||
// Configuration for the proxy init container that enables forwarding.
|
||||
// Not valid to apply to ProxyGroups of type "kube-apiserver".
|
||||
// +optional
|
||||
TailscaleInitContainer *Container `json:"tailscaleInitContainer,omitempty"`
|
||||
// Proxy Pod's security context.
|
||||
@ -364,12 +365,21 @@ type Container struct {
|
||||
// the future.
|
||||
// +optional
|
||||
Env []Env `json:"env,omitempty"`
|
||||
// Container image name. By default images are pulled from
|
||||
// docker.io/tailscale/tailscale, but the official images are also
|
||||
// available at ghcr.io/tailscale/tailscale. 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.
|
||||
// Container image name. By default images are pulled from docker.io/tailscale,
|
||||
// but the official images are also available at ghcr.io/tailscale.
|
||||
//
|
||||
// For all uses except on ProxyGroups of type "kube-apiserver", this image must
|
||||
// be either tailscale/tailscale, or an equivalent mirror of that image.
|
||||
// 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
|
||||
// +optional
|
||||
Image string `json:"image,omitempty"`
|
||||
|
Loading…
x
Reference in New Issue
Block a user