mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-08-18 21:26:56 +00:00
helm: enterprise license Secret, and a persistent-claim option for master data (#10601)
* helm: mount an enterprise license Secret into every component
Running the enterprise image under this chart meant hand-rolling
extraVolumes and extraVolumeMounts on every component. Missing one is
easy and quiet: a component without the license silently drops to
community mode, and on the admin that surfaces only as Data Recovery and
Point-in-Time Recovery refusing to enable, with the master looking fine.
Add global.seaweedfs.license.existingSecret. The Secret is mounted
read-only into master, volume, filer, s3, sftp, admin, worker and
all-in-one, and SEAWEED_LICENSE points every process at the file rather
than relying on the binary's search paths, which depend on the working
directory.
The mount is a directory, never a subPath: kubelet refreshes Secret
contents in place, but a subPath is resolved once at container start and
never updates, which would break license renewal. There is deliberately
no checksum annotation on the pod template either — that would roll every
pod on renewal, the opposite of what is wanted. Verified on kind: the
renewed file reached a running master ~70s after the Secret was patched,
same pod UID, restartCount 0.
Also documents that only the master re-reads the license on a timer
today; the other components pick a renewal up on their next restart.
* helm: keep master data on a claim by default
The master's -mdir holds its Raft log and snapshots, and with them the
cluster's topology UUID — the identity an enterprise license is issued
against. It defaulted to a hostPath under /ssd, which does not follow a
rescheduled pod: the master came back with an empty data directory, a
freshly generated cluster UUID, and a license that no longer matched.
With the chart's default of a single master replica there is no peer to
recover the identity from either.
Default master.data.type to persistentVolumeClaim, sized 1Gi (Raft state
is small). hostPath stays available for anyone who wants it.
This is breaking for existing releases: volumeClaimTemplates is immutable,
so helm upgrade on a release installed with the old default fails with
"updates to statefulset spec for fields other than ... are forbidden".
Verified on kind, along with both ways out — pinning
master.data.type=hostPath upgrades cleanly, and the documented migration
(stop the master, pre-seed a claim named after the StatefulSet, upgrade)
preserves the cluster UUID. Seeding has to happen while the master is
stopped; copying into a live pod loses the state, since the running
master rewrites its Raft files before the restart.
* helm: mount the license on masters only
The master is what reads the license file: it validates it, enforces the
capacity limit and binds it to the cluster UUID. Mounting the Secret on
volume, filer, s3, sftp, admin and worker put it in six more containers
that never look at it, so drop it there and keep master plus all-in-one,
which runs `weed server -master`.
Two fixes from review while here:
- project only the configured key out of the Secret, so an unrelated
key in the same Secret is not exposed to the container. Verified the
key-scoped projection still updates in place: patching the Secret
reached the running master in ~50s, same pod UID, restartCount 0.
- drop SEAWEED_LICENSE from merged extraEnvironmentVars while a
license Secret is configured. It used to be possible to render the
key twice in one container, with the user's value winning over the
path the chart actually mounts.
CI now pins the scope (master only, all-in-one separately), the
key-scoped projection, readOnly, and that SEAWEED_LICENSE renders once.
* helm: fix the documented master-data migration
The seed pod in the migration never mounted the claim it was supposed to
seed, so following the steps verbatim copied the Raft state onto the
pod's ephemeral filesystem and threw it away with the pod — landing the
reader in exactly the empty-claim, new-cluster-UUID state the section
exists to avoid. Give the pod the volume.
The names were assembled as <release>-seaweedfs-*, which is wrong
whenever the release name already contains the chart name or an override
is set; read the StatefulSet name from the cluster instead and derive the
claim from it. Also scope the procedure to the chart's single-master
default, and create the Secret in the release namespace.
Trims the enterprise prose this section had accumulated: this is the OSS
chart, and the master-data default is a durability fix that stands on its
own.
* helm: quote the projected license key, reserve it on the secret env path
A secretKey that YAML reads as a non-string (123, yes, no) rendered
unquoted into the volume's items, so the projection would not match the
Secret's key. Quote both key and path.
all-in-one renders secretExtraEnvironmentVars itself, outside the merge
helper that already drops SEAWEED_LICENSE, so an entry there could still
render the variable twice. Skip it there too while a license Secret is
configured. The master template has no such block, so this is the only
remaining path.
* helm: correct the license helper comments after scoping to masters
* helm: keep hostPath as the master data default
Defaulting master.data.type to a claim broke every existing release:
volumeClaimTemplates is immutable on a StatefulSet, so helm upgrade
failed with "updates to statefulset spec for fields other than ... are
forbidden" before it changed anything.
Keep hostPath as the default and document the claim as the option to
choose — for a new install, or for an existing one via the migration
already in the README. The chart supported both types all along; only
the default moves back.
Every immutable field of every rendered StatefulSet is now identical to
upstream under default values, so an in-place upgrade cannot trip the
API. Verified on kind: install with the unmodified upstream chart,
upgrade to this branch (ok), upgrade again turning the license Secret on
(ok, volume added in place). A fresh install with
master.data.type=persistentVolumeClaim binds its claim as before.
The whole PR is additive now: nothing renders differently until a value
is set.
* helm: scope the migration's StatefulSet lookup to the release
* helm: trim the comments added by this change
This commit is contained in:
@@ -1296,6 +1296,180 @@ jobs:
|
||||
PYEOF
|
||||
echo "NetworkPolicy rendering tests passed"
|
||||
|
||||
echo ""
|
||||
echo "=== Testing enterprise license mount + master persistence ==="
|
||||
python3 - "$CHART_DIR" <<'PYEOF'
|
||||
import subprocess, sys, yaml
|
||||
chart = sys.argv[1]
|
||||
|
||||
def render(values):
|
||||
args = ["helm", "template", "test", chart]
|
||||
for k, v in values.items():
|
||||
args += ["--set", f"{k}={v}"]
|
||||
return subprocess.check_output(args, text=True)
|
||||
|
||||
def workloads(manifest):
|
||||
for d in yaml.safe_load_all(manifest):
|
||||
if d and d.get("kind") in ("Deployment", "StatefulSet"):
|
||||
yield d
|
||||
|
||||
ALL_ON = {
|
||||
"admin.enabled": "true",
|
||||
"s3.enabled": "true",
|
||||
"sftp.enabled": "true",
|
||||
"worker.enabled": "true",
|
||||
}
|
||||
failed = []
|
||||
|
||||
# Case 1: unconfigured renders nothing, so existing installs are unaffected.
|
||||
out = render(ALL_ON)
|
||||
if "SEAWEED_LICENSE" in out or "seaweedfs-license" in out:
|
||||
failed.append("defaults: license plumbing should not render")
|
||||
else:
|
||||
print("defaults: no license volume/env (unchanged)")
|
||||
|
||||
# Case 2: only the workloads that run a master carry the license.
|
||||
LICENSE_PATH = "/etc/seaweedfs/license/seaweed-license.json"
|
||||
out = render(dict(ALL_ON, **{
|
||||
"global.seaweedfs.license.existingSecret": "lic",
|
||||
}))
|
||||
carrying = set()
|
||||
for d in workloads(out):
|
||||
name = d["metadata"]["name"]
|
||||
pod = d["spec"]["template"]["spec"]
|
||||
vols = [v for v in pod.get("volumes", []) if v.get("name") == "seaweedfs-license"]
|
||||
if not vols:
|
||||
continue
|
||||
carrying.add(name)
|
||||
secret = vols[0].get("secret", {})
|
||||
if secret.get("secretName") != "lic":
|
||||
failed.append(f"{name}: license volume points at {secret.get('secretName')!r}, not the configured Secret")
|
||||
items = [i.get("key") for i in secret.get("items", [])]
|
||||
if items != ["seaweed-license.json"]:
|
||||
failed.append(f"{name}: license volume projects {items}, expected only the license key")
|
||||
for c in pod.get("containers", []):
|
||||
mounts = [m for m in c.get("volumeMounts", []) if m["name"] == "seaweedfs-license"]
|
||||
if not mounts:
|
||||
failed.append(f"{name}/{c['name']}: license volume not mounted")
|
||||
continue
|
||||
# subPath would freeze the file at container start.
|
||||
if any("subPath" in m for m in mounts):
|
||||
failed.append(f"{name}/{c['name']}: license mount uses subPath (renewals would not propagate)")
|
||||
if not all(m.get("readOnly") for m in mounts):
|
||||
failed.append(f"{name}/{c['name']}: license mount is not readOnly")
|
||||
env = {e["name"]: e.get("value") for e in c.get("env", [])}
|
||||
if env.get("SEAWEED_LICENSE") != LICENSE_PATH:
|
||||
failed.append(f"{name}/{c['name']}: SEAWEED_LICENSE not set to the mounted file")
|
||||
if carrying != {"test-seaweedfs-master"}:
|
||||
failed.append(f"license: expected only the master to carry it, got {sorted(carrying)}")
|
||||
else:
|
||||
print("license: master only, key-scoped, readOnly, no subPath, SEAWEED_LICENSE set")
|
||||
|
||||
# all-in-one runs `weed server -master`, so it needs the license too.
|
||||
out = render({
|
||||
"allInOne.enabled": "true",
|
||||
"master.enabled": "false",
|
||||
"volume.enabled": "false",
|
||||
"filer.enabled": "false",
|
||||
"global.seaweedfs.license.existingSecret": "lic",
|
||||
})
|
||||
aio = [d for d in workloads(out) if d["metadata"]["name"].endswith("-all-in-one")]
|
||||
if len(aio) != 1:
|
||||
failed.append(f"all-in-one: expected 1 workload, got {len(aio)}")
|
||||
else:
|
||||
pod = aio[0]["spec"]["template"]["spec"]
|
||||
if not any(v.get("name") == "seaweedfs-license" for v in pod.get("volumes", [])):
|
||||
failed.append("all-in-one: missing the license volume")
|
||||
else:
|
||||
print("all-in-one: carries the license")
|
||||
|
||||
# SEAWEED_LICENSE is reserved on both env paths.
|
||||
out = render({
|
||||
"global.seaweedfs.license.existingSecret": "lic",
|
||||
"global.seaweedfs.extraEnvironmentVars.SEAWEED_LICENSE": "/tmp/bogus.json",
|
||||
})
|
||||
for d in workloads(out):
|
||||
if not d["metadata"]["name"].endswith("-master"):
|
||||
continue
|
||||
for c in d["spec"]["template"]["spec"].get("containers", []):
|
||||
names = [e["name"] for e in c.get("env", [])]
|
||||
if names.count("SEAWEED_LICENSE") != 1:
|
||||
failed.append(f"SEAWEED_LICENSE rendered {names.count('SEAWEED_LICENSE')} times")
|
||||
else:
|
||||
value = next(e.get("value") for e in c["env"] if e["name"] == "SEAWEED_LICENSE")
|
||||
if value != LICENSE_PATH:
|
||||
failed.append(f"SEAWEED_LICENSE overridden to {value!r} by extraEnvironmentVars")
|
||||
else:
|
||||
print("SEAWEED_LICENSE: reserved, rendered once, points at the mount")
|
||||
|
||||
# secretExtraEnvironmentVars is rendered outside the merge helper.
|
||||
out = render({
|
||||
"allInOne.enabled": "true",
|
||||
"master.enabled": "false",
|
||||
"volume.enabled": "false",
|
||||
"filer.enabled": "false",
|
||||
"global.seaweedfs.license.existingSecret": "lic",
|
||||
"allInOne.secretExtraEnvironmentVars.SEAWEED_LICENSE.secretKeyRef.name": "x",
|
||||
"allInOne.secretExtraEnvironmentVars.SEAWEED_LICENSE.secretKeyRef.key": "y",
|
||||
})
|
||||
for d in workloads(out):
|
||||
if not d["metadata"]["name"].endswith("-all-in-one"):
|
||||
continue
|
||||
for c in d["spec"]["template"]["spec"].get("containers", []):
|
||||
names = [e["name"] for e in c.get("env", [])]
|
||||
if names.count("SEAWEED_LICENSE") != 1:
|
||||
failed.append(f"all-in-one: SEAWEED_LICENSE rendered {names.count('SEAWEED_LICENSE')} times via secretExtraEnvironmentVars")
|
||||
elif next(e.get("value") for e in c["env"] if e["name"] == "SEAWEED_LICENSE") != LICENSE_PATH:
|
||||
failed.append("all-in-one: secretExtraEnvironmentVars overrode SEAWEED_LICENSE")
|
||||
else:
|
||||
print("all-in-one: SEAWEED_LICENSE reserved on the secret env path too")
|
||||
|
||||
# Case 3: the default stays hostPath. volumeClaimTemplates is immutable,
|
||||
# so growing one here would break helm upgrade on existing releases.
|
||||
masters = [d for d in workloads(render({})) if d["metadata"]["name"].endswith("-master")]
|
||||
if len(masters) != 1:
|
||||
failed.append(f"master: expected 1 StatefulSet by default, got {len(masters)}")
|
||||
else:
|
||||
d = masters[0]
|
||||
claims = [c["metadata"]["name"] for c in d["spec"].get("volumeClaimTemplates", [])]
|
||||
if any(c.startswith("data-") for c in claims):
|
||||
failed.append(f"master: default grew a data volumeClaimTemplate ({claims}); "
|
||||
"that breaks helm upgrade on existing releases")
|
||||
data_host = [v["name"] for v in d["spec"]["template"]["spec"].get("volumes", [])
|
||||
if v.get("hostPath") and v["name"].startswith("data-")]
|
||||
if not data_host:
|
||||
failed.append("master: default no longer renders a hostPath data volume")
|
||||
else:
|
||||
print("master: default still hostPath (upgrade-compatible)")
|
||||
|
||||
# Case 4: opting into a claim renders one, with the requested size.
|
||||
masters = [d for d in workloads(render({"master.data.type": "persistentVolumeClaim"}))
|
||||
if d["metadata"]["name"].endswith("-master")]
|
||||
if len(masters) != 1:
|
||||
failed.append(f"master.data.type=persistentVolumeClaim: expected 1 StatefulSet, got {len(masters)}")
|
||||
else:
|
||||
d = masters[0]
|
||||
claims = {c["metadata"]["name"]: c["spec"]["resources"]["requests"]["storage"]
|
||||
for c in d["spec"].get("volumeClaimTemplates", [])}
|
||||
data = {k: v for k, v in claims.items() if k.startswith("data-")}
|
||||
if not data:
|
||||
failed.append(f"master.data.type=persistentVolumeClaim: no data claim rendered ({claims})")
|
||||
elif set(data.values()) != {"1Gi"}:
|
||||
failed.append(f"master.data.type=persistentVolumeClaim: unexpected size {data}")
|
||||
else:
|
||||
print(f"master.data.type=persistentVolumeClaim: renders {sorted(data)} at 1Gi")
|
||||
if any(v.get("hostPath") and v["name"].startswith("data-")
|
||||
for v in d["spec"]["template"]["spec"].get("volumes", [])):
|
||||
failed.append("master.data.type=persistentVolumeClaim: data still on a hostPath")
|
||||
|
||||
if failed:
|
||||
print("\nFAIL:", file=sys.stderr)
|
||||
for f in failed:
|
||||
print(f" - {f}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
PYEOF
|
||||
echo "License + master persistence tests passed"
|
||||
|
||||
echo "All template rendering tests passed!"
|
||||
|
||||
- name: Create kind cluster
|
||||
|
||||
@@ -107,6 +107,120 @@ https://github.com/rancher/local-path-provisioner
|
||||
you can use ANY storage class you like, just update the correct storage-class
|
||||
for your deployment.
|
||||
|
||||
### Master data: hostPath vs a claim
|
||||
|
||||
The master's `-mdir` holds its Raft log and snapshots, and with them the
|
||||
cluster's identity (its topology UUID). `master.data.type` defaults to
|
||||
`hostPath`, which does not follow a pod to another node: a master that is
|
||||
rescheduled comes back with an empty data directory and a brand new cluster
|
||||
UUID. With the chart's default of a single master replica there is no peer to
|
||||
recover the identity from either.
|
||||
|
||||
Putting the master's data on a claim avoids that:
|
||||
|
||||
```yaml
|
||||
master:
|
||||
data:
|
||||
type: "persistentVolumeClaim"
|
||||
size: "1Gi"
|
||||
storageClass: "" # empty uses the cluster's default StorageClass
|
||||
```
|
||||
|
||||
Raft state is small, so a modest claim is enough — sizing matters far more for
|
||||
volume and filer.
|
||||
|
||||
The default is left at `hostPath` for backward compatibility:
|
||||
`volumeClaimTemplates` is immutable on a StatefulSet, so flipping the type on a
|
||||
release that already exists fails, whether the chart changes the default or you
|
||||
change it yourself:
|
||||
|
||||
```text
|
||||
StatefulSet.apps "<release>-seaweedfs-master" is invalid: spec: Forbidden:
|
||||
updates to statefulset spec for fields other than 'replicas', ... are forbidden
|
||||
```
|
||||
|
||||
New installs can set the claim from the start. To move an **existing** release
|
||||
onto a claim without losing the cluster UUID, use the migration below. The
|
||||
claim has to be seeded while the master is stopped: a running master rewrites
|
||||
its Raft state, so copying into a live pod is silently undone by the next
|
||||
restart.
|
||||
|
||||
The steps below are for the chart's default of a single master
|
||||
(`master.replicas: 1`). With several master replicas, repeat steps 1, 3 and 4
|
||||
for every ordinal, or migrate one at a time and let the remaining quorum
|
||||
re-replicate.
|
||||
|
||||
Take the names from the cluster rather than assembling them — the release
|
||||
name, `nameOverride` and `fullnameOverride` all feed the chart's fullname
|
||||
helper, so `<release>-seaweedfs` is not always right:
|
||||
|
||||
```bash
|
||||
NS=<namespace>; REL=<release>
|
||||
# scope by instance as well as component: several releases can share a namespace
|
||||
STS=$(kubectl -n $NS get sts \
|
||||
-l app.kubernetes.io/instance=$REL,app.kubernetes.io/component=master \
|
||||
-o jsonpath='{.items[0].metadata.name}')
|
||||
POD=$STS-0
|
||||
# a StatefulSet names its claims <template>-<statefulset>-<ordinal>, and this
|
||||
# chart's template is data-<namespace>
|
||||
PVC=data-$NS-$STS-0
|
||||
|
||||
# 1. back up the master data directory
|
||||
kubectl -n $NS cp $POD:/data ./master-backup
|
||||
|
||||
# 2. stop the master, leaving the rest of the release running
|
||||
kubectl -n $NS delete sts $STS --cascade=orphan
|
||||
kubectl -n $NS delete pod $POD
|
||||
|
||||
# 3. create the claim the new StatefulSet will adopt, and seed it through a
|
||||
# pod that actually mounts it
|
||||
kubectl -n $NS apply -f - <<EOF
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: $PVC
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: seed
|
||||
spec:
|
||||
containers:
|
||||
- name: seed
|
||||
image: alpine:3.20
|
||||
command: ["sleep", "600"]
|
||||
volumeMounts:
|
||||
- name: d
|
||||
mountPath: /data
|
||||
volumes:
|
||||
- name: d
|
||||
persistentVolumeClaim:
|
||||
claimName: $PVC
|
||||
EOF
|
||||
kubectl -n $NS wait --for=condition=Ready pod/seed --timeout=120s
|
||||
kubectl -n $NS cp ./master-backup/m9333 seed:/data/
|
||||
kubectl -n $NS exec seed -- ls /data/m9333 # conf, log, snapshot, state
|
||||
kubectl -n $NS delete pod seed
|
||||
|
||||
# 4. upgrade; the StatefulSet adopts the claim you created
|
||||
helm upgrade $REL seaweedfs/seaweedfs -n $NS -f values.yaml
|
||||
```
|
||||
|
||||
Confirm the UUID survived — it must match what the cluster reported before the
|
||||
migration:
|
||||
|
||||
```bash
|
||||
kubectl -n $NS exec $POD -- curl -s localhost:9333/license/status
|
||||
```
|
||||
|
||||
**Or accept a new cluster UUID** and, if you run the enterprise edition, have
|
||||
the license re-issued against it.
|
||||
|
||||
## current instances config (AIO):
|
||||
|
||||
1 instance for each type (master/filer+s3/volume)
|
||||
@@ -426,3 +540,38 @@ helm install seaweedfs seaweedfs/seaweedfs \
|
||||
|
||||
For enterprise users, please visit [seaweedfs.com](https://seaweedfs.com) for the SeaweedFS Enterprise Edition,
|
||||
which has advanced features, including data recovery, self-healing storage, customizable erasure coding, EC vacuum and repair, etc.
|
||||
|
||||
To run it, set the image and point the chart at a Secret holding the license
|
||||
file:
|
||||
|
||||
```bash
|
||||
kubectl create secret generic seaweedfs-license -n <namespace> \
|
||||
--from-file=seaweed-license.json=/path/to/seaweed-license.json
|
||||
```
|
||||
|
||||
```yaml
|
||||
global:
|
||||
seaweedfs:
|
||||
image:
|
||||
name: chrislusf/seaweedfs-enterprise
|
||||
license:
|
||||
existingSecret: seaweedfs-license
|
||||
# secretKey: seaweed-license.json # key within the Secret
|
||||
# mountPath: /etc/seaweedfs/license # directory it is mounted at
|
||||
```
|
||||
|
||||
Set the image globally rather than per component: a per-component
|
||||
`imageOverride` wins, and a cluster that mixes editions comes up looking
|
||||
healthy with enterprise features quietly off.
|
||||
|
||||
Only the master reads the license, so the Secret is mounted read-only there
|
||||
and on all-in-one (which runs `weed server -master`). It is mounted as a
|
||||
directory, not a `subPath`, so a renewed Secret reaches the running master —
|
||||
which re-reads the file periodically — without a restart.
|
||||
|
||||
The license is tied to the cluster UUID kept in the master's Raft state, so put
|
||||
`master.data` on a claim — a master that restarts onto an empty data directory
|
||||
generates a new UUID and the license stops matching. See
|
||||
[Master data](#master-data-hostpath-vs-a-claim). Check the binding with
|
||||
`kubectl exec <master-pod> -- curl -s localhost:9333/license/status`
|
||||
(`cluster_uuid` must equal `license_uuid`).
|
||||
|
||||
@@ -80,6 +80,7 @@ spec:
|
||||
image: {{ template "seaweedfs.master.image" . }}
|
||||
imagePullPolicy: {{ default "IfNotPresent" .Values.global.seaweedfs.imagePullPolicy }}
|
||||
env:
|
||||
{{- include "seaweedfs.licenseEnv" . | nindent 12 }}
|
||||
{{- /* Determine default cluster alias and the corresponding env var keys to avoid conflicts */}}
|
||||
{{- $mergedExtraEnvironmentVars := dict }}
|
||||
{{- include "seaweedfs.mergeExtraEnvironmentVars" (dict "global" .Values.global.seaweedfs "component" .Values.allInOne "target" $mergedExtraEnvironmentVars) }}
|
||||
@@ -120,11 +121,13 @@ spec:
|
||||
value: {{ include "seaweedfs.cluster.filerAddress" . | quote }}
|
||||
{{- if .Values.allInOne.secretExtraEnvironmentVars }}
|
||||
{{- range $key, $value := .Values.allInOne.secretExtraEnvironmentVars }}
|
||||
{{- if not (and $key (eq $key "SEAWEED_LICENSE") (include "seaweedfs.licenseEnabled" $)) }}
|
||||
- name: {{ $key }}
|
||||
valueFrom:
|
||||
{{ toYaml $value | nindent 16 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
command:
|
||||
- "/bin/sh"
|
||||
- "-ec"
|
||||
@@ -352,6 +355,7 @@ spec:
|
||||
{{- include "seaweedfs.s3.tlsVolumeMount" . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- include "seaweedfs.licenseVolumeMount" . | nindent 12 }}
|
||||
{{ tpl .Values.allInOne.extraVolumeMounts . | nindent 12 }}
|
||||
ports:
|
||||
- containerPort: {{ .Values.master.port }}
|
||||
@@ -419,6 +423,7 @@ spec:
|
||||
{{- include "seaweedfs.tplvalues.render" (dict "value" .Values.allInOne.sidecars "context" $) | nindent 8 }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
{{- include "seaweedfs.licenseVolume" . | nindent 8 }}
|
||||
- name: data
|
||||
{{- if eq .Values.allInOne.data.type "hostPath" }}
|
||||
hostPath:
|
||||
|
||||
@@ -83,6 +83,7 @@ spec:
|
||||
image: {{ template "seaweedfs.master.image" . }}
|
||||
imagePullPolicy: {{ default "IfNotPresent" .Values.global.seaweedfs.imagePullPolicy }}
|
||||
env:
|
||||
{{- include "seaweedfs.licenseEnv" . | nindent 12 }}
|
||||
- name: POD_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
@@ -211,6 +212,7 @@ spec:
|
||||
readOnly: true
|
||||
mountPath: /usr/local/share/ca-certificates/client/
|
||||
{{- end }}
|
||||
{{- include "seaweedfs.licenseVolumeMount" . | nindent 12 }}
|
||||
{{ tpl .Values.master.extraVolumeMounts . | nindent 12 | trim }}
|
||||
ports:
|
||||
- containerPort: {{ .Values.master.port }}
|
||||
@@ -256,6 +258,7 @@ spec:
|
||||
{{- include "seaweedfs.tplvalues.render" (dict "value" .Values.master.sidecars "context" $) | nindent 8 }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
{{- include "seaweedfs.licenseVolume" . | nindent 8 }}
|
||||
{{- if eq .Values.master.logs.type "hostPath" }}
|
||||
- name: seaweedfs-master-log-volume
|
||||
hostPath:
|
||||
|
||||
@@ -69,6 +69,11 @@ Inject extra environment vars in the format key:value, if populated
|
||||
{{- range $key, $value := $component }}
|
||||
{{- $_ := set $target $key $value }}
|
||||
{{- end }}
|
||||
{{/* the license block owns SEAWEED_LICENSE; letting one through here too would
|
||||
render the key twice in one container */}}
|
||||
{{- if ((.global | default dict).license | default dict).existingSecret }}
|
||||
{{- $_ := unset $target "SEAWEED_LICENSE" }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Return the proper filer image */}}
|
||||
@@ -450,6 +455,47 @@ true
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
|
||||
{{/* True when an enterprise license Secret is configured. */}}
|
||||
{{- define "seaweedfs.licenseEnabled" -}}
|
||||
{{- if ((.Values.global.seaweedfs).license).existingSecret -}}
|
||||
true
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Enterprise license volume. Projects just the license key. */}}
|
||||
{{- define "seaweedfs.licenseVolume" -}}
|
||||
{{- if include "seaweedfs.licenseEnabled" . -}}
|
||||
- name: seaweedfs-license
|
||||
secret:
|
||||
secretName: {{ .Values.global.seaweedfs.license.existingSecret }}
|
||||
defaultMode: 0444
|
||||
items:
|
||||
- key: {{ .Values.global.seaweedfs.license.secretKey | default "seaweed-license.json" | quote }}
|
||||
path: {{ .Values.global.seaweedfs.license.secretKey | default "seaweed-license.json" | quote }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Enterprise license volume mount. Never a subPath: that is resolved once at
|
||||
container start, so a renewed Secret would not reach a running master. */}}
|
||||
{{- define "seaweedfs.licenseVolumeMount" -}}
|
||||
{{- if include "seaweedfs.licenseEnabled" . -}}
|
||||
- name: seaweedfs-license
|
||||
readOnly: true
|
||||
mountPath: {{ .Values.global.seaweedfs.license.mountPath | default "/etc/seaweedfs/license" | quote }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
|
||||
{{/* SEAWEED_LICENSE, set explicitly rather than relying on the binary's search
|
||||
paths, which depend on the working directory. */}}
|
||||
{{- define "seaweedfs.licenseEnv" -}}
|
||||
{{- if include "seaweedfs.licenseEnabled" . -}}
|
||||
- name: SEAWEED_LICENSE
|
||||
value: {{ printf "%s/%s"
|
||||
(.Values.global.seaweedfs.license.mountPath | default "/etc/seaweedfs/license")
|
||||
(.Values.global.seaweedfs.license.secretKey | default "seaweed-license.json") | quote }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Generate a compatible trafficDistribution value due to "PreferClose" fast deprecation in k8s v1.35.
|
||||
Accepts a dict with "value" (the trafficDistribution string) and "Capabilities". */}}
|
||||
{{- define "seaweedfs.trafficDistribution" -}}
|
||||
|
||||
@@ -12,7 +12,16 @@ global:
|
||||
image:
|
||||
# if repository is set, it overrides the namespace part of image.name
|
||||
repository: ""
|
||||
# chrislusf/seaweedfs-enterprise for the enterprise edition
|
||||
name: chrislusf/seaweedfs
|
||||
# Enterprise license file, held in a Secret in the release namespace and
|
||||
# mounted read-only on the components that run a master. A renewed Secret
|
||||
# reaches the running master without a restart. SEAWEED_LICENSE is reserved
|
||||
# while this is set: an extraEnvironmentVars entry of that name is dropped.
|
||||
license:
|
||||
existingSecret: ""
|
||||
secretKey: seaweed-license.json
|
||||
mountPath: /etc/seaweedfs/license
|
||||
imagePullPolicy: IfNotPresent
|
||||
restartPolicy: Always
|
||||
loggingLevel: 1
|
||||
@@ -133,8 +142,13 @@ master:
|
||||
# You can also use emptyDir storage:
|
||||
# data:
|
||||
# type: "emptyDir"
|
||||
# -mdir holds the Raft log and snapshots, and with them the cluster's
|
||||
# identity (its topology UUID). A hostPath does not follow a rescheduled pod,
|
||||
# so prefer type "persistentVolumeClaim" for new installs. The default stays
|
||||
# hostPath so existing releases keep upgrading; see the README to switch one.
|
||||
data:
|
||||
type: "hostPath"
|
||||
size: "1Gi"
|
||||
storageClass: ""
|
||||
hostPathPrefix: /ssd
|
||||
|
||||
|
||||
Reference in New Issue
Block a user