diff --git a/.github/workflows/helm-chart-ci.yml b/.github/workflows/helm-chart-ci.yml
new file mode 100644
index 0000000000..c4d8794abe
--- /dev/null
+++ b/.github/workflows/helm-chart-ci.yml
@@ -0,0 +1,188 @@
+#
+# 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 -fsSL https://get.helm.sh/helm-v3.16.2-linux-amd64.tar.gz | tar -xz -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 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.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 must land in the PD JAVA_OPTS verbatim:
+ # 3 on the default topology, 1 on the single-node preset
+ helm template ci helm/hugegraph \
+ | grep -qF 'value: "-Dpartition.default-shard-count=3"'
+ helm template ci helm/hugegraph -f helm/hugegraph/values-single.yaml \
+ | grep -qF 'value: "-Dpartition.default-shard-count=1"'
+
+ - 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
+ }
+ 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
+ must_fail --set hubble.enabled=true
+ 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
+ must_fail --set server.ingress.enabled=true \
+ --set server.ingress.allowPlainHttp=true
+ # 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
+ 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.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 adf9792776..a876069740 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..16884212a6
--- /dev/null
+++ b/helm/hugegraph/README.md
@@ -0,0 +1,672 @@
+# 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 |
+
+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.
+- **Store waits for PD quorum** in an init container before starting, so Store
+ never registers against an incomplete PD Raft group.
+- **The Server startup probe allows at least 450 seconds.** The image may spend
+ 300 seconds waiting for storage and a further 120 seconds in the start
+ command. A lower configured `failureThreshold` is raised to this floor rather
+ than being rejected.
+- **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
+
+```bash
+helm install hugegraph ./helm/hugegraph --namespace hugegraph --create-namespace
+```
+
+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 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.
+
+This first chart is version `0.1.0`. While the contribution is a draft, its
+component image tags and `appVersion` track `latest` with pull policy `Always`.
+Before stable publication, pin all three component tags and `appVersion` to the
+next HugeGraph release 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, so it schedules on clusters of any node count |
+| `values-single.yaml` | Single-node 1+1+1 example |
+| `values-cluster.yaml` | Production 3+3+3 starting point with JVM/resources, PD/Store PDBs, and required anti-affinity for PD and Store; Hubble stays opt-in because the preset does not enable authentication |
+
+`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.
+
+## Upgrading the Chart
+
+```bash
+helm upgrade hugegraph ./helm/hugegraph --namespace hugegraph --reuse-values
+```
+
+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` are the current
+cases. 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.
+
+## 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. Tracks the development image until the next release is pinned | `latest` |
+| `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.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 | `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 | `{}` |
+| `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.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. Tracks the development image until the next release is pinned | `latest` |
+| `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 | `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-quorum init container. Hardened by default; `runAsNonRoot` is not set because the published images run as root | `allowPrivilegeEscalation: false`, `capabilities.drop: [ALL]`, `seccompProfile: RuntimeDefault` |
+| `store.waitTimeoutSeconds` | Bound on the PD-quorum 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`. No PDB is rendered when `store.replicas` is 1 | `2` |
+| `store.waitImage` | Image for the PD-quorum 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. Tracks the development image until the next release is pinned | `latest` |
+| `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.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 | `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 | `false` |
+| `server.auth.existingSecret` | Required when auth is enabled; must contain key `password` | `""` |
+| `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.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 | `[]` |
+| `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`.
+
+### Hubble (optional UI)
+
+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. `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 its client Service URL 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.
+
+Enabling `pd`-mode Hubble switches the Server into PD meta mode (`usePD`,
+`server.urls_to_pd`, `server.deploy_in_k8s`) so that PD can hand Hubble a
+resolvable Server address; auth-enabled installs already run in this mode.
+On an existing release this change rolls the Server Deployment once. 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.
+
+**Enable `server.auth` when using Hubble.** Current Hubble images gate the
+UI behind a login that authenticates against the cluster; with server
+authentication disabled the login cannot complete (the server rejects
+`/auth/login` with "Unconfigured authenticator"), so Hubble is only useful
+on an auth-enabled deployment, where the admin credential from
+`server.auth.existingSecret` logs in. The chart therefore refuses to render
+`hubble.enabled=true` without `server.auth` unless
+`hubble.allowWithoutServerAuth=true` explicitly 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. Tracks the development image until the next release is pinned | `latest` |
+| `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.*`, plus `allowPlainHttp`, which applies to the Hubble Ingress only | `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, `server.auth.existingSecret` must name a Secret
+ containing a `password` key. With authentication disabled it 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.tag` must be non-empty (the chart `appVersion` tracks the
+ Server release, not Hubble), and a Hubble Ingress without `tls` is rejected
+ unless `hubble.ingress.allowPlainHttp=true`.
+- `hubble.enabled` without `server.auth.enabled` is rejected unless
+ `hubble.allowWithoutServerAuth=true`, and setting
+ `server.ingress.allowPlainHttp` is rejected because the plain-HTTP opt-in
+ applies to the Hubble Ingress only.
+
+## Deep Dive
+
+### Connecting to the Cluster
+
+```bash
+kubectl port-forward -n hugegraph svc/hugegraph-server 8080:8080
+curl http://127.0.0.1:8080/versions
+curl 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.
+
+---
+
+### 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. PD exposes REST triggers,
+reachable through the PD client Service:
+
+```bash
+kubectl port-forward -n hugegraph svc/hugegraph-pd-client 8620:8620
+curl http://127.0.0.1:8620/v1/task/patrolPartitions # reconcile shard groups, process tombstoned Stores
+curl http://127.0.0.1:8620/v1/task/balanceLeaders # spread Raft leaders
+curl http://127.0.0.1:8620/v1/task/balancePartitions # spread partition data
+```
+
+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.
+
+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.
+
+## Troubleshooting
+
+### Store Pods Stuck in `Init:0/1`
+
+The Store init container waits for PD to reach Raft quorum. Check PD first:
+
+```bash
+kubectl get pods -l app.kubernetes.io/component=pd
+kubectl 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 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:
+
+```bash
+kubectl exec -c server -- curl -s localhost:8080/graphs
+```
+
+### Queries Fail with "Could not rebind" Right After Creating a Graph
+
+Creating a graph returns before every Server replica has opened and bound it.
+The creating Server writes the graph to PD metadata and registers its own
+Gremlin binding; the other replicas converge independently through a PD watch
+plus a local graph open. Until they do, a Gremlin query routed through the
+load-balanced Service to a not-yet-converged replica fails with a 400 error
+such as `Could not rebind [g]`. This is upstream behavior, not a chart
+setting. Mitigations:
+
+- 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, so follow-up queries hit the creating replica.
+- Poll `/graphs` on each replica until the new graph appears everywhere
+ before opening query traffic.
+
+The underlying fix, orchestrating graph creation through PD, is upstream
+work.
+
+### 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 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
+
+- 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.
+- Newly created graphs are visible on all Server replicas only after a short
+ propagation window (see Troubleshooting: "Could not rebind").
+- 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.
+- 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.
+- 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..cd555837e4
--- /dev/null
+++ b/helm/hugegraph/templates/NOTES.txt
@@ -0,0 +1,85 @@
+{{/*
+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
+
+PD reaches Raft quorum before Store registers, so Store Pods stay in Init until
+PD is ready.
+
+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 }} {{ .Values.server.auth.existingSecret }} -o jsonpath='{.data.password}' | 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 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 }}
diff --git a/helm/hugegraph/templates/_helpers.tpl b/helm/hugegraph/templates/_helpers.tpl
new file mode 100644
index 0000000000..a7993b7e9b
--- /dev/null
+++ b/helm/hugegraph/templates/_helpers.tpl
@@ -0,0 +1,484 @@
+#
+# 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 }}
+
+{{/*
+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 }}
+
+{{/*
+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. Announced to PD via
+server.urls_to_pd so PD-discovered clients (Hubble) get a resolvable address
+instead of the in-pod 0.0.0.0 default.
+*/}}
+{{- define "hugegraph.server.clientUrl" -}}
+{{- printf "http://%s.%s.svc:%d" (include "hugegraph.server.name" .) .Release.Namespace (int .Values.server.port) -}}
+{{- 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.
+*/}}
+{{- 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 -}}
+{{- $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 }}
+
+{{/*
+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" -}}
+{{- $networkPolicy := get .Values "networkPolicy" | default dict -}}
+{{- if (get $networkPolicy "enabled" | default false) -}}
+{{- fail "networkPolicy.enabled=true is unsupported because this chart does not implement NetworkPolicy resources" -}}
+{{- 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 -}}
+{{/*
+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 -}}
+{{/*
+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 -}}
+{{- $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 hasKey $serverIngress "allowPlainHttp" -}}
+{{- fail "server.ingress.allowPlainHttp has no effect; the plain-HTTP opt-in applies to hubble.ingress only" -}}
+{{- 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" "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 "HG_SERVER_BACKEND" "HG_SERVER_PD_PEERS" "HG_SERVER_PD_REST_ENDPOINT" "STORE_REST" "HG_SERVER_INIT_STORE_ENABLED" "HG_SERVER_URLS_TO_PD" "PASSWORD" "JAVA_OPTS" "JAVA_OPTIONS")
+ "hubble" (list "HG_HUBBLE_PD_PEERS" "HG_HUBBLE_PD_SERVER" "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 eq (trim (get $hubbleImage "tag" | default "")) "" -}}
+{{- fail "hubble.image.tag must not be empty: the chart appVersion tracks the Server release, not Hubble, so there is no meaningful fallback" -}}
+{{- 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 -}}
+{{- end }}
+
+{{/*
+podAntiAffinity snippet for a component label key.
+mode: required | preferred | disabled
+*/}}
+{{- 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 }}
diff --git a/helm/hugegraph/templates/hubble-deployment.yaml b/helm/hugegraph/templates/hubble-deployment.yaml
new file mode 100644
index 0000000000..d094db4a45
--- /dev/null
+++ b/helm/hugegraph/templates/hubble-deployment.yaml
@@ -0,0 +1,268 @@
+#
+# 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 }}
+ {{- with .Values.hubble.podAnnotations }}
+ annotations:
+ {{- 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" .) ) }}
+ {{- with .Values.hubble.terminationGracePeriodSeconds }}
+ 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: "{{ .Values.hubble.image.repository }}:{{ .Values.hubble.image.tag }}"
+ 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_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.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
+ 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_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/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..d40cc9325c
--- /dev/null
+++ b/helm/hugegraph/templates/pd-service-client.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: v1
+kind: Service
+metadata:
+ name: {{ include "hugegraph.pd.clientName" . }}
+ labels:
+ {{- include "hugegraph.labels" . | nindent 4 }}
+ app.kubernetes.io/component: pd
+spec:
+ type: ClusterIP
+ selector:
+ {{- include "hugegraph.selectorLabels" . | nindent 4 }}
+ app.kubernetes.io/component: pd
+ ports:
+ - name: rest
+ port: {{ .Values.pd.ports.rest }}
+ targetPort: rest
+ - name: grpc
+ port: {{ .Values.pd.ports.grpc }}
+ targetPort: grpc
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..e16ff53b2d
--- /dev/null
+++ b/helm/hugegraph/templates/pd-statefulset.yaml
@@ -0,0 +1,158 @@
+#
+# 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
+ 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 }}
+ {{- with .Values.pd.podAnnotations }}
+ annotations:
+ {{- 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" .) ) }}
+ {{- with .Values.pd.terminationGracePeriodSeconds }}
+ 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: "{{ .Values.pd.image.repository }}:{{ .Values.pd.image.tag | default $.Chart.AppVersion }}"
+ 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 }}
+ {{- 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: /v1/health
+ 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: /v1/health
+ 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: /v1/health
+ 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-deployment.yaml b/helm/hugegraph/templates/server-deployment.yaml
new file mode 100644
index 0000000000..2d9b1c333a
--- /dev/null
+++ b/helm/hugegraph/templates/server-deployment.yaml
@@ -0,0 +1,304 @@
+#
+# 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 }}
+{{- $hubble := get .Values "hubble" | default dict }}
+{{- $hubblePdMode := and (get $hubble "enabled" | default false) (ne (get $hubble "mode" | default "pd") "direct") }}
+{{/*
+PD meta mode (usePD) is required on two paths: the built-in authenticator
+creates the admin on the PD startup path, and PD-mode Hubble discovers the
+Server through the service URL the Server registers with PD. Both need the
+same four properties, so they share one condition.
+*/}}
+{{- $pdMeta := or .Values.server.auth.enabled $hubblePdMode }}
+{{- $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 }}
+ {{- with .Values.server.podAnnotations }}
+ annotations:
+ {{- 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" .) ) }}
+ {{- with .Values.server.terminationGracePeriodSeconds }}
+ 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: "{{ .Values.server.image.repository }}:{{ .Values.server.image.tag | default $.Chart.AppVersion }}"
+ 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
+ {{- 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. 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: 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 }}
+ - name: STORE_REST
+ value: {{ include "hugegraph.store.restPrimary" . | quote }}
+ - name: HG_SERVER_INIT_STORE_ENABLED
+ value: {{ .Values.server.initStoreEnabled | quote }}
+ {{- if $pdMeta }}
+ - name: HG_SERVER_URLS_TO_PD
+ value: {{ include "hugegraph.server.clientUrl" . | quote }}
+ {{- end }}
+ {{- with .Values.server.extraEnv }}{{ toYaml . | nindent 12 }}{{- end }}
+ {{- with include "hugegraph.javaOptsEnv" .Values.server.javaOpts }}{{ . | trim | nindent 12 }}{{- end }}
+ {{- if and .Values.server.auth.enabled .Values.server.auth.existingSecret }}
+ - name: PASSWORD
+ valueFrom:
+ secretKeyRef:
+ name: {{ .Values.server.auth.existingSecret | quote }}
+ key: password
+ {{- 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: /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-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..973ecacdff
--- /dev/null
+++ b/helm/hugegraph/templates/store-statefulset.yaml
@@ -0,0 +1,193 @@
+#
+# 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
+ 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" .) ) }}
+ {{- with .Values.store.terminationGracePeriodSeconds }}
+ 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 ))
+ echo "Waiting for PD quorum (${REQUIRED}) among: ${HEALTH_PEERS}"
+ until [ "$(
+ ok=0
+ for peer in ${HEALTH_PEERS}; do
+ if curl -fsS "http://${peer}/v1/health" >/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 PD quorum (${REQUIRED}) 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 quorum..."
+ sleep 5
+ done
+ echo "PD quorum reached."
+ {{- with .Values.store.waitResources }}
+ resources:
+ {{- toYaml . | nindent 12 }}
+ {{- end }}
+ containers:
+ - name: store
+ image: "{{ .Values.store.image.repository }}:{{ .Values.store.image.tag | default $.Chart.AppVersion }}"
+ 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..beef5c1d47
--- /dev/null
+++ b/helm/hugegraph/templates/tests/test-connection.yaml
@@ -0,0 +1,83 @@
+#
+# 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:
+ 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: {{ .Values.server.auth.existingSecret | quote }}
+ key: password
+ {{- 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..d3d34cea1f
--- /dev/null
+++ b/helm/hugegraph/testdata/values-pre-hardening.yaml
@@ -0,0 +1,128 @@
+#
+# 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
+ existingSecret: ""
+ 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/values-cluster.yaml b/helm/hugegraph/values-cluster.yaml
new file mode 100644
index 0000000000..98202e5f16
--- /dev/null
+++ b/helm/hugegraph/values-cluster.yaml
@@ -0,0 +1,106 @@
+#
+# 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
+ resources:
+ requests:
+ cpu: "1"
+ memory: 2Gi
+ limits:
+ cpu: "4"
+ memory: 4Gi
+ 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
+
+# The optional Hubble UI is not enabled here: this preset does not enable
+# server authentication, and current Hubble images cannot complete their
+# login against an auth-less cluster. Enable it together with server.auth,
+# starting from:
+#
+# hubble:
+# enabled: true
+# persistence:
+# enabled: true
+# size: 1Gi
+# resources:
+# requests:
+# cpu: 250m
+# memory: 768Mi
+# limits:
+# cpu: "1"
+# memory: 1536Mi
diff --git a/helm/hugegraph/values-single.yaml b/helm/hugegraph/values-single.yaml
new file mode 100644
index 0000000000..86dd71abd6
--- /dev/null
+++ b/helm/hugegraph/values-single.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.
+#
+
+# 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
diff --git a/helm/hugegraph/values.schema.json b/helm/hugegraph/values.schema.json
new file mode 100644
index 0000000000..fa99e12a41
--- /dev/null
+++ b/helm/hugegraph/values.schema.json
@@ -0,0 +1,919 @@
+{
+ "$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"
+ }
+ },
+ "definitions": {
+ "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"
+ ]
+ }
+ }
+ },
+ "resources": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "requests": {
+ "$ref": "#/definitions/resourceList"
+ },
+ "limits": {
+ "$ref": "#/definitions/resourceList"
+ }
+ }
+ },
+ "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": {
+ "$ref": "#/definitions/probe"
+ },
+ "readiness": {
+ "$ref": "#/definitions/probe"
+ },
+ "liveness": {
+ "$ref": "#/definitions/probe"
+ }
+ }
+ },
+ "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"
+ },
+ "partition": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "defaultShardCount": {
+ "$ref": "#/definitions/optionalPositiveInteger"
+ },
+ "storeMaxShardCount": {
+ "$ref": "#/definitions/optionalPositiveInteger"
+ }
+ }
+ },
+ "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"
+ },
+ "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"
+ }
+ }
+ }
+ }
+ },
+ "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
+ },
+ "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"
+ }
+ }
+ }
+ }
+ },
+ "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",
+ "existingSecret"
+ ],
+ "properties": {
+ "enabled": {
+ "type": "boolean"
+ },
+ "existingSecret": {
+ "type": "string"
+ }
+ },
+ "allOf": [
+ {
+ "if": {
+ "properties": {
+ "enabled": {
+ "const": true
+ }
+ }
+ },
+ "then": {
+ "properties": {
+ "existingSecret": {
+ "minLength": 1
+ }
+ }
+ }
+ },
+ {
+ "if": {
+ "properties": {
+ "enabled": {
+ "const": false
+ }
+ }
+ },
+ "then": {
+ "properties": {
+ "existingSecret": {
+ "const": ""
+ }
+ }
+ }
+ }
+ ]
+ },
+ "ingress": {
+ "$ref": "#/definitions/ingress"
+ },
+ "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
+ }
+ }
+ },
+ "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"
+ }
+ }
+ },
+ "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"
+ }
+ }
+ },
+ "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..b13a190e50
--- /dev/null
+++ b/helm/hugegraph/values.yaml
@@ -0,0 +1,375 @@
+#
+# 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
+ # The draft tracks latest until the next HugeGraph release tag is available.
+ # Pin the release tag and switch to IfNotPresent before stable publication.
+ tag: latest
+ 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
+ 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
+ pdb:
+ enabled: true
+ minAvailable: 2
+ # Startup can take a while during Raft bootstrap
+ 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
+ # The draft tracks latest until the next HugeGraph release tag is available.
+ # Pin the release tag and switch to IfNotPresent before stable publication.
+ tag: latest
+ 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
+ # Bound the PD-quorum wait so a cluster that never reaches quorum fails
+ # visibly instead of sitting in Init:0/1 forever.
+ waitTimeoutSeconds: 900
+ # Optional bounds for the PD-quorum wait init container.
+ waitResources: {}
+ 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
+ # The draft tracks latest until the next HugeGraph release tag is available.
+ # Pin the release tag and switch to IfNotPresent before stable publication.
+ tag: latest
+ pullPolicy: Always
+ # Empty preserves the image entrypoint's automatic JVM sizing.
+ javaOpts: ""
+ port: 8080
+ 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.
+ testResources: {}
+ 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:
+ enabled: false
+ # Secret must contain key "password" (no newlines). Applied as
+ # auth.admin_pa when the admin account is first created; changing the
+ # Secret later does not rotate an existing cluster's password.
+ existingSecret: ""
+ 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.
+ 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
+ # The draft tracks latest until the next HugeGraph release tag is available.
+ # Pin the release tag and switch to IfNotPresent before stable publication.
+ tag: latest
+ 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
+
+# No init Job; see README.md for the HStore initialization contract.
+# Install with: helm install ... --wait (no --wait-for-jobs)