From 717af088c166821e06dbeb96e76fb848e2a3e0e5 Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Mon, 8 Jun 2026 17:57:06 +0400 Subject: [PATCH 1/4] fix(ci): adapt previous CI workflow Signed-off-by: Andrey Kolkov --- .github/release-drafter.yml | 64 + .github/workflows/ci.yml | 4 +- .github/workflows/docker-publish.yml | 79 + .github/workflows/helm-publish.yml | 68 + .github/workflows/publish.yml | 31 - .github/workflows/release-assets.yml | 59 + .github/workflows/release-drafter.yml | 36 + .github/workflows/release-smoke.yml | 64 + .gitignore | 3 + Makefile | 39 + charts/etcd-operator/.helmignore | 6 + charts/etcd-operator/Chart.yaml | 11 + ...cd-operator.cozystack.io_etcdclusters.yaml | 3255 +++++++++++++++++ ...tcd-operator.cozystack.io_etcdmembers.yaml | 1662 +++++++++ ...d-operator.cozystack.io_etcdsnapshots.yaml | 235 ++ charts/etcd-operator/templates/_helpers.tpl | 56 + .../etcd-operator/templates/deployment.yaml | 107 + .../templates/metrics-service.yaml | 18 + charts/etcd-operator/templates/rbac.yaml | 128 + .../templates/serviceaccount.yaml | 13 + charts/etcd-operator/values.yaml | 73 + docs/installation.md | 39 +- hack/release-smoke.sh | 156 + 23 files changed, 6172 insertions(+), 34 deletions(-) create mode 100644 .github/release-drafter.yml create mode 100644 .github/workflows/docker-publish.yml create mode 100644 .github/workflows/helm-publish.yml delete mode 100644 .github/workflows/publish.yml create mode 100644 .github/workflows/release-assets.yml create mode 100644 .github/workflows/release-drafter.yml create mode 100644 .github/workflows/release-smoke.yml create mode 100644 charts/etcd-operator/.helmignore create mode 100644 charts/etcd-operator/Chart.yaml create mode 100644 charts/etcd-operator/crds/etcd-operator.cozystack.io_etcdclusters.yaml create mode 100644 charts/etcd-operator/crds/etcd-operator.cozystack.io_etcdmembers.yaml create mode 100644 charts/etcd-operator/crds/etcd-operator.cozystack.io_etcdsnapshots.yaml create mode 100644 charts/etcd-operator/templates/_helpers.tpl create mode 100644 charts/etcd-operator/templates/deployment.yaml create mode 100644 charts/etcd-operator/templates/metrics-service.yaml create mode 100644 charts/etcd-operator/templates/rbac.yaml create mode 100644 charts/etcd-operator/templates/serviceaccount.yaml create mode 100644 charts/etcd-operator/values.yaml create mode 100755 hack/release-smoke.sh diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 00000000..4d2e32a6 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,64 @@ +name-template: 'v$RESOLVED_VERSION' +tag-template: 'v$RESOLVED_VERSION' +categories: + - title: 'Features' + labels: + - 'feature' + - 'enhancement' + - title: 'Bug Fixes' + labels: + - 'bugfix' + - title: 'Maintenance' + labels: + - 'chore' + - 'dependencies' + - 'documentation' +change-template: '- $TITLE @$AUTHOR (#$NUMBER)' +change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. +version-resolver: + major: + labels: + - 'major' + minor: + labels: + - 'minor' + patch: + labels: + - 'patch' + default: patch +exclude-labels: + - 'skip-changelog' +autolabeler: + - label: 'api-change' + files: + - 'api/**' + - label: 'controllers' + files: + - 'controllers/**' + - 'internal/**' + - label: 'bugfix' + branch: + - '/fix\/.+/' + - '/bugfix\/.+/' + - label: 'feature' + branch: + - '/feature\/.+/' + - '/feat\/.+/' + - label: 'enhancement' + branch: + - '/enh\/.+/' + - label: 'chore' + branch: + - '/chore\/.+/' + - label: 'dependencies' + branch: + - '/deps\/.+/' + - '/renovate\/.+/' + - label: 'documentation' + files: + - '**/*.md' + branch: + - '/docs\/.+/' +template: | + ## Changes + $CHANGES diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b50150e..3628f3b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: CI on: pull_request: - branches: [ master ] + branches: [ main ] push: - branches: [ master ] + branches: [ main ] jobs: verify: diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 00000000..5d17a319 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,79 @@ +name: Docker publish + +# Tag-based image release. Pushing a semver tag (e.g. v0.5.0) builds the +# operator image multi-arch, pushes it to GHCR under this repo's name, and +# signs it with cosign. This is the same shape as the legacy v1alpha1 release +# process, retargeted at ghcr.io// via the built-in GITHUB_TOKEN +# (no Docker Hub secrets needed). +# +# Release order: push the tag FIRST (this builds ghcr.io/.../etcd-operator:), +# then publish the GitHub release for that tag — release-assets.yml renders the +# install manifests pointing at this image. + +on: + push: + tags: [ 'v*.*.*' ] + +env: + REGISTRY: ghcr.io + # github.repository is /, e.g. cozystack/etcd-operator + IMAGE_NAME: ${{ github.repository }} + +jobs: + build: + runs-on: ubuntu-22.04 + permissions: + contents: read + packages: write + # Needed for the keyless cosign identity challenge (sigstore/fulcio). + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install cosign + uses: sigstore/cosign-installer@v3.5.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3.3.0 + + - name: Log into registry ${{ env.REGISTRY }} + uses: docker/login-action@v3.2.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5.5.1 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + # Pin the published tag to the exact git ref (e.g. v0.5.0). This is + # the SAME source release-assets.yml uses for the IMG it bakes into + # the install manifest (its RELEASE_TAG is github.ref_name too), so + # the image that ships and the image the manifest references are + # provably identical. Don't rely on metadata-action's implicit + # default: it also emits a moving `latest` and its default tag set is + # easy to misread — explicit keeps the publish/manifest contract clear. + tags: | + type=raw,value=${{ github.ref_name }} + + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + platforms: linux/amd64,linux/arm64 + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Sign the published Docker image + env: + TAGS: ${{ steps.meta.outputs.tags }} + DIGEST: ${{ steps.build-and-push.outputs.digest }} + run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST} diff --git a/.github/workflows/helm-publish.yml b/.github/workflows/helm-publish.yml new file mode 100644 index 00000000..3d195eb0 --- /dev/null +++ b/.github/workflows/helm-publish.yml @@ -0,0 +1,68 @@ +name: Helm publish + +# Tag-based Helm chart release. Pushing a semver tag packages +# charts/etcd-operator and pushes it as an OCI chart to GHCR under the org's +# charts repo (ghcr.io//charts/etcd-operator), versioned from the tag. +# Same shape as the legacy v1alpha1 helm-publish, retargeted at this org and +# using the built-in GITHUB_TOKEN. +on: + push: + tags: [ 'v*.*.*' ] + +env: + REGISTRY: ghcr.io + CHARTS_REPOSITORY: ${{ github.repository_owner }}/charts + CHART_NAME: etcd-operator + +jobs: + build: + runs-on: ubuntu-22.04 + permissions: + contents: read + packages: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # make helm-sync regenerates the CRDs (controller-gen) and copies them + # into the chart, so the published package always carries CRDs matching + # the tagged API types — never a stale committed copy. + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + + - name: Install Helm + uses: azure/setup-helm@v4 + with: + version: 'v3.16.4' + + - name: Sync CRDs into the chart + run: make helm-sync + + - name: Resolve chart versions from tag + run: | + TAG=${{ github.ref_name }} + echo "RELEASE_TAG=${TAG}" >> $GITHUB_ENV + # Chart version is semver without the leading v; appVersion keeps it. + echo "RELEASE_TAG_TRIMMED_V=${TAG#v}" >> $GITHUB_ENV + + - name: Helm registry login + run: | + helm registry login \ + --username ${{ github.actor }} \ + --password ${{ secrets.GITHUB_TOKEN }} \ + ${{ env.REGISTRY }} + + - name: Package chart + working-directory: charts + run: | + helm package ${{ env.CHART_NAME }} \ + --version "${RELEASE_TAG_TRIMMED_V}" \ + --app-version "${RELEASE_TAG}" + + - name: Push chart + working-directory: charts + run: | + helm push "${{ env.CHART_NAME }}-${RELEASE_TAG_TRIMMED_V}.tgz" \ + "oci://${{ env.REGISTRY }}/${{ env.CHARTS_REPOSITORY }}" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index 54e0bc6e..00000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Build and Publish Docker Image - -on: - push: - branches: [ master ] - -jobs: - build-and-publish: - runs-on: ubuntu-latest - steps: - ## checks out our project source code - - uses: actions/checkout@v2 - - ## Builds our docker image! - - name: Build the Docker image - run: docker build . --file Dockerfile --tag lllamnyp/etcd-operator:$(date +%s) - - ## Publishes our image to Docker Hub 😎 - - name: Get image tag - id: get_tag - run: echo "IMAGE_TAG=$(date +%s)" >> $GITHUB_ENV - - name: Publish to Registry - uses: elgohr/Publish-Docker-Github-Action@master - with: - ## the name of our image - name: lllamnyp/etcd-operator - ## Here we pass in our Docker Username - username: ${{ secrets.DOCKER_USERNAME }} - ## and our Docker password which - password: ${{ secrets.DOCKER_PASSWORD }} - tags: "latest,${{ env.IMAGE_TAG }}" diff --git a/.github/workflows/release-assets.yml b/.github/workflows/release-assets.yml new file mode 100644 index 00000000..fd1b36e7 --- /dev/null +++ b/.github/workflows/release-assets.yml @@ -0,0 +1,59 @@ +name: Upload release assets + +# When a GitHub release is created for a tag, render the install manifests for +# that tag's image and attach them to the release. `make build-dist-manifests` +# sets the `controller` image (and, via the kustomize replacement in +# config/default, the manager's OPERATOR_IMAGE env) to the released ref, so the +# attached YAML deploys the matching operator and its snapshot/restore agent. + +on: + release: + types: [ created ] + workflow_dispatch: + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + release-assets: + runs-on: ubuntu-22.04 + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + + - name: Resolve release tag + run: echo "RELEASE_TAG=${{ github.ref_name }}" >> $GITHUB_ENV + + - name: Render install manifests + run: make build-dist-manifests IMG=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${RELEASE_TAG} + + - uses: svenstaro/upload-release-action@2.9.0 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: dist/etcd-operator.yaml + asset_name: etcd-operator.yaml + tag: ${{ github.ref }} + overwrite: true + + - uses: svenstaro/upload-release-action@2.9.0 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: dist/etcd-operator.crds.yaml + asset_name: etcd-operator.crds.yaml + tag: ${{ github.ref }} + overwrite: true + + - uses: svenstaro/upload-release-action@2.9.0 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: dist/etcd-operator.non-crds.yaml + asset_name: etcd-operator.non-crds.yaml + tag: ${{ github.ref }} + overwrite: true diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml new file mode 100644 index 00000000..b4580405 --- /dev/null +++ b/.github/workflows/release-drafter.yml @@ -0,0 +1,36 @@ +name: Release Drafter + +# Maintains a draft GitHub release on main, accumulating merged-PR titles into +# categorised notes. Publishing that draft for a tag is what triggers +# release-assets.yml; pushing the tag is what triggers docker-publish.yml. + +on: + push: + branches: [ main ] + # pull_request_target runs in the context of the BASE branch (main) with a + # read/write token, which is what lets the autolabeler label PRs from forks. + # It is used ONLY to read PR metadata (title, labels, changed-file globs). + pull_request_target: + types: [ opened, reopened, synchronize ] + workflow_dispatch: + +jobs: + release-drafter: + runs-on: ubuntu-22.04 + permissions: + contents: write + pull-requests: write + steps: + # SECURITY: never add `actions/checkout` of the PR head (or run any code + # from the PR) in this job. pull_request_target grants a write token and + # secrets while running base-branch config; checking out + executing fork + # code under it is the canonical fork-to-RCE pattern. release-drafter + # touches no repo code, so this job stays safe as long as nothing here + # checks out untrusted refs. + - uses: release-drafter/release-drafter@v6.0.0 + with: + disable-releaser: ${{ github.ref != 'refs/heads/main' }} + config-name: release-drafter.yml + commitish: main + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-smoke.yml b/.github/workflows/release-smoke.yml new file mode 100644 index 00000000..4ca72973 --- /dev/null +++ b/.github/workflows/release-smoke.yml @@ -0,0 +1,64 @@ +name: Release install smoke + +# Gates changes to the tag-release machinery by running the full ship-then- +# install flow on a throwaway kind cluster (hack/release-smoke.sh), for BOTH +# shipped install paths: the rendered manifest (release-assets.yml) and the +# Helm chart (helm-publish.yml). Each builds the image, installs it, and +# asserts the operator comes up and reconciles a 1-node cluster. Catches +# release-pipeline regressions (image/manifest tag drift, broken OPERATOR_IMAGE +# wiring, CRDs that don't apply, a chart missing an RBAC rule) on the PR that +# introduces them, not on the first real tag. The image is loaded into kind, +# never pushed — no registry credentials. +on: + pull_request: + paths: + - '.github/workflows/release-smoke.yml' + - '.github/workflows/docker-publish.yml' + - '.github/workflows/release-assets.yml' + - '.github/workflows/helm-publish.yml' + - 'hack/release-smoke.sh' + - 'charts/**' + - 'Makefile' + - 'Dockerfile' + - 'config/**' + - 'api/**' + workflow_dispatch: + +concurrency: + group: release-smoke-${{ github.ref }} + cancel-in-progress: true + +jobs: + smoke: + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + strategy: + fail-fast: false + matrix: + mode: [ manifest, helm ] + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + + - name: Install Helm + if: matrix.mode == 'helm' + uses: azure/setup-helm@v4 + with: + version: 'v3.16.4' + + - name: release-install smoke (${{ matrix.mode }}, kind) + # Builds the image, installs via the matrix path, and asserts the + # operator is Available and a 1-node EtcdCluster reaches READY. On + # failure the script's EXIT trap dumps cluster state before teardown. + run: | + if [ "${{ matrix.mode }}" = helm ]; then + make helm-smoke + else + make release-smoke + fi diff --git a/.gitignore b/.gitignore index ed60d564..e6d6e8be 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,9 @@ bin /kubectl-etcd # etcd-migrate tool: same rule — build to bin/, never commit the root-level artifact /etcd-migrate +# release install manifests rendered by `make build-dist-manifests`; the +# release-assets workflow regenerates and attaches these per tag +/dist # Test binary, build with `go test -c` *.test diff --git a/Makefile b/Makefile index 64c47dd3..af38e2d1 100644 --- a/Makefile +++ b/Makefile @@ -69,6 +69,14 @@ test-e2e: ## Run the e2e suite against the current kubeconfig context (expects c e2e: ## Provision a kind cluster with cert-manager and Kamaji, deploy the operator, run the e2e suite. KEEP_CLUSTER=1 keeps the cluster for debugging. hack/e2e.sh +.PHONY: release-smoke +release-smoke: ## Smoke-test the tag-release manifest install path on kind: build image -> render dist manifests -> apply -> assert operator Available and a 1-node cluster READY. KEEP_CLUSTER=1 keeps the cluster. + hack/release-smoke.sh + +.PHONY: helm-smoke +helm-smoke: helm-sync ## Smoke-test the Helm chart install path on kind: build image -> helm install chart -> assert operator Available and a 1-node cluster READY. KEEP_CLUSTER=1 keeps the cluster. + INSTALL_MODE=helm hack/release-smoke.sh + ##@ Build .PHONY: build @@ -115,6 +123,30 @@ docker-buildx: test ## Build and push docker image for the manager for cross-pla - docker buildx rm project-v3-builder rm Dockerfile.cross +.PHONY: build-dist-manifests +build-dist-manifests: manifests generate kustomize yq ## Render the release install manifests into dist/ for IMG. + # Produces the YAMLs the release-assets workflow attaches to a tag: + # dist/etcd-operator.yaml – everything (CRDs + deployment) + # dist/etcd-operator.crds.yaml – CRDs only + # dist/etcd-operator.non-crds.yaml – everything except CRDs + # Setting the `controller` image also propagates into the manager's + # OPERATOR_IMAGE env via the kustomize replacement in config/default, so + # the snapshot/restore agent runs the same ref. Pass IMG=/etcd-operator:. + mkdir -p dist + cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} + $(KUSTOMIZE) build config/default > dist/etcd-operator.yaml + $(KUSTOMIZE) build config/default | $(YQ) eval 'select(.kind != "CustomResourceDefinition")' - > dist/etcd-operator.non-crds.yaml + $(KUSTOMIZE) build config/default | $(YQ) eval 'select(.kind == "CustomResourceDefinition")' - > dist/etcd-operator.crds.yaml + +.PHONY: helm-sync +helm-sync: manifests ## Vendor the generated CRDs into the Helm chart's crds/ directory. + # The chart's RBAC/Deployment templates are hand-authored from config/, but + # the CRDs (large, schema-bearing, regenerated by controller-gen) are copied + # verbatim so they never drift from the API types. helm-publish.yml re-runs + # this before packaging so a published chart always carries current CRDs. + mkdir -p charts/etcd-operator/crds + cp config/crd/bases/*.yaml charts/etcd-operator/crds/ + ##@ Deployment ifndef ignore-not-found @@ -148,12 +180,14 @@ $(LOCALBIN): ## Tool Versions KUSTOMIZE_VERSION ?= v5.6.0 CONTROLLER_TOOLS_VERSION ?= v0.18.0 +YQ_VERSION ?= v4.44.1 ## Tool Binaries (version-suffixed so a version bump auto-triggers reinstall ## and stale builds of an old version stay on disk alongside the new one). KUSTOMIZE ?= $(LOCALBIN)/kustomize-$(KUSTOMIZE_VERSION) CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen-$(CONTROLLER_TOOLS_VERSION) ENVTEST ?= $(LOCALBIN)/setup-envtest +YQ ?= $(LOCALBIN)/yq-$(YQ_VERSION) # go-install-tool installs $2@$3 under $1. `go install` drops the binary at # $LOCALBIN/, so we rename it after install to the version-suffixed @@ -182,3 +216,8 @@ $(CONTROLLER_GEN): $(LOCALBIN) envtest: $(ENVTEST) ## Download envtest-setup locally if necessary. $(ENVTEST): $(LOCALBIN) test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest + +.PHONY: yq +yq: $(YQ) ## Download yq locally if necessary. +$(YQ): $(LOCALBIN) + $(call go-install-tool,$(YQ),github.com/mikefarah/yq/v4,$(YQ_VERSION)) diff --git a/charts/etcd-operator/.helmignore b/charts/etcd-operator/.helmignore new file mode 100644 index 00000000..c59a757d --- /dev/null +++ b/charts/etcd-operator/.helmignore @@ -0,0 +1,6 @@ +# Patterns to ignore when building packages. +*.tmpl +.git +.gitignore +*.tgz +README.md.gotmpl diff --git a/charts/etcd-operator/Chart.yaml b/charts/etcd-operator/Chart.yaml new file mode 100644 index 00000000..cf3dba73 --- /dev/null +++ b/charts/etcd-operator/Chart.yaml @@ -0,0 +1,11 @@ +apiVersion: v2 +name: etcd-operator +description: Kubernetes operator for running etcd clusters (etcd-operator.cozystack.io/v1alpha2) +type: application +# Placeholders. The release pipeline (helm-publish.yml) sets the real values +# from the git tag: --version , --app-version . +version: 0.0.0 +appVersion: "v0.0.0" +home: https://github.com/cozystack/etcd-operator +sources: + - https://github.com/cozystack/etcd-operator diff --git a/charts/etcd-operator/crds/etcd-operator.cozystack.io_etcdclusters.yaml b/charts/etcd-operator/crds/etcd-operator.cozystack.io_etcdclusters.yaml new file mode 100644 index 00000000..6be40900 --- /dev/null +++ b/charts/etcd-operator/crds/etcd-operator.cozystack.io_etcdclusters.yaml @@ -0,0 +1,3255 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: etcdclusters.etcd-operator.cozystack.io +spec: + group: etcd-operator.cozystack.io + names: + kind: EtcdCluster + listKind: EtcdClusterList + plural: etcdclusters + singular: etcdcluster + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.version + name: Version + type: string + - jsonPath: .spec.replicas + name: Replicas + type: integer + - jsonPath: .status.readyMembers + name: Ready + type: integer + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: EtcdCluster is the Schema for the etcdclusters API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + EtcdClusterSpec defines the desired state of an etcd cluster. + + CEL validation rules (k8s 1.29+ apiserver-enforced; both + CustomResourceValidationExpressions and the quantity() extension + are GA in 1.29): + + - storage.medium=Memory + replicas=0 wedges the cluster on resume + (the dormant flip deletes the Pod and the tmpfs goes with it but + the resume path treats the member as if its data were preserved). + Reject the combination outright; recreate is the only safe path. + + - storage.medium=Memory requires storage.size > 0. Without a SizeLimit + the tmpfs is unbounded against node memory, which defeats the whole + point of opting into memory backing. + properties: + additionalMetadata: + description: |- + AdditionalMetadata holds extra labels and annotations the operator + merges onto every object it creates for this cluster — member Pods, + the per-member data PVCs, the client and headless Services, the + PodDisruptionBudget, and the EtcdMember CRs. Operator-owned keys + (the app.kubernetes.io/* set and the cluster/role labels, and any + operator-set annotation) always win on collision, so this cannot be + used to shadow the operator's own metadata. + + Like spec.resources, changes take effect on newly-created objects + (scale-up, replacement); the operator does not re-stamp existing + Pods in place. The value is latched through status.observed with the + rest of the target spec, so a mid-flight edit only applies once the + current target is reached. + properties: + annotations: + additionalProperties: + type: string + description: Annotations are extra annotations merged onto created + objects. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels are extra labels merged onto created objects. Operator-owned + label keys take precedence on collision. + type: object + type: object + affinity: + description: |- + Affinity sets the scheduling affinity/anti-affinity for member Pods. + Passed straight through to each member Pod's spec.affinity. A common + use is a pod anti-affinity on app.kubernetes.io/instance= to + keep members off the same node. + + Updates take effect on newly-created members (scale-up, replacement); + the operator does not roll existing Pods to apply an affinity change + in place. + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the + pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with the + corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the corresponding + nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate + this pod in the same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. + avoid putting this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + auth: + description: |- + Auth configures in-etcd authentication. Absent means no auth + (anonymous access on the client API, subject only to TLS). See + AuthSpec for the single-user parity model and its constraints + (requires spec.tls.client; immutable post-create). + properties: + enabled: + description: |- + Enabled turns on etcd authentication. The operator provisions the + root user + role and runs `auth enable` once the cluster has + converged to a healthy quorum (see status.authEnabled). Mirrors + `etcdctl auth enable` and the AuthStatusResponse.Enabled field. + + Requires spec.tls.client to be set: auth credentials must not cross + a plaintext wire. Immutable post-create — enabling or disabling auth + on a live cluster mutates persisted data-store state in lockstep with + the operator's own client, which this version does not roll back, so + the field is frozen the same way spec.tls is. Delete and recreate to + change it. + type: boolean + rootCredentialsSecretRef: + description: |- + RootCredentialsSecretRef references a Secret in the cluster's + namespace holding the etcd root user's credentials. Required when + Enabled is true (CEL-enforced). + + The Secret is expected to be of type kubernetes.io/basic-auth: the + operator reads the `password` key and provisions the etcd `root` user + with it. The `username` key is for consumers (e.g. a Kamaji DataStore + pointing at the same Secret) and must be "root" — the etcd user is + always root, since etcd requires a user named root to enable auth. + + Immutable post-create (part of the immutable auth subtree). The + operator reads the password on every dial; changing the Secret's + contents after auth is enabled would desync the operator from etcd — + in-place password rotation is not supported in this version, recreate + to change. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: object + bootstrap: + description: |- + Bootstrap configures one-time cluster initialization options. It is + consulted only at first bootstrap (while status.clusterID is unset) + and is immutable post-create. Absent means a normal empty-cluster + bootstrap. + properties: + restore: + description: |- + Restore initializes the new cluster from an existing etcd snapshot + instead of an empty data dir. Absent means a normal empty bootstrap. + properties: + source: + description: |- + Source is where the snapshot is read from (S3 or PVC). Same shape as + an EtcdSnapshot destination. + properties: + pvc: + description: PVC stores the snapshot on a PersistentVolumeClaim. + properties: + claimName: + description: ClaimName is the name of a PVC in the + cluster's namespace. + minLength: 1 + type: string + subPath: + description: SubPath is an optional subdirectory within + the volume. + type: string + required: + - claimName + type: object + s3: + description: S3 stores the snapshot in an S3-compatible + object store. + properties: + bucket: + description: Bucket is the destination bucket. + minLength: 1 + type: string + credentialsSecretRef: + description: |- + CredentialsSecretRef references a Secret in the cluster's namespace + holding AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY keys. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + endpoint: + description: |- + Endpoint is the S3 endpoint URL (e.g. "https://s3.amazonaws.com" or a + MinIO/Ceph endpoint). + minLength: 1 + type: string + forcePathStyle: + description: |- + ForcePathStyle selects path-style addressing (bucket in the path + rather than the host). MinIO/Ceph typically require true. + type: boolean + key: + description: |- + Key is an optional object-key prefix within the bucket. The operator + appends ".db". + type: string + region: + description: Region is the S3 region. Optional for + endpoints that ignore it. + type: string + required: + - bucket + - credentialsSecretRef + - endpoint + type: object + type: object + x-kubernetes-validations: + - message: exactly one of destination.s3 or destination.pvc + must be set + rule: has(self.s3) != has(self.pvc) + required: + - source + type: object + x-kubernetes-validations: + - message: bootstrap.restore.source.s3.key must be the exact (non-empty) + object key for a restore source + rule: '!has(self.source.s3) || (has(self.source.s3.key) && size(self.source.s3.key) + > 0)' + - message: bootstrap.restore.source.pvc.subPath must be the exact + (non-empty) snapshot file path for a restore source + rule: '!has(self.source.pvc) || (has(self.source.pvc.subPath) + && size(self.source.pvc.subPath) > 0)' + type: object + options: + description: |- + Options carries etcd server tuning flags (backend quota, + auto-compaction, raft snapshot count) passed to each member's + command line. A closed typed set — see EtcdOptions for why there + is deliberately no free-form flag map. + + Updates take effect on newly-created members (scale-up, + replacement); the operator does not roll existing Pods to apply a + tuning change in place. + properties: + autoCompactionMode: + description: |- + AutoCompactionMode sets --auto-compaction-mode: how + AutoCompactionRetention is interpreted, "periodic" (time-based) + or "revision" (revision-count-based). Absent means etcd's default + ("periodic" — though compaction only activates when a retention + is set). + enum: + - periodic + - revision + type: string + autoCompactionRetention: + description: |- + AutoCompactionRetention sets --auto-compaction-retention. In + periodic mode a duration ("5m", "1h"; a bare integer means hours); + in revision mode a revision count. Absent or "0" disables + auto-compaction. The pattern admits what etcd itself parses: a + bare non-negative integer or a Go duration. + pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$|^[0-9]+$ + type: string + quotaBackendBytes: + description: |- + QuotaBackendBytes sets --quota-backend-bytes: the backend database + size limit in bytes before the member raises the cluster-wide + NOSPACE alarm. 0 or absent means etcd's built-in default (2GiB). + etcd's documented practical maximum is 8GiB. + format: int64 + minimum: 0 + type: integer + snapshotCount: + description: |- + SnapshotCount sets --snapshot-count: the number of committed + raft entries to retain in memory before triggering an internal + raft snapshot (this is unrelated to EtcdSnapshot backups). Absent + means etcd's built-in default. Lower values trade replay speed on + restart for a smaller memory footprint. + format: int64 + minimum: 1 + type: integer + type: object + progressDeadlineSeconds: + default: 600 + description: |- + ProgressDeadlineSeconds bounds the time the operator spends trying to + reach a desired state before abandoning the in-flight target and + adopting whatever the user has set as the new spec. Defaults to 600 + (10 minutes). A patch to status.progressDeadline can shorten this for + a stuck reconcile (set it to "now" to abort immediately). + format: int32 + minimum: 1 + type: integer + replicas: + default: 3 + description: |- + Replicas is the desired number of cluster members. Should be odd. + A value of 0 parks the cluster ("scale to zero"): the operator + flips spec.dormant=true on the surviving EtcdMember, which causes + the member controller to delete that member's Pod. The EtcdMember + CR and its PVC are preserved (the PVC stays owned by the same + EtcdMember across the pause). Scaling back up to >=1 flips + spec.dormant=false on the same member; etcd resumes from its + existing data dir with the same ClusterID and member ID. + format: int32 + minimum: 0 + type: integer + resources: + description: |- + Resources sets the etcd container's resource requests and limits. + When omitted, the operator falls back to a conservative default + (100m CPU + 128Mi memory requests, no limits) suitable for + kicking the tyres but not for production. Memory-backed clusters + specifically should set limits.memory covering the tmpfs SizeLimit + plus etcd's own headroom. + + Updates take effect on newly-created members (scale-up, + replacement). The operator does not roll existing Pods to apply + a resource change in place — wire a VerticalPodAutoscaler at + targetRef={kind: EtcdCluster, name: } for that, or + delete one Pod at a time to recreate them with the new spec. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + storage: + default: + medium: "" + size: 1Gi + description: |- + Storage configures the per-member data directory: size and medium + (PVC or tmpfs). The size shrink-rejection and medium immutability + rules live as field-level CEL on the inner fields; the spec-level + CEL above couples replicas and storage.medium. + properties: + medium: + default: "" + description: |- + Medium selects the volume backend: "" (PVC) or "Memory" (tmpfs + emptyDir). See the StorageMedium type doc for operational trade-offs. + + Immutable: changing the medium on an existing cluster would orphan + the previous PVC (or tmpfs) and the rolling-migrate path is not + implemented. The default ("") is set explicitly so the apiserver + always stores the field on Create — without it, a first-time set + from absent → Memory would slip past the transition rule. + enum: + - "" + - Memory + type: string + x-kubernetes-validations: + - message: spec.storage.medium is immutable; delete and recreate + the cluster to change the storage backend + rule: self == oldSelf + size: + anyOf: + - type: integer + - type: string + default: 1Gi + description: |- + Size is the requested capacity per member. For Medium="" (PVC) this + is the PVC's requested storage. For Medium="Memory" this is the + tmpfs emptyDir's SizeLimit. + + Shrinking is rejected on UPDATE: PVCs cannot shrink and tmpfs + SizeLimit reduction does not free already-allocated memory. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + x-kubernetes-validations: + - message: spec.storage.size cannot be shrunk + rule: quantity(string(self)).compareTo(quantity(string(oldSelf))) + >= 0 + storageClassName: + description: |- + StorageClassName selects the StorageClass for the per-member PVC. + Mirrors corev1.PersistentVolumeClaimSpec.StorageClassName semantics: + nil (the default) uses the namespace's default StorageClass; the + empty string explicitly disables dynamic provisioning (a + pre-provisioned PV must already match the PVC selector); any other + value names a specific StorageClass. + + Ignored when Medium=Memory (no PVC is created). + + Immutable post-create — PersistentVolumeClaim.spec.storageClassName + is itself immutable after PVC creation, so honouring a mid-life + change would require a rolling PVC-recreation flow that this + operator does not perform. The immutability rules live at the + EtcdClusterSpec level (alongside the other pointer-field rules) + because *string transition CEL on the inner field cannot fire when + the field is being added from nil. + type: string + type: object + tls: + description: |- + TLS configures transport-layer security for the etcd client and + peer APIs. Absent means plaintext on both surfaces. The whole + subtree is immutable post-create — the immutability rules live at + the EtcdClusterSpec level (above) because pointer-field transition + rules don't fire when the field is being added (nil → set) and we + want to reject that direction too. + properties: + client: + description: |- + Client configures TLS for the etcd client API (port 2379). Absent + means plaintext. See ClientTLS for the mTLS-toggle semantics. + properties: + certManager: + description: |- + CertManager configures operator-driven TLS material provisioning + via cert-manager.io/v1 Certificate resources. Mutually exclusive + with ServerSecretRef. The operator owns the emitted Certificates + (they GC with the EtcdCluster) and the resulting Secrets are + mounted into the etcd Pods the same way BYO Secrets are. + properties: + operatorClientIssuerRef: + description: |- + OperatorClientIssuerRef is the Issuer or ClusterIssuer that will + sign the operator's etcd-client identity. Presence ⇒ client mTLS + is on; absence ⇒ server-TLS only. Resulting Secret name is + "-operator-client-tls". + + In the happy path the same Issuer signs both server and operator- + client certs, so the CA visible in each Secret's ca.crt is the + same content, doubling as etcd's --trusted-ca-file. Splitting the + two Issuers across separate root CAs requires the user to ensure + the trust bundle on the server side covers both — that case is an + edge of this v1. + properties: + kind: + default: Issuer + description: |- + Kind is either "Issuer" or "ClusterIssuer". Defaults to "Issuer" + — a namespaced issuer living next to the EtcdCluster. + enum: + - Issuer + - ClusterIssuer + type: string + name: + description: Name is the issuer resource's name. + minLength: 1 + type: string + required: + - name + type: object + serverIssuerRef: + description: |- + ServerIssuerRef is the Issuer or ClusterIssuer that will sign the + etcd server cert. Resulting Secret name is + "-server-tls". + properties: + kind: + default: Issuer + description: |- + Kind is either "Issuer" or "ClusterIssuer". Defaults to "Issuer" + — a namespaced issuer living next to the EtcdCluster. + enum: + - Issuer + - ClusterIssuer + type: string + name: + description: Name is the issuer resource's name. + minLength: 1 + type: string + required: + - name + type: object + required: + - serverIssuerRef + type: object + operatorClientSecretRef: + description: |- + OperatorClientSecretRef points at a Secret in the cluster's + namespace holding the operator's etcd-client identity (tls.crt, + tls.key). Setting this enables mTLS — etcd is started with + --client-cert-auth=true and the operator presents this cert when + dialing. Leaving it unset selects server-TLS-only mode. + + Cannot be combined with CertManager; use + CertManager.OperatorClientIssuerRef instead. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + serverSecretRef: + description: |- + ServerSecretRef points at a Secret in the cluster's namespace + holding the etcd server cert in the standard kubernetes.io/tls + shape: tls.crt, tls.key, and ca.crt. ca.crt is always required + (the operator's own etcd client needs it to verify the server, + and when mTLS is on it doubles as --trusted-ca-file). + + Mutually exclusive with CertManager. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: object + x-kubernetes-validations: + - message: exactly one of spec.tls.client.serverSecretRef or spec.tls.client.certManager + must be set + rule: has(self.serverSecretRef) != has(self.certManager) + - message: spec.tls.client.operatorClientSecretRef cannot be combined + with certManager; use certManager.operatorClientIssuerRef + rule: '!has(self.certManager) || !has(self.operatorClientSecretRef)' + peer: + description: |- + Peer configures TLS for the etcd peer API (port 2380). Absent + means plaintext. When set, peer is always mTLS — etcd's peer mesh + is symmetric and there is no useful encrypt-only-no-identity mode + for it. + properties: + certManager: + description: |- + CertManager configures operator-driven TLS material provisioning + for the peer plane via cert-manager.io/v1 Certificate resources. + Mutually exclusive with SecretRef. + properties: + issuerRef: + description: |- + IssuerRef is the Issuer or ClusterIssuer that signs the peer cert. + Peer is symmetric (same cert serves and dials), so this single + Issuer covers both directions of peer mTLS. Resulting Secret name + is "-peer-tls". + properties: + kind: + default: Issuer + description: |- + Kind is either "Issuer" or "ClusterIssuer". Defaults to "Issuer" + — a namespaced issuer living next to the EtcdCluster. + enum: + - Issuer + - ClusterIssuer + type: string + name: + description: Name is the issuer resource's name. + minLength: 1 + type: string + required: + - name + type: object + required: + - issuerRef + type: object + secretRef: + description: |- + SecretRef points at a Secret in the cluster's namespace holding + the peer cert+key in the standard kubernetes.io/tls shape: + tls.crt, tls.key, ca.crt. ca.crt is required (peer is symmetric + — same cert is used to serve inbound and dial outbound peer + connections — and --peer-trusted-ca-file is always populated). + The peer cert MUST carry both serverAuth and clientAuth in EKU. + + Mutually exclusive with CertManager. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: object + x-kubernetes-validations: + - message: exactly one of spec.tls.peer.secretRef or spec.tls.peer.certManager + must be set + rule: has(self.secretRef) != has(self.certManager) + type: object + topologySpreadConstraints: + description: |- + TopologySpreadConstraints controls how member Pods are spread across + topology domains (zones, nodes). Passed straight through to each + member Pod's spec.topologySpreadConstraints. + + Updates take effect on newly-created members (scale-up, replacement); + the operator does not roll existing Pods to apply a change in place. + items: + description: TopologySpreadConstraint specifies how to spread matching + pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + version: + description: Version is the desired etcd version (e.g. "3.6.11"). + pattern: ^\d+\.\d+\.\d+$ + type: string + required: + - version + type: object + x-kubernetes-validations: + - message: 'spec.replicas=0 with spec.storage.medium=Memory is unsupported: + pausing a memory-backed cluster wedges on resume. Delete and recreate + the cluster instead.' + rule: '!(has(self.replicas) && self.replicas == 0 && has(self.storage) + && has(self.storage.medium) && self.storage.medium == ''Memory'')' + - message: spec.storage.size must be > 0 when spec.storage.medium=Memory + (the tmpfs sizeLimit cannot be zero). + rule: '!(has(self.storage) && has(self.storage.medium) && self.storage.medium + == ''Memory'') || quantity(string(self.storage.size)).isGreaterThan(quantity(''0''))' + - message: spec.tls cannot be added to or removed from an existing cluster; + delete and recreate + rule: has(self.tls) == has(oldSelf.tls) + - message: spec.tls is immutable post-create; delete and recreate the + cluster to change TLS configuration + rule: '!has(self.tls) || !has(oldSelf.tls) || self.tls == oldSelf.tls' + - message: spec.storage.storageClassName cannot be added to or removed + from an existing cluster; delete and recreate + rule: has(self.storage.storageClassName) == has(oldSelf.storage.storageClassName) + - message: spec.storage.storageClassName is immutable post-create (a PVC's + storageClassName itself is immutable, and the operator does not roll + PVCs); delete and recreate the cluster to change the StorageClass + rule: '!has(self.storage.storageClassName) || !has(oldSelf.storage.storageClassName) + || self.storage.storageClassName == oldSelf.storage.storageClassName' + - message: spec.auth cannot be added to or removed from an existing cluster; + delete and recreate + rule: has(self.auth) == has(oldSelf.auth) + - message: spec.auth is immutable post-create; delete and recreate the + cluster to change auth configuration + rule: '!has(self.auth) || !has(oldSelf.auth) || self.auth == oldSelf.auth' + - message: spec.auth.enabled requires spec.tls.client (auth credentials + must not cross a plaintext connection) + rule: '!(has(self.auth) && self.auth.enabled) || (has(self.tls) && has(self.tls.client))' + - message: spec.auth.enabled requires spec.auth.rootCredentialsSecretRef + rule: '!(has(self.auth) && self.auth.enabled) || has(self.auth.rootCredentialsSecretRef)' + - message: spec.bootstrap cannot be added to or removed from an existing + cluster; it is consulted only at first bootstrap + rule: has(self.bootstrap) == has(oldSelf.bootstrap) + - message: spec.bootstrap is immutable post-create; it is consulted only + at first bootstrap + rule: '!has(self.bootstrap) || !has(oldSelf.bootstrap) || self.bootstrap + == oldSelf.bootstrap' + - message: 'spec.bootstrap.restore is unsupported with spec.storage.medium=Memory: + the restored data dir is tmpfs, so any seed Pod restart re-restores + the snapshot — reverting writes (single member) or breaking the cluster + with a fresh ID it can''t rejoin (multi-member). Use persistent storage + to restore.' + rule: '!(has(self.bootstrap) && has(self.bootstrap.restore)) || !(has(self.storage) + && has(self.storage.medium) && self.storage.medium == ''Memory'')' + status: + description: EtcdClusterStatus defines the observed state of an etcd cluster. + properties: + authEnabled: + description: |- + AuthEnabled is true once the operator has successfully run + `auth enable` against the cluster. It is latched (never cleared — + spec.auth.enabled is immutable) and is the single signal every + operator etcd dial consults to decide whether to present the root + credentials: false ⇒ dial anonymously (auth not yet on, e.g. during + the bootstrap window before the cluster has converged), true ⇒ dial + as root. Decoupling this from spec.auth.enabled is what makes + the bootstrap window correct — clientv3 attempts an Authenticate RPC + on connect when a username is set, which fails until auth is enabled. + type: boolean + brokenMembers: + description: |- + BrokenMembers is the count of members the operator considers broken. + While the auto-replacement predicate is a stub it is always 0; surfaced + here so the predicate has a tested call site and the field already + exists when the policy lands. + format: int32 + type: integer + clusterID: + description: |- + ClusterID is the etcd cluster ID in hex (e.g. "769f1c9e0d723d0b"), + set after initial bootstrap. Stored as a string because uint64 values + can exceed JSON's safe integer range. + type: string + clusterToken: + description: |- + ClusterToken is the value passed to etcd's --initial-cluster-token, + recorded at bootstrap. Reused for all subsequent scale-up operations + so existing clusters keep their original token even if the derivation + rule changes in a later release. + type: string + conditions: + description: Conditions represent the latest available observations + of the cluster's state. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + observed: + description: |- + Observed is the locked-in desired state the operator is currently + reconciling toward. The reconciler ignores spec changes while a target + is in flight; Observed is only updated from spec when the current + target is met or its deadline has expired. nil before the first + reconcile. + properties: + additionalMetadata: + description: |- + AdditionalMetadata is the locked target extra labels/annotations + stamped onto objects created for this cluster. Latched with the rest + of the target spec so a mid-flight metadata edit only applies to + objects created once the current target is reached. + properties: + annotations: + additionalProperties: + type: string + description: Annotations are extra annotations merged onto + created objects. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels are extra labels merged onto created objects. Operator-owned + label keys take precedence on collision. + type: object + type: object + affinity: + description: |- + Affinity is the locked target scheduling affinity for member Pods. + Latched alongside the rest of the target spec so a mid-flight change + only applies to members created once the current target is reached. + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for + the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + options: + description: |- + Options is the locked target etcd tuning flags for member Pods. + Latched with the rest of the target spec so a mid-flight tuning + edit only applies to members created once the current target is + reached. + properties: + autoCompactionMode: + description: |- + AutoCompactionMode sets --auto-compaction-mode: how + AutoCompactionRetention is interpreted, "periodic" (time-based) + or "revision" (revision-count-based). Absent means etcd's default + ("periodic" — though compaction only activates when a retention + is set). + enum: + - periodic + - revision + type: string + autoCompactionRetention: + description: |- + AutoCompactionRetention sets --auto-compaction-retention. In + periodic mode a duration ("5m", "1h"; a bare integer means hours); + in revision mode a revision count. Absent or "0" disables + auto-compaction. The pattern admits what etcd itself parses: a + bare non-negative integer or a Go duration. + pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$|^[0-9]+$ + type: string + quotaBackendBytes: + description: |- + QuotaBackendBytes sets --quota-backend-bytes: the backend database + size limit in bytes before the member raises the cluster-wide + NOSPACE alarm. 0 or absent means etcd's built-in default (2GiB). + etcd's documented practical maximum is 8GiB. + format: int64 + minimum: 0 + type: integer + snapshotCount: + description: |- + SnapshotCount sets --snapshot-count: the number of committed + raft entries to retain in memory before triggering an internal + raft snapshot (this is unrelated to EtcdSnapshot backups). Absent + means etcd's built-in default. Lower values trade replay speed on + restart for a smaller memory footprint. + format: int64 + minimum: 1 + type: integer + type: object + replicas: + description: Replicas is the locked target replica count. + format: int32 + type: integer + resources: + description: |- + Resources is the locked target etcd container resources. The + locking pattern prevents a mid-flight resource change from being + honoured on newly-created members until the current target is + reached or its deadline expires. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + storage: + description: |- + Storage is the locked target storage configuration. The locking + pattern prevents a mid-flight size grow from being honoured until + the current target is reached or its deadline expires. (Medium + can't change at all post-create — that's enforced by spec-level + CEL.) + properties: + medium: + default: "" + description: |- + Medium selects the volume backend: "" (PVC) or "Memory" (tmpfs + emptyDir). See the StorageMedium type doc for operational trade-offs. + + Immutable: changing the medium on an existing cluster would orphan + the previous PVC (or tmpfs) and the rolling-migrate path is not + implemented. The default ("") is set explicitly so the apiserver + always stores the field on Create — without it, a first-time set + from absent → Memory would slip past the transition rule. + enum: + - "" + - Memory + type: string + x-kubernetes-validations: + - message: spec.storage.medium is immutable; delete and recreate + the cluster to change the storage backend + rule: self == oldSelf + size: + anyOf: + - type: integer + - type: string + default: 1Gi + description: |- + Size is the requested capacity per member. For Medium="" (PVC) this + is the PVC's requested storage. For Medium="Memory" this is the + tmpfs emptyDir's SizeLimit. + + Shrinking is rejected on UPDATE: PVCs cannot shrink and tmpfs + SizeLimit reduction does not free already-allocated memory. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + x-kubernetes-validations: + - message: spec.storage.size cannot be shrunk + rule: quantity(string(self)).compareTo(quantity(string(oldSelf))) + >= 0 + storageClassName: + description: |- + StorageClassName selects the StorageClass for the per-member PVC. + Mirrors corev1.PersistentVolumeClaimSpec.StorageClassName semantics: + nil (the default) uses the namespace's default StorageClass; the + empty string explicitly disables dynamic provisioning (a + pre-provisioned PV must already match the PVC selector); any other + value names a specific StorageClass. + + Ignored when Medium=Memory (no PVC is created). + + Immutable post-create — PersistentVolumeClaim.spec.storageClassName + is itself immutable after PVC creation, so honouring a mid-life + change would require a rolling PVC-recreation flow that this + operator does not perform. The immutability rules live at the + EtcdClusterSpec level (alongside the other pointer-field rules) + because *string transition CEL on the inner field cannot fire when + the field is being added from nil. + type: string + type: object + topologySpreadConstraints: + description: |- + TopologySpreadConstraints is the locked target spread configuration + for member Pods. Latched with the rest of the target spec. + items: + description: TopologySpreadConstraint specifies how to spread + matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + version: + description: Version is the locked target etcd version. + type: string + required: + - replicas + - storage + - version + type: object + progressDeadline: + description: |- + ProgressDeadline is the time at which the in-flight reconciliation + will be abandoned in favor of the latest spec. Cleared when the + cluster reaches Observed. Patch this to a time in the past to abort + a stuck reconcile. + format: date-time + type: string + readyMembers: + description: |- + ReadyMembers is the count of members that are healthy and serving. + Also exposed as Scale.Status.Replicas via the /scale subresource so + kubectl scale and clients like VerticalPodAutoscaler can read + "current scale" without a custom status field. + format: int32 + type: integer + selector: + description: |- + Selector is the label-selector form ("etcd-operator.cozystack.io/cluster=") + matching every Pod owned by this cluster. Surfaced for the /scale + subresource — the VPA admission controller reads it via Scales().Get() + to know which Pods to inject recommendations into. Plain users won't + see this field directly. + type: string + type: object + type: object + served: true + storage: true + subresources: + scale: + labelSelectorPath: .status.selector + specReplicasPath: .spec.replicas + statusReplicasPath: .status.readyMembers + status: {} diff --git a/charts/etcd-operator/crds/etcd-operator.cozystack.io_etcdmembers.yaml b/charts/etcd-operator/crds/etcd-operator.cozystack.io_etcdmembers.yaml new file mode 100644 index 00000000..b1e5222a --- /dev/null +++ b/charts/etcd-operator/crds/etcd-operator.cozystack.io_etcdmembers.yaml @@ -0,0 +1,1662 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: etcdmembers.etcd-operator.cozystack.io +spec: + group: etcd-operator.cozystack.io + names: + kind: EtcdMember + listKind: EtcdMemberList + plural: etcdmembers + singular: etcdmember + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.clusterName + name: Cluster + type: string + - jsonPath: .spec.version + name: Version + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: |- + EtcdMember represents a single member of an etcd cluster. + EtcdMember resources are created and deleted by the EtcdCluster controller. + Users should not create these directly. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + EtcdMemberSpec defines the desired state of a single etcd member. + Created and managed by the EtcdCluster controller. + properties: + additionalMetadata: + description: |- + AdditionalMetadata mirrors EtcdCluster.spec.additionalMetadata at the + time this member was created. The member controller merges it onto the + member's Pod (operator-owned label keys win on collision). + properties: + annotations: + additionalProperties: + type: string + description: Annotations are extra annotations merged onto created + objects. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels are extra labels merged onto created objects. Operator-owned + label keys take precedence on collision. + type: object + type: object + affinity: + description: |- + Affinity mirrors EtcdCluster.spec.affinity at the time this member was + created. The member controller passes it straight to the Pod's + spec.affinity at build time. + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the + pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with the + corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the corresponding + nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate + this pod in the same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. + avoid putting this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + bootstrap: + description: |- + Bootstrap indicates this member is part of the initial cluster formation. + When true the member starts with --initial-cluster-state=new. + type: boolean + clusterName: + description: ClusterName is the name of the owning EtcdCluster. + minLength: 1 + type: string + clusterToken: + description: |- + ClusterToken is the value passed to etcd's --initial-cluster-token. + Copied from EtcdCluster.status.clusterToken so all members of a cluster + agree, and so changes to the cluster's token derivation rule don't + affect already-running members. + type: string + dormant: + description: |- + Dormant marks the member as paused. While dormant, the member + controller deletes the member's Pod but leaves the PVC in place + (the PVC stays owned by this EtcdMember). The cluster controller + flips Dormant=true on the surviving member when the user sets + EtcdCluster.spec.replicas=0 on a 1-member cluster, and flips it + back to false when the user scales up. Re-creating the Pod against + the existing PVC lets etcd resume from the existing data dir with + the same ClusterID and member ID. While dormant, the member does + not count toward the EtcdCluster's `current` replica accounting. + type: boolean + initialCluster: + description: |- + InitialCluster is the value passed to etcd's --initial-cluster flag. + Set by the cluster controller at creation time. + type: string + options: + description: |- + Options mirrors EtcdCluster.spec.options at the time this member + was created. The member controller renders the set fields as etcd + command-line flags at Pod-build time; existing members are not + re-templated when the cluster spec changes. + properties: + autoCompactionMode: + description: |- + AutoCompactionMode sets --auto-compaction-mode: how + AutoCompactionRetention is interpreted, "periodic" (time-based) + or "revision" (revision-count-based). Absent means etcd's default + ("periodic" — though compaction only activates when a retention + is set). + enum: + - periodic + - revision + type: string + autoCompactionRetention: + description: |- + AutoCompactionRetention sets --auto-compaction-retention. In + periodic mode a duration ("5m", "1h"; a bare integer means hours); + in revision mode a revision count. Absent or "0" disables + auto-compaction. The pattern admits what etcd itself parses: a + bare non-negative integer or a Go duration. + pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$|^[0-9]+$ + type: string + quotaBackendBytes: + description: |- + QuotaBackendBytes sets --quota-backend-bytes: the backend database + size limit in bytes before the member raises the cluster-wide + NOSPACE alarm. 0 or absent means etcd's built-in default (2GiB). + etcd's documented practical maximum is 8GiB. + format: int64 + minimum: 0 + type: integer + snapshotCount: + description: |- + SnapshotCount sets --snapshot-count: the number of committed + raft entries to retain in memory before triggering an internal + raft snapshot (this is unrelated to EtcdSnapshot backups). Absent + means etcd's built-in default. Lower values trade replay speed on + restart for a smaller memory footprint. + format: int64 + minimum: 1 + type: integer + type: object + replicas: + default: 1 + description: |- + Replicas exists only because the PodDisruptionBudget controller + traverses Pods' controllerRef looking for /scale on the parent and + fails closed ("does not implement the scale subresource") if it + isn't there. Each EtcdMember represents exactly one Pod; this field + is locked to 1 by validation and cannot be tuned. + format: int32 + maximum: 1 + minimum: 1 + type: integer + resources: + description: |- + Resources mirrors EtcdCluster.spec.resources at the time this + member was created. The cluster controller copies it onto each + member at creation. The member controller passes the value + straight to the etcd container's resources field at Pod-build + time; existing members are not re-templated when the cluster + spec changes. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + restore: + description: |- + Restore is set only on the bootstrap seed when the parent cluster's + spec.bootstrap.restore is configured. It causes the member controller + to run a restore initContainer that populates the data dir from the + snapshot before etcd starts. Inert once the data dir is initialized. + properties: + source: + description: |- + Source is where the snapshot is read from (S3 or PVC). Same shape as + an EtcdSnapshot destination. + properties: + pvc: + description: PVC stores the snapshot on a PersistentVolumeClaim. + properties: + claimName: + description: ClaimName is the name of a PVC in the cluster's + namespace. + minLength: 1 + type: string + subPath: + description: SubPath is an optional subdirectory within + the volume. + type: string + required: + - claimName + type: object + s3: + description: S3 stores the snapshot in an S3-compatible object + store. + properties: + bucket: + description: Bucket is the destination bucket. + minLength: 1 + type: string + credentialsSecretRef: + description: |- + CredentialsSecretRef references a Secret in the cluster's namespace + holding AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY keys. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + endpoint: + description: |- + Endpoint is the S3 endpoint URL (e.g. "https://s3.amazonaws.com" or a + MinIO/Ceph endpoint). + minLength: 1 + type: string + forcePathStyle: + description: |- + ForcePathStyle selects path-style addressing (bucket in the path + rather than the host). MinIO/Ceph typically require true. + type: boolean + key: + description: |- + Key is an optional object-key prefix within the bucket. The operator + appends ".db". + type: string + region: + description: Region is the S3 region. Optional for endpoints + that ignore it. + type: string + required: + - bucket + - credentialsSecretRef + - endpoint + type: object + type: object + x-kubernetes-validations: + - message: exactly one of destination.s3 or destination.pvc must + be set + rule: has(self.s3) != has(self.pvc) + required: + - source + type: object + x-kubernetes-validations: + - message: bootstrap.restore.source.s3.key must be the exact (non-empty) + object key for a restore source + rule: '!has(self.source.s3) || (has(self.source.s3.key) && size(self.source.s3.key) + > 0)' + - message: bootstrap.restore.source.pvc.subPath must be the exact + (non-empty) snapshot file path for a restore source + rule: '!has(self.source.pvc) || (has(self.source.pvc.subPath) && + size(self.source.pvc.subPath) > 0)' + storage: + description: |- + Storage mirrors EtcdCluster.spec.storage at the time this member + was created. The cluster controller copies size and medium onto + each member at creation; the member controller treats it as + immutable per-member spec. + properties: + medium: + default: "" + description: |- + Medium selects the volume backend: "" (PVC) or "Memory" (tmpfs + emptyDir). See the StorageMedium type doc for operational trade-offs. + + Immutable: changing the medium on an existing cluster would orphan + the previous PVC (or tmpfs) and the rolling-migrate path is not + implemented. The default ("") is set explicitly so the apiserver + always stores the field on Create — without it, a first-time set + from absent → Memory would slip past the transition rule. + enum: + - "" + - Memory + type: string + x-kubernetes-validations: + - message: spec.storage.medium is immutable; delete and recreate + the cluster to change the storage backend + rule: self == oldSelf + size: + anyOf: + - type: integer + - type: string + default: 1Gi + description: |- + Size is the requested capacity per member. For Medium="" (PVC) this + is the PVC's requested storage. For Medium="Memory" this is the + tmpfs emptyDir's SizeLimit. + + Shrinking is rejected on UPDATE: PVCs cannot shrink and tmpfs + SizeLimit reduction does not free already-allocated memory. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + x-kubernetes-validations: + - message: spec.storage.size cannot be shrunk + rule: quantity(string(self)).compareTo(quantity(string(oldSelf))) + >= 0 + storageClassName: + description: |- + StorageClassName selects the StorageClass for the per-member PVC. + Mirrors corev1.PersistentVolumeClaimSpec.StorageClassName semantics: + nil (the default) uses the namespace's default StorageClass; the + empty string explicitly disables dynamic provisioning (a + pre-provisioned PV must already match the PVC selector); any other + value names a specific StorageClass. + + Ignored when Medium=Memory (no PVC is created). + + Immutable post-create — PersistentVolumeClaim.spec.storageClassName + is itself immutable after PVC creation, so honouring a mid-life + change would require a rolling PVC-recreation flow that this + operator does not perform. The immutability rules live at the + EtcdClusterSpec level (alongside the other pointer-field rules) + because *string transition CEL on the inner field cannot fire when + the field is being added from nil. + type: string + type: object + tls: + description: |- + TLS mirrors the parent cluster's TLS configuration at the time + this member was created. Carries only what the etcd Pod needs to + see (secret references and the mTLS flag); operator-side material + stays on the parent cluster spec. + properties: + clientMTLS: + description: |- + ClientMTLS mirrors "EtcdClusterTLS.Client.OperatorClientSecretRef + is set" — i.e. whether the etcd server should be started with + --client-cert-auth=true and --trusted-ca-file. Decoupled from the + secret ref because the secret itself is operator-side only. + type: boolean + clientServerSecretRef: + description: |- + ClientServerSecretRef mirrors EtcdClusterTLS.Client.ServerSecretRef. + When nil, the member runs the client API in plaintext. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + peerSecretRef: + description: |- + PeerSecretRef mirrors EtcdClusterTLS.Peer.SecretRef. When nil, the + member runs the peer API in plaintext. When set, peer is always + mTLS (--peer-client-cert-auth=true). + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: object + topologySpreadConstraints: + description: |- + TopologySpreadConstraints mirrors EtcdCluster.spec.topologySpreadConstraints + at the time this member was created. Passed straight to the Pod's + spec.topologySpreadConstraints at build time. + items: + description: TopologySpreadConstraint specifies how to spread matching + pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + version: + description: Version is the etcd version for this member. + pattern: ^\d+\.\d+\.\d+$ + type: string + required: + - clusterName + - clusterToken + - initialCluster + - storage + - version + type: object + status: + description: EtcdMemberStatus defines the observed state of a single etcd + member. + properties: + conditions: + description: Conditions represent the latest available observations + of the member's state. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + isVoter: + description: |- + IsVoter is true when etcd's MemberList reports this member with + IsLearner=false — i.e. it counts toward quorum. Written by the + cluster controller during its MemberList processing and pre-stamped + true at seed creation (the seed is never a learner). Read by the + member controller to apply the role=voter Pod label that the + per-cluster PodDisruptionBudget selects on. Default value false is + the safe-but-temporary state for a freshly-added learner before + MemberPromote runs. + type: boolean + memberID: + description: |- + MemberID is the etcd-assigned member ID in hex (e.g. "ae36f238164a08ad"), + set once the member joins the cluster. Stored as a string because uint64 + values can exceed JSON's safe integer range. + type: string + podName: + description: PodName is the name of the Pod running this member. + type: string + podUID: + description: |- + PodUID is the UID of the Pod most recently observed for this member. + Set when the Pod is created or found; cleared when the Pod is gone + and the member controller intentionally removed it (e.g. dormant). + For memory-backed members the operator compares the live Pod's UID + against this value to detect Pod loss: a stored UID with no live + matching Pod means the tmpfs is gone and the member must be replaced. + type: string + pvcName: + description: PVCName is the name of the PersistentVolumeClaim for + this member's data. + type: string + replicas: + description: |- + Replicas exposes via /scale "this EtcdMember owns 1 Pod if it has + a PodName, 0 otherwise". Required by the PodDisruptionBudget + controller to derive expectedPods for the cluster's PDB — without + /scale on the Pod controller-ref it sets the PDB to SyncFailed. + format: int32 + type: integer + selector: + description: |- + Selector exposes the label-selector that matches this member's Pod + via /scale (consumed by the PDB controller; not user-facing). + type: string + type: object + type: object + served: true + storage: true + subresources: + scale: + labelSelectorPath: .status.selector + specReplicasPath: .spec.replicas + statusReplicasPath: .status.replicas + status: {} diff --git a/charts/etcd-operator/crds/etcd-operator.cozystack.io_etcdsnapshots.yaml b/charts/etcd-operator/crds/etcd-operator.cozystack.io_etcdsnapshots.yaml new file mode 100644 index 00000000..95de7476 --- /dev/null +++ b/charts/etcd-operator/crds/etcd-operator.cozystack.io_etcdsnapshots.yaml @@ -0,0 +1,235 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: etcdsnapshots.etcd-operator.cozystack.io +spec: + group: etcd-operator.cozystack.io + names: + kind: EtcdSnapshot + listKind: EtcdSnapshotList + plural: etcdsnapshots + singular: etcdsnapshot + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.clusterRef.name + name: Cluster + type: string + - jsonPath: .status.phase + name: Phase + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: |- + EtcdSnapshot is the Schema for the etcdsnapshots API. It captures a one-shot + snapshot of an EtcdCluster to a destination (S3 or PVC). + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + EtcdSnapshotSpec defines a one-shot etcd snapshot of a cluster to a + destination. Snapshots are immutable: change the destination by creating a + new EtcdSnapshot. + properties: + clusterRef: + description: ClusterRef names the EtcdCluster (same namespace) to + snapshot. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + destination: + description: Destination selects where the snapshot is stored (S3 + or PVC). + properties: + pvc: + description: PVC stores the snapshot on a PersistentVolumeClaim. + properties: + claimName: + description: ClaimName is the name of a PVC in the cluster's + namespace. + minLength: 1 + type: string + subPath: + description: SubPath is an optional subdirectory within the + volume. + type: string + required: + - claimName + type: object + s3: + description: S3 stores the snapshot in an S3-compatible object + store. + properties: + bucket: + description: Bucket is the destination bucket. + minLength: 1 + type: string + credentialsSecretRef: + description: |- + CredentialsSecretRef references a Secret in the cluster's namespace + holding AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY keys. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + endpoint: + description: |- + Endpoint is the S3 endpoint URL (e.g. "https://s3.amazonaws.com" or a + MinIO/Ceph endpoint). + minLength: 1 + type: string + forcePathStyle: + description: |- + ForcePathStyle selects path-style addressing (bucket in the path + rather than the host). MinIO/Ceph typically require true. + type: boolean + key: + description: |- + Key is an optional object-key prefix within the bucket. The operator + appends ".db". + type: string + region: + description: Region is the S3 region. Optional for endpoints + that ignore it. + type: string + required: + - bucket + - credentialsSecretRef + - endpoint + type: object + type: object + x-kubernetes-validations: + - message: exactly one of destination.s3 or destination.pvc must be + set + rule: has(self.s3) != has(self.pvc) + required: + - clusterRef + - destination + type: object + status: + description: EtcdSnapshotStatus is the observed state of an EtcdSnapshot. + properties: + artifact: + description: Artifact is populated once the snapshot completes. + properties: + checksum: + description: Checksum is ":", e.g. "sha256:abc123...". + type: string + sizeBytes: + description: SizeBytes is the snapshot size in bytes. + format: int64 + type: integer + uri: + description: |- + URI is the full snapshot location, e.g. "s3://bucket/key.db" or + "file:///snapshot/data/name.db". + type: string + required: + - uri + type: object + conditions: + description: Conditions represent the latest available observations. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + phase: + description: Phase is the high-level lifecycle phase. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/charts/etcd-operator/templates/_helpers.tpl b/charts/etcd-operator/templates/_helpers.tpl new file mode 100644 index 00000000..475b2cdb --- /dev/null +++ b/charts/etcd-operator/templates/_helpers.tpl @@ -0,0 +1,56 @@ +{{/* Chart name (overridable). */}} +{{- define "etcd-operator.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* Fully qualified resource-name prefix. */}} +{{- define "etcd-operator.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{- define "etcd-operator.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* Common labels. */}} +{{- define "etcd-operator.labels" -}} +helm.sh/chart: {{ include "etcd-operator.chart" . }} +{{ include "etcd-operator.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* Selector labels — stable; also used by the metrics Service selector. */}} +{{- define "etcd-operator.selectorLabels" -}} +app.kubernetes.io/name: {{ include "etcd-operator.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} + +{{/* ServiceAccount name. */}} +{{- define "etcd-operator.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} +{{- include "etcd-operator.fullname" . -}} +{{- else -}} +default +{{- end -}} +{{- end -}} + +{{/* +Full operator image reference. Used for BOTH the manager container image and +its OPERATOR_IMAGE env var — they MUST be identical, or the operator refuses to +start (the snapshot/restore agent runs this same image). +*/}} +{{- define "etcd-operator.image" -}} +{{- printf "%s:%s" .Values.image.repository (.Values.image.tag | default .Chart.AppVersion) -}} +{{- end -}} diff --git a/charts/etcd-operator/templates/deployment.yaml b/charts/etcd-operator/templates/deployment.yaml new file mode 100644 index 00000000..69bbb487 --- /dev/null +++ b/charts/etcd-operator/templates/deployment.yaml @@ -0,0 +1,107 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "etcd-operator.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "etcd-operator.labels" . | nindent 4 }} + control-plane: controller-manager +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "etcd-operator.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: manager + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "etcd-operator.selectorLabels" . | nindent 8 }} + control-plane: controller-manager + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + serviceAccountName: {{ include "etcd-operator.serviceAccountName" . }} + terminationGracePeriodSeconds: 10 + securityContext: + runAsNonRoot: true + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: manager + image: {{ include "etcd-operator.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: + - /manager + args: + - --health-probe-bind-address=:8081 + {{- if .Values.kubeRbacProxy.enabled }} + - --metrics-bind-address=127.0.0.1:8080 + {{- else }} + - --metrics-bind-address=:8080 + {{- end }} + - --leader-elect + env: + # MUST equal the manager image: the operator launches this ref for + # snapshot Jobs and restore init containers, and refuses to start if it + # is left at a placeholder. + - name: OPERATOR_IMAGE + value: {{ include "etcd-operator.image" . }} + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - "ALL" + resources: + {{- toYaml .Values.manager.resources | nindent 10 }} + {{- if .Values.kubeRbacProxy.enabled }} + - name: kube-rbac-proxy + image: {{ .Values.kubeRbacProxy.image.repository }}:{{ .Values.kubeRbacProxy.image.tag }} + imagePullPolicy: {{ .Values.kubeRbacProxy.image.pullPolicy }} + args: + - --secure-listen-address=0.0.0.0:8443 + - --upstream=http://127.0.0.1:8080/ + - --logtostderr=true + - --v=0 + ports: + - containerPort: 8443 + protocol: TCP + name: https + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - "ALL" + resources: + {{- toYaml .Values.kubeRbacProxy.resources | nindent 10 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/etcd-operator/templates/metrics-service.yaml b/charts/etcd-operator/templates/metrics-service.yaml new file mode 100644 index 00000000..c0326a20 --- /dev/null +++ b/charts/etcd-operator/templates/metrics-service.yaml @@ -0,0 +1,18 @@ +{{- if .Values.kubeRbacProxy.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "etcd-operator.fullname" . }}-metrics + namespace: {{ .Release.Namespace }} + labels: + {{- include "etcd-operator.labels" . | nindent 4 }} + control-plane: controller-manager +spec: + ports: + - name: https + port: {{ .Values.metricsService.port }} + protocol: TCP + targetPort: https + selector: + {{- include "etcd-operator.selectorLabels" . | nindent 4 }} +{{- end }} diff --git a/charts/etcd-operator/templates/rbac.yaml b/charts/etcd-operator/templates/rbac.yaml new file mode 100644 index 00000000..d3b0258e --- /dev/null +++ b/charts/etcd-operator/templates/rbac.yaml @@ -0,0 +1,128 @@ +# RBAC. The manager ClusterRole rules below mirror config/rbac/role.yaml (the +# generated source of truth from the +kubebuilder:rbac markers). They change +# rarely; the release-install e2e (helm mode) is the guard — a missing +# permission means the operator can't reconcile and the smoke's 1-node cluster +# never reaches READY. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "etcd-operator.fullname" . }}-manager-role + labels: + {{- include "etcd-operator.labels" . | nindent 4 }} +rules: +- apiGroups: [""] + resources: [persistentvolumeclaims] + verbs: [create, delete, get, list, patch, update, watch] +- apiGroups: [""] + resources: [pods] + verbs: [create, delete, get, list, patch, watch] +- apiGroups: [""] + resources: [pods/log] + verbs: [get] +- apiGroups: [""] + resources: [secrets] + verbs: [get, list, watch] +- apiGroups: [""] + resources: [services] + verbs: [create, get, list, patch, update, watch] +- apiGroups: [batch] + resources: [jobs] + verbs: [create, delete, get, list, watch] +- apiGroups: [cert-manager.io] + resources: [certificates] + verbs: [create, get, list, patch, update, watch] +- apiGroups: [etcd-operator.cozystack.io] + resources: [etcdclusters] + verbs: [get, list, watch] +- apiGroups: [etcd-operator.cozystack.io] + resources: [etcdclusters/finalizers, etcdmembers/finalizers, etcdsnapshots/finalizers] + verbs: [update] +- apiGroups: [etcd-operator.cozystack.io] + resources: [etcdclusters/status, etcdmembers/status, etcdsnapshots/status] + verbs: [get, patch, update] +- apiGroups: [etcd-operator.cozystack.io] + resources: [etcdmembers, etcdsnapshots] + verbs: [create, delete, get, list, patch, update, watch] +- apiGroups: [policy] + resources: [poddisruptionbudgets] + verbs: [create, delete, get, list, patch, update, watch] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "etcd-operator.fullname" . }}-manager-rolebinding + labels: + {{- include "etcd-operator.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "etcd-operator.fullname" . }}-manager-role +subjects: +- kind: ServiceAccount + name: {{ include "etcd-operator.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "etcd-operator.fullname" . }}-leader-election-role + namespace: {{ .Release.Namespace }} + labels: + {{- include "etcd-operator.labels" . | nindent 4 }} +rules: +- apiGroups: [""] + resources: [configmaps] + verbs: [get, list, watch, create, update, patch, delete] +- apiGroups: [coordination.k8s.io] + resources: [leases] + verbs: [get, list, watch, create, update, patch, delete] +- apiGroups: [""] + resources: [events] + verbs: [create, patch] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "etcd-operator.fullname" . }}-leader-election-rolebinding + namespace: {{ .Release.Namespace }} + labels: + {{- include "etcd-operator.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "etcd-operator.fullname" . }}-leader-election-role +subjects: +- kind: ServiceAccount + name: {{ include "etcd-operator.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- if .Values.kubeRbacProxy.enabled }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "etcd-operator.fullname" . }}-proxy-role + labels: + {{- include "etcd-operator.labels" . | nindent 4 }} +rules: +- apiGroups: [authentication.k8s.io] + resources: [tokenreviews] + verbs: [create] +- apiGroups: [authorization.k8s.io] + resources: [subjectaccessreviews] + verbs: [create] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "etcd-operator.fullname" . }}-proxy-rolebinding + labels: + {{- include "etcd-operator.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "etcd-operator.fullname" . }}-proxy-role +subjects: +- kind: ServiceAccount + name: {{ include "etcd-operator.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/etcd-operator/templates/serviceaccount.yaml b/charts/etcd-operator/templates/serviceaccount.yaml new file mode 100644 index 00000000..9efde7c4 --- /dev/null +++ b/charts/etcd-operator/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "etcd-operator.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "etcd-operator.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/etcd-operator/values.yaml b/charts/etcd-operator/values.yaml new file mode 100644 index 00000000..95f91af7 --- /dev/null +++ b/charts/etcd-operator/values.yaml @@ -0,0 +1,73 @@ +# Default values for etcd-operator. + +image: + # -- Operator image repository. The published image is ghcr.io/cozystack/etcd-operator. + repository: ghcr.io/cozystack/etcd-operator + # -- Image tag. Defaults to the chart's appVersion (set to the release tag by the pipeline). + tag: "" + # -- Image pull policy. + pullPolicy: IfNotPresent + +# -- Number of operator replicas (leader election picks the active one). +replicaCount: 1 + +# -- Image pull secrets for private registries. +imagePullSecrets: [] + +# -- Override the chart name portion of resource names. +nameOverride: "" +# -- Override the full resource-name prefix. +fullnameOverride: "" + +serviceAccount: + # -- Create the operator ServiceAccount. + create: true + # -- Extra annotations for the ServiceAccount. + annotations: {} + +# -- Extra annotations for the operator Pod. +podAnnotations: {} +# -- Extra labels for the operator Pod. +podLabels: {} +# -- Node selector for the operator Pod. +nodeSelector: {} +# -- Tolerations for the operator Pod. +tolerations: [] +# -- Affinity for the operator Pod. +affinity: {} + +manager: + # -- Resource requests/limits for the manager container. + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 10m + memory: 64Mi + +# kube-rbac-proxy fronts the manager's /metrics endpoint with SubjectAccessReview +# authz. Mirrors the kustomize config/default install. Disable to expose metrics +# without the proxy (the manager then binds metrics on :8080 directly). +kubeRbacProxy: + # -- Run the kube-rbac-proxy metrics sidecar. + enabled: true + image: + # -- kube-rbac-proxy image repository (upstream's GitHub org; the old gcr.io/kubebuilder one is gone). + repository: ghcr.io/kube-rbac-proxy/kube-rbac-proxy + # -- kube-rbac-proxy image tag. + tag: v0.22.0 + # -- kube-rbac-proxy image pull policy. + pullPolicy: IfNotPresent + # -- Resource requests/limits for the proxy container. + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 5m + memory: 64Mi + +metricsService: + # -- Port the metrics Service exposes (proxy https port). + port: 8443 diff --git a/docs/installation.md b/docs/installation.md index 1fc420ff..4916cbf1 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -15,7 +15,44 @@ For the operator's runtime behaviour see [concepts](concepts.md); for day-2 oper Workload-side: every etcd Pod runs as UID 65532 with `runAsNonRoot=true`, `allowPrivilegeEscalation=false`, all capabilities dropped, and `seccompProfile=RuntimeDefault`. The Pods comply with the `restricted` PodSecurity profile. If your cluster enforces a stricter policy, see `controllers/etcdmember_controller.go`'s `buildPod` for the exact security context the operator emits and adjust accordingly. -## Quick deploy +## Install from a release + +Tagged releases publish a signed multi-arch operator image to GHCR and attach +ready-to-apply install manifests to the GitHub release — no checkout, no build, +no registry of your own. This is the recommended path for consuming a release. + +```sh +# Pick a released version (see https://github.com/cozystack/etcd-operator/releases). +VERSION=v0.5.0 + +# Everything (CRDs + namespace + RBAC + controller Deployment + Service): +kubectl apply -f https://github.com/cozystack/etcd-operator/releases/download/$VERSION/etcd-operator.yaml +``` + +The manifest already points the manager (and its `OPERATOR_IMAGE`, used for +snapshot/restore Pods) at `ghcr.io/cozystack/etcd-operator:$VERSION` — the same +tag whose image the release published, so there is nothing to substitute. + +If you split CRDs from the rest (e.g. CRDs are applied by a separate +cluster-admin step, or server-side-applied to dodge the annotation size limit): + +```sh +kubectl apply --server-side -f https://github.com/cozystack/etcd-operator/releases/download/$VERSION/etcd-operator.crds.yaml +kubectl apply -f https://github.com/cozystack/etcd-operator/releases/download/$VERSION/etcd-operator.non-crds.yaml +``` + +The image is cosign-signed (keyless). To verify before deploying: + +```sh +cosign verify ghcr.io/cozystack/etcd-operator:$VERSION \ + --certificate-identity-regexp 'https://github.com/cozystack/etcd-operator/.github/workflows/.+' \ + --certificate-oidc-issuer https://token.actions.githubusercontent.com +``` + +To pull a prebuilt image without the release manifests (e.g. to feed your own +overlay), the image ref is `ghcr.io/cozystack/etcd-operator:`. + +## Quick deploy (build from source) The repo's Makefile drives a complete install. From a checkout: diff --git a/hack/release-smoke.sh b/hack/release-smoke.sh new file mode 100755 index 00000000..e64d8137 --- /dev/null +++ b/hack/release-smoke.sh @@ -0,0 +1,156 @@ +#!/usr/bin/env bash +# Release-install smoke test: exercises a release install path end to end on a +# throwaway kind cluster, then proves the installed operator actually works. +# +# 1. build the operator image at $IMG (what docker-publish.yml ships) +# 2. kind load it (stand-in for the GHCR push/pull) +# 3. install it one of two ways (INSTALL_MODE): +# manifest (default) — make build-dist-manifests + kubectl apply, the +# path release-assets.yml ships +# helm — helm install charts/etcd-operator, the path +# helm-publish.yml ships +# 4. assert the operator Deployment goes Available +# 5. create a 1-node EtcdCluster and assert it reaches READY +# +# Why this is the right test (vs. grepping the workflow/chart files): the +# contract under test is "the image the release publishes == the image the +# install deploys, and that artifact actually runs and reconciles". The single +# $IMG threaded through build, load, and install makes a tag mismatch +# impossible by construction; step 4 is where a broken mismatch WOULD surface +# (wrong tag => ImagePullBackOff => never Available). It also catches subtler +# failures static checks can't: a broken OPERATOR_IMAGE wiring (the operator +# refuses to start on the placeholder) fails step 4, and a missing RBAC rule in +# the chart fails step 5 (the cluster never goes READY). +set -euo pipefail + +# Always build/load for the host architecture; a stray +# DOCKER_DEFAULT_PLATFORM=linux/amd64 would pull an emulated kind node whose +# control plane never goes healthy. (Same rationale as hack/e2e.sh.) +unset DOCKER_DEFAULT_PLATFORM + +# ── Pinned component versions (kept in sync with hack/e2e.sh) ───────────── +KIND_VERSION=v0.32.0 +KIND_NODE_IMAGE=kindest/node:v1.34.0 + +INSTALL_MODE=${INSTALL_MODE:-manifest} # manifest | helm +KIND_CLUSTER_NAME=${KIND_CLUSTER_NAME:-etcd-operator-release-smoke-$INSTALL_MODE} +NAMESPACE=etcd-operator-system +# A registry-qualified, non-:latest tag: mirrors the real release ref +# (ghcr.io//etcd-operator:) and, being non-latest, makes the +# default imagePullPolicy IfNotPresent — so the kind-loaded image is used +# instead of attempting a registry pull. +IMG=${IMG:-ghcr.io/cozystack/etcd-operator:v0.0.0-smoke} +KEEP_CLUSTER=${KEEP_CLUSTER:-} + +case "$INSTALL_MODE" in + manifest|helm) ;; + *) echo "ERROR: INSTALL_MODE must be 'manifest' or 'helm', got '$INSTALL_MODE'"; exit 2 ;; +esac + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" +LOCALBIN="$ROOT/bin" +mkdir -p "$LOCALBIN" +export PATH="$LOCALBIN:$PATH" + +if ! command -v kind >/dev/null 2>&1; then + echo "--- installing kind $KIND_VERSION into $LOCALBIN" + GOBIN="$LOCALBIN" go install sigs.k8s.io/kind@"$KIND_VERSION" +fi +if [ "$INSTALL_MODE" = helm ] && ! command -v helm >/dev/null 2>&1; then + echo "ERROR: INSTALL_MODE=helm requires helm on PATH"; exit 2 +fi + +dump_diagnostics() { + echo "--- release smoke ($INSTALL_MODE) failed; dumping cluster state before teardown" + kubectl get etcdclusters,etcdmembers,pods -A || true + kubectl -n "$NAMESPACE" get deploy -o wide || true + kubectl -n "$NAMESPACE" describe deploy || true + kubectl -n "$NAMESPACE" logs -l app.kubernetes.io/name=etcd-operator \ + --all-containers --tail=200 || true + kubectl -n "$NAMESPACE" logs -l control-plane=controller-manager \ + --all-containers --tail=200 || true + # The most informative failure signals: an ImagePullBackOff (tag mismatch) + # or the operator's OPERATOR_IMAGE-placeholder refusal show up here. + kubectl -n "$NAMESPACE" get events --sort-by=.lastTimestamp | tail -30 || true + for p in $(kubectl get pods -l etcd-operator.cozystack.io/cluster=smoke -o name 2>/dev/null); do + echo "--- logs: $p" + kubectl logs "$p" --all-containers --tail=100 || true + done +} + +cleanup() { + status=$? + [ "$status" -ne 0 ] && dump_diagnostics + if [ -n "$KEEP_CLUSTER" ]; then + echo "--- KEEP_CLUSTER set; kind cluster '$KIND_CLUSTER_NAME' left running" + return + fi + echo "--- deleting kind cluster '$KIND_CLUSTER_NAME'" + kind delete cluster --name "$KIND_CLUSTER_NAME" >/dev/null 2>&1 || true +} +trap cleanup EXIT + +echo "--- creating kind cluster '$KIND_CLUSTER_NAME' ($KIND_NODE_IMAGE)" +kind create cluster --name "$KIND_CLUSTER_NAME" --image "$KIND_NODE_IMAGE" --wait 5m +kubectl config use-context "kind-$KIND_CLUSTER_NAME" + +echo "--- building operator image ($IMG) and loading it into kind" +docker build -t "$IMG" . +kind load docker-image "$IMG" --name "$KIND_CLUSTER_NAME" + +if [ "$INSTALL_MODE" = manifest ]; then + echo "--- [manifest] rendering release install manifests (IMG=$IMG)" + make build-dist-manifests IMG="$IMG" + echo "--- [manifest] installing from the rendered release manifest" + # Server-side apply: the consolidated manifest embeds the full CRD schemas, + # whose size can exceed the client-side last-applied-config annotation + # limit. This is also the documented release-install path. + kubectl apply --server-side -f dist/etcd-operator.yaml +else + echo "--- [helm] vendoring CRDs and installing the chart (image=$IMG)" + make helm-sync + # Split $IMG into repo:tag for the chart's image.repository / image.tag. + helm upgrade --install etcd-operator charts/etcd-operator \ + --namespace "$NAMESPACE" --create-namespace \ + --set image.repository="${IMG%:*}" \ + --set image.tag="${IMG##*:}" \ + --wait --timeout 5m +fi + +echo "--- waiting for the operator to become Available" +# Fails (times out) on either a tag mismatch (ImagePullBackOff) or a broken +# OPERATOR_IMAGE substitution (operator refuses to start on the placeholder). +# Select by the label both install paths set, so this is mode-agnostic. +kubectl -n "$NAMESPACE" wait deploy \ + -l control-plane=controller-manager \ + --for=condition=Available --timeout=5m + +echo "--- bootstrapping a 1-node EtcdCluster to prove the operator reconciles" +kubectl apply -f - <<'EOF' +apiVersion: etcd-operator.cozystack.io/v1alpha2 +kind: EtcdCluster +metadata: + name: smoke + namespace: default +spec: + replicas: 1 + version: 3.6.11 + storage: + size: 256Mi +EOF + +echo "--- waiting for EtcdCluster 'smoke' to reach READY=1" +# Poll readyMembers rather than `kubectl wait --for=condition`: the cluster's +# Available condition may not be registered on the object until the first +# status write, which makes an early `wait` error out ("no matching condition"). +deadline=$(( $(date +%s) + 300 )) +until [ "$(kubectl get etcdcluster smoke -o jsonpath='{.status.readyMembers}' 2>/dev/null || echo 0)" = "1" ]; do + if [ "$(date +%s)" -ge "$deadline" ]; then + echo "ERROR: EtcdCluster 'smoke' did not reach READY=1 within 5m" + exit 1 + fi + sleep 5 +done + +echo "--- release-install smoke PASSED (mode=$INSTALL_MODE, operator Available, cluster READY=1, IMG=$IMG)" From 7b04604effa2f8fc3d1638584bbb35514c9e9535 Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Wed, 10 Jun 2026 15:42:15 +0400 Subject: [PATCH 2/4] review fixes + retire kustomize Signed-off-by: Andrey Kolkov --- .github/workflows/ci.yml | 10 +- .github/workflows/helm-publish.yml | 12 +- .github/workflows/release-assets.yml | 62 +- .github/workflows/release-smoke.yml | 1 - Makefile | 123 +- README.md | 12 +- api/v1alpha2/validation_envtest_test.go | 7 +- ...cd-operator.cozystack.io_etcdclusters.yaml | 0 ...tcd-operator.cozystack.io_etcdmembers.yaml | 0 ...d-operator.cozystack.io_etcdsnapshots.yaml | 0 .../files/manager-role-rules.yaml | 120 + charts/etcd-operator/templates/crds.yaml | 18 + charts/etcd-operator/templates/namespace.yaml | 15 + charts/etcd-operator/templates/rbac.yaml | 60 +- .../templates/servicemonitor.yaml | 31 + charts/etcd-operator/values.yaml | 25 +- cmd/etcd-migrate/config.go | 17 +- cmd/etcd-migrate/main.go | 14 + cmd/kubectl-etcd/main.go | 11 +- ...cd-operator.cozystack.io_etcdclusters.yaml | 3255 ----------------- ...tcd-operator.cozystack.io_etcdmembers.yaml | 1662 --------- ...d-operator.cozystack.io_etcdsnapshots.yaml | 235 -- config/crd/kustomization.yaml | 23 - config/crd/kustomizeconfig.yaml | 19 - .../patches/cainjection_in_etcdclusters.yaml | 7 - .../crd/patches/webhook_in_etcdclusters.yaml | 16 - config/default/kustomization.yaml | 88 - config/default/manager_auth_proxy_patch.yaml | 58 - config/default/manager_config_patch.yaml | 10 - config/manager/kustomization.yaml | 2 - config/manager/manager.yaml | 117 - config/prometheus/kustomization.yaml | 2 - config/prometheus/monitor.yaml | 26 - .../rbac/auth_proxy_client_clusterrole.yaml | 16 - config/rbac/auth_proxy_role.yaml | 24 - config/rbac/auth_proxy_role_binding.yaml | 19 - config/rbac/auth_proxy_service.yaml | 21 - config/rbac/etcdcluster_editor_role.yaml | 31 - config/rbac/etcdcluster_viewer_role.yaml | 27 - config/rbac/kustomization.yaml | 18 - config/rbac/leader_election_role.yaml | 44 - config/rbac/leader_election_role_binding.yaml | 19 - config/rbac/role.yaml | 126 - config/rbac/role_binding.yaml | 19 - config/rbac/service_account.yaml | 12 - config/samples/kustomization.yaml | 4 - docs/installation.md | 139 +- docs/migration.md | 25 +- docs/operations.md | 2 +- hack/e2e.sh | 10 +- hack/release-smoke.sh | 18 +- main.go | 10 +- 52 files changed, 572 insertions(+), 6070 deletions(-) rename charts/etcd-operator/{crds => crd-bases}/etcd-operator.cozystack.io_etcdclusters.yaml (100%) rename charts/etcd-operator/{crds => crd-bases}/etcd-operator.cozystack.io_etcdmembers.yaml (100%) rename charts/etcd-operator/{crds => crd-bases}/etcd-operator.cozystack.io_etcdsnapshots.yaml (100%) create mode 100644 charts/etcd-operator/files/manager-role-rules.yaml create mode 100644 charts/etcd-operator/templates/crds.yaml create mode 100644 charts/etcd-operator/templates/namespace.yaml create mode 100644 charts/etcd-operator/templates/servicemonitor.yaml delete mode 100644 config/crd/bases/etcd-operator.cozystack.io_etcdclusters.yaml delete mode 100644 config/crd/bases/etcd-operator.cozystack.io_etcdmembers.yaml delete mode 100644 config/crd/bases/etcd-operator.cozystack.io_etcdsnapshots.yaml delete mode 100644 config/crd/kustomization.yaml delete mode 100644 config/crd/kustomizeconfig.yaml delete mode 100644 config/crd/patches/cainjection_in_etcdclusters.yaml delete mode 100644 config/crd/patches/webhook_in_etcdclusters.yaml delete mode 100644 config/default/kustomization.yaml delete mode 100644 config/default/manager_auth_proxy_patch.yaml delete mode 100644 config/default/manager_config_patch.yaml delete mode 100644 config/manager/kustomization.yaml delete mode 100644 config/manager/manager.yaml delete mode 100644 config/prometheus/kustomization.yaml delete mode 100644 config/prometheus/monitor.yaml delete mode 100644 config/rbac/auth_proxy_client_clusterrole.yaml delete mode 100644 config/rbac/auth_proxy_role.yaml delete mode 100644 config/rbac/auth_proxy_role_binding.yaml delete mode 100644 config/rbac/auth_proxy_service.yaml delete mode 100644 config/rbac/etcdcluster_editor_role.yaml delete mode 100644 config/rbac/etcdcluster_viewer_role.yaml delete mode 100644 config/rbac/kustomization.yaml delete mode 100644 config/rbac/leader_election_role.yaml delete mode 100644 config/rbac/leader_election_role_binding.yaml delete mode 100644 config/rbac/role.yaml delete mode 100644 config/rbac/role_binding.yaml delete mode 100644 config/rbac/service_account.yaml delete mode 100644 config/samples/kustomization.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3628f3b5..8d9b1c3e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,10 +18,12 @@ jobs: cache: true - name: codegen drift - # If a contributor edits an API type without re-running codegen, - # this gate catches it before CRDs and deepcopy ship out of sync - # with the Go types. Runs before `make test` so the as-committed - # state of generated files is what we check. + # If a contributor edits an API type or +kubebuilder:rbac markers + # without re-running codegen, this gate catches it before the chart's + # CRDs (charts/etcd-operator/crd-bases), manager RBAC rules + # (charts/etcd-operator/files/manager-role-rules.yaml), or deepcopy ship + # out of sync. Runs before `make test` so the as-committed state of + # generated files is what we check. run: | make generate manifests if ! git diff --quiet --exit-code; then diff --git a/.github/workflows/helm-publish.yml b/.github/workflows/helm-publish.yml index 3d195eb0..0b46978a 100644 --- a/.github/workflows/helm-publish.yml +++ b/.github/workflows/helm-publish.yml @@ -24,9 +24,11 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - # make helm-sync regenerates the CRDs (controller-gen) and copies them - # into the chart, so the published package always carries CRDs matching - # the tagged API types — never a stale committed copy. + # make manifests regenerates the CRDs and manager RBAC rules (controller-gen) + # straight into the chart, so the published package always matches the + # tagged API types and +kubebuilder:rbac markers — never a stale committed + # copy. (ci.yml's drift gate already enforces this on PRs; this is belt-and- + # suspenders at publish time.) - uses: actions/setup-go@v5 with: go-version-file: go.mod @@ -37,8 +39,8 @@ jobs: with: version: 'v3.16.4' - - name: Sync CRDs into the chart - run: make helm-sync + - name: Regenerate CRDs and RBAC into the chart + run: make manifests - name: Resolve chart versions from tag run: | diff --git a/.github/workflows/release-assets.yml b/.github/workflows/release-assets.yml index fd1b36e7..68b3f2d3 100644 --- a/.github/workflows/release-assets.yml +++ b/.github/workflows/release-assets.yml @@ -2,9 +2,10 @@ name: Upload release assets # When a GitHub release is created for a tag, render the install manifests for # that tag's image and attach them to the release. `make build-dist-manifests` -# sets the `controller` image (and, via the kustomize replacement in -# config/default, the manager's OPERATOR_IMAGE env) to the released ref, so the -# attached YAML deploys the matching operator and its snapshot/restore agent. +# is `helm template` of the chart with image.repository/tag set to the released +# ref; the chart renders image == OPERATOR_IMAGE, so the attached YAML deploys +# the matching operator and its snapshot/restore agent. For consumers who +# kubectl-apply rather than helm-install. on: release: @@ -28,6 +29,12 @@ jobs: go-version-file: go.mod cache: true + # build-dist-manifests renders the chart via `helm template`. + - name: Install Helm + uses: azure/setup-helm@v4 + with: + version: 'v3.16.4' + - name: Resolve release tag run: echo "RELEASE_TAG=${{ github.ref_name }}" >> $GITHUB_ENV @@ -57,3 +64,52 @@ jobs: asset_name: etcd-operator.non-crds.yaml tag: ${{ github.ref }} overwrite: true + + # Standalone client CLIs (kubectl-etcd plugin + etcd-migrate). They are not in + # the operator image (client-side / admin tools), so they ship as + # cross-compiled release binaries. Separate job: no Helm needed, and a failure + # here doesn't block the manifest assets above. + cli-binaries: + runs-on: ubuntu-22.04 + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + + - name: Resolve release tag + run: echo "RELEASE_TAG=${{ github.ref_name }}" >> $GITHUB_ENV + + - name: Cross-compile CLIs + run: make dist-cli VERSION=${RELEASE_TAG} + + - name: Upload etcd-migrate binaries + uses: svenstaro/upload-release-action@2.9.0 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: dist/etcd-migrate-* + file_glob: true + tag: ${{ github.ref }} + overwrite: true + + - name: Upload kubectl-etcd binaries + uses: svenstaro/upload-release-action@2.9.0 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: dist/kubectl-etcd-* + file_glob: true + tag: ${{ github.ref }} + overwrite: true + + - name: Upload CLI checksums + uses: svenstaro/upload-release-action@2.9.0 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: dist/cli-SHA256SUMS.txt + asset_name: cli-SHA256SUMS.txt + tag: ${{ github.ref }} + overwrite: true diff --git a/.github/workflows/release-smoke.yml b/.github/workflows/release-smoke.yml index 4ca72973..b4a057c6 100644 --- a/.github/workflows/release-smoke.yml +++ b/.github/workflows/release-smoke.yml @@ -20,7 +20,6 @@ on: - 'charts/**' - 'Makefile' - 'Dockerfile' - - 'config/**' - 'api/**' workflow_dispatch: diff --git a/Makefile b/Makefile index af38e2d1..80060fa4 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,11 @@ # Image URL to use all building/pushing image targets IMG ?= controller:latest + +# Version stamped into the standalone CLIs (etcd-migrate, kubectl-etcd) via +# -ldflags. Defaults to `git describe`; the release workflow passes the tag. +VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev) +CLI_LDFLAGS ?= -X main.version=$(VERSION) # ENVTEST_K8S_VERSION is derived from the k8s.io/api version in go.mod so a # dependency bump automatically pulls the matching envtest assets — no need # to remember to update this in two places. (Pattern stolen from @@ -42,8 +47,20 @@ help: ## Display this help. ##@ Development .PHONY: manifests -manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. - $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases +manifests: controller-gen yq ## Generate CRDs and the manager RBAC rules straight into the Helm chart. + # CRDs land in charts/etcd-operator/crd-bases/ (templates/crds.yaml renders + # them, with the helm.sh/resource-policy:keep annotation); the manager + # ClusterRole rules land in charts/etcd-operator/files/ for templates/rbac.yaml + # to pull in via .Files.Get. ci.yml's codegen-drift gate (make manifests + + # git diff) then guards BOTH against drift from the API types and the + # +kubebuilder:rbac markers — no second source of truth, no grep guard. + $(CONTROLLER_GEN) rbac:roleName=manager-role crd paths="./..." \ + output:crd:artifacts:config=charts/etcd-operator/crd-bases \ + output:rbac:artifacts:config=charts/etcd-operator/files + # controller-gen emits a whole ClusterRole; the chart only needs its rules + # (it wraps them in a release-named, labelled ClusterRole of its own). + $(YQ) eval '.rules' charts/etcd-operator/files/role.yaml > charts/etcd-operator/files/manager-role-rules.yaml + rm -f charts/etcd-operator/files/role.yaml .PHONY: generate generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. @@ -74,7 +91,7 @@ release-smoke: ## Smoke-test the tag-release manifest install path on kind: buil hack/release-smoke.sh .PHONY: helm-smoke -helm-smoke: helm-sync ## Smoke-test the Helm chart install path on kind: build image -> helm install chart -> assert operator Available and a 1-node cluster READY. KEEP_CLUSTER=1 keeps the cluster. +helm-smoke: ## Smoke-test the Helm chart install path on kind: build image -> helm install chart -> assert operator Available and a 1-node cluster READY. KEEP_CLUSTER=1 keeps the cluster. INSTALL_MODE=helm hack/release-smoke.sh ##@ Build @@ -85,11 +102,27 @@ build: manifests generate fmt vet ## Build manager binary. .PHONY: kubectl-etcd kubectl-etcd: fmt vet ## Build the kubectl-etcd plugin binary. - go build -o bin/kubectl-etcd ./cmd/kubectl-etcd + go build -ldflags "$(CLI_LDFLAGS)" -o bin/kubectl-etcd ./cmd/kubectl-etcd .PHONY: etcd-migrate etcd-migrate: fmt vet ## Build the etcd-migrate (legacy v1alpha1 -> v1alpha2) CLI binary. - go build -o bin/etcd-migrate ./cmd/etcd-migrate + go build -ldflags "$(CLI_LDFLAGS)" -o bin/etcd-migrate ./cmd/etcd-migrate + +.PHONY: dist-cli +dist-cli: ## Cross-compile etcd-migrate and kubectl-etcd into dist/ for release (linux/darwin x amd64/arm64). VERSION stamps the binary version. + # Produces the standalone CLIs the release-assets workflow attaches to a + # release, named --, plus a SHA256 checksum file. These are + # client-side tools (kubectl-etcd is a kubectl plugin, etcd-migrate is an + # admin-run migration CLI), so they ship as binaries, not in the operator image. + mkdir -p dist + for os in linux darwin; do for arch in amd64 arm64; do \ + for cmd in etcd-migrate kubectl-etcd; do \ + echo "building dist/$$cmd-$$os-$$arch"; \ + CGO_ENABLED=0 GOOS=$$os GOARCH=$$arch \ + go build -ldflags "$(CLI_LDFLAGS)" -o dist/$$cmd-$$os-$$arch ./cmd/$$cmd; \ + done; \ + done; done + cd dist && { command -v sha256sum >/dev/null 2>&1 && sha256sum etcd-migrate-* kubectl-etcd-* || shasum -a 256 etcd-migrate-* kubectl-etcd-*; } > cli-SHA256SUMS.txt .PHONY: run run: manifests generate fmt vet ## Run a controller from your host. @@ -124,51 +157,46 @@ docker-buildx: test ## Build and push docker image for the manager for cross-pla rm Dockerfile.cross .PHONY: build-dist-manifests -build-dist-manifests: manifests generate kustomize yq ## Render the release install manifests into dist/ for IMG. - # Produces the YAMLs the release-assets workflow attaches to a tag: - # dist/etcd-operator.yaml – everything (CRDs + deployment) +build-dist-manifests: manifests generate require-helm yq ## Render the release install manifests into dist/ for IMG. + # Produces the YAMLs the release-assets workflow attaches to a tag, for users + # who kubectl-apply instead of helm-install: + # dist/etcd-operator.yaml – everything (Namespace + CRDs + operator) # dist/etcd-operator.crds.yaml – CRDs only # dist/etcd-operator.non-crds.yaml – everything except CRDs - # Setting the `controller` image also propagates into the manager's - # OPERATOR_IMAGE env via the kustomize replacement in config/default, so - # the snapshot/restore agent runs the same ref. Pass IMG=/etcd-operator:. + # This is just `helm template` of the chart, so the rendered manifest IS the + # chart: the image == OPERATOR_IMAGE wiring and the RBAC come from one source. + # namespace.create emits the Namespace so a bare `kubectl apply -f + # etcd-operator.yaml` is self-contained. Rendering is pure — no tracked file + # is mutated. Pass IMG=/etcd-operator:. mkdir -p dist - cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} - $(KUSTOMIZE) build config/default > dist/etcd-operator.yaml - $(KUSTOMIZE) build config/default | $(YQ) eval 'select(.kind != "CustomResourceDefinition")' - > dist/etcd-operator.non-crds.yaml - $(KUSTOMIZE) build config/default | $(YQ) eval 'select(.kind == "CustomResourceDefinition")' - > dist/etcd-operator.crds.yaml - -.PHONY: helm-sync -helm-sync: manifests ## Vendor the generated CRDs into the Helm chart's crds/ directory. - # The chart's RBAC/Deployment templates are hand-authored from config/, but - # the CRDs (large, schema-bearing, regenerated by controller-gen) are copied - # verbatim so they never drift from the API types. helm-publish.yml re-runs - # this before packaging so a published chart always carries current CRDs. - mkdir -p charts/etcd-operator/crds - cp config/crd/bases/*.yaml charts/etcd-operator/crds/ + img='$(IMG)'; $(HELM) template etcd-operator charts/etcd-operator \ + --namespace etcd-operator-system \ + --set image.repository="$${img%:*}" --set image.tag="$${img##*:}" \ + --set namespace.create=true \ + > dist/etcd-operator.yaml + $(YQ) eval 'select(.kind != "CustomResourceDefinition")' dist/etcd-operator.yaml > dist/etcd-operator.non-crds.yaml + $(YQ) eval 'select(.kind == "CustomResourceDefinition")' dist/etcd-operator.yaml > dist/etcd-operator.crds.yaml ##@ Deployment -ifndef ignore-not-found - ignore-not-found = false -endif - -.PHONY: install -install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. - $(KUSTOMIZE) build config/crd | kubectl apply -f - - -.PHONY: uninstall -uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. - $(KUSTOMIZE) build config/crd | kubectl delete --ignore-not-found=$(ignore-not-found) -f - +# The Helm-driven install targets below. The chart is the single source of +# truth for CRDs, RBAC, and the manager Deployment. +HELM_RELEASE ?= etcd-operator +NAMESPACE ?= etcd-operator-system .PHONY: deploy -deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. - cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} - $(KUSTOMIZE) build config/default | kubectl apply -f - +deploy: manifests require-helm ## Install/upgrade the operator (CRDs + RBAC + manager) via Helm. Pass IMG=/etcd-operator:. + # The chart renders image == OPERATOR_IMAGE, so there is no separate image- + # replacement step; CRDs are templated into the release so `helm upgrade` + # keeps them current. The IMG split into repository:tag handles registry ports. + img='$(IMG)'; $(HELM) upgrade --install $(HELM_RELEASE) charts/etcd-operator \ + --namespace $(NAMESPACE) --create-namespace \ + --set image.repository="$${img%:*}" --set image.tag="$${img##*:}" \ + --wait --timeout 5m .PHONY: undeploy -undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. - $(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f - +undeploy: require-helm ## Uninstall the operator release. CRDs carry resource-policy:keep, so EtcdClusters survive — delete them (and the CRDs) by hand to wipe data. + $(HELM) uninstall $(HELM_RELEASE) --namespace $(NAMESPACE) ##@ Build Dependencies @@ -178,16 +206,17 @@ $(LOCALBIN): mkdir -p $(LOCALBIN) ## Tool Versions -KUSTOMIZE_VERSION ?= v5.6.0 CONTROLLER_TOOLS_VERSION ?= v0.18.0 YQ_VERSION ?= v4.44.1 ## Tool Binaries (version-suffixed so a version bump auto-triggers reinstall ## and stale builds of an old version stay on disk alongside the new one). -KUSTOMIZE ?= $(LOCALBIN)/kustomize-$(KUSTOMIZE_VERSION) CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen-$(CONTROLLER_TOOLS_VERSION) ENVTEST ?= $(LOCALBIN)/setup-envtest YQ ?= $(LOCALBIN)/yq-$(YQ_VERSION) +# Helm is the one tool we don't vendor (no clean `go install`); it must be on +# PATH. release-smoke/e2e and the publish workflows install it via setup-helm. +HELM ?= helm # go-install-tool installs $2@$3 under $1. `go install` drops the binary at # $LOCALBIN/, so we rename it after install to the version-suffixed @@ -202,10 +231,12 @@ mv "$$(echo "$(1)" | sed "s/-$(3)$$//")" $(1) ;\ } endef -.PHONY: kustomize -kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. -$(KUSTOMIZE): $(LOCALBIN) - $(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION)) +.PHONY: require-helm +require-helm: ## Assert Helm is on PATH (used by deploy/undeploy/build-dist-manifests). + @command -v $(HELM) >/dev/null 2>&1 || { \ + echo "ERROR: helm not found on PATH. Install Helm v3.16+ (https://helm.sh/docs/intro/install/)."; \ + exit 1; \ + } .PHONY: controller-gen controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. diff --git a/README.md b/README.md index e8a2d0a0..aa9774d0 100644 --- a/README.md +++ b/README.md @@ -37,12 +37,12 @@ No multi-user / per-tenant RBAC inside etcd — single-user `root` auth is avail ## Quick start ```sh -# 1. Install CRDs and the operator. Builds an image and pushes it to your -# registry; substitute IMG= for a prebuilt tag if you have one. The cluster -# must be able to pull from — for local clusters (kind / -# minikube / k3d) sideload the image or use an ephemeral registry such as -# ttl.sh, otherwise the operator Deployment will sit in ImagePullBackOff. -make install +# 1. Install the operator (CRDs + RBAC + manager) with Helm. Builds an image and +# pushes it to your registry; substitute IMG= for a prebuilt tag if you have +# one. The cluster must be able to pull from — for local +# clusters (kind / minikube / k3d) sideload the image or use an ephemeral +# registry such as ttl.sh, otherwise the Deployment sits in ImagePullBackOff. +# `make deploy` runs `helm upgrade --install` (needs helm v3.16+ on PATH). make docker-build docker-push deploy IMG=/etcd-operator: # 2. Create a cluster. diff --git a/api/v1alpha2/validation_envtest_test.go b/api/v1alpha2/validation_envtest_test.go index f28483e8..156c9dbf 100644 --- a/api/v1alpha2/validation_envtest_test.go +++ b/api/v1alpha2/validation_envtest_test.go @@ -77,11 +77,12 @@ func TestMain(m *testing.M) { os.Exit(code) } -// crdBasesDir resolves config/crd/bases relative to this test file — -// go test's CWD is the package directory and the CRDs live two levels up. +// crdBasesDir resolves the chart's raw generated CRDs relative to this test +// file — go test's CWD is the package directory and the CRDs (written by +// `make manifests`) live under charts/etcd-operator/crd-bases two levels up. func crdBasesDir() string { _, here, _, _ := runtime.Caller(0) - return filepath.Join(filepath.Dir(here), "..", "..", "config", "crd", "bases") + return filepath.Join(filepath.Dir(here), "..", "..", "charts", "etcd-operator", "crd-bases") } func ptr32(v int32) *int32 { return &v } diff --git a/charts/etcd-operator/crds/etcd-operator.cozystack.io_etcdclusters.yaml b/charts/etcd-operator/crd-bases/etcd-operator.cozystack.io_etcdclusters.yaml similarity index 100% rename from charts/etcd-operator/crds/etcd-operator.cozystack.io_etcdclusters.yaml rename to charts/etcd-operator/crd-bases/etcd-operator.cozystack.io_etcdclusters.yaml diff --git a/charts/etcd-operator/crds/etcd-operator.cozystack.io_etcdmembers.yaml b/charts/etcd-operator/crd-bases/etcd-operator.cozystack.io_etcdmembers.yaml similarity index 100% rename from charts/etcd-operator/crds/etcd-operator.cozystack.io_etcdmembers.yaml rename to charts/etcd-operator/crd-bases/etcd-operator.cozystack.io_etcdmembers.yaml diff --git a/charts/etcd-operator/crds/etcd-operator.cozystack.io_etcdsnapshots.yaml b/charts/etcd-operator/crd-bases/etcd-operator.cozystack.io_etcdsnapshots.yaml similarity index 100% rename from charts/etcd-operator/crds/etcd-operator.cozystack.io_etcdsnapshots.yaml rename to charts/etcd-operator/crd-bases/etcd-operator.cozystack.io_etcdsnapshots.yaml diff --git a/charts/etcd-operator/files/manager-role-rules.yaml b/charts/etcd-operator/files/manager-role-rules.yaml new file mode 100644 index 00000000..4ad4b945 --- /dev/null +++ b/charts/etcd-operator/files/manager-role-rules.yaml @@ -0,0 +1,120 @@ +- apiGroups: + - "" + resources: + - persistentvolumeclaims + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - pods + verbs: + - create + - delete + - get + - list + - patch + - watch +- apiGroups: + - "" + resources: + - pods/log + verbs: + - get +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - services + verbs: + - create + - get + - list + - patch + - update + - watch +- apiGroups: + - batch + resources: + - jobs + verbs: + - create + - delete + - get + - list + - watch +- apiGroups: + - cert-manager.io + resources: + - certificates + verbs: + - create + - get + - list + - patch + - update + - watch +- apiGroups: + - etcd-operator.cozystack.io + resources: + - etcdclusters + verbs: + - get + - list + - watch +- apiGroups: + - etcd-operator.cozystack.io + resources: + - etcdclusters/finalizers + - etcdmembers/finalizers + - etcdsnapshots/finalizers + verbs: + - update +- apiGroups: + - etcd-operator.cozystack.io + resources: + - etcdclusters/status + - etcdmembers/status + - etcdsnapshots/status + verbs: + - get + - patch + - update +- apiGroups: + - etcd-operator.cozystack.io + resources: + - etcdmembers + - etcdsnapshots + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - policy + resources: + - poddisruptionbudgets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch diff --git a/charts/etcd-operator/templates/crds.yaml b/charts/etcd-operator/templates/crds.yaml new file mode 100644 index 00000000..937a3938 --- /dev/null +++ b/charts/etcd-operator/templates/crds.yaml @@ -0,0 +1,18 @@ +{{- /* +CRDs, templated (not Helm's install-only crds/ dir) so `helm upgrade` keeps +them current with the release. The raw, controller-gen-generated CRDs live in +crd-bases/ (written by `make manifests`, guarded by ci.yml's codegen-drift +gate); we round-trip each through fromYaml/toYaml to inject the +helm.sh/resource-policy:keep annotation, which stops `helm uninstall` from +deleting the CRDs — that would cascade-delete every EtcdCluster and its data. +*/ -}} +{{- if .Values.crds.enabled }} +{{- range $path, $_ := .Files.Glob "crd-bases/*.yaml" }} +{{- $crd := $.Files.Get $path | fromYaml }} +{{- if $.Values.crds.keep }} +{{- $_ := set $crd.metadata "annotations" (merge (dict "helm.sh/resource-policy" "keep") (default (dict) $crd.metadata.annotations)) }} +{{- end }} +--- +{{ toYaml $crd }} +{{- end }} +{{- end }} diff --git a/charts/etcd-operator/templates/namespace.yaml b/charts/etcd-operator/templates/namespace.yaml new file mode 100644 index 00000000..2ce49424 --- /dev/null +++ b/charts/etcd-operator/templates/namespace.yaml @@ -0,0 +1,15 @@ +{{- /* +Only rendered for `helm template`-based manifest rendering (build-dist-manifests +sets namespace.create=true) so a bare `kubectl apply -f etcd-operator.yaml` is +self-contained. Real `helm install` uses --create-namespace instead, so this +stays off by default. +*/ -}} +{{- if .Values.namespace.create }} +apiVersion: v1 +kind: Namespace +metadata: + name: {{ .Release.Namespace }} + labels: + {{- include "etcd-operator.labels" . | nindent 4 }} + control-plane: controller-manager +{{- end }} diff --git a/charts/etcd-operator/templates/rbac.yaml b/charts/etcd-operator/templates/rbac.yaml index d3b0258e..4989dfb4 100644 --- a/charts/etcd-operator/templates/rbac.yaml +++ b/charts/etcd-operator/templates/rbac.yaml @@ -1,8 +1,8 @@ -# RBAC. The manager ClusterRole rules below mirror config/rbac/role.yaml (the -# generated source of truth from the +kubebuilder:rbac markers). They change -# rarely; the release-install e2e (helm mode) is the guard — a missing -# permission means the operator can't reconcile and the smoke's 1-node cluster -# never reaches READY. +# RBAC for the operator. The manager ClusterRole's rules are generated from the +# +kubebuilder:rbac markers into files/manager-role-rules.yaml (by `make +# manifests`) and pulled in below — a single source of truth, guarded by +# ci.yml's codegen-drift gate. The release-scoped name, labels, binding, and the +# leader-election / proxy roles stay templated here. apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: @@ -10,42 +10,7 @@ metadata: labels: {{- include "etcd-operator.labels" . | nindent 4 }} rules: -- apiGroups: [""] - resources: [persistentvolumeclaims] - verbs: [create, delete, get, list, patch, update, watch] -- apiGroups: [""] - resources: [pods] - verbs: [create, delete, get, list, patch, watch] -- apiGroups: [""] - resources: [pods/log] - verbs: [get] -- apiGroups: [""] - resources: [secrets] - verbs: [get, list, watch] -- apiGroups: [""] - resources: [services] - verbs: [create, get, list, patch, update, watch] -- apiGroups: [batch] - resources: [jobs] - verbs: [create, delete, get, list, watch] -- apiGroups: [cert-manager.io] - resources: [certificates] - verbs: [create, get, list, patch, update, watch] -- apiGroups: [etcd-operator.cozystack.io] - resources: [etcdclusters] - verbs: [get, list, watch] -- apiGroups: [etcd-operator.cozystack.io] - resources: [etcdclusters/finalizers, etcdmembers/finalizers, etcdsnapshots/finalizers] - verbs: [update] -- apiGroups: [etcd-operator.cozystack.io] - resources: [etcdclusters/status, etcdmembers/status, etcdsnapshots/status] - verbs: [get, patch, update] -- apiGroups: [etcd-operator.cozystack.io] - resources: [etcdmembers, etcdsnapshots] - verbs: [create, delete, get, list, patch, update, watch] -- apiGroups: [policy] - resources: [poddisruptionbudgets] - verbs: [create, delete, get, list, patch, update, watch] + {{- .Files.Get "files/manager-role-rules.yaml" | nindent 2 }} --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding @@ -125,4 +90,17 @@ subjects: - kind: ServiceAccount name: {{ include "etcd-operator.serviceAccountName" . }} namespace: {{ .Release.Namespace }} +--- +# Convenience role granting GET on /metrics through the proxy (e.g. for a +# Prometheus scrape identity). Folded in from config/rbac's metrics-reader; +# the chart defines it but does not bind it. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "etcd-operator.fullname" . }}-metrics-reader + labels: + {{- include "etcd-operator.labels" . | nindent 4 }} +rules: +- nonResourceURLs: ["/metrics"] + verbs: [get] {{- end }} diff --git a/charts/etcd-operator/templates/servicemonitor.yaml b/charts/etcd-operator/templates/servicemonitor.yaml new file mode 100644 index 00000000..f8c0e6a1 --- /dev/null +++ b/charts/etcd-operator/templates/servicemonitor.yaml @@ -0,0 +1,31 @@ +{{- /* +prometheus-operator ServiceMonitor for the metrics endpoint (folded in from the +old config/prometheus). Off by default: it needs the monitoring.coreos.com CRDs +installed, and it scrapes the kube-rbac-proxy https port, so it only makes sense +when kubeRbacProxy is enabled. +*/ -}} +{{- if .Values.metrics.serviceMonitor.enabled }} +{{- if not .Values.kubeRbacProxy.enabled }} +{{- fail "metrics.serviceMonitor.enabled requires kubeRbacProxy.enabled (the ServiceMonitor scrapes the proxy's https metrics port)" }} +{{- end }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "etcd-operator.fullname" . }}-metrics + namespace: {{ .Release.Namespace }} + labels: + {{- include "etcd-operator.labels" . | nindent 4 }} + control-plane: controller-manager +spec: + endpoints: + - path: /metrics + port: https + scheme: https + bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token + tlsConfig: + insecureSkipVerify: true + selector: + matchLabels: + {{- include "etcd-operator.selectorLabels" . | nindent 6 }} + control-plane: controller-manager +{{- end }} diff --git a/charts/etcd-operator/values.yaml b/charts/etcd-operator/values.yaml index 95f91af7..9205d58f 100644 --- a/charts/etcd-operator/values.yaml +++ b/charts/etcd-operator/values.yaml @@ -1,5 +1,26 @@ # Default values for etcd-operator. +crds: + # -- Render the CRDs as part of the release. Templated (not Helm's install-only + # crds/ dir) so `helm upgrade` keeps them current with the API types. + enabled: true + # -- Annotate CRDs with helm.sh/resource-policy: keep so `helm uninstall` leaves + # them in place. Strongly recommended: deleting the CRDs cascade-deletes every + # EtcdCluster and its data. + keep: true + +# -- Render a Namespace object. Off by default (real `helm install` uses +# --create-namespace); build-dist-manifests turns it on so the rendered +# kubectl-apply manifest is self-contained. +namespace: + create: false + +metrics: + serviceMonitor: + # -- Create a prometheus-operator ServiceMonitor for the metrics endpoint. + # Requires the monitoring.coreos.com CRDs and kubeRbacProxy.enabled. + enabled: false + image: # -- Operator image repository. The published image is ghcr.io/cozystack/etcd-operator. repository: ghcr.io/cozystack/etcd-operator @@ -47,8 +68,8 @@ manager: memory: 64Mi # kube-rbac-proxy fronts the manager's /metrics endpoint with SubjectAccessReview -# authz. Mirrors the kustomize config/default install. Disable to expose metrics -# without the proxy (the manager then binds metrics on :8080 directly). +# authz. Disable to expose metrics without the proxy (the manager then binds +# metrics on :8080 directly). kubeRbacProxy: # -- Run the kube-rbac-proxy metrics sidecar. enabled: true diff --git a/cmd/etcd-migrate/config.go b/cmd/etcd-migrate/config.go index 8b65d152..3c0a8b29 100644 --- a/cmd/etcd-migrate/config.go +++ b/cmd/etcd-migrate/config.go @@ -21,10 +21,15 @@ import ( "k8s.io/client-go/util/homedir" ) -// defaultControllerRef is where both this repo's kustomize config and the -// legacy repo's deploy the controller; the two generations share the name, -// so a single Deployment commonly answers both checks. -const defaultControllerRef = "etcd-operator-system/etcd-operator-controller-manager" +// defaultLegacyControllerRef is where the legacy v1alpha1 repo's kustomize +// install deploys its controller. +const defaultLegacyControllerRef = "etcd-operator-system/etcd-operator-controller-manager" + +// defaultNewControllerRef is where this repo's Helm chart deploys the operator — +// release name "etcd-operator" in the etcd-operator-system namespace. (The +// generations no longer share a name: kustomize named it +// etcd-operator-controller-manager; the chart names it after the release.) +const defaultNewControllerRef = "etcd-operator-system/etcd-operator" // Config holds every flag of the migrate CLI. type Config struct { @@ -72,8 +77,8 @@ func bindFlags(cmd *cobra.Command, cfg *Config) { f.BoolVar(&cfg.Apply, "apply", false, "Execute the adoption. Without it the tool only prints the plan (dry-run).") f.BoolVarP(&cfg.Yes, "yes", "y", false, "Skip the interactive confirmation before --apply mutates the cluster") f.BoolVar(&cfg.SkipControllerCheck, "skip-controller-check", false, "Skip verifying that both operator Deployments are scaled down") - f.StringVar(&cfg.LegacyController, "legacy-controller", defaultControllerRef, "Legacy operator Deployment as namespace/name") - f.StringVar(&cfg.NewController, "new-controller", defaultControllerRef, "New operator Deployment as namespace/name") + f.StringVar(&cfg.LegacyController, "legacy-controller", defaultLegacyControllerRef, "Legacy operator Deployment as namespace/name") + f.StringVar(&cfg.NewController, "new-controller", defaultNewControllerRef, "New operator Deployment as namespace/name") f.StringVar(&cfg.Version, "version", "", "etcd version (X.Y.Z) to set on every migrated cluster, overriding image-tag extraction") f.StringVar(&cfg.AuthSecret, "auth-secret", "", "Existing kubernetes.io/basic-auth Secret (in each cluster's namespace) to reference for clusters with enableAuth; default generates one per cluster") diff --git a/cmd/etcd-migrate/main.go b/cmd/etcd-migrate/main.go index d9a2eee4..af1ab3fd 100644 --- a/cmd/etcd-migrate/main.go +++ b/cmd/etcd-migrate/main.go @@ -38,6 +38,10 @@ import ( "github.com/cozystack/etcd-operator/internal/migrate" ) +// version is stamped at build time via -ldflags "-X main.version=" +// (see the Makefile's CLI_LDFLAGS); "dev" for un-stamped local builds. +var version = "dev" + func main() { cfg := &Config{} rootCmd := &cobra.Command{ @@ -66,6 +70,16 @@ explicit --skip-backup.`, }, } bindFlags(rootCmd, cfg) + // A `version` subcommand rather than a --version flag: --version is already + // taken by the etcd-version override (see bindFlags). + rootCmd.AddCommand(&cobra.Command{ + Use: "version", + Short: "Print the etcd-migrate binary version", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, _ []string) { + fmt.Fprintln(cmd.OutOrStdout(), version) + }, + }) if err := rootCmd.Execute(); err != nil { os.Exit(1) } diff --git a/cmd/kubectl-etcd/main.go b/cmd/kubectl-etcd/main.go index c1328371..ec2d62d6 100644 --- a/cmd/kubectl-etcd/main.go +++ b/cmd/kubectl-etcd/main.go @@ -27,11 +27,16 @@ import ( "github.com/cozystack/etcd-operator/internal/portforward" ) +// version is stamped at build time via -ldflags "-X main.version=" +// (see the Makefile's CLI_LDFLAGS); "dev" for un-stamped local builds. +var version = "dev" + func main() { var rootCmd = &cobra.Command{ - Use: "kubectl-etcd", - Short: "Kubectl etcd plugin", - Long: `Manage etcd pods spawned by etcd-operator`, + Use: "kubectl-etcd", + Version: version, + Short: "Kubectl etcd plugin", + Long: `Manage etcd pods spawned by etcd-operator`, // Subcommands report failures by returning an error (RunE); a runtime // failure is not a usage error, so don't dump the help text for it. SilenceUsage: true, diff --git a/config/crd/bases/etcd-operator.cozystack.io_etcdclusters.yaml b/config/crd/bases/etcd-operator.cozystack.io_etcdclusters.yaml deleted file mode 100644 index 6be40900..00000000 --- a/config/crd/bases/etcd-operator.cozystack.io_etcdclusters.yaml +++ /dev/null @@ -1,3255 +0,0 @@ ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.18.0 - name: etcdclusters.etcd-operator.cozystack.io -spec: - group: etcd-operator.cozystack.io - names: - kind: EtcdCluster - listKind: EtcdClusterList - plural: etcdclusters - singular: etcdcluster - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.version - name: Version - type: string - - jsonPath: .spec.replicas - name: Replicas - type: integer - - jsonPath: .status.readyMembers - name: Ready - type: integer - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha2 - schema: - openAPIV3Schema: - description: EtcdCluster is the Schema for the etcdclusters API. - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: |- - EtcdClusterSpec defines the desired state of an etcd cluster. - - CEL validation rules (k8s 1.29+ apiserver-enforced; both - CustomResourceValidationExpressions and the quantity() extension - are GA in 1.29): - - - storage.medium=Memory + replicas=0 wedges the cluster on resume - (the dormant flip deletes the Pod and the tmpfs goes with it but - the resume path treats the member as if its data were preserved). - Reject the combination outright; recreate is the only safe path. - - - storage.medium=Memory requires storage.size > 0. Without a SizeLimit - the tmpfs is unbounded against node memory, which defeats the whole - point of opting into memory backing. - properties: - additionalMetadata: - description: |- - AdditionalMetadata holds extra labels and annotations the operator - merges onto every object it creates for this cluster — member Pods, - the per-member data PVCs, the client and headless Services, the - PodDisruptionBudget, and the EtcdMember CRs. Operator-owned keys - (the app.kubernetes.io/* set and the cluster/role labels, and any - operator-set annotation) always win on collision, so this cannot be - used to shadow the operator's own metadata. - - Like spec.resources, changes take effect on newly-created objects - (scale-up, replacement); the operator does not re-stamp existing - Pods in place. The value is latched through status.observed with the - rest of the target spec, so a mid-flight edit only applies once the - current target is reached. - properties: - annotations: - additionalProperties: - type: string - description: Annotations are extra annotations merged onto created - objects. - type: object - labels: - additionalProperties: - type: string - description: |- - Labels are extra labels merged onto created objects. Operator-owned - label keys take precedence on collision. - type: object - type: object - affinity: - description: |- - Affinity sets the scheduling affinity/anti-affinity for member Pods. - Passed straight through to each member Pod's spec.affinity. A common - use is a pod anti-affinity on app.kubernetes.io/instance= to - keep members off the same node. - - Updates take effect on newly-created members (scale-up, replacement); - the operator does not roll existing Pods to apply an affinity change - in place. - properties: - nodeAffinity: - description: Describes node affinity scheduling rules for the - pod. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. The node that is - most preferred is the one with the greatest sum of weights, i.e. - for each node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node matches the corresponding matchExpressions; the - node(s) with the highest sum are the most preferred. - items: - description: |- - An empty preferred scheduling term matches all objects with implicit weight 0 - (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). - properties: - preference: - description: A node selector term, associated with the - corresponding weight. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - type: object - x-kubernetes-map-type: atomic - weight: - description: Weight associated with matching the corresponding - nodeSelectorTerm, in the range 1-100. - format: int32 - type: integer - required: - - preference - - weight - type: object - type: array - x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: - description: |- - If the affinity requirements specified by this field are not met at - scheduling time, the pod will not be scheduled onto the node. - If the affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to an update), the system - may or may not try to eventually evict the pod from its node. - properties: - nodeSelectorTerms: - description: Required. A list of node selector terms. - The terms are ORed. - items: - description: |- - A null or empty node selector term matches no objects. The requirements of - them are ANDed. - The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - type: object - x-kubernetes-map-type: atomic - type: array - x-kubernetes-list-type: atomic - required: - - nodeSelectorTerms - type: object - x-kubernetes-map-type: atomic - type: object - podAffinity: - description: Describes pod affinity scheduling rules (e.g. co-locate - this pod in the same node, zone, etc. as some other pod(s)). - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. The node that is - most preferred is the one with the greatest sum of weights, i.e. - for each node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the - node(s) with the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Required. A pod affinity term, associated - with the corresponding weight. - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - weight: - description: |- - weight associated with matching the corresponding podAffinityTerm, - in the range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object - type: array - x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: - description: |- - If the affinity requirements specified by this field are not met at - scheduling time, the pod will not be scheduled onto the node. - If the affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to a pod label update), the - system may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding to each - podAffinityTerm are intersected, i.e. all terms must be satisfied. - items: - description: |- - Defines a set of pods (namely those matching the labelSelector - relative to the given namespace(s)) that this pod should be - co-located (affinity) or not co-located (anti-affinity) with, - where co-located is defined as running on a node whose value of - the label with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - type: array - x-kubernetes-list-type: atomic - type: object - podAntiAffinity: - description: Describes pod anti-affinity scheduling rules (e.g. - avoid putting this pod in the same node, zone, etc. as some - other pod(s)). - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the anti-affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. The node that is - most preferred is the one with the greatest sum of weights, i.e. - for each node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the - node(s) with the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Required. A pod affinity term, associated - with the corresponding weight. - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - weight: - description: |- - weight associated with matching the corresponding podAffinityTerm, - in the range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object - type: array - x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: - description: |- - If the anti-affinity requirements specified by this field are not met at - scheduling time, the pod will not be scheduled onto the node. - If the anti-affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to a pod label update), the - system may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding to each - podAffinityTerm are intersected, i.e. all terms must be satisfied. - items: - description: |- - Defines a set of pods (namely those matching the labelSelector - relative to the given namespace(s)) that this pod should be - co-located (affinity) or not co-located (anti-affinity) with, - where co-located is defined as running on a node whose value of - the label with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - type: array - x-kubernetes-list-type: atomic - type: object - type: object - auth: - description: |- - Auth configures in-etcd authentication. Absent means no auth - (anonymous access on the client API, subject only to TLS). See - AuthSpec for the single-user parity model and its constraints - (requires spec.tls.client; immutable post-create). - properties: - enabled: - description: |- - Enabled turns on etcd authentication. The operator provisions the - root user + role and runs `auth enable` once the cluster has - converged to a healthy quorum (see status.authEnabled). Mirrors - `etcdctl auth enable` and the AuthStatusResponse.Enabled field. - - Requires spec.tls.client to be set: auth credentials must not cross - a plaintext wire. Immutable post-create — enabling or disabling auth - on a live cluster mutates persisted data-store state in lockstep with - the operator's own client, which this version does not roll back, so - the field is frozen the same way spec.tls is. Delete and recreate to - change it. - type: boolean - rootCredentialsSecretRef: - description: |- - RootCredentialsSecretRef references a Secret in the cluster's - namespace holding the etcd root user's credentials. Required when - Enabled is true (CEL-enforced). - - The Secret is expected to be of type kubernetes.io/basic-auth: the - operator reads the `password` key and provisions the etcd `root` user - with it. The `username` key is for consumers (e.g. a Kamaji DataStore - pointing at the same Secret) and must be "root" — the etcd user is - always root, since etcd requires a user named root to enable auth. - - Immutable post-create (part of the immutable auth subtree). The - operator reads the password on every dial; changing the Secret's - contents after auth is enabled would desync the operator from etcd — - in-place password rotation is not supported in this version, recreate - to change. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - type: object - bootstrap: - description: |- - Bootstrap configures one-time cluster initialization options. It is - consulted only at first bootstrap (while status.clusterID is unset) - and is immutable post-create. Absent means a normal empty-cluster - bootstrap. - properties: - restore: - description: |- - Restore initializes the new cluster from an existing etcd snapshot - instead of an empty data dir. Absent means a normal empty bootstrap. - properties: - source: - description: |- - Source is where the snapshot is read from (S3 or PVC). Same shape as - an EtcdSnapshot destination. - properties: - pvc: - description: PVC stores the snapshot on a PersistentVolumeClaim. - properties: - claimName: - description: ClaimName is the name of a PVC in the - cluster's namespace. - minLength: 1 - type: string - subPath: - description: SubPath is an optional subdirectory within - the volume. - type: string - required: - - claimName - type: object - s3: - description: S3 stores the snapshot in an S3-compatible - object store. - properties: - bucket: - description: Bucket is the destination bucket. - minLength: 1 - type: string - credentialsSecretRef: - description: |- - CredentialsSecretRef references a Secret in the cluster's namespace - holding AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY keys. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - endpoint: - description: |- - Endpoint is the S3 endpoint URL (e.g. "https://s3.amazonaws.com" or a - MinIO/Ceph endpoint). - minLength: 1 - type: string - forcePathStyle: - description: |- - ForcePathStyle selects path-style addressing (bucket in the path - rather than the host). MinIO/Ceph typically require true. - type: boolean - key: - description: |- - Key is an optional object-key prefix within the bucket. The operator - appends ".db". - type: string - region: - description: Region is the S3 region. Optional for - endpoints that ignore it. - type: string - required: - - bucket - - credentialsSecretRef - - endpoint - type: object - type: object - x-kubernetes-validations: - - message: exactly one of destination.s3 or destination.pvc - must be set - rule: has(self.s3) != has(self.pvc) - required: - - source - type: object - x-kubernetes-validations: - - message: bootstrap.restore.source.s3.key must be the exact (non-empty) - object key for a restore source - rule: '!has(self.source.s3) || (has(self.source.s3.key) && size(self.source.s3.key) - > 0)' - - message: bootstrap.restore.source.pvc.subPath must be the exact - (non-empty) snapshot file path for a restore source - rule: '!has(self.source.pvc) || (has(self.source.pvc.subPath) - && size(self.source.pvc.subPath) > 0)' - type: object - options: - description: |- - Options carries etcd server tuning flags (backend quota, - auto-compaction, raft snapshot count) passed to each member's - command line. A closed typed set — see EtcdOptions for why there - is deliberately no free-form flag map. - - Updates take effect on newly-created members (scale-up, - replacement); the operator does not roll existing Pods to apply a - tuning change in place. - properties: - autoCompactionMode: - description: |- - AutoCompactionMode sets --auto-compaction-mode: how - AutoCompactionRetention is interpreted, "periodic" (time-based) - or "revision" (revision-count-based). Absent means etcd's default - ("periodic" — though compaction only activates when a retention - is set). - enum: - - periodic - - revision - type: string - autoCompactionRetention: - description: |- - AutoCompactionRetention sets --auto-compaction-retention. In - periodic mode a duration ("5m", "1h"; a bare integer means hours); - in revision mode a revision count. Absent or "0" disables - auto-compaction. The pattern admits what etcd itself parses: a - bare non-negative integer or a Go duration. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$|^[0-9]+$ - type: string - quotaBackendBytes: - description: |- - QuotaBackendBytes sets --quota-backend-bytes: the backend database - size limit in bytes before the member raises the cluster-wide - NOSPACE alarm. 0 or absent means etcd's built-in default (2GiB). - etcd's documented practical maximum is 8GiB. - format: int64 - minimum: 0 - type: integer - snapshotCount: - description: |- - SnapshotCount sets --snapshot-count: the number of committed - raft entries to retain in memory before triggering an internal - raft snapshot (this is unrelated to EtcdSnapshot backups). Absent - means etcd's built-in default. Lower values trade replay speed on - restart for a smaller memory footprint. - format: int64 - minimum: 1 - type: integer - type: object - progressDeadlineSeconds: - default: 600 - description: |- - ProgressDeadlineSeconds bounds the time the operator spends trying to - reach a desired state before abandoning the in-flight target and - adopting whatever the user has set as the new spec. Defaults to 600 - (10 minutes). A patch to status.progressDeadline can shorten this for - a stuck reconcile (set it to "now" to abort immediately). - format: int32 - minimum: 1 - type: integer - replicas: - default: 3 - description: |- - Replicas is the desired number of cluster members. Should be odd. - A value of 0 parks the cluster ("scale to zero"): the operator - flips spec.dormant=true on the surviving EtcdMember, which causes - the member controller to delete that member's Pod. The EtcdMember - CR and its PVC are preserved (the PVC stays owned by the same - EtcdMember across the pause). Scaling back up to >=1 flips - spec.dormant=false on the same member; etcd resumes from its - existing data dir with the same ClusterID and member ID. - format: int32 - minimum: 0 - type: integer - resources: - description: |- - Resources sets the etcd container's resource requests and limits. - When omitted, the operator falls back to a conservative default - (100m CPU + 128Mi memory requests, no limits) suitable for - kicking the tyres but not for production. Memory-backed clusters - specifically should set limits.memory covering the tmpfs SizeLimit - plus etcd's own headroom. - - Updates take effect on newly-created members (scale-up, - replacement). The operator does not roll existing Pods to apply - a resource change in place — wire a VerticalPodAutoscaler at - targetRef={kind: EtcdCluster, name: } for that, or - delete one Pod at a time to recreate them with the new spec. - properties: - claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - - This field is immutable. It can only be set for containers. - items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. - properties: - name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. - type: string - request: - description: |- - Request is the name chosen for a request in the referenced claim. - If empty, everything from the claim is made available, otherwise - only the result of this request. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - type: object - storage: - default: - medium: "" - size: 1Gi - description: |- - Storage configures the per-member data directory: size and medium - (PVC or tmpfs). The size shrink-rejection and medium immutability - rules live as field-level CEL on the inner fields; the spec-level - CEL above couples replicas and storage.medium. - properties: - medium: - default: "" - description: |- - Medium selects the volume backend: "" (PVC) or "Memory" (tmpfs - emptyDir). See the StorageMedium type doc for operational trade-offs. - - Immutable: changing the medium on an existing cluster would orphan - the previous PVC (or tmpfs) and the rolling-migrate path is not - implemented. The default ("") is set explicitly so the apiserver - always stores the field on Create — without it, a first-time set - from absent → Memory would slip past the transition rule. - enum: - - "" - - Memory - type: string - x-kubernetes-validations: - - message: spec.storage.medium is immutable; delete and recreate - the cluster to change the storage backend - rule: self == oldSelf - size: - anyOf: - - type: integer - - type: string - default: 1Gi - description: |- - Size is the requested capacity per member. For Medium="" (PVC) this - is the PVC's requested storage. For Medium="Memory" this is the - tmpfs emptyDir's SizeLimit. - - Shrinking is rejected on UPDATE: PVCs cannot shrink and tmpfs - SizeLimit reduction does not free already-allocated memory. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - x-kubernetes-validations: - - message: spec.storage.size cannot be shrunk - rule: quantity(string(self)).compareTo(quantity(string(oldSelf))) - >= 0 - storageClassName: - description: |- - StorageClassName selects the StorageClass for the per-member PVC. - Mirrors corev1.PersistentVolumeClaimSpec.StorageClassName semantics: - nil (the default) uses the namespace's default StorageClass; the - empty string explicitly disables dynamic provisioning (a - pre-provisioned PV must already match the PVC selector); any other - value names a specific StorageClass. - - Ignored when Medium=Memory (no PVC is created). - - Immutable post-create — PersistentVolumeClaim.spec.storageClassName - is itself immutable after PVC creation, so honouring a mid-life - change would require a rolling PVC-recreation flow that this - operator does not perform. The immutability rules live at the - EtcdClusterSpec level (alongside the other pointer-field rules) - because *string transition CEL on the inner field cannot fire when - the field is being added from nil. - type: string - type: object - tls: - description: |- - TLS configures transport-layer security for the etcd client and - peer APIs. Absent means plaintext on both surfaces. The whole - subtree is immutable post-create — the immutability rules live at - the EtcdClusterSpec level (above) because pointer-field transition - rules don't fire when the field is being added (nil → set) and we - want to reject that direction too. - properties: - client: - description: |- - Client configures TLS for the etcd client API (port 2379). Absent - means plaintext. See ClientTLS for the mTLS-toggle semantics. - properties: - certManager: - description: |- - CertManager configures operator-driven TLS material provisioning - via cert-manager.io/v1 Certificate resources. Mutually exclusive - with ServerSecretRef. The operator owns the emitted Certificates - (they GC with the EtcdCluster) and the resulting Secrets are - mounted into the etcd Pods the same way BYO Secrets are. - properties: - operatorClientIssuerRef: - description: |- - OperatorClientIssuerRef is the Issuer or ClusterIssuer that will - sign the operator's etcd-client identity. Presence ⇒ client mTLS - is on; absence ⇒ server-TLS only. Resulting Secret name is - "-operator-client-tls". - - In the happy path the same Issuer signs both server and operator- - client certs, so the CA visible in each Secret's ca.crt is the - same content, doubling as etcd's --trusted-ca-file. Splitting the - two Issuers across separate root CAs requires the user to ensure - the trust bundle on the server side covers both — that case is an - edge of this v1. - properties: - kind: - default: Issuer - description: |- - Kind is either "Issuer" or "ClusterIssuer". Defaults to "Issuer" - — a namespaced issuer living next to the EtcdCluster. - enum: - - Issuer - - ClusterIssuer - type: string - name: - description: Name is the issuer resource's name. - minLength: 1 - type: string - required: - - name - type: object - serverIssuerRef: - description: |- - ServerIssuerRef is the Issuer or ClusterIssuer that will sign the - etcd server cert. Resulting Secret name is - "-server-tls". - properties: - kind: - default: Issuer - description: |- - Kind is either "Issuer" or "ClusterIssuer". Defaults to "Issuer" - — a namespaced issuer living next to the EtcdCluster. - enum: - - Issuer - - ClusterIssuer - type: string - name: - description: Name is the issuer resource's name. - minLength: 1 - type: string - required: - - name - type: object - required: - - serverIssuerRef - type: object - operatorClientSecretRef: - description: |- - OperatorClientSecretRef points at a Secret in the cluster's - namespace holding the operator's etcd-client identity (tls.crt, - tls.key). Setting this enables mTLS — etcd is started with - --client-cert-auth=true and the operator presents this cert when - dialing. Leaving it unset selects server-TLS-only mode. - - Cannot be combined with CertManager; use - CertManager.OperatorClientIssuerRef instead. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - serverSecretRef: - description: |- - ServerSecretRef points at a Secret in the cluster's namespace - holding the etcd server cert in the standard kubernetes.io/tls - shape: tls.crt, tls.key, and ca.crt. ca.crt is always required - (the operator's own etcd client needs it to verify the server, - and when mTLS is on it doubles as --trusted-ca-file). - - Mutually exclusive with CertManager. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - type: object - x-kubernetes-validations: - - message: exactly one of spec.tls.client.serverSecretRef or spec.tls.client.certManager - must be set - rule: has(self.serverSecretRef) != has(self.certManager) - - message: spec.tls.client.operatorClientSecretRef cannot be combined - with certManager; use certManager.operatorClientIssuerRef - rule: '!has(self.certManager) || !has(self.operatorClientSecretRef)' - peer: - description: |- - Peer configures TLS for the etcd peer API (port 2380). Absent - means plaintext. When set, peer is always mTLS — etcd's peer mesh - is symmetric and there is no useful encrypt-only-no-identity mode - for it. - properties: - certManager: - description: |- - CertManager configures operator-driven TLS material provisioning - for the peer plane via cert-manager.io/v1 Certificate resources. - Mutually exclusive with SecretRef. - properties: - issuerRef: - description: |- - IssuerRef is the Issuer or ClusterIssuer that signs the peer cert. - Peer is symmetric (same cert serves and dials), so this single - Issuer covers both directions of peer mTLS. Resulting Secret name - is "-peer-tls". - properties: - kind: - default: Issuer - description: |- - Kind is either "Issuer" or "ClusterIssuer". Defaults to "Issuer" - — a namespaced issuer living next to the EtcdCluster. - enum: - - Issuer - - ClusterIssuer - type: string - name: - description: Name is the issuer resource's name. - minLength: 1 - type: string - required: - - name - type: object - required: - - issuerRef - type: object - secretRef: - description: |- - SecretRef points at a Secret in the cluster's namespace holding - the peer cert+key in the standard kubernetes.io/tls shape: - tls.crt, tls.key, ca.crt. ca.crt is required (peer is symmetric - — same cert is used to serve inbound and dial outbound peer - connections — and --peer-trusted-ca-file is always populated). - The peer cert MUST carry both serverAuth and clientAuth in EKU. - - Mutually exclusive with CertManager. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - type: object - x-kubernetes-validations: - - message: exactly one of spec.tls.peer.secretRef or spec.tls.peer.certManager - must be set - rule: has(self.secretRef) != has(self.certManager) - type: object - topologySpreadConstraints: - description: |- - TopologySpreadConstraints controls how member Pods are spread across - topology domains (zones, nodes). Passed straight through to each - member Pod's spec.topologySpreadConstraints. - - Updates take effect on newly-created members (scale-up, replacement); - the operator does not roll existing Pods to apply a change in place. - items: - description: TopologySpreadConstraint specifies how to spread matching - pods among the given topology. - properties: - labelSelector: - description: |- - LabelSelector is used to find matching pods. - Pods that match this label selector are counted to determine the number of pods - in their corresponding topology domain. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select the pods over which - spreading will be calculated. The keys are used to lookup values from the - incoming pod labels, those key-value labels are ANDed with labelSelector - to select the group of existing pods over which spreading will be calculated - for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. - MatchLabelKeys cannot be set when LabelSelector isn't set. - Keys that don't exist in the incoming pod labels will - be ignored. A null or empty list means only match against labelSelector. - - This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). - items: - type: string - type: array - x-kubernetes-list-type: atomic - maxSkew: - description: |- - MaxSkew describes the degree to which pods may be unevenly distributed. - When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference - between the number of matching pods in the target topology and the global minimum. - The global minimum is the minimum number of matching pods in an eligible domain - or zero if the number of eligible domains is less than MinDomains. - For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same - labelSelector spread as 2/2/1: - In this case, the global minimum is 1. - | zone1 | zone2 | zone3 | - | P P | P P | P | - - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; - scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) - violate MaxSkew(1). - - if MaxSkew is 2, incoming pod can be scheduled onto any zone. - When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence - to topologies that satisfy it. - It's a required field. Default value is 1 and 0 is not allowed. - format: int32 - type: integer - minDomains: - description: |- - MinDomains indicates a minimum number of eligible domains. - When the number of eligible domains with matching topology keys is less than minDomains, - Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. - And when the number of eligible domains with matching topology keys equals or greater than minDomains, - this value has no effect on scheduling. - As a result, when the number of eligible domains is less than minDomains, - scheduler won't schedule more than maxSkew Pods to those domains. - If value is nil, the constraint behaves as if MinDomains is equal to 1. - Valid values are integers greater than 0. - When value is not nil, WhenUnsatisfiable must be DoNotSchedule. - - For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same - labelSelector spread as 2/2/2: - | zone1 | zone2 | zone3 | - | P P | P P | P P | - The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. - In this situation, new pod with the same labelSelector cannot be scheduled, - because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, - it will violate MaxSkew. - format: int32 - type: integer - nodeAffinityPolicy: - description: |- - NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector - when calculating pod topology spread skew. Options are: - - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. - - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. - - If this value is nil, the behavior is equivalent to the Honor policy. - type: string - nodeTaintsPolicy: - description: |- - NodeTaintsPolicy indicates how we will treat node taints when calculating - pod topology spread skew. Options are: - - Honor: nodes without taints, along with tainted nodes for which the incoming pod - has a toleration, are included. - - Ignore: node taints are ignored. All nodes are included. - - If this value is nil, the behavior is equivalent to the Ignore policy. - type: string - topologyKey: - description: |- - TopologyKey is the key of node labels. Nodes that have a label with this key - and identical values are considered to be in the same topology. - We consider each as a "bucket", and try to put balanced number - of pods into each bucket. - We define a domain as a particular instance of a topology. - Also, we define an eligible domain as a domain whose nodes meet the requirements of - nodeAffinityPolicy and nodeTaintsPolicy. - e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. - And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. - It's a required field. - type: string - whenUnsatisfiable: - description: |- - WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy - the spread constraint. - - DoNotSchedule (default) tells the scheduler not to schedule it. - - ScheduleAnyway tells the scheduler to schedule the pod in any location, - but giving higher precedence to topologies that would help reduce the - skew. - A constraint is considered "Unsatisfiable" for an incoming pod - if and only if every possible node assignment for that pod would violate - "MaxSkew" on some topology. - For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same - labelSelector spread as 3/1/1: - | zone1 | zone2 | zone3 | - | P P P | P | P | - If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled - to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies - MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler - won't make it *more* imbalanced. - It's a required field. - type: string - required: - - maxSkew - - topologyKey - - whenUnsatisfiable - type: object - type: array - version: - description: Version is the desired etcd version (e.g. "3.6.11"). - pattern: ^\d+\.\d+\.\d+$ - type: string - required: - - version - type: object - x-kubernetes-validations: - - message: 'spec.replicas=0 with spec.storage.medium=Memory is unsupported: - pausing a memory-backed cluster wedges on resume. Delete and recreate - the cluster instead.' - rule: '!(has(self.replicas) && self.replicas == 0 && has(self.storage) - && has(self.storage.medium) && self.storage.medium == ''Memory'')' - - message: spec.storage.size must be > 0 when spec.storage.medium=Memory - (the tmpfs sizeLimit cannot be zero). - rule: '!(has(self.storage) && has(self.storage.medium) && self.storage.medium - == ''Memory'') || quantity(string(self.storage.size)).isGreaterThan(quantity(''0''))' - - message: spec.tls cannot be added to or removed from an existing cluster; - delete and recreate - rule: has(self.tls) == has(oldSelf.tls) - - message: spec.tls is immutable post-create; delete and recreate the - cluster to change TLS configuration - rule: '!has(self.tls) || !has(oldSelf.tls) || self.tls == oldSelf.tls' - - message: spec.storage.storageClassName cannot be added to or removed - from an existing cluster; delete and recreate - rule: has(self.storage.storageClassName) == has(oldSelf.storage.storageClassName) - - message: spec.storage.storageClassName is immutable post-create (a PVC's - storageClassName itself is immutable, and the operator does not roll - PVCs); delete and recreate the cluster to change the StorageClass - rule: '!has(self.storage.storageClassName) || !has(oldSelf.storage.storageClassName) - || self.storage.storageClassName == oldSelf.storage.storageClassName' - - message: spec.auth cannot be added to or removed from an existing cluster; - delete and recreate - rule: has(self.auth) == has(oldSelf.auth) - - message: spec.auth is immutable post-create; delete and recreate the - cluster to change auth configuration - rule: '!has(self.auth) || !has(oldSelf.auth) || self.auth == oldSelf.auth' - - message: spec.auth.enabled requires spec.tls.client (auth credentials - must not cross a plaintext connection) - rule: '!(has(self.auth) && self.auth.enabled) || (has(self.tls) && has(self.tls.client))' - - message: spec.auth.enabled requires spec.auth.rootCredentialsSecretRef - rule: '!(has(self.auth) && self.auth.enabled) || has(self.auth.rootCredentialsSecretRef)' - - message: spec.bootstrap cannot be added to or removed from an existing - cluster; it is consulted only at first bootstrap - rule: has(self.bootstrap) == has(oldSelf.bootstrap) - - message: spec.bootstrap is immutable post-create; it is consulted only - at first bootstrap - rule: '!has(self.bootstrap) || !has(oldSelf.bootstrap) || self.bootstrap - == oldSelf.bootstrap' - - message: 'spec.bootstrap.restore is unsupported with spec.storage.medium=Memory: - the restored data dir is tmpfs, so any seed Pod restart re-restores - the snapshot — reverting writes (single member) or breaking the cluster - with a fresh ID it can''t rejoin (multi-member). Use persistent storage - to restore.' - rule: '!(has(self.bootstrap) && has(self.bootstrap.restore)) || !(has(self.storage) - && has(self.storage.medium) && self.storage.medium == ''Memory'')' - status: - description: EtcdClusterStatus defines the observed state of an etcd cluster. - properties: - authEnabled: - description: |- - AuthEnabled is true once the operator has successfully run - `auth enable` against the cluster. It is latched (never cleared — - spec.auth.enabled is immutable) and is the single signal every - operator etcd dial consults to decide whether to present the root - credentials: false ⇒ dial anonymously (auth not yet on, e.g. during - the bootstrap window before the cluster has converged), true ⇒ dial - as root. Decoupling this from spec.auth.enabled is what makes - the bootstrap window correct — clientv3 attempts an Authenticate RPC - on connect when a username is set, which fails until auth is enabled. - type: boolean - brokenMembers: - description: |- - BrokenMembers is the count of members the operator considers broken. - While the auto-replacement predicate is a stub it is always 0; surfaced - here so the predicate has a tested call site and the field already - exists when the policy lands. - format: int32 - type: integer - clusterID: - description: |- - ClusterID is the etcd cluster ID in hex (e.g. "769f1c9e0d723d0b"), - set after initial bootstrap. Stored as a string because uint64 values - can exceed JSON's safe integer range. - type: string - clusterToken: - description: |- - ClusterToken is the value passed to etcd's --initial-cluster-token, - recorded at bootstrap. Reused for all subsequent scale-up operations - so existing clusters keep their original token even if the derivation - rule changes in a later release. - type: string - conditions: - description: Conditions represent the latest available observations - of the cluster's state. - items: - description: Condition contains details for one aspect of the current - state of this API Resource. - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - observed: - description: |- - Observed is the locked-in desired state the operator is currently - reconciling toward. The reconciler ignores spec changes while a target - is in flight; Observed is only updated from spec when the current - target is met or its deadline has expired. nil before the first - reconcile. - properties: - additionalMetadata: - description: |- - AdditionalMetadata is the locked target extra labels/annotations - stamped onto objects created for this cluster. Latched with the rest - of the target spec so a mid-flight metadata edit only applies to - objects created once the current target is reached. - properties: - annotations: - additionalProperties: - type: string - description: Annotations are extra annotations merged onto - created objects. - type: object - labels: - additionalProperties: - type: string - description: |- - Labels are extra labels merged onto created objects. Operator-owned - label keys take precedence on collision. - type: object - type: object - affinity: - description: |- - Affinity is the locked target scheduling affinity for member Pods. - Latched alongside the rest of the target spec so a mid-flight change - only applies to members created once the current target is reached. - properties: - nodeAffinity: - description: Describes node affinity scheduling rules for - the pod. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. The node that is - most preferred is the one with the greatest sum of weights, i.e. - for each node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node matches the corresponding matchExpressions; the - node(s) with the highest sum are the most preferred. - items: - description: |- - An empty preferred scheduling term matches all objects with implicit weight 0 - (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). - properties: - preference: - description: A node selector term, associated with - the corresponding weight. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - type: object - x-kubernetes-map-type: atomic - weight: - description: Weight associated with matching the - corresponding nodeSelectorTerm, in the range 1-100. - format: int32 - type: integer - required: - - preference - - weight - type: object - type: array - x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: - description: |- - If the affinity requirements specified by this field are not met at - scheduling time, the pod will not be scheduled onto the node. - If the affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to an update), the system - may or may not try to eventually evict the pod from its node. - properties: - nodeSelectorTerms: - description: Required. A list of node selector terms. - The terms are ORed. - items: - description: |- - A null or empty node selector term matches no objects. The requirements of - them are ANDed. - The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - type: object - x-kubernetes-map-type: atomic - type: array - x-kubernetes-list-type: atomic - required: - - nodeSelectorTerms - type: object - x-kubernetes-map-type: atomic - type: object - podAffinity: - description: Describes pod affinity scheduling rules (e.g. - co-locate this pod in the same node, zone, etc. as some - other pod(s)). - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. The node that is - most preferred is the one with the greatest sum of weights, i.e. - for each node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the - node(s) with the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred - node(s) - properties: - podAffinityTerm: - description: Required. A pod affinity term, associated - with the corresponding weight. - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - weight: - description: |- - weight associated with matching the corresponding podAffinityTerm, - in the range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object - type: array - x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: - description: |- - If the affinity requirements specified by this field are not met at - scheduling time, the pod will not be scheduled onto the node. - If the affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to a pod label update), the - system may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding to each - podAffinityTerm are intersected, i.e. all terms must be satisfied. - items: - description: |- - Defines a set of pods (namely those matching the labelSelector - relative to the given namespace(s)) that this pod should be - co-located (affinity) or not co-located (anti-affinity) with, - where co-located is defined as running on a node whose value of - the label with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - type: array - x-kubernetes-list-type: atomic - type: object - podAntiAffinity: - description: Describes pod anti-affinity scheduling rules - (e.g. avoid putting this pod in the same node, zone, etc. - as some other pod(s)). - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the anti-affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. The node that is - most preferred is the one with the greatest sum of weights, i.e. - for each node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the - node(s) with the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred - node(s) - properties: - podAffinityTerm: - description: Required. A pod affinity term, associated - with the corresponding weight. - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - weight: - description: |- - weight associated with matching the corresponding podAffinityTerm, - in the range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object - type: array - x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: - description: |- - If the anti-affinity requirements specified by this field are not met at - scheduling time, the pod will not be scheduled onto the node. - If the anti-affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to a pod label update), the - system may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding to each - podAffinityTerm are intersected, i.e. all terms must be satisfied. - items: - description: |- - Defines a set of pods (namely those matching the labelSelector - relative to the given namespace(s)) that this pod should be - co-located (affinity) or not co-located (anti-affinity) with, - where co-located is defined as running on a node whose value of - the label with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - type: array - x-kubernetes-list-type: atomic - type: object - type: object - options: - description: |- - Options is the locked target etcd tuning flags for member Pods. - Latched with the rest of the target spec so a mid-flight tuning - edit only applies to members created once the current target is - reached. - properties: - autoCompactionMode: - description: |- - AutoCompactionMode sets --auto-compaction-mode: how - AutoCompactionRetention is interpreted, "periodic" (time-based) - or "revision" (revision-count-based). Absent means etcd's default - ("periodic" — though compaction only activates when a retention - is set). - enum: - - periodic - - revision - type: string - autoCompactionRetention: - description: |- - AutoCompactionRetention sets --auto-compaction-retention. In - periodic mode a duration ("5m", "1h"; a bare integer means hours); - in revision mode a revision count. Absent or "0" disables - auto-compaction. The pattern admits what etcd itself parses: a - bare non-negative integer or a Go duration. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$|^[0-9]+$ - type: string - quotaBackendBytes: - description: |- - QuotaBackendBytes sets --quota-backend-bytes: the backend database - size limit in bytes before the member raises the cluster-wide - NOSPACE alarm. 0 or absent means etcd's built-in default (2GiB). - etcd's documented practical maximum is 8GiB. - format: int64 - minimum: 0 - type: integer - snapshotCount: - description: |- - SnapshotCount sets --snapshot-count: the number of committed - raft entries to retain in memory before triggering an internal - raft snapshot (this is unrelated to EtcdSnapshot backups). Absent - means etcd's built-in default. Lower values trade replay speed on - restart for a smaller memory footprint. - format: int64 - minimum: 1 - type: integer - type: object - replicas: - description: Replicas is the locked target replica count. - format: int32 - type: integer - resources: - description: |- - Resources is the locked target etcd container resources. The - locking pattern prevents a mid-flight resource change from being - honoured on newly-created members until the current target is - reached or its deadline expires. - properties: - claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - - This field is immutable. It can only be set for containers. - items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. - properties: - name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. - type: string - request: - description: |- - Request is the name chosen for a request in the referenced claim. - If empty, everything from the claim is made available, otherwise - only the result of this request. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - type: object - storage: - description: |- - Storage is the locked target storage configuration. The locking - pattern prevents a mid-flight size grow from being honoured until - the current target is reached or its deadline expires. (Medium - can't change at all post-create — that's enforced by spec-level - CEL.) - properties: - medium: - default: "" - description: |- - Medium selects the volume backend: "" (PVC) or "Memory" (tmpfs - emptyDir). See the StorageMedium type doc for operational trade-offs. - - Immutable: changing the medium on an existing cluster would orphan - the previous PVC (or tmpfs) and the rolling-migrate path is not - implemented. The default ("") is set explicitly so the apiserver - always stores the field on Create — without it, a first-time set - from absent → Memory would slip past the transition rule. - enum: - - "" - - Memory - type: string - x-kubernetes-validations: - - message: spec.storage.medium is immutable; delete and recreate - the cluster to change the storage backend - rule: self == oldSelf - size: - anyOf: - - type: integer - - type: string - default: 1Gi - description: |- - Size is the requested capacity per member. For Medium="" (PVC) this - is the PVC's requested storage. For Medium="Memory" this is the - tmpfs emptyDir's SizeLimit. - - Shrinking is rejected on UPDATE: PVCs cannot shrink and tmpfs - SizeLimit reduction does not free already-allocated memory. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - x-kubernetes-validations: - - message: spec.storage.size cannot be shrunk - rule: quantity(string(self)).compareTo(quantity(string(oldSelf))) - >= 0 - storageClassName: - description: |- - StorageClassName selects the StorageClass for the per-member PVC. - Mirrors corev1.PersistentVolumeClaimSpec.StorageClassName semantics: - nil (the default) uses the namespace's default StorageClass; the - empty string explicitly disables dynamic provisioning (a - pre-provisioned PV must already match the PVC selector); any other - value names a specific StorageClass. - - Ignored when Medium=Memory (no PVC is created). - - Immutable post-create — PersistentVolumeClaim.spec.storageClassName - is itself immutable after PVC creation, so honouring a mid-life - change would require a rolling PVC-recreation flow that this - operator does not perform. The immutability rules live at the - EtcdClusterSpec level (alongside the other pointer-field rules) - because *string transition CEL on the inner field cannot fire when - the field is being added from nil. - type: string - type: object - topologySpreadConstraints: - description: |- - TopologySpreadConstraints is the locked target spread configuration - for member Pods. Latched with the rest of the target spec. - items: - description: TopologySpreadConstraint specifies how to spread - matching pods among the given topology. - properties: - labelSelector: - description: |- - LabelSelector is used to find matching pods. - Pods that match this label selector are counted to determine the number of pods - in their corresponding topology domain. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select the pods over which - spreading will be calculated. The keys are used to lookup values from the - incoming pod labels, those key-value labels are ANDed with labelSelector - to select the group of existing pods over which spreading will be calculated - for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. - MatchLabelKeys cannot be set when LabelSelector isn't set. - Keys that don't exist in the incoming pod labels will - be ignored. A null or empty list means only match against labelSelector. - - This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). - items: - type: string - type: array - x-kubernetes-list-type: atomic - maxSkew: - description: |- - MaxSkew describes the degree to which pods may be unevenly distributed. - When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference - between the number of matching pods in the target topology and the global minimum. - The global minimum is the minimum number of matching pods in an eligible domain - or zero if the number of eligible domains is less than MinDomains. - For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same - labelSelector spread as 2/2/1: - In this case, the global minimum is 1. - | zone1 | zone2 | zone3 | - | P P | P P | P | - - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; - scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) - violate MaxSkew(1). - - if MaxSkew is 2, incoming pod can be scheduled onto any zone. - When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence - to topologies that satisfy it. - It's a required field. Default value is 1 and 0 is not allowed. - format: int32 - type: integer - minDomains: - description: |- - MinDomains indicates a minimum number of eligible domains. - When the number of eligible domains with matching topology keys is less than minDomains, - Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. - And when the number of eligible domains with matching topology keys equals or greater than minDomains, - this value has no effect on scheduling. - As a result, when the number of eligible domains is less than minDomains, - scheduler won't schedule more than maxSkew Pods to those domains. - If value is nil, the constraint behaves as if MinDomains is equal to 1. - Valid values are integers greater than 0. - When value is not nil, WhenUnsatisfiable must be DoNotSchedule. - - For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same - labelSelector spread as 2/2/2: - | zone1 | zone2 | zone3 | - | P P | P P | P P | - The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. - In this situation, new pod with the same labelSelector cannot be scheduled, - because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, - it will violate MaxSkew. - format: int32 - type: integer - nodeAffinityPolicy: - description: |- - NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector - when calculating pod topology spread skew. Options are: - - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. - - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. - - If this value is nil, the behavior is equivalent to the Honor policy. - type: string - nodeTaintsPolicy: - description: |- - NodeTaintsPolicy indicates how we will treat node taints when calculating - pod topology spread skew. Options are: - - Honor: nodes without taints, along with tainted nodes for which the incoming pod - has a toleration, are included. - - Ignore: node taints are ignored. All nodes are included. - - If this value is nil, the behavior is equivalent to the Ignore policy. - type: string - topologyKey: - description: |- - TopologyKey is the key of node labels. Nodes that have a label with this key - and identical values are considered to be in the same topology. - We consider each as a "bucket", and try to put balanced number - of pods into each bucket. - We define a domain as a particular instance of a topology. - Also, we define an eligible domain as a domain whose nodes meet the requirements of - nodeAffinityPolicy and nodeTaintsPolicy. - e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. - And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. - It's a required field. - type: string - whenUnsatisfiable: - description: |- - WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy - the spread constraint. - - DoNotSchedule (default) tells the scheduler not to schedule it. - - ScheduleAnyway tells the scheduler to schedule the pod in any location, - but giving higher precedence to topologies that would help reduce the - skew. - A constraint is considered "Unsatisfiable" for an incoming pod - if and only if every possible node assignment for that pod would violate - "MaxSkew" on some topology. - For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same - labelSelector spread as 3/1/1: - | zone1 | zone2 | zone3 | - | P P P | P | P | - If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled - to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies - MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler - won't make it *more* imbalanced. - It's a required field. - type: string - required: - - maxSkew - - topologyKey - - whenUnsatisfiable - type: object - type: array - version: - description: Version is the locked target etcd version. - type: string - required: - - replicas - - storage - - version - type: object - progressDeadline: - description: |- - ProgressDeadline is the time at which the in-flight reconciliation - will be abandoned in favor of the latest spec. Cleared when the - cluster reaches Observed. Patch this to a time in the past to abort - a stuck reconcile. - format: date-time - type: string - readyMembers: - description: |- - ReadyMembers is the count of members that are healthy and serving. - Also exposed as Scale.Status.Replicas via the /scale subresource so - kubectl scale and clients like VerticalPodAutoscaler can read - "current scale" without a custom status field. - format: int32 - type: integer - selector: - description: |- - Selector is the label-selector form ("etcd-operator.cozystack.io/cluster=") - matching every Pod owned by this cluster. Surfaced for the /scale - subresource — the VPA admission controller reads it via Scales().Get() - to know which Pods to inject recommendations into. Plain users won't - see this field directly. - type: string - type: object - type: object - served: true - storage: true - subresources: - scale: - labelSelectorPath: .status.selector - specReplicasPath: .spec.replicas - statusReplicasPath: .status.readyMembers - status: {} diff --git a/config/crd/bases/etcd-operator.cozystack.io_etcdmembers.yaml b/config/crd/bases/etcd-operator.cozystack.io_etcdmembers.yaml deleted file mode 100644 index b1e5222a..00000000 --- a/config/crd/bases/etcd-operator.cozystack.io_etcdmembers.yaml +++ /dev/null @@ -1,1662 +0,0 @@ ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.18.0 - name: etcdmembers.etcd-operator.cozystack.io -spec: - group: etcd-operator.cozystack.io - names: - kind: EtcdMember - listKind: EtcdMemberList - plural: etcdmembers - singular: etcdmember - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.clusterName - name: Cluster - type: string - - jsonPath: .spec.version - name: Version - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha2 - schema: - openAPIV3Schema: - description: |- - EtcdMember represents a single member of an etcd cluster. - EtcdMember resources are created and deleted by the EtcdCluster controller. - Users should not create these directly. - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: |- - EtcdMemberSpec defines the desired state of a single etcd member. - Created and managed by the EtcdCluster controller. - properties: - additionalMetadata: - description: |- - AdditionalMetadata mirrors EtcdCluster.spec.additionalMetadata at the - time this member was created. The member controller merges it onto the - member's Pod (operator-owned label keys win on collision). - properties: - annotations: - additionalProperties: - type: string - description: Annotations are extra annotations merged onto created - objects. - type: object - labels: - additionalProperties: - type: string - description: |- - Labels are extra labels merged onto created objects. Operator-owned - label keys take precedence on collision. - type: object - type: object - affinity: - description: |- - Affinity mirrors EtcdCluster.spec.affinity at the time this member was - created. The member controller passes it straight to the Pod's - spec.affinity at build time. - properties: - nodeAffinity: - description: Describes node affinity scheduling rules for the - pod. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. The node that is - most preferred is the one with the greatest sum of weights, i.e. - for each node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node matches the corresponding matchExpressions; the - node(s) with the highest sum are the most preferred. - items: - description: |- - An empty preferred scheduling term matches all objects with implicit weight 0 - (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). - properties: - preference: - description: A node selector term, associated with the - corresponding weight. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - type: object - x-kubernetes-map-type: atomic - weight: - description: Weight associated with matching the corresponding - nodeSelectorTerm, in the range 1-100. - format: int32 - type: integer - required: - - preference - - weight - type: object - type: array - x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: - description: |- - If the affinity requirements specified by this field are not met at - scheduling time, the pod will not be scheduled onto the node. - If the affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to an update), the system - may or may not try to eventually evict the pod from its node. - properties: - nodeSelectorTerms: - description: Required. A list of node selector terms. - The terms are ORed. - items: - description: |- - A null or empty node selector term matches no objects. The requirements of - them are ANDed. - The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - type: object - x-kubernetes-map-type: atomic - type: array - x-kubernetes-list-type: atomic - required: - - nodeSelectorTerms - type: object - x-kubernetes-map-type: atomic - type: object - podAffinity: - description: Describes pod affinity scheduling rules (e.g. co-locate - this pod in the same node, zone, etc. as some other pod(s)). - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. The node that is - most preferred is the one with the greatest sum of weights, i.e. - for each node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the - node(s) with the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Required. A pod affinity term, associated - with the corresponding weight. - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - weight: - description: |- - weight associated with matching the corresponding podAffinityTerm, - in the range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object - type: array - x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: - description: |- - If the affinity requirements specified by this field are not met at - scheduling time, the pod will not be scheduled onto the node. - If the affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to a pod label update), the - system may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding to each - podAffinityTerm are intersected, i.e. all terms must be satisfied. - items: - description: |- - Defines a set of pods (namely those matching the labelSelector - relative to the given namespace(s)) that this pod should be - co-located (affinity) or not co-located (anti-affinity) with, - where co-located is defined as running on a node whose value of - the label with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - type: array - x-kubernetes-list-type: atomic - type: object - podAntiAffinity: - description: Describes pod anti-affinity scheduling rules (e.g. - avoid putting this pod in the same node, zone, etc. as some - other pod(s)). - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the anti-affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. The node that is - most preferred is the one with the greatest sum of weights, i.e. - for each node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the - node(s) with the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Required. A pod affinity term, associated - with the corresponding weight. - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - weight: - description: |- - weight associated with matching the corresponding podAffinityTerm, - in the range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object - type: array - x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: - description: |- - If the anti-affinity requirements specified by this field are not met at - scheduling time, the pod will not be scheduled onto the node. - If the anti-affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to a pod label update), the - system may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding to each - podAffinityTerm are intersected, i.e. all terms must be satisfied. - items: - description: |- - Defines a set of pods (namely those matching the labelSelector - relative to the given namespace(s)) that this pod should be - co-located (affinity) or not co-located (anti-affinity) with, - where co-located is defined as running on a node whose value of - the label with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - type: array - x-kubernetes-list-type: atomic - type: object - type: object - bootstrap: - description: |- - Bootstrap indicates this member is part of the initial cluster formation. - When true the member starts with --initial-cluster-state=new. - type: boolean - clusterName: - description: ClusterName is the name of the owning EtcdCluster. - minLength: 1 - type: string - clusterToken: - description: |- - ClusterToken is the value passed to etcd's --initial-cluster-token. - Copied from EtcdCluster.status.clusterToken so all members of a cluster - agree, and so changes to the cluster's token derivation rule don't - affect already-running members. - type: string - dormant: - description: |- - Dormant marks the member as paused. While dormant, the member - controller deletes the member's Pod but leaves the PVC in place - (the PVC stays owned by this EtcdMember). The cluster controller - flips Dormant=true on the surviving member when the user sets - EtcdCluster.spec.replicas=0 on a 1-member cluster, and flips it - back to false when the user scales up. Re-creating the Pod against - the existing PVC lets etcd resume from the existing data dir with - the same ClusterID and member ID. While dormant, the member does - not count toward the EtcdCluster's `current` replica accounting. - type: boolean - initialCluster: - description: |- - InitialCluster is the value passed to etcd's --initial-cluster flag. - Set by the cluster controller at creation time. - type: string - options: - description: |- - Options mirrors EtcdCluster.spec.options at the time this member - was created. The member controller renders the set fields as etcd - command-line flags at Pod-build time; existing members are not - re-templated when the cluster spec changes. - properties: - autoCompactionMode: - description: |- - AutoCompactionMode sets --auto-compaction-mode: how - AutoCompactionRetention is interpreted, "periodic" (time-based) - or "revision" (revision-count-based). Absent means etcd's default - ("periodic" — though compaction only activates when a retention - is set). - enum: - - periodic - - revision - type: string - autoCompactionRetention: - description: |- - AutoCompactionRetention sets --auto-compaction-retention. In - periodic mode a duration ("5m", "1h"; a bare integer means hours); - in revision mode a revision count. Absent or "0" disables - auto-compaction. The pattern admits what etcd itself parses: a - bare non-negative integer or a Go duration. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$|^[0-9]+$ - type: string - quotaBackendBytes: - description: |- - QuotaBackendBytes sets --quota-backend-bytes: the backend database - size limit in bytes before the member raises the cluster-wide - NOSPACE alarm. 0 or absent means etcd's built-in default (2GiB). - etcd's documented practical maximum is 8GiB. - format: int64 - minimum: 0 - type: integer - snapshotCount: - description: |- - SnapshotCount sets --snapshot-count: the number of committed - raft entries to retain in memory before triggering an internal - raft snapshot (this is unrelated to EtcdSnapshot backups). Absent - means etcd's built-in default. Lower values trade replay speed on - restart for a smaller memory footprint. - format: int64 - minimum: 1 - type: integer - type: object - replicas: - default: 1 - description: |- - Replicas exists only because the PodDisruptionBudget controller - traverses Pods' controllerRef looking for /scale on the parent and - fails closed ("does not implement the scale subresource") if it - isn't there. Each EtcdMember represents exactly one Pod; this field - is locked to 1 by validation and cannot be tuned. - format: int32 - maximum: 1 - minimum: 1 - type: integer - resources: - description: |- - Resources mirrors EtcdCluster.spec.resources at the time this - member was created. The cluster controller copies it onto each - member at creation. The member controller passes the value - straight to the etcd container's resources field at Pod-build - time; existing members are not re-templated when the cluster - spec changes. - properties: - claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - - This field is immutable. It can only be set for containers. - items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. - properties: - name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. - type: string - request: - description: |- - Request is the name chosen for a request in the referenced claim. - If empty, everything from the claim is made available, otherwise - only the result of this request. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - type: object - restore: - description: |- - Restore is set only on the bootstrap seed when the parent cluster's - spec.bootstrap.restore is configured. It causes the member controller - to run a restore initContainer that populates the data dir from the - snapshot before etcd starts. Inert once the data dir is initialized. - properties: - source: - description: |- - Source is where the snapshot is read from (S3 or PVC). Same shape as - an EtcdSnapshot destination. - properties: - pvc: - description: PVC stores the snapshot on a PersistentVolumeClaim. - properties: - claimName: - description: ClaimName is the name of a PVC in the cluster's - namespace. - minLength: 1 - type: string - subPath: - description: SubPath is an optional subdirectory within - the volume. - type: string - required: - - claimName - type: object - s3: - description: S3 stores the snapshot in an S3-compatible object - store. - properties: - bucket: - description: Bucket is the destination bucket. - minLength: 1 - type: string - credentialsSecretRef: - description: |- - CredentialsSecretRef references a Secret in the cluster's namespace - holding AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY keys. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - endpoint: - description: |- - Endpoint is the S3 endpoint URL (e.g. "https://s3.amazonaws.com" or a - MinIO/Ceph endpoint). - minLength: 1 - type: string - forcePathStyle: - description: |- - ForcePathStyle selects path-style addressing (bucket in the path - rather than the host). MinIO/Ceph typically require true. - type: boolean - key: - description: |- - Key is an optional object-key prefix within the bucket. The operator - appends ".db". - type: string - region: - description: Region is the S3 region. Optional for endpoints - that ignore it. - type: string - required: - - bucket - - credentialsSecretRef - - endpoint - type: object - type: object - x-kubernetes-validations: - - message: exactly one of destination.s3 or destination.pvc must - be set - rule: has(self.s3) != has(self.pvc) - required: - - source - type: object - x-kubernetes-validations: - - message: bootstrap.restore.source.s3.key must be the exact (non-empty) - object key for a restore source - rule: '!has(self.source.s3) || (has(self.source.s3.key) && size(self.source.s3.key) - > 0)' - - message: bootstrap.restore.source.pvc.subPath must be the exact - (non-empty) snapshot file path for a restore source - rule: '!has(self.source.pvc) || (has(self.source.pvc.subPath) && - size(self.source.pvc.subPath) > 0)' - storage: - description: |- - Storage mirrors EtcdCluster.spec.storage at the time this member - was created. The cluster controller copies size and medium onto - each member at creation; the member controller treats it as - immutable per-member spec. - properties: - medium: - default: "" - description: |- - Medium selects the volume backend: "" (PVC) or "Memory" (tmpfs - emptyDir). See the StorageMedium type doc for operational trade-offs. - - Immutable: changing the medium on an existing cluster would orphan - the previous PVC (or tmpfs) and the rolling-migrate path is not - implemented. The default ("") is set explicitly so the apiserver - always stores the field on Create — without it, a first-time set - from absent → Memory would slip past the transition rule. - enum: - - "" - - Memory - type: string - x-kubernetes-validations: - - message: spec.storage.medium is immutable; delete and recreate - the cluster to change the storage backend - rule: self == oldSelf - size: - anyOf: - - type: integer - - type: string - default: 1Gi - description: |- - Size is the requested capacity per member. For Medium="" (PVC) this - is the PVC's requested storage. For Medium="Memory" this is the - tmpfs emptyDir's SizeLimit. - - Shrinking is rejected on UPDATE: PVCs cannot shrink and tmpfs - SizeLimit reduction does not free already-allocated memory. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - x-kubernetes-validations: - - message: spec.storage.size cannot be shrunk - rule: quantity(string(self)).compareTo(quantity(string(oldSelf))) - >= 0 - storageClassName: - description: |- - StorageClassName selects the StorageClass for the per-member PVC. - Mirrors corev1.PersistentVolumeClaimSpec.StorageClassName semantics: - nil (the default) uses the namespace's default StorageClass; the - empty string explicitly disables dynamic provisioning (a - pre-provisioned PV must already match the PVC selector); any other - value names a specific StorageClass. - - Ignored when Medium=Memory (no PVC is created). - - Immutable post-create — PersistentVolumeClaim.spec.storageClassName - is itself immutable after PVC creation, so honouring a mid-life - change would require a rolling PVC-recreation flow that this - operator does not perform. The immutability rules live at the - EtcdClusterSpec level (alongside the other pointer-field rules) - because *string transition CEL on the inner field cannot fire when - the field is being added from nil. - type: string - type: object - tls: - description: |- - TLS mirrors the parent cluster's TLS configuration at the time - this member was created. Carries only what the etcd Pod needs to - see (secret references and the mTLS flag); operator-side material - stays on the parent cluster spec. - properties: - clientMTLS: - description: |- - ClientMTLS mirrors "EtcdClusterTLS.Client.OperatorClientSecretRef - is set" — i.e. whether the etcd server should be started with - --client-cert-auth=true and --trusted-ca-file. Decoupled from the - secret ref because the secret itself is operator-side only. - type: boolean - clientServerSecretRef: - description: |- - ClientServerSecretRef mirrors EtcdClusterTLS.Client.ServerSecretRef. - When nil, the member runs the client API in plaintext. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - peerSecretRef: - description: |- - PeerSecretRef mirrors EtcdClusterTLS.Peer.SecretRef. When nil, the - member runs the peer API in plaintext. When set, peer is always - mTLS (--peer-client-cert-auth=true). - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - type: object - topologySpreadConstraints: - description: |- - TopologySpreadConstraints mirrors EtcdCluster.spec.topologySpreadConstraints - at the time this member was created. Passed straight to the Pod's - spec.topologySpreadConstraints at build time. - items: - description: TopologySpreadConstraint specifies how to spread matching - pods among the given topology. - properties: - labelSelector: - description: |- - LabelSelector is used to find matching pods. - Pods that match this label selector are counted to determine the number of pods - in their corresponding topology domain. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select the pods over which - spreading will be calculated. The keys are used to lookup values from the - incoming pod labels, those key-value labels are ANDed with labelSelector - to select the group of existing pods over which spreading will be calculated - for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. - MatchLabelKeys cannot be set when LabelSelector isn't set. - Keys that don't exist in the incoming pod labels will - be ignored. A null or empty list means only match against labelSelector. - - This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). - items: - type: string - type: array - x-kubernetes-list-type: atomic - maxSkew: - description: |- - MaxSkew describes the degree to which pods may be unevenly distributed. - When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference - between the number of matching pods in the target topology and the global minimum. - The global minimum is the minimum number of matching pods in an eligible domain - or zero if the number of eligible domains is less than MinDomains. - For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same - labelSelector spread as 2/2/1: - In this case, the global minimum is 1. - | zone1 | zone2 | zone3 | - | P P | P P | P | - - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; - scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) - violate MaxSkew(1). - - if MaxSkew is 2, incoming pod can be scheduled onto any zone. - When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence - to topologies that satisfy it. - It's a required field. Default value is 1 and 0 is not allowed. - format: int32 - type: integer - minDomains: - description: |- - MinDomains indicates a minimum number of eligible domains. - When the number of eligible domains with matching topology keys is less than minDomains, - Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. - And when the number of eligible domains with matching topology keys equals or greater than minDomains, - this value has no effect on scheduling. - As a result, when the number of eligible domains is less than minDomains, - scheduler won't schedule more than maxSkew Pods to those domains. - If value is nil, the constraint behaves as if MinDomains is equal to 1. - Valid values are integers greater than 0. - When value is not nil, WhenUnsatisfiable must be DoNotSchedule. - - For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same - labelSelector spread as 2/2/2: - | zone1 | zone2 | zone3 | - | P P | P P | P P | - The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. - In this situation, new pod with the same labelSelector cannot be scheduled, - because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, - it will violate MaxSkew. - format: int32 - type: integer - nodeAffinityPolicy: - description: |- - NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector - when calculating pod topology spread skew. Options are: - - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. - - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. - - If this value is nil, the behavior is equivalent to the Honor policy. - type: string - nodeTaintsPolicy: - description: |- - NodeTaintsPolicy indicates how we will treat node taints when calculating - pod topology spread skew. Options are: - - Honor: nodes without taints, along with tainted nodes for which the incoming pod - has a toleration, are included. - - Ignore: node taints are ignored. All nodes are included. - - If this value is nil, the behavior is equivalent to the Ignore policy. - type: string - topologyKey: - description: |- - TopologyKey is the key of node labels. Nodes that have a label with this key - and identical values are considered to be in the same topology. - We consider each as a "bucket", and try to put balanced number - of pods into each bucket. - We define a domain as a particular instance of a topology. - Also, we define an eligible domain as a domain whose nodes meet the requirements of - nodeAffinityPolicy and nodeTaintsPolicy. - e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. - And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. - It's a required field. - type: string - whenUnsatisfiable: - description: |- - WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy - the spread constraint. - - DoNotSchedule (default) tells the scheduler not to schedule it. - - ScheduleAnyway tells the scheduler to schedule the pod in any location, - but giving higher precedence to topologies that would help reduce the - skew. - A constraint is considered "Unsatisfiable" for an incoming pod - if and only if every possible node assignment for that pod would violate - "MaxSkew" on some topology. - For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same - labelSelector spread as 3/1/1: - | zone1 | zone2 | zone3 | - | P P P | P | P | - If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled - to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies - MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler - won't make it *more* imbalanced. - It's a required field. - type: string - required: - - maxSkew - - topologyKey - - whenUnsatisfiable - type: object - type: array - version: - description: Version is the etcd version for this member. - pattern: ^\d+\.\d+\.\d+$ - type: string - required: - - clusterName - - clusterToken - - initialCluster - - storage - - version - type: object - status: - description: EtcdMemberStatus defines the observed state of a single etcd - member. - properties: - conditions: - description: Conditions represent the latest available observations - of the member's state. - items: - description: Condition contains details for one aspect of the current - state of this API Resource. - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - isVoter: - description: |- - IsVoter is true when etcd's MemberList reports this member with - IsLearner=false — i.e. it counts toward quorum. Written by the - cluster controller during its MemberList processing and pre-stamped - true at seed creation (the seed is never a learner). Read by the - member controller to apply the role=voter Pod label that the - per-cluster PodDisruptionBudget selects on. Default value false is - the safe-but-temporary state for a freshly-added learner before - MemberPromote runs. - type: boolean - memberID: - description: |- - MemberID is the etcd-assigned member ID in hex (e.g. "ae36f238164a08ad"), - set once the member joins the cluster. Stored as a string because uint64 - values can exceed JSON's safe integer range. - type: string - podName: - description: PodName is the name of the Pod running this member. - type: string - podUID: - description: |- - PodUID is the UID of the Pod most recently observed for this member. - Set when the Pod is created or found; cleared when the Pod is gone - and the member controller intentionally removed it (e.g. dormant). - For memory-backed members the operator compares the live Pod's UID - against this value to detect Pod loss: a stored UID with no live - matching Pod means the tmpfs is gone and the member must be replaced. - type: string - pvcName: - description: PVCName is the name of the PersistentVolumeClaim for - this member's data. - type: string - replicas: - description: |- - Replicas exposes via /scale "this EtcdMember owns 1 Pod if it has - a PodName, 0 otherwise". Required by the PodDisruptionBudget - controller to derive expectedPods for the cluster's PDB — without - /scale on the Pod controller-ref it sets the PDB to SyncFailed. - format: int32 - type: integer - selector: - description: |- - Selector exposes the label-selector that matches this member's Pod - via /scale (consumed by the PDB controller; not user-facing). - type: string - type: object - type: object - served: true - storage: true - subresources: - scale: - labelSelectorPath: .status.selector - specReplicasPath: .spec.replicas - statusReplicasPath: .status.replicas - status: {} diff --git a/config/crd/bases/etcd-operator.cozystack.io_etcdsnapshots.yaml b/config/crd/bases/etcd-operator.cozystack.io_etcdsnapshots.yaml deleted file mode 100644 index 95de7476..00000000 --- a/config/crd/bases/etcd-operator.cozystack.io_etcdsnapshots.yaml +++ /dev/null @@ -1,235 +0,0 @@ ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.18.0 - name: etcdsnapshots.etcd-operator.cozystack.io -spec: - group: etcd-operator.cozystack.io - names: - kind: EtcdSnapshot - listKind: EtcdSnapshotList - plural: etcdsnapshots - singular: etcdsnapshot - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.clusterRef.name - name: Cluster - type: string - - jsonPath: .status.phase - name: Phase - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha2 - schema: - openAPIV3Schema: - description: |- - EtcdSnapshot is the Schema for the etcdsnapshots API. It captures a one-shot - snapshot of an EtcdCluster to a destination (S3 or PVC). - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: |- - EtcdSnapshotSpec defines a one-shot etcd snapshot of a cluster to a - destination. Snapshots are immutable: change the destination by creating a - new EtcdSnapshot. - properties: - clusterRef: - description: ClusterRef names the EtcdCluster (same namespace) to - snapshot. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - destination: - description: Destination selects where the snapshot is stored (S3 - or PVC). - properties: - pvc: - description: PVC stores the snapshot on a PersistentVolumeClaim. - properties: - claimName: - description: ClaimName is the name of a PVC in the cluster's - namespace. - minLength: 1 - type: string - subPath: - description: SubPath is an optional subdirectory within the - volume. - type: string - required: - - claimName - type: object - s3: - description: S3 stores the snapshot in an S3-compatible object - store. - properties: - bucket: - description: Bucket is the destination bucket. - minLength: 1 - type: string - credentialsSecretRef: - description: |- - CredentialsSecretRef references a Secret in the cluster's namespace - holding AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY keys. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - endpoint: - description: |- - Endpoint is the S3 endpoint URL (e.g. "https://s3.amazonaws.com" or a - MinIO/Ceph endpoint). - minLength: 1 - type: string - forcePathStyle: - description: |- - ForcePathStyle selects path-style addressing (bucket in the path - rather than the host). MinIO/Ceph typically require true. - type: boolean - key: - description: |- - Key is an optional object-key prefix within the bucket. The operator - appends ".db". - type: string - region: - description: Region is the S3 region. Optional for endpoints - that ignore it. - type: string - required: - - bucket - - credentialsSecretRef - - endpoint - type: object - type: object - x-kubernetes-validations: - - message: exactly one of destination.s3 or destination.pvc must be - set - rule: has(self.s3) != has(self.pvc) - required: - - clusterRef - - destination - type: object - status: - description: EtcdSnapshotStatus is the observed state of an EtcdSnapshot. - properties: - artifact: - description: Artifact is populated once the snapshot completes. - properties: - checksum: - description: Checksum is ":", e.g. "sha256:abc123...". - type: string - sizeBytes: - description: SizeBytes is the snapshot size in bytes. - format: int64 - type: integer - uri: - description: |- - URI is the full snapshot location, e.g. "s3://bucket/key.db" or - "file:///snapshot/data/name.db". - type: string - required: - - uri - type: object - conditions: - description: Conditions represent the latest available observations. - items: - description: Condition contains details for one aspect of the current - state of this API Resource. - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - phase: - description: Phase is the high-level lifecycle phase. - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml deleted file mode 100644 index 82de31a7..00000000 --- a/config/crd/kustomization.yaml +++ /dev/null @@ -1,23 +0,0 @@ -# This kustomization.yaml is not intended to be run by itself, -# since it depends on service name and namespace that are out of this kustomize package. -# It should be run by config/default -resources: -- bases/etcd-operator.cozystack.io_etcdclusters.yaml -- bases/etcd-operator.cozystack.io_etcdmembers.yaml -- bases/etcd-operator.cozystack.io_etcdsnapshots.yaml -#+kubebuilder:scaffold:crdkustomizeresource - -patchesStrategicMerge: -# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. -# patches here are for enabling the conversion webhook for each CRD -#- patches/webhook_in_etcdclusters.yaml -#+kubebuilder:scaffold:crdkustomizewebhookpatch - -# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. -# patches here are for enabling the CA injection for each CRD -#- patches/cainjection_in_etcdclusters.yaml -#+kubebuilder:scaffold:crdkustomizecainjectionpatch - -# the following config is for teaching kustomize how to do kustomization for CRDs. -configurations: -- kustomizeconfig.yaml diff --git a/config/crd/kustomizeconfig.yaml b/config/crd/kustomizeconfig.yaml deleted file mode 100644 index ec5c150a..00000000 --- a/config/crd/kustomizeconfig.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# This file is for teaching kustomize how to substitute name and namespace reference in CRD -nameReference: -- kind: Service - version: v1 - fieldSpecs: - - kind: CustomResourceDefinition - version: v1 - group: apiextensions.k8s.io - path: spec/conversion/webhook/clientConfig/service/name - -namespace: -- kind: CustomResourceDefinition - version: v1 - group: apiextensions.k8s.io - path: spec/conversion/webhook/clientConfig/service/namespace - create: false - -varReference: -- path: metadata/annotations diff --git a/config/crd/patches/cainjection_in_etcdclusters.yaml b/config/crd/patches/cainjection_in_etcdclusters.yaml deleted file mode 100644 index 73f9aa37..00000000 --- a/config/crd/patches/cainjection_in_etcdclusters.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# The following patch adds a directive for certmanager to inject CA into the CRD -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) - name: etcdclusters.etcd-operator.cozystack.io diff --git a/config/crd/patches/webhook_in_etcdclusters.yaml b/config/crd/patches/webhook_in_etcdclusters.yaml deleted file mode 100644 index 24aa8134..00000000 --- a/config/crd/patches/webhook_in_etcdclusters.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# The following patch enables a conversion webhook for the CRD -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: etcdclusters.etcd-operator.cozystack.io -spec: - conversion: - strategy: Webhook - webhook: - clientConfig: - service: - namespace: system - name: webhook-service - path: /convert - conversionReviewVersions: - - v1 diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml deleted file mode 100644 index 5edb3c1e..00000000 --- a/config/default/kustomization.yaml +++ /dev/null @@ -1,88 +0,0 @@ -# Adds namespace to all resources. -namespace: etcd-operator-system - -# Value of this field is prepended to the -# names of all resources, e.g. a deployment named -# "wordpress" becomes "alices-wordpress". -# Note that it should also match with the prefix (text before '-') of the namespace -# field above. -namePrefix: etcd-operator- - -# Labels to add to all resources and selectors. -#commonLabels: -# someName: someValue - -bases: -- ../crd -- ../rbac -- ../manager -# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in -# crd/kustomization.yaml -#- ../webhook -# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. -#- ../certmanager -# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. -#- ../prometheus - -patchesStrategicMerge: -# Protect the /metrics endpoint by putting it behind auth. -# If you want your controller-manager to expose the /metrics -# endpoint w/o any authn/z, please comment the following line. -- manager_auth_proxy_patch.yaml - -# Keep the manager's OPERATOR_IMAGE env in sync with its own container image, -# so the snapshot Job and restore init container always run the deployed -# operator image. `make deploy IMG=...` rewrites the image; this copies that -# value into the env var. -replacements: -- source: - kind: Deployment - name: controller-manager - fieldPath: spec.template.spec.containers.[name=manager].image - targets: - - select: - kind: Deployment - name: controller-manager - fieldPaths: - - spec.template.spec.containers.[name=manager].env.[name=OPERATOR_IMAGE].value - - - -# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in -# crd/kustomization.yaml -#- manager_webhook_patch.yaml - -# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. -# Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. -# 'CERTMANAGER' needs to be enabled to use ca injection -#- webhookcainjection_patch.yaml - -# the following config is for teaching kustomize how to do var substitution -vars: -# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. -#- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR -# objref: -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert # this name should match the one in certificate.yaml -# fieldref: -# fieldpath: metadata.namespace -#- name: CERTIFICATE_NAME -# objref: -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert # this name should match the one in certificate.yaml -#- name: SERVICE_NAMESPACE # namespace of the service -# objref: -# kind: Service -# version: v1 -# name: webhook-service -# fieldref: -# fieldpath: metadata.namespace -#- name: SERVICE_NAME -# objref: -# kind: Service -# version: v1 -# name: webhook-service diff --git a/config/default/manager_auth_proxy_patch.yaml b/config/default/manager_auth_proxy_patch.yaml deleted file mode 100644 index 61c1328d..00000000 --- a/config/default/manager_auth_proxy_patch.yaml +++ /dev/null @@ -1,58 +0,0 @@ -# This patch inject a sidecar container which is a HTTP proxy for the -# controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. -apiVersion: apps/v1 -kind: Deployment -metadata: - name: controller-manager - namespace: system -spec: - template: - spec: - affinity: - nodeAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - nodeSelectorTerms: - - matchExpressions: - - key: kubernetes.io/arch - operator: In - values: - - amd64 - - arm64 - - ppc64le - - s390x - - key: kubernetes.io/os - operator: In - values: - - linux - containers: - - name: kube-rbac-proxy - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - "ALL" - # gcr.io/kubebuilder/kube-rbac-proxy is gone (the gcr.io/kubebuilder - # registry shut down in early 2025); upstream now releases under its - # own GitHub org at ghcr.io/kube-rbac-proxy/kube-rbac-proxy. - image: ghcr.io/kube-rbac-proxy/kube-rbac-proxy:v0.22.0 - args: - - "--secure-listen-address=0.0.0.0:8443" - - "--upstream=http://127.0.0.1:8080/" - - "--logtostderr=true" - - "--v=0" - ports: - - containerPort: 8443 - protocol: TCP - name: https - resources: - limits: - cpu: 500m - memory: 128Mi - requests: - cpu: 5m - memory: 64Mi - - name: manager - args: - - "--health-probe-bind-address=:8081" - - "--metrics-bind-address=127.0.0.1:8080" - - "--leader-elect" diff --git a/config/default/manager_config_patch.yaml b/config/default/manager_config_patch.yaml deleted file mode 100644 index f6f58916..00000000 --- a/config/default/manager_config_patch.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: controller-manager - namespace: system -spec: - template: - spec: - containers: - - name: manager diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml deleted file mode 100644 index 5c5f0b84..00000000 --- a/config/manager/kustomization.yaml +++ /dev/null @@ -1,2 +0,0 @@ -resources: -- manager.yaml diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml deleted file mode 100644 index b3558dc6..00000000 --- a/config/manager/manager.yaml +++ /dev/null @@ -1,117 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - labels: - control-plane: controller-manager - app.kubernetes.io/name: namespace - app.kubernetes.io/instance: system - app.kubernetes.io/component: manager - app.kubernetes.io/created-by: etcd-operator - app.kubernetes.io/part-of: etcd-operator - app.kubernetes.io/managed-by: kustomize - name: system ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: controller-manager - namespace: system - labels: - control-plane: controller-manager - app.kubernetes.io/name: deployment - app.kubernetes.io/instance: controller-manager - app.kubernetes.io/component: manager - app.kubernetes.io/created-by: etcd-operator - app.kubernetes.io/part-of: etcd-operator - app.kubernetes.io/managed-by: kustomize -spec: - selector: - matchLabels: - control-plane: controller-manager - replicas: 1 - template: - metadata: - annotations: - kubectl.kubernetes.io/default-container: manager - labels: - control-plane: controller-manager - spec: - # TODO(user): Uncomment the following code to configure the nodeAffinity expression - # according to the platforms which are supported by your solution. - # It is considered best practice to support multiple architectures. You can - # build your manager image using the makefile target docker-buildx. - # affinity: - # nodeAffinity: - # requiredDuringSchedulingIgnoredDuringExecution: - # nodeSelectorTerms: - # - matchExpressions: - # - key: kubernetes.io/arch - # operator: In - # values: - # - amd64 - # - arm64 - # - ppc64le - # - s390x - # - key: kubernetes.io/os - # operator: In - # values: - # - linux - securityContext: - runAsNonRoot: true - # TODO(user): For common cases that do not require escalating privileges - # it is recommended to ensure that all your Pods/Containers are restrictive. - # More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted - # Please uncomment the following code if your project does NOT have to work on old Kubernetes - # versions < 1.19 or on vendors versions which do NOT support this field by default (i.e. Openshift < 4.11 ). - # seccompProfile: - # type: RuntimeDefault - containers: - - command: - - /manager - args: - - --leader-elect - image: controller:latest - name: manager - # OPERATOR_IMAGE must equal this container's own image: the snapshot Job - # and the restore init container run the operator image as the agent. - # The value here is a placeholder kept in sync with the `image:` field - # by a kustomize replacement in config/default; `make deploy IMG=...` - # propagates the real ref automatically. The flag --operator-image - # defaults to this env var. - # - # WARNING: if you apply config/ directly (or via a kustomize base that - # does NOT run that image replacement), this stays "controller:latest". - # The operator refuses to start on that placeholder (it would otherwise - # make every snapshot/restore Pod ImagePullBackOff), exiting with a clear - # error — override OPERATOR_IMAGE (and image:) to the real operator ref. - env: - - name: OPERATOR_IMAGE - value: controller:latest - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - "ALL" - livenessProbe: - httpGet: - path: /healthz - port: 8081 - initialDelaySeconds: 15 - periodSeconds: 20 - readinessProbe: - httpGet: - path: /readyz - port: 8081 - initialDelaySeconds: 5 - periodSeconds: 10 - # TODO(user): Configure the resources accordingly based on the project requirements. - # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - resources: - limits: - cpu: 500m - memory: 128Mi - requests: - cpu: 10m - memory: 64Mi - serviceAccountName: controller-manager - terminationGracePeriodSeconds: 10 diff --git a/config/prometheus/kustomization.yaml b/config/prometheus/kustomization.yaml deleted file mode 100644 index ed137168..00000000 --- a/config/prometheus/kustomization.yaml +++ /dev/null @@ -1,2 +0,0 @@ -resources: -- monitor.yaml diff --git a/config/prometheus/monitor.yaml b/config/prometheus/monitor.yaml deleted file mode 100644 index 70b780ba..00000000 --- a/config/prometheus/monitor.yaml +++ /dev/null @@ -1,26 +0,0 @@ - -# Prometheus Monitor Service (Metrics) -apiVersion: monitoring.coreos.com/v1 -kind: ServiceMonitor -metadata: - labels: - control-plane: controller-manager - app.kubernetes.io/name: servicemonitor - app.kubernetes.io/instance: controller-manager-metrics-monitor - app.kubernetes.io/component: metrics - app.kubernetes.io/created-by: etcd-operator - app.kubernetes.io/part-of: etcd-operator - app.kubernetes.io/managed-by: kustomize - name: controller-manager-metrics-monitor - namespace: system -spec: - endpoints: - - path: /metrics - port: https - scheme: https - bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token - tlsConfig: - insecureSkipVerify: true - selector: - matchLabels: - control-plane: controller-manager diff --git a/config/rbac/auth_proxy_client_clusterrole.yaml b/config/rbac/auth_proxy_client_clusterrole.yaml deleted file mode 100644 index 71f03b57..00000000 --- a/config/rbac/auth_proxy_client_clusterrole.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - app.kubernetes.io/name: clusterrole - app.kubernetes.io/instance: metrics-reader - app.kubernetes.io/component: kube-rbac-proxy - app.kubernetes.io/created-by: etcd-operator - app.kubernetes.io/part-of: etcd-operator - app.kubernetes.io/managed-by: kustomize - name: metrics-reader -rules: -- nonResourceURLs: - - "/metrics" - verbs: - - get diff --git a/config/rbac/auth_proxy_role.yaml b/config/rbac/auth_proxy_role.yaml deleted file mode 100644 index eab3a381..00000000 --- a/config/rbac/auth_proxy_role.yaml +++ /dev/null @@ -1,24 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - app.kubernetes.io/name: clusterrole - app.kubernetes.io/instance: proxy-role - app.kubernetes.io/component: kube-rbac-proxy - app.kubernetes.io/created-by: etcd-operator - app.kubernetes.io/part-of: etcd-operator - app.kubernetes.io/managed-by: kustomize - name: proxy-role -rules: -- apiGroups: - - authentication.k8s.io - resources: - - tokenreviews - verbs: - - create -- apiGroups: - - authorization.k8s.io - resources: - - subjectaccessreviews - verbs: - - create diff --git a/config/rbac/auth_proxy_role_binding.yaml b/config/rbac/auth_proxy_role_binding.yaml deleted file mode 100644 index a33b0e8d..00000000 --- a/config/rbac/auth_proxy_role_binding.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - labels: - app.kubernetes.io/name: clusterrolebinding - app.kubernetes.io/instance: proxy-rolebinding - app.kubernetes.io/component: kube-rbac-proxy - app.kubernetes.io/created-by: etcd-operator - app.kubernetes.io/part-of: etcd-operator - app.kubernetes.io/managed-by: kustomize - name: proxy-rolebinding -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: proxy-role -subjects: -- kind: ServiceAccount - name: controller-manager - namespace: system diff --git a/config/rbac/auth_proxy_service.yaml b/config/rbac/auth_proxy_service.yaml deleted file mode 100644 index 79609e81..00000000 --- a/config/rbac/auth_proxy_service.yaml +++ /dev/null @@ -1,21 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - labels: - control-plane: controller-manager - app.kubernetes.io/name: service - app.kubernetes.io/instance: controller-manager-metrics-service - app.kubernetes.io/component: kube-rbac-proxy - app.kubernetes.io/created-by: etcd-operator - app.kubernetes.io/part-of: etcd-operator - app.kubernetes.io/managed-by: kustomize - name: controller-manager-metrics-service - namespace: system -spec: - ports: - - name: https - port: 8443 - protocol: TCP - targetPort: https - selector: - control-plane: controller-manager diff --git a/config/rbac/etcdcluster_editor_role.yaml b/config/rbac/etcdcluster_editor_role.yaml deleted file mode 100644 index 17e4e2b9..00000000 --- a/config/rbac/etcdcluster_editor_role.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# permissions for end users to edit etcdclusters. -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - app.kubernetes.io/name: clusterrole - app.kubernetes.io/instance: etcdcluster-editor-role - app.kubernetes.io/component: rbac - app.kubernetes.io/created-by: etcd-operator - app.kubernetes.io/part-of: etcd-operator - app.kubernetes.io/managed-by: kustomize - name: etcdcluster-editor-role -rules: -- apiGroups: - - etcd-operator.cozystack.io - resources: - - etcdclusters - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - etcd-operator.cozystack.io - resources: - - etcdclusters/status - verbs: - - get diff --git a/config/rbac/etcdcluster_viewer_role.yaml b/config/rbac/etcdcluster_viewer_role.yaml deleted file mode 100644 index 260c8de1..00000000 --- a/config/rbac/etcdcluster_viewer_role.yaml +++ /dev/null @@ -1,27 +0,0 @@ -# permissions for end users to view etcdclusters. -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - app.kubernetes.io/name: clusterrole - app.kubernetes.io/instance: etcdcluster-viewer-role - app.kubernetes.io/component: rbac - app.kubernetes.io/created-by: etcd-operator - app.kubernetes.io/part-of: etcd-operator - app.kubernetes.io/managed-by: kustomize - name: etcdcluster-viewer-role -rules: -- apiGroups: - - etcd-operator.cozystack.io - resources: - - etcdclusters - verbs: - - get - - list - - watch -- apiGroups: - - etcd-operator.cozystack.io - resources: - - etcdclusters/status - verbs: - - get diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml deleted file mode 100644 index 731832a6..00000000 --- a/config/rbac/kustomization.yaml +++ /dev/null @@ -1,18 +0,0 @@ -resources: -# All RBAC will be applied under this service account in -# the deployment namespace. You may comment out this resource -# if your manager will use a service account that exists at -# runtime. Be sure to update RoleBinding and ClusterRoleBinding -# subjects if changing service account names. -- service_account.yaml -- role.yaml -- role_binding.yaml -- leader_election_role.yaml -- leader_election_role_binding.yaml -# Comment the following 4 lines if you want to disable -# the auth proxy (https://github.com/brancz/kube-rbac-proxy) -# which protects your /metrics endpoint. -- auth_proxy_service.yaml -- auth_proxy_role.yaml -- auth_proxy_role_binding.yaml -- auth_proxy_client_clusterrole.yaml diff --git a/config/rbac/leader_election_role.yaml b/config/rbac/leader_election_role.yaml deleted file mode 100644 index 879932e2..00000000 --- a/config/rbac/leader_election_role.yaml +++ /dev/null @@ -1,44 +0,0 @@ -# permissions to do leader election. -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - labels: - app.kubernetes.io/name: role - app.kubernetes.io/instance: leader-election-role - app.kubernetes.io/component: rbac - app.kubernetes.io/created-by: etcd-operator - app.kubernetes.io/part-of: etcd-operator - app.kubernetes.io/managed-by: kustomize - name: leader-election-role -rules: -- apiGroups: - - "" - resources: - - configmaps - verbs: - - get - - list - - watch - - create - - update - - patch - - delete -- apiGroups: - - coordination.k8s.io - resources: - - leases - verbs: - - get - - list - - watch - - create - - update - - patch - - delete -- apiGroups: - - "" - resources: - - events - verbs: - - create - - patch diff --git a/config/rbac/leader_election_role_binding.yaml b/config/rbac/leader_election_role_binding.yaml deleted file mode 100644 index 53c67521..00000000 --- a/config/rbac/leader_election_role_binding.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - labels: - app.kubernetes.io/name: rolebinding - app.kubernetes.io/instance: leader-election-rolebinding - app.kubernetes.io/component: rbac - app.kubernetes.io/created-by: etcd-operator - app.kubernetes.io/part-of: etcd-operator - app.kubernetes.io/managed-by: kustomize - name: leader-election-rolebinding -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: leader-election-role -subjects: -- kind: ServiceAccount - name: controller-manager - namespace: system diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml deleted file mode 100644 index 910c0b04..00000000 --- a/config/rbac/role.yaml +++ /dev/null @@ -1,126 +0,0 @@ ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: manager-role -rules: -- apiGroups: - - "" - resources: - - persistentvolumeclaims - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - "" - resources: - - pods - verbs: - - create - - delete - - get - - list - - patch - - watch -- apiGroups: - - "" - resources: - - pods/log - verbs: - - get -- apiGroups: - - "" - resources: - - secrets - verbs: - - get - - list - - watch -- apiGroups: - - "" - resources: - - services - verbs: - - create - - get - - list - - patch - - update - - watch -- apiGroups: - - batch - resources: - - jobs - verbs: - - create - - delete - - get - - list - - watch -- apiGroups: - - cert-manager.io - resources: - - certificates - verbs: - - create - - get - - list - - patch - - update - - watch -- apiGroups: - - etcd-operator.cozystack.io - resources: - - etcdclusters - verbs: - - get - - list - - watch -- apiGroups: - - etcd-operator.cozystack.io - resources: - - etcdclusters/finalizers - - etcdmembers/finalizers - - etcdsnapshots/finalizers - verbs: - - update -- apiGroups: - - etcd-operator.cozystack.io - resources: - - etcdclusters/status - - etcdmembers/status - - etcdsnapshots/status - verbs: - - get - - patch - - update -- apiGroups: - - etcd-operator.cozystack.io - resources: - - etcdmembers - - etcdsnapshots - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - policy - resources: - - poddisruptionbudgets - verbs: - - create - - delete - - get - - list - - patch - - update - - watch diff --git a/config/rbac/role_binding.yaml b/config/rbac/role_binding.yaml deleted file mode 100644 index 966b6e66..00000000 --- a/config/rbac/role_binding.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - labels: - app.kubernetes.io/name: clusterrolebinding - app.kubernetes.io/instance: manager-rolebinding - app.kubernetes.io/component: rbac - app.kubernetes.io/created-by: etcd-operator - app.kubernetes.io/part-of: etcd-operator - app.kubernetes.io/managed-by: kustomize - name: manager-rolebinding -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: manager-role -subjects: -- kind: ServiceAccount - name: controller-manager - namespace: system diff --git a/config/rbac/service_account.yaml b/config/rbac/service_account.yaml deleted file mode 100644 index 092beca0..00000000 --- a/config/rbac/service_account.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - labels: - app.kubernetes.io/name: serviceaccount - app.kubernetes.io/instance: controller-manager - app.kubernetes.io/component: rbac - app.kubernetes.io/created-by: etcd-operator - app.kubernetes.io/part-of: etcd-operator - app.kubernetes.io/managed-by: kustomize - name: controller-manager - namespace: system diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml deleted file mode 100644 index b32c636e..00000000 --- a/config/samples/kustomization.yaml +++ /dev/null @@ -1,4 +0,0 @@ -## Append samples you want in your CSV to this file as resources ## -resources: -- _v1alpha2_etcdcluster.yaml -#+kubebuilder:scaffold:manifestskustomizesamples diff --git a/docs/installation.md b/docs/installation.md index 4916cbf1..73d3378b 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -52,56 +52,108 @@ cosign verify ghcr.io/cozystack/etcd-operator:$VERSION \ To pull a prebuilt image without the release manifests (e.g. to feed your own overlay), the image ref is `ghcr.io/cozystack/etcd-operator:`. -## Quick deploy (build from source) +## Install with Helm -The repo's Makefile drives a complete install. From a checkout: +Helm is the primary install path: the chart is the single source of truth for +the CRDs, RBAC, and the manager Deployment (the release manifests below are just +`helm template` of this same chart). Tagged releases publish it as an OCI Helm +chart to GHCR (`ghcr.io/cozystack/charts/etcd-operator`), versioned from the +same tag — the chart version is the tag without the leading `v`, and +`appVersion` keeps the `v`. The CRDs are generated straight into the chart and +templated into the release. ```sh -# 1. Install the CRDs cluster-wide. -make install +# Chart version == release tag without the leading 'v' +# (see https://github.com/cozystack/etcd-operator/releases). +VERSION=0.5.0 -# 2. Build the operator image (or skip to a prebuilt registry tag). +helm install etcd-operator oci://ghcr.io/cozystack/charts/etcd-operator \ + --version "$VERSION" \ + --namespace etcd-operator-system --create-namespace +``` + +By default the chart pulls `ghcr.io/cozystack/etcd-operator:` — the +image that same release published — so a stock install has nothing to +substitute. The chart wires that ref into **both** the manager's `image:` and +its `OPERATOR_IMAGE` env var (the image launched for snapshot/restore Pods); the +two must be identical, and the chart keeps them equal for you. Override the +image via `image.repository` / `image.tag` and both follow. + +The CRDs are **templated** into the release (not in Helm's install-only `crds/` +directory), so `helm upgrade` keeps them current with the chart — no separate +CRD-apply step on upgrade. They carry `helm.sh/resource-policy: keep`, so `helm +uninstall` leaves the CRDs (and therefore your `EtcdCluster`s and their data) in +place; deleting the CRDs is a deliberate, manual step. Set `crds.enabled=false` +to manage CRDs out-of-band, or `crds.keep=false` to let uninstall remove them. + +Common values (`--set key=value`, or a `-f my-values.yaml`): + +| Value | Default | Purpose | +|---|---|---| +| `image.repository` | `ghcr.io/cozystack/etcd-operator` | Operator image repo. Override to mirror or fork. | +| `image.tag` | chart `appVersion` | Operator image tag; also becomes `OPERATOR_IMAGE` (see above). | +| `replicaCount` | `1` | Operator replicas (leader election picks the active one). | +| `kubeRbacProxy.enabled` | `true` | Front `/metrics` with the kube-rbac-proxy SubjectAccessReview sidecar. Set `false` to bind metrics on `:8080` directly with no proxy. | +| `metrics.serviceMonitor.enabled` | `false` | Create a prometheus-operator `ServiceMonitor` for the metrics endpoint (needs the `monitoring.coreos.com` CRDs and `kubeRbacProxy.enabled`). | +| `crds.enabled` / `crds.keep` | `true` / `true` | Render the CRDs with the release / annotate them so uninstall keeps them. | +| `manager.resources` | 10m/64Mi → 500m/128Mi | Manager container requests/limits. | +| `imagePullSecrets` | `[]` | Pull secrets for a private registry mirror. | + +See `charts/etcd-operator/values.yaml` for the complete, annotated list. Verify +the install: + +```sh +kubectl -n etcd-operator-system get deploy +``` + +With release name `etcd-operator` the Deployment is named `etcd-operator`. The +release manifests (the kubectl-apply path above) render from this same chart, so +they produce the same name. The Deployment carries the label +`control-plane=controller-manager`, a name-agnostic handle for scripts. + +## Build from source + +The repo's Makefile drives a complete install via Helm. From a checkout (needs +`helm` v3.16+ on PATH): + +```sh +# 1. Build the operator image (or skip to a prebuilt registry tag). make docker-build docker-push IMG=/etcd-operator: -# 3. Deploy the operator (creates the etcd-operator-system namespace, -# ClusterRole/Binding, controller Deployment, metrics service). +# 2. Install/upgrade the operator (CRDs + RBAC + manager) with Helm. +# `make deploy` runs `helm upgrade --install` and wires image == OPERATOR_IMAGE. make deploy IMG=/etcd-operator: ``` The cluster must be able to pull from ``. For local clusters (`kind` / `minikube` / `k3d`), either sideload the image (`kind load docker-image ...`) or push to an ephemeral registry the cluster can reach (e.g. `ttl.sh/:1h`); otherwise the operator Deployment goes `ImagePullBackOff` with no clear hint from the operator side. -By default this lands in the `etcd-operator-system` namespace. The deployment name is `etcd-operator-controller-manager`. Verify: +`make deploy` installs the release `etcd-operator` into the `etcd-operator-system` +namespace (override with `HELM_RELEASE=` / `NAMESPACE=`). The Deployment is named +after the release. Verify: ```sh kubectl get pod -n etcd-operator-system -kubectl logs -n etcd-operator-system deploy/etcd-operator-controller-manager \ - -c manager --tail=20 +kubectl -n etcd-operator-system logs deploy/etcd-operator -c manager --tail=20 ``` -You should see the manager start lines and an empty work-queue (no `EtcdCluster` resources yet). +You should see the manager start lines and an empty work-queue (no `EtcdCluster` resources yet). Tear down with `make undeploy` (see [Teardown](#teardown)). -## Manual install (no Make) +## Rendering manifests (GitOps / no in-cluster Helm) -If you don't want to invoke the Makefile (e.g. GitOps environments where `kustomize` is run by a controller in-cluster): +For GitOps flows that apply plain YAML, render the chart with `helm template` +instead of installing it — this is exactly what `make build-dist-manifests` (and +the release pipeline) does to produce the release's `etcd-operator.yaml`: ```sh -# CRDs -kubectl apply -f config/crd/bases/ - -# Operator + RBAC + Service, rendered by kustomize: -bin/kustomize-v5.6.0 build config/default | kubectl apply -f - +helm template etcd-operator charts/etcd-operator \ + --namespace etcd-operator-system \ + --set image.repository= --set image.tag= \ + --set namespace.create=true | kubectl apply --server-side -f - ``` -Override the image inline: +`--server-side` avoids the client-side last-applied-config annotation size limit (the consolidated manifest embeds the full CRD schemas). `namespace.create=true` emits the Namespace so the output is self-contained. -```sh -cd config/manager && bin/kustomize-v5.6.0 edit set image controller= -cd ../.. && bin/kustomize-v5.6.0 build config/default | kubectl apply -f - -``` - -The `bin/kustomize-v*` binary is auto-downloaded by `make kustomize` (version pinned to `v5.6.0` in the Makefile); a system-installed `kustomize` works equally if you have one. - -> **Set the operator image, or snapshots/restores won't run.** The `config/default` overlay rewrites both the manager's `image:` *and* its `OPERATOR_IMAGE` env var to the same ref via a kustomize replacement — `OPERATOR_IMAGE` is the image the operator launches for snapshot Jobs and restore init containers. If you bypass that overlay (e.g. `kubectl apply -f config/manager/` directly, or a base that drops the replacement), `OPERATOR_IMAGE` stays the placeholder `controller:latest`. The operator **refuses to start** on that placeholder and exits with a clear error (rather than letting snapshot/restore Pods `ImagePullBackOff` later). Always render through `config/default` (which the commands above do) or set `OPERATOR_IMAGE` to the real ref by hand. +> **The chart keeps `image:` and `OPERATOR_IMAGE` equal for you.** `OPERATOR_IMAGE` is the image the operator launches for snapshot Jobs and restore init containers; it must match the manager image. The chart renders both from `image.repository`/`image.tag` (the `etcd-operator.image` helper in `_helpers.tpl`), so setting the image once covers both. If you hand-craft manifests and leave `OPERATOR_IMAGE` at the placeholder `controller:latest`, the operator **refuses to start** and exits with a clear error (rather than letting snapshot/restore Pods `ImagePullBackOff` later). ## Create your first cluster @@ -203,7 +255,7 @@ Operator's own toolchain (relevant when building from source): | controller-runtime | v0.21 | | k8s.io/api, k8s.io/client-go | v0.33 | | controller-gen | v0.18.0 | -| kustomize | v5.6.0 | +| Helm (install/render the chart) | v3.16+ | | etcd client (`go.etcd.io/etcd/client/v3`) | v3.6.11 | | Kubebuilder layout | v4 | @@ -211,7 +263,7 @@ All pinned in `go.mod`, `Dockerfile`, and `Makefile`. ## RBAC -The operator runs as a ClusterRole — it needs to watch `EtcdCluster` and `EtcdMember` across all namespaces, plus create/delete the per-member Pods, PVCs, and Services in each user namespace. The full role lives in `config/rbac/role.yaml` (regenerated from `+kubebuilder:rbac` markers — don't hand-edit). +The operator runs as a ClusterRole — it needs to watch `EtcdCluster` and `EtcdMember` across all namespaces, plus create/delete the per-member Pods, PVCs, and Services in each user namespace. The rules are generated from the `+kubebuilder:rbac` markers (by `make manifests`) into `charts/etcd-operator/files/manager-role-rules.yaml` and pulled into the chart's templated ClusterRole — don't hand-edit; edit the markers and regenerate. Single-namespace scoping is not currently exposed: `main.go` does not wire a namespace flag into the manager's `Cache.DefaultNamespaces`, so the manager always watches all namespaces. Limiting RBAC alone (ClusterRole → Role) is not sufficient — the manager will still attempt list/watch across the cluster and the API server will deny it. Scoped deployment is a follow-up. @@ -238,12 +290,16 @@ This is outside the operator's scope but documented because operators ask. # Remove individual clusters first — their finalizers will clean up etcd state. kubectl delete etcdcluster.etcd-operator.cozystack.io --all -A -# Remove the operator. +# Remove the operator (helm uninstall). The CRDs carry +# helm.sh/resource-policy: keep, so they (and any surviving EtcdClusters) are +# intentionally left in place. make undeploy -# Remove the CRDs (only after all EtcdClusters are gone; the CRDs have -# protected finalizers via the operator). -make uninstall +# Remove the CRDs too (only after all EtcdClusters are gone) — deleting them +# cascade-deletes every remaining EtcdCluster: +kubectl delete crd etcdclusters.etcd-operator.cozystack.io \ + etcdmembers.etcd-operator.cozystack.io \ + etcdsnapshots.etcd-operator.cozystack.io ``` Deleting an `EtcdCluster` while it's running cascades through every owned resource: the operator's finalizer on each `EtcdMember` calls `MemberRemove` (when the cluster itself is also being deleted, the operator detects this and skips `MemberRemove` to avoid a deadlock — see `handleDeletion` in `controllers/etcdmember_controller.go`). Pods and PVCs are then GC'd via owner-refs. @@ -261,6 +317,23 @@ For now, in-place operator upgrades work via `kubectl set image` on the operator This is manual and slow. A native rolling upgrade is a tracked follow-up. +## kubectl-etcd plugin + +`kubectl-etcd` is an optional client-side [kubectl plugin](https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/) for day-2 operations on operator-managed clusters (member list, status, defrag, compact, alarms, snapshot, member add/remove). It runs on your workstation against your kubeconfig — it is **not** part of the operator image. + +Each release attaches `kubectl-etcd--` binaries (with `cli-SHA256SUMS.txt`). Install it onto your `PATH` named `kubectl-etcd`, and kubectl picks it up as `kubectl etcd`: + +```sh +VERSION=v0.5.0; OS=$(uname -s | tr A-Z a-z); ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') +curl -sSLo kubectl-etcd "https://github.com/cozystack/etcd-operator/releases/download/$VERSION/kubectl-etcd-$OS-$ARCH" +chmod +x kubectl-etcd && sudo mv kubectl-etcd /usr/local/bin/ # any dir on $PATH works + +kubectl etcd --version +kubectl etcd members --help +``` + +Or build from a checkout with `make kubectl-etcd` (lands in `bin/kubectl-etcd`). There is no krew package yet. + ## Development Out-of-cluster development run (against the current `$KUBECONFIG`): diff --git a/docs/migration.md b/docs/migration.md index 881f2e42..c4cc2277 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -19,7 +19,16 @@ Clients that connect by DNS name keep working; one Service changes shape (ClusterIP → headless) and has consumer prerequisites — see [Endpoint compatibility](#endpoint-compatibility) before you `--apply`. -Build it with `make etcd-migrate` (lands in `bin/etcd-migrate`). +Get it from the GitHub release — each release attaches +`etcd-migrate--` binaries (with a `cli-SHA256SUMS.txt`): + +```sh +VERSION=v0.5.0; OS=$(uname -s | tr A-Z a-z); ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') +curl -sSLo etcd-migrate "https://github.com/cozystack/etcd-operator/releases/download/$VERSION/etcd-migrate-$OS-$ARCH" +chmod +x etcd-migrate && ./etcd-migrate version +``` + +Or build from a checkout with `make etcd-migrate` (lands in `bin/etcd-migrate`). ### How adoption works @@ -92,17 +101,21 @@ adoption. ### Prerequisites 1. **Scale both operators to zero.** The legacy etcd Pods keep running — only - the controllers must be quiet: + the controllers must be quiet. The legacy (v1alpha1) controller is + `etcd-operator-controller-manager`; this operator's Helm release is named + `etcd-operator`: ```sh - kubectl -n etcd-operator-system scale deploy etcd-operator-controller-manager --replicas=0 + kubectl -n etcd-operator-system scale deploy etcd-operator-controller-manager --replicas=0 # legacy + kubectl -n etcd-operator-system scale deploy etcd-operator --replicas=0 # new ``` The tool verifies this for both Deployments before doing anything (`--legacy-controller` / `--new-controller` override the coordinates, `--skip-controller-check` bypasses the gate). -2. The new CRDs (`etcd-operator.cozystack.io/v1alpha2`) must be installed - (`make install`). +2. The new CRDs (`etcd-operator.cozystack.io/v1alpha2`) must be installed — + they ship with the operator chart (`make deploy IMG=...`, or `helm install`; + see [installation](installation.md)). 3. A kubeconfig that can list/delete the legacy CRs cluster-wide, create the new ones, and patch pods/PVCs/Services. 4. **All etcd pods Ready.** Adoption refuses clusters with missing members, @@ -230,7 +243,7 @@ After `--apply` succeeds, **scale the new operator up** — it takes over the adopted clusters without touching the pods: ```sh -kubectl -n etcd-operator-system scale deploy etcd-operator-controller-manager --replicas=1 +kubectl -n etcd-operator-system scale deploy etcd-operator --replicas=1 ``` The tool deletes the migrated legacy **CRs** but never the **CRDs**. Once no diff --git a/docs/operations.md b/docs/operations.md index 0bc2efe6..c5ce76f3 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -355,7 +355,7 @@ kubectl exec -n "$POD" -- etcdctl --endpoints=http://localhost:2379 \ The operator runs in `etcd-operator-system` by default. Log lines you'll see most often: ```sh -kubectl logs -n etcd-operator-system deploy/etcd-operator-controller-manager \ +kubectl logs -n etcd-operator-system deploy/etcd-operator \ -c manager --tail=200 ``` diff --git a/hack/e2e.sh b/hack/e2e.sh index 3ecca865..dcb2ffb5 100755 --- a/hack/e2e.sh +++ b/hack/e2e.sh @@ -47,7 +47,7 @@ dump_diagnostics() { echo "--- e2e failed; dumping cluster state before teardown" kubectl get etcdclusters,etcdmembers,pods,certificates,secrets -A || true kubectl get datastores,tenantcontrolplanes -A || true - kubectl -n etcd-operator-system logs deploy/etcd-operator-controller-manager --tail=200 || true + kubectl -n etcd-operator-system logs -l control-plane=controller-manager --all-containers --tail=200 || true kubectl -n kamaji-system logs deploy/kamaji --tail=200 || true # The tenant namespace is where the longest wait (TenantControlPlane # Ready) fails — dump every pod's logs there, or the one failure mode @@ -109,8 +109,12 @@ helm upgrade --install kamaji clastix/kamaji \ echo "--- building and deploying the operator ($IMG)" docker build -t "$IMG" . kind load docker-image "$IMG" --name "$KIND_CLUSTER_NAME" -make install deploy IMG="$IMG" -kubectl -n etcd-operator-system wait deploy/etcd-operator-controller-manager \ +# Helm install: CRDs are templated into the release and image == OPERATOR_IMAGE +# is wired by the chart, so this one command lands CRDs + RBAC + manager. +make deploy IMG="$IMG" +# Select by the chart's control-plane label rather than a fixed Deployment name. +kubectl -n etcd-operator-system wait deploy \ + -l control-plane=controller-manager \ --for=condition=Available --timeout=5m echo "--- running e2e suite" diff --git a/hack/release-smoke.sh b/hack/release-smoke.sh index e64d8137..f559b80a 100755 --- a/hack/release-smoke.sh +++ b/hack/release-smoke.sh @@ -102,15 +102,27 @@ kind load docker-image "$IMG" --name "$KIND_CLUSTER_NAME" if [ "$INSTALL_MODE" = manifest ]; then echo "--- [manifest] rendering release install manifests (IMG=$IMG)" make build-dist-manifests IMG="$IMG" + # build-dist-manifests must render purely (it is `helm template` piped through + # yq): it writes only dist/ (gitignored) and must not mutate any tracked file. + # A dirty tree here means a regression reintroduced an in-place edit, which + # would also spuriously trip ci.yml's codegen-drift gate. Assert cleanliness + # (skipped if this isn't a git checkout, e.g. a release tarball). + if command -v git >/dev/null 2>&1 && git rev-parse --git-dir >/dev/null 2>&1 \ + && [ -n "$(git status --porcelain --untracked-files=no)" ]; then + echo "ERROR: 'make build-dist-manifests' modified tracked files (it must render purely):" + git --no-pager status --porcelain --untracked-files=no + git --no-pager diff + exit 1 + fi echo "--- [manifest] installing from the rendered release manifest" # Server-side apply: the consolidated manifest embeds the full CRD schemas, # whose size can exceed the client-side last-applied-config annotation # limit. This is also the documented release-install path. kubectl apply --server-side -f dist/etcd-operator.yaml else - echo "--- [helm] vendoring CRDs and installing the chart (image=$IMG)" - make helm-sync - # Split $IMG into repo:tag for the chart's image.repository / image.tag. + echo "--- [helm] installing the chart (image=$IMG)" + # CRDs are templated into the chart and committed (drift-gated), so no sync + # step is needed. Split $IMG into repo:tag for image.repository / image.tag. helm upgrade --install etcd-operator charts/etcd-operator \ --namespace "$NAMESPACE" --create-namespace \ --set image.repository="${IMG%:*}" \ diff --git a/main.go b/main.go index e79b46a0..1aac2b56 100644 --- a/main.go +++ b/main.go @@ -48,16 +48,18 @@ import ( const defaultClusterDomain = "cluster.local" -// placeholderOperatorImage is the image:/OPERATOR_IMAGE value baked into -// config/manager as the kustomize-replacement target. Running with it -// un-rewritten means snapshot/restore Pods would ImagePullBackOff forever. +// placeholderOperatorImage is the un-set sentinel image ref. The Helm chart +// always renders a real repository:tag (and keeps image == OPERATOR_IMAGE), so +// this only trips when the operator is run with OPERATOR_IMAGE explicitly left +// at the placeholder — running on it means snapshot/restore Pods would +// ImagePullBackOff forever. const placeholderOperatorImage = "controller:latest" // operatorImageError rejects the un-substituted image placeholder so the // operator fails fast at startup rather than silently at the first snapshot. func operatorImageError(img string) error { if img == placeholderOperatorImage { - return fmt.Errorf("operator image is the un-substituted placeholder %q; set OPERATOR_IMAGE / --operator-image to the real operator image (deploy via the config/default overlay or `make deploy IMG=...`), otherwise snapshot/restore Pods will ImagePullBackOff", img) + return fmt.Errorf("operator image is the un-substituted placeholder %q; set OPERATOR_IMAGE / --operator-image to the real operator image (deploy with `make deploy IMG=...`, or `helm install --set image.repository= --set image.tag=`), otherwise snapshot/restore Pods will ImagePullBackOff", img) } return nil } From 27ff90d9fbbfa77cdb60ae56cad0dbdbd75c2c1b Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Wed, 10 Jun 2026 15:20:13 +0300 Subject: [PATCH 3/4] fix(ci): build the operator image natively for multi-arch Pin the Dockerfile builder stage to $BUILDPLATFORM so a buildx --platform=linux/amd64,linux/arm64 build cross-compiles via GOARCH instead of running go build under emulation; without it the arm64 leg fails with exec format error on an amd64 runner with no QEMU. Add a no-QEMU multi-arch build-only CI job that fails closed if the pin regresses. Signed-off-by: Timofei Larkin --- .github/workflows/ci.yml | 14 ++++++++++++++ Dockerfile | 9 +++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8d9b1c3e..4ce89287 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,20 @@ on: branches: [ main ] jobs: + image-multiarch: + # Build-only assertion that the operator image builds for every published + # platform. Deliberately sets up buildx WITHOUT QEMU: the Dockerfile builder + # is pinned to $BUILDPLATFORM and Go cross-compiles via GOARCH, so both legs + # must build natively on this amd64 runner with no emulation. If the + # --platform=$BUILDPLATFORM pin regresses, the arm64 leg fails here (exec + # format error) instead of silently breaking the tag-release publish. + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: docker/setup-buildx-action@v3.3.0 + - name: Build multi-arch image (no push, no QEMU) + run: docker buildx build --platform linux/amd64,linux/arm64 -t etcd-operator:buildtest . + verify: runs-on: ubuntu-latest steps: diff --git a/Dockerfile b/Dockerfile index 82729f64..117e934f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,10 @@ -# Build the manager binary -FROM golang:1.25.10 AS builder +# Build the manager binary. +# Pin the builder to the BUILD platform so a multi-arch `buildx --platform` +# build runs the builder natively (no QEMU) and Go cross-compiles via the +# GOARCH=${TARGETARCH} below. Without this, buildx instantiates the builder as +# the target arch and `go build` runs under emulation — which fails the arm64 +# leg on an amd64 runner that has no binfmt registered. +FROM --platform=$BUILDPLATFORM golang:1.25.10 AS builder ARG TARGETOS ARG TARGETARCH From 31e0445522a11affb3d6ce361be39d2c8e192d4b Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Thu, 11 Jun 2026 11:29:28 +0300 Subject: [PATCH 4/4] fix(ci): address review quick wins - Quote/env-ize shell expansions in helm-publish and release-assets (script-injection hardening for github.ref_name/github.actor; shellcheck-clean). - Require serviceAccount.name when serviceAccount.create=false instead of falling back to the namespace default SA (avoids binding the operator ClusterRole to default). - Add least-privilege 'permissions: contents: read' to the CI workflow. - Pin release-drafter to its v6.0.0 commit SHA (runs under pull_request_target). - Check the fmt.Fprintln error in etcd-migrate's version command (errcheck). Signed-off-by: Timofei Larkin --- .github/workflows/ci.yml | 4 ++++ .github/workflows/helm-publish.yml | 19 ++++++++++++------- .github/workflows/release-assets.yml | 12 ++++++++---- .github/workflows/release-drafter.yml | 2 +- charts/etcd-operator/templates/_helpers.tpl | 5 ++++- charts/etcd-operator/values.yaml | 3 +++ cmd/etcd-migrate/main.go | 2 +- 7 files changed, 33 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4ce89287..46b845f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,10 @@ on: push: branches: [ main ] +# CI only builds and tests — no writes to the repo, releases, or packages. +permissions: + contents: read + jobs: image-multiarch: # Build-only assertion that the operator image builds for every published diff --git a/.github/workflows/helm-publish.yml b/.github/workflows/helm-publish.yml index 0b46978a..59f2373e 100644 --- a/.github/workflows/helm-publish.yml +++ b/.github/workflows/helm-publish.yml @@ -43,23 +43,28 @@ jobs: run: make manifests - name: Resolve chart versions from tag + env: + REF_NAME: ${{ github.ref_name }} run: | - TAG=${{ github.ref_name }} - echo "RELEASE_TAG=${TAG}" >> $GITHUB_ENV + TAG="$REF_NAME" + echo "RELEASE_TAG=${TAG}" >> "$GITHUB_ENV" # Chart version is semver without the leading v; appVersion keeps it. - echo "RELEASE_TAG_TRIMMED_V=${TAG#v}" >> $GITHUB_ENV + echo "RELEASE_TAG_TRIMMED_V=${TAG#v}" >> "$GITHUB_ENV" - name: Helm registry login + env: + ACTOR: ${{ github.actor }} + TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | helm registry login \ - --username ${{ github.actor }} \ - --password ${{ secrets.GITHUB_TOKEN }} \ - ${{ env.REGISTRY }} + --username "$ACTOR" \ + --password "$TOKEN" \ + "${{ env.REGISTRY }}" - name: Package chart working-directory: charts run: | - helm package ${{ env.CHART_NAME }} \ + helm package "${{ env.CHART_NAME }}" \ --version "${RELEASE_TAG_TRIMMED_V}" \ --app-version "${RELEASE_TAG}" diff --git a/.github/workflows/release-assets.yml b/.github/workflows/release-assets.yml index 68b3f2d3..1e853381 100644 --- a/.github/workflows/release-assets.yml +++ b/.github/workflows/release-assets.yml @@ -36,10 +36,12 @@ jobs: version: 'v3.16.4' - name: Resolve release tag - run: echo "RELEASE_TAG=${{ github.ref_name }}" >> $GITHUB_ENV + env: + REF_NAME: ${{ github.ref_name }} + run: echo "RELEASE_TAG=$REF_NAME" >> "$GITHUB_ENV" - name: Render install manifests - run: make build-dist-manifests IMG=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${RELEASE_TAG} + run: make build-dist-manifests IMG="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${RELEASE_TAG}" - uses: svenstaro/upload-release-action@2.9.0 with: @@ -82,10 +84,12 @@ jobs: cache: true - name: Resolve release tag - run: echo "RELEASE_TAG=${{ github.ref_name }}" >> $GITHUB_ENV + env: + REF_NAME: ${{ github.ref_name }} + run: echo "RELEASE_TAG=$REF_NAME" >> "$GITHUB_ENV" - name: Cross-compile CLIs - run: make dist-cli VERSION=${RELEASE_TAG} + run: make dist-cli VERSION="${RELEASE_TAG}" - name: Upload etcd-migrate binaries uses: svenstaro/upload-release-action@2.9.0 diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index b4580405..5d18ee13 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -27,7 +27,7 @@ jobs: # code under it is the canonical fork-to-RCE pattern. release-drafter # touches no repo code, so this job stays safe as long as nothing here # checks out untrusted refs. - - uses: release-drafter/release-drafter@v6.0.0 + - uses: release-drafter/release-drafter@3f0f87098bd6b5c5b9a36d49c41d998ea58f9348 # v6.0.0 with: disable-releaser: ${{ github.ref != 'refs/heads/main' }} config-name: release-drafter.yml diff --git a/charts/etcd-operator/templates/_helpers.tpl b/charts/etcd-operator/templates/_helpers.tpl index 475b2cdb..75c467b5 100644 --- a/charts/etcd-operator/templates/_helpers.tpl +++ b/charts/etcd-operator/templates/_helpers.tpl @@ -42,7 +42,10 @@ app.kubernetes.io/instance: {{ .Release.Name }} {{- if .Values.serviceAccount.create -}} {{- include "etcd-operator.fullname" . -}} {{- else -}} -default +{{- /* Don't silently fall back to the namespace "default" SA: rbac.yaml binds +the operator's broad ClusterRole to this name, and binding it to "default" +would hand those permissions to every workload using the default SA. */ -}} +{{- required "serviceAccount.name is required when serviceAccount.create is false" .Values.serviceAccount.name -}} {{- end -}} {{- end -}} diff --git a/charts/etcd-operator/values.yaml b/charts/etcd-operator/values.yaml index 9205d58f..fb68d85c 100644 --- a/charts/etcd-operator/values.yaml +++ b/charts/etcd-operator/values.yaml @@ -43,6 +43,9 @@ fullnameOverride: "" serviceAccount: # -- Create the operator ServiceAccount. create: true + # -- Name of an existing ServiceAccount to use when create is false. Required + # in that case — the operator's ClusterRole is bound to this name. + name: "" # -- Extra annotations for the ServiceAccount. annotations: {} diff --git a/cmd/etcd-migrate/main.go b/cmd/etcd-migrate/main.go index af1ab3fd..171793bb 100644 --- a/cmd/etcd-migrate/main.go +++ b/cmd/etcd-migrate/main.go @@ -77,7 +77,7 @@ explicit --skip-backup.`, Short: "Print the etcd-migrate binary version", Args: cobra.NoArgs, Run: func(cmd *cobra.Command, _ []string) { - fmt.Fprintln(cmd.OutOrStdout(), version) + _, _ = fmt.Fprintln(cmd.OutOrStdout(), version) }, }) if err := rootCmd.Execute(); err != nil {