From 3a6d3f1a5b744fede5418f04aba4a710d72044ca Mon Sep 17 00:00:00 2001 From: Irbe Krumina Date: Fri, 7 Jun 2024 16:18:44 +0100 Subject: [PATCH] cmd/k8s-operator,k8s-operator,go.{mod,sum}: make individual proxy images/image pull policies configurable (#11928) cmd/k8s-operator,k8s-operator,go.{mod,sum}: make individual proxy images/image pull policies configurable Allow to configure images and image pull policies for individual proxies via ProxyClass.Spec.StatefulSet.Pod.{TailscaleContainer,TailscaleInitContainer}.Image, and ProxyClass.Spec.StatefulSet.Pod.{TailscaleContainer,TailscaleInitContainer}.ImagePullPolicy fields. Document that we have images in ghcr.io on the relevant Helm chart fields. Updates tailscale/tailscale#11675 Signed-off-by: Irbe Krumina --- cmd/k8s-operator/deploy/chart/values.yaml | 2 + .../crds/tailscale.com_proxyclasses.yaml | 20 ++++++++ .../deploy/examples/proxyclass.yaml | 6 +++ .../deploy/manifests/operator.yaml | 20 ++++++++ cmd/k8s-operator/proxyclass.go | 15 ++++++ cmd/k8s-operator/proxyclass_test.go | 50 ++++++++++++++++--- cmd/k8s-operator/sts.go | 6 +++ cmd/k8s-operator/sts_test.go | 18 +++++-- go.mod | 1 + go.sum | 2 + k8s-operator/api.md | 32 ++++++++++++ .../apis/v1alpha1/types_proxyclass.go | 48 +++++++++++------- .../apis/v1alpha1/zz_generated.deepcopy.go | 12 ++--- 13 files changed, 200 insertions(+), 32 deletions(-) diff --git a/cmd/k8s-operator/deploy/chart/values.yaml b/cmd/k8s-operator/deploy/chart/values.yaml index 9862ed5f7..c0d33acc9 100644 --- a/cmd/k8s-operator/deploy/chart/values.yaml +++ b/cmd/k8s-operator/deploy/chart/values.yaml @@ -23,6 +23,7 @@ operatorConfig: - "tag:k8s-operator" image: + # Repository defaults to DockerHub, but images are also synced to ghcr.io/tailscale/k8s-operator. repository: tailscale/k8s-operator # Digest will be prioritized over tag. If neither are set appVersion will be # used. @@ -57,6 +58,7 @@ operatorConfig: # https://tailscale.com/kb/1236/kubernetes-operator#cluster-resource-customization-using-proxyclass-custom-resource proxyConfig: image: + # Repository defaults to DockerHub, but images are also synced to ghcr.io/tailscale/tailscale. repository: tailscale/tailscale # Digest will be prioritized over tag. If neither are set appVersion will be # used. diff --git a/cmd/k8s-operator/deploy/crds/tailscale.com_proxyclasses.yaml b/cmd/k8s-operator/deploy/crds/tailscale.com_proxyclasses.yaml index 9d1d587a1..a2b8195a8 100644 --- a/cmd/k8s-operator/deploy/crds/tailscale.com_proxyclasses.yaml +++ b/cmd/k8s-operator/deploy/crds/tailscale.com_proxyclasses.yaml @@ -721,6 +721,16 @@ spec: value: description: 'Variable references $(VAR_NAME) are expanded using the previously defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".' 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. https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#image + type: string + imagePullPolicy: + description: Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always. https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#image + type: string + enum: + - Always + - Never + - IfNotPresent resources: description: Container resource requirements. By default Tailscale Kubernetes operator does not apply any resource requirements. The amount of resources required wil depend on the amount of resources the operator needs to parse, usage patterns and cluster size. https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#resources type: object @@ -864,6 +874,16 @@ spec: value: description: 'Variable references $(VAR_NAME) are expanded using the previously defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".' 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. https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#image + type: string + imagePullPolicy: + description: Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always. https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#image + type: string + enum: + - Always + - Never + - IfNotPresent resources: description: Container resource requirements. By default Tailscale Kubernetes operator does not apply any resource requirements. The amount of resources required wil depend on the amount of resources the operator needs to parse, usage patterns and cluster size. https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#resources type: object diff --git a/cmd/k8s-operator/deploy/examples/proxyclass.yaml b/cmd/k8s-operator/deploy/examples/proxyclass.yaml index 3f0d2afa5..b36e9ac1d 100644 --- a/cmd/k8s-operator/deploy/examples/proxyclass.yaml +++ b/cmd/k8s-operator/deploy/examples/proxyclass.yaml @@ -15,3 +15,9 @@ spec: kubernetes.io/os: "linux" imagePullSecrets: - name: "foo" + tailscaleContainer: + image: "ghcr.io/tailscale/tailscale:v1.64" + imagePullPolicy: IfNotPresent + tailscaleInitContainer: + image: "ghcr.io/tailscale/tailscale:v1.64" + imagePullPolicy: IfNotPresent diff --git a/cmd/k8s-operator/deploy/manifests/operator.yaml b/cmd/k8s-operator/deploy/manifests/operator.yaml index 78553542f..abbc6d242 100644 --- a/cmd/k8s-operator/deploy/manifests/operator.yaml +++ b/cmd/k8s-operator/deploy/manifests/operator.yaml @@ -970,6 +970,16 @@ spec: - name type: object 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. https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#image + type: string + imagePullPolicy: + description: Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always. https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#image + enum: + - Always + - Never + - IfNotPresent + type: string resources: description: Container resource requirements. By default Tailscale Kubernetes operator does not apply any resource requirements. The amount of resources required wil depend on the amount of resources the operator needs to parse, usage patterns and cluster size. https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#resources properties: @@ -1113,6 +1123,16 @@ spec: - name type: object 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. https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#image + type: string + imagePullPolicy: + description: Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always. https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#image + enum: + - Always + - Never + - IfNotPresent + type: string resources: description: Container resource requirements. By default Tailscale Kubernetes operator does not apply any resource requirements. The amount of resources required wil depend on the amount of resources the operator needs to parse, usage patterns and cluster size. https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#resources properties: diff --git a/cmd/k8s-operator/proxyclass.go b/cmd/k8s-operator/proxyclass.go index a76a67a89..bb1fa668e 100644 --- a/cmd/k8s-operator/proxyclass.go +++ b/cmd/k8s-operator/proxyclass.go @@ -10,6 +10,7 @@ "fmt" "strings" + dockerref "github.com/distribution/reference" "go.uber.org/zap" corev1 "k8s.io/api/core/v1" apiequality "k8s.io/apimachinery/pkg/api/equality" @@ -111,6 +112,20 @@ func (a *ProxyClassReconciler) validate(pc *tsapi.ProxyClass) (violations field. a.recorder.Event(pc, corev1.EventTypeWarning, reasonCustomTSEnvVar, fmt.Sprintf(messageCustomTSEnvVar, string(e.Name), "tailscale")) } } + if tc.Image != "" { + // Same validation as used by kubelet https://github.com/kubernetes/kubernetes/blob/release-1.30/pkg/kubelet/images/image_manager.go#L212 + if _, err := dockerref.ParseNormalizedNamed(tc.Image); err != nil { + violations = append(violations, field.TypeInvalid(field.NewPath("spec", "statefulSet", "pod", "tailscaleContainer", "image"), tc.Image, err.Error())) + } + } + } + if tc := pod.TailscaleInitContainer; tc != nil { + if tc.Image != "" { + // Same validation as used by kubelet https://github.com/kubernetes/kubernetes/blob/release-1.30/pkg/kubelet/images/image_manager.go#L212 + if _, err := dockerref.ParseNormalizedNamed(tc.Image); err != nil { + violations = append(violations, field.TypeInvalid(field.NewPath("spec", "statefulSet", "pod", "tailscaleInitContainer", "image"), tc.Image, err.Error())) + } + } } } } diff --git a/cmd/k8s-operator/proxyclass_test.go b/cmd/k8s-operator/proxyclass_test.go index 4cc4f7844..e26563a16 100644 --- a/cmd/k8s-operator/proxyclass_test.go +++ b/cmd/k8s-operator/proxyclass_test.go @@ -36,9 +36,13 @@ func TestProxyClass(t *testing.T) { Labels: map[string]string{"foo": "bar", "xyz1234": "abc567"}, Annotations: map[string]string{"foo.io/bar": "{'key': 'val1232'}"}, Pod: &tsapi.Pod{ - Labels: map[string]string{"foo": "bar", "xyz1234": "abc567"}, - Annotations: map[string]string{"foo.io/bar": "{'key': 'val1232'}"}, - TailscaleContainer: &tsapi.Container{Env: []tsapi.Env{{Name: "FOO", Value: "BAR"}}}, + Labels: map[string]string{"foo": "bar", "xyz1234": "abc567"}, + Annotations: map[string]string{"foo.io/bar": "{'key': 'val1232'}"}, + TailscaleContainer: &tsapi.Container{ + Env: []tsapi.Env{{Name: "FOO", Value: "BAR"}}, + ImagePullPolicy: "IfNotPresent", + Image: "ghcr.my-repo/tailscale:v0.01testsomething", + }, }, }, }, @@ -73,7 +77,7 @@ func TestProxyClass(t *testing.T) { expectEqual(t, fc, pc, nil) - // 2. An invalid ProxyClass resource gets its status updated to Invalid. + // 2. A ProxyClass resource with invalid labels gets its status updated to Invalid with an error message. pc.Spec.StatefulSet.Labels["foo"] = "?!someVal" mustUpdate(t, fc, "", "test", func(proxyClass *tsapi.ProxyClass) { proxyClass.Spec.StatefulSet.Labels = pc.Spec.StatefulSet.Labels @@ -85,9 +89,43 @@ func TestProxyClass(t *testing.T) { expectedEvent := "Warning ProxyClassInvalid ProxyClass is not valid: .spec.statefulSet.labels: Invalid value: \"?!someVal\": a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyValue', or 'my_value', or '12345', regex used for validation is '(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?')" expectEvents(t, fr, []string{expectedEvent}) - // 2. An valid ProxyClass but with a Tailscale env vars set results in warning events. + // 3. A ProxyClass resource with invalid image reference gets it status updated to Invalid with an error message. + pc.Spec.StatefulSet.Labels = nil + pc.Spec.StatefulSet.Pod.TailscaleContainer.Image = "FOO bar" mustUpdate(t, fc, "", "test", func(proxyClass *tsapi.ProxyClass) { - proxyClass.Spec.StatefulSet.Labels = nil // unset invalid labels from the previous test + proxyClass.Spec.StatefulSet.Labels = nil + proxyClass.Spec.StatefulSet.Pod.TailscaleContainer.Image = pc.Spec.StatefulSet.Pod.TailscaleContainer.Image + }) + expectReconciled(t, pcr, "", "test") + msg = `ProxyClass is not valid: spec.statefulSet.pod.tailscaleContainer.image: Invalid value: "FOO bar": invalid reference format: repository name (library/FOO bar) must be lowercase` + tsoperator.SetProxyClassCondition(pc, tsapi.ProxyClassready, metav1.ConditionFalse, reasonProxyClassInvalid, msg, 0, cl, zl.Sugar()) + expectEqual(t, fc, pc, nil) + expectedEvent = `Warning ProxyClassInvalid ProxyClass is not valid: spec.statefulSet.pod.tailscaleContainer.image: Invalid value: "FOO bar": invalid reference format: repository name (library/FOO bar) must be lowercase` + expectEvents(t, fr, []string{expectedEvent}) + + // 4. A ProxyClass resource with invalid init container image reference gets it status updated to Invalid with an error message. + pc.Spec.StatefulSet.Labels = nil + pc.Spec.StatefulSet.Pod.TailscaleContainer.Image = "" + pc.Spec.StatefulSet.Pod.TailscaleInitContainer = &tsapi.Container{ + Image: "FOO bar", + } + mustUpdate(t, fc, "", "test", func(proxyClass *tsapi.ProxyClass) { + proxyClass.Spec.StatefulSet.Pod.TailscaleContainer.Image = pc.Spec.StatefulSet.Pod.TailscaleContainer.Image + proxyClass.Spec.StatefulSet.Pod.TailscaleInitContainer = &tsapi.Container{ + Image: pc.Spec.StatefulSet.Pod.TailscaleInitContainer.Image, + } + }) + expectReconciled(t, pcr, "", "test") + msg = `ProxyClass is not valid: spec.statefulSet.pod.tailscaleInitContainer.image: Invalid value: "FOO bar": invalid reference format: repository name (library/FOO bar) must be lowercase` + tsoperator.SetProxyClassCondition(pc, tsapi.ProxyClassready, metav1.ConditionFalse, reasonProxyClassInvalid, msg, 0, cl, zl.Sugar()) + expectEqual(t, fc, pc, nil) + expectedEvent = `Warning ProxyClassInvalid ProxyClass is not valid: spec.statefulSet.pod.tailscaleInitContainer.image: Invalid value: "FOO bar": invalid reference format: repository name (library/FOO bar) must be lowercase` + expectEvents(t, fr, []string{expectedEvent}) + + // 5. An valid ProxyClass but with a Tailscale env vars set results in warning events. + pc.Spec.StatefulSet.Pod.TailscaleInitContainer.Image = "" // unset previous test + mustUpdate(t, fc, "", "test", func(proxyClass *tsapi.ProxyClass) { + proxyClass.Spec.StatefulSet.Pod.TailscaleInitContainer.Image = pc.Spec.StatefulSet.Pod.TailscaleInitContainer.Image proxyClass.Spec.StatefulSet.Pod.TailscaleContainer.Env = []tsapi.Env{{Name: "TS_USERSPACE", Value: "true"}, {Name: "EXPERIMENTAL_TS_CONFIGFILE_PATH"}, {Name: "EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS"}} }) expectedEvents := []string{"Warning CustomTSEnvVar ProxyClass overrides the default value for TS_USERSPACE env var for tailscale container. Running with custom values for Tailscale env vars is not recommended and might break in the future.", diff --git a/cmd/k8s-operator/sts.go b/cmd/k8s-operator/sts.go index d2527da6f..0104df4ea 100644 --- a/cmd/k8s-operator/sts.go +++ b/cmd/k8s-operator/sts.go @@ -690,6 +690,12 @@ func applyProxyClassToStatefulSet(pc *tsapi.ProxyClass, ss *appsv1.StatefulSet, // in the env var list overrides an earlier one. base.Env = append(base.Env, corev1.EnvVar{Name: string(e.Name), Value: e.Value}) } + if overlay.Image != "" { + base.Image = overlay.Image + } + if overlay.ImagePullPolicy != "" { + base.ImagePullPolicy = overlay.ImagePullPolicy + } return base } for i, c := range ss.Spec.Template.Spec.Containers { diff --git a/cmd/k8s-operator/sts_test.go b/cmd/k8s-operator/sts_test.go index cca0167ce..b2b2c8b93 100644 --- a/cmd/k8s-operator/sts_test.go +++ b/cmd/k8s-operator/sts_test.go @@ -81,7 +81,9 @@ func Test_applyProxyClassToStatefulSet(t *testing.T) { Limits: corev1.ResourceList{corev1.ResourceCPU: resource.MustParse("1000m"), corev1.ResourceMemory: resource.MustParse("128Mi")}, Requests: corev1.ResourceList{corev1.ResourceCPU: resource.MustParse("500m"), corev1.ResourceMemory: resource.MustParse("64Mi")}, }, - Env: []tsapi.Env{{Name: "foo", Value: "bar"}, {Name: "TS_USERSPACE", Value: "true"}, {Name: "bar"}}, + Env: []tsapi.Env{{Name: "foo", Value: "bar"}, {Name: "TS_USERSPACE", Value: "true"}, {Name: "bar"}}, + ImagePullPolicy: "IfNotPresent", + Image: "ghcr.io/my-repo/tailscale:v0.01testsomething", }, TailscaleInitContainer: &tsapi.Container{ SecurityContext: &corev1.SecurityContext{ @@ -92,7 +94,9 @@ func Test_applyProxyClassToStatefulSet(t *testing.T) { Limits: corev1.ResourceList{corev1.ResourceCPU: resource.MustParse("1000m"), corev1.ResourceMemory: resource.MustParse("128Mi")}, Requests: corev1.ResourceList{corev1.ResourceCPU: resource.MustParse("500m"), corev1.ResourceMemory: resource.MustParse("64Mi")}, }, - Env: []tsapi.Env{{Name: "foo", Value: "bar"}, {Name: "TS_USERSPACE", Value: "true"}, {Name: "bar"}}, + Env: []tsapi.Env{{Name: "foo", Value: "bar"}, {Name: "TS_USERSPACE", Value: "true"}, {Name: "bar"}}, + ImagePullPolicy: "IfNotPresent", + Image: "ghcr.io/my-repo/tailscale:v0.01testsomething", }, }, }, @@ -135,10 +139,12 @@ func Test_applyProxyClassToStatefulSet(t *testing.T) { env := []corev1.EnvVar{{Name: "TS_HOSTNAME", Value: "nginx"}} userspaceProxySS.Labels = labels userspaceProxySS.Annotations = annots + userspaceProxySS.Spec.Template.Spec.Containers[0].Image = "tailscale/tailscale:v0.0.1" userspaceProxySS.Spec.Template.Spec.Containers[0].Env = env nonUserspaceProxySS.ObjectMeta.Labels = labels nonUserspaceProxySS.ObjectMeta.Annotations = annots nonUserspaceProxySS.Spec.Template.Spec.Containers[0].Env = env + nonUserspaceProxySS.Spec.Template.Spec.InitContainers[0].Image = "tailscale/tailscale:v0.0.1" // 1. Test that a ProxyClass with all fields set gets correctly applied // to a Statefulset built from non-userspace proxy template. @@ -159,6 +165,10 @@ func Test_applyProxyClassToStatefulSet(t *testing.T) { wantSS.Spec.Template.Spec.InitContainers[0].Resources = proxyClassAllOpts.Spec.StatefulSet.Pod.TailscaleInitContainer.Resources wantSS.Spec.Template.Spec.InitContainers[0].Env = append(wantSS.Spec.Template.Spec.InitContainers[0].Env, []corev1.EnvVar{{Name: "foo", Value: "bar"}, {Name: "TS_USERSPACE", Value: "true"}, {Name: "bar"}}...) wantSS.Spec.Template.Spec.Containers[0].Env = append(wantSS.Spec.Template.Spec.Containers[0].Env, []corev1.EnvVar{{Name: "foo", Value: "bar"}, {Name: "TS_USERSPACE", Value: "true"}, {Name: "bar"}}...) + wantSS.Spec.Template.Spec.Containers[0].Image = "ghcr.io/my-repo/tailscale:v0.01testsomething" + wantSS.Spec.Template.Spec.Containers[0].ImagePullPolicy = "IfNotPresent" + wantSS.Spec.Template.Spec.InitContainers[0].Image = "ghcr.io/my-repo/tailscale:v0.01testsomething" + wantSS.Spec.Template.Spec.InitContainers[0].ImagePullPolicy = "IfNotPresent" gotSS := applyProxyClassToStatefulSet(proxyClassAllOpts, nonUserspaceProxySS.DeepCopy(), new(tailscaleSTSConfig), zl.Sugar()) if diff := cmp.Diff(gotSS, wantSS); diff != "" { @@ -194,9 +204,11 @@ func Test_applyProxyClassToStatefulSet(t *testing.T) { wantSS.Spec.Template.Spec.Containers[0].SecurityContext = proxyClassAllOpts.Spec.StatefulSet.Pod.TailscaleContainer.SecurityContext wantSS.Spec.Template.Spec.Containers[0].Resources = proxyClassAllOpts.Spec.StatefulSet.Pod.TailscaleContainer.Resources wantSS.Spec.Template.Spec.Containers[0].Env = append(wantSS.Spec.Template.Spec.Containers[0].Env, []corev1.EnvVar{{Name: "foo", Value: "bar"}, {Name: "TS_USERSPACE", Value: "true"}, {Name: "bar"}}...) + wantSS.Spec.Template.Spec.Containers[0].ImagePullPolicy = "IfNotPresent" + wantSS.Spec.Template.Spec.Containers[0].Image = "ghcr.io/my-repo/tailscale:v0.01testsomething" gotSS = applyProxyClassToStatefulSet(proxyClassAllOpts, userspaceProxySS.DeepCopy(), new(tailscaleSTSConfig), zl.Sugar()) if diff := cmp.Diff(gotSS, wantSS); diff != "" { - t.Fatalf("Unexpected result applying ProxyClass with custom labels and annotations to a StatefulSet for a userspace proxy (-got +want):\n%s", diff) + t.Fatalf("Unexpected result applying ProxyClass with all options to a StatefulSet for a userspace proxy (-got +want):\n%s", diff) } // 4. Test that a ProxyClass with custom labels and annotations gets correctly applied diff --git a/go.mod b/go.mod index a06906388..8b88c7609 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,7 @@ require ( github.com/dave/patsy v0.0.0-20210517141501-957256f50cba github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e + github.com/distribution/reference v0.6.0 github.com/djherbis/times v1.6.0 github.com/dsnet/try v0.0.3 github.com/evanw/esbuild v0.19.11 diff --git a/go.sum b/go.sum index 66f8f5b2b..9ab32f028 100644 --- a/go.sum +++ b/go.sum @@ -251,6 +251,8 @@ github.com/denis-tingaikin/go-header v0.4.3 h1:tEaZKAlqql6SKCY++utLmkPLd6K8IBM20 github.com/denis-tingaikin/go-header v0.4.3/go.mod h1:0wOCWuN71D5qIgE2nz9KrKmuYBAC2Mra5RassOIQ2/c= github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q= github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= github.com/docker/cli v25.0.0+incompatible h1:zaimaQdnX7fYWFqzN88exE9LDEvRslexpFowZBX6GoQ= diff --git a/k8s-operator/api.md b/k8s-operator/api.md index 4b1d59178..9ec4641b2 100644 --- a/k8s-operator/api.md +++ b/k8s-operator/api.md @@ -2446,6 +2446,22 @@ Configuration for the proxy container running tailscale. List of environment variables to set in the container. https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#environment-variables Note that environment variables provided here will take precedence over Tailscale-specific environment variables set by the operator, however running proxies with custom values for Tailscale environment variables (i.e TS_USERSPACE) is not recommended and might break in the future.
false + + image + string + + 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. https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#image
+ + false + + imagePullPolicy + enum + + Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always. https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#image
+
+ Enum: Always, Never, IfNotPresent
+ + false resources object @@ -2857,6 +2873,22 @@ Configuration for the proxy init container that enables forwarding. List of environment variables to set in the container. https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#environment-variables Note that environment variables provided here will take precedence over Tailscale-specific environment variables set by the operator, however running proxies with custom values for Tailscale environment variables (i.e TS_USERSPACE) is not recommended and might break in the future.
false + + image + string + + 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. https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#image
+ + false + + imagePullPolicy + enum + + Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always. https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#image
+
+ Enum: Always, Never, IfNotPresent
+ + false resources object diff --git a/k8s-operator/apis/v1alpha1/types_proxyclass.go b/k8s-operator/apis/v1alpha1/types_proxyclass.go index 82db0f69e..bfc2a3f6c 100644 --- a/k8s-operator/apis/v1alpha1/types_proxyclass.go +++ b/k8s-operator/apis/v1alpha1/types_proxyclass.go @@ -151,23 +151,6 @@ type Metrics struct { } type Container struct { - // Container security context. - // Security context specified here will override the security context by the operator. - // By default the operator: - // - sets 'privileged: true' for the init container - // - set NET_ADMIN capability for tailscale container for proxies that - // are created for Services or Connector. - // https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#security-context - // +optional - SecurityContext *corev1.SecurityContext `json:"securityContext,omitempty"` - // Container resource requirements. - // By default Tailscale Kubernetes operator does not apply any resource - // requirements. The amount of resources required wil depend on the - // amount of resources the operator needs to parse, usage patterns and - // cluster size. - // https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#resources - // +optional - Resources corev1.ResourceRequirements `json:"resources,omitempty"` // List of environment variables to set in the container. // https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#environment-variables // Note that environment variables provided here will take precedence @@ -177,6 +160,37 @@ 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. + // https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#image + // +optional + Image string `json:"image,omitempty"` + // Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always. + // https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#image + // +kubebuilder:validation:Enum=Always;Never;IfNotPresent + // +optional + ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty"` + // Container resource requirements. + // By default Tailscale Kubernetes operator does not apply any resource + // requirements. The amount of resources required wil depend on the + // amount of resources the operator needs to parse, usage patterns and + // cluster size. + // https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#resources + // +optional + Resources corev1.ResourceRequirements `json:"resources,omitempty"` + // Container security context. + // Security context specified here will override the security context by the operator. + // By default the operator: + // - sets 'privileged: true' for the init container + // - set NET_ADMIN capability for tailscale container for proxies that + // are created for Services or Connector. + // https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#security-context + // +optional + SecurityContext *corev1.SecurityContext `json:"securityContext,omitempty"` } type Env struct { diff --git a/k8s-operator/apis/v1alpha1/zz_generated.deepcopy.go b/k8s-operator/apis/v1alpha1/zz_generated.deepcopy.go index 3d5840ad2..8c88b1aab 100644 --- a/k8s-operator/apis/v1alpha1/zz_generated.deepcopy.go +++ b/k8s-operator/apis/v1alpha1/zz_generated.deepcopy.go @@ -140,17 +140,17 @@ func (in *ConnectorStatus) DeepCopy() *ConnectorStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Container) DeepCopyInto(out *Container) { *out = *in - if in.SecurityContext != nil { - in, out := &in.SecurityContext, &out.SecurityContext - *out = new(v1.SecurityContext) - (*in).DeepCopyInto(*out) - } - in.Resources.DeepCopyInto(&out.Resources) if in.Env != nil { in, out := &in.Env, &out.Env *out = make([]Env, len(*in)) copy(*out, *in) } + in.Resources.DeepCopyInto(&out.Resources) + if in.SecurityContext != nil { + in, out := &in.SecurityContext, &out.SecurityContext + *out = new(v1.SecurityContext) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Container.