diff --git a/.github/workflows/helm-chart-ci.yml b/.github/workflows/helm-chart-ci.yml new file mode 100644 index 0000000000..34e606698e --- /dev/null +++ b/.github/workflows/helm-chart-ci.yml @@ -0,0 +1,245 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +name: "Helm Chart CI" + +on: + push: + branches: [ master, 'release-*' ] + paths: [ 'helm/**', '.github/workflows/helm-chart-ci.yml' ] + pull_request: + paths: [ 'helm/**', '.github/workflows/helm-chart-ci.yml' ] + +permissions: + contents: read + +jobs: + lint-and-render: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + # azure/setup-helm is not on the ASF-approved actions allowlist, which + # fails the workflow at startup; install the pinned release directly. + - name: install helm + run: | + curl -fsSLo /tmp/helm.tgz https://get.helm.sh/helm-v3.16.2-linux-amd64.tar.gz + echo "9318379b847e333460d33d291d4c088156299a26cd93d570a7f5d0c36e50b5bb /tmp/helm.tgz" | sha256sum -c - + tar -xzf /tmp/helm.tgz -C /tmp + sudo install -m 0755 /tmp/linux-amd64/helm /usr/local/bin/helm + helm version + + - name: helm lint + run: | + helm lint helm/hugegraph + helm lint helm/hugegraph -f helm/hugegraph/values-single.yaml + helm lint helm/hugegraph -f helm/hugegraph/values-cluster.yaml + + - name: helm unittest + run: | + # v1.0.0 is the newest helm-unittest whose plugin.yaml Helm 3.16.2 can + # parse (v1.1.x adds a platformHooks field that 3.16.2 rejects). + # Pinned by the tag's commit so a moved tag cannot change what runs. + helm plugin install https://github.com/helm-unittest/helm-unittest.git \ + --version ddd5feaa9465e7a3591792524ce7f441d4c5157e + helm unittest helm/hugegraph + + - name: helm template + run: | + for preset in "" "-f helm/hugegraph/values-single.yaml" "-f helm/hugegraph/values-cluster.yaml"; do + # shellcheck disable=SC2086 # $preset intentionally splits into flags + helm template ci helm/hugegraph $preset > /dev/null + done + # Positive coverage for every hubble wrapper branch: pd mode with + # auth, direct mode with a custom port, and a TLS-less ingress with + # the explicit opt-in. + helm template ci helm/hugegraph \ + --set hubble.enabled=true \ + --set server.auth.enabled=true \ + --set server.auth.admin.existingSecret=ci-auth > /dev/null + helm template ci helm/hugegraph \ + --set hubble.enabled=true \ + --set hubble.allowWithoutServerAuth=true \ + --set hubble.mode=direct \ + --set hubble.port=9090 \ + --set hubble.persistence.enabled=true \ + --set hubble.ingress.enabled=true \ + --set hubble.ingress.allowPlainHttp=true > /dev/null + # PD sharding knobs rendered as -D system properties, including + # the values-file shape where the count arrives as a numeric string + helm template ci helm/hugegraph \ + --set pd.partition.defaultShardCount=3 \ + --set pd.partition.storeMaxShardCount=12 > /dev/null + helm template ci helm/hugegraph \ + --set-string pd.partition.defaultShardCount=3 > /dev/null + # The derived shard count and the raft whitelist and RPC-timeout flags must land in + # the PD JAVA_OPTS verbatim: shard count 3 on the default topology, + # 1 on the single-node preset, whitelist disabled in both. + helm template ci helm/hugegraph \ + | grep -qF 'value: "-Dpartition.default-shard-count=3 -Draft.ip-whitelist.enabled=false -Draft.rpc-timeout=3000"' + helm template ci helm/hugegraph -f helm/hugegraph/values-single.yaml \ + | grep -qF 'value: "-Dpartition.default-shard-count=1 -Draft.ip-whitelist.enabled=false -Draft.rpc-timeout=3000"' + # The stock distributed install must put every Server replica on + # the shared PD graph catalog, even without Hubble. Auth is on by + # default, so the chart-managed admin Secret must render too. + DEFAULT=$(helm template ci helm/hugegraph) + grep -qF "printf 'usePD=true\\n'" <<<"$DEFAULT" + grep -qF "printf 'pd.peers=%s\\n' \"\${HG_SERVER_PD_PEERS}\"" \ + <<<"$DEFAULT" + grep -qF 'value: "ci-hugegraph-pd-0.ci-hugegraph-pd.default.svc:8686,ci-hugegraph-pd-1.ci-hugegraph-pd.default.svc:8686,ci-hugegraph-pd-2.ci-hugegraph-pd.default.svc:8686"' \ + <<<"$DEFAULT" + grep -qE '^kind: Secret$' <<<"$DEFAULT" + grep -qF 'name: ci-admin' <<<"$DEFAULT" + grep -qF 'name: ci-auth-token' <<<"$DEFAULT" + # A YAML-coercible Secret key must render quoted, or the API + # server stores the key as a boolean and the pods cannot find it. + helm template ci helm/hugegraph --set-string server.auth.admin.key=on \ + | grep -qF '"on":' + + - name: reject invalid values + run: | + # Each case must fail to render; the schema and helpers are the + # contract. `! cmd` alone is NOT enforced under `set -e` (errexit + # ignores inverted commands), so every case checks explicitly. + must_fail() { + if helm template ci helm/hugegraph "$@" >/dev/null 2>&1; then + echo "expected render failure for: $*" >&2 + exit 1 + fi + } + # Kubernetes permits successThreshold > 1 only for readiness. + for component in pd store server hubble; do + for probe in startup liveness; do + must_fail --set "${component}.probes.${probe}.successThreshold=2" + done + helm template ci helm/hugegraph \ + --set "${component}.probes.readiness.successThreshold=2" > /dev/null + done + must_fail --set pd.replicas=100 + must_fail --set pd.pdb.minAvailable=3 + must_fail --set server.hpa.enabled=true + must_fail \ + --set server.hpa.enabled=true \ + --set server.hpa.minReplicas=2 \ + --set server.resources.requests.cpu=100m \ + --set server.pdb.enabled=true \ + --set server.pdb.minAvailable=2 + must_fail --set server.auth.enabled=true --set server.auth.admin.autoGenerate=false --set server.auth.token.autoGenerate=false + # pd.auth with every source disabled fails the schema anyOf; the + # unit framework at v1.0.0 cannot match schema aborts, so the case + # lives here. + must_fail --set pd.auth.autoGenerate=false + # Commons Configuration trims a properties value, so a credential + # with surrounding whitespace would be stored in the Secret and + # applied to the account without it. The schema rejects both ends; + # these are schema aborts, so they live here rather than in the + # unit suite. --set-string keeps the value a string. + must_fail --set-string "server.auth.admin.password=review-secret " + must_fail --set-string "$(printf 'server.auth.admin.password=review-secret\t')" + must_fail --set-string "pd.auth.value=review-secret " + # An inner space stays legal: only the trimmed ends are the problem. + helm template ci helm/hugegraph \ + --set-string "server.auth.admin.password=review secret" >/dev/null + # Auth defaults to on, so Hubble alone is valid; refuse Hubble only + # when authentication is explicitly disabled. + must_fail --set hubble.enabled=true --set server.auth.enabled=false + A=(--set hubble.enabled=true --set hubble.allowWithoutServerAuth=true) + must_fail "${A[@]}" --set hubble.port=0 + must_fail "${A[@]}" \ + --set hubble.persistence.enabled=true \ + --set hubble.persistence.size="" + must_fail "${A[@]}" --set hubble.service.nodePort=30080 + must_fail "${A[@]}" --set hubble.mode=bogus + must_fail "${A[@]}" --set hubble.image.tag="" + must_fail "${A[@]}" --set hubble.ingress.enabled=true + # A TLS-less Server Ingress needs the explicit plain-HTTP opt-in + must_fail --set server.ingress.enabled=true + helm template ci helm/hugegraph \ + --set server.ingress.enabled=true \ + --set server.ingress.allowPlainHttp=true > /dev/null + # PD PDB must keep the Raft majority: floor(replicas/2)+1 + must_fail --set pd.replicas=5 --set pd.pdb.minAvailable=2 + must_fail --set pd.replicas=4 --set pd.pdb.minAvailable=2 + helm template ci helm/hugegraph \ + --set pd.replicas=5 --set pd.pdb.minAvailable=3 > /dev/null + helm template ci helm/hugegraph \ + --set pd.replicas=5 --set pd.pdb.minAvailable=4 > /dev/null + # PD sharding knobs: empty or a positive integer; an explicit + # shard count must be odd and stay within the store count + must_fail --set pd.partition.defaultShardCount=0 + must_fail --set pd.partition.defaultShardCount=-1 + must_fail --set-string pd.partition.defaultShardCount=abc + must_fail --set pd.partition.defaultShardCount=2 + must_fail --set pd.partition.defaultShardCount=5 + must_fail --set pd.partition.storeMaxShardCount=0 + # extraEnv must not override chart-managed variables + must_fail --set 'server.extraEnv[0].name=HG_SERVER_INIT_STORE_ENABLED' \ + --set 'server.extraEnv[0].value=true' + must_fail --set 'pd.extraEnv[0].name=HG_PD_RAFT_PEERS_LIST' \ + --set 'pd.extraEnv[0].value=x' + must_fail --set 'store.extraEnv[0].name=HG_STORE_PD_ADDRESS' \ + --set 'store.extraEnv[0].value=x' + # JAVA_OPTIONS is reserved: a preset value makes the start scripts + # skip auto heap sizing and drop the chart's JAVA_OPTS entirely + must_fail --set 'pd.extraEnv[0].name=JAVA_OPTIONS' \ + --set 'pd.extraEnv[0].value=-Xmx1g' + + - name: kubeconform + shell: bash + run: | + set -o pipefail + curl -sSLo /tmp/kc.tar.gz https://github.com/yannh/kubeconform/releases/download/v0.6.7/kubeconform-linux-amd64.tar.gz + echo "95f14e87aa28c09d5941f11bd024c1d02fdc0303ccaa23f61cef67bc92619d73 /tmp/kc.tar.gz" | sha256sum -c - + tar -xzf /tmp/kc.tar.gz -C /tmp + for preset in "" "-f helm/hugegraph/values-single.yaml" "-f helm/hugegraph/values-cluster.yaml"; do + # shellcheck disable=SC2086 # $preset intentionally splits into flags + helm template ci helm/hugegraph $preset | /tmp/kubeconform -strict -summary -kubernetes-version 1.23.0 + done + helm template ci helm/hugegraph \ + --set hubble.enabled=true \ + --set hubble.allowWithoutServerAuth=true \ + --set hubble.mode=direct \ + --set hubble.port=9090 \ + --set hubble.persistence.enabled=true \ + --set hubble.ingress.enabled=true \ + --set hubble.ingress.allowPlainHttp=true \ + | /tmp/kubeconform -strict -summary -kubernetes-version 1.23.0 + helm template ci helm/hugegraph \ + --set hubble.enabled=true \ + --set server.auth.enabled=true \ + --set server.auth.admin.existingSecret=ci-auth \ + | /tmp/kubeconform -strict -summary -kubernetes-version 1.23.0 + helm template ci helm/hugegraph \ + --set pd.partition.defaultShardCount=3 \ + --set pd.partition.storeMaxShardCount=12 \ + | /tmp/kubeconform -strict -summary -kubernetes-version 1.23.0 + + - name: legacy --reuse-values compatibility + run: | + # A release created before a field existed must still render. This has + # regressed five times, so it is guarded here rather than by review. + # `-f` is NOT equivalent: it merges over the new defaults, whereas + # --reuse-values discards them, so the fixture must become values.yaml. + cp -R helm/hugegraph /tmp/legacy + cp helm/hugegraph/testdata/values-pre-hardening.yaml /tmp/legacy/values.yaml + helm template legacy /tmp/legacy > /dev/null + helm template legacy /tmp/legacy --is-upgrade > /dev/null + + - name: helm package + run: helm package helm/hugegraph -d /tmp/chart diff --git a/README.md b/README.md index 0e48ced340..e3726af4c8 100644 --- a/README.md +++ b/README.md @@ -207,9 +207,16 @@ For advanced Docker configurations, see: > > **Version Tags**: Use release tags (e.g., `1.7.0`) for stable deployments. The `latest` tag should only be used for testing or development. +### Option 2: Kubernetes with Helm + +The HStore Helm chart deploys HugeGraph PD, Store, and Server as a distributed +Kubernetes cluster. See the [chart documentation](helm/hugegraph/README.md) for +single-node and highly available presets, configuration, and +upgrade guidance. +
-Option 2: Download Binary Package +Option 3: Download Binary Package Download pre-built packages from the [Download Page](https://hugegraph.apache.org/docs/download/download/): @@ -242,7 +249,7 @@ For detailed instructions, see the [Binary Installation Guide](https://hugegraph
-Option 3: Build from Source +Option 4: Build from Source Build from source for development or customization: diff --git a/helm/hugegraph/.helmignore b/helm/hugegraph/.helmignore new file mode 100644 index 0000000000..8016b1b85d --- /dev/null +++ b/helm/hugegraph/.helmignore @@ -0,0 +1,33 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Patterns to ignore when building packages. +.DS_Store +.git/ +.gitignore +*.swp +*.bak +*.tmp +*.orig +*~ +.idea/ +.vscode/ + +# Contributor tooling, if present in a working tree. Never ship it. +scripts/ +testdata/ + +# Never package a chart archive inside a chart. +*.tgz diff --git a/helm/hugegraph/Chart.yaml b/helm/hugegraph/Chart.yaml new file mode 100644 index 0000000000..0b7f7ccde1 --- /dev/null +++ b/helm/hugegraph/Chart.yaml @@ -0,0 +1,36 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +apiVersion: v2 +name: hugegraph +description: Helm chart for Apache HugeGraph HStore cluster (PD + Store + Server) +type: application +version: 0.1.0 +appVersion: "latest" +kubeVersion: ">=1.23.0-0" +keywords: + - hugegraph + - graph + - hstore + - raft +home: https://hugegraph.apache.org/ +sources: + - https://github.com/apache/hugegraph +maintainers: + - name: HugeGraph Community + url: https://hugegraph.apache.org/ + email: dev@hugegraph.apache.org diff --git a/helm/hugegraph/README.md b/helm/hugegraph/README.md new file mode 100644 index 0000000000..40d329a0e9 --- /dev/null +++ b/helm/hugegraph/README.md @@ -0,0 +1,1307 @@ +# HugeGraph HStore Helm Chart + +[Apache HugeGraph](https://hugegraph.apache.org/) - an open source, distributed graph database. + +## Documentation + +This chart deploys a distributed HugeGraph cluster - PD, Store, and Server - on +Kubernetes. For HugeGraph itself see . + +Note that this chart requires Helm 3. `--reset-then-reuse-values`, referenced +under Upgrading, requires Helm 3.14 or later. + +## Prerequisites Details + +* Kubernetes 1.23+ (the chart renders `autoscaling/v2` and `policy/v1`) +* PV support on the underlying infrastructure: a default StorageClass, or an + explicit `storageClassName` for PD and Store +* Sufficient memory for nine JVM processes in the default topology. + Insufficient memory causes OOM kills that surface as silent Raft failures + rather than as clear errors. + +## Chart Details + +| Component | Workload | Purpose | +|---|---|---| +| PD | StatefulSet + PVC | Placement driver; Raft group tracking Stores and partitions | +| Store | StatefulSet + PVC | Graph data storage (HStore) | +| Server | Deployment | Gremlin and REST query layer | +| Hubble | Deployment + optional PVC | Web UI, off by default; enable with `hubble.enabled` | + +A distributed HugeGraph cluster has a startup contract that this chart encodes +so operators do not have to: + +- **Server does not run `init-store`.** The chart injects + `HG_SERVER_INIT_STORE_ENABLED=false`, and the image's `init-store` exits when + `init_store.enabled=false`, after which Server registers with PD normally. + This matters because nothing serializes Server replicas: without the gate, + every replica would initialize the same backend concurrently. The chart + creates no init Job and does not set `HG_SERVER_SKIP_INIT`. Standalone + behavior is unchanged, because the option defaults to `true` when unset. +- **Every Server uses PD for graph metadata.** The startup wrapper always writes + `usePD=true` and the chart-derived `pd.peers` into + `rest-server.properties`, so all Server replicas share the graph catalog + through PD. It also registers each Server Pod IP with PD for in-cluster + discovery (`server.advertiseUrl` replaces that with one shared URL). This is + required for distributed HStore and does not make a local RocksDB backend + shared across replicas. +- **Store waits for PD** in an init container before starting: a majority of + the PD peers must answer `store.waitPath`. The default `/v1/ready` stays 503 + until a raft leader exists, so that majority is a quorum and not merely a set + of live listeners. +- **One PD REST secret, three readers.** PD checks the Basic-auth password of + every management call against `auth.secret-key` and refuses to start + without one. The chart keeps that value in a release-pd-auth Secret + (or `pd.auth.existingSecret`) and hands it to PD as `HG_PD_AUTH_SECRET_KEY`, + to the Server storage wait as `PD_AUTH_PASSWORD`, and to Hubble as + `operations.pd.password`; a `checksum/pd-auth` annotation rolls all three + when the Secret changes. +- **The Server startup probe allows at least 450 seconds, and the image gets + the same budget.** The container may spend 300 seconds waiting for storage + and the rest in the start command, so the chart sets + `HG_SERVER_STARTUP_TIMEOUT_S` to the startup probe's own budget + (`failureThreshold` * `periodSeconds`, 450 seconds by default) rather than + leaving the image's 120-second default, which would self-kill a Server that + was still starting. A lower configured `failureThreshold` is raised to the + 450-second floor rather than being rejected, and raising the probe budget + raises the timeout with it. The variable is chart-managed, so + `server.extraEnv` may not set it; change the probe instead. +- **The wrapper writes `auth.admin_pa` from the auth Secret.** With + `init_store.enabled=false` the admin credential is created on the PD startup + path from `auth.admin_pa`, not from the Docker `PASSWORD` stdin path. When + authentication is enabled, the chart's wrapper therefore writes + `auth.admin_pa` from the mounted Secret alongside `usePD=true` and + `pd.peers`, then hands off to the image entrypoint. Two caveats: + `auth.admin_pa` applies only when the admin is first created, so changing the + Secret does not rotate an existing cluster's password, and the value lands in + `rest-server.properties` inside the container (file mode 600). Because the + Java properties parser reinterprets them, the Secret value must not contain + newlines, carriage returns, or backslashes; the wrapper refuses to start if + it does. +- **Resource names reserve their suffix and StatefulSet ordinal before + truncation,** so a long release name cannot produce colliding or over-long + Pod and Service names, and PD/Store identities stay fixed when replicas + change. + +## Installing the Chart + +Before installing, confirm `kubectl` points at the intended cluster and that +it can provision volumes. The default topology needs 3 PD and 3 Store PVCs, +and PVCs stuck `Pending` for want of a StorageClass are the most common +first-run failure: + +```bash +kubectl config current-context +kubectl get nodes +kubectl get storageclass +``` + +```bash +helm install hugegraph ./helm/hugegraph --namespace hugegraph \ + --create-namespace --wait --timeout 15m +``` + +This deploys 3 PD + 3 Store + 3 Server, preserves the image's automatic JVM +sizing, and sets no resource requests or limits. Set resources before +production use. + +The command examples in this document assume the release is named +`hugegraph`. With a different release name, substitute the release-prefixed +resource names (`kubectl get svc,secret -n ` lists them). +Workloads and Services are named `-hugegraph-*`, while the kept +Secrets are `-admin`, `-auth-token`, and +`-pd-auth`. + +**Authentication is enabled by default.** The chart creates a kept Secret +named `-admin` (for example `hugegraph-admin`) with a random +password unless `server.auth.admin.existingSecret` points at a pre-created Secret. +To manage the credential yourself, create the Secret before installing and set +`server.auth.admin.existingSecret`. It always takes priority, and the chart does +not overwrite or manage that Secret: + +```bash +kubectl -n hugegraph create secret generic my-hugegraph-admin \ + --from-literal=password='CHANGE_ME' +``` + +Then add `--set-string server.auth.admin.existingSecret=my-hugegraph-admin` to +the install command. The Secret must contain a `password` key with no newlines, +carriage returns, backslashes, or surrounding whitespace. The last one bites +quietly: the Server wrapper writes the value into a properties file, and +Commons Configuration trims it when the Server reads it back, so a padded +Secret would create the account under the trimmed password and then fail to +authenticate with the value the Secret holds. The schema rejects padding on +inline values; for a bring-your-own Secret the chart cannot see the value, so +check it yourself. The JWT signing key uses +the same shape under `server.auth.token` (`value`, `existingSecret`, +`autoGenerate`), and its value must be at least 32 bytes. +Read the password and exercise the API: + +```bash +PASSWORD="$(kubectl get secret -n hugegraph hugegraph-admin \ + -o jsonpath='{.data.password}' | base64 --decode)" +kubectl port-forward -n hugegraph svc/hugegraph-server 8080:8080 +curl --user "admin:${PASSWORD}" http://127.0.0.1:8080/versions +``` + +**Hubble is not installed by default.** Enable the optional UI after install: + +```bash +helm upgrade hugegraph ./helm/hugegraph --namespace hugegraph \ + --reuse-values --set hubble.enabled=true +``` + +`--reuse-values` keeps the release's existing overrides (presets, images, +resources, Secrets); without it the upgrade rebuilds the release from chart +defaults. Auth is already on, so that single flag is enough. Login uses the same admin +credential from the chart-managed (or BYO) Secret. + +The default anti-affinity for `pd`, `store`, and `server` is `preferred` +(Server always was; Hubble has no anti-affinity knob because it is +single-replica by design), so the chart schedules even on clusters with +fewer nodes than replicas. Production should pin `pd.antiAffinity` and +`store.antiAffinity` to `required`, as `values-cluster.yaml` does, so one +node failure cannot take out the PD quorum or co-locate shard replicas; see +Scheduling below. + +A fresh install seeds PD with a partition shard count of 3 when +`store.replicas` is at least 3, and 1 otherwise, instead of the image +default of 1. The seed applies at first bootstrap only; see Partition +Sharding below. + +The component image tags and `appVersion` track `latest` until the next +HugeGraph release tag is published. For production, pin the image tags (or +digests) and switch the component pull policies to `IfNotPresent`. + +Verify the release: + +```bash +helm test hugegraph --namespace hugegraph +``` + +### Values Presets + +| File | Purpose | +|---|---| +| `values.yaml` | Default 3+3+3 topology with preferred anti-affinity, authentication on, and Hubble off | +| `values-single.yaml` | Single-node 1+1+1 example with authentication on | +| `values-cluster.yaml` | Production 3+3+3 starting point with JVM/resources, PD/Store PDBs, required anti-affinity for PD and Store, and NetworkPolicy on; authentication on, Hubble still opt-in | + +`values-cluster.yaml` is a production starting point, not a capacity +guarantee. Recalculate capacity for the graph size, traffic, failure budget, +node topology, and storage class before production use. + +### Local Kubernetes (Kind / minikube) + +Only needed when there is no cluster yet, or to test locally built images. +Build the three images, load them into the cluster, and override their tags +and pull policies. The override is required, not optional: this chart +defaults `pullPolicy: Always`, so without `Never` the kubelet tries to pull +your local tag from Docker Hub and fails even though the image is loaded. + +```bash +kind create cluster --name hg + +docker build -f hugegraph-pd/Dockerfile -t hugegraph/pd:local . +docker build -f hugegraph-store/Dockerfile -t hugegraph/store:local . +docker build -f hugegraph-server/Dockerfile-hstore -t hugegraph/server:local . + +kind load docker-image hugegraph/pd:local hugegraph/store:local \ + hugegraph/server:local --name hg +# minikube: minikube image load + +helm upgrade --install hugegraph ./helm/hugegraph \ + --namespace hugegraph --create-namespace \ + -f helm/hugegraph/values-single.yaml \ + --set pd.image.tag=local --set pd.image.pullPolicy=Never \ + --set store.image.tag=local --set store.image.pullPolicy=Never \ + --set server.image.tag=local --set server.image.pullPolicy=Never +``` + +Server uses `Dockerfile-hstore` so the image's default backend is HStore. +Skipping the load step fails the Pods with `ErrImageNeverPull`; do not retag +Docker Hub images as `local`. + +## Upgrading the Chart + +```bash +helm upgrade hugegraph ./helm/hugegraph --namespace hugegraph --reuse-values +``` + +Any upgrade that changes a Pod template rolls that workload once. + +A release created before the exposure gates existed can hit them on its +next upgrade, `--reuse-values` included: a non-ClusterIP `pd.service.type` +now needs `pd.service.allowInsecureExposure=true`, and a TLS-less Server +Ingress needs `server.ingress.allowPlainHttp=true`. The render error names +the value to set. + +PD and Store storage sizes live in the StatefulSet `volumeClaimTemplates`, +which Kubernetes forbids changing, so an upgrade with a new size is +rejected in full. To grow storage on a StorageClass that supports volume +expansion: patch each PVC's `spec.resources.requests.storage`, wait for +the resize to finish, recreate the StatefulSet object without touching +Pods (`kubectl delete statefulset --cascade=orphan`), then upgrade +with the matching value. + +Two cases are worth knowing about in advance: + +- **PD** restarts one pod at a time whenever its Pod template changes, which + includes adopting the `-Draft.ip-whitelist.enabled=false` setting described + under Limitations. For a maintenance-window upgrade, set + `pd.updateStrategy.type=OnDelete` and restart the pods yourself. +- **Store** rolling updates advance on `/v1/health`, which reports the + listener, not shard recovery: the controller can replace the next Store + while the previous one is still rejoining its shard groups. For a + production image roll, set `store.updateStrategy.type=OnDelete` and delete + Store Pods one at a time, checking shard membership between deletions. + + `Up` in PD is not that check. PD sets `StoreState.Up` and persists it in + `StoreNodeService.register()`, and only then does the notification reach + the Store, whose `HgStoreEngine.stateChanged` starts + `restoreLocalPartitionEngine()`; a failure there is logged and leaves the + state `Up`. A Store is therefore `Up` before it has restored anything, and + stays `Up` if restoring fails. + + The strongest check the current images support is shard membership and + leadership per group, read from the PD leader: + + ```bash + # PD leader, then its shard groups (see Disaster Recovery for the port-forward) + curl -s -u "hg:${PD_SECRET}" http://127.0.0.1:8620/v1/shardGroups | jq ' + .shardGroups[] | {id, + shards: [.shards[] | {storeId, role}], + leaders: [.shards[] | select(.role=="Leader")] | length}' + ``` + + Delete the next Store only when every group reports the full shard count + from `pd.partition.shardCount`, exactly one `Leader`, and the replaced + Store's id back in the groups it holds. `/v1/shardLeaders` gives the same + leadership view grouped by Store raft address. + + Know what this does not prove. The shard list is PD's membership record, + not a statement that the Store finished loading those partitions locally + and caught up on the raft log. No endpoint in these images reports + restoration-complete, so a group can list a Store whose local engine is + still behind. Leave a margin after the membership check rather than + deleting the next Pod on the same second, keep `store.pdb.minAvailable` at + `replicas - 1` so an accidental second eviction is refused, and treat a + group that is short a shard or has no leader as a stop. Closing that gap + needs an image-side readiness signal for partition restoration, which is + the Store-side counterpart of the Server work in + [apache/hugegraph#3212](https://github.com/apache/hugegraph/issues/3212). +- **Server** rolls once on the first `helm upgrade` after a fresh install, + when the `checksum/auth` annotation first observes the install-created + Secrets. Template-only pipelines (`helm template`, GitOps renderers) never + see live Secrets, so there the annotation is a constant and Secret rotation + does not roll pods. +- **PD and Hubble** roll once on the first `helm upgrade` after a fresh + install as well, when the `checksum/pd-auth` annotation first observes the + install-created PD REST Secret (same mechanism as the Server annotation + above; measured on a kind cluster: PD, Server and Hubble replaced, Store + untouched). A PD roll is a raft rolling restart, one pod at a time; for a + maintenance-window upgrade set `pd.updateStrategy.type=OnDelete` and + restart the PD pods yourself. Rotating the PD REST Secret later rolls the + same three workloads together, which keeps their copies of the secret in + step. + +Every optional field stays optional, so a release created by an earlier +revision continues to render under `--reuse-values`. Note that `--reuse-values` +keeps the old release's values as the complete base, so a release created +before a field existed does **not** pick up its new default, including the +hardened `securityContext`, ServiceAccounts, and `terminationGracePeriodSeconds`. +Use `-f` with your own values, or `--reset-then-reuse-values`, to adopt them. +That rule covers values-sourced defaults only; the asymmetry is that +template-derived settings **are** applied even under `--reuse-values`, +because they are computed at render time from whatever values are in effect. +Pod-level token mounting (disabled unconditionally) and the derived +`-Dpartition.default-shard-count` in the PD `JAVA_OPTS`, plus the Server's +enforced PD metadata mode, are the current cases. The PD metadata change rolls +the Server Deployment. On an already-initialized cluster the seeded shard +count is inert either way; see Partition Sharding. + +Upgrading an existing release to this chart version rolls the PD StatefulSet +once: PD Pods now always carry a `JAVA_OPTS` environment variable with the +chart-derived partition properties, where previous versions set the variable +only when `pd.javaOpts` was non-empty. + +The `pd.antiAffinity` and `store.antiAffinity` defaults changed from +`required` to `preferred` in this version. `--reuse-values` keeps the old +effective value, but installs that relied on the old `required` default +while supplying their own values files must now pin `antiAffinity: required` +explicitly. + +PD and Store resource names reserve room for their StatefulSet ordinal before truncation, so +identities stay fixed across replica changes and scaling never renames a +PersistentVolumeClaim. + +## Uninstalling the Chart + +```bash +helm uninstall hugegraph --namespace hugegraph +``` + +Helm does not remove PersistentVolumeClaims created by StatefulSets. Delete +them explicitly, and only when the data is no longer needed. + +The chart-managed authentication Secret is kept on uninstall and reused by a +later install of the same release name. Do not delete it unless you intend to +manage the password separately. `helm template` and client-side dry runs cannot +read an existing Secret, so the password they generate is only a render-time +placeholder; a live install or upgrade reuses the existing Secret when Helm has +permission to read it. + +## Configuration + +The following table lists the configurable parameters of the chart and their +default values. + +### Global + +| Parameter | Description | Default | +|---|---|---| +| `nameOverride` | Override the chart name in generated resource names | `""` | +| `fullnameOverride` | Override the full generated resource name | `""` | +| `imagePullSecrets` | Secrets used to pull the PD, Store, and Server images | `[]` | + +### PD + +| Parameter | Description | Default | +|---|---|---| +| `pd.replicas` | PD StatefulSet replicas. Maximum `99` | `3` | +| `pd.image.repository` | PD image repository | `hugegraph/pd` | +| `pd.image.tag` | PD image tag; pin it (or a digest) for production | `latest` | +| `pd.image.digest` | Optional immutable digest such as `sha256:...`; when set it takes priority over the tag | `""` | +| `pd.image.pullPolicy` | PD image pull policy | `Always` | +| `pd.javaOpts` | Extra JVM flags, rendered after the chart-derived `-D` properties below so an explicit duplicate here wins. The image's automatic heap sizing is preserved unless heap flags are set | `""` | +| `pd.raftIpWhitelistEnabled` | Enable PD's raft peer IP whitelist. Off in-cluster because PD resolves peers once at boot; requires a PD image carrying the upstream switch | `false` | +| `pd.raftRpcTimeoutMs` | Raft RPC timeout (`-Draft.rpc-timeout`). Bounds the wait on a vanished leader, so it bounds leader elections: the image default of 10000 was measured leaderless for about a minute, 3000 elects in seconds. Empty preserves the image default | `3000` | +| `pd.partition.defaultShardCount` | Shard replicas per partition, seeded into PD's persisted config at first bootstrap only; inert on an initialized cluster (see Partition Sharding). Empty derives 3 when `store.replicas` is at least 3, else 1. An explicit value must be odd and must not exceed `store.replicas` | `""` | +| `pd.partition.storeMaxShardCount` | Maximum shards per Store, seeded at first bootstrap only. Also fixes the initial partition count, `store.replicas x storeMaxShardCount / shardCount` (see Partition Sharding). Empty preserves the image default of `12` | `""` | +| `pd.ports.grpc` | PD gRPC port | `8686` | +| `pd.ports.rest` | PD REST port, also used by probes | `8620` | +| `pd.ports.raft` | PD Raft port | `8610` | +| `pd.dataPath` | PD data directory inside the container | `/hugegraph-pd/pd_data` | +| `pd.storage.size` | PD PersistentVolumeClaim size. Applies at install; see Upgrading for the resize procedure | `10Gi` | +| `pd.storage.storageClassName` | Empty uses the cluster default StorageClass | `""` | +| `pd.resources` | PD container resources. Set these for production | `{}` | +| `pd.podSecurityContext` | Pod-level securityContext, rendered only when set | `{}` | +| `pd.securityContext` | Container-level securityContext. Hardened by default; `runAsNonRoot` is not set because the published images run as root | `allowPrivilegeEscalation: false`, `capabilities.drop: [ALL]`, `seccompProfile: RuntimeDefault` | +| `pd.antiAffinity` | One of `required`, `preferred`, `disabled`. `preferred` schedules on clusters with fewer nodes than replicas; production should use `required` so one node failure cannot take out the PD quorum | `preferred` | +| `pd.nodeSelector` | Node selector for pd Pods | `{}` | +| `pd.tolerations` | Tolerations for pd Pods | `[]` | +| `pd.affinity` | Raw affinity; overrides `pd.antiAffinity` when set | `{}` | +| `pd.topologySpreadConstraints` | Topology spread constraints for pd Pods | `[]` | +| `pd.priorityClassName` | PriorityClass for pd Pods | `""` | +| `pd.podAnnotations` | Extra annotations on pd Pods | `{}` | +| `pd.podLabels` | Extra labels on pd Pods. The `app.kubernetes.io/name`, `instance` and `component` keys are chart-managed and rejected | `{}` | +| `pd.extraEnv` | Extra environment variables for the PD container | `[]` | +| `pd.terminationGracePeriodSeconds` | Shutdown grace period | `300` | +| `pd.serviceAccount.create` | Create a ServiceAccount for pd | `true` | +| `pd.serviceAccount.name` | Use an existing ServiceAccount instead | `""` | +| `pd.serviceAccount.annotations` | Annotations on the created ServiceAccount | `{}` | +| `pd.serviceAccount.automountServiceAccountToken` | Mount an API token. The chart makes no API calls | `false` | +| `pd.pdb.enabled` | Create a PodDisruptionBudget for PD | `true` | +| `pd.pdb.minAvailable` | Must be strictly less than `pd.replicas`. No PDB is rendered when `pd.replicas` is 1 | `2` | +| `pd.readinessPath` | Path the PD readinessProbe hits. `/v1/ready` is quorum-aware and returns 503 without a raft leader | `/v1/ready` | +| `pd.livenessPath` | Path the PD startup and liveness probes hit. Empty derives it from `pd.replicas`: `/v1/health` above one replica, `/v1/ready` at one | `""` | +| `pd.auth.value` | Plaintext PD REST secret (`auth.secret-key`). Prefer `existingSecret` in shared clusters. Printable ASCII, no backslashes, no leading or trailing space (a properties read trims it) | `""` | +| `pd.auth.existingSecret` | Pre-created Secret holding the PD REST secret under `pd.auth.key`. Wins over `value` and `autoGenerate`; the chart does not manage it. Its value must meet the same constraint as `pd.auth.value`: printable ASCII, no backslashes, no leading or trailing space | `""` | +| `pd.auth.key` | Key inside the PD REST Secret | `secret-key` | +| `pd.auth.autoGenerate` | Create and keep a random release-pd-auth Secret when `value` and `existingSecret` are empty | `true` | +| `pd.probes.*.periodSeconds` | Probe interval | see `values.yaml` | +| `pd.probes.*.failureThreshold` | Probe failure threshold | see `values.yaml` | +| `pd.probes.*.timeoutSeconds` | Probe timeout. Defaults to `5` on readiness/liveness; Kubernetes would otherwise apply `1` | `5` | +| `pd.probes.*.initialDelaySeconds` | Optional probe start delay | unset | +| `pd.probes.*.successThreshold` | Optional probe success threshold | unset | + +### Store + +| Parameter | Description | Default | +|---|---|---| +| `store.replicas` | Store StatefulSet replicas. Maximum `99` | `3` | +| `store.image.repository` | Store image repository | `hugegraph/store` | +| `store.image.tag` | Store image tag; pin it (or a digest) for production | `latest` | +| `store.image.digest` | Optional immutable digest such as `sha256:...`; when set it takes priority over the tag | `""` | +| `store.image.pullPolicy` | Store image pull policy | `Always` | +| `store.javaOpts` | Empty preserves the image's automatic JVM sizing | `""` | +| `store.ports.grpc` | Store gRPC port | `8500` | +| `store.ports.raft` | Store Raft port | `8510` | +| `store.ports.rest` | Store REST port | `8520` | +| `store.dataPath` | Store data directory | `/hugegraph-store/storage` | +| `store.storage.size` | Store PersistentVolumeClaim size. Applies at install; see Upgrading for the resize procedure | `50Gi` | +| `store.storage.storageClassName` | Empty uses the cluster default StorageClass | `""` | +| `store.resources` | Store container resources. Set these for production | `{}` | +| `store.podSecurityContext` | Pod-level securityContext, rendered only when set | `{}` | +| `store.securityContext` | Container-level securityContext; also applied to the PD wait init container. Hardened by default; `runAsNonRoot` is not set because the published images run as root | `allowPrivilegeEscalation: false`, `capabilities.drop: [ALL]`, `seccompProfile: RuntimeDefault` | +| `store.waitPath` | Path the init container polls on each PD peer; a majority must answer 2xx. `/v1/ready` counts quorum members, not merely live listeners | `/v1/ready` | +| `store.waitTimeoutSeconds` | Bound on the PD wait before the init container fails | `900` | +| `store.antiAffinity` | One of `required`, `preferred`, `disabled`. `preferred` schedules on clusters with fewer nodes than replicas; production should use `required` so one node failure cannot co-locate shard replicas | `preferred` | +| `store.nodeSelector` | Node selector for store Pods | `{}` | +| `store.tolerations` | Tolerations for store Pods | `[]` | +| `store.affinity` | Raw affinity; overrides `store.antiAffinity` when set | `{}` | +| `store.topologySpreadConstraints` | Topology spread constraints for store Pods | `[]` | +| `store.priorityClassName` | PriorityClass for store Pods | `""` | +| `store.podAnnotations` | Extra annotations on store Pods | `{}` | +| `store.podLabels` | Extra labels on store Pods | `{}` | +| `store.extraEnv` | Extra environment variables for the Store container | `[]` | +| `store.terminationGracePeriodSeconds` | Shutdown grace period | `300` | +| `store.serviceAccount.create` | Create a ServiceAccount for store | `true` | +| `store.serviceAccount.name` | Use an existing ServiceAccount instead | `""` | +| `store.serviceAccount.annotations` | Annotations on the created ServiceAccount | `{}` | +| `store.serviceAccount.automountServiceAccountToken` | Mount an API token. The chart makes no API calls | `false` | +| `store.pdb.enabled` | Create a PodDisruptionBudget for Store | `true` | +| `store.pdb.minAvailable` | Must be strictly less than `store.replicas` and at least `store.replicas - 1`, so voluntary evictions cannot remove two copies of one shard at once. No PDB is rendered when `store.replicas` is 1 | `2` | +| `store.waitImage` | Image for the PD wait init container | `curlimages/curl:8.5.0` | +| `store.waitResources` | Resources for the init container | `{}` | +| `store.probes.*` | Same probe keys as PD | see `values.yaml` | + +### Server + +| Parameter | Description | Default | +|---|---|---| +| `server.replicas` | Server Deployment replicas. Ignored when `server.hpa.enabled` | `3` | +| `server.image.repository` | Server image repository | `hugegraph/server` | +| `server.image.tag` | Server image tag; pin it (or a digest) for production | `latest` | +| `server.image.digest` | Optional immutable digest such as `sha256:...`; when set it takes priority over the tag | `""` | +| `server.image.pullPolicy` | Server image pull policy | `Always` | +| `server.javaOpts` | Empty preserves the image's automatic JVM sizing | `""` | +| `server.port` | Server REST port, container port, and Service port | `8080` | +| `server.readinessPath` | Path the Server readinessProbe hits. Set `/readiness` once the Server image serves it (apache/hugegraph#3212); it answers 503 while the Server cannot serve graph traffic. Startup and liveness stay on `/versions` | `/versions` | +| `server.backend` | Storage backend | `hstore` | +| `server.resources` | Server resources. `requests.cpu` is required when HPA is enabled | `{}` | +| `server.podSecurityContext` | Pod-level securityContext, rendered only when set | `{}` | +| `server.securityContext` | Container-level securityContext. Hardened by default; `runAsNonRoot` is not set because the published images run as root | `allowPrivilegeEscalation: false`, `capabilities.drop: [ALL]`, `seccompProfile: RuntimeDefault` | +| `server.pdb.enabled` | Create a PodDisruptionBudget for Server. Off by default: Server holds no quorum. `values-cluster.yaml` enables it so a node drain cannot evict every Server at once | `false` | +| `server.pdb.minAvailable` | Must be less than `server.hpa.minReplicas` when HPA is enabled, otherwise less than `server.replicas` | `2` | +| `server.antiAffinity` | One of `required`, `preferred`, `disabled`. Defaults to `preferred` rather than `required` because HPA may scale Server past the node count; set `required` when replicas always stay below it | `preferred` | +| `server.nodeSelector` | Node selector for server Pods | `{}` | +| `server.tolerations` | Tolerations for server Pods | `[]` | +| `server.affinity` | Raw affinity; overrides `server.antiAffinity` when set | `{}` | +| `server.topologySpreadConstraints` | Topology spread constraints for server Pods | `[]` | +| `server.priorityClassName` | PriorityClass for server Pods | `""` | +| `server.podAnnotations` | Extra annotations on server Pods | `{}` | +| `server.podLabels` | Extra labels on server Pods | `{}` | +| `server.extraEnv` | Extra environment variables for the Server container | `[]` | +| `server.terminationGracePeriodSeconds` | Shutdown grace period | `60` | +| `server.serviceAccount.create` | Create a ServiceAccount for server | `true` | +| `server.serviceAccount.name` | Use an existing ServiceAccount instead | `""` | +| `server.serviceAccount.annotations` | Annotations on the created ServiceAccount | `{}` | +| `server.serviceAccount.automountServiceAccountToken` | Mount an API token. The chart makes no API calls | `false` | +| `server.waitImage` | Image for the Helm test hook | `curlimages/curl:8.5.0` | +| `server.testResources` | Resources for the Helm test hook container | `{}` | +| `server.restServer.minFreeMemory` | Empty preserves the image default | `""` | +| `server.restServer.batchMaxWriteThreads` | Empty preserves the image default | `""` | +| `server.initStoreEnabled` | Must remain `false` for distributed HStore | `false` | +| `server.auth.enabled` | Enable admin authentication | `true` | +| `server.auth.admin.password` | Optional inline admin password; prefer a Secret in shared clusters | `""` | +| `server.auth.admin.existingSecret` | Pre-created Secret name (key defaults to `password`); takes priority | `""` | +| `server.auth.admin.key` | Key inside the admin password Secret | `password` | +| `server.auth.admin.autoGenerate` | Create and keep a random release-admin Secret when password and existingSecret are empty | `true` | +| `server.auth.token.value` | Optional inline JWT signing key, minimum 32 bytes; prefer a Secret in shared clusters | `""` | +| `server.auth.token.existingSecret` | Pre-created Secret for the JWT signing key (`auth.token_secret`) | `""` | +| `server.auth.token.key` | Key inside the JWT signing Secret | `token_secret` | +| `server.auth.token.autoGenerate` | Create and keep a random release-auth-token Secret when value and existingSecret are empty | `true` | +| `server.ingress.enabled` | Create an Ingress for the Server Service | `false` | +| `server.ingress.className` | IngressClass name | `""` | +| `server.ingress.annotations` | Ingress annotations (cert-manager, nginx, ALB) | `{}` | +| `server.advertiseUrl` | Absolute Server URL registered with PD (`server.urls_to_pd`). Empty registers each Server Pod IP for in-cluster discovery | `""` | +| `server.service.type` | Server Service type | `ClusterIP` | +| `server.service.annotations` | Server Service annotations | `{}` | +| `server.ingress.hosts` | Ingress hosts and paths | see `values.yaml` | +| `server.ingress.tls` | Ingress TLS configuration. Empty is refused unless `allowPlainHttp` opts in: the Server carries Basic-auth credentials and JWTs | `[]` | +| `server.ingress.allowPlainHttp` | Explicit opt-in to a TLS-less Server Ingress on a trusted network | unset | +| `server.hpa.enabled` | Create a HorizontalPodAutoscaler | `false` | +| `server.hpa.minReplicas` | HPA minimum replicas | `3` | +| `server.hpa.maxReplicas` | HPA maximum replicas | `10` | +| `server.hpa.targetCPUUtilizationPercentage` | HPA CPU utilization target | `70` | +| `server.probes.startup.failureThreshold` | Raised automatically so the budget is at least 450s | `90` | +| `server.probes.startup.periodSeconds` | Startup probe interval | `5` | +| `server.probes.*` | Same optional probe keys as PD | see `values.yaml` | + +When `server.hpa.enabled` is `true` the Deployment omits `spec.replicas`, so a +Helm upgrade does not overwrite the autoscaler's live replica count. Enabling +utilization-based HPA requires a strictly positive +`server.resources.requests.cpu`. + + +### Reaching Hubble (pick one path) + +Most people should stop at **1**. Use **2** only if Hubble must run outside +the cluster. Use **3** only if that outside Hubble must discover Server through +PD. Store Operations metrics from outside the cluster are out of scope here. + +#### 1. In-cluster Hubble (recommended) + +Set `hubble.enabled=true` (off by default so API-only clusters stay lean). The +chart wires PD/Server for you. + +Open the UI with one port-forward: + +```bash +kubectl -n port-forward svc/-hugegraph-hubble 8088:8088 +``` + +Then open `http://127.0.0.1:8088`. For a shared environment, expose Hubble with +`hubble.service.type` NodePort/LoadBalancer or `hubble.ingress` instead of +port-forward. Log in with the chart admin password from the NOTES / admin +Secret. + +This is the average-user path: no Docker, no advertise URL, no PD peer list. + +#### 2. Outside Hubble, direct Server URL (simple external) + +Use this when Hubble runs on a host or VM outside the cluster, and you only +need graph / schema / data / Gremlin (not PD discovery). + +1. Leave in-chart Hubble off (`hubble.enabled=false`). +2. Expose Server (`server.service.type` NodePort/LoadBalancer, or Ingress). +3. Run a standalone Hubble image with `pd.enabled=false` and + `server.direct_url` set to that reachable Server URL (match Server auth). +4. Open the standalone Hubble port in a browser (or SSH tunnel to it). + +Use HTTPS (or a trusted channel such as a local port-forward) for +`server.direct_url`: login sends the Server credentials over that URL. + +Example property fragment for the standalone process: + +```properties +pd.enabled=false +server.direct_url=https://: +``` + +Mount the file at `/hubble/conf/hugegraph-hubble.properties` inside the +official image (workdir is `/hubble`). One Server URL is enough; you do not +need to expose PD. + +#### 3. Outside Hubble, PD discovery (advanced) + +Use this when an outside Hubble must ask PD for the Server address. + +In-cluster names such as `*.svc` are not reachable from outside. The chart +helps with two knobs: advertise a reachable Server URL to PD, and expose the +PD client Service. + +The chart always registers `server.urls_to_pd` with PD, so `server.advertiseUrl` +is honored whenever it is set. + +1. Leave in-chart Hubble off if the bundled UI is not wanted. +2. Expose Server and set `server.advertiseUrl` to the absolute `http(s)://` + URL outside Hubble will use after discovery. The chart registers it via + `server.urls_to_pd` instead of the in-cluster Service URL. +3. Expose PD (`pd.service.type` NodePort/LoadBalancer, which needs + `pd.service.allowInsecureExposure=true`; PD gRPC has no authentication, + so restrict who can reach it first) so Hubble can dial PD + REST and gRPC. +4. Run standalone Hubble with `pd.enabled=true` and `pd.peers` / `pd.server` + pointed at those external PD addresses. Mount config at + `/hubble/conf/hugegraph-hubble.properties`. + +Example property fragment: + +```properties +pd.enabled=true +pd.peers=: +pd.server=: +``` + +Trade-off: when `server.advertiseUrl` is set, every Server replica registers that same logical URL and PD returns it to every discovery client, including an in-cluster Hubble. Leave it empty for the default in-cluster path, where each Server Pod registers its own IP and Hubble can retain the replica list. + +Local quick test (cluster and Hubble on the same machine): port-forward Server +`8080` and PD client `8620`/`8686`, set +`server.advertiseUrl=http://127.0.0.1:8080`, run standalone Hubble with +`--network host` and the PD properties above, then open Hubble on `8088` +(or SSH `-L 8088:127.0.0.1:8088` from a laptop). + +| Parameter | Description | Default | +|---|---|---| +| `server.advertiseUrl` | Absolute Server URL registered with PD for discovery clients. Empty registers each Server Pod IP for in-cluster discovery | `""` | +| `pd.service.type` | PD client Service type (`ClusterIP`, `NodePort`, `LoadBalancer`). A non-ClusterIP type requires `pd.service.allowInsecureExposure` | `ClusterIP` | +| `pd.service.allowInsecureExposure` | Acknowledgement that a non-ClusterIP PD Service exposes the unauthenticated gRPC port; restrict reachability by other means first | `false` | +| `pd.service.annotations` | Annotations on the PD client Service | `{}` | +| `pd.service.restNodePort` | Optional fixed NodePort for PD REST; requires NodePort/LoadBalancer | unset | +| `pd.service.grpcNodePort` | Optional fixed NodePort for PD gRPC; requires NodePort/LoadBalancer | unset | + +### Hubble (optional UI) + +How to open Hubble (in-cluster vs outside) is under +[Reaching Hubble](#reaching-hubble-pick-one-path) above. This section covers +chart wiring and parameters. + +Set `hubble.enabled=true` to deploy [HugeGraph Hubble](https://hugegraph.apache.org/docs/quickstart/toolchain/hugegraph-hubble/), +the web UI for graph management, schema browsing, Gremlin queries, and the +cluster operations view. A default install leaves Hubble off so API-only +clusters stay lean; authentication is already on, so enabling the UI is a +single flag (see Installing above). Login uses the admin credential from +`server.auth.admin.existingSecret` or the chart-managed `-admin` Secret. +`hubble.mode` selects the wiring. In the default +`pd` mode the chart points `pd.peers` at the PD gRPC peers, `pd.server` at +the PD client Service REST port, and the Store metrics allow-list at the +Store REST endpoints, so the cluster view works without manual wiring; the +Server is additionally configured to register each Server Pod IP with PD +(see below). In `direct` mode Hubble only receives `server.direct_url` +pointing at the Server client Service; there is no PD discovery and no +operations view. Everything else in `hugegraph-hubble.properties` keeps the +image default. + +The chart always runs the Server in PD meta mode (`usePD`, `pd.peers`, +`server.urls_to_pd`, `server.deploy_in_k8s`). In `pd` mode, Hubble uses that +registration so PD can hand it a resolvable Server address. The Store +allow-list is computed from `store.replicas` at render time, so scale Store +with `helm upgrade`, not `kubectl scale`, or the list goes stale until the next +upgrade. + +Hubble is one replica by design: it keeps UI connection metadata, including +any graph credentials entered in the UI, in an embedded per-instance H2 +database. Enable `hubble.persistence` to keep that metadata across Pod +replacement; the chart then redirects the H2 location into the mounted +volume through Spring's environment binding. The Deployment uses the +`Recreate` strategy so two Hubble instances never attach the same database. +The PVC is kept on `helm uninstall` (delete it explicitly to discard the +stored metadata), `size` and `storageClassName` apply at install time only, +and a non-root `podSecurityContext` needs a matching `fsGroup` so H2 can +write the volume. + +**Current Hubble images still require `server.auth`.** The UI login +authenticates against the cluster; with authentication explicitly disabled +the login cannot complete (the server rejects `/auth/login` with +"Unconfigured authenticator"). The chart therefore refuses to render +`hubble.enabled=true` when `server.auth.enabled=false` unless +`hubble.allowWithoutServerAuth=true` overrides it for images whose login +does not need cluster authentication. + +**Hubble serves plain HTTP.** Reach it with `kubectl port-forward` or behind +an HTTPS-terminating Ingress; never expose the port directly to an untrusted +network. An Ingress without `tls` is rejected at render time unless +`hubble.ingress.allowPlainHttp=true` explicitly accepts plain HTTP for a +trusted network. + +| Parameter | Description | Default | +|---|---|---| +| `hubble.enabled` | Deploy the Hubble UI. Requires `server.auth` (see below) | `false` | +| `hubble.mode` | `pd` discovers the cluster through PD and enables the operations view; `direct` talks to the Server client Service only | `pd` | +| `hubble.allowWithoutServerAuth` | Renders Hubble without `server.auth`, for future images whose login does not require cluster authentication | `false` | +| `hubble.image.repository` | Hubble image repository | `hugegraph/hubble` | +| `hubble.image.tag` | Hubble image tag; pin it (or a digest) for production | `latest` | +| `hubble.image.digest` | Optional immutable digest such as `sha256:...`; when set it takes priority over the tag | `""` | +| `hubble.image.pullPolicy` | Hubble image pull policy | `Always` | +| `hubble.port` | Hubble HTTP port, container port, and Service port | `8088` | +| `hubble.persistence.enabled` | Persist UI connection metadata in a PVC | `false` | +| `hubble.persistence.size` | PVC size | `1Gi` | +| `hubble.persistence.storageClassName` | Empty uses the cluster default StorageClass | `""` | +| `hubble.resources` | Hubble resources | `{}` | +| `hubble.podSecurityContext` | Pod-level securityContext, rendered only when set | `{}` | +| `hubble.securityContext` | Container-level securityContext, hardened like the other components | see `values.yaml` | +| `hubble.service.type` | Hubble Service type | `ClusterIP` | +| `hubble.service.annotations` | Hubble Service annotations | `{}` | +| `hubble.service.nodePort` | Requires a `NodePort` or `LoadBalancer` Service type | unset | +| `hubble.ingress.*` | Same Ingress keys as `server.ingress.*`, including `allowPlainHttp` | `enabled: false` | +| `hubble.serviceAccount.*` | Same ServiceAccount keys as the other components | `create: true` | +| `hubble.nodeSelector` / `tolerations` / `affinity` / `topologySpreadConstraints` | Scheduling controls | unset | +| `hubble.priorityClassName` | PriorityClass for the hubble Pod | `""` | +| `hubble.podAnnotations` / `hubble.podLabels` | Extra Pod metadata | `{}` | +| `hubble.extraEnv` | Extra environment variables for the hubble container | `[]` | +| `hubble.terminationGracePeriodSeconds` | Shutdown grace period | `30` | +| `hubble.probes.*` | Same probe keys as PD; startup and readiness check `/actuator/health`, liveness is a TCP check | see `values.yaml` | + +Specify each parameter with `--set`, or supply a YAML file with `-f`: + +```bash +helm install hugegraph ./helm/hugegraph --set server.replicas=5 +``` + +`values.schema.json` and template helpers reject invalid input at render time, +before anything reaches the cluster: + +- Unknown keys and wrong types are rejected. +- `server.initStoreEnabled` must remain `false` for a distributed deployment. +- With authentication enabled, either `server.auth.admin.existingSecret` must + name a Secret containing the configured key (default `password`), or + `server.auth.admin.password` must be set, or `server.auth.admin.autoGenerate` + must be true. The same shape applies to `server.auth.token` (`existingSecret` + / `value` / `autoGenerate`). With authentication disabled, + `admin.existingSecret`, `admin.password`, `token.existingSecret`, and + `token.value` must be empty, so a configured but inactive Secret reference + cannot be overlooked. A missing Secret fails when Kubernetes configures the + container; an empty `password` fails in the Server startup wrapper. +- `server.hpa.minReplicas` must not exceed `maxReplicas`, and enabling + utilization-based HPA requires a strictly positive + `server.resources.requests.cpu`. +- `pdb.minAvailable` must be less than the matching `replicas`, so a + PodDisruptionBudget cannot permanently block node drains. +- `pd.pdb.minAvailable` must also be at least the PD Raft majority, + `floor(replicas/2)+1`, so the budget cannot permit evictions that drop PD + below quorum. With 2 PD replicas no valid budget exists (the majority is + the whole membership); disable the PD PDB or use an odd replica count. +- `extraEnv` must not set chart-managed variable names (for example + `HG_SERVER_INIT_STORE_ENABLED` or the PD/Store identity and topology + variables): entries render after the chart-owned variables and the last + duplicate wins, so an override would silently bypass a validated contract. + `JAVA_OPTS` and `JAVA_OPTIONS` are reserved for the same reason: the + component start scripts skip automatic heap sizing and drop the chart's + `JAVA_OPTS` flags entirely when `JAVA_OPTIONS` arrives preset. +- `pd.replicas` and `store.replicas` are capped at 99. +- `pd.partition.defaultShardCount` and `pd.partition.storeMaxShardCount` + must each be empty or a positive integer. An explicit shard count must + also be odd (PD's config API rejects even values, and PD clamps 2 to 1) + and must not exceed `store.replicas`, past which PD would silently clamp + it to the live store count. +- `hubble.port` must be a valid port, `hubble.persistence.size` must be + non-empty, and `hubble.service.nodePort` requires a `NodePort` or + `LoadBalancer` Service type. +- `hubble.image` needs a tag or a digest (the chart `appVersion` tracks the + Server release, not Hubble), and an Ingress without `tls` is rejected for + Server and Hubble alike unless the matching `ingress.allowPlainHttp=true` + opts in. +- `hubble.enabled` without `server.auth.enabled` is rejected unless + `hubble.allowWithoutServerAuth=true`, and + `hubble.securityContext.readOnlyRootFilesystem=true` is rejected because + the Hubble wrapper writes its properties file inside the image at startup. +- `store.pdb.minAvailable` must be at least `store.replicas - 1`, so + voluntary evictions cannot remove two copies of one shard at once. +- `podLabels` may not override the chart-managed `app.kubernetes.io/name`, + `instance` or `component` keys on any workload. +- A non-ClusterIP `pd.service.type` requires + `pd.service.allowInsecureExposure=true`. +- With `networkPolicy.enabled`, exposing PD, Server or Hubble (a NodePort + or LoadBalancer Service, a Server or Hubble Ingress, or a set + `server.advertiseUrl`) requires a non-empty + `networkPolicy..extraIngress` naming who may connect. +- An upgrade may not shrink `pd.replicas` or `store.replicas` below the + live StatefulSet; see Scaling for the manual procedure. + +### NetworkPolicy + +`networkPolicy.enabled` renders one NetworkPolicy per component (PD, Store, +Server, and Hubble when enabled). Each one isolates its own Pods in both +directions and lists the traffic they need, so the policies take effect in any +apply order. It is off in `values.yaml`, because the base values cannot know +who your clients are, and on in `values-cluster.yaml`. + +It only works when the cluster's network plugin enforces NetworkPolicy (kind +v0.25 or later, k3s, Calico, Cilium). Other plugins accept the objects and +enforce nothing. To check, run a Pod without chart labels in another +namespace and `curl` the PD client Service on the REST port: it must time out. + +With it on, the release admits only its own traffic: + +| To | From, ports | +|---|---| +| PD | PD: raft, gRPC. Store, Server, and Hubble in `pd` mode: gRPC, REST | +| Store | Store: raft. Server: gRPC, REST. Hubble in `pd` mode: REST | +| Server | Hubble and the `helm test` Pod: `server.port` | +| Hubble | nothing (port-forward uses loopback and needs no rule) | + +Every component may also resolve DNS on port 53. Nothing outside the release +is admitted unless it is listed in `networkPolicy..extraIngress`, +including the Ingress controller and clients of a NodePort or LoadBalancer +Service. Exposing PD, Server or Hubble that way, or setting +`server.advertiseUrl`, with an empty `extraIngress` fails the render instead +of opening the port. For PD this is the reachability restriction that +`pd.service.allowInsecureExposure` asks for. The check sees only exposure the +chart creates; a Service, Gateway route or proxy you add yourself needs its +own `extraIngress` entry. + +PD, Store and Server reach nothing outside the release except DNS, so a +feature that calls out (for example hugegraph-computer jobs through the +Kubernetes API, which this chart does not enable) does not work with the +policies on. One call is made by default: on every start the Store image +downloads `libjemalloc.so` from github.com. With the policies on that +connection times out after about two minutes, the Store starts without +jemalloc and continues (measured on kind: Ready after 151 s instead of 11 s). +The same happens on any cluster without internet access. The `helm test` Pod is selected by no chart policy, so its egress +is open only while nothing else selects it: under a namespace-wide +default-deny policy of your own, allow it egress to `server.port` and DNS. + +| Parameter | Description | Default | +|---|---|---| +| `networkPolicy.enabled` | Render the policies | `false` | +| `networkPolicy..extraIngress` | Extra NetworkPolicy ingress rules, appended as written | `[]` | +| `networkPolicy.hubble.extraEgress` | Extra egress rules for Hubble's optional outside endpoints (`es.urls`, `prometheus.url`) | `[]` | + +
+Letting other workloads in + +Anything outside the release is blocked until it is listed. Every rule must +name its peers in `from` (the schema rejects a rule without one); to admit any +address, write an `ipBlock` such as `0.0.0.0/0` explicitly. A +`namespaceSelector` and a `podSelector` in the same peer must both match; as +two separate peers, either one is enough. What a +NodePort or LoadBalancer client looks like from the Pod depends on the network +plugin, `externalTrafficPolicy` and the node the request arrives on. Measured +with a NodePort Server on two-node kind clusters: + +- kindnet, and Cilium with kube-proxy replacement: a call to the Server's own + node arrived with the client address; through the other node it arrived + with that node's address. +- Calico: through the other node the call arrived from that node's tunnel + address inside the Pod CIDR. +- Cilium with kube-proxy: no `ipBlock` rule admitted NodePort traffic, because + Cilium identifies node addresses by its own node identities rather than by + CIDR. + +Test with the plugin you run and name the CIDR you see arriving. + +```yaml +networkPolicy: + server: + extraIngress: + # The ingress-nginx controller, when server.ingress is enabled. + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: ingress-nginx + podSelector: + matchLabels: + app.kubernetes.io/name: ingress-nginx + ports: + - port: 8080 + # Applications in namespace "apps" call the Server API. + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: apps + ports: + - port: 8080 + pd: + extraIngress: + # Prometheus scrapes /actuator/prometheus on PD REST. + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: monitoring + ports: + - port: 8620 + # Vermeer reads partition metadata over PD gRPC. + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: vermeer + ports: + - port: 8686 + store: + extraIngress: + # Vermeer scans Store over gRPC; Prometheus scrapes Store REST. + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: vermeer + ports: + - port: 8500 + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: monitoring + ports: + - port: 8520 +``` + +
+ +## Deep Dive + +### Connecting to the Cluster + +```bash +PASSWORD="$(kubectl get secret -n hugegraph hugegraph-admin \ + -o jsonpath='{.data.password}' | base64 --decode)" +kubectl port-forward -n hugegraph svc/hugegraph-server 8080:8080 +curl --user "admin:${PASSWORD}" http://127.0.0.1:8080/versions +curl --user "admin:${PASSWORD}" http://127.0.0.1:8080/graphs +``` + +### Cluster Health + +| Component | Port | Purpose | +|------|-------------|---------| +| PD | `8686` | gRPC (Store and Server clients) | +| PD | `8620` | REST / health probes | +| PD | `8610` | Raft | +| Store | `8500` | gRPC | +| Store | `8510` | Raft | +| Store | `8520` | REST / health probes | +| Server | `8080` | Gremlin and REST API | + +All ports are configurable through `values.yaml`. Changing `server.port` updates +the listener, container port, and Service together. + +A stalled component (process alive but frozen) is ended by its liveness +probe, so the default 20 s period and 3-failure threshold bound the blast +radius of a stalled Store at roughly one minute; raft moves its partition +leaders within seconds of the restart. + +--- + +### Scheduling + +Every component (`pd`, `store`, `server`, `hubble`) exposes the full set of +scheduling controls: `nodeSelector`, `tolerations`, `affinity`, +`topologySpreadConstraints`, and `priorityClassName`. For example, pinning +Store to labeled nodes is just: + +```yaml +store: + nodeSelector: + hugegraph/role: storage +``` + +`antiAffinity` (`required` | `preferred` | `disabled`) renders a hostname +pod-anti-affinity preset for `pd`, `store`, and `server`; Hubble has no +`antiAffinity` key because it is single-replica by design. Setting a raw +`affinity` replaces the preset entirely. All three default to `preferred` +(Server always did; the pd and store defaults changed from `required`), so +the chart schedules on clusters with fewer nodes than replicas (including +single-node development clusters). The trade: `preferred` lets the +scheduler co-locate replicas under node pressure, so a single node failure +can then take more than one PD or Store replica with it. Production +clusters with enough nodes should pin `pd.antiAffinity` and +`store.antiAffinity` to `required`, as `values-cluster.yaml` does. + +### Partition Sharding + +A fresh install seeds PD's persisted configuration with a partition shard +count of 3 when `store.replicas` is at least 3, and 1 otherwise. Without +this the PD image's `conf/application.yml` would pin +`partition.default-shard-count` to 1, leaving chart-deployed clusters +without store-level HA. The derivation never produces 2 because PD clamps a +shard count of 2 to 1: two shards cannot elect a leader. + +The chart renders the setting as `-Dpartition.default-shard-count` in the PD +container's `JAVA_OPTS`; system properties outrank the shipped config file, +and the PD start script appends `JAVA_OPTS` after its automatically computed +heap flags, so the image's JVM auto-sizing is unaffected. + +**The seed applies at first bootstrap only.** PD persists the shard count +into its own metadata the first time it starts with empty storage, and from +then on the stored value is authoritative: every PD leader change re-reads +it from storage, overwriting whatever the `-D` flag says. Changing +`pd.partition.defaultShardCount` later, or scaling `store.replicas` across +the derivation boundary, therefore has **no** effect on an initialized +cluster. Nor is the value frozen at partition creation: PD reconciles +existing shard groups toward the stored value whenever a partition patrol +runs. To change the shard count of a running cluster, use PD's own config +API (which accepts only odd values not exceeding the live store count) and +then trigger `GET /v1/task/patrolPartitions`; expect shard-group +reallocation when the counts differ. + +The shard count also fixes the initial partition count: +`store.replicas x storeMaxShardCount / shardCount`, computed once at +bootstrap. With the image's `store-max-shard-count` default of 12, the +derived shard count moves a default 3-store install from 36 partitions +(shard count 1) to 12 (shard count 3). Set +`pd.partition.storeMaxShardCount` higher to compensate when more partitions +are wanted; it is likewise seeded at first bootstrap only. + +An explicit `pd.partition.defaultShardCount` must be odd and at most +`store.replicas`. The chart rejects other values at render time: PD would +silently clamp a value above the live store count, clamp 2 to 1, and reject +even values at its config API, so an accepted render would not mean an +honored setting. + +### Disaster Recovery + +What PD automates on current builds is narrow. A scheduled patrol runs on a +hardcoded 60-second cadence and only marks Stores that stopped sending +heartbeats as `Offline`; it does not touch partitions. There is **no +automatic re-replication**: re-placing the replicas of a lost Store, +reconciling shard groups against the stored shard count, and processing +tombstoned Stores all run only when a partition patrol is triggered +explicitly. PD's configuration binds `pd.patrol-interval` and +`store.max-down-time` keys, but no code path on current builds reads +either, which is why this chart does not expose them. + +Recovery and rebalancing are operator-triggered, and the task endpoints +execute **locally on the PD that receives them**: a follower answers with +an empty success and does no recovery work. Port-forwarding the client +Service selects an arbitrary PD, so identify the leader first and +port-forward that Pod: + +`kubectl port-forward` runs in the foreground, so use a second terminal +(or background the forward) for the curls, and stop the Service forward +before starting the leader one: + +```bash +kubectl port-forward -n hugegraph svc/hugegraph-pd-client 8620:8620 +PD_SECRET="$(kubectl -n hugegraph get secret hugegraph-pd-auth \ + -o jsonpath='{.data.secret-key}' | base64 --decode)" +# Read .data.pdLeader.raftUrl; its host names the leader Pod. +curl -su "hg:${PD_SECRET}" http://127.0.0.1:8620/v1/members +# Stop the Service forward, then forward the leader Pod instead. +kubectl port-forward -n hugegraph pod/ 8620:8620 +# Reconcile shard groups and process tombstoned Stores. +curl -u "hg:${PD_SECRET}" http://127.0.0.1:8620/v1/task/patrolPartitions +# Spread Raft leaders, then partition data. +curl -u "hg:${PD_SECRET}" http://127.0.0.1:8620/v1/task/balanceLeaders +curl -u "hg:${PD_SECRET}" http://127.0.0.1:8620/v1/task/balancePartitions +``` + +Read `/v1/members` again after the tasks: if leadership moved mid-sequence, +the later tasks ran on a follower and did nothing, so rerun them on the new +leader. + +The credential is required; PD answers 401 without it. The Secret name +follows the release (`-pd-auth`) unless `pd.auth.existingSecret` +is set. + +Run `patrolPartitions` after replacing a Store that is not coming back, +`balancePartitions` once the cluster is stable again, and `balanceLeaders` +after restarts that skewed leader placement. + +A Store replaced with an empty PVC registers under a **new Store ID**, even +though its Pod name and DNS address are unchanged, and the old ID stays +`Offline` in PD with its shard memberships intact; the patrol repairs only +`Tombstone` members, so it never touches the `Offline` entry. After such a +replacement, retire the old ID explicitly on the leader: find the +`Offline` entry in `/v1/stores` whose address matches the replaced Pod, +mark it `Tombstone` with `curl -u "hg:${PD_SECRET}" -X POST -H +'Content-Type: application/json' -d '{"storeState":"Tombstone"}' +http://127.0.0.1:8620/v1/store/` (this hands its shards to the +patrol), then run `patrolPartitions` and verify every shard group lists +only `Up` Stores. `DELETE /v1/store/` only erases the record and +strands the shard memberships; use it, if at all, as cleanup after the +patrol has finished. + +Periodic balancing and shard-sync progress metrics do not exist upstream +yet and are out of scope for this chart. Periodic leader balancing is +tracked in +[apache/hugegraph#3135](https://github.com/apache/hugegraph/issues/3135); +disaster-recovery metrics are tracked in +[apache/hugegraph#3136](https://github.com/apache/hugegraph/issues/3136). + +--- + +### Scaling + +PD and Store reserve the maximum StatefulSet ordinal in their resource names, +so scaling never renames a PersistentVolumeClaim or shifts a Pod identity. +Both are capped at 99 replicas. + +Server scales through `server.replicas`, or by enabling `server.hpa`. With HPA +enabled the Deployment omits `spec.replicas`, so a Helm upgrade does not +overwrite the autoscaler's live replica count. + +`values.schema.json` requires at least one replica per component, so a +staged rollout (PD and Server first, Stores later) cannot be written in a +values file. Install the full topology and stage it with +`kubectl scale statefulset -hugegraph-store --replicas=0`, scaling +back up when ready; the Servers wait, not-ready, until Stores register. +`kubectl scale` changes only the live StatefulSet: the next `helm upgrade` +renders `store.replicas` from values again and restores the full topology. + +Changing PD or Store replicas on a live release is not a values change. +Raft and shard membership are persisted, and Pods alone do not reconfigure +them. The chart rejects both directions for PD and a shrink for Store, and +reads the live StatefulSet to do it, so a fresh install at any replica count +is unaffected and a client-side `--dry-run` does not show the guard. + +**PD, either direction.** The peer list the chart renders reaches raft only +as `NodeOptions.setInitialConf`, which jraft applies when a node bootstraps +without a configuration of its own. On an initialized group it is inert: a +3-to-5 upgrade starts two more PDs and changes the bootstrap list, while the +voting configuration stays at three, and a 3-to-1 shrink loses quorum +outright. Membership changes through `RaftEngine.changePeerList`, which the +PD client API reaches and no REST route exposes, so this is a client-side +operation the chart cannot perform and does not wrap. Change the membership +through PD, confirm the new configuration in `/v1/members`, scale the live +StatefulSet, then `helm upgrade` with the matching value. Until you have run +and verified that sequence on your own build, treat a PD replica change as +unsupported and install the PD count you intend to keep. + +**Store, shrinking.** Draining is a state transition, not a balance. +`patrolPartitions` reallocates groups whose shard count does not match the +configured replication factor and hands off the groups of Stores already in +`Tombstone`; `balancePartitions` spreads shards across the active Stores, +including the ones you mean to remove, so neither call retires a healthy +Store and the "no shard lists them" condition may never arrive. Retire the +leaving Store the same way the Disaster Recovery section retires a replaced +one: + +1. Check the remaining Stores can still hold the persisted replication + factor: after the shrink, live Stores must be at least + `pd.partition.shardCount`. +2. Map the ordinals the shrink will delete (the highest ones) to Store ids + through `/v1/stores`, matching on the Pod address. +3. `POST /v1/store/{id}` with `{"storeState":"Tombstone"}` for each leaving + id, which is what drives `storeTurnoff` and the reallocation. +4. Wait until `/v1/shardGroups` no longer lists those ids and every group + reports its full shard count with one leader. +5. Scale the live StatefulSet with `kubectl -n scale statefulset + --replicas=`, then `helm upgrade` with the matching value. + +Deleting the PersistentVolumeClaims of the removed ordinals is separate and +permanent; do it only after step 4 reports the data moved. + +## Troubleshooting + +### Store Pods Stuck in `Init:0/1` + +The Store init container waits for a majority of PD peers to answer +`store.waitPath`. Check PD first: + +```bash +kubectl -n get pods -l app.kubernetes.io/component=pd +kubectl -n logs -c wait-for-pd +``` + +The wait is bounded by `store.waitTimeoutSeconds` (default 900). On timeout the +init container exits with a message naming the peers it polled, so the failure +appears in `kubectl describe pod` instead of hanging silently. + +### PersistentVolumeClaims Stay `Pending` + +No default StorageClass, or the provisioner is unhealthy: + +```bash +kubectl get sc +kubectl -n get pvc -l app.kubernetes.io/instance= +kubectl -n get pods +``` + +### Server Ready but Queries Fail + +The Server readiness probe uses `/versions`, which can report ready before the +graph is fully able to serve index-backed queries. Confirm the graph is live +(the server image does not ship `curl`, so probe through a port-forward): + +```bash +kubectl port-forward -n hugegraph svc/hugegraph-server 8080:8080 +curl -s --user "admin:${PASSWORD}" http://127.0.0.1:8080/graphs +``` + +### Queries Fail with "Could not rebind" Right After Creating a Graph + +The Server that handles `CreateGraph` waits for its own Gremlin binding +before returning HTTP 200 +([#3138](https://github.com/apache/hugegraph/pull/3138)), so create-then-query +on the **same** Server (or sticky routing to that Pod) is reliable. + +Other Server replicas still converge independently through a PD metadata +watch plus a local graph open. Until they finish, a Gremlin query routed +through the load-balanced Service to a not-yet-converged replica can still +fail with a 400 error such as `Could not rebind [g]`. This is upstream +behavior, not a chart setting. Mitigations for multi-replica load-balanced +deployments: + +- Retry with backoff in the client; the window normally closes in seconds. +- Use sticky routing (or `kubectl port-forward` to one Pod) for + create-then-verify flows. +- Poll `/graphs` on each replica until the new graph appears everywhere + before opening query traffic. + +Cluster-wide readiness and PD-owned graph creation remain tracked in +[#3137](https://github.com/apache/hugegraph/issues/3137) (Phase 2: +[#3139](https://github.com/apache/hugegraph/pull/3139); Phase 3: PD +orchestration). + +### Pods OOM Killed or Restarting + +The default `values.yaml` sets **no** resource requests or limits and preserves +the image's automatic JVM sizing. Set resources explicitly before production +use; see `values-cluster.yaml`. + +```bash +kubectl get pods -o wide +kubectl -n describe pod | grep -A5 "Last State" +``` + +### Release Name Too Long + +Helm itself rejects release names longer than 53 characters, before this chart +renders anything: + +```text +invalid release name ... the length must not be longer than 53 +``` + +Within that limit the chart is safe: resource names reserve their suffix and +StatefulSet ordinal before truncation, so every generated Service and Pod name +stays inside the 63-character DNS label limit, and PD/Store identities do not +shift when replicas change. Use `fullnameOverride` to shorten generated names +independently of the release name. + +--- + +## Limitations + +- The default values set no container resources, so every pod is QoS class + BestEffort and each JVM sizes its heap against total NODE memory rather than + a cgroup limit. That is fine for a single-node or development install, but on + a multi-node cluster where several pods share a node the heaps oversubscribe + it and pods abort. A measured example: on 7.6 GB workers the default install + gave PD `-Xmx3299m` and, with three to four pods per node, never converged. + Use `values-cluster.yaml`, or set your own `resources`, for any multi-node + deployment. +- The Store's memory ceiling is not its heap. The shipped + `conf/application.yml` includes the `pd` Spring profile, and + `conf/application-pd.yml` sets `rocksdb.total_memory_size` to + `32000000000`; `RaftRocksdbOptions` splits that number into a RocksDB + write cache and block cache, so those native caches are bounded by 32 GB + and not by the container. The jraft log storage also registers its own + 1 GiB LRU block cache once per process. With the cluster preset's + `-Xmx1024m -XX:MaxDirectMemorySize=512m`, a 4Gi limit sat below the + steady state and the kernel OOM-killed all three Stores after about 1 GB + of data; the preset now asks for 5Gi and limits at 8Gi, where a k3s run + measured 4.42 GiB anonymous RSS (2026-09-19). Scale both numbers with the + data size. The chart cannot lower the RocksDB budget itself: the Store + entrypoint rebuilds `SPRING_APPLICATION_JSON` from its own variables and + the chart mounts no config file, so `rocksdb.total_memory_size` can only + be changed in the image or through a custom config mount. +- PD's raft IP whitelist resolves peer hostnames to IPs once at startup, + which under Kubernetes can block peers whose pod IPs were unpublished at + that moment or change later. The chart therefore disables the whitelist + in-cluster via the upstream `raft.ip-whitelist.enabled` switch, leaving + peer authentication to Kubernetes-level controls: enable + `networkPolicy.enabled` (on in `values-cluster.yaml`) so that only PD Pods + reach the raft port. Setting + `pd.raftIpWhitelistEnabled=true` restores the image default along with + its one-shot resolution semantics (bring-up races and pod-IP-change + rejections included) at the operator's own risk. +- PD's `/v1/health` answers 200 as soon as the REST listener is up and never + consults raft, so it cannot see a lost quorum. With more than one PD the + chart uses it for startup and liveness on purpose, so that a follower which + merely lost its leader is not restarted, and puts readiness and the Store + wait on `/v1/ready`, which answers 503 without a raft leader. A single PD + is the exception: it has no election to lose, and a PD that steps down for + good, as after a failed raft snapshot on a full disk + ([apache/hugegraph#3222](https://github.com/apache/hugegraph/issues/3222)), + answers `/v1/health` forever while serving no writes. At `pd.replicas: 1` + startup and liveness therefore derive to `/v1/ready`, so the kubelet + restarts such a PD; `pd.livenessPath` overrides the derivation. If a future + PD answers 503 from `/v1/health` in that state, the value becomes + unnecessary. +- Server discovery is a lease. Each Server re-registers its Pod IP with PD + every 15 seconds and PD drops an entry after three missed heartbeats, so a + replaced or evicted Server can stay in PD's list for up to 45 seconds after + it stops (measured 30 to 35 seconds on a live rollout). Hubble's cluster + view and other discovery clients may show that stale address for the + duration; application traffic is unaffected because it reaches Servers + through the Service, which drops the Pod immediately. +- No TLS, backups, Operator, multi-cluster support, automatic leader transfer, + or a complete monitoring stack. Store recovery is manual on current builds: + re-replication after Store loss, leader balancing, and partition + rebalancing run only when triggered (see Disaster Recovery); periodic + balancing and shard-sync metrics are upstream feature work. +- After [#3138](https://github.com/apache/hugegraph/pull/3138), the creating + Server is consistent at HTTP 200; other replicas may still lag for a short + window on load-balanced installs (see Troubleshooting: "Could not rebind"; + [#3137](https://github.com/apache/hugegraph/issues/3137) stays open for + cluster-wide and PD-owned creation). +- The published images run as root, so `runAsNonRoot` and + `readOnlyRootFilesystem` are not chart defaults. The container + `securityContext` does default to `allowPrivilegeEscalation: false`, + `capabilities.drop: [ALL]`, and `seccompProfile: RuntimeDefault`, which are + valid for a root image; `podSecurityContext` and `securityContext` are fully + configurable per component. +- `values-cluster.yaml` is a starting point, not a capacity guarantee. +- Authentication is on by default. The auth Secret sets the admin password + only at first creation via `auth.admin_pa`; the chart cannot rotate an + existing cluster's admin password. +- Every Server replica must share one JWT signing key. The chart injects + `HG_SERVER_AUTH_TOKEN_SECRET` from `server.auth.token` + (chart-managed by default) so Hubble login stays stable behind a + multi-replica Service. +- Hubble is single-replica, serves plain HTTP, requires `server.auth` to be + enabled for its login to complete, and keeps UI connection metadata, + including any graph credentials entered in the UI, in an embedded H2 + database that is lost on Pod replacement unless `hubble.persistence` is + enabled. diff --git a/helm/hugegraph/templates/NOTES.txt b/helm/hugegraph/templates/NOTES.txt new file mode 100644 index 0000000000..421b88c901 --- /dev/null +++ b/helm/hugegraph/templates/NOTES.txt @@ -0,0 +1,118 @@ +{{/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/}} +{{- $hubbleEnabled := get (get .Values "hubble" | default dict) "enabled" | default false }} +{{ .Release.Name }} installed in namespace {{ .Release.Namespace }}. + + PD {{ .Values.pd.replicas }} replica(s) + Store {{ .Values.store.replicas }} replica(s) + Server {{ if .Values.server.hpa.enabled }}HPA {{ .Values.server.hpa.minReplicas }}-{{ .Values.server.hpa.maxReplicas }}{{ else }}{{ .Values.server.replicas }} replica(s){{ end }} +{{- if $hubbleEnabled }} + Hubble 1 replica (UI) +{{- end }} + +Watch the cluster come up: + + kubectl get pods -n {{ .Release.Namespace }} -l app.kubernetes.io/instance={{ .Release.Name }} -w + +Store Pods stay in Init until a majority of PD peers answer store.waitPath +(/v1/ready by default, which stays 503 until a raft leader exists, so the +majority counted is a quorum and not merely a set of live listeners). + +PD's management REST API (Disaster Recovery calls in the README) takes the +release's PD secret as the Basic-auth password: + + PD_SECRET="$(kubectl get secret -n {{ .Release.Namespace }} {{ include "hugegraph.pd.authSecretName" . }} -o jsonpath='{.data.{{ include "hugegraph.pd.authSecretKey" . | replace "." "\\." }}}' | base64 --decode)" + +Verify the release: + + helm test {{ .Release.Name }} --namespace {{ .Release.Namespace }} + +Reach the Server API: + + kubectl port-forward -n {{ .Release.Namespace }} svc/{{ include "hugegraph.server.name" . }} {{ .Values.server.port }}:{{ .Values.server.port }} +{{- if .Values.server.auth.enabled }} + PASSWORD="$(kubectl get secret -n {{ .Release.Namespace }} {{ include "hugegraph.server.authSecretName" . }} -o jsonpath='{.data.{{ include "hugegraph.server.authSecretKey" . | replace "." "\\." }}}' | base64 --decode)" + curl --user "admin:${PASSWORD}" http://127.0.0.1:{{ .Values.server.port }}/versions +{{- else }} + curl http://127.0.0.1:{{ .Values.server.port }}/versions +{{- end }} +{{- if $hubbleEnabled }} + +Reach the Hubble UI: + + kubectl port-forward -n {{ .Release.Namespace }} svc/{{ include "hugegraph.hubble.name" . }} {{ .Values.hubble.port }}:{{ .Values.hubble.port }} + + then open http://127.0.0.1:{{ .Values.hubble.port }} + +{{- if not .Values.server.auth.enabled }} + +Server authentication is disabled, so the Hubble login cannot complete on +this release: current Hubble images authenticate against the cluster. +Enable server.auth to use Hubble. +{{- end }} + +Hubble serves plain HTTP. Reach it through port-forward or an +HTTPS-terminating Ingress; never expose it directly to an untrusted network. +{{- if not .Values.hubble.persistence.enabled }} +Hubble persistence is disabled: UI connection metadata is lost when the +Hubble Pod is replaced. Graph data is unaffected. +{{- end }} +{{- end }} +{{- if not .Values.server.auth.enabled }} + +Authentication is disabled. Do not expose this release to untrusted networks. +{{- end }} +{{- if (get (get .Values "networkPolicy" | default dict) "enabled") }} + +NetworkPolicy is on: only this release's components, Hubble and the helm test +Pod reach each other. List other clients (applications, Prometheus, Vermeer) +in networkPolicy..extraIngress; see README, NetworkPolicy. The +policies are only enforced by a network plugin that implements NetworkPolicy. +{{- end }} +{{- $advertiseUrl := trim (default "" .Values.server.advertiseUrl) }} +{{- if $advertiseUrl }} + +Outside PD discovery: Server registers this URL with PD: + + {{ $advertiseUrl }} + +Keep server.auth.enabled=true so this registration stays active. Expose the +PD client Service (pd.service.type=NodePort or LoadBalancer) and point a +standalone Hubble at that PD endpoint with pd.enabled=true. Graph UI works +via PD discovery; Store Operations metrics are unchanged and still use +in-cluster Store URLs unless configured separately. +{{- end }} + +{{- if and (gt (int .Values.pd.replicas) 1) (ne (get .Values.pd "antiAffinity" | default "") "required") (empty (get .Values.pd "affinity")) }} + +pd.antiAffinity is not "required", so the scheduler may co-locate PD quorum +members on one node and a single node loss can take down the PD quorum. The +PD PodDisruptionBudget only limits voluntary disruption (drains, evictions), +not node failure. On clusters with at least {{ .Values.pd.replicas }} nodes, +set pd.antiAffinity=required; see values-cluster.yaml. +{{- end }} +{{- if or (empty .Values.pd.resources) (empty .Values.store.resources) (empty .Values.server.resources) }} + +One or more components have no resource requests or limits. Set them before +production use; see values-cluster.yaml. +{{- end }} + +{{- if and (empty .Values.pd.resources) (empty .Values.store.resources) (empty .Values.server.resources) }} +WARNING: no resources are set, so every pod is BestEffort and each JVM sizes its +heap against total node memory. On a multi-node cluster this oversubscribes the +nodes and pods may abort. Use values-cluster.yaml or set resources explicitly. +{{- end }} diff --git a/helm/hugegraph/templates/_helpers.tpl b/helm/hugegraph/templates/_helpers.tpl new file mode 100644 index 0000000000..105167bf54 --- /dev/null +++ b/helm/hugegraph/templates/_helpers.tpl @@ -0,0 +1,893 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{/* +Expand the name of the chart. +*/}} +{{- define "hugegraph.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "hugegraph.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 "hugegraph.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "hugegraph.labels" -}} +helm.sh/chart: {{ include "hugegraph.chart" . }} +{{ include "hugegraph.selectorLabels" . }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{- define "hugegraph.selectorLabels" -}} +app.kubernetes.io/name: {{ include "hugegraph.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{- define "hugegraph.pd.name" -}} +{{- printf "%s-pd" (include "hugegraph.fullname" . | trunc 57 | trimSuffix "-") }} +{{- end }} + +{{- define "hugegraph.pd.clientName" -}} +{{- printf "%s-pd-client" (include "hugegraph.fullname" . | trunc 53 | trimSuffix "-") }} +{{- end }} + +{{- define "hugegraph.store.name" -}} +{{- printf "%s-store" (include "hugegraph.fullname" . | trunc 54 | trimSuffix "-") }} +{{- end }} + +{{- define "hugegraph.server.name" -}} +{{- printf "%s-server" (include "hugegraph.fullname" . | trunc 56 | trimSuffix "-") }} +{{- end }} + +{{- define "hugegraph.hubble.name" -}} +{{- printf "%s-hubble" (include "hugegraph.fullname" . | trunc 56 | trimSuffix "-") }} +{{- end }} + +{{- define "hugegraph.hubble.dataName" -}} +{{- printf "%s-hubble-data" (include "hugegraph.fullname" . | trunc 51 | trimSuffix "-") }} +{{- end }} + +{{- define "hugegraph.test.name" -}} +{{- printf "%s-test-connection" (include "hugegraph.fullname" . | trunc 47 | trimSuffix "-") }} +{{- end }} + +{{/* +Resolve the Server authentication Secret. A user-provided Secret always wins; +otherwise use a stable chart-managed name so the generated Secret can survive +uninstall and be reused by a later install of the same release. +*/}} +{{- define "hugegraph.server.authSecretName" -}} +{{- $auth := get .Values.server "auth" | default dict -}} +{{- $admin := get $auth "admin" | default dict -}} +{{- $existingSecret := get $admin "existingSecret" | default "" -}} +{{- if $existingSecret -}} +{{- $existingSecret -}} +{{- else -}} +{{- printf "%s-admin" (.Release.Name | trunc 55 | trimSuffix "-") -}} +{{- end -}} +{{- end }} + +{{- define "hugegraph.server.authSecretKey" -}} +{{- $auth := get .Values.server "auth" | default dict -}} +{{- $admin := get $auth "admin" | default dict -}} +{{- get $admin "key" | default "password" -}} +{{- end }} + +{{/* +Return the chart-managed admin password (base64). Inline admin.password wins +on first write; otherwise lookup keeps upgrades from rotating a generated +credential. Never used for an external existingSecret. +*/}} +{{- define "hugegraph.server.authSecretPassword" -}} +{{- $auth := get .Values.server "auth" | default dict -}} +{{- $admin := get $auth "admin" | default dict -}} +{{- $password := get $admin "password" | default "" -}} +{{- if $password -}} +{{- $password | b64enc -}} +{{- else -}} +{{- $key := include "hugegraph.server.authSecretKey" . -}} +{{- $secret := lookup "v1" "Secret" .Release.Namespace (include "hugegraph.server.authSecretName" .) -}} +{{- if and $secret (hasKey $secret "data") (hasKey (get $secret "data") $key) -}} +{{- get (get $secret "data") $key -}} +{{- else -}} +{{- randAlphaNum 32 | b64enc -}} +{{- end -}} +{{- end -}} +{{- end }} + +{{/* +Resolve the JWT signing Secret. User-provided token.existingSecret wins; +otherwise use a stable chart-managed name so every Server replica shares one key. +*/}} +{{- define "hugegraph.server.authTokenSecretName" -}} +{{- $auth := get .Values.server "auth" | default dict -}} +{{- $token := get $auth "token" | default dict -}} +{{- $existing := get $token "existingSecret" | default "" -}} +{{- if $existing -}} +{{- $existing -}} +{{- else -}} +{{- printf "%s-auth-token" (.Release.Name | trunc 51 | trimSuffix "-") -}} +{{- end -}} +{{- end }} + +{{- define "hugegraph.server.authTokenSecretKey" -}} +{{- $auth := get .Values.server "auth" | default dict -}} +{{- $token := get $auth "token" | default dict -}} +{{- get $token "key" | default "token_secret" -}} +{{- end }} + +{{/* +Return the chart-managed JWT signing secret (base64). Inline token.value wins +on first write; otherwise lookup keeps multi-replica pods and upgrades on the +same signing key. +*/}} +{{- define "hugegraph.server.authTokenSecretValue" -}} +{{- $auth := get .Values.server "auth" | default dict -}} +{{- $token := get $auth "token" | default dict -}} +{{- $value := get $token "value" | default "" -}} +{{- if $value -}} +{{- $value | b64enc -}} +{{- else -}} +{{- $name := include "hugegraph.server.authTokenSecretName" . -}} +{{- $key := include "hugegraph.server.authTokenSecretKey" . -}} +{{- $secret := lookup "v1" "Secret" .Release.Namespace $name -}} +{{- if and $secret (hasKey $secret "data") (hasKey (get $secret "data") $key) -}} +{{- get (get $secret "data") $key -}} +{{- else -}} +{{- randAlphaNum 32 | b64enc -}} +{{- end -}} +{{- end -}} +{{- end }} + +{{/* +Resolve the PD REST auth Secret. User-provided pd.auth.existingSecret wins; +otherwise a stable chart-managed name shared by PD, the Server storage wait +and Hubble. +*/}} +{{- define "hugegraph.pd.authSecretName" -}} +{{- $auth := get .Values.pd "auth" | default dict -}} +{{- $existing := get $auth "existingSecret" | default "" -}} +{{- if $existing -}} +{{- $existing -}} +{{- else -}} +{{- printf "%s-pd-auth" (.Release.Name | trunc 55 | trimSuffix "-") -}} +{{- end -}} +{{- end }} + +{{- define "hugegraph.pd.authSecretKey" -}} +{{- $auth := get .Values.pd "auth" | default dict -}} +{{- get $auth "key" | default "secret-key" -}} +{{- end }} + +{{/* +Return the chart-managed PD REST secret (base64). Inline pd.auth.value wins +on first write; otherwise lookup keeps every PD, Server and Hubble Pod, and +every upgrade, on the same secret. +*/}} +{{- define "hugegraph.pd.authSecretValue" -}} +{{- $auth := get .Values.pd "auth" | default dict -}} +{{- $value := get $auth "value" | default "" -}} +{{- if $value -}} +{{- $value | b64enc -}} +{{- else -}} +{{- $name := include "hugegraph.pd.authSecretName" . -}} +{{- $key := include "hugegraph.pd.authSecretKey" . -}} +{{- $secret := lookup "v1" "Secret" .Release.Namespace $name -}} +{{- if and $secret (hasKey $secret "data") (hasKey (get $secret "data") $key) -}} +{{- get (get $secret "data") $key -}} +{{- else -}} +{{- randAlphaNum 32 | b64enc -}} +{{- end -}} +{{- end -}} +{{- end }} + +{{/* +Checksum for the PD, Server and Hubble pod templates so rotating the PD REST +Secret rolls the Pods that read it. Never hashes Secret data: it hashes the +Secret name, its key, and one revision input chosen by where the credential +comes from. + +The revision input is per credential source, because the two sources move at +different times. An active inline value is known at render time, so its +digest is the revision and it changes exactly once, on the upgrade that +rotates it. Mixing the live resourceVersion into that case would roll the +Pods a second time on the next no-change upgrade, once the rotated Secret had +been applied and its resourceVersion moved. An external or chart-generated +Secret has no render-time value to hash, so the live resourceVersion is the +only signal that it changed; there the lookup is kept and template-only +renders emit a constant. +*/}} +{{- define "hugegraph.pd.authChecksum" -}} +{{- $parts := list (include "hugegraph.pd.authSecretName" .) (include "hugegraph.pd.authSecretKey" .) -}} +{{- $pdAuthCfg := get .Values.pd "auth" | default dict -}} +{{- $inline := get $pdAuthCfg "value" | default "" -}} +{{- if and $inline (not (get $pdAuthCfg "existingSecret" | default "")) -}} +{{- $parts = append $parts (sha256sum $inline) -}} +{{- else -}} +{{- $secret := lookup "v1" "Secret" .Release.Namespace (include "hugegraph.pd.authSecretName" .) -}} +{{- if $secret -}}{{- $parts = append $parts (dig "metadata" "resourceVersion" "" $secret) -}}{{- end -}} +{{- end -}} +{{- join "|" $parts | sha256sum -}} +{{- end }} + +{{/* +Path the PD startup and liveness probes hit. + +/v1/health answers 200 as soon as the REST listener is up and never consults +raft, which is what a multi-PD deployment wants: losing leadership is normal +during an election, and restarting a follower for it would turn one election +into a rolling outage. Readiness carries the raft-aware signal instead. + +A single PD has no election to lose. There, a PD that steps down and cannot +recover, as after a failed snapshot on a full disk +(apache/hugegraph#3222), keeps answering /v1/health forever and liveness +never restarts it, so the default moves to /v1/ready when pd.replicas is 1. + +The startup probe follows this path as well. Kubernetes suppresses liveness +until the startup probe succeeds, so leaving startup on /v1/health would give +a single PD only the liveness budget (60 s by default) to reach raft +readiness after a restart, and a slow log replay would crash-loop instead of +booting. Following the same path puts that window inside the startup budget +(300 s by default) instead. + +Setting pd.livenessPath overrides the choice in both places. If PD starts +answering 503 from /v1/health in this state, this value stops being needed. +*/}} +{{- define "hugegraph.pd.livenessPath" -}} +{{- $explicit := get .Values.pd "livenessPath" | default "" -}} +{{- if $explicit -}} +{{- $explicit -}} +{{- else if eq (int .Values.pd.replicas) 1 -}} +/v1/ready +{{- else -}} +/v1/health +{{- end -}} +{{- end }} + +{{/* +PD Raft peers list: pod-0.svc.ns.svc:8610,... +Uses short headless DNS (cluster.local optional) resolvable inside the namespace. +*/}} +{{- define "hugegraph.pd.raftPeersList" -}} +{{- $peers := list -}} +{{- $replicas := int .Values.pd.replicas -}} +{{- $name := include "hugegraph.pd.name" . -}} +{{- $ns := .Release.Namespace -}} +{{- $port := int .Values.pd.ports.raft -}} +{{- range $i := until $replicas -}} + {{- $peers = append $peers (printf "%s-%d.%s.%s.svc:%d" $name $i $name $ns $port) -}} +{{- end -}} +{{- join "," $peers -}} +{{- end }} + +{{/* +PD gRPC peers for Store/Server. +*/}} +{{- define "hugegraph.pd.grpcPeersList" -}} +{{- $peers := list -}} +{{- $replicas := int .Values.pd.replicas -}} +{{- $name := include "hugegraph.pd.name" . -}} +{{- $ns := .Release.Namespace -}} +{{- $port := int .Values.pd.ports.grpc -}} +{{- range $i := until $replicas -}} + {{- $peers = append $peers (printf "%s-%d.%s.%s.svc:%d" $name $i $name $ns $port) -}} +{{- end -}} +{{- join "," $peers -}} +{{- end }} + +{{/* +PD REST endpoints for Server storage-readiness checks. +*/}} +{{- define "hugegraph.pd.restPeersList" -}} +{{- $peers := list -}} +{{- $replicas := int .Values.pd.replicas -}} +{{- $name := include "hugegraph.pd.name" . -}} +{{- $ns := .Release.Namespace -}} +{{- $port := int .Values.pd.ports.rest -}} +{{- range $i := until $replicas -}} + {{- $peers = append $peers (printf "%s-%d.%s.%s.svc:%d" $name $i $name $ns $port) -}} +{{- end -}} +{{- join "," $peers -}} +{{- end }} + +{{/* +Checksum for the Server pod template so rotating the referenced auth Secrets +rolls Server pods. Hashes Secret names and keys, never Secret data, so the +annotation carries no credential-derived material. + +The admin and token credentials pick their revision input independently, by +source, for the reason given on hugegraph.pd.authChecksum: an active inline +value contributes its digest and nothing else, so a rotation rolls Server +once rather than again on the next no-change upgrade; an external or +chart-generated Secret contributes its live metadata.resourceVersion. + +The lookup half is best-effort: plain `helm template` (and template-only +GitOps renderers) see no live Secrets and emit a constant; the first upgrade +after a fresh install rolls Server once as the checksum picks up the Secrets +created by that install; out-of-band rotation of an existingSecret applies on +the next `helm upgrade`. +*/}} +{{- define "hugegraph.server.authChecksum" -}} +{{- $parts := list (include "hugegraph.server.authSecretName" .) (include "hugegraph.server.authSecretKey" .) (include "hugegraph.server.authTokenSecretName" .) (include "hugegraph.server.authTokenSecretKey" .) -}} +{{- $srvAuth := get .Values.server "auth" | default dict -}} +{{- $adminCfg := get $srvAuth "admin" | default dict -}} +{{- $inlineAdmin := get $adminCfg "password" | default "" -}} +{{- if and $inlineAdmin (not (get $adminCfg "existingSecret" | default "")) -}}{{- $parts = append $parts (sha256sum $inlineAdmin) -}}{{- end -}} +{{- $tokenCfg := get $srvAuth "token" | default dict -}} +{{- $inlineToken := get $tokenCfg "value" | default "" -}} +{{- if and $inlineToken (not (get $tokenCfg "existingSecret" | default "")) -}}{{- $parts = append $parts (sha256sum $inlineToken) -}}{{- end -}} +{{- if not (and $inlineAdmin (not (get $adminCfg "existingSecret" | default ""))) -}} +{{- $admin := lookup "v1" "Secret" .Release.Namespace (include "hugegraph.server.authSecretName" .) -}} +{{- if $admin -}}{{- $parts = append $parts (dig "metadata" "resourceVersion" "" $admin) -}}{{- end -}} +{{- end -}} +{{- if not (and $inlineToken (not (get $tokenCfg "existingSecret" | default ""))) -}} +{{- $token := lookup "v1" "Secret" .Release.Namespace (include "hugegraph.server.authTokenSecretName" .) -}} +{{- if $token -}}{{- $parts = append $parts (dig "metadata" "resourceVersion" "" $token) -}}{{- end -}} +{{- end -}} +{{- join "|" $parts | sha256sum -}} +{{- end }} + +{{/* +Initial store list for PD bootstrap: store-0.svc.ns.svc:8500,... +*/}} +{{- define "hugegraph.store.initialStoreList" -}} +{{- $peers := list -}} +{{- $replicas := int .Values.store.replicas -}} +{{- $name := include "hugegraph.store.name" . -}} +{{- $ns := .Release.Namespace -}} +{{- $port := int .Values.store.ports.grpc -}} +{{- range $i := until $replicas -}} + {{- $peers = append $peers (printf "%s-%d.%s.%s.svc:%d" $name $i $name $ns $port) -}} +{{- end -}} +{{- join "," $peers -}} +{{- end }} + +{{/* +First store REST endpoint for STORE_REST / wait-partition. +*/}} +{{- define "hugegraph.store.restPrimary" -}} +{{- $name := include "hugegraph.store.name" . -}} +{{- $ns := .Release.Namespace -}} +{{- printf "%s-0.%s.%s.svc:%d" $name $name $ns (int .Values.store.ports.rest) -}} +{{- end }} + +{{/* +Server REST URL reached through the client Service. +*/}} +{{- define "hugegraph.server.clientUrl" -}} +{{- printf "http://%s.%s.svc:%d" (include "hugegraph.server.name" .) .Release.Namespace (int .Values.server.port) -}} +{{- end }} + +{{/* +URL registered with PD (server.urls_to_pd / HG_SERVER_URLS_TO_PD). +server.advertiseUrl wins when set so outside PD-mode Hubble receives a reachable address; otherwise each Server Pod announces its own Pod IP so PD discovery preserves the replica list for in-cluster clients. +*/}} +{{- define "hugegraph.server.urlsToPd" -}} +{{- $advertise := trim (default "" .Values.server.advertiseUrl) -}} +{{- if $advertise -}} +{{- $advertise -}} +{{- else -}} +{{- printf "http://$(POD_IP):%d" (int .Values.server.port) -}} +{{- end -}} +{{- end }} + +{{/* +PD REST endpoint reached through the client Service, for Hubble's pd.server. +*/}} +{{- define "hugegraph.pd.restClientEndpoint" -}} +{{- printf "%s.%s.svc:%d" (include "hugegraph.pd.clientName" .) .Release.Namespace (int .Values.pd.ports.rest) -}} +{{- end }} + +{{/* +Store REST origins in Hubble's bracketed allow-list form: +[http://store-0.svc.ns.svc:8520,...] +*/}} +{{- define "hugegraph.store.restOriginsList" -}} +{{- $origins := list -}} +{{- $replicas := int .Values.store.replicas -}} +{{- $name := include "hugegraph.store.name" . -}} +{{- $ns := .Release.Namespace -}} +{{- $port := int .Values.store.ports.rest -}} +{{- range $i := until $replicas -}} + {{- $origins = append $origins (printf "http://%s-%d.%s.%s.svc:%d" $name $i $name $ns $port) -}} +{{- end -}} +{{- printf "[%s]" (join "," $origins) -}} +{{- end }} + +{{/* +Quorum size: floor(replicas/2)+1 +*/}} +{{- define "hugegraph.pd.quorum" -}} +{{- add (div (int .Values.pd.replicas) 2) 1 -}} +{{- end }} + +{{/* +Render JAVA_OPTS only when explicitly configured. An empty value preserves the +image entrypoint's existing automatic JVM sizing behavior. +*/}} +{{- define "hugegraph.javaOptsEnv" -}} +{{- $javaOpts := default "" . -}} +{{- if ne (trim $javaOpts) "" -}} +- name: JAVA_OPTS + value: {{ $javaOpts | quote }} +{{- end -}} +{{- end }} + +{{/* +String form of a possibly-absent scalar value, preserving zero. sprig's +`default` treats 0 as unset, which would let a zero slip past the named +validation below, so absence is detected explicitly instead. Numbers from a +values file arrive as float64, whose toString switches to scientific +notation at 1e6 or higher (1000000 becomes "1e+06"), so integral float64 +values are formatted without an exponent. Non-integral floats keep their +raw form on purpose: the schema already rejects them, and the raw form +fails the named validation instead of being silently rounded. +*/}} +{{- define "hugegraph.optionalScalar" -}} +{{- if not (kindIs "invalid" .) -}} +{{- if and (kindIs "float64" .) (eq (floor .) .) -}}{{- printf "%.0f" . -}}{{- else -}}{{- trim (toString .) -}}{{- end -}} +{{- end -}} +{{- end }} + +{{/* +Effective PD JAVA_OPTS: chart-derived -D system properties, then pd.javaOpts. + +The -D route is the grounded override mechanism: the PD image's +docker-entrypoint.sh forwards JAVA_OPTS via `-j` into +bin/start-hugegraph-pd.sh, which places it on the java command line ahead of +-Dspring.config.location, and Spring system properties outrank the shipped +conf/application.yml (which pins partition.default-shard-count to 1). + +The shard count is always derived, so PD Pods always carry a JAVA_OPTS +variable, which shadows the PD image's `ENV JAVA_OPTS` default +(-XX:MaxRAMPercentage=50, -XX:+UseContainerSupport, -XshowSettings:vm). +That is acceptable: the start script always computes explicit -Xms/-Xmx +heap flags when the separate JAVA_OPTIONS variable is unset, which makes +MaxRAMPercentage moot, and only the -XshowSettings:vm startup diagnostics +are lost. + +JVM auto-sizing is preserved, verified against the PD dist start script: +`-j` lands in USER_OPTION, while the automatic heap sizing branch is gated on +the separate JAVA_OPTIONS variable and appends USER_OPTION after the computed +-Xms/-Xmx flags. A JAVA_OPTS holding only -D flags therefore still gets +automatic heap sizing, and heap flags in pd.javaOpts land later on the +command line, so they win. The derived -D flags come first for the same +reason: an explicit duplicate in pd.javaOpts overrides them. + +Both -D properties seed PD's persisted config at first bootstrap only: +ConfigService.loadConfig persists them when no stored config exists, and +every leader change re-reads the stored values (updatePDConfig), so on an +initialized cluster the flags are inert and the authoritative values live +in PD metadata, changeable only through PD's own config API. PD reconciles +existing shard groups toward the stored value when a partition patrol is +triggered (TaskScheduleService reallocShards). The empty-value derivation +is 3 when store.replicas is at least 3, else 1, because PD clamps a shard +count of 2 to 1 (two shards cannot elect a leader) and its config API +accepts only odd values. store-max-shard-count is rendered only when set, +keeping the image default. All lookups tolerate absent keys so releases +stored before these values existed keep rendering under --reuse-values. + +raft.ip-whitelist.enabled is always rendered, default false: PD resolves its +raft peer allowlist once at boot, which under Kubernetes blocks peers whose +pod IPs were unpublished at that moment or change later, so the switch is +off in-cluster per the upstream design and k8s auth owns that layer. Images +without the property ignore the flag. Set pd.raftIpWhitelistEnabled=true to +restore the image default. + +raft.rpc-timeout is a plain runtime property, applied on every start rather +than seeded at bootstrap. It bounds how long the surviving PDs wait on a +peer that stopped answering without closing its sockets, which is what a +leader election waits on; empty preserves the image default. +*/}} +{{- define "hugegraph.pd.effectiveJavaOpts" -}} +{{- $pd := .Values.pd -}} +{{- $partition := get $pd "partition" | default dict -}} +{{- $flags := list -}} +{{- $shardCount := include "hugegraph.optionalScalar" (get $partition "defaultShardCount") -}} +{{- if eq $shardCount "" -}} +{{- $shardCount = ternary "3" "1" (ge (int .Values.store.replicas) 3) -}} +{{- end -}} +{{- $flags = append $flags (printf "-Dpartition.default-shard-count=%s" $shardCount) -}} +{{- $maxShard := include "hugegraph.optionalScalar" (get $partition "storeMaxShardCount") -}} +{{- if ne $maxShard "" -}} +{{- $flags = append $flags (printf "-Dpartition.store-max-shard-count=%s" $maxShard) -}} +{{- end -}} +{{- $ipWhitelist := ternary "true" "false" (eq (get $pd "raftIpWhitelistEnabled" | toString) "true") -}} +{{- $flags = append $flags (printf "-Draft.ip-whitelist.enabled=%s" $ipWhitelist) -}} +{{- $rpcTimeout := include "hugegraph.optionalScalar" (get $pd "raftRpcTimeoutMs") -}} +{{- if ne $rpcTimeout "" -}} +{{- $flags = append $flags (printf "-Draft.rpc-timeout=%s" $rpcTimeout) -}} +{{- end -}} +{{- $userOpts := trim (get $pd "javaOpts" | default "") -}} +{{- if ne $userOpts "" -}} +{{- $flags = append $flags $userOpts -}} +{{- end -}} +{{- join " " $flags -}} +{{- end }} + +{{/* +Keep the startup probe alive for the 300-second storage wait, the Server's +120-second start timeout, and 30 seconds of process overhead. Older stored +values remain accepted, but their rendered threshold is raised to this floor. +*/}} +{{- define "hugegraph.server.startupFailureThreshold" -}} +{{- $period := int .Values.server.probes.startup.periodSeconds -}} +{{- $configured := int .Values.server.probes.startup.failureThreshold -}} +{{- $minimum := div (add 449 $period) $period -}} +{{- max $configured $minimum -}} +{{- end }} + +{{/* +Seconds the chart gives the Server image to finish starting, passed as +HG_SERVER_STARTUP_TIMEOUT_S. The image defaults that to 120 seconds, which is +shorter than the storage wait alone, so a Server still coming up kills itself +before Kubernetes has given up on it. The value therefore tracks the startup +probe: the effective budget above (floored at 450 seconds) minus the +300-second storage wait the entrypoint runs first, so the start command and +kubelet give up together instead of the image outliving the probe. Floored +at the image's own 120-second default, and capped at the entrypoint's 86400 +maximum rather than rendered into a Pod that refuses to start. +*/}} +{{- define "hugegraph.server.startupTimeoutSeconds" -}} +{{- $period := int .Values.server.probes.startup.periodSeconds -}} +{{- $threshold := include "hugegraph.server.startupFailureThreshold" . | int -}} +{{- min 86400 (max 120 (sub (mul $threshold $period) 300)) -}} +{{- end }} + +{{/* +Optional probe tunables, emitted only when explicitly set. Kubernetes defaults +timeoutSeconds to 1 second, which a garbage-collection pause can exceed on a +loaded Server; operators need a supported way to raise it without forking the +chart. Only explicitly configured fields are rendered. +*/}} +{{- define "hugegraph.probeTuning" -}} +{{- if hasKey . "timeoutSeconds" }} +timeoutSeconds: {{ .timeoutSeconds }} +{{- end }} +{{- if hasKey . "initialDelaySeconds" }} +initialDelaySeconds: {{ .initialDelaySeconds }} +{{- end }} +{{- if hasKey . "successThreshold" }} +successThreshold: {{ .successThreshold }} +{{- end }} +{{- end }} + +{{/* +Resolve the ServiceAccount name for a component: an explicit name wins, +otherwise the generated one when create is true, otherwise "default". +*/}} +{{- define "hugegraph.serviceAccountName" -}} +{{- $sa := get .component "serviceAccount" | default dict -}} +{{- if get $sa "name" -}} +{{- get $sa "name" -}} +{{- else if (get $sa "create" | default false) -}} +{{- .name -}} +{{- else -}} +default +{{- end -}} +{{- end }} + +{{/* +The minimum Server replica count that a PDB must remain valid against. +*/}} +{{- define "hugegraph.server.replicaFloor" -}} +{{- if .Values.server.hpa.enabled -}} +{{- .Values.server.hpa.minReplicas -}} +{{- else -}} +{{- .Values.server.replicas -}} +{{- end -}} +{{- end }} + +{{/* +Cross-field validation that JSON Schema draft-07 cannot express. +*/}} +{{- define "hugegraph.validateValues" -}} +{{- range $comp := list "pd" "store" "server" "hubble" -}} +{{- $compLabels := get (get $.Values $comp | default dict) "podLabels" | default dict -}} +{{- range $reserved := list "app.kubernetes.io/name" "app.kubernetes.io/instance" "app.kubernetes.io/component" -}} +{{- if hasKey $compLabels $reserved -}} +{{- fail (printf "%s.podLabels must not set %s: the chart manages it and the workload selectors, Services and PDBs match on it" $comp $reserved) -}} +{{- end -}} +{{- end -}} +{{- end -}} +{{- if and .Values.server.hpa.enabled (gt (int .Values.server.hpa.minReplicas) (int .Values.server.hpa.maxReplicas)) -}} +{{- fail "server.hpa.minReplicas must be less than or equal to server.hpa.maxReplicas" -}} +{{- end -}} +{{- if .Values.server.hpa.enabled -}} +{{- $serverResources := .Values.server.resources | default dict -}} +{{- $serverRequests := get $serverResources "requests" | default dict -}} +{{- if not (hasKey $serverRequests "cpu") -}} +{{- fail "server.resources.requests.cpu is required when server.hpa.enabled=true" -}} +{{- end -}} +{{- $cpuRequest := trim (toString (get $serverRequests "cpu")) -}} +{{- if or (eq $cpuRequest "") (hasPrefix "-" $cpuRequest) (regexMatch "^[+]?((0+([.]0*)?)|([.]0+))(([KMGTPE]i)|[numkMGTPE]|[eE][+-]?[0-9]+)?$" $cpuRequest) -}} +{{- fail "server.resources.requests.cpu must be strictly positive when server.hpa.enabled=true" -}} +{{- end -}} +{{- end -}} +{{/* +Raft and shard membership are persisted; deleting Pods does not reconfigure +them, so an in-place replica shrink permanently loses PD quorum or Store +shard majorities. The guard reads the live StatefulSet, so it fires only on +a real upgrade against a cluster; template-only renders have no live object +and skip it. An operator who has completed the documented manual scale-down +procedure has already scaled the live StatefulSet, so desired equals live +and the upgrade passes. +*/}} +{{- range $comp := list "pd" "store" -}} +{{- $stsName := "" -}} +{{- if eq $comp "pd" -}}{{- $stsName = include "hugegraph.pd.name" $ -}}{{- else -}}{{- $stsName = include "hugegraph.store.name" $ -}}{{- end -}} +{{- $live := lookup "apps/v1" "StatefulSet" $.Release.Namespace $stsName -}} +{{- if $live -}} +{{- $liveReplicas := int (dig "spec" "replicas" 0 $live) -}} +{{- $desired := int (get (get $.Values $comp) "replicas") -}} +{{- if and (gt $liveReplicas 0) (lt $desired $liveReplicas) -}} +{{- fail (printf "%s.replicas cannot shrink from %d to %d through a helm upgrade: raft and shard membership are persisted, and removing Pods does not reconfigure them. Follow the manual scale-down procedure in the README (Scaling), which ends by scaling the live StatefulSet; the upgrade passes once the live replicas match the value" $comp $liveReplicas $desired) -}} +{{- end -}} +{{/* +PD raft membership is the persisted voting configuration, and the peer list +the chart renders reaches it only as NodeOptions.setInitialConf, which jraft +applies when bootstrapping a node that has no configuration of its own. On an +initialized group, adding Pods adds non-voting strangers: the extra PD starts, +the peer list changes, and the voting configuration does not. PD exposes the +change through RaftEngine.changePeerList, reachable from the PD client API but +from no REST route, so the chart cannot perform it and does not pretend to. +Growing Store is ordinary scale-out and stays allowed. +*/}} +{{- if and (eq $comp "pd") (gt $liveReplicas 0) (gt $desired $liveReplicas) -}} +{{- fail (printf "pd.replicas cannot grow from %d to %d through a helm upgrade: the rendered peer list reaches raft only as the initial configuration, so new Pods would start without joining the voting configuration. Change the persisted membership through PD first, then scale the live StatefulSet, then upgrade with the matching value; the README (Scaling) has the procedure and its limits. A fresh install at any replica count is unaffected" $liveReplicas $desired) -}} +{{- end -}} +{{- end -}} +{{- end -}} +{{/* +Only validate minAvailable where a PDB is actually rendered. The pd/store PDB +templates require replicas > 1, so a single-replica release never creates one +and must not be failed for a value that has no effect. +*/}} +{{- if and .Values.pd.pdb.enabled (gt (int .Values.pd.replicas) 1) (ge (int .Values.pd.pdb.minAvailable) (int .Values.pd.replicas)) -}} +{{- fail "pd.pdb.minAvailable must be less than pd.replicas, otherwise the PDB permanently blocks voluntary disruptions such as node drains" -}} +{{- end -}} +{{- if and .Values.pd.pdb.enabled (gt (int .Values.pd.replicas) 1) (lt (int .Values.pd.pdb.minAvailable) (include "hugegraph.pd.quorum" . | int)) -}} +{{- fail "pd.pdb.minAvailable must be at least the PD Raft majority, floor(replicas/2)+1, otherwise the budget permits voluntary disruptions that drop PD below quorum. Note a PDB only limits voluntary disruption such as drains and evictions; it cannot protect quorum from node failure" -}} +{{- end -}} +{{- if and .Values.store.pdb.enabled (gt (int .Values.store.replicas) 1) (ge (int .Values.store.pdb.minAvailable) (int .Values.store.replicas)) -}} +{{- fail "store.pdb.minAvailable must be less than store.replicas, otherwise the PDB permanently blocks voluntary disruptions such as node drains" -}} +{{- end -}} +{{- if and .Values.store.pdb.enabled (gt (int .Values.store.replicas) 1) (lt (int .Values.store.pdb.minAvailable) (sub (int .Values.store.replicas) 1)) -}} +{{- fail "store.pdb.minAvailable must be at least store.replicas - 1: each shard keeps its copies on a subset of the Stores, so permitting more than one concurrent voluntary eviction can remove a shard majority regardless of the Store count" -}} +{{- end -}} +{{/* +PD -D system properties must be empty or a positive integer. The schema +enforces the types; these checks add a named failure for zero, negative, and +nonsense values, and check an explicit shard count against PD's real +constraints: PD's config API accepts only odd shard counts, PD clamps a +count of 2 to 1 (two shards cannot elect a leader), and PD clamps the +effective count to the number of live stores. All lookups tolerate absent +keys for releases stored before the values existed. +*/}} +{{- $pdPartition := get .Values.pd "partition" | default dict -}} +{{- $pdProps := dict + "pd.partition.defaultShardCount" (include "hugegraph.optionalScalar" (get $pdPartition "defaultShardCount")) + "pd.partition.storeMaxShardCount" (include "hugegraph.optionalScalar" (get $pdPartition "storeMaxShardCount")) -}} +{{- range $label, $raw := $pdProps -}} +{{- if and (ne $raw "") (or (not (regexMatch "^[0-9]+$" $raw)) (eq (int $raw) 0)) -}} +{{- fail (printf "%s must be empty or a positive integer" $label) -}} +{{- end -}} +{{- end -}} +{{- $explicitShards := include "hugegraph.optionalScalar" (get $pdPartition "defaultShardCount") -}} +{{- if regexMatch "^[1-9][0-9]*$" $explicitShards -}} +{{- if eq (mod (int $explicitShards) 2) 0 -}} +{{- fail "pd.partition.defaultShardCount must be odd: PD's config API rejects even shard counts, and PD clamps a bootstrap value of 2 to 1 because two shards cannot elect a leader" -}} +{{- end -}} +{{- if gt (int $explicitShards) (int .Values.store.replicas) -}} +{{- fail "pd.partition.defaultShardCount is greater than store.replicas: PD would clamp the effective shard count to the number of live stores, so the extra replicas would silently never be placed. Raise store.replicas or lower the shard count" -}} +{{- end -}} +{{- end -}} +{{- $svc := get .Values.server "service" | default dict -}} +{{- if and (get $svc "nodePort") (not (has (get $svc "type" | default "ClusterIP") (list "NodePort" "LoadBalancer"))) -}} +{{- fail "server.service.nodePort requires server.service.type to be NodePort or LoadBalancer" -}} +{{- end -}} +{{- $advertiseUrl := trim (default "" .Values.server.advertiseUrl) -}} +{{- if and $advertiseUrl (not (or (hasPrefix "http://" $advertiseUrl) (hasPrefix "https://" $advertiseUrl))) -}} +{{- fail "server.advertiseUrl must be an absolute http:// or https:// URL when set" -}} +{{- end -}} +{{- $pdSvc := get .Values.pd "service" | default dict -}} +{{- $pdSvcType := get $pdSvc "type" | default "ClusterIP" -}} +{{- if and (or (get $pdSvc "restNodePort") (get $pdSvc "grpcNodePort")) (not (has $pdSvcType (list "NodePort" "LoadBalancer"))) -}} +{{- fail "pd.service.restNodePort and pd.service.grpcNodePort require pd.service.type to be NodePort or LoadBalancer" -}} +{{- end -}} +{{- if and (ne $pdSvcType "ClusterIP") (not (get $pdSvc "allowInsecureExposure" | default false)) -}} +{{- fail "pd.service.type NodePort or LoadBalancer exposes PD's unauthenticated gRPC port outside the cluster, raft membership RPCs included; keep ClusterIP, or set pd.service.allowInsecureExposure=true once reachability is restricted by other means (NetworkPolicy, load balancer allowlist, firewall)" -}} +{{- end -}} +{{- $serverPdb := get .Values.server "pdb" | default dict -}} +{{- $serverReplicaFloor := include "hugegraph.server.replicaFloor" . | int -}} +{{- if and (get $serverPdb "enabled" | default false) (gt $serverReplicaFloor 1) (ge (int (get $serverPdb "minAvailable" | default 1)) $serverReplicaFloor) -}} +{{- fail "server.pdb.minAvailable must be less than the active Server replica floor (server.hpa.minReplicas when HPA is enabled, otherwise server.replicas), otherwise the PDB permanently blocks voluntary disruptions such as node drains" -}} +{{- end -}} +{{- $serverIngress := get .Values.server "ingress" | default dict -}} +{{- if and (get $serverIngress "enabled" | default false) (empty (get $serverIngress "tls")) (not (get $serverIngress "allowPlainHttp" | default false)) -}} +{{- fail "server.ingress.enabled without tls publishes Basic-auth credentials and JWTs over plain HTTP; configure server.ingress.tls, or set server.ingress.allowPlainHttp=true to accept that on a trusted network" -}} +{{- end -}} +{{/* +extraEnv entries render after the chart-owned variables and Kubernetes lets +the last duplicate win, so a duplicate name would silently override a +validated contract (for example re-enabling init-store across Server +replicas). Reserved names are rejected instead. JAVA_OPTIONS is reserved +for pd, store, and server because each component's start script skips its +automatic heap sizing and drops the chart's JAVA_OPTS flags entirely when +JAVA_OPTIONS arrives preset in the environment (verified in +start-hugegraph-pd.sh, start-hugegraph-store.sh, and hugegraph-server.sh). +*/}} +{{- $reservedEnv := dict + "pd" (list "HG_PD_GRPC_HOST" "HG_PD_GRPC_PORT" "HG_PD_REST_PORT" "HG_PD_RAFT_ADDRESS" "HG_PD_RAFT_PEERS_LIST" "HG_PD_INITIAL_STORE_LIST" "HG_PD_INITIAL_STORE_COUNT" "HG_PD_DATA_PATH" "HG_PD_AUTH_SECRET_KEY" "JAVA_OPTS" "JAVA_OPTIONS") + "store" (list "HG_STORE_PD_ADDRESS" "HG_STORE_GRPC_HOST" "HG_STORE_GRPC_PORT" "HG_STORE_REST_PORT" "HG_STORE_RAFT_ADDRESS" "HG_STORE_DATA_PATH" "JAVA_OPTS" "JAVA_OPTIONS") + "server" (list "POD_IP" "HG_SERVER_BACKEND" "HG_SERVER_PD_PEERS" "HG_SERVER_PD_REST_ENDPOINT" "STORE_REST" "HG_SERVER_INIT_STORE_ENABLED" "HG_SERVER_URLS_TO_PD" "HG_SERVER_STARTUP_TIMEOUT_S" "PD_AUTH_PASSWORD" "PASSWORD" "HG_SERVER_AUTH_TOKEN_SECRET" "JAVA_OPTS" "JAVA_OPTIONS") + "hubble" (list "HG_HUBBLE_PD_PEERS" "HG_HUBBLE_PD_SERVER" "HG_HUBBLE_PD_PASSWORD" "HG_HUBBLE_STORE_TARGETS" "HG_HUBBLE_SERVER_URL" "SPRING_DATASOURCE_URL") -}} +{{- range $component, $reserved := $reservedEnv -}} +{{- $componentValues := get $.Values $component | default dict -}} +{{- range $entry := get $componentValues "extraEnv" | default list -}} +{{- if has (get $entry "name") $reserved -}} +{{- fail (printf "%s.extraEnv must not set the chart-managed variable %s" $component (get $entry "name")) -}} +{{- end -}} +{{- end -}} +{{- end -}} +{{- $hubble := get .Values "hubble" | default dict -}} +{{- if get $hubble "enabled" | default false -}} +{{- if and (not .Values.server.auth.enabled) (not (get $hubble "allowWithoutServerAuth" | default false)) -}} +{{- fail "hubble.enabled requires server.auth: current Hubble images authenticate against the cluster and cannot complete their login on an auth-less deployment. Enable server.auth, or set hubble.allowWithoutServerAuth=true for images that support it" -}} +{{- end -}} +{{- $hubbleSvc := get $hubble "service" | default dict -}} +{{- if and (get $hubbleSvc "nodePort") (not (has (get $hubbleSvc "type" | default "ClusterIP") (list "NodePort" "LoadBalancer"))) -}} +{{- fail "hubble.service.nodePort requires hubble.service.type to be NodePort or LoadBalancer" -}} +{{- end -}} +{{- $hubbleImage := get $hubble "image" | default dict -}} +{{- if and (eq (trim (get $hubbleImage "tag" | default "")) "") (eq (trim (get $hubbleImage "digest" | default "")) "") -}} +{{- fail "hubble.image needs a tag or a digest: the chart appVersion tracks the Server release, not Hubble, so there is no meaningful fallback" -}} +{{- end -}} +{{- if get (get $hubble "securityContext" | default dict) "readOnlyRootFilesystem" | default false -}} +{{- fail "hubble.securityContext.readOnlyRootFilesystem=true breaks Hubble: its wrapper writes conf/hugegraph-hubble.properties inside the image at startup and the chart mounts no writable volume there" -}} +{{- end -}} +{{- $hubbleIngress := get $hubble "ingress" | default dict -}} +{{- if and (get $hubbleIngress "enabled" | default false) (empty (get $hubbleIngress "tls")) (not (get $hubbleIngress "allowPlainHttp" | default false)) -}} +{{- fail "hubble.ingress.enabled without tls publishes the plain-HTTP, unauthenticated Hubble UI; configure hubble.ingress.tls, or set hubble.ingress.allowPlainHttp=true to accept that on a trusted network" -}} +{{- end -}} +{{- end -}} +{{- $pdAuth := get .Values.pd "auth" | default dict -}} +{{/* A values set with no pd.auth block at all (a release stored before the + field existed, replayed by --reuse-values) gets the chart default, + autoGenerate; only an explicit autoGenerate=false with nothing else set + is an error. */}} +{{- $pdAutoGen := ternary (get $pdAuth "autoGenerate") true (hasKey $pdAuth "autoGenerate") -}} +{{- if and (not (get $pdAuth "existingSecret" | default "")) (not (get $pdAuth "value" | default "")) (not $pdAutoGen) -}} +{{- fail "pd.auth requires existingSecret, value, or autoGenerate=true: PD refuses to start without a REST secret" -}} +{{- end -}} +{{- $auth := get .Values.server "auth" | default dict -}} +{{- $admin := get $auth "admin" | default dict -}} +{{- $token := get $auth "token" | default dict -}} +{{- if get $auth "enabled" | default false -}} +{{- if and (not (get $admin "existingSecret" | default "")) (not (get $admin "password" | default "")) (not (get $admin "autoGenerate" | default false)) -}} +{{- fail "server.auth.admin requires existingSecret, password, or autoGenerate=true when auth is enabled" -}} +{{- end -}} +{{- if and (not (get $token "existingSecret" | default "")) (not (get $token "value" | default "")) (not (get $token "autoGenerate" | default false)) -}} +{{- fail "server.auth.token requires existingSecret, value, or autoGenerate=true when auth is enabled" -}} +{{- end -}} +{{- end -}} +{{/* NetworkPolicy exposure check runs last, so an exposure the other checks + refuse (allowInsecureExposure, Ingress TLS) is reported first. */}} +{{- $networkPolicy := get .Values "networkPolicy" | default dict -}} +{{- if get $networkPolicy "enabled" -}} +{{- $exposed := dict + "pd" (ne .Values.pd.service.type "ClusterIP") + "server" (or (ne .Values.server.service.type "ClusterIP") .Values.server.ingress.enabled (ne (trim (default "" .Values.server.advertiseUrl)) "")) + "hubble" (and .Values.hubble.enabled (or (ne .Values.hubble.service.type "ClusterIP") .Values.hubble.ingress.enabled)) -}} +{{- range $comp := list "pd" "server" "hubble" -}} +{{- if and (get $exposed $comp) (empty (get (get $networkPolicy $comp | default dict) "extraIngress")) -}} +{{- fail (printf "networkPolicy.enabled admits nothing from outside the release, so the %s exposure (NodePort/LoadBalancer Service, Ingress%s) is unreachable; list its callers in networkPolicy.%s.extraIngress, for example the Ingress controller's namespace or a client CIDR" $comp (ternary ", server.advertiseUrl" "" (eq $comp "server")) $comp) -}} +{{- end -}} +{{- end -}} +{{- end -}} +{{- end }} + +{{/* +podAntiAffinity snippet for a component label key. +mode: required | preferred | disabled +*/}} +{{/* +NetworkPolicy building blocks: a same-release peer by component, a TCP port +list, and DNS egress by port only, so it works wherever the cluster runs its +resolver (CoreDNS in any namespace, NodeLocal DNSCache). +*/}} +{{- define "hugegraph.netpol.peer" -}} +- podSelector: + matchLabels: + {{- include "hugegraph.selectorLabels" .root | nindent 6 }} + app.kubernetes.io/component: {{ .component }} +{{- end }} + +{{- define "hugegraph.netpol.ports" -}} +{{- $rules := list -}} +{{- range . -}} +{{- $rules = append $rules (printf "- protocol: TCP\n port: %d" (int .)) -}} +{{- end -}} +{{- join "\n" $rules -}} +{{- end }} + +{{- define "hugegraph.netpol.dns" -}} +- ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 +{{- end }} + +{{- define "hugegraph.antiAffinity" -}} +{{- $mode := .mode -}} +{{- $component := .component -}} +{{- $labels := .labels -}} +{{- if eq $mode "required" }} +affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + {{- toYaml $labels | nindent 12 }} + app.kubernetes.io/component: {{ $component }} + topologyKey: kubernetes.io/hostname +{{- else if eq $mode "preferred" }} +affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchLabels: + {{- toYaml $labels | nindent 14 }} + app.kubernetes.io/component: {{ $component }} + topologyKey: kubernetes.io/hostname +{{- end }} +{{- end }} + +{{/* +Render a container image reference. An explicit image.digest pins immutably and wins +over tag; otherwise fall back to tag, then to the chart appVersion. Takes a dict of +(image, appVersion). +*/}} +{{- define "hugegraph.image" -}} +{{- $img := .image -}} +{{- $digest := trim (get $img "digest" | default "") -}} +{{- if ne $digest "" -}} +{{- printf "%s@%s" $img.repository $digest -}} +{{- else -}} +{{- printf "%s:%s" $img.repository (default .appVersion $img.tag) -}} +{{- end -}} +{{- end }} diff --git a/helm/hugegraph/templates/hubble-deployment.yaml b/helm/hugegraph/templates/hubble-deployment.yaml new file mode 100644 index 0000000000..3f13339342 --- /dev/null +++ b/helm/hugegraph/templates/hubble-deployment.yaml @@ -0,0 +1,292 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- if get (get .Values "hubble" | default dict) "enabled" | default false }} +{{- include "hugegraph.validateValues" . }} +{{- $customPort := ne (int .Values.hubble.port) 8088 }} +{{- $mode := .Values.hubble.mode | default "pd" }} +{{- if not (has $mode (list "pd" "direct")) }} +{{- fail "hubble.mode must be one of: pd, direct" }} +{{- end }} +{{- $pdMode := eq $mode "pd" }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "hugegraph.hubble.name" . }} + labels: + {{- include "hugegraph.labels" . | nindent 4 }} + app.kubernetes.io/component: hubble +spec: + # Hubble keeps its connection metadata in an embedded per-instance H2 + # database, so this Deployment is fixed at one replica and replaces the Pod + # instead of rolling, which also keeps a persistent volume single-attached. + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + {{- include "hugegraph.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: hubble + template: + metadata: + labels: + {{- include "hugegraph.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: hubble + {{- with .Values.hubble.podLabels }}{{ toYaml . | nindent 8 }}{{- end }} + annotations: + {{- if $pdMode }} + # Rolls Hubble when the PD REST Secret written into its properties changes. + checksum/pd-auth: {{ include "hugegraph.pd.authChecksum" . | quote }} + {{- end }} + {{- with .Values.hubble.podAnnotations }}{{ toYaml . | nindent 8 }}{{- end }} + spec: + automountServiceAccountToken: {{ get (get .Values.hubble "serviceAccount" | default dict) "automountServiceAccountToken" | default false }} + serviceAccountName: {{ include "hugegraph.serviceAccountName" (dict "component" .Values.hubble "name" (include "hugegraph.hubble.name" .) ) }} + {{- if hasKey .Values.hubble "terminationGracePeriodSeconds" }} + terminationGracePeriodSeconds: {{ .Values.hubble.terminationGracePeriodSeconds }} + {{- end }} + {{- with .Values.hubble.priorityClassName }} + priorityClassName: {{ . | quote }} + {{- end }} + {{- with .Values.hubble.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.hubble.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.hubble.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.hubble.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.hubble.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: hubble + image: {{ include "hugegraph.image" (dict "image" .Values.hubble.image "appVersion" $.Chart.AppVersion) | quote }} + imagePullPolicy: {{ .Values.hubble.image.pullPolicy }} + {{- with .Values.hubble.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + # The Hubble image reads conf/hugegraph-hubble.properties only; it + # has no environment mapping for these keys, so the wrapper rewrites + # them before handing off to the image's own start script. The image + # ships no dumb-init; start-hubble.sh -f execs java, which then + # receives signals directly. + command: + - /bin/bash + - -c + args: + - | + set -euo pipefail + CONF=./conf/hugegraph-hubble.properties + if [[ ! -r "${CONF}" ]]; then + echo "missing or unreadable ${CONF}; refusing to write a stub config" >&2 + exit 1 + fi + TMP=$(mktemp) + FOUND_PD_ENABLED=false + {{- if $pdMode }} + FOUND_PD_PEERS=false + FOUND_PD_SERVER=false + FOUND_PD_USERNAME=false + FOUND_PD_PASSWORD=false + FOUND_STORE_TARGETS=false + {{- else }} + FOUND_DIRECT_URL=false + {{- end }} + FOUND_SERVER_HOST=false + {{- if $customPort }} + FOUND_HUBBLE_PORT=false + FOUND_SERVER_PORT=false + {{- end }} + while IFS= read -r LINE || [[ -n "${LINE}" ]]; do + case "${LINE}" in + pd.enabled=*) + printf 'pd.enabled={{ $pdMode }}\n' >>"${TMP}" + FOUND_PD_ENABLED=true + ;; + {{- if $pdMode }} + pd.peers=*) + printf 'pd.peers=%s\n' "${HG_HUBBLE_PD_PEERS}" >>"${TMP}" + FOUND_PD_PEERS=true + ;; + pd.server=*) + printf 'pd.server=%s\n' "${HG_HUBBLE_PD_SERVER}" >>"${TMP}" + FOUND_PD_SERVER=true + ;; + operations.pd.username=*) + printf 'operations.pd.username=hubble\n' >>"${TMP}" + FOUND_PD_USERNAME=true + ;; + operations.pd.password=*) + printf 'operations.pd.password=%s\n' "${HG_HUBBLE_PD_PASSWORD}" >>"${TMP}" + FOUND_PD_PASSWORD=true + ;; + operations.store.allowed_targets=*) + printf 'operations.store.allowed_targets=%s\n' \ + "${HG_HUBBLE_STORE_TARGETS}" >>"${TMP}" + FOUND_STORE_TARGETS=true + ;; + {{- else }} + server.direct_url=*) + printf 'server.direct_url=%s\n' "${HG_HUBBLE_SERVER_URL}" >>"${TMP}" + FOUND_DIRECT_URL=true + ;; + {{- end }} + server.host=*) + printf 'server.host=0.0.0.0\n' >>"${TMP}" + FOUND_SERVER_HOST=true + ;; + {{- if $customPort }} + hubble.port=*) + printf 'hubble.port=%s\n' {{ .Values.hubble.port | quote }} >>"${TMP}" + FOUND_HUBBLE_PORT=true + ;; + server.port=*) + printf 'server.port=%s\n' {{ .Values.hubble.port | quote }} >>"${TMP}" + FOUND_SERVER_PORT=true + ;; + {{- end }} + *) + printf '%s\n' "${LINE}" >>"${TMP}" + ;; + esac + done <"${CONF}" + if [[ "${FOUND_PD_ENABLED}" == false ]]; then + printf 'pd.enabled={{ $pdMode }}\n' >>"${TMP}" + fi + {{- if $pdMode }} + if [[ "${FOUND_PD_PEERS}" == false ]]; then + printf 'pd.peers=%s\n' "${HG_HUBBLE_PD_PEERS}" >>"${TMP}" + fi + if [[ "${FOUND_PD_SERVER}" == false ]]; then + printf 'pd.server=%s\n' "${HG_HUBBLE_PD_SERVER}" >>"${TMP}" + fi + # PD REST credential for the operations API. + if [[ "${FOUND_PD_USERNAME}" == false ]]; then + printf 'operations.pd.username=hubble\n' >>"${TMP}" + fi + if [[ "${FOUND_PD_PASSWORD}" == false ]]; then + printf 'operations.pd.password=%s\n' "${HG_HUBBLE_PD_PASSWORD}" >>"${TMP}" + fi + if [[ "${FOUND_STORE_TARGETS}" == false ]]; then + printf 'operations.store.allowed_targets=%s\n' \ + "${HG_HUBBLE_STORE_TARGETS}" >>"${TMP}" + fi + {{- else }} + if [[ "${FOUND_DIRECT_URL}" == false ]]; then + printf 'server.direct_url=%s\n' "${HG_HUBBLE_SERVER_URL}" >>"${TMP}" + fi + {{- end }} + # Current Hubble binds server.host, which defaults to + # localhost; the shipped conf's hubble.host=0.0.0.0 line is + # legacy, is ignored by current images, and is preserved + # verbatim above for images that still read it. + if [[ "${FOUND_SERVER_HOST}" == false ]]; then + printf 'server.host=0.0.0.0\n' >>"${TMP}" + fi + {{- if $customPort }} + if [[ "${FOUND_HUBBLE_PORT}" == false ]]; then + printf 'hubble.port=%s\n' {{ .Values.hubble.port | quote }} >>"${TMP}" + fi + if [[ "${FOUND_SERVER_PORT}" == false ]]; then + printf 'server.port=%s\n' {{ .Values.hubble.port | quote }} >>"${TMP}" + fi + {{- end }} + chmod 600 "${TMP}" + mv "${TMP}" "${CONF}" + exec ./bin/start-hubble.sh -f + ports: + - name: http + containerPort: {{ .Values.hubble.port }} + env: + {{- if $pdMode }} + - name: HG_HUBBLE_PD_PEERS + value: {{ include "hugegraph.pd.grpcPeersList" . | quote }} + - name: HG_HUBBLE_PD_SERVER + value: {{ include "hugegraph.pd.restClientEndpoint" . | quote }} + - name: HG_HUBBLE_PD_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "hugegraph.pd.authSecretName" . | quote }} + key: {{ include "hugegraph.pd.authSecretKey" . | quote }} + - name: HG_HUBBLE_STORE_TARGETS + value: {{ include "hugegraph.store.restOriginsList" . | quote }} + {{- else }} + - name: HG_HUBBLE_SERVER_URL + value: {{ include "hugegraph.server.clientUrl" . | quote }} + {{- end }} + {{- if .Values.hubble.persistence.enabled }} + # Hubble's H2 location is fixed to ./db inside the image classpath + # config; Spring Boot's environment binding is the supported way to + # point it into the mounted volume. + - name: SPRING_DATASOURCE_URL + value: "jdbc:h2:file:/hubble-data/db;DB_CLOSE_ON_EXIT=FALSE" + {{- end }} + {{- with .Values.hubble.extraEnv }}{{ toYaml . | nindent 12 }}{{- end }} + {{- if .Values.hubble.persistence.enabled }} + volumeMounts: + - name: data + mountPath: /hubble-data + {{- end }} + startupProbe: + httpGet: + path: /actuator/health + port: http + failureThreshold: {{ .Values.hubble.probes.startup.failureThreshold }} + periodSeconds: {{ .Values.hubble.probes.startup.periodSeconds }} + {{- with include "hugegraph.probeTuning" .Values.hubble.probes.startup }}{{ . | trim | nindent 12 }}{{- end }} + readinessProbe: + httpGet: + path: /actuator/health + port: http + periodSeconds: {{ .Values.hubble.probes.readiness.periodSeconds }} + failureThreshold: {{ .Values.hubble.probes.readiness.failureThreshold }} + {{- with include "hugegraph.probeTuning" .Values.hubble.probes.readiness }}{{ . | trim | nindent 12 }}{{- end }} + # TCP for liveness: /actuator/health aggregates datasource health, + # and a transiently slow persistent volume must not get a healthy + # JVM killed (Recreate + RWO makes an overlapping restart worse). + livenessProbe: + tcpSocket: + port: http + periodSeconds: {{ .Values.hubble.probes.liveness.periodSeconds }} + failureThreshold: {{ .Values.hubble.probes.liveness.failureThreshold }} + {{- with include "hugegraph.probeTuning" .Values.hubble.probes.liveness }}{{ . | trim | nindent 12 }}{{- end }} + {{- with .Values.hubble.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if .Values.hubble.persistence.enabled }} + volumes: + - name: data + persistentVolumeClaim: + claimName: {{ include "hugegraph.hubble.dataName" . }} + {{- end }} +{{- end }} diff --git a/helm/hugegraph/templates/hubble-ingress.yaml b/helm/hugegraph/templates/hubble-ingress.yaml new file mode 100644 index 0000000000..845730619d --- /dev/null +++ b/helm/hugegraph/templates/hubble-ingress.yaml @@ -0,0 +1,54 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- $hubble := get .Values "hubble" | default dict }} +{{- if and (get $hubble "enabled" | default false) .Values.hubble.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "hugegraph.hubble.name" . }} + labels: + {{- include "hugegraph.labels" . | nindent 4 }} + app.kubernetes.io/component: hubble + {{- with (get .Values.hubble.ingress "annotations") }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.hubble.ingress.className }} + ingressClassName: {{ .Values.hubble.ingress.className | quote }} + {{- end }} + {{- with .Values.hubble.ingress.tls }} + tls: + {{- toYaml . | nindent 4 }} + {{- end }} + rules: + {{- range .Values.hubble.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path | quote }} + pathType: {{ .pathType }} + backend: + service: + name: {{ include "hugegraph.hubble.name" $ }} + port: + number: {{ $.Values.hubble.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/helm/hugegraph/templates/hubble-pvc.yaml b/helm/hugegraph/templates/hubble-pvc.yaml new file mode 100644 index 0000000000..8143fab844 --- /dev/null +++ b/helm/hugegraph/templates/hubble-pvc.yaml @@ -0,0 +1,41 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- $hubble := get .Values "hubble" | default dict }} +{{- if and (get $hubble "enabled" | default false) .Values.hubble.persistence.enabled }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "hugegraph.hubble.dataName" . }} + labels: + {{- include "hugegraph.labels" . | nindent 4 }} + app.kubernetes.io/component: hubble + # Survive helm uninstall, matching the PD/Store volumeClaimTemplates + # behavior; delete the PVC explicitly to discard the stored connection + # metadata and credentials. + annotations: + helm.sh/resource-policy: keep +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.hubble.persistence.size }} + {{- if .Values.hubble.persistence.storageClassName }} + storageClassName: {{ .Values.hubble.persistence.storageClassName | quote }} + {{- end }} +{{- end }} diff --git a/helm/hugegraph/templates/hubble-service.yaml b/helm/hugegraph/templates/hubble-service.yaml new file mode 100644 index 0000000000..53eed8ec4a --- /dev/null +++ b/helm/hugegraph/templates/hubble-service.yaml @@ -0,0 +1,43 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- if get (get .Values "hubble" | default dict) "enabled" | default false }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "hugegraph.hubble.name" . }} + labels: + {{- include "hugegraph.labels" . | nindent 4 }} + app.kubernetes.io/component: hubble + {{- $svc := get .Values.hubble "service" | default dict }} + {{- with (get $svc "annotations") }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ get $svc "type" | default "ClusterIP" }} + selector: + {{- include "hugegraph.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: hubble + ports: + - name: http + port: {{ .Values.hubble.port }} + targetPort: http + {{- with (get $svc "nodePort") }} + nodePort: {{ . }} + {{- end }} +{{- end }} diff --git a/helm/hugegraph/templates/networkpolicy.yaml b/helm/hugegraph/templates/networkpolicy.yaml new file mode 100644 index 0000000000..9b20c0afb1 --- /dev/null +++ b/helm/hugegraph/templates/networkpolicy.yaml @@ -0,0 +1,199 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- /* +One NetworkPolicy per component. Each object isolates its own Pods in both +directions and carries all of their allows, so no object ever denies a Pod +before that Pod's allows exist, whatever order the objects are applied in. +Peers match the release and component labels; ports are the values the +containers bind. The helm test Pod is selected by no policy, so its egress +stays open and the Server policy admits it. Nothing outside the release is +admitted unless .extraIngress names it; exposing a component +without such rules fails in validateValues instead of opening its ports. +*/}} +{{- $np := get .Values "networkPolicy" | default dict }} +{{- if get $np "enabled" }} +{{- $pd := .Values.pd.ports }} +{{- $store := .Values.store.ports }} +{{- $serverPort := .Values.server.port }} +{{- $hubbleEnabled := .Values.hubble.enabled }} +{{- $hubblePd := and $hubbleEnabled (eq .Values.hubble.mode "pd") }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "hugegraph.pd.name" . }} + labels: + {{- include "hugegraph.labels" . | nindent 4 }} + app.kubernetes.io/component: pd +spec: + podSelector: + matchLabels: + {{- include "hugegraph.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: pd + policyTypes: + - Ingress + - Egress + ingress: + - from: + {{- include "hugegraph.netpol.peer" (dict "root" . "component" "pd") | nindent 8 }} + ports: + {{- include "hugegraph.netpol.ports" (list $pd.raft $pd.grpc) | nindent 8 }} + - from: + {{- include "hugegraph.netpol.peer" (dict "root" . "component" "store") | nindent 8 }} + {{- include "hugegraph.netpol.peer" (dict "root" . "component" "server") | nindent 8 }} + {{- if $hubblePd }} + {{- include "hugegraph.netpol.peer" (dict "root" . "component" "hubble") | nindent 8 }} + {{- end }} + ports: + {{- include "hugegraph.netpol.ports" (list $pd.grpc $pd.rest) | nindent 8 }} + {{- with get (get $np "pd" | default dict) "extraIngress" }} + {{- toYaml . | nindent 4 }} + {{- end }} + egress: + - to: + {{- include "hugegraph.netpol.peer" (dict "root" . "component" "pd") | nindent 8 }} + ports: + {{- include "hugegraph.netpol.ports" (list $pd.raft $pd.grpc) | nindent 8 }} + {{- include "hugegraph.netpol.dns" . | nindent 4 }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "hugegraph.store.name" . }} + labels: + {{- include "hugegraph.labels" . | nindent 4 }} + app.kubernetes.io/component: store +spec: + podSelector: + matchLabels: + {{- include "hugegraph.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: store + policyTypes: + - Ingress + - Egress + ingress: + - from: + {{- include "hugegraph.netpol.peer" (dict "root" . "component" "store") | nindent 8 }} + ports: + {{- include "hugegraph.netpol.ports" (list $store.raft) | nindent 8 }} + - from: + {{- include "hugegraph.netpol.peer" (dict "root" . "component" "server") | nindent 8 }} + ports: + {{- include "hugegraph.netpol.ports" (list $store.grpc $store.rest) | nindent 8 }} + {{- if $hubblePd }} + - from: + {{- include "hugegraph.netpol.peer" (dict "root" . "component" "hubble") | nindent 8 }} + ports: + {{- include "hugegraph.netpol.ports" (list $store.rest) | nindent 8 }} + {{- end }} + {{- with get (get $np "store" | default dict) "extraIngress" }} + {{- toYaml . | nindent 4 }} + {{- end }} + egress: + - to: + {{- include "hugegraph.netpol.peer" (dict "root" . "component" "pd") | nindent 8 }} + ports: + {{- include "hugegraph.netpol.ports" (list $pd.grpc $pd.rest) | nindent 8 }} + - to: + {{- include "hugegraph.netpol.peer" (dict "root" . "component" "store") | nindent 8 }} + ports: + {{- include "hugegraph.netpol.ports" (list $store.raft) | nindent 8 }} + {{- include "hugegraph.netpol.dns" . | nindent 4 }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "hugegraph.server.name" . }} + labels: + {{- include "hugegraph.labels" . | nindent 4 }} + app.kubernetes.io/component: server +spec: + podSelector: + matchLabels: + {{- include "hugegraph.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: server + policyTypes: + - Ingress + - Egress + ingress: + - from: + {{- include "hugegraph.netpol.peer" (dict "root" . "component" "test") | nindent 8 }} + {{- if $hubbleEnabled }} + {{- include "hugegraph.netpol.peer" (dict "root" . "component" "hubble") | nindent 8 }} + {{- end }} + ports: + {{- include "hugegraph.netpol.ports" (list $serverPort) | nindent 8 }} + {{- with get (get $np "server" | default dict) "extraIngress" }} + {{- toYaml . | nindent 4 }} + {{- end }} + egress: + - to: + {{- include "hugegraph.netpol.peer" (dict "root" . "component" "pd") | nindent 8 }} + ports: + {{- include "hugegraph.netpol.ports" (list $pd.grpc $pd.rest) | nindent 8 }} + - to: + {{- include "hugegraph.netpol.peer" (dict "root" . "component" "store") | nindent 8 }} + ports: + {{- include "hugegraph.netpol.ports" (list $store.grpc $store.rest) | nindent 8 }} + {{- include "hugegraph.netpol.dns" . | nindent 4 }} +{{- if $hubbleEnabled }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "hugegraph.hubble.name" . }} + labels: + {{- include "hugegraph.labels" . | nindent 4 }} + app.kubernetes.io/component: hubble +spec: + podSelector: + matchLabels: + {{- include "hugegraph.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: hubble + policyTypes: + - Ingress + - Egress + {{- with get (get $np "hubble" | default dict) "extraIngress" }} + ingress: + {{- toYaml . | nindent 4 }} + {{- else }} + # No ingress rules under policyTypes Ingress admits nothing. The key is left + # out rather than rendered empty, because the API server drops an empty list + # and Helm would then rewrite the object on every upgrade. kubectl + # port-forward reaches the Pod over loopback, which no policy filters. + {{- end }} + egress: + - to: + {{- include "hugegraph.netpol.peer" (dict "root" . "component" "server") | nindent 8 }} + ports: + {{- include "hugegraph.netpol.ports" (list $serverPort) | nindent 8 }} + {{- if $hubblePd }} + - to: + {{- include "hugegraph.netpol.peer" (dict "root" . "component" "pd") | nindent 8 }} + ports: + {{- include "hugegraph.netpol.ports" (list $pd.grpc $pd.rest) | nindent 8 }} + - to: + {{- include "hugegraph.netpol.peer" (dict "root" . "component" "store") | nindent 8 }} + ports: + {{- include "hugegraph.netpol.ports" (list $store.rest) | nindent 8 }} + {{- end }} + {{- include "hugegraph.netpol.dns" . | nindent 4 }} + {{- with get (get $np "hubble" | default dict) "extraEgress" }} + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} +{{- end }} diff --git a/helm/hugegraph/templates/pd-auth-secret.yaml b/helm/hugegraph/templates/pd-auth-secret.yaml new file mode 100644 index 0000000000..66a484bdf5 --- /dev/null +++ b/helm/hugegraph/templates/pd-auth-secret.yaml @@ -0,0 +1,32 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- $auth := get .Values.pd "auth" | default dict -}} +{{- $autoGen := ternary (get $auth "autoGenerate") true (hasKey $auth "autoGenerate") -}} +{{- if and (not (get $auth "existingSecret" | default "")) (or (get $auth "value" | default "") $autoGen) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "hugegraph.pd.authSecretName" . }} + labels: + {{- include "hugegraph.labels" . | nindent 4 }} + annotations: + helm.sh/resource-policy: keep +type: Opaque +data: + {{ include "hugegraph.pd.authSecretKey" . | quote }}: {{ include "hugegraph.pd.authSecretValue" . | quote }} +{{- end }} diff --git a/helm/hugegraph/templates/pd-pdb.yaml b/helm/hugegraph/templates/pd-pdb.yaml new file mode 100644 index 0000000000..a0d93a098e --- /dev/null +++ b/helm/hugegraph/templates/pd-pdb.yaml @@ -0,0 +1,32 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- if and .Values.pd.pdb.enabled (gt (int .Values.pd.replicas) 1) }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "hugegraph.pd.name" . }} + labels: + {{- include "hugegraph.labels" . | nindent 4 }} + app.kubernetes.io/component: pd +spec: + minAvailable: {{ .Values.pd.pdb.minAvailable }} + selector: + matchLabels: + {{- include "hugegraph.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: pd +{{- end }} diff --git a/helm/hugegraph/templates/pd-service-client.yaml b/helm/hugegraph/templates/pd-service-client.yaml new file mode 100644 index 0000000000..a25108b511 --- /dev/null +++ b/helm/hugegraph/templates/pd-service-client.yaml @@ -0,0 +1,47 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- $svc := get .Values.pd "service" | default dict }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "hugegraph.pd.clientName" . }} + labels: + {{- include "hugegraph.labels" . | nindent 4 }} + app.kubernetes.io/component: pd + {{- with (get $svc "annotations") }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ get $svc "type" | default "ClusterIP" }} + selector: + {{- include "hugegraph.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: pd + ports: + - name: rest + port: {{ .Values.pd.ports.rest }} + targetPort: rest + {{- with (get $svc "restNodePort") }} + nodePort: {{ . }} + {{- end }} + - name: grpc + port: {{ .Values.pd.ports.grpc }} + targetPort: grpc + {{- with (get $svc "grpcNodePort") }} + nodePort: {{ . }} + {{- end }} diff --git a/helm/hugegraph/templates/pd-service-headless.yaml b/helm/hugegraph/templates/pd-service-headless.yaml new file mode 100644 index 0000000000..3b724f9dcf --- /dev/null +++ b/helm/hugegraph/templates/pd-service-headless.yaml @@ -0,0 +1,41 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +apiVersion: v1 +kind: Service +metadata: + name: {{ include "hugegraph.pd.name" . }} + labels: + {{- include "hugegraph.labels" . | nindent 4 }} + app.kubernetes.io/component: pd +spec: + clusterIP: None + # Mandatory: Raft bootstrap needs DNS for not-yet-ready pods + publishNotReadyAddresses: true + selector: + {{- include "hugegraph.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: pd + ports: + - name: grpc + port: {{ .Values.pd.ports.grpc }} + targetPort: grpc + - name: rest + port: {{ .Values.pd.ports.rest }} + targetPort: rest + - name: raft + port: {{ .Values.pd.ports.raft }} + targetPort: raft diff --git a/helm/hugegraph/templates/pd-statefulset.yaml b/helm/hugegraph/templates/pd-statefulset.yaml new file mode 100644 index 0000000000..070945b208 --- /dev/null +++ b/helm/hugegraph/templates/pd-statefulset.yaml @@ -0,0 +1,172 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "hugegraph.pd.name" . }} + labels: + {{- include "hugegraph.labels" . | nindent 4 }} + app.kubernetes.io/component: pd +spec: + serviceName: {{ include "hugegraph.pd.name" . }} + replicas: {{ .Values.pd.replicas }} + podManagementPolicy: Parallel + {{- with .Values.pd.updateStrategy }} + updateStrategy: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.pd.persistentVolumeClaimRetentionPolicy }} + persistentVolumeClaimRetentionPolicy: + {{- toYaml . | nindent 4 }} + {{- end }} + selector: + matchLabels: + {{- include "hugegraph.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: pd + template: + metadata: + labels: + {{- include "hugegraph.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: pd + {{- with .Values.pd.podLabels }}{{ toYaml . | nindent 8 }}{{- end }} + annotations: + # Rolls PD pods when the resolved REST auth Secret changes, so rotating + # an existingSecret takes effect without a manual restart. + checksum/pd-auth: {{ include "hugegraph.pd.authChecksum" . | quote }} + {{- with .Values.pd.podAnnotations }}{{ toYaml . | nindent 8 }}{{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + automountServiceAccountToken: {{ get (get .Values.pd "serviceAccount" | default dict) "automountServiceAccountToken" | default false }} + serviceAccountName: {{ include "hugegraph.serviceAccountName" (dict "component" .Values.pd "name" (include "hugegraph.pd.name" .) ) }} + {{- if hasKey .Values.pd "terminationGracePeriodSeconds" }} + terminationGracePeriodSeconds: {{ .Values.pd.terminationGracePeriodSeconds }} + {{- end }} + {{- with .Values.pd.priorityClassName }} + priorityClassName: {{ . | quote }} + {{- end }} + {{- with .Values.pd.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.pd.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.pd.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.pd.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.pd.affinity }} + affinity: + {{- toYaml .Values.pd.affinity | nindent 8 }} + {{- else }} + {{- with include "hugegraph.antiAffinity" (dict "mode" .Values.pd.antiAffinity "component" "pd" "labels" (include "hugegraph.selectorLabels" . | fromYaml)) }}{{ . | trim | nindent 6 }}{{- end }} + {{- end }} + containers: + - name: pd + image: {{ include "hugegraph.image" (dict "image" .Values.pd.image "appVersion" $.Chart.AppVersion) | quote }} + imagePullPolicy: {{ .Values.pd.image.pullPolicy }} + {{- with .Values.pd.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: grpc + containerPort: {{ .Values.pd.ports.grpc }} + - name: rest + containerPort: {{ .Values.pd.ports.rest }} + - name: raft + containerPort: {{ .Values.pd.ports.raft }} + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: HG_PD_GRPC_HOST + value: "$(POD_NAME).{{ include "hugegraph.pd.name" . }}.$(NAMESPACE).svc" + - name: HG_PD_GRPC_PORT + value: {{ .Values.pd.ports.grpc | quote }} + - name: HG_PD_REST_PORT + value: {{ .Values.pd.ports.rest | quote }} + - name: HG_PD_RAFT_ADDRESS + value: "$(POD_NAME).{{ include "hugegraph.pd.name" . }}.$(NAMESPACE).svc:{{ .Values.pd.ports.raft }}" + - name: HG_PD_RAFT_PEERS_LIST + value: {{ include "hugegraph.pd.raftPeersList" . | quote }} + - name: HG_PD_INITIAL_STORE_LIST + value: {{ include "hugegraph.store.initialStoreList" . | quote }} + - name: HG_PD_INITIAL_STORE_COUNT + value: {{ .Values.store.replicas | quote }} + - name: HG_PD_DATA_PATH + value: {{ .Values.pd.dataPath | quote }} + - name: HG_PD_AUTH_SECRET_KEY + valueFrom: + secretKeyRef: + name: {{ include "hugegraph.pd.authSecretName" . | quote }} + key: {{ include "hugegraph.pd.authSecretKey" . | quote }} + {{- with .Values.pd.extraEnv }}{{ toYaml . | nindent 12 }}{{- end }} + {{- with include "hugegraph.javaOptsEnv" (include "hugegraph.pd.effectiveJavaOpts" .) }}{{ . | trim | nindent 12 }}{{- end }} + volumeMounts: + - name: pd-data + mountPath: {{ .Values.pd.dataPath }} + startupProbe: + httpGet: + path: {{ include "hugegraph.pd.livenessPath" . }} + port: rest + failureThreshold: {{ .Values.pd.probes.startup.failureThreshold }} + periodSeconds: {{ .Values.pd.probes.startup.periodSeconds }} + {{- with include "hugegraph.probeTuning" .Values.pd.probes.startup }}{{ . | trim | nindent 12 }}{{- end }} + readinessProbe: + httpGet: + path: {{ .Values.pd.readinessPath | default "/v1/ready" }} + port: rest + periodSeconds: {{ .Values.pd.probes.readiness.periodSeconds }} + failureThreshold: {{ .Values.pd.probes.readiness.failureThreshold }} + {{- with include "hugegraph.probeTuning" .Values.pd.probes.readiness }}{{ . | trim | nindent 12 }}{{- end }} + livenessProbe: + httpGet: + path: {{ include "hugegraph.pd.livenessPath" . }} + port: rest + periodSeconds: {{ .Values.pd.probes.liveness.periodSeconds }} + failureThreshold: {{ .Values.pd.probes.liveness.failureThreshold }} + {{- with include "hugegraph.probeTuning" .Values.pd.probes.liveness }}{{ . | trim | nindent 12 }}{{- end }} + {{- with .Values.pd.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeClaimTemplates: + - metadata: + name: pd-data + spec: + accessModes: ["ReadWriteOnce"] + {{- if .Values.pd.storage.storageClassName }} + storageClassName: {{ .Values.pd.storage.storageClassName | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.pd.storage.size }} diff --git a/helm/hugegraph/templates/server-auth-token-secret.yaml b/helm/hugegraph/templates/server-auth-token-secret.yaml new file mode 100644 index 0000000000..717d4fc23b --- /dev/null +++ b/helm/hugegraph/templates/server-auth-token-secret.yaml @@ -0,0 +1,32 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- $auth := get .Values.server "auth" | default dict -}} +{{- $token := get $auth "token" | default dict -}} +{{- if and (get $auth "enabled" | default false) (not (get $token "existingSecret" | default "")) (or (get $token "value" | default "") (get $token "autoGenerate" | default false)) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "hugegraph.server.authTokenSecretName" . }} + labels: + {{- include "hugegraph.labels" . | nindent 4 }} + annotations: + helm.sh/resource-policy: keep +type: Opaque +data: + {{ include "hugegraph.server.authTokenSecretKey" . | quote }}: {{ include "hugegraph.server.authTokenSecretValue" . | quote }} +{{- end }} diff --git a/helm/hugegraph/templates/server-deployment.yaml b/helm/hugegraph/templates/server-deployment.yaml new file mode 100644 index 0000000000..93709f0c9b --- /dev/null +++ b/helm/hugegraph/templates/server-deployment.yaml @@ -0,0 +1,335 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- include "hugegraph.validateValues" . }} +{{- $restServer := .Values.server.restServer | default dict }} +{{- $minFreeMemory := "" }} +{{- $batchMaxWriteThreads := "" }} +{{- if hasKey $restServer "minFreeMemory" }} +{{- $minFreeMemory = toString (get $restServer "minFreeMemory") }} +{{- end }} +{{- if hasKey $restServer "batchMaxWriteThreads" }} +{{- $batchMaxWriteThreads = toString (get $restServer "batchMaxWriteThreads") }} +{{- end }} +{{- $customPort := ne (int .Values.server.port) 8080 }} +{{/* +Distributed HStore requires every Server replica to share graph metadata +through PD. The same registration properties let PD-mode Hubble discover the +Server and let the built-in authenticator create the admin on the PD path. +*/}} +{{- $pdMeta := true }} +{{- $wrapper := or $pdMeta $customPort (ne $minFreeMemory "") (ne $batchMaxWriteThreads "") }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "hugegraph.server.name" . }} + labels: + {{- include "hugegraph.labels" . | nindent 4 }} + app.kubernetes.io/component: server +spec: + {{- if not .Values.server.hpa.enabled }} + replicas: {{ .Values.server.replicas }} + {{- end }} + selector: + matchLabels: + {{- include "hugegraph.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: server + template: + metadata: + labels: + {{- include "hugegraph.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: server + {{- with .Values.server.podLabels }}{{ toYaml . | nindent 8 }}{{- end }} + annotations: + {{- if .Values.server.auth.enabled }} + # Rolls Server pods when the resolved auth Secrets change, so rotating + # an existingSecret takes effect without a manual restart. + checksum/auth: {{ include "hugegraph.server.authChecksum" . | quote }} + {{- end }} + # Rolls Server pods when the PD REST Secret its storage wait uses changes. + checksum/pd-auth: {{ include "hugegraph.pd.authChecksum" . | quote }} + {{- with .Values.server.podAnnotations }}{{ toYaml . | nindent 8 }}{{- end }} + spec: + automountServiceAccountToken: {{ get (get .Values.server "serviceAccount" | default dict) "automountServiceAccountToken" | default false }} + serviceAccountName: {{ include "hugegraph.serviceAccountName" (dict "component" .Values.server "name" (include "hugegraph.server.name" .) ) }} + {{- if hasKey .Values.server "terminationGracePeriodSeconds" }} + terminationGracePeriodSeconds: {{ .Values.server.terminationGracePeriodSeconds }} + {{- end }} + {{- with .Values.server.priorityClassName }} + priorityClassName: {{ . | quote }} + {{- end }} + {{- with .Values.server.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.server.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.server.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.server.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.server.affinity }} + affinity: + {{- toYaml .Values.server.affinity | nindent 8 }} + {{- else }} + {{- with include "hugegraph.antiAffinity" (dict "mode" (.Values.server.antiAffinity | default "preferred") "component" "server" "labels" (include "hugegraph.selectorLabels" . | fromYaml)) }}{{ . | trim | nindent 6 }}{{- end }} + {{- end }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: server + image: {{ include "hugegraph.image" (dict "image" .Values.server.image "appVersion" $.Chart.AppVersion) | quote }} + imagePullPolicy: {{ .Values.server.image.pullPolicy }} + {{- with .Values.server.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if $wrapper }} + command: + - /usr/bin/dumb-init + - -- + - /bin/bash + - -c + args: + - | + set -euo pipefail + {{- if .Values.server.auth.enabled }} + : "${PASSWORD:?auth Secret key 'password' must not be empty}" + # The value is written into a Java properties file, whose parser + # treats CR as a line terminator, unescapes backslashes, and + # skips whitespace after the separator, so these shapes would + # inject config lines or silently store a password that differs + # from the Secret. + case "${PASSWORD}" in + *$'\n'* | *$'\r'* | *\\* | ' '* | $'\t'*) + echo "auth Secret key 'password' must not contain newlines," \ + "carriage returns, or backslashes, or start with" \ + "whitespace" >&2 + exit 1 + ;; + esac + {{- end }} + CONF=./conf/rest-server.properties + if [[ ! -r "${CONF}" ]]; then + echo "missing or unreadable ${CONF}; refusing to write a stub config" >&2 + exit 1 + fi + TMP=$(mktemp) + {{- if $pdMeta }} + FOUND_USE_PD=false + FOUND_PD_PEERS=false + FOUND_URLS_TO_PD=false + FOUND_DEPLOY_IN_K8S=false + # An IPv6 POD_IP must be bracketed inside the announcement URL. + # Only the chart's own default is rewritten; an explicit + # server.advertiseUrl is the operator's to format. + if [[ "${POD_IP:-}" == *:* && "${HG_SERVER_URLS_TO_PD:-}" == "http://${POD_IP}:"* ]]; then + HG_SERVER_URLS_TO_PD="http://[${POD_IP}]:${HG_SERVER_URLS_TO_PD##*:}" + fi + {{- end }} + {{- if .Values.server.auth.enabled }} + FOUND_AUTH_ADMIN_PA=false + {{- end }} + {{- if $customPort }} + FOUND_RESTSERVER_URL=false + {{- end }} + {{- if ne $minFreeMemory "" }} + FOUND_MIN_FREE_MEMORY=false + {{- end }} + {{- if ne $batchMaxWriteThreads "" }} + FOUND_BATCH_MAX_WRITE_THREADS=false + {{- end }} + while IFS= read -r LINE || [[ -n "${LINE}" ]]; do + case "${LINE}" in + {{- if $pdMeta }} + usePD=*) + printf 'usePD=true\n' >>"${TMP}" + FOUND_USE_PD=true + ;; + pd.peers=*) + printf 'pd.peers=%s\n' "${HG_SERVER_PD_PEERS}" >>"${TMP}" + FOUND_PD_PEERS=true + ;; + server.urls_to_pd=*) + printf 'server.urls_to_pd=%s\n' "${HG_SERVER_URLS_TO_PD}" >>"${TMP}" + FOUND_URLS_TO_PD=true + ;; + server.deploy_in_k8s=*) + printf 'server.deploy_in_k8s=true\n' >>"${TMP}" + FOUND_DEPLOY_IN_K8S=true + ;; + {{- end }} + {{- if .Values.server.auth.enabled }} + auth.admin_pa=*) + printf 'auth.admin_pa=%s\n' "${PASSWORD}" >>"${TMP}" + FOUND_AUTH_ADMIN_PA=true + ;; + {{- end }} + {{- if $customPort }} + restserver.url=*) + printf 'restserver.url=http://0.0.0.0:%s\n' \ + {{ .Values.server.port | quote }} >>"${TMP}" + FOUND_RESTSERVER_URL=true + ;; + {{- end }} + {{- if ne $minFreeMemory "" }} + restserver.min_free_memory=*) + printf 'restserver.min_free_memory=%s\n' \ + {{ $minFreeMemory | quote }} >>"${TMP}" + FOUND_MIN_FREE_MEMORY=true + ;; + {{- end }} + {{- if ne $batchMaxWriteThreads "" }} + batch.max_write_threads=*) + printf 'batch.max_write_threads=%s\n' \ + {{ $batchMaxWriteThreads | quote }} >>"${TMP}" + FOUND_BATCH_MAX_WRITE_THREADS=true + ;; + {{- end }} + *) + printf '%s\n' "${LINE}" >>"${TMP}" + ;; + esac + done <"${CONF}" + {{- if $pdMeta }} + if [[ "${FOUND_USE_PD}" == false ]]; then + printf 'usePD=true\n' >>"${TMP}" + fi + if [[ "${FOUND_PD_PEERS}" == false ]]; then + printf 'pd.peers=%s\n' "${HG_SERVER_PD_PEERS}" >>"${TMP}" + fi + # PD hands this URL to discovery clients such as Hubble. + # HG_SERVER_URLS_TO_PD uses server.advertiseUrl when set, + # otherwise the in-cluster Server Service URL. The k8s + # branch is taken only when server.deploy_in_k8s is true; + # otherwise the announcement falls back to restserver.url, + # whose 0.0.0.0 is never resolvable from another Pod. + if [[ "${FOUND_URLS_TO_PD}" == false ]]; then + printf 'server.urls_to_pd=%s\n' "${HG_SERVER_URLS_TO_PD}" >>"${TMP}" + fi + if [[ "${FOUND_DEPLOY_IN_K8S}" == false ]]; then + printf 'server.deploy_in_k8s=true\n' >>"${TMP}" + fi + {{- end }} + {{- if .Values.server.auth.enabled }} + if [[ "${FOUND_AUTH_ADMIN_PA}" == false ]]; then + printf 'auth.admin_pa=%s\n' "${PASSWORD}" >>"${TMP}" + fi + {{- end }} + {{- if $customPort }} + if [[ "${FOUND_RESTSERVER_URL}" == false ]]; then + printf 'restserver.url=http://0.0.0.0:%s\n' \ + {{ .Values.server.port | quote }} >>"${TMP}" + fi + {{- end }} + {{- if ne $minFreeMemory "" }} + if [[ "${FOUND_MIN_FREE_MEMORY}" == false ]]; then + printf 'restserver.min_free_memory=%s\n' \ + {{ $minFreeMemory | quote }} >>"${TMP}" + fi + {{- end }} + {{- if ne $batchMaxWriteThreads "" }} + if [[ "${FOUND_BATCH_MAX_WRITE_THREADS}" == false ]]; then + printf 'batch.max_write_threads=%s\n' \ + {{ $batchMaxWriteThreads | quote }} >>"${TMP}" + fi + {{- end }} + chmod 600 "${TMP}" + mv "${TMP}" "${CONF}" + exec ./docker-entrypoint.sh + {{- end }} + ports: + - name: http + containerPort: {{ .Values.server.port }} + env: + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: HG_SERVER_BACKEND + value: {{ .Values.server.backend | quote }} + - name: HG_SERVER_PD_PEERS + value: {{ include "hugegraph.pd.grpcPeersList" . | quote }} + - name: HG_SERVER_PD_REST_ENDPOINT + value: {{ include "hugegraph.pd.restPeersList" . | quote }} + # wait-storage.sh authenticates its PD readiness checks as the + # store service user with this password. + - name: PD_AUTH_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "hugegraph.pd.authSecretName" . | quote }} + key: {{ include "hugegraph.pd.authSecretKey" . | quote }} + - name: STORE_REST + value: {{ include "hugegraph.store.restPrimary" . | quote }} + - name: HG_SERVER_INIT_STORE_ENABLED + value: {{ .Values.server.initStoreEnabled | quote }} + # The image would otherwise give the start command 120 seconds, + # less than the storage wait, and kill a Server that is still + # coming up. Track the startup probe's own budget instead. + - name: HG_SERVER_STARTUP_TIMEOUT_S + value: {{ include "hugegraph.server.startupTimeoutSeconds" . | quote }} + {{- if $pdMeta }} + - name: HG_SERVER_URLS_TO_PD + value: {{ include "hugegraph.server.urlsToPd" . | quote }} + {{- end }} + {{- with .Values.server.extraEnv }}{{ toYaml . | nindent 12 }}{{- end }} + {{- with include "hugegraph.javaOptsEnv" .Values.server.javaOpts }}{{ . | trim | nindent 12 }}{{- end }} + {{- if .Values.server.auth.enabled }} + - name: PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "hugegraph.server.authSecretName" . | quote }} + key: {{ include "hugegraph.server.authSecretKey" . | quote }} + - name: HG_SERVER_AUTH_TOKEN_SECRET + valueFrom: + secretKeyRef: + name: {{ include "hugegraph.server.authTokenSecretName" . | quote }} + key: {{ include "hugegraph.server.authTokenSecretKey" . | quote }} + {{- end }} + startupProbe: + httpGet: + path: /versions + port: http + failureThreshold: {{ include "hugegraph.server.startupFailureThreshold" . }} + periodSeconds: {{ .Values.server.probes.startup.periodSeconds }} + {{- with include "hugegraph.probeTuning" .Values.server.probes.startup }}{{ . | trim | nindent 12 }}{{- end }} + readinessProbe: + httpGet: + path: {{ .Values.server.readinessPath | default "/versions" }} + port: http + periodSeconds: {{ .Values.server.probes.readiness.periodSeconds }} + failureThreshold: {{ .Values.server.probes.readiness.failureThreshold }} + {{- with include "hugegraph.probeTuning" .Values.server.probes.readiness }}{{ . | trim | nindent 12 }}{{- end }} + livenessProbe: + httpGet: + path: /versions + port: http + periodSeconds: {{ .Values.server.probes.liveness.periodSeconds }} + failureThreshold: {{ .Values.server.probes.liveness.failureThreshold }} + {{- with include "hugegraph.probeTuning" .Values.server.probes.liveness }}{{ . | trim | nindent 12 }}{{- end }} + {{- with .Values.server.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} diff --git a/helm/hugegraph/templates/server-hpa.yaml b/helm/hugegraph/templates/server-hpa.yaml new file mode 100644 index 0000000000..40e95c0d99 --- /dev/null +++ b/helm/hugegraph/templates/server-hpa.yaml @@ -0,0 +1,40 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- if .Values.server.hpa.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "hugegraph.server.name" . }} + labels: + {{- include "hugegraph.labels" . | nindent 4 }} + app.kubernetes.io/component: server +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "hugegraph.server.name" . }} + minReplicas: {{ .Values.server.hpa.minReplicas }} + maxReplicas: {{ .Values.server.hpa.maxReplicas }} + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.server.hpa.targetCPUUtilizationPercentage }} +{{- end }} diff --git a/helm/hugegraph/templates/server-ingress.yaml b/helm/hugegraph/templates/server-ingress.yaml new file mode 100644 index 0000000000..ad473fde73 --- /dev/null +++ b/helm/hugegraph/templates/server-ingress.yaml @@ -0,0 +1,53 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- if .Values.server.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "hugegraph.server.name" . }} + labels: + {{- include "hugegraph.labels" . | nindent 4 }} + app.kubernetes.io/component: server + {{- with (get .Values.server.ingress "annotations") }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.server.ingress.className }} + ingressClassName: {{ .Values.server.ingress.className | quote }} + {{- end }} + {{- with .Values.server.ingress.tls }} + tls: + {{- toYaml . | nindent 4 }} + {{- end }} + rules: + {{- range .Values.server.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path | quote }} + pathType: {{ .pathType }} + backend: + service: + name: {{ include "hugegraph.server.name" $ }} + port: + number: {{ $.Values.server.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/helm/hugegraph/templates/server-pdb.yaml b/helm/hugegraph/templates/server-pdb.yaml new file mode 100644 index 0000000000..7fb4826aec --- /dev/null +++ b/helm/hugegraph/templates/server-pdb.yaml @@ -0,0 +1,34 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- $pdb := get .Values.server "pdb" | default dict }} +{{- $replicaFloor := include "hugegraph.server.replicaFloor" . | int }} +{{- if and (get $pdb "enabled" | default false) (gt $replicaFloor 1) }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "hugegraph.server.name" . }} + labels: + {{- include "hugegraph.labels" . | nindent 4 }} + app.kubernetes.io/component: server +spec: + minAvailable: {{ get $pdb "minAvailable" | default 1 }} + selector: + matchLabels: + {{- include "hugegraph.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: server +{{- end }} diff --git a/helm/hugegraph/templates/server-secret.yaml b/helm/hugegraph/templates/server-secret.yaml new file mode 100644 index 0000000000..4d132224a6 --- /dev/null +++ b/helm/hugegraph/templates/server-secret.yaml @@ -0,0 +1,32 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- $auth := get .Values.server "auth" | default dict -}} +{{- $admin := get $auth "admin" | default dict -}} +{{- if and (get $auth "enabled" | default false) (not (get $admin "existingSecret" | default "")) (or (get $admin "password" | default "") (get $admin "autoGenerate" | default false)) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "hugegraph.server.authSecretName" . }} + labels: + {{- include "hugegraph.labels" . | nindent 4 }} + annotations: + helm.sh/resource-policy: keep +type: Opaque +data: + {{ include "hugegraph.server.authSecretKey" . | quote }}: {{ include "hugegraph.server.authSecretPassword" . | quote }} +{{- end }} diff --git a/helm/hugegraph/templates/server-service.yaml b/helm/hugegraph/templates/server-service.yaml new file mode 100644 index 0000000000..b4053dc4ba --- /dev/null +++ b/helm/hugegraph/templates/server-service.yaml @@ -0,0 +1,41 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +apiVersion: v1 +kind: Service +metadata: + name: {{ include "hugegraph.server.name" . }} + labels: + {{- include "hugegraph.labels" . | nindent 4 }} + app.kubernetes.io/component: server + {{- $svc := get .Values.server "service" | default dict }} + {{- with (get $svc "annotations") }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ get $svc "type" | default "ClusterIP" }} + selector: + {{- include "hugegraph.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: server + ports: + - name: http + port: {{ .Values.server.port }} + targetPort: http + {{- with (get $svc "nodePort") }} + nodePort: {{ . }} + {{- end }} diff --git a/helm/hugegraph/templates/serviceaccount.yaml b/helm/hugegraph/templates/serviceaccount.yaml new file mode 100644 index 0000000000..0dbf4c302c --- /dev/null +++ b/helm/hugegraph/templates/serviceaccount.yaml @@ -0,0 +1,39 @@ +{{- /* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ -}} +{{- $components := list "pd" "store" "server" }} +{{- if get (get .Values "hubble" | default dict) "enabled" | default false }} +{{- $components = append $components "hubble" }} +{{- end }} +{{- range $component := $components }} +{{- $values := index $.Values $component }} +{{- $sa := get $values "serviceAccount" | default dict }} +{{- if and (get $sa "create" | default false) (not (get $sa "name")) }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include (printf "hugegraph.%s.name" $component) $ }} + labels: + {{- include "hugegraph.labels" $ | nindent 4 }} + app.kubernetes.io/component: {{ $component }} + {{- with (get $sa "annotations") }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ get $sa "automountServiceAccountToken" | default false }} +{{- end }} +{{- end }} diff --git a/helm/hugegraph/templates/store-pdb.yaml b/helm/hugegraph/templates/store-pdb.yaml new file mode 100644 index 0000000000..9fb1e536da --- /dev/null +++ b/helm/hugegraph/templates/store-pdb.yaml @@ -0,0 +1,32 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{{- if and .Values.store.pdb.enabled (gt (int .Values.store.replicas) 1) }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "hugegraph.store.name" . }} + labels: + {{- include "hugegraph.labels" . | nindent 4 }} + app.kubernetes.io/component: store +spec: + minAvailable: {{ .Values.store.pdb.minAvailable }} + selector: + matchLabels: + {{- include "hugegraph.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: store +{{- end }} diff --git a/helm/hugegraph/templates/store-service-headless.yaml b/helm/hugegraph/templates/store-service-headless.yaml new file mode 100644 index 0000000000..4c82023b5d --- /dev/null +++ b/helm/hugegraph/templates/store-service-headless.yaml @@ -0,0 +1,41 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +apiVersion: v1 +kind: Service +metadata: + name: {{ include "hugegraph.store.name" . }} + labels: + {{- include "hugegraph.labels" . | nindent 4 }} + app.kubernetes.io/component: store +spec: + clusterIP: None + # Mandatory: Raft / self-FQDN resolution before readiness + publishNotReadyAddresses: true + selector: + {{- include "hugegraph.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: store + ports: + - name: grpc + port: {{ .Values.store.ports.grpc }} + targetPort: grpc + - name: raft + port: {{ .Values.store.ports.raft }} + targetPort: raft + - name: rest + port: {{ .Values.store.ports.rest }} + targetPort: rest diff --git a/helm/hugegraph/templates/store-statefulset.yaml b/helm/hugegraph/templates/store-statefulset.yaml new file mode 100644 index 0000000000..69dc03d746 --- /dev/null +++ b/helm/hugegraph/templates/store-statefulset.yaml @@ -0,0 +1,202 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "hugegraph.store.name" . }} + labels: + {{- include "hugegraph.labels" . | nindent 4 }} + app.kubernetes.io/component: store +spec: + serviceName: {{ include "hugegraph.store.name" . }} + replicas: {{ .Values.store.replicas }} + podManagementPolicy: Parallel + {{- with .Values.store.updateStrategy }} + updateStrategy: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.store.persistentVolumeClaimRetentionPolicy }} + persistentVolumeClaimRetentionPolicy: + {{- toYaml . | nindent 4 }} + {{- end }} + selector: + matchLabels: + {{- include "hugegraph.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: store + template: + metadata: + labels: + {{- include "hugegraph.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: store + {{- with .Values.store.podLabels }}{{ toYaml . | nindent 8 }}{{- end }} + {{- with .Values.store.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + automountServiceAccountToken: {{ get (get .Values.store "serviceAccount" | default dict) "automountServiceAccountToken" | default false }} + serviceAccountName: {{ include "hugegraph.serviceAccountName" (dict "component" .Values.store "name" (include "hugegraph.store.name" .) ) }} + {{- if hasKey .Values.store "terminationGracePeriodSeconds" }} + terminationGracePeriodSeconds: {{ .Values.store.terminationGracePeriodSeconds }} + {{- end }} + {{- with .Values.store.priorityClassName }} + priorityClassName: {{ . | quote }} + {{- end }} + {{- with .Values.store.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.store.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.store.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.store.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.store.affinity }} + affinity: + {{- toYaml .Values.store.affinity | nindent 8 }} + {{- else }} + {{- with include "hugegraph.antiAffinity" (dict "mode" .Values.store.antiAffinity "component" "store" "labels" (include "hugegraph.selectorLabels" . | fromYaml)) }}{{ . | trim | nindent 6 }}{{- end }} + {{- end }} + initContainers: + - name: wait-for-pd + image: {{ .Values.store.waitImage | quote }} + {{- with .Values.store.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + command: + - sh + - -c + - | + set -eu + REQUIRED={{ include "hugegraph.pd.quorum" . }} + HEALTH_PEERS=$(echo "{{ include "hugegraph.pd.restPeersList" . }}" | tr ',' ' ') + TIMEOUT={{ .Values.store.waitTimeoutSeconds | default 900 }} + DEADLINE=$(( $(date +%s) + TIMEOUT )) + WAIT_PATH={{ .Values.store.waitPath | default "/v1/ready" | quote }} + echo "Waiting for ${REQUIRED} PD peers to answer ${WAIT_PATH} among: ${HEALTH_PEERS}" + until [ "$( + ok=0 + for peer in ${HEALTH_PEERS}; do + if curl -fsS --connect-timeout 2 --max-time 5 "http://${peer}${WAIT_PATH}" >/dev/null 2>&1; then + ok=$((ok+1)) + fi + done + echo "$ok" + )" -ge "${REQUIRED}" ]; do + if [ "$(date +%s)" -ge "${DEADLINE}" ]; then + echo "Timed out after ${TIMEOUT}s waiting for ${REQUIRED} PD peers to answer ${WAIT_PATH} among: ${HEALTH_PEERS}" >&2 + echo "Check PD Pods: kubectl get pods -l app.kubernetes.io/component=pd" >&2 + exit 1 + fi + echo "Waiting for PD peers..." + sleep 5 + done + echo "Enough PD peers answered ${WAIT_PATH}." + {{- with .Values.store.waitResources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + containers: + - name: store + image: {{ include "hugegraph.image" (dict "image" .Values.store.image "appVersion" $.Chart.AppVersion) | quote }} + imagePullPolicy: {{ .Values.store.image.pullPolicy }} + {{- with .Values.store.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: grpc + containerPort: {{ .Values.store.ports.grpc }} + - name: raft + containerPort: {{ .Values.store.ports.raft }} + - name: rest + containerPort: {{ .Values.store.ports.rest }} + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: HG_STORE_PD_ADDRESS + value: {{ include "hugegraph.pd.grpcPeersList" . | quote }} + - name: HG_STORE_GRPC_HOST + value: "$(POD_NAME).{{ include "hugegraph.store.name" . }}.$(NAMESPACE).svc" + - name: HG_STORE_GRPC_PORT + value: {{ .Values.store.ports.grpc | quote }} + - name: HG_STORE_REST_PORT + value: {{ .Values.store.ports.rest | quote }} + - name: HG_STORE_RAFT_ADDRESS + value: "$(POD_NAME).{{ include "hugegraph.store.name" . }}.$(NAMESPACE).svc:{{ .Values.store.ports.raft }}" + - name: HG_STORE_DATA_PATH + value: {{ .Values.store.dataPath | quote }} + {{- with .Values.store.extraEnv }}{{ toYaml . | nindent 12 }}{{- end }} + {{- with include "hugegraph.javaOptsEnv" .Values.store.javaOpts }}{{ . | trim | nindent 12 }}{{- end }} + volumeMounts: + - name: store-data + mountPath: {{ .Values.store.dataPath }} + startupProbe: + httpGet: + path: /v1/health + port: rest + failureThreshold: {{ .Values.store.probes.startup.failureThreshold }} + periodSeconds: {{ .Values.store.probes.startup.periodSeconds }} + {{- with include "hugegraph.probeTuning" .Values.store.probes.startup }}{{ . | trim | nindent 12 }}{{- end }} + readinessProbe: + httpGet: + path: /v1/health + port: rest + periodSeconds: {{ .Values.store.probes.readiness.periodSeconds }} + failureThreshold: {{ .Values.store.probes.readiness.failureThreshold }} + {{- with include "hugegraph.probeTuning" .Values.store.probes.readiness }}{{ . | trim | nindent 12 }}{{- end }} + livenessProbe: + httpGet: + path: /v1/health + port: rest + periodSeconds: {{ .Values.store.probes.liveness.periodSeconds }} + failureThreshold: {{ .Values.store.probes.liveness.failureThreshold }} + {{- with include "hugegraph.probeTuning" .Values.store.probes.liveness }}{{ . | trim | nindent 12 }}{{- end }} + {{- with .Values.store.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeClaimTemplates: + - metadata: + name: store-data + spec: + accessModes: ["ReadWriteOnce"] + {{- if .Values.store.storage.storageClassName }} + storageClassName: {{ .Values.store.storage.storageClassName | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.store.storage.size }} diff --git a/helm/hugegraph/templates/tests/test-connection.yaml b/helm/hugegraph/templates/tests/test-connection.yaml new file mode 100644 index 0000000000..4a15184a78 --- /dev/null +++ b/helm/hugegraph/templates/tests/test-connection.yaml @@ -0,0 +1,87 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Official Helm chart test hook — only runs with: helm test +# Not an install-time hook. Safe with --wait (no init Job in no-init design). +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "hugegraph.test.name" . }}" + labels: + {{- include "hugegraph.labels" . | nindent 4 }} + app.kubernetes.io/component: test + annotations: + "helm.sh/hook": test + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 4 }} + {{- end }} + automountServiceAccountToken: false + restartPolicy: Never + securityContext: + seccompProfile: + type: RuntimeDefault + containers: + - name: curl + image: {{ .Values.server.waitImage | quote }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + readOnlyRootFilesystem: true + runAsGroup: 101 + runAsNonRoot: true + runAsUser: 100 + {{- with .Values.server.testResources }} + resources: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.server.auth.enabled }} + env: + - name: PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "hugegraph.server.authSecretName" . | quote }} + key: {{ include "hugegraph.server.authSecretKey" . | quote }} + {{- end }} + command: + - sh + - -c + - | + set -eu + SVC="{{ include "hugegraph.server.name" . }}" + PORT="{{ .Values.server.port }}" + {{- if .Values.server.auth.enabled }} + AUTH_HEADER=$(printf 'admin:%s' "${PASSWORD}" | base64 | tr -d '\r\n') + request() { + printf 'header = "Authorization: Basic %s"\n' "${AUTH_HEADER}" | + curl --config - -fsS "$1" + } + {{- else }} + request() { + curl -fsS "$1" + } + {{- end }} + echo "helm test: GET http://${SVC}:${PORT}/versions" + request "http://${SVC}:${PORT}/versions" + echo + echo "helm test: GET http://${SVC}:${PORT}/graphs" + request "http://${SVC}:${PORT}/graphs" + echo + echo "helm test: OK" diff --git a/helm/hugegraph/testdata/values-pre-hardening.yaml b/helm/hugegraph/testdata/values-pre-hardening.yaml new file mode 100644 index 0000000000..990f5fe7e0 --- /dev/null +++ b/helm/hugegraph/testdata/values-pre-hardening.yaml @@ -0,0 +1,137 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Values shape shipped before the production-hardening fields were added. +# validate-chart.sh installs this as the chart default values to emulate a +# Helm --reuse-values upgrade whose stored release values lack all new keys. + +fullnameOverride: "" +nameOverride: "" + +imagePullSecrets: [] + +pd: + replicas: 3 + image: + repository: hugegraph/pd + tag: latest + pullPolicy: IfNotPresent + ports: + grpc: 8686 + rest: 8620 + raft: 8610 + dataPath: /hugegraph-pd/pd_data + storage: + size: 10Gi + storageClassName: "" + resources: {} + antiAffinity: required + pdb: + enabled: true + minAvailable: 2 + probes: + startup: + failureThreshold: 30 + periodSeconds: 10 + readiness: + periodSeconds: 10 + failureThreshold: 3 + liveness: + periodSeconds: 20 + failureThreshold: 3 + +store: + replicas: 3 + image: + repository: hugegraph/store + tag: latest + pullPolicy: IfNotPresent + ports: + grpc: 8500 + raft: 8510 + rest: 8520 + dataPath: /hugegraph-store/storage + storage: + size: 50Gi + storageClassName: "" + resources: {} + antiAffinity: required + pdb: + enabled: true + minAvailable: 2 + waitImage: curlimages/curl:8.5.0 + probes: + startup: + failureThreshold: 40 + periodSeconds: 10 + readiness: + periodSeconds: 10 + failureThreshold: 3 + liveness: + periodSeconds: 20 + failureThreshold: 3 + +server: + replicas: 3 + image: + repository: hugegraph/server + tag: latest + pullPolicy: IfNotPresent + port: 8080 + backend: hstore + resources: {} + waitImage: curlimages/curl:8.5.0 + initStoreEnabled: false + auth: + enabled: false + admin: + password: "" + existingSecret: "" + key: password + autoGenerate: true + token: + value: "" + existingSecret: "" + key: token_secret + autoGenerate: true + ingress: + enabled: false + className: "" + hosts: + - host: hugegraph.local + paths: + - path: / + pathType: Prefix + tls: [] + hpa: + enabled: false + minReplicas: 3 + maxReplicas: 10 + targetCPUUtilizationPercentage: 70 + probes: + startup: + failureThreshold: 30 + periodSeconds: 5 + readiness: + periodSeconds: 10 + failureThreshold: 3 + liveness: + periodSeconds: 20 + failureThreshold: 3 + +networkPolicy: + enabled: false diff --git a/helm/hugegraph/tests/__snapshot__/networkpolicy_test.yaml.snap b/helm/hugegraph/tests/__snapshot__/networkpolicy_test.yaml.snap new file mode 100644 index 0000000000..447049801c --- /dev/null +++ b/helm/hugegraph/tests/__snapshot__/networkpolicy_test.yaml.snap @@ -0,0 +1,286 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +matches the reviewed cluster plus Hubble render: + 1: | + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + labels: + app.kubernetes.io/component: pd + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: hugegraph + app.kubernetes.io/version: latest + helm.sh/chart: hugegraph-0.1.0 + name: RELEASE-NAME-hugegraph-pd + spec: + egress: + - ports: + - port: 8610 + protocol: TCP + - port: 8686 + protocol: TCP + to: + - podSelector: + matchLabels: + app.kubernetes.io/component: pd + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/name: hugegraph + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + ingress: + - from: + - podSelector: + matchLabels: + app.kubernetes.io/component: pd + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/name: hugegraph + ports: + - port: 8610 + protocol: TCP + - port: 8686 + protocol: TCP + - from: + - podSelector: + matchLabels: + app.kubernetes.io/component: store + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/name: hugegraph + - podSelector: + matchLabels: + app.kubernetes.io/component: server + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/name: hugegraph + - podSelector: + matchLabels: + app.kubernetes.io/component: hubble + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/name: hugegraph + ports: + - port: 8686 + protocol: TCP + - port: 8620 + protocol: TCP + podSelector: + matchLabels: + app.kubernetes.io/component: pd + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/name: hugegraph + policyTypes: + - Ingress + - Egress + 2: | + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + labels: + app.kubernetes.io/component: store + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: hugegraph + app.kubernetes.io/version: latest + helm.sh/chart: hugegraph-0.1.0 + name: RELEASE-NAME-hugegraph-store + spec: + egress: + - ports: + - port: 8686 + protocol: TCP + - port: 8620 + protocol: TCP + to: + - podSelector: + matchLabels: + app.kubernetes.io/component: pd + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/name: hugegraph + - ports: + - port: 8510 + protocol: TCP + to: + - podSelector: + matchLabels: + app.kubernetes.io/component: store + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/name: hugegraph + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + ingress: + - from: + - podSelector: + matchLabels: + app.kubernetes.io/component: store + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/name: hugegraph + ports: + - port: 8510 + protocol: TCP + - from: + - podSelector: + matchLabels: + app.kubernetes.io/component: server + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/name: hugegraph + ports: + - port: 8500 + protocol: TCP + - port: 8520 + protocol: TCP + - from: + - podSelector: + matchLabels: + app.kubernetes.io/component: hubble + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/name: hugegraph + ports: + - port: 8520 + protocol: TCP + podSelector: + matchLabels: + app.kubernetes.io/component: store + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/name: hugegraph + policyTypes: + - Ingress + - Egress + 3: | + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + labels: + app.kubernetes.io/component: server + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: hugegraph + app.kubernetes.io/version: latest + helm.sh/chart: hugegraph-0.1.0 + name: RELEASE-NAME-hugegraph-server + spec: + egress: + - ports: + - port: 8686 + protocol: TCP + - port: 8620 + protocol: TCP + to: + - podSelector: + matchLabels: + app.kubernetes.io/component: pd + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/name: hugegraph + - ports: + - port: 8500 + protocol: TCP + - port: 8520 + protocol: TCP + to: + - podSelector: + matchLabels: + app.kubernetes.io/component: store + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/name: hugegraph + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + ingress: + - from: + - podSelector: + matchLabels: + app.kubernetes.io/component: test + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/name: hugegraph + - podSelector: + matchLabels: + app.kubernetes.io/component: hubble + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/name: hugegraph + ports: + - port: 8080 + protocol: TCP + podSelector: + matchLabels: + app.kubernetes.io/component: server + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/name: hugegraph + policyTypes: + - Ingress + - Egress + 4: | + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + labels: + app.kubernetes.io/component: hubble + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: hugegraph + app.kubernetes.io/version: latest + helm.sh/chart: hugegraph-0.1.0 + name: RELEASE-NAME-hugegraph-hubble + spec: + egress: + - ports: + - port: 8080 + protocol: TCP + to: + - podSelector: + matchLabels: + app.kubernetes.io/component: server + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/name: hugegraph + - ports: + - port: 8686 + protocol: TCP + - port: 8620 + protocol: TCP + to: + - podSelector: + matchLabels: + app.kubernetes.io/component: pd + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/name: hugegraph + - ports: + - port: 8520 + protocol: TCP + to: + - podSelector: + matchLabels: + app.kubernetes.io/component: store + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/name: hugegraph + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + podSelector: + matchLabels: + app.kubernetes.io/component: hubble + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/name: hugegraph + policyTypes: + - Ingress + - Egress diff --git a/helm/hugegraph/tests/auth_checksum_source_test.yaml b/helm/hugegraph/tests/auth_checksum_source_test.yaml new file mode 100644 index 0000000000..a2f4fea187 --- /dev/null +++ b/helm/hugegraph/tests/auth_checksum_source_test.yaml @@ -0,0 +1,89 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Each rollout checksum picks its revision input by credential source. These +# cases pin the exact digest, so they fail if the parts list changes at all, +# not merely if it stops being 64 hex characters. Each expected value is +# sha256 of the parts joined with "|", computed outside Helm: +# +# inline PD value sha256("t-pd-auth|secret-key|" + sha256(value)) +# PD existingSecret sha256("byo-pd-secret|secret-key") +# +# The existingSecret cases are the ones that prove the selection: the inline +# value is set there too, and it must not reach the annotation, because the +# Pods do not read it. +# +# A render has no live Secret, so these cases cover the inline half only. The +# lookup half, where mixing a live resourceVersion into an inline rotation +# caused a second rollout on the following no-change upgrade, is observable +# only against a cluster and belongs to the lifecycle run. +suite: Rollout checksum revision source +release: + name: t +tests: + - it: hashes the inline PD value with the Secret name and key, and nothing else + template: pd-statefulset.yaml + set: + pd.auth.value: pd-secret-steady + asserts: + - equal: + path: spec.template.metadata.annotations["checksum/pd-auth"] + value: c3e18a4d518ea495ef75e4fec03a68675b23ef0be93714e7a777fbb50a5957c1 + + - it: drops the inline PD digest once an existingSecret supplies the credential + template: pd-statefulset.yaml + set: + pd.auth.existingSecret: byo-pd-secret + pd.auth.value: ignored-by-the-pods + asserts: + - equal: + path: spec.template.metadata.annotations["checksum/pd-auth"] + value: 86fdc89040752c37324032343cd1d9a53ca23a46f724689dd61c2f5f829c0d33 + + - it: hashes both inline Server credentials with their Secret names and keys + template: server-deployment.yaml + set: + server.auth.admin.password: admin-after + server.auth.token.value: 0123456789abcdef0123456789abcdef + asserts: + - equal: + path: spec.template.metadata.annotations["checksum/auth"] + value: d6ce6518e8b740a46bbacf0b55e4094580048c87912c85f21448b701f972fd72 + + - it: selects the admin and token sources independently + template: server-deployment.yaml + set: + server.auth.admin.existingSecret: byo-admin + server.auth.admin.password: ignored-by-the-pods + server.auth.token.value: 0123456789abcdef0123456789abcdef + asserts: + - equal: + path: spec.template.metadata.annotations["checksum/auth"] + value: 7f13191bccb01ef3b2c6ffcce2fc6b6a73a6c6416583dcc4c593a5cfde1a24a0 + + - it: keeps credential plaintext out of the annotation + template: server-deployment.yaml + set: + server.auth.admin.password: admin-plaintext-value + server.auth.token.value: 0123456789abcdef0123456789abcdef + asserts: + - notMatchRegex: + path: spec.template.metadata.annotations["checksum/auth"] + pattern: admin-plaintext-value + - notMatchRegex: + path: spec.template.metadata.annotations["checksum/auth"] + pattern: 0123456789abcdef0123456789abcdef diff --git a/helm/hugegraph/tests/auth_token_length_test.yaml b/helm/hugegraph/tests/auth_token_length_test.yaml new file mode 100644 index 0000000000..0ca92a38c5 --- /dev/null +++ b/helm/hugegraph/tests/auth_token_length_test.yaml @@ -0,0 +1,38 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +suite: Auth token length guard +templates: + - server-deployment.yaml +tests: + - it: accepts an empty token value, which defers to autoGenerate + asserts: + - hasDocuments: + count: 1 + + - it: accepts a signing key of at least 32 characters + set: + server.auth.token.value: ktestJwtSigningKey2026xyzABCDEFGH12345678 + asserts: + - hasDocuments: + count: 1 + + - it: rejects a signing key shorter than 32 characters + set: + server.auth.token.value: tooshortkey + asserts: + - failedTemplate: {} diff --git a/helm/hugegraph/tests/cluster_preset_resources_test.yaml b/helm/hugegraph/tests/cluster_preset_resources_test.yaml new file mode 100644 index 0000000000..44a515c056 --- /dev/null +++ b/helm/hugegraph/tests/cluster_preset_resources_test.yaml @@ -0,0 +1,40 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +suite: Cluster preset Store memory +values: + - ../values-cluster.yaml +tests: + - it: gives the Store room for the caches the image commits outside its heap + template: store-statefulset.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].resources.limits.memory + value: 8Gi + - equal: + path: spec.template.spec.containers[0].resources.requests.memory + value: 5Gi + + - it: keeps the Store heap flags the memory budget is derived from + template: store-statefulset.yaml + asserts: + - matchRegex: + path: spec.template.spec.containers[0].env[?(@.name=="JAVA_OPTS")].value + pattern: -Xmx1024m + - matchRegex: + path: spec.template.spec.containers[0].env[?(@.name=="JAVA_OPTS")].value + pattern: -XX:MaxDirectMemorySize=512m diff --git a/helm/hugegraph/tests/image_digest_test.yaml b/helm/hugegraph/tests/image_digest_test.yaml new file mode 100644 index 0000000000..da518908bd --- /dev/null +++ b/helm/hugegraph/tests/image_digest_test.yaml @@ -0,0 +1,57 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +suite: Image reference and digest pinning +tests: + - it: renders repository and tag when no digest is set + template: pd-statefulset.yaml + set: + pd.image.tag: pinned-for-test + asserts: + - equal: + path: spec.template.spec.containers[0].image + value: hugegraph/pd:pinned-for-test + + - it: pins by digest when one is supplied, ignoring the tag + template: pd-statefulset.yaml + set: + pd.image.digest: sha256:43999a5ccda34883a9e4e458e25cb903ce34adbc66782d9f4a5260d68b2b5a82 + asserts: + - equal: + path: spec.template.spec.containers[0].image + value: hugegraph/pd@sha256:43999a5ccda34883a9e4e458e25cb903ce34adbc66782d9f4a5260d68b2b5a82 + - notMatchRegex: + path: spec.template.spec.containers[0].image + pattern: "helm-dev" + + - it: supports digest pinning on store as well + template: store-statefulset.yaml + set: + store.image.digest: sha256:458d77a18542e8a2f7980b075f2fda32026d582200a8983921354398467ff860 + asserts: + - matchRegex: + path: spec.template.spec.containers[0].image + pattern: "^hugegraph/store@sha256:458d77a1" + + - it: supports digest pinning on server as well + template: server-deployment.yaml + set: + server.image.digest: sha256:30f99c6b9ab605accf96e9c179434b13495547f532dcb89d840231c54f1cc101 + asserts: + - matchRegex: + path: spec.template.spec.containers[0].image + pattern: "^hugegraph/server@sha256:30f99c6b" diff --git a/helm/hugegraph/tests/networkpolicy_test.yaml b/helm/hugegraph/tests/networkpolicy_test.yaml new file mode 100644 index 0000000000..0bdcbc45c6 --- /dev/null +++ b/helm/hugegraph/tests/networkpolicy_test.yaml @@ -0,0 +1,913 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +suite: NetworkPolicy +templates: + - networkpolicy.yaml +tests: + - it: renders nothing with the base values + asserts: + - hasDocuments: + count: 0 + + - it: renders nothing with the single preset as shipped + values: + - ../values-single.yaml + asserts: + - hasDocuments: + count: 0 + + - it: renders pd, store and server policies on the cluster preset + values: + - ../values-cluster.yaml + asserts: + - hasDocuments: + count: 3 + - isKind: + of: NetworkPolicy + + - it: renders pd, store and server policies on the single preset when enabled + values: + - ../values-single.yaml + set: + networkPolicy.enabled: true + asserts: + - hasDocuments: + count: 3 + + - it: adds a Hubble policy when Hubble is enabled + values: + - ../values-cluster.yaml + set: + hubble.enabled: true + asserts: + - hasDocuments: + count: 4 + + - it: isolates PD in both directions and admits only its peers and clients + values: + - ../values-cluster.yaml + set: + hubble.enabled: true + documentSelector: + path: metadata.name + value: RELEASE-NAME-hugegraph-pd + asserts: + - equal: + path: spec.podSelector.matchLabels + value: + app.kubernetes.io/name: hugegraph + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/component: pd + - equal: + path: spec.policyTypes + value: [Ingress, Egress] + - lengthEqual: + path: spec.ingress + count: 2 + - equal: + path: spec.ingress[0] + value: + from: + - podSelector: + matchLabels: + app.kubernetes.io/name: hugegraph + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/component: pd + ports: + - protocol: TCP + port: 8610 + - protocol: TCP + port: 8686 + - lengthEqual: + path: spec.ingress[1].from + count: 3 + - equal: + path: spec.ingress[1].from[0].podSelector.matchLabels["app.kubernetes.io/component"] + value: store + - equal: + path: spec.ingress[1].from[1].podSelector.matchLabels["app.kubernetes.io/component"] + value: server + - equal: + path: spec.ingress[1].from[2].podSelector.matchLabels["app.kubernetes.io/component"] + value: hubble + - equal: + path: spec.ingress[1].ports + value: + - protocol: TCP + port: 8686 + - protocol: TCP + port: 8620 + - equal: + path: spec.egress[0].to[0].podSelector.matchLabels["app.kubernetes.io/component"] + value: pd + - equal: + path: spec.egress[0].ports + value: + - protocol: TCP + port: 8610 + - protocol: TCP + port: 8686 + - equal: + path: spec.egress[1] + value: + ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + - lengthEqual: + path: spec.egress + count: 2 + + - it: lets Store peers use raft only and admits Server and Hubble on their ports + values: + - ../values-cluster.yaml + set: + hubble.enabled: true + documentSelector: + path: metadata.name + value: RELEASE-NAME-hugegraph-store + asserts: + - lengthEqual: + path: spec.ingress + count: 3 + - lengthEqual: + path: spec.ingress[0].from + count: 1 + - lengthEqual: + path: spec.ingress[1].from + count: 1 + - lengthEqual: + path: spec.ingress[2].from + count: 1 + - lengthEqual: + path: spec.egress[0].to + count: 1 + - lengthEqual: + path: spec.egress[1].to + count: 1 + - equal: + path: spec.ingress[0].from[0].podSelector.matchLabels["app.kubernetes.io/component"] + value: store + - equal: + path: spec.ingress[0].ports + value: + - protocol: TCP + port: 8510 + - equal: + path: spec.ingress[1].from[0].podSelector.matchLabels["app.kubernetes.io/component"] + value: server + - equal: + path: spec.ingress[1].ports + value: + - protocol: TCP + port: 8500 + - protocol: TCP + port: 8520 + - equal: + path: spec.ingress[2].from[0].podSelector.matchLabels["app.kubernetes.io/component"] + value: hubble + - equal: + path: spec.ingress[2].ports + value: + - protocol: TCP + port: 8520 + - equal: + path: spec.egress[0].to[0].podSelector.matchLabels["app.kubernetes.io/component"] + value: pd + - equal: + path: spec.egress[0].ports + value: + - protocol: TCP + port: 8686 + - protocol: TCP + port: 8620 + - equal: + path: spec.egress[1].to[0].podSelector.matchLabels["app.kubernetes.io/component"] + value: store + - equal: + path: spec.egress[1].ports + value: + - protocol: TCP + port: 8510 + - equal: + path: spec.egress[2].ports + value: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + + - it: admits the test hook and Hubble to the Server and lets it reach PD and Store + values: + - ../values-cluster.yaml + set: + hubble.enabled: true + documentSelector: + path: metadata.name + value: RELEASE-NAME-hugegraph-server + asserts: + - lengthEqual: + path: spec.egress[0].to + count: 1 + - lengthEqual: + path: spec.egress[1].to + count: 1 + - lengthEqual: + path: spec.egress + count: 3 + - lengthEqual: + path: spec.ingress + count: 1 + - lengthEqual: + path: spec.ingress[0].from + count: 2 + - equal: + path: spec.ingress[0].from[0].podSelector.matchLabels["app.kubernetes.io/component"] + value: test + - equal: + path: spec.ingress[0].from[1].podSelector.matchLabels["app.kubernetes.io/component"] + value: hubble + - equal: + path: spec.ingress[0].ports + value: + - protocol: TCP + port: 8080 + - equal: + path: spec.egress[0].ports + value: + - protocol: TCP + port: 8686 + - protocol: TCP + port: 8620 + - equal: + path: spec.egress[1].to[0].podSelector.matchLabels["app.kubernetes.io/component"] + value: store + - equal: + path: spec.egress[1].ports + value: + - protocol: TCP + port: 8500 + - protocol: TCP + port: 8520 + - equal: + path: spec.egress[2].ports + value: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + + - it: lets PD-mode Hubble reach Server, PD and Store REST and nothing reach it + values: + - ../values-cluster.yaml + set: + hubble.enabled: true + documentSelector: + path: metadata.name + value: RELEASE-NAME-hugegraph-hubble + asserts: + - lengthEqual: + path: spec.egress + count: 4 + - notExists: + path: spec.ingress + - contains: + path: spec.policyTypes + content: Ingress + - equal: + path: spec.egress[0].to[0].podSelector.matchLabels["app.kubernetes.io/component"] + value: server + - equal: + path: spec.egress[0].ports + value: + - protocol: TCP + port: 8080 + - equal: + path: spec.egress[1].to[0].podSelector.matchLabels["app.kubernetes.io/component"] + value: pd + - equal: + path: spec.egress[1].ports + value: + - protocol: TCP + port: 8686 + - protocol: TCP + port: 8620 + - equal: + path: spec.egress[2].to[0].podSelector.matchLabels["app.kubernetes.io/component"] + value: store + - equal: + path: spec.egress[2].ports + value: + - protocol: TCP + port: 8520 + - equal: + path: spec.egress[3].ports + value: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + + - it: keeps direct-mode Hubble to the Server only + values: + - ../values-cluster.yaml + set: + hubble.enabled: true + hubble.mode: direct + asserts: + - lengthEqual: + path: spec.egress + count: 2 + documentSelector: + path: metadata.name + value: RELEASE-NAME-hugegraph-hubble + - lengthEqual: + path: spec.ingress[1].from + count: 2 + documentSelector: + path: metadata.name + value: RELEASE-NAME-hugegraph-pd + - equal: + path: spec.ingress[1].from[0].podSelector.matchLabels["app.kubernetes.io/component"] + value: store + documentSelector: + path: metadata.name + value: RELEASE-NAME-hugegraph-pd + - equal: + path: spec.ingress[1].from[1].podSelector.matchLabels["app.kubernetes.io/component"] + value: server + documentSelector: + path: metadata.name + value: RELEASE-NAME-hugegraph-pd + - lengthEqual: + path: spec.ingress + count: 2 + documentSelector: + path: metadata.name + value: RELEASE-NAME-hugegraph-store + + - it: leaves every Hubble peer out when Hubble is disabled + values: + - ../values-cluster.yaml + asserts: + - hasDocuments: + count: 3 + - notContains: + path: spec.egress + content: + to: + - podSelector: + matchLabels: + app.kubernetes.io/name: hugegraph + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/component: hubble + - notContains: + path: spec.ingress[1].from + content: + podSelector: + matchLabels: + app.kubernetes.io/name: hugegraph + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/component: hubble + documentSelector: + path: metadata.name + value: RELEASE-NAME-hugegraph-pd + - lengthEqual: + path: spec.ingress + count: 2 + documentSelector: + path: metadata.name + value: RELEASE-NAME-hugegraph-store + - lengthEqual: + path: spec.ingress[0].from + count: 1 + documentSelector: + path: metadata.name + value: RELEASE-NAME-hugegraph-server + - equal: + path: spec.ingress[0].from[0].podSelector.matchLabels["app.kubernetes.io/component"] + value: test + documentSelector: + path: metadata.name + value: RELEASE-NAME-hugegraph-server + + - it: follows port overrides in ingress and egress + values: + - ../values-cluster.yaml + set: + pd.ports.raft: 9610 + store.ports.rest: 9520 + server.port: 9080 + asserts: + - equal: + path: spec.ingress[0].ports + value: + - protocol: TCP + port: 9610 + - protocol: TCP + port: 8686 + documentSelector: + path: metadata.name + value: RELEASE-NAME-hugegraph-pd + - equal: + path: spec.egress[1].ports + value: + - protocol: TCP + port: 8500 + - protocol: TCP + port: 9520 + documentSelector: + path: metadata.name + value: RELEASE-NAME-hugegraph-server + - equal: + path: spec.ingress[0].ports + value: + - protocol: TCP + port: 9080 + documentSelector: + path: metadata.name + value: RELEASE-NAME-hugegraph-server + + - it: appends the PD extraIngress when PD is exposed and adds nothing open + values: + - ../values-cluster.yaml + set: + pd.service.type: NodePort + pd.service.allowInsecureExposure: true + networkPolicy.pd.extraIngress: + - from: + - ipBlock: + cidr: 10.0.0.0/8 + ports: + - port: 8620 + documentSelector: + path: metadata.name + value: RELEASE-NAME-hugegraph-pd + asserts: + - lengthEqual: + path: spec.ingress + count: 3 + - exists: + path: spec.ingress[0].from + - exists: + path: spec.ingress[1].from + - equal: + path: spec.ingress[2] + value: + from: + - ipBlock: + cidr: 10.0.0.0/8 + ports: + - port: 8620 + + - it: appends the Server extraIngress for a LoadBalancer Service and adds nothing open + values: + - ../values-cluster.yaml + set: + server.service.type: LoadBalancer + networkPolicy.server.extraIngress: + - from: + - ipBlock: + cidr: 192.168.0.0/16 + documentSelector: + path: metadata.name + value: RELEASE-NAME-hugegraph-server + asserts: + - lengthEqual: + path: spec.ingress + count: 2 + - exists: + path: spec.ingress[0].from + - equal: + path: spec.ingress[1].from[0].ipBlock.cidr + value: 192.168.0.0/16 + + - it: appends the Server extraIngress for an Ingress and keeps the test peer first + values: + - ../values-cluster.yaml + set: + server.ingress.enabled: true + server.ingress.allowPlainHttp: true + networkPolicy.server.extraIngress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: ingress-nginx + podSelector: + matchLabels: + app.kubernetes.io/name: ingress-nginx + ports: + - port: 8080 + documentSelector: + path: metadata.name + value: RELEASE-NAME-hugegraph-server + asserts: + - lengthEqual: + path: spec.ingress + count: 2 + - equal: + path: spec.ingress[0].from[0].podSelector.matchLabels["app.kubernetes.io/component"] + value: test + - equal: + path: spec.ingress[1] + value: + from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: ingress-nginx + podSelector: + matchLabels: + app.kubernetes.io/name: ingress-nginx + ports: + - port: 8080 + + - it: appends the Server extraIngress for an advertised URL and adds nothing open + values: + - ../values-cluster.yaml + set: + server.advertiseUrl: http://hg.example.com:8080 + networkPolicy.server.extraIngress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: proxy + documentSelector: + path: metadata.name + value: RELEASE-NAME-hugegraph-server + asserts: + - lengthEqual: + path: spec.ingress + count: 2 + - exists: + path: spec.ingress[1].from + + - it: gives an exposed Hubble exactly the listed ingress rules + values: + - ../values-cluster.yaml + set: + hubble.enabled: true + hubble.service.type: NodePort + networkPolicy.hubble.extraIngress: + - from: + - ipBlock: + cidr: 172.18.0.0/16 + ports: + - port: 8088 + documentSelector: + path: metadata.name + value: RELEASE-NAME-hugegraph-hubble + asserts: + - equal: + path: spec.ingress + value: + - from: + - ipBlock: + cidr: 172.18.0.0/16 + ports: + - port: 8088 + + - it: renders no rule without a peer on any document when every exposure is set + values: + - ../values-cluster.yaml + set: + hubble.enabled: true + pd.service.type: LoadBalancer + pd.service.allowInsecureExposure: true + server.service.type: NodePort + server.advertiseUrl: http://hg.example.com:8080 + hubble.service.type: NodePort + networkPolicy.pd.extraIngress: + - from: + - ipBlock: + cidr: 10.0.0.0/8 + networkPolicy.server.extraIngress: + - from: + - ipBlock: + cidr: 10.0.0.0/8 + networkPolicy.hubble.extraIngress: + - from: + - ipBlock: + cidr: 10.0.0.0/8 + asserts: + - hasDocuments: + count: 4 + - notContains: + path: spec.ingress + content: + ports: + - protocol: TCP + port: 8686 + - protocol: TCP + port: 8620 + - notContains: + path: spec.ingress + content: + ports: + - protocol: TCP + port: 8080 + - notContains: + path: spec.ingress + content: + ports: + - protocol: TCP + port: 8088 + + - it: selects a component on every document and renders no default-deny object + values: + - ../values-cluster.yaml + set: + hubble.enabled: true + asserts: + - hasDocuments: + count: 4 + - exists: + path: spec.podSelector.matchLabels["app.kubernetes.io/component"] + - exists: + path: spec.podSelector.matchLabels["app.kubernetes.io/instance"] + + - it: follows nameOverride in every selector and fullnameOverride in names + values: + - ../values-cluster.yaml + set: + nameOverride: graphdb + fullnameOverride: x + asserts: + - equal: + path: metadata.name + value: x-pd + documentIndex: 0 + - equal: + path: metadata.name + value: x-store + documentIndex: 1 + - equal: + path: metadata.name + value: x-server + documentIndex: 2 + - equal: + path: spec.podSelector.matchLabels["app.kubernetes.io/name"] + value: graphdb + - equal: + path: spec.ingress[0].from[0].podSelector.matchLabels["app.kubernetes.io/name"] + value: graphdb + - equal: + path: spec.egress[0].to[0].podSelector.matchLabels["app.kubernetes.io/name"] + value: graphdb + + - it: keeps names within 63 characters and the full release name in selectors + release: + name: abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabc + values: + - ../values-cluster.yaml + set: + hubble.enabled: true + asserts: + - matchRegex: + path: metadata.name + pattern: ^[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?$ + - equal: + path: spec.podSelector.matchLabels["app.kubernetes.io/instance"] + value: abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabc + - equal: + path: spec.egress[0].to[0].podSelector.matchLabels["app.kubernetes.io/instance"] + value: abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabc + + - it: keeps two extraIngress rules in order after the chart rules + values: + - ../values-cluster.yaml + set: + networkPolicy.pd.extraIngress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: monitoring + ports: + - port: 8620 + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: vermeer + ports: + - port: 8686 + documentSelector: + path: metadata.name + value: RELEASE-NAME-hugegraph-pd + asserts: + - lengthEqual: + path: spec.ingress + count: 4 + - equal: + path: spec.ingress[2].from[0].namespaceSelector.matchLabels["kubernetes.io/metadata.name"] + value: monitoring + - equal: + path: spec.ingress[3].from[0].namespaceSelector.matchLabels["kubernetes.io/metadata.name"] + value: vermeer + + - it: ignores Hubble extra rules while Hubble is disabled + values: + - ../values-cluster.yaml + set: + networkPolicy.hubble.extraIngress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: ops + networkPolicy.hubble.extraEgress: + - to: + - ipBlock: + cidr: 10.0.0.10/32 + asserts: + - hasDocuments: + count: 3 + - notContains: + path: spec.ingress + content: + from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: ops + + - it: treats a whitespace-only advertiseUrl as unset + values: + - ../values-cluster.yaml + set: + server.advertiseUrl: " " + documentSelector: + path: metadata.name + value: RELEASE-NAME-hugegraph-server + asserts: + - lengthEqual: + path: spec.ingress + count: 1 + + - it: renders a component whose networkPolicy key was removed + values: + - ../values-cluster.yaml + set: + networkPolicy.pd: null + asserts: + - hasDocuments: + count: 3 + + - it: matches the reviewed cluster plus Hubble render + values: + - ../values-cluster.yaml + set: + hubble.enabled: true + asserts: + - matchSnapshot: {} + + - it: appends extra rules verbatim + values: + - ../values-cluster.yaml + set: + hubble.enabled: true + networkPolicy.store.extraIngress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: monitoring + ports: + - port: 8520 + networkPolicy.hubble.extraEgress: + - to: + - ipBlock: + cidr: 10.0.0.10/32 + ports: + - port: 9200 + asserts: + - contains: + path: spec.ingress + content: + from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: monitoring + ports: + - port: 8520 + documentSelector: + path: metadata.name + value: RELEASE-NAME-hugegraph-store + - contains: + path: spec.egress + content: + to: + - ipBlock: + cidr: 10.0.0.10/32 + ports: + - port: 9200 + documentSelector: + path: metadata.name + value: RELEASE-NAME-hugegraph-hubble + + - it: rejects a non-boolean enabled flag through the schema + set: + networkPolicy.enabled: "yes" + asserts: + - failedTemplate: + errorPattern: "networkPolicy" + + - it: rejects egress rules on components that take none + set: + networkPolicy.store.extraEgress: + - to: + - ipBlock: + cidr: 10.0.0.0/8 + asserts: + - failedTemplate: + errorPattern: "(additional properties 'extraEgress' not allowed|Additional property extraEgress is not allowed)" + + - it: rejects a non-list extraIngress + set: + networkPolicy.pd.extraIngress: "abc" + asserts: + - failedTemplate: + errorPattern: "extraIngress" + + - it: rejects a null extraIngress rule + set: + networkPolicy.pd.extraIngress: + - null + asserts: + - failedTemplate: + errorPattern: "extraIngress" + + - it: rejects an unknown networkPolicy key + set: + networkPolicy.unknown: 1 + asserts: + - failedTemplate: + errorPattern: "(additional properties 'unknown' not allowed|Additional property unknown is not allowed)" + + - it: rejects the removed ingressControllers key + set: + networkPolicy.ingressControllers: [] + asserts: + - failedTemplate: + errorPattern: "(additional properties 'ingressControllers' not allowed|Additional property ingressControllers is not allowed)" + + - it: rejects a string enabled flag + set: + networkPolicy.enabled: "true" + asserts: + - failedTemplate: + errorPattern: "networkPolicy[./]enabled" + + - it: rejects an extraIngress rule without from + set: + networkPolicy.server.extraIngress: + - ports: + - port: 8080 + asserts: + - failedTemplate: + errorPattern: "(missing property 'from'|from is required)" + + - it: rejects an extraIngress rule with an empty from + set: + networkPolicy.server.extraIngress: + - from: [] + asserts: + - failedTemplate: + errorPattern: "(minItems|at least 1 items)" + + - it: rejects an empty extraIngress rule + set: + networkPolicy.pd.extraIngress: + - {} + asserts: + - failedTemplate: + errorPattern: "(missing property 'from'|from is required)" + + - it: rejects an empty peer + set: + networkPolicy.pd.extraIngress: + - from: + - {} + asserts: + - failedTemplate: + errorPattern: "(minProperties|at least 1 properties)" + + - it: rejects a Hubble egress rule without to + set: + networkPolicy.hubble.extraEgress: + - ports: + - port: 9200 + asserts: + - failedTemplate: + errorPattern: "(missing property 'to'|to is required)" diff --git a/helm/hugegraph/tests/pd_auth_secret_test.yaml b/helm/hugegraph/tests/pd_auth_secret_test.yaml new file mode 100644 index 0000000000..89ed20302c --- /dev/null +++ b/helm/hugegraph/tests/pd_auth_secret_test.yaml @@ -0,0 +1,137 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +suite: PD REST auth Secret wiring +tests: + - it: creates a kept release-pd-auth Secret by default + template: pd-auth-secret.yaml + asserts: + - hasDocuments: + count: 1 + - equal: + path: metadata.name + value: RELEASE-NAME-pd-auth + - equal: + path: metadata.annotations["helm.sh/resource-policy"] + value: keep + - isNotEmpty: + path: data["secret-key"] + + - it: renders no Secret when an operator-supplied Secret is named + template: pd-auth-secret.yaml + set: + pd.auth.existingSecret: my-pd-secret + asserts: + - hasDocuments: + count: 0 + + - it: hands PD the secret from the Secret, never a literal value + template: pd-statefulset.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: HG_PD_AUTH_SECRET_KEY + valueFrom: + secretKeyRef: + name: RELEASE-NAME-pd-auth + key: secret-key + - isNotEmpty: + path: spec.template.metadata.annotations["checksum/pd-auth"] + + - it: gives the Server storage wait the same secret as PD_AUTH_PASSWORD + template: server-deployment.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: PD_AUTH_PASSWORD + valueFrom: + secretKeyRef: + name: RELEASE-NAME-pd-auth + key: secret-key + - isNotEmpty: + path: spec.template.metadata.annotations["checksum/pd-auth"] + + - it: gives Hubble the secret and writes it into the properties file + template: hubble-deployment.yaml + set: + hubble.enabled: true + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: HG_HUBBLE_PD_PASSWORD + valueFrom: + secretKeyRef: + name: RELEASE-NAME-pd-auth + key: secret-key + - matchRegex: + path: spec.template.spec.containers[0].args[0] + pattern: "operations\\.pd\\.username=hubble" + - matchRegex: + path: spec.template.spec.containers[0].args[0] + pattern: "operations\\.pd\\.password=%s" + - isNotEmpty: + path: spec.template.metadata.annotations["checksum/pd-auth"] + + - it: points PD at the operator-supplied Secret and key + template: pd-statefulset.yaml + set: + pd.auth.existingSecret: my-pd-secret + pd.auth.key: pd-pass + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: HG_PD_AUTH_SECRET_KEY + valueFrom: + secretKeyRef: + name: my-pd-secret + key: pd-pass + + - it: points the Server storage wait at the operator-supplied Secret and key + template: server-deployment.yaml + set: + pd.auth.existingSecret: my-pd-secret + pd.auth.key: pd-pass + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: PD_AUTH_PASSWORD + valueFrom: + secretKeyRef: + name: my-pd-secret + key: pd-pass + + - it: rejects operator overrides of the chart-managed PD_AUTH_PASSWORD + template: server-deployment.yaml + set: + server.extraEnv: + - name: PD_AUTH_PASSWORD + value: x + asserts: + - failedTemplate: + errorPattern: "server.extraEnv must not set the chart-managed variable PD_AUTH_PASSWORD" + + - it: keeps a YAML-coercible Secret key literal + template: server-secret.yaml + set: + server.auth.admin.key: "on" + asserts: + - exists: + path: data.on diff --git a/helm/hugegraph/tests/pd_javaopts_test.yaml b/helm/hugegraph/tests/pd_javaopts_test.yaml new file mode 100644 index 0000000000..fa0d506fc4 --- /dev/null +++ b/helm/hugegraph/tests/pd_javaopts_test.yaml @@ -0,0 +1,66 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +suite: PD JAVA_OPTS derivation +templates: + - pd-statefulset.yaml +tests: + - it: disables the raft IP whitelist in-cluster by default + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: JAVA_OPTS + value: "-Dpartition.default-shard-count=3 -Draft.ip-whitelist.enabled=false -Draft.rpc-timeout=3000" + + - it: re-enables the whitelist when the operator opts in + set: + pd.raftIpWhitelistEnabled: true + asserts: + - matchRegex: + path: spec.template.spec.containers[0].env[?(@.name=="JAVA_OPTS")].value + pattern: "-Draft\\.ip-whitelist\\.enabled=true" + + - it: derives shard count 1 when store replicas are below 3 + set: + store.replicas: 1 + asserts: + - matchRegex: + path: spec.template.spec.containers[0].env[?(@.name=="JAVA_OPTS")].value + pattern: "-Dpartition\\.default-shard-count=1" + + - it: appends operator javaOpts after the derived flags + set: + pd.javaOpts: "-Xmx2g" + asserts: + - matchRegex: + path: spec.template.spec.containers[0].env[?(@.name=="JAVA_OPTS")].value + pattern: "-Draft\\.rpc-timeout=3000 -Xmx2g$" + - it: renders a custom raft RPC timeout + set: + pd.raftRpcTimeoutMs: 5000 + asserts: + - matchRegex: + path: spec.template.spec.containers[0].env[?(@.name=="JAVA_OPTS")].value + pattern: "-Draft\\.rpc-timeout=5000" + - it: omits the raft RPC timeout when set empty, preserving the image default + set: + pd.raftRpcTimeoutMs: "" + asserts: + - notMatchRegex: + path: spec.template.spec.containers[0].env[?(@.name=="JAVA_OPTS")].value + pattern: "rpc-timeout" diff --git a/helm/hugegraph/tests/pd_readiness_path_test.yaml b/helm/hugegraph/tests/pd_readiness_path_test.yaml new file mode 100644 index 0000000000..33d4d230ad --- /dev/null +++ b/helm/hugegraph/tests/pd_readiness_path_test.yaml @@ -0,0 +1,117 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +suite: PD readiness path and Store wait path +tests: + - it: reads PD readiness from /v1/ready by default, startup and liveness from /v1/health + template: pd-statefulset.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].readinessProbe.httpGet.path + value: /v1/ready + - equal: + path: spec.template.spec.containers[0].startupProbe.httpGet.path + value: /v1/health + - equal: + path: spec.template.spec.containers[0].livenessProbe.httpGet.path + value: /v1/health + + - it: moves only the PD readiness probe when pd.readinessPath is set back for an older image + template: pd-statefulset.yaml + set: + pd.readinessPath: /v1/health + asserts: + - equal: + path: spec.template.spec.containers[0].readinessProbe.httpGet.path + value: /v1/health + - equal: + path: spec.template.spec.containers[0].startupProbe.httpGet.path + value: /v1/health + - equal: + path: spec.template.spec.containers[0].livenessProbe.httpGet.path + value: /v1/health + + - it: polls /v1/ready on each PD peer in the Store wait by default + template: store-statefulset.yaml + asserts: + - matchRegex: + path: spec.template.spec.initContainers[0].command[2] + pattern: 'WAIT_PATH="/v1/ready"' + - matchRegex: + path: spec.template.spec.initContainers[0].command[2] + pattern: 'curl -fsS --connect-timeout 2 --max-time 5 "http://\${peer}\${WAIT_PATH}"' + + - it: polls store.waitPath on each PD peer when set back for an older image + template: store-statefulset.yaml + set: + store.waitPath: /v1/health + asserts: + - matchRegex: + path: spec.template.spec.initContainers[0].command[2] + pattern: 'WAIT_PATH="/v1/health"' + - notMatchRegex: + path: spec.template.spec.initContainers[0].command[2] + pattern: 'v1/ready' + + - it: leaves the Store's own probes on /v1/health whatever store.waitPath is + template: store-statefulset.yaml + set: + store.waitPath: /v1/ready + asserts: + - equal: + path: spec.template.spec.containers[0].readinessProbe.httpGet.path + value: /v1/health + + - it: keeps PD startup and liveness off the raft-aware path with several PDs + template: pd-statefulset.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].startupProbe.httpGet.path + value: /v1/health + - equal: + path: spec.template.spec.containers[0].livenessProbe.httpGet.path + value: /v1/health + + # A single PD has no election to lose, so a permanent step-down has to be + # restartable; /v1/health would pass forever. Startup follows liveness so the + # boot-to-ready window is charged to the startup budget, not the liveness one. + - it: moves PD startup and liveness to the raft-aware path at one replica + template: pd-statefulset.yaml + set: + pd.replicas: 1 + asserts: + - equal: + path: spec.template.spec.containers[0].startupProbe.httpGet.path + value: /v1/ready + - equal: + path: spec.template.spec.containers[0].livenessProbe.httpGet.path + value: /v1/ready + - equal: + path: spec.template.spec.containers[0].readinessProbe.httpGet.path + value: /v1/ready + + - it: lets pd.livenessPath override the derived choice in both probes + template: pd-statefulset.yaml + set: + pd.livenessPath: /v1/ready + asserts: + - equal: + path: spec.template.spec.containers[0].startupProbe.httpGet.path + value: /v1/ready + - equal: + path: spec.template.spec.containers[0].livenessProbe.httpGet.path + value: /v1/ready diff --git a/helm/hugegraph/tests/server_auth_checksum_test.yaml b/helm/hugegraph/tests/server_auth_checksum_test.yaml new file mode 100644 index 0000000000..b25b6a67c8 --- /dev/null +++ b/helm/hugegraph/tests/server_auth_checksum_test.yaml @@ -0,0 +1,66 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +suite: Server auth checksum and secret wiring +templates: + - server-deployment.yaml +tests: + - it: stamps a checksum/auth annotation on the pod template + asserts: + - matchRegex: + path: spec.template.metadata.annotations["checksum/auth"] + pattern: "^[a-f0-9]{64}$" + + - it: keeps a valid checksum when an external admin Secret is supplied + set: + server.auth.admin.existingSecret: ext-admin-secret + server.auth.admin.key: password + asserts: + - matchRegex: + path: spec.template.metadata.annotations["checksum/auth"] + pattern: "^[a-f0-9]{64}$" + + - it: never places credential material in pod metadata + set: + server.auth.admin.password: sup3rs3cr3tpw + server.auth.token.value: t0k3nvalu3PADDINGtoREACH32charsMIN + asserts: + - notMatchRegex: + path: spec.template.metadata.annotations["checksum/auth"] + pattern: "(sup3rs3cr3tpw|t0k3nvalu3PADDING)" + + - it: sources the admin password from a Secret, never a literal value + set: + server.auth.admin.password: sup3rs3cr3tpw + asserts: + - exists: + path: spec.template.spec.containers[0].env[?(@.name=="PASSWORD")].valueFrom.secretKeyRef + - notExists: + path: spec.template.spec.containers[0].env[?(@.name=="PASSWORD")].value + + - it: points the admin Secret ref at an operator-supplied Secret when set + set: + server.auth.admin.existingSecret: ext-admin-secret + asserts: + - equal: + path: spec.template.spec.containers[0].env[?(@.name=="PASSWORD")].valueFrom.secretKeyRef.name + value: ext-admin-secret + + - it: shares one JWT signing key across replicas via a Secret ref + asserts: + - exists: + path: spec.template.spec.containers[0].env[?(@.name=="HG_SERVER_AUTH_TOKEN_SECRET")].valueFrom.secretKeyRef diff --git a/helm/hugegraph/tests/server_discovery_test.yaml b/helm/hugegraph/tests/server_discovery_test.yaml new file mode 100644 index 0000000000..63aca4db3d --- /dev/null +++ b/helm/hugegraph/tests/server_discovery_test.yaml @@ -0,0 +1,49 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +suite: Server replica discovery announced to PD +templates: + - server-deployment.yaml +tests: + - it: injects the pod IP through the downward API + asserts: + - equal: + path: spec.template.spec.containers[0].env[?(@.name=="POD_IP")].valueFrom.fieldRef.fieldPath + value: status.podIP + + - it: announces each pod's own address to PD by default + asserts: + - matchRegex: + path: spec.template.spec.containers[0].env[?(@.name=="HG_SERVER_URLS_TO_PD")].value + pattern: "^http://\\$\\(POD_IP\\):8080$" + + - it: announces the shared advertiseUrl instead when one is set + set: + server.advertiseUrl: http://graph.example.com:30080 + asserts: + - equal: + path: spec.template.spec.containers[0].env[?(@.name=="HG_SERVER_URLS_TO_PD")].value + value: http://graph.example.com:30080 + + - it: refuses an operator extraEnv that would shadow POD_IP + set: + server.extraEnv: + - name: POD_IP + value: 10.0.0.1 + asserts: + - failedTemplate: + errorPattern: "POD_IP" diff --git a/helm/hugegraph/tests/server_readiness_path_test.yaml b/helm/hugegraph/tests/server_readiness_path_test.yaml new file mode 100644 index 0000000000..aa4eeecfdf --- /dev/null +++ b/helm/hugegraph/tests/server_readiness_path_test.yaml @@ -0,0 +1,46 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +suite: Server readiness path +tests: + - it: reads all three Server probes from /versions by default + template: server-deployment.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].readinessProbe.httpGet.path + value: /versions + - equal: + path: spec.template.spec.containers[0].startupProbe.httpGet.path + value: /versions + - equal: + path: spec.template.spec.containers[0].livenessProbe.httpGet.path + value: /versions + + - it: moves only the Server readiness probe when server.readinessPath targets /readiness + template: server-deployment.yaml + set: + server.readinessPath: /readiness + asserts: + - equal: + path: spec.template.spec.containers[0].readinessProbe.httpGet.path + value: /readiness + - equal: + path: spec.template.spec.containers[0].startupProbe.httpGet.path + value: /versions + - equal: + path: spec.template.spec.containers[0].livenessProbe.httpGet.path + value: /versions diff --git a/helm/hugegraph/tests/server_startup_timeout_test.yaml b/helm/hugegraph/tests/server_startup_timeout_test.yaml new file mode 100644 index 0000000000..9ba6f68cf9 --- /dev/null +++ b/helm/hugegraph/tests/server_startup_timeout_test.yaml @@ -0,0 +1,76 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +suite: Server startup timeout tracks the startup probe budget +templates: + - server-deployment.yaml +tests: + - it: passes the derived 150 second start budget to the image + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: HG_SERVER_STARTUP_TIMEOUT_S + value: "150" + - equal: + path: spec.template.spec.containers[0].startupProbe.failureThreshold + value: 90 + - equal: + path: spec.template.spec.containers[0].startupProbe.periodSeconds + value: 5 + + - it: follows a raised startup probe budget + set: + server.probes.startup.failureThreshold: 200 + server.probes.startup.periodSeconds: 10 + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: HG_SERVER_STARTUP_TIMEOUT_S + value: "1700" + + - it: follows the 450 second probe floor when a lower failureThreshold is configured + set: + server.probes.startup.failureThreshold: 1 + server.probes.startup.periodSeconds: 5 + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: HG_SERVER_STARTUP_TIMEOUT_S + value: "150" + + - it: caps at the entrypoint's 86400 second maximum + set: + server.probes.startup.failureThreshold: 100000 + server.probes.startup.periodSeconds: 10 + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: HG_SERVER_STARTUP_TIMEOUT_S + value: "86400" + + - it: refuses an extraEnv override that would silently diverge from the probe + set: + server.extraEnv: + - name: HG_SERVER_STARTUP_TIMEOUT_S + value: "60" + asserts: + - failedTemplate: + errorMessage: server.extraEnv must not set the chart-managed variable HG_SERVER_STARTUP_TIMEOUT_S diff --git a/helm/hugegraph/tests/statefulset_hardening_test.yaml b/helm/hugegraph/tests/statefulset_hardening_test.yaml new file mode 100644 index 0000000000..aecbdda335 --- /dev/null +++ b/helm/hugegraph/tests/statefulset_hardening_test.yaml @@ -0,0 +1,51 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +suite: StatefulSet update and PVC retention hardening +templates: + - pd-statefulset.yaml + - store-statefulset.yaml +tests: + - it: declares an explicit updateStrategy + asserts: + - equal: + path: spec.updateStrategy.type + value: RollingUpdate + + - it: retains PVCs on delete and scale-down + asserts: + - equal: + path: spec.persistentVolumeClaimRetentionPolicy.whenDeleted + value: Retain + - equal: + path: spec.persistentVolumeClaimRetentionPolicy.whenScaled + value: Retain + + - it: uses Parallel pod management for raft bring-up + asserts: + - equal: + path: spec.podManagementPolicy + value: Parallel + + - it: honours an OnDelete updateStrategy override + set: + pd.updateStrategy.type: OnDelete + store.updateStrategy.type: OnDelete + asserts: + - equal: + path: spec.updateStrategy.type + value: OnDelete diff --git a/helm/hugegraph/tests/termination_grace_period_test.yaml b/helm/hugegraph/tests/termination_grace_period_test.yaml new file mode 100644 index 0000000000..0c65493b95 --- /dev/null +++ b/helm/hugegraph/tests/termination_grace_period_test.yaml @@ -0,0 +1,35 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +suite: Workload termination grace period overrides +templates: + - pd-statefulset.yaml + - store-statefulset.yaml + - server-deployment.yaml + - hubble-deployment.yaml +tests: + - it: preserves explicit zero grace periods + set: + hubble.enabled: true + pd.terminationGracePeriodSeconds: 0 + store.terminationGracePeriodSeconds: 0 + server.terminationGracePeriodSeconds: 0 + hubble.terminationGracePeriodSeconds: 0 + asserts: + - equal: + path: spec.template.spec.terminationGracePeriodSeconds + value: 0 diff --git a/helm/hugegraph/tests/test_hook_resources_test.yaml b/helm/hugegraph/tests/test_hook_resources_test.yaml new file mode 100644 index 0000000000..e96af6ba22 --- /dev/null +++ b/helm/hugegraph/tests/test_hook_resources_test.yaml @@ -0,0 +1,55 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +suite: Helm test hook is resource bounded +templates: + - tests/test-connection.yaml +tests: + - it: bounds the test hook by default so quota-managed namespaces accept it + asserts: + - exists: + path: spec.containers[0].resources.limits.cpu + - exists: + path: spec.containers[0].resources.limits.memory + - exists: + path: spec.containers[0].resources.requests.cpu + - exists: + path: spec.containers[0].resources.requests.memory + + - it: lets an operator override the hook resources + set: + server.testResources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 500m + memory: 128Mi + asserts: + - equal: + path: spec.containers[0].resources.limits.cpu + value: 500m + + - it: passes imagePullSecrets to the test hook Pod + set: + imagePullSecrets: + - name: registry-creds + asserts: + - contains: + path: spec.imagePullSecrets + content: + name: registry-creds diff --git a/helm/hugegraph/tests/topology_quorum_test.yaml b/helm/hugegraph/tests/topology_quorum_test.yaml new file mode 100644 index 0000000000..1069ed196d --- /dev/null +++ b/helm/hugegraph/tests/topology_quorum_test.yaml @@ -0,0 +1,109 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +suite: PD topology, quorum math and PDB gating +tests: + - it: points the PD StatefulSet at its own headless Service + template: pd-statefulset.yaml + asserts: + - matchRegex: + path: spec.serviceName + pattern: "-hugegraph-pd$" + + - it: builds a DNS-based raft peer list that tracks pd.replicas + template: pd-statefulset.yaml + set: + pd.replicas: 3 + asserts: + - matchRegex: + path: spec.template.spec.containers[0].env[?(@.name=="HG_PD_RAFT_PEERS_LIST")].value + pattern: "pd-0\\..*svc.*pd-1\\..*svc.*pd-2\\..*svc" + + - it: shrinks the raft peer list for a single-node install + template: pd-statefulset.yaml + set: + pd.replicas: 1 + pd.partition.defaultShardCount: 1 + store.replicas: 1 + asserts: + - notMatchRegex: + path: spec.template.spec.containers[0].env[?(@.name=="HG_PD_RAFT_PEERS_LIST")].value + pattern: "pd-1\\." + + - it: waits for a majority of 2 when PD has 3 replicas + template: store-statefulset.yaml + set: + pd.replicas: 3 + asserts: + - matchRegex: + path: spec.template.spec.initContainers[0].command[2] + pattern: "REQUIRED=2" + + - it: waits for a majority of 3 when PD has 5 replicas + template: store-statefulset.yaml + set: + pd.replicas: 5 + pd.pdb.minAvailable: 3 + asserts: + - matchRegex: + path: spec.template.spec.initContainers[0].command[2] + pattern: "REQUIRED=3" + + - it: waits for a majority of 1 when PD is single-node + template: store-statefulset.yaml + set: + pd.replicas: 1 + pd.partition.defaultShardCount: 1 + store.replicas: 1 + asserts: + - matchRegex: + path: spec.template.spec.initContainers[0].command[2] + pattern: "REQUIRED=1" + + - it: emits no PodDisruptionBudget for a single-replica PD + template: pd-pdb.yaml + set: + pd.replicas: 1 + pd.partition.defaultShardCount: 1 + store.replicas: 1 + asserts: + - hasDocuments: + count: 0 + + - it: publishes not-ready addresses so peer DNS resolves before readiness + template: pd-service-headless.yaml + asserts: + - equal: + path: spec.publishNotReadyAddresses + value: true + - equal: + path: spec.clusterIP + value: None + + # The PD replica guard reads the live StatefulSet, which a render never has, + # so a fresh install at any supported replica count must pass. The guarded + # cases, growing or shrinking an initialized group, need a cluster and + # cannot be reached from a render. + - it: leaves a fresh install at five PD replicas alone + template: pd-statefulset.yaml + set: + pd.replicas: 5 + pd.pdb.minAvailable: 3 + asserts: + - equal: + path: spec.replicas + value: 5 diff --git a/helm/hugegraph/tests/validate_values_test.yaml b/helm/hugegraph/tests/validate_values_test.yaml new file mode 100644 index 0000000000..16d8453634 --- /dev/null +++ b/helm/hugegraph/tests/validate_values_test.yaml @@ -0,0 +1,229 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +suite: validateValues guard rails +templates: + - server-deployment.yaml +tests: + - it: rejects a PDB that would let PD drop below raft majority + set: + pd.pdb.minAvailable: 1 + asserts: + - failedTemplate: + errorPattern: "pd.pdb.minAvailable must be at least the PD Raft majority" + + - it: rejects a PDB that would permanently block drains + set: + pd.pdb.minAvailable: 3 + asserts: + - failedTemplate: + errorPattern: "must be less than pd.replicas" + + - it: rejects an even shard count + set: + pd.partition.defaultShardCount: 2 + asserts: + - failedTemplate: + errorPattern: "must be odd" + + - it: rejects a shard count above store.replicas + set: + pd.partition.defaultShardCount: 5 + asserts: + - failedTemplate: + errorPattern: "greater than store.replicas" + + - it: rejects operator overrides of the chart-managed JAVA_OPTIONS + set: + server.extraEnv: + - name: JAVA_OPTIONS + value: "-Xmx1g" + asserts: + - failedTemplate: + errorPattern: "must not set the chart-managed variable JAVA_OPTIONS" + + - it: rejects a PD NodePort under NetworkPolicy without pd extraIngress + set: + networkPolicy.enabled: true + pd.service.type: NodePort + pd.service.allowInsecureExposure: true + asserts: + - failedTemplate: + errorPattern: "admits nothing from outside the release, so the pd exposure" + + - it: rejects a PD LoadBalancer under NetworkPolicy without pd extraIngress + set: + networkPolicy.enabled: true + pd.service.type: LoadBalancer + pd.service.allowInsecureExposure: true + asserts: + - failedTemplate: + errorPattern: "admits nothing from outside the release, so the pd exposure" + + - it: rejects a Server NodePort under NetworkPolicy without server extraIngress + set: + networkPolicy.enabled: true + server.service.type: NodePort + asserts: + - failedTemplate: + errorPattern: "admits nothing from outside the release, so the server exposure" + + - it: rejects a Server LoadBalancer under NetworkPolicy without server extraIngress + set: + networkPolicy.enabled: true + server.service.type: LoadBalancer + asserts: + - failedTemplate: + errorPattern: "admits nothing from outside the release, so the server exposure" + + - it: rejects a Server Ingress under NetworkPolicy without server extraIngress + set: + networkPolicy.enabled: true + server.ingress.enabled: true + server.ingress.allowPlainHttp: true + asserts: + - failedTemplate: + errorPattern: "admits nothing from outside the release, so the server exposure" + + - it: rejects a Server advertiseUrl under NetworkPolicy without server extraIngress + set: + networkPolicy.enabled: true + server.advertiseUrl: http://hg.example.com:8080 + asserts: + - failedTemplate: + errorPattern: "admits nothing from outside the release, so the server exposure" + + - it: rejects a Hubble NodePort under NetworkPolicy without hubble extraIngress + set: + networkPolicy.enabled: true + hubble.enabled: true + hubble.service.type: NodePort + asserts: + - failedTemplate: + errorPattern: "admits nothing from outside the release, so the hubble exposure" + + - it: rejects a Hubble LoadBalancer under NetworkPolicy without hubble extraIngress + set: + networkPolicy.enabled: true + hubble.enabled: true + hubble.service.type: LoadBalancer + asserts: + - failedTemplate: + errorPattern: "admits nothing from outside the release, so the hubble exposure" + + - it: rejects a Hubble Ingress under NetworkPolicy without hubble extraIngress + set: + networkPolicy.enabled: true + hubble.enabled: true + hubble.ingress.enabled: true + hubble.ingress.allowPlainHttp: true + asserts: + - failedTemplate: + errorPattern: "admits nothing from outside the release, so the hubble exposure" + + - it: names advertiseUrl in the Server exposure message only + set: + networkPolicy.enabled: true + server.advertiseUrl: http://hg.example.com:8080 + asserts: + - failedTemplate: + errorPattern: "server exposure \\(NodePort/LoadBalancer Service, Ingress, server.advertiseUrl\\)" + + - it: reports the PD acknowledgement before the NetworkPolicy exposure check + set: + networkPolicy.enabled: true + pd.service.type: NodePort + asserts: + - failedTemplate: + errorPattern: "pd.service.allowInsecureExposure=true" + + - it: leaves exposure alone while NetworkPolicy is off + set: + server.service.type: LoadBalancer + server.advertiseUrl: http://hg.example.com:8080 + asserts: + - hasDocuments: + count: 1 + + - it: ignores Hubble exposure settings while Hubble is disabled + set: + networkPolicy.enabled: true + hubble.service.type: NodePort + asserts: + - hasDocuments: + count: 1 + + - it: rejects Hubble without Server auth + set: + server.auth.enabled: false + hubble.enabled: true + asserts: + - failedTemplate: + errorPattern: "hubble.enabled requires server.auth" + + - it: rejects a plain-HTTP Hubble Ingress + set: + hubble.ingress.enabled: true + hubble.enabled: true + asserts: + - failedTemplate: + errorPattern: "publishes the plain-HTTP, unauthenticated Hubble UI" + + - it: rejects an empty Hubble image tag + set: + hubble.image.tag: "" + hubble.enabled: true + asserts: + - failedTemplate: + errorPattern: "hubble.image needs a tag or a digest" + + - it: rejects a Store PDB that permits two concurrent evictions + set: + store.replicas: 4 + asserts: + - failedTemplate: + errorPattern: "store.pdb.minAvailable must be at least store.replicas - 1" + + - it: rejects podLabels that overwrite a selector label + set: + store.podLabels: + app.kubernetes.io/component: hacked + asserts: + - failedTemplate: + errorPattern: "store.podLabels must not set app.kubernetes.io/component" + + - it: rejects a TLS-less Server Ingress without the plain-HTTP opt-in + set: + server.ingress.enabled: true + asserts: + - failedTemplate: + errorPattern: "server.ingress.enabled without tls" + + - it: rejects a non-ClusterIP PD Service without the exposure acknowledgement + set: + pd.service.type: NodePort + asserts: + - failedTemplate: + errorPattern: "pd.service.type NodePort or LoadBalancer exposes" + + - it: rejects a read-only root filesystem on Hubble + set: + hubble.enabled: true + hubble.securityContext.readOnlyRootFilesystem: true + asserts: + - failedTemplate: + errorPattern: "readOnlyRootFilesystem=true breaks Hubble" diff --git a/helm/hugegraph/values-cluster.yaml b/helm/hugegraph/values-cluster.yaml new file mode 100644 index 0000000000..e6751bdc51 --- /dev/null +++ b/helm/hugegraph/values-cluster.yaml @@ -0,0 +1,138 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Production starting point: 3 Server + 3 PD + 3 Store. +# Size this profile again for the real graph, traffic, and failure budget. +# Usage: helm install hg ./helm/hugegraph -f values-cluster.yaml + +pd: + replicas: 3 + javaOpts: >- + -Xms256m -Xmx512m + -XX:MaxMetaspaceSize=256m + -XX:MaxDirectMemorySize=256m + -XX:+UseContainerSupport + resources: + requests: + cpu: 500m + memory: 1Gi + limits: + cpu: "2" + memory: 2Gi + # Pinned to "required": production must not co-locate PD quorum members. + antiAffinity: required + pdb: + enabled: true + minAvailable: 2 + storage: + size: 10Gi + +store: + replicas: 3 + javaOpts: >- + -Xms512m -Xmx1024m + -XX:MaxMetaspaceSize=256m + -XX:MaxDirectMemorySize=512m + -XX:+UseContainerSupport + # The Store commits far more than its heap. conf/application-pd.yml, which + # the shipped conf/application.yml includes as a Spring profile, sets + # rocksdb.total_memory_size to 32000000000, and RaftRocksdbOptions splits + # that value into a RocksDB write cache and block cache, so those native + # caches are bounded by 32 GB rather than by the container. On top of that + # the jraft log storage registers its own 1 GiB LRU block cache once per + # process, plus heap, direct memory, metaspace and about a thousand threads. + # A 4Gi limit was below the steady state and OOM-killed all three Stores + # after roughly 1 GB of data; 8Gi holds, with 4.42 GiB anonymous RSS + # measured on k3s (2026-09-19). The limit bounds the damage, it does not + # bound RocksDB: the chart cannot set rocksdb.total_memory_size today, + # because the Store entrypoint rebuilds SPRING_APPLICATION_JSON from its + # own variables and no conf file is mounted. Raise both numbers together + # with the data size, or lower the RocksDB budget in a custom image. + resources: + requests: + cpu: "1" + memory: 5Gi + limits: + cpu: "4" + memory: 8Gi + waitResources: + requests: + cpu: 25m + memory: 32Mi + limits: + cpu: 250m + memory: 64Mi + # Pinned to "required": production must not co-locate shard replicas. + antiAffinity: required + pdb: + enabled: true + minAvailable: 2 + storage: + size: 50Gi + +server: + replicas: 3 + javaOpts: >- + -Xms512m -Xmx1024m + -XX:MaxMetaspaceSize=256m + -XX:MaxDirectMemorySize=256m + -XX:+UseContainerSupport + resources: + requests: + cpu: "1" + memory: 1Gi + limits: + cpu: "4" + memory: 2Gi + hpa: + enabled: false + # Keep at least two Servers through voluntary evictions such as node + # drains; the default leaves the PDB off because Server holds no quorum. + pdb: + enabled: true + minAvailable: 2 + # Auth is on by default (chart-managed admin Secret). Pin it here so a + # production overlay cannot accidentally drop authentication. + auth: + enabled: true + admin: + autoGenerate: true + token: + autoGenerate: true + +# The optional Hubble UI is not enabled here: authentication is already on +# by default, so turn Hubble on with --set hubble.enabled=true (or the +# snippet below) when the browser UI is wanted. Current Hubble images still +# refuse to render if server.auth is explicitly disabled. +# +# hubble: +# enabled: true +# persistence: +# enabled: true +# size: 1Gi +# resources: +# requests: +# cpu: 250m +# memory: 768Mi +# limits: +# cpu: "1" +# memory: 1536Mi + +# Production-shaped installs isolate the release; list outside clients in +# networkPolicy..extraIngress (see README, NetworkPolicy). +networkPolicy: + enabled: true diff --git a/helm/hugegraph/values-single.yaml b/helm/hugegraph/values-single.yaml new file mode 100644 index 0000000000..92f4acf4d5 --- /dev/null +++ b/helm/hugegraph/values-single.yaml @@ -0,0 +1,46 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Convenience preset: single-node / local kind-minikube development. +# Usage: helm install hg ./helm/hugegraph -f values-single.yaml + +pd: + replicas: 1 + antiAffinity: disabled + pdb: + enabled: false + storage: + size: 5Gi + +store: + replicas: 1 + antiAffinity: disabled + pdb: + enabled: false + storage: + size: 10Gi + +server: + replicas: 1 + hpa: + enabled: false + auth: + enabled: true + admin: + autoGenerate: true + token: + autoGenerate: true diff --git a/helm/hugegraph/values.schema.json b/helm/hugegraph/values.schema.json new file mode 100644 index 0000000000..159b8361b6 --- /dev/null +++ b/helm/hugegraph/values.schema.json @@ -0,0 +1,1399 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "HugeGraph HStore Helm values", + "type": "object", + "required": [ + "pd", + "store", + "server" + ], + "properties": { + "fullnameOverride": { + "type": "string" + }, + "nameOverride": { + "type": "string" + }, + "imagePullSecrets": { + "type": "array", + "items": { + "type": "object" + } + }, + "pd": { + "$ref": "#/definitions/pd" + }, + "store": { + "$ref": "#/definitions/store" + }, + "server": { + "$ref": "#/definitions/server" + }, + "global": { + "type": "object" + }, + "hubble": { + "$ref": "#/definitions/hubble" + }, + "networkPolicy": { + "$ref": "#/definitions/networkPolicy" + } + }, + "definitions": { + "networkPolicyPeers": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "minProperties": 1 + } + }, + "networkPolicyIngressRules": { + "type": "array", + "items": { + "type": "object", + "required": [ + "from" + ], + "properties": { + "from": { + "$ref": "#/definitions/networkPolicyPeers" + } + } + } + }, + "networkPolicyEgressRules": { + "type": "array", + "items": { + "type": "object", + "required": [ + "to" + ], + "properties": { + "to": { + "$ref": "#/definitions/networkPolicyPeers" + } + } + } + }, + "networkPolicyComponent": { + "type": "object", + "additionalProperties": false, + "properties": { + "extraIngress": { + "$ref": "#/definitions/networkPolicyIngressRules" + } + } + }, + "networkPolicy": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean" + }, + "pd": { + "$ref": "#/definitions/networkPolicyComponent" + }, + "store": { + "$ref": "#/definitions/networkPolicyComponent" + }, + "server": { + "$ref": "#/definitions/networkPolicyComponent" + }, + "hubble": { + "type": "object", + "additionalProperties": false, + "properties": { + "extraIngress": { + "$ref": "#/definitions/networkPolicyIngressRules" + }, + "extraEgress": { + "$ref": "#/definitions/networkPolicyEgressRules" + } + } + } + } + }, + "optionalPositiveInteger": { + "oneOf": [ + { + "type": "integer", + "minimum": 1 + }, + { + "type": "string", + "pattern": "^([1-9][0-9]*)?$" + } + ] + }, + "image": { + "type": "object", + "additionalProperties": false, + "required": [ + "repository", + "tag", + "pullPolicy" + ], + "properties": { + "repository": { + "type": "string", + "minLength": 1 + }, + "tag": { + "type": "string" + }, + "pullPolicy": { + "type": "string", + "enum": [ + "Always", + "IfNotPresent", + "Never" + ] + }, + "digest": { + "type": "string", + "description": "Optional immutable image digest, for example sha256:... Wins over tag when set." + } + } + }, + "resources": { + "type": "object", + "additionalProperties": false, + "properties": { + "requests": { + "$ref": "#/definitions/resourceList" + }, + "limits": { + "$ref": "#/definitions/resourceList" + } + } + }, + "updateStrategy": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "RollingUpdate", + "OnDelete" + ] + }, + "rollingUpdate": { + "type": "object", + "additionalProperties": false, + "properties": { + "partition": { + "type": "integer", + "minimum": 0 + }, + "maxUnavailable": { + "type": [ + "integer", + "string" + ] + } + } + } + } + }, + "persistentVolumeClaimRetentionPolicy": { + "type": "object", + "additionalProperties": false, + "properties": { + "whenDeleted": { + "type": "string", + "enum": [ + "Retain", + "Delete" + ] + }, + "whenScaled": { + "type": "string", + "enum": [ + "Retain", + "Delete" + ] + } + } + }, + "resourceList": { + "type": "object", + "additionalProperties": { + "type": [ + "string", + "number" + ] + }, + "properties": { + "cpu": { + "type": [ + "string", + "number" + ] + }, + "memory": { + "type": [ + "string", + "number" + ] + }, + "ephemeral-storage": { + "type": [ + "string", + "number" + ] + } + } + }, + "storage": { + "type": "object", + "additionalProperties": false, + "required": [ + "size", + "storageClassName" + ], + "properties": { + "size": { + "type": "string", + "minLength": 1 + }, + "storageClassName": { + "type": "string" + } + } + }, + "pdb": { + "type": "object", + "additionalProperties": false, + "required": [ + "enabled", + "minAvailable" + ], + "properties": { + "enabled": { + "type": "boolean" + }, + "minAvailable": { + "type": "integer", + "minimum": 1 + } + } + }, + "probe": { + "type": "object", + "additionalProperties": false, + "required": [ + "periodSeconds", + "failureThreshold" + ], + "properties": { + "periodSeconds": { + "type": "integer", + "minimum": 1 + }, + "failureThreshold": { + "type": "integer", + "minimum": 1 + }, + "timeoutSeconds": { + "type": "integer", + "minimum": 1 + }, + "initialDelaySeconds": { + "type": "integer", + "minimum": 0 + }, + "successThreshold": { + "type": "integer", + "minimum": 1 + } + } + }, + "probes": { + "type": "object", + "additionalProperties": false, + "required": [ + "startup", + "readiness", + "liveness" + ], + "properties": { + "startup": { + "allOf": [ + { "$ref": "#/definitions/probe" }, + { "properties": { "successThreshold": { "maximum": 1 } } } + ] + }, + "readiness": { + "$ref": "#/definitions/probe" + }, + "liveness": { + "allOf": [ + { "$ref": "#/definitions/probe" }, + { "properties": { "successThreshold": { "maximum": 1 } } } + ] + } + } + }, + "ports": { + "type": "object", + "additionalProperties": false, + "required": [ + "grpc", + "rest", + "raft" + ], + "properties": { + "grpc": { + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "rest": { + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "raft": { + "type": "integer", + "minimum": 1, + "maximum": 65535 + } + } + }, + "pd": { + "type": "object", + "additionalProperties": false, + "required": [ + "replicas", + "image", + "ports", + "dataPath", + "storage", + "resources", + "antiAffinity", + "pdb", + "probes" + ], + "properties": { + "replicas": { + "type": "integer", + "minimum": 1, + "maximum": 99 + }, + "image": { + "$ref": "#/definitions/image" + }, + "javaOpts": { + "type": "string" + }, + "raftIpWhitelistEnabled": { + "type": "boolean" + }, + "raftRpcTimeoutMs": { + "$ref": "#/definitions/optionalPositiveInteger" + }, + "updateStrategy": { + "$ref": "#/definitions/updateStrategy" + }, + "persistentVolumeClaimRetentionPolicy": { + "$ref": "#/definitions/persistentVolumeClaimRetentionPolicy" + }, + "partition": { + "type": "object", + "additionalProperties": false, + "properties": { + "defaultShardCount": { + "$ref": "#/definitions/optionalPositiveInteger" + }, + "storeMaxShardCount": { + "$ref": "#/definitions/optionalPositiveInteger" + } + } + }, + "ports": { + "$ref": "#/definitions/ports" + }, + "dataPath": { + "type": "string", + "minLength": 1 + }, + "auth": { + "type": "object", + "additionalProperties": false, + "required": [ + "value", + "existingSecret", + "key", + "autoGenerate" + ], + "description": "PD REST Basic-auth secret (auth.secret-key), shared with the Server storage wait and Hubble.", + "properties": { + "value": { + "type": "string", + "pattern": "^([\\x21-\\x5b\\x5d-\\x7e]([\\x20-\\x5b\\x5d-\\x7e]*[\\x21-\\x5b\\x5d-\\x7e])?)?$", + "description": "Plaintext secret. Empty defers to existingSecret or autoGenerate. Printable ASCII with no backslash, and no leading or trailing space: Commons Configuration trims a properties value, so padding would make the stored secret and the effective one differ." + }, + "existingSecret": { + "type": "string" + }, + "key": { + "type": "string", + "minLength": 1 + }, + "autoGenerate": { + "type": "boolean" + } + }, + "anyOf": [ + { + "properties": { + "existingSecret": { + "minLength": 1 + } + } + }, + { + "properties": { + "value": { + "minLength": 1 + }, + "existingSecret": { + "const": "" + } + } + }, + { + "properties": { + "autoGenerate": { + "const": true + }, + "existingSecret": { + "const": "" + } + } + } + ] + }, + "storage": { + "$ref": "#/definitions/storage" + }, + "resources": { + "$ref": "#/definitions/resources" + }, + "antiAffinity": { + "type": "string", + "enum": [ + "required", + "preferred", + "disabled" + ] + }, + "pdb": { + "$ref": "#/definitions/pdb" + }, + "probes": { + "$ref": "#/definitions/probes" + }, + "podSecurityContext": { + "type": "object" + }, + "securityContext": { + "type": "object" + }, + "nodeSelector": { + "type": "object" + }, + "tolerations": { + "type": "array" + }, + "affinity": { + "type": "object" + }, + "topologySpreadConstraints": { + "type": "array" + }, + "priorityClassName": { + "type": "string" + }, + "podAnnotations": { + "type": "object" + }, + "podLabels": { + "type": "object" + }, + "extraEnv": { + "type": "array" + }, + "terminationGracePeriodSeconds": { + "type": "integer", + "minimum": 0 + }, + "serviceAccount": { + "type": "object", + "properties": { + "create": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "annotations": { + "type": "object" + }, + "automountServiceAccountToken": { + "type": "boolean" + } + } + }, + "service": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "ClusterIP", + "NodePort", + "LoadBalancer" + ] + }, + "annotations": { + "type": "object" + }, + "allowInsecureExposure": { + "type": "boolean", + "description": "Required acknowledgement for a non-ClusterIP PD Service: the exposed gRPC port has no authentication." + }, + "restNodePort": { + "type": [ + "integer", + "null" + ], + "minimum": 30000, + "maximum": 32767 + }, + "grpcNodePort": { + "type": [ + "integer", + "null" + ], + "minimum": 30000, + "maximum": 32767 + } + } + }, + "livenessPath": { + "type": "string", + "pattern": "^(/.*)?$", + "description": "HTTP path for the PD startup and liveness probes. Empty derives it from pd.replicas: /v1/health above one replica, /v1/ready at one (apache/hugegraph#3222)" + }, + "readinessPath": { + "type": "string", + "pattern": "^/", + "description": "HTTP path for the PD readinessProbe; /v1/ready (default) on PD images that serve it, /v1/health on older ones" + } + } + }, + "store": { + "type": "object", + "additionalProperties": false, + "required": [ + "replicas", + "image", + "ports", + "dataPath", + "storage", + "resources", + "antiAffinity", + "pdb", + "waitImage", + "probes" + ], + "properties": { + "replicas": { + "type": "integer", + "minimum": 1, + "maximum": 99 + }, + "image": { + "$ref": "#/definitions/image" + }, + "javaOpts": { + "type": "string" + }, + "ports": { + "$ref": "#/definitions/ports" + }, + "dataPath": { + "type": "string", + "minLength": 1 + }, + "storage": { + "$ref": "#/definitions/storage" + }, + "resources": { + "$ref": "#/definitions/resources" + }, + "antiAffinity": { + "type": "string", + "enum": [ + "required", + "preferred", + "disabled" + ] + }, + "pdb": { + "$ref": "#/definitions/pdb" + }, + "waitImage": { + "type": "string", + "minLength": 1 + }, + "waitResources": { + "$ref": "#/definitions/resources" + }, + "probes": { + "$ref": "#/definitions/probes" + }, + "podSecurityContext": { + "type": "object" + }, + "securityContext": { + "type": "object" + }, + "waitTimeoutSeconds": { + "type": "integer", + "minimum": 1 + }, + "updateStrategy": { + "$ref": "#/definitions/updateStrategy" + }, + "persistentVolumeClaimRetentionPolicy": { + "$ref": "#/definitions/persistentVolumeClaimRetentionPolicy" + }, + "nodeSelector": { + "type": "object" + }, + "tolerations": { + "type": "array" + }, + "affinity": { + "type": "object" + }, + "topologySpreadConstraints": { + "type": "array" + }, + "priorityClassName": { + "type": "string" + }, + "podAnnotations": { + "type": "object" + }, + "podLabels": { + "type": "object" + }, + "extraEnv": { + "type": "array" + }, + "terminationGracePeriodSeconds": { + "type": "integer", + "minimum": 0 + }, + "serviceAccount": { + "type": "object", + "properties": { + "create": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "annotations": { + "type": "object" + }, + "automountServiceAccountToken": { + "type": "boolean" + } + } + }, + "waitPath": { + "type": "string", + "pattern": "^/", + "description": "HTTP path the Store init container polls on each PD peer; /v1/ready (default) on PD images that serve it, /v1/health on older ones" + } + } + }, + "server": { + "type": "object", + "additionalProperties": false, + "required": [ + "replicas", + "image", + "port", + "backend", + "resources", + "waitImage", + "initStoreEnabled", + "auth", + "ingress", + "hpa", + "probes" + ], + "properties": { + "replicas": { + "type": "integer", + "minimum": 1 + }, + "image": { + "$ref": "#/definitions/image" + }, + "javaOpts": { + "type": "string" + }, + "port": { + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "backend": { + "type": "string", + "const": "hstore" + }, + "resources": { + "$ref": "#/definitions/resources" + }, + "waitImage": { + "type": "string", + "minLength": 1, + "description": "Image used by the Helm test hook" + }, + "waitResources": { + "$ref": "#/definitions/resources" + }, + "restServer": { + "type": "object", + "additionalProperties": false, + "properties": { + "minFreeMemory": { + "oneOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string", + "const": "" + } + ] + }, + "batchMaxWriteThreads": { + "oneOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string", + "const": "" + } + ] + } + } + }, + "initStoreEnabled": { + "type": "boolean", + "const": false + }, + "auth": { + "type": "object", + "additionalProperties": false, + "required": [ + "enabled", + "admin", + "token" + ], + "properties": { + "enabled": { + "type": "boolean" + }, + "admin": { + "type": "object", + "additionalProperties": false, + "required": [ + "password", + "existingSecret", + "key", + "autoGenerate" + ], + "properties": { + "password": { + "type": "string", + "pattern": "^([^ \\t\\f\\r\\n\\\\]([^\\r\\n\\\\]*[^ \\t\\f\\r\\n\\\\])?)?$", + "description": "Inline admin password. Empty defers to existingSecret or autoGenerate. The Server wrapper rejects newlines, carriage returns and backslashes, so the schema rejects them before install. Leading and trailing whitespace is rejected too: Commons Configuration trims a properties value, so the Secret would hold the padded string while the account was created with the trimmed one." + }, + "existingSecret": { + "type": "string" + }, + "key": { + "type": "string", + "minLength": 1 + }, + "autoGenerate": { + "type": "boolean" + } + } + }, + "token": { + "type": "object", + "additionalProperties": false, + "required": [ + "value", + "existingSecret", + "key", + "autoGenerate" + ], + "properties": { + "value": { + "type": "string", + "description": "JWT signing key. Either empty, which defers to existingSecret or autoGenerate, or at least 32 characters: the Server entrypoint rejects a shorter key and the pods CrashLoop.", + "anyOf": [ + { + "maxLength": 0 + }, + { + "minLength": 32 + } + ] + }, + "existingSecret": { + "type": "string" + }, + "key": { + "type": "string", + "minLength": 1 + }, + "autoGenerate": { + "type": "boolean" + } + } + } + }, + "allOf": [ + { + "if": { + "properties": { + "enabled": { + "const": true + } + } + }, + "then": { + "properties": { + "admin": { + "anyOf": [ + { + "properties": { + "existingSecret": { + "minLength": 1 + } + } + }, + { + "properties": { + "password": { + "minLength": 1 + }, + "existingSecret": { + "const": "" + } + } + }, + { + "properties": { + "autoGenerate": { + "const": true + }, + "existingSecret": { + "const": "" + } + } + } + ] + }, + "token": { + "anyOf": [ + { + "properties": { + "existingSecret": { + "minLength": 1 + } + } + }, + { + "properties": { + "value": { + "minLength": 1 + }, + "existingSecret": { + "const": "" + } + } + }, + { + "properties": { + "autoGenerate": { + "const": true + }, + "existingSecret": { + "const": "" + } + } + } + ] + } + } + } + }, + { + "if": { + "properties": { + "enabled": { + "const": false + } + } + }, + "then": { + "properties": { + "admin": { + "properties": { + "existingSecret": { + "const": "" + }, + "password": { + "const": "" + } + } + }, + "token": { + "properties": { + "existingSecret": { + "const": "" + }, + "value": { + "const": "" + } + } + } + } + } + } + ] + }, + "ingress": { + "$ref": "#/definitions/serverIngress" + }, + "hpa": { + "type": "object", + "additionalProperties": false, + "required": [ + "enabled", + "minReplicas", + "maxReplicas", + "targetCPUUtilizationPercentage" + ], + "properties": { + "enabled": { + "type": "boolean" + }, + "minReplicas": { + "type": "integer", + "minimum": 1 + }, + "maxReplicas": { + "type": "integer", + "minimum": 1 + }, + "targetCPUUtilizationPercentage": { + "type": "integer", + "minimum": 1, + "maximum": 100 + } + } + }, + "readinessPath": { + "type": "string", + "pattern": "^/", + "description": "HTTP path for the Server readinessProbe; /readiness (apache/hugegraph#3212) on Server images that serve it, /versions (default) on older ones" + }, + "probes": { + "$ref": "#/definitions/probes" + }, + "antiAffinity": { + "type": "string", + "enum": [ + "required", + "preferred", + "disabled" + ] + }, + "podSecurityContext": { + "type": "object" + }, + "securityContext": { + "type": "object" + }, + "pdb": { + "$ref": "#/definitions/pdb" + }, + "nodeSelector": { + "type": "object" + }, + "tolerations": { + "type": "array" + }, + "affinity": { + "type": "object" + }, + "topologySpreadConstraints": { + "type": "array" + }, + "priorityClassName": { + "type": "string" + }, + "podAnnotations": { + "type": "object" + }, + "podLabels": { + "type": "object" + }, + "extraEnv": { + "type": "array" + }, + "terminationGracePeriodSeconds": { + "type": "integer", + "minimum": 0 + }, + "serviceAccount": { + "type": "object", + "properties": { + "create": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "annotations": { + "type": "object" + }, + "automountServiceAccountToken": { + "type": "boolean" + } + } + }, + "testResources": { + "$ref": "#/definitions/resources" + }, + "service": { + "$ref": "#/definitions/service" + }, + "advertiseUrl": { + "type": "string", + "description": "URL registered with PD via server.urls_to_pd. Empty uses the in-cluster Server Service URL. Set to an externally reachable URL for outside Hubble PD discovery." + } + } + }, + "service": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "ClusterIP", + "NodePort", + "LoadBalancer" + ] + }, + "annotations": { + "type": "object" + }, + "nodePort": { + "type": [ + "integer", + "null" + ], + "minimum": 30000, + "maximum": 32767 + } + } + }, + "ingress": { + "type": "object", + "additionalProperties": false, + "required": [ + "enabled", + "className", + "hosts", + "tls" + ], + "properties": { + "enabled": { + "type": "boolean" + }, + "className": { + "type": "string" + }, + "hosts": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "host", + "paths" + ], + "properties": { + "host": { + "type": "string", + "minLength": 1 + }, + "paths": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "pathType" + ], + "properties": { + "path": { + "type": "string" + }, + "pathType": { + "type": "string", + "enum": [ + "Exact", + "Prefix", + "ImplementationSpecific" + ] + } + } + } + } + } + } + }, + "tls": { + "type": "array", + "items": { + "type": "object" + } + }, + "annotations": { + "type": "object" + }, + "allowPlainHttp": { + "type": "boolean" + } + } + }, + "serverIngress": { + "type": "object", + "additionalProperties": false, + "required": [ + "enabled", + "className", + "hosts", + "tls" + ], + "properties": { + "enabled": { + "type": "boolean" + }, + "className": { + "type": "string" + }, + "hosts": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "host", + "paths" + ], + "properties": { + "host": { + "type": "string", + "minLength": 1 + }, + "paths": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "pathType" + ], + "properties": { + "path": { + "type": "string" + }, + "pathType": { + "type": "string", + "enum": [ + "Exact", + "Prefix", + "ImplementationSpecific" + ] + } + } + } + } + } + } + }, + "tls": { + "type": "array", + "items": { + "type": "object" + } + }, + "annotations": { + "type": "object" + }, + "allowPlainHttp": { + "type": "boolean", + "description": "Explicit opt-in to a TLS-less Server Ingress; without it the render fails when tls is empty." + } + } + }, + "hubble": { + "type": "object", + "additionalProperties": false, + "required": [ + "enabled", + "mode", + "image", + "port", + "persistence", + "resources", + "service", + "ingress", + "probes" + ], + "properties": { + "enabled": { + "type": "boolean" + }, + "mode": { + "type": "string", + "enum": [ + "pd", + "direct" + ] + }, + "allowWithoutServerAuth": { + "type": "boolean" + }, + "image": { + "$ref": "#/definitions/image" + }, + "port": { + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "persistence": { + "type": "object", + "additionalProperties": false, + "required": [ + "enabled", + "size", + "storageClassName" + ], + "properties": { + "enabled": { + "type": "boolean" + }, + "size": { + "type": "string", + "minLength": 1 + }, + "storageClassName": { + "type": "string" + } + } + }, + "resources": { + "$ref": "#/definitions/resources" + }, + "podSecurityContext": { + "type": "object" + }, + "securityContext": { + "type": "object" + }, + "nodeSelector": { + "type": "object" + }, + "tolerations": { + "type": "array" + }, + "affinity": { + "type": "object" + }, + "topologySpreadConstraints": { + "type": "array" + }, + "priorityClassName": { + "type": "string" + }, + "podAnnotations": { + "type": "object" + }, + "podLabels": { + "type": "object" + }, + "extraEnv": { + "type": "array" + }, + "terminationGracePeriodSeconds": { + "type": "integer", + "minimum": 0 + }, + "serviceAccount": { + "type": "object", + "properties": { + "create": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "annotations": { + "type": "object" + }, + "automountServiceAccountToken": { + "type": "boolean" + } + } + }, + "service": { + "$ref": "#/definitions/service" + }, + "ingress": { + "$ref": "#/definitions/ingress" + }, + "probes": { + "$ref": "#/definitions/probes" + } + } + } + } +} diff --git a/helm/hugegraph/values.yaml b/helm/hugegraph/values.yaml new file mode 100644 index 0000000000..a5a2b3355e --- /dev/null +++ b/helm/hugegraph/values.yaml @@ -0,0 +1,537 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Default values for HugeGraph HStore Helm chart. +# See values-single.yaml and values-cluster.yaml for presets. + +fullnameOverride: "" +nameOverride: "" + +imagePullSecrets: [] + +pd: + replicas: 3 + image: + repository: hugegraph/pd + # Tracks latest until the next HugeGraph release tag is published. Pin the + # tag (or digest) and switch to IfNotPresent for production. + tag: latest + # Optional immutable digest, for example sha256:abc... When set it wins over + # tag and the image is pulled by digest, which is what a release gate should + # assert instead of a mutable tag. + digest: "" + pullPolicy: Always + # Empty preserves the image entrypoint's automatic JVM sizing. The chart + # renders its partition settings below as -D system properties ahead of + # this value, so flags placed here win on conflict. + javaOpts: "" + # Partition sharding, rendered as -D system properties that outrank the + # image's conf/application.yml. PD copies both values into its own + # persisted metadata at first bootstrap only; from then on the stored + # values are authoritative, so changing these later (or scaling + # store.replicas across the derivation boundary) has no effect on an + # initialized cluster. Post-bootstrap changes go through PD's config API + # and take effect on existing shard groups only when a partition patrol + # is triggered; see the README's Partition Sharding section. + partition: + # Shard replicas per partition, seeding PD at first bootstrap only. + # Empty derives 3 when store.replicas is at least 3, else 1, so a fresh + # multi-store install gets store-level HA instead of the image default + # of 1. The derivation skips 2 because PD clamps a shard count of 2 to + # 1 (two shards cannot elect a leader). An explicit value must be odd + # and must not exceed store.replicas. + defaultShardCount: "" + # Maximum shards per store, seeding PD at first bootstrap only. Also + # fixes the initial partition count: store.replicas x this value / + # shard count. The derived shard count of 3 gives the default topology + # 12 partitions instead of 36; raise this value to compensate when more + # partitions are wanted. Empty preserves the image default of 12. + storeMaxShardCount: "" + ports: + grpc: 8686 + rest: 8620 + raft: 8610 + dataPath: /hugegraph-pd/pd_data + # PD REST Basic-auth credential. PD compares the password of every /v1 + # request against auth.secret-key and refuses to start without one + # (HG_PD_AUTH_SECRET_KEY). The chart hands the same value to the Server + # storage wait (PD_AUTH_PASSWORD, user store) and to Hubble + # (operations.pd.password, user hubble). The Secret is kept on uninstall + # like the Server auth Secrets. The value must be printable ASCII with + # no leading whitespace and no backslashes: it is written into Hubble's + # Java properties file, which strips leading whitespace, unescapes + # backslashes, and reads the file as ISO-8859-1. Priority: + # existingSecret > value > autoGenerate. + auth: + # Optional plaintext secret (prefer existingSecret in shared clusters). + value: "" + # Pre-created Secret name. Must contain the key below; chart does not manage it. + existingSecret: "" + key: secret-key + # When existingSecret and value are empty, create a kept release-pd-auth Secret. + autoGenerate: true + storage: + size: 10Gi + storageClassName: "" + resources: {} + # Pod-level and container-level securityContext. Empty by default because the + # published images run as root; set these to satisfy a restricted namespace. + podSecurityContext: {} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + seccompProfile: + type: RuntimeDefault + # required | preferred | disabled. "preferred" schedules on clusters with + # fewer nodes than replicas; production should use "required" (see + # values-cluster.yaml) so one node failure cannot take out PD quorum. + antiAffinity: preferred + # Scheduling. `affinity` takes precedence over the antiAffinity preset. + nodeSelector: {} + tolerations: [] + affinity: {} + topologySpreadConstraints: [] + priorityClassName: "" + podAnnotations: {} + podLabels: {} + # Extra environment variables appended to the pd container. + extraEnv: [] + # Gives a JVM with an on-disk store time to shut down cleanly on drain. + terminationGracePeriodSeconds: 300 + serviceAccount: + create: true + name: "" + annotations: {} + # This chart makes no Kubernetes API calls, so no token is mounted. + automountServiceAccountToken: false + # Client Service for PD REST/gRPC. Defaults to ClusterIP for in-cluster + # consumers (Server, Store, in-chart Hubble). Set type to NodePort or + # LoadBalancer when an outside Hubble must reach PD for discovery. + service: + type: ClusterIP + annotations: {} + # A non-ClusterIP type exposes PD's gRPC port, which has no + # authentication (raft membership RPCs included). The render refuses it + # unless this acknowledgement is set and reachability is restricted by + # other means (NetworkPolicy, load balancer allowlist, firewall). + allowInsecureExposure: false + # Optional fixed NodePorts; require service.type NodePort or LoadBalancer. + restNodePort: + grpcNodePort: + pdb: + enabled: true + minAvailable: 2 + # PD's raft IP whitelist resolves peers once at boot, which under + # Kubernetes blocks peers whose pod IPs were not yet published or change + # later. Off by default in-cluster (upstream switch + # raft.ip-whitelist.enabled); k8s network policy/auth owns that layer. + # Set true to restore the image default. + raftIpWhitelistEnabled: false + # Raft RPC timeout in milliseconds, rendered as -Draft.rpc-timeout. A + # vanished (not crashed) leader is waited on for this long per attempt, so + # the image default of 10000 leaves the cluster leaderless for about a + # minute where 3000 elects a new leader in seconds. Empty preserves the + # image default. + raftRpcTimeoutMs: 3000 + # Explicit rollout strategy instead of the implicit StatefulSet default. + updateStrategy: + type: RollingUpdate + # Keep graph metadata PVCs across delete and scale-down; data outlives the + # workload by default. Set to Delete for disposable environments. Honored + # on Kubernetes >=1.27 (or with the StatefulSetAutoDeletePVC feature gate); + # older API servers drop the field, which matches Retain behavior anyway. + persistentVolumeClaimRetentionPolicy: + whenDeleted: Retain + whenScaled: Retain + # Startup can take a while during Raft bootstrap + # HTTP path the PD readinessProbe hits. /v1/ready answers 503 while there + # is no raft leader, so a PD outside the quorum stops passing readiness. + # Startup and liveness stay on /v1/health so a PD that merely lost its + # leader is not restarted. + readinessPath: /v1/ready + # HTTP path the PD startup and liveness probes hit. Empty derives it from + # the replica count: /v1/health with more than one PD, so a normal election + # never restarts a follower, and /v1/ready with a single PD, which has no + # election to lose and would otherwise pass /v1/health forever after losing + # leadership for good (apache/hugegraph#3222). Startup follows liveness so + # the boot-to-ready window sits inside the startup budget. + livenessPath: "" + probes: + startup: + failureThreshold: 30 + periodSeconds: 10 + timeoutSeconds: 5 + readiness: + periodSeconds: 10 + failureThreshold: 3 + timeoutSeconds: 5 + liveness: + periodSeconds: 20 + failureThreshold: 3 + timeoutSeconds: 5 + +store: + replicas: 3 + image: + repository: hugegraph/store + # Tracks latest until the next HugeGraph release tag is published. Pin the + # tag (or digest) and switch to IfNotPresent for production. + tag: latest + # Optional immutable digest, for example sha256:abc... When set it wins over + # tag and the image is pulled by digest, which is what a release gate should + # assert instead of a mutable tag. + digest: "" + pullPolicy: Always + # Empty preserves the image entrypoint's automatic JVM sizing. + javaOpts: "" + ports: + grpc: 8500 + raft: 8510 + rest: 8520 + dataPath: /hugegraph-store/storage + storage: + size: 50Gi + storageClassName: "" + resources: {} + podSecurityContext: {} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + seccompProfile: + type: RuntimeDefault + # required | preferred | disabled. "preferred" schedules on clusters with + # fewer nodes than replicas; production should use "required" (see + # values-cluster.yaml) so one node failure cannot co-locate shard replicas. + antiAffinity: preferred + # Scheduling. `affinity` takes precedence over the antiAffinity preset. + nodeSelector: {} + tolerations: [] + affinity: {} + topologySpreadConstraints: [] + priorityClassName: "" + podAnnotations: {} + podLabels: {} + # Extra environment variables appended to the store container. + extraEnv: [] + # Gives a JVM with an on-disk store time to shut down cleanly on drain. + terminationGracePeriodSeconds: 300 + serviceAccount: + create: true + name: "" + annotations: {} + # This chart makes no Kubernetes API calls, so no token is mounted. + automountServiceAccountToken: false + pdb: + enabled: true + minAvailable: 2 + waitImage: curlimages/curl:8.5.0 + # HTTP path the init container polls on every PD peer; a majority must + # answer 2xx before the Store starts. /v1/ready makes that majority a + # raft quorum instead of a set of live listeners. + waitPath: /v1/ready + # Bound the PD wait so a cluster whose PDs never come up fails visibly + # instead of sitting in Init:0/1 forever. + waitTimeoutSeconds: 900 + # Optional bounds for the PD wait init container. + waitResources: {} + # Explicit rollout strategy instead of the implicit StatefulSet default. + updateStrategy: + type: RollingUpdate + # Keep graph data PVCs across delete and scale-down; data outlives the + # workload by default. Set to Delete for disposable environments. + persistentVolumeClaimRetentionPolicy: + whenDeleted: Retain + whenScaled: Retain + probes: + startup: + failureThreshold: 40 + periodSeconds: 10 + timeoutSeconds: 5 + readiness: + periodSeconds: 10 + failureThreshold: 3 + timeoutSeconds: 5 + liveness: + periodSeconds: 20 + failureThreshold: 3 + timeoutSeconds: 5 + +server: + replicas: 3 + image: + repository: hugegraph/server + # Tracks latest until the next HugeGraph release tag is published. Pin the + # tag (or digest) and switch to IfNotPresent for production. + tag: latest + # Optional immutable digest, for example sha256:abc... When set it wins over + # tag and the image is pulled by digest, which is what a release gate should + # assert instead of a mutable tag. + digest: "" + pullPolicy: Always + # Empty preserves the image entrypoint's automatic JVM sizing. + javaOpts: "" + port: 8080 + # HTTP path the Server readinessProbe hits. /readiness (apache/hugegraph#3212) + # answers 503 while the Server cannot serve graph traffic, so such a Server + # drops out of the Service instead of answering 500 to every graph request. + # Keep /versions on Server images that do not serve it. Startup and liveness + # stay on /versions so a Server that merely lost its storage is not restarted. + readinessPath: /versions + backend: hstore + resources: {} + # Server is stateless and may scale past the node count via HPA, so the + # default only prefers spreading. Use "required" when replicas are always + # fewer than schedulable nodes. + # required | preferred | disabled + antiAffinity: preferred + # Scheduling. `affinity` takes precedence over the antiAffinity preset. + nodeSelector: {} + tolerations: [] + affinity: {} + topologySpreadConstraints: [] + priorityClassName: "" + podAnnotations: {} + podLabels: {} + # Extra environment variables appended to the server container. + extraEnv: [] + # Allows in-flight requests to drain before the Server is stopped. + terminationGracePeriodSeconds: 60 + serviceAccount: + create: true + name: "" + annotations: {} + # This chart makes no Kubernetes API calls, so no token is mounted. + automountServiceAccountToken: false + podSecurityContext: {} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + seccompProfile: + type: RuntimeDefault + # Server has no quorum to preserve, so no PodDisruptionBudget by default. + pdb: + enabled: false + minAvailable: 2 + # Image used by the Helm test hook. + waitImage: curlimages/curl:8.5.0 + # Optional resources for the Helm test hook container. + # Resources for the Helm test hook container. Bounded by default so the hook + # cannot run unlimited on a restricted or quota-managed namespace. + testResources: + requests: + cpu: 25m + memory: 32Mi + limits: + cpu: 250m + memory: 64Mi + restServer: + # Empty preserves the image's restserver.min_free_memory default. + minFreeMemory: "" + # Empty preserves the image's batch.max_write_threads default. + batchMaxWriteThreads: "" + # Distributed HStore: the init-store gate must be explicitly false, so that + # concurrent Server replicas never initialize the same backend. No + # HG_SERVER_SKIP_INIT and no init Job. + initStoreEnabled: false + auth: + # On by default: a chart-managed release-admin Secret supplies the password + # unless admin.existingSecret or admin.password is set. Set enabled=false + # only for trusted networks. + enabled: true + # Admin password: inline value vs Kubernetes Secret name are separate keys. + # Priority: existingSecret > password > autoGenerate. + admin: + # Optional plaintext password (prefer existingSecret in shared clusters). + password: "" + # Pre-created Secret name. Must contain key below; chart does not manage it. + existingSecret: "" + key: password + # When existingSecret and password are empty, create a kept release-admin Secret. + autoGenerate: true + # JWT signing key for auth.token_secret / HG_SERVER_AUTH_TOKEN_SECRET. + # Must be identical on every Server replica or Hubble login fails behind + # the Service. Priority: existingSecret > value > autoGenerate. + token: + # Optional plaintext signing key (prefer existingSecret in shared clusters). + value: "" + # Pre-created Secret name. Chart does not manage it. + existingSecret: "" + key: token_secret + # When existingSecret and value are empty, create a kept release-auth-token Secret. + autoGenerate: true + # URL announced to PD via server.urls_to_pd for discovery clients such as Hubble. + # Empty announces each Server Pod IP so in-cluster clients retain the full replica list. + # Set this to a URL reachable from outside the cluster (NodePort, LoadBalancer, or Ingress) when an external Hubble uses PD mode; all replicas then announce that shared logical endpoint. + advertiseUrl: "" + service: + type: ClusterIP + annotations: {} + ingress: + enabled: false + className: "" + annotations: {} + hosts: + - host: hugegraph.local + paths: + - path: / + pathType: Prefix + tls: [] + hpa: + enabled: false + minReplicas: 3 + maxReplicas: 10 + targetCPUUtilizationPercentage: 70 + probes: + startup: + # 450s covers the 300s storage wait and Server process startup. The + # chart passes the same budget (failureThreshold * periodSeconds) to + # the image as HG_SERVER_STARTUP_TIMEOUT_S, so the start command and + # the probe give up together instead of the command self-killing at + # the image default of 120s. + failureThreshold: 90 + periodSeconds: 5 + timeoutSeconds: 5 + readiness: + periodSeconds: 10 + failureThreshold: 3 + timeoutSeconds: 5 + liveness: + periodSeconds: 20 + failureThreshold: 3 + timeoutSeconds: 5 + +# Optional Hubble UI. Serves plain HTTP; keep it on a ClusterIP Service or +# behind an HTTPS-terminating Ingress, never directly on an untrusted network. +hubble: + enabled: false + # pd: discover the cluster through PD (enables the cluster operations view). + # direct: talk to the Server client Service only, without PD discovery. + # Current Hubble images require server.auth to be enabled: the UI login + # authenticates against the cluster, so rendering fails otherwise unless + # allowWithoutServerAuth explicitly overrides for images that support it. + mode: pd + allowWithoutServerAuth: false + image: + repository: hugegraph/hubble + # Tracks latest until the next HugeGraph release tag is published. Pin the + # tag (or digest) and switch to IfNotPresent for production. + tag: latest + # Optional immutable digest, for example sha256:abc... When set it wins over + # tag and the image is pulled by digest, which is what a release gate should + # assert instead of a mutable tag. + digest: "" + pullPolicy: Always + port: 8088 + # Hubble keeps UI connection metadata, including any graph credentials + # entered in the UI, in an embedded per-instance H2 database, so the + # Deployment is fixed at a single replica (pointing SPRING_DATASOURCE_URL + # at an external database via extraEnv is not a supported configuration). + # Without persistence that metadata is lost on Pod replacement; graph data + # is unaffected. size and storageClassName apply at install time only, and + # the PVC is kept on helm uninstall. + persistence: + enabled: false + size: 1Gi + storageClassName: "" + resources: {} + # When persistence is enabled and the pod runs as non-root, set a matching + # fsGroup here so H2 can write /hubble-data. + podSecurityContext: {} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + seccompProfile: + type: RuntimeDefault + nodeSelector: {} + tolerations: [] + affinity: {} + topologySpreadConstraints: [] + priorityClassName: "" + podAnnotations: {} + podLabels: {} + # Extra environment variables appended to the hubble container. + extraEnv: [] + terminationGracePeriodSeconds: 30 + serviceAccount: + create: true + name: "" + annotations: {} + # This chart makes no Kubernetes API calls, so no token is mounted. + automountServiceAccountToken: false + service: + type: ClusterIP + annotations: {} + ingress: + enabled: false + className: "" + annotations: {} + hosts: + - host: hubble.local + paths: + - path: / + pathType: Prefix + tls: [] + # Hubble serves plain HTTP, so an Ingress without tls is rejected at + # render time unless this is explicitly set to true for a trusted + # network. + allowPlainHttp: false + probes: + startup: + failureThreshold: 30 + periodSeconds: 5 + timeoutSeconds: 5 + readiness: + periodSeconds: 10 + failureThreshold: 3 + timeoutSeconds: 5 + liveness: + periodSeconds: 20 + failureThreshold: 3 + timeoutSeconds: 5 + +# Kubernetes NetworkPolicy: one policy per component that admits only the +# traffic the components exchange, plus DNS. Off here because these values +# cannot know this release's clients; values-cluster.yaml turns it on. Only +# enforced when the network plugin implements NetworkPolicy (kind v0.25+, +# k3s, Calico, Cilium); elsewhere the objects are accepted and do nothing. +# Nothing outside the release is admitted unless it is listed in +# .extraIngress as standard NetworkPolicy ingress rules: apps in +# other namespaces, Prometheus, Vermeer, and the Ingress controller. Exposing +# a component (NodePort/LoadBalancer pd, server or hubble Service, a Server or +# Hubble Ingress, server.advertiseUrl) with an empty extraIngress fails the +# render. Every extra rule must name its peers ("from", or "to" for egress); +# to admit any address, say so with an ipBlock such as 0.0.0.0/0. +networkPolicy: + enabled: false + pd: + extraIngress: [] + store: + extraIngress: [] + server: + extraIngress: [] + hubble: + extraIngress: [] + # For Hubble's optional outside endpoints (es.urls, prometheus.url set + # through extraEnv). PD, Store and Server reach only the release and DNS. + extraEgress: [] + +# No init Job; see README.md for the HStore initialization contract. +# Install with: helm install ... --wait (no --wait-for-jobs)