cmd/k8s-operator: generate static kube manifests from the Helm chart. (#10436)

* cmd/k8s-operator: generate static manifests from Helm charts

This is done to ensure that there is a single source of truth
for the operator kube manifests.
Also adds linux node selector to the static manifests as
this was added as a default to the Helm chart.

Static manifests can now be generated by running
`go generate tailscale.com/cmd/k8s-operator`.

Updates tailscale/tailscale#9222

Signed-off-by: Irbe Krumina <irbe@tailscale.com>
pull/10307/head
Irbe Krumina 12 months ago committed by GitHub
parent 263e01c47b
commit 49fd0a62c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -22,3 +22,9 @@ jobs:
eval `./tool/go run ./cmd/mkversion` eval `./tool/go run ./cmd/mkversion`
./tool/helm package --app-version="${VERSION_SHORT}" --version=${VERSION_SHORT} './cmd/k8s-operator/deploy/chart' ./tool/helm package --app-version="${VERSION_SHORT}" --version=${VERSION_SHORT} './cmd/k8s-operator/deploy/chart'
./tool/helm lint "tailscale-operator-${VERSION_SHORT}.tgz" ./tool/helm lint "tailscale-operator-${VERSION_SHORT}.tgz"
- name: Verify that static manifests are up to date
run: |
./tool/go generate tailscale.com/cmd/k8s-operator
echo
echo
git diff --name-only --exit-code || (echo "Static manifests for Tailscale Kubernetes operator are out of date. Please run 'go generate tailscale.com/cmd/k8s-operator' and commit the diff."; exit 1)

@ -429,7 +429,7 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: check that 'go generate' is clean - name: check that 'go generate' is clean
run: | run: |
pkgs=$(./tool/go list ./... | grep -v dnsfallback) pkgs=$(./tool/go list ./... | grep -Ev 'dnsfallback|k8s-operator')
./tool/go generate $pkgs ./tool/go generate $pkgs
echo echo
echo echo

@ -0,0 +1,12 @@
# Tailscale Kubernetes operator deployment manifests
./cmd/k8s-operator/deploy contain various Tailscale Kubernetes operator deployment manifests.
## Helm chart
`./cmd/k8s-operator/deploy/chart` contains Tailscale operator Helm chart templates.
The chart templates are also used to generate the static manifest, so developers must ensure that any changes applied to the chart have been propagated to the static manifest by running `go generate tailscale.com/cmd/k8s-operator`
## Static manifests
`./cmd/k8s-operator/deploy/manifests/operator.yaml` is a static manifest for the operator generated from the Helm chart templates for the operator.

@ -7,155 +7,172 @@ metadata:
name: tailscale name: tailscale
--- ---
apiVersion: v1 apiVersion: v1
kind: ServiceAccount kind: Secret
metadata:
name: proxies
namespace: tailscale
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: metadata:
name: proxies name: operator-oauth
namespace: tailscale namespace: tailscale
rules: stringData:
- apiGroups: [""] client_id: # SET CLIENT ID HERE
resources: ["secrets"] client_secret: # SET CLIENT SECRET HERE
verbs: ["*"]
--- ---
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: v1
kind: RoleBinding kind: ServiceAccount
metadata: metadata:
name: proxies name: operator
namespace: tailscale namespace: tailscale
subjects:
- kind: ServiceAccount
name: proxies
namespace: tailscale
roleRef:
kind: Role
name: proxies
apiGroup: rbac.authorization.k8s.io
--- ---
apiVersion: v1 apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
metadata: metadata:
name: operator name: proxies
namespace: tailscale namespace: tailscale
--- ---
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole kind: ClusterRole
metadata: metadata:
name: tailscale-operator name: tailscale-operator
rules: rules:
- apiGroups: [""] - apiGroups:
resources: ["events", "services", "services/status"] - ""
verbs: ["*"] resources:
- apiGroups: ["networking.k8s.io"] - events
resources: ["ingresses", "ingresses/status"] - services
verbs: ["*"] - services/status
verbs:
- '*'
- apiGroups:
- networking.k8s.io
resources:
- ingresses
- ingresses/status
verbs:
- '*'
--- ---
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding kind: ClusterRoleBinding
metadata: metadata:
name: tailscale-operator name: tailscale-operator
subjects:
- kind: ServiceAccount
name: operator
namespace: tailscale
roleRef: roleRef:
kind: ClusterRole apiGroup: rbac.authorization.k8s.io
name: tailscale-operator kind: ClusterRole
apiGroup: rbac.authorization.k8s.io name: tailscale-operator
subjects:
- kind: ServiceAccount
name: operator
namespace: tailscale
--- ---
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: Role kind: Role
metadata: metadata:
name: operator name: operator
namespace: tailscale namespace: tailscale
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- '*'
- apiGroups:
- apps
resources:
- statefulsets
verbs:
- '*'
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: proxies
namespace: tailscale
rules: rules:
- apiGroups: [""] - apiGroups:
resources: ["secrets"] - ""
verbs: ["*"] resources:
- apiGroups: ["apps"] - secrets
resources: ["statefulsets"] verbs:
verbs: ["*"] - '*'
--- ---
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding kind: RoleBinding
metadata: metadata:
name: operator name: operator
namespace: tailscale namespace: tailscale
subjects:
- kind: ServiceAccount
name: operator
namespace: tailscale
roleRef: roleRef:
kind: Role apiGroup: rbac.authorization.k8s.io
name: operator kind: Role
apiGroup: rbac.authorization.k8s.io name: operator
subjects:
- kind: ServiceAccount
name: operator
namespace: tailscale
--- ---
apiVersion: v1 apiVersion: rbac.authorization.k8s.io/v1
kind: Secret kind: RoleBinding
metadata: metadata:
name: operator-oauth name: proxies
namespace: tailscale namespace: tailscale
stringData: roleRef:
client_id: # SET CLIENT ID HERE apiGroup: rbac.authorization.k8s.io
client_secret: # SET CLIENT SECRET HERE kind: Role
name: proxies
subjects:
- kind: ServiceAccount
name: proxies
namespace: tailscale
--- ---
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: operator name: operator
namespace: tailscale namespace: tailscale
spec: spec:
replicas: 1 replicas: 1
strategy: selector:
type: Recreate matchLabels:
selector: app: operator
matchLabels: strategy:
app: operator type: Recreate
template: template:
metadata: metadata:
labels: labels:
app: operator app: operator
spec: spec:
serviceAccountName: operator containers:
volumes: - env:
- name: oauth - name: OPERATOR_HOSTNAME
secret: value: tailscale-operator
secretName: operator-oauth - name: OPERATOR_SECRET
containers: value: operator
- name: operator - name: OPERATOR_LOGGING
image: tailscale/k8s-operator:unstable value: info
resources: - name: OPERATOR_NAMESPACE
requests: valueFrom:
cpu: 500m fieldRef:
memory: 100Mi fieldPath: metadata.namespace
env: - name: CLIENT_ID_FILE
- name: OPERATOR_HOSTNAME value: /oauth/client_id
value: tailscale-operator - name: CLIENT_SECRET_FILE
- name: OPERATOR_SECRET value: /oauth/client_secret
value: operator - name: PROXY_IMAGE
- name: OPERATOR_LOGGING value: tailscale/tailscale:unstable
value: info - name: PROXY_TAGS
- name: OPERATOR_NAMESPACE value: tag:k8s
valueFrom: - name: APISERVER_PROXY
fieldRef: value: "false"
fieldPath: metadata.namespace - name: PROXY_FIREWALL_MODE
- name: CLIENT_ID_FILE value: auto
value: /oauth/client_id image: tailscale/k8s-operator:unstable
- name: CLIENT_SECRET_FILE imagePullPolicy: Always
value: /oauth/client_secret name: operator
- name: PROXY_IMAGE volumeMounts:
value: tailscale/tailscale:unstable - mountPath: /oauth
- name: PROXY_TAGS name: oauth
value: tag:k8s readOnly: true
- name: APISERVER_PROXY nodeSelector:
value: "false" kubernetes.io/os: linux
- name: PROXY_FIREWALL_MODE serviceAccountName: operator
value: auto volumes:
volumeMounts: - name: oauth
- name: oauth secret:
mountPath: /oauth secretName: operator-oauth
readOnly: true

@ -0,0 +1,3 @@
# Copyright (c) Tailscale Inc & AUTHORS
# SPDX-License-Identifier: BSD-3-Clause

@ -0,0 +1,5 @@
apiVersion: v1
kind: Namespace
metadata:
name: tailscale
---

@ -0,0 +1,9 @@
apiVersion: v1
kind: Secret
metadata:
name: operator-oauth
namespace: tailscale
stringData:
client_id: # SET CLIENT ID HERE
client_secret: # SET CLIENT SECRET HERE
---

@ -0,0 +1,74 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !plan9
package main
import (
"bytes"
"fmt"
"io"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"gopkg.in/yaml.v3"
)
func main() {
repoRoot := "../../"
cmd := exec.Command("./tool/helm", "template", "operator", "./cmd/k8s-operator/deploy/chart",
"--namespace=tailscale")
cmd.Dir = repoRoot
var out bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Fatalf("error templating helm manifests: %v", err)
}
var final bytes.Buffer
templatePath := filepath.Join(repoRoot, "cmd/k8s-operator/deploy/manifests/templates")
fileInfos, err := os.ReadDir(templatePath)
if err != nil {
log.Fatalf("error reading templates: %v", err)
}
for _, fi := range fileInfos {
templateBytes, err := os.ReadFile(filepath.Join(templatePath, fi.Name()))
if err != nil {
log.Fatalf("error reading template: %v", err)
}
final.Write(templateBytes)
}
decoder := yaml.NewDecoder(&out)
for {
var document any
err := decoder.Decode(&document)
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("failed read from input data: %v", err)
}
bytes, err := yaml.Marshal(document)
if err != nil {
log.Fatalf("failed to marshal YAML document: %v", err)
}
if strings.TrimSpace(string(bytes)) == "null" {
continue
}
if _, err = final.Write(bytes); err != nil {
log.Fatalf("error marshaling yaml: %v", err)
}
fmt.Fprint(&final, "---\n")
}
finalString, _ := strings.CutSuffix(final.String(), "---\n")
if err := os.WriteFile(filepath.Join(repoRoot, "cmd/k8s-operator/deploy/manifests/operator.yaml"), []byte(finalString), 0664); err != nil {
log.Fatalf("error writing new file: %v", err)
}
}

@ -42,6 +42,8 @@ import (
"tailscale.com/version" "tailscale.com/version"
) )
//go:generate go run tailscale.com/cmd/k8s-operator/generate
func main() { func main() {
// Required to use our client API. We're fine with the instability since the // Required to use our client API. We're fine with the instability since the
// client lives in the same repo as this code. // client lives in the same repo as this code.

@ -358,7 +358,7 @@ require (
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1
howett.net/plist v1.0.0 // indirect howett.net/plist v1.0.0 // indirect
k8s.io/apiextensions-apiserver v0.28.2 // indirect k8s.io/apiextensions-apiserver v0.28.2 // indirect
k8s.io/component-base v0.28.2 // indirect k8s.io/component-base v0.28.2 // indirect

Loading…
Cancel
Save