Compare commits

...

2 Commits

Author SHA1 Message Date
Andrei Kvapil
42a70bcdd1 [seaweedfs] add externalClients support
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-07-24 01:17:14 +02:00
Andrei Kvapil
480364d842 [seaweedfs] Add External topology
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-07-22 14:11:08 +02:00
13 changed files with 420 additions and 13 deletions

View File

@@ -4,4 +4,4 @@ include ../../../scripts/package.mk
generate:
readme-generator-for-helm -v values.yaml -s values.schema.json -r README.md
yq -o json -i '.properties.topology.enum = ["Simple","MultiZone"]' values.schema.json
yq -o json -i '.properties.topology.enum = ["Simple","MultiZone","External"]' values.schema.json

View File

@@ -7,9 +7,10 @@
| Name | Description | Value |
| ------------------- | ------------------------------------------------------------------------------------------------------ | -------- |
| `host` | The hostname used to access the SeaweedFS externally (defaults to 's3' subdomain for the tenant host). | `""` |
| `topology` | The topology of the SeaweedFS cluster. (allowed values: Simple, MultiZone) | `Simple` |
| `topology` | The topology of the SeaweedFS cluster. (allowed values: Simple, MultiZone, External) | `Simple` |
| `replicationFactor` | The number of replicas for each volume in the SeaweedFS cluster. | `2` |
| `replicas` | Persistent Volume size for SeaweedFS | `2` |
| `size` | Persistent Volume size | `10Gi` |
| `storageClass` | StorageClass used to store the data | `""` |
| `zones` | A map of zones for MultiZone topology. Each zone can have its own number of replicas and size. | `{}` |
| `externalClients` | A map of external clusters for managing bucketclaims. | `{}` |

View File

@@ -0,0 +1,6 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-deployed-topology
data:
topology: "{{ .Values.topology }}"

View File

@@ -3,6 +3,7 @@ kind: Role
metadata:
name: {{ .Release.Name }}-dashboard-resources
rules:
{{- if not (eq .Values.topology "External") }}
- apiGroups:
- ""
resources:
@@ -27,6 +28,16 @@ rules:
- {{ $.Release.Name }}-volume
- {{ $.Release.Name }}-db
verbs: ["get", "list", "watch"]
{{- else }}
- apiGroups:
- ""
resources:
- secrets
resourceNames:
- {{ .Release.Namespace }}-seaweedfs-objectstorage-provisioner
verbs: ["get", "list", "watch"]
{{- end }}
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
@@ -38,3 +49,15 @@ roleRef:
kind: Role
name: {{ .Release.Name }}-dashboard-resources
apiGroup: rbac.authorization.k8s.io
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: {{ .Release.Name }}-dashboard-resources
subjects:
{{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "super-admin" .Release.Namespace) }}
roleRef:
kind: Role
name: {{ .Release.Name }}-dashboard-resources
apiGroup: rbac.authorization.k8s.io

View File

@@ -0,0 +1,159 @@
{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }}
{{- $ingress := index $myNS.metadata.annotations "namespace.cozystack.io/ingress" }}
{{- $host := index $myNS.metadata.annotations "namespace.cozystack.io/host" }}
{{- range $clientName, $client := .Values.externalClients }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ $.Release.Name }}-objectstorage-provisioner-{{ $clientName }}
namespace: {{ $client.namespace }}
labels:
app.kubernetes.io/component: objectstorage-provisioner-{{ $clientName }}
app.kubernetes.io/instance: seaweedfs
app.kubernetes.io/name: {{ $.Release.Name }}
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/component: objectstorage-provisioner-{{ $clientName }}
app.kubernetes.io/instance: seaweedfs
app.kubernetes.io/name: {{ $.Release.Name }}
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
app.kubernetes.io/component: objectstorage-provisioner-{{ $clientName }}
app.kubernetes.io/instance: seaweedfs
app.kubernetes.io/name: {{ $.Release.Name }}
spec:
containers:
- name: seaweedfs-cosi-driver
image: ghcr.io/seaweedfs/seaweedfs-cosi-driver:v0.1.2
imagePullPolicy: IfNotPresent
env:
- name: DRIVERNAME
value: {{ printf "%s.seaweedfs.objectstorage.k8s.io" $client.namespace }}
- name: ENDPOINT
value: https://{{ $.Values.host | default (printf "s3.%s" $host) }}
- name: SEAWEEDFS_FILER
value: seaweedfs-filer:18888
- name: WEED_GRPC_CLIENT_KEY
value: /usr/local/share/ca-certificates/client/tls.key
- name: WEED_GRPC_CLIENT_CERT
value: /usr/local/share/ca-certificates/client/tls.crt
- name: WEED_GRPC_CA
value: /usr/local/share/ca-certificates/client/ca.crt
- name: WEED_CLUSTER_DEFAULT
value: sw
- name: WEED_CLUSTER_SW_FILER
value: seaweedfs-filer-client:8888
- name: WEED_CLUSTER_SW_MASTER
value: seaweedfs-master:9333
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
volumeMounts:
- mountPath: /var/lib/cosi
name: socket
- mountPath: /etc/seaweedfs/security.toml
name: security-config
readOnly: true
subPath: security.toml
- mountPath: /usr/local/share/ca-certificates/ca/
name: ca-cert
readOnly: true
- mountPath: /usr/local/share/ca-certificates/master/
name: master-cert
readOnly: true
- mountPath: /usr/local/share/ca-certificates/volume/
name: volume-cert
readOnly: true
- mountPath: /usr/local/share/ca-certificates/filer/
name: filer-cert
readOnly: true
- mountPath: /usr/local/share/ca-certificates/client/
name: client-cert
readOnly: true
- name: seaweedfs-cosi-sidecar
image: ghcr.io/cozystack/cozystack/objectstorage-sidecar
imagePullPolicy: IfNotPresent
args:
- --v=5
env:
- name: KUBECONFIG
value: /kubeconfig
- name: POD_NAMESPACE
value: {{ $client.namespace }}
volumeMounts:
- mountPath: /var/lib/cosi
name: socket
- mountPath: /kubeconfig
name: kubeconfig
subPath: kubeconfig
readOnly: true
enableServiceLinks: false
restartPolicy: Always
terminationGracePeriodSeconds: 10
volumes:
- name: socket
- configMap:
name: {{ $.Release.Name }}-security-config
name: security-config
- name: ca-cert
secret:
secretName: {{ $.Release.Name }}-ca-cert
- name: master-cert
secret:
secretName: {{ $.Release.Name }}-master-cert
- name: volume-cert
secret:
secretName: {{ $.Release.Name }}-volume-cert
- name: filer-cert
secret:
secretName: {{ $.Release.Name }}-filer-cert
- name: client-cert
secret:
secretName: {{ $.Release.Name }}-client-cert
- name: kubeconfig
secret:
secretName: {{ $.Release.Name }}-objectstorage-provisioner-{{ $clientName }}
---
apiVersion: v1
kind: Secret
metadata:
name: {{ $.Release.Name }}-objectstorage-provisioner-{{ $clientName }}
labels:
app.kubernetes.io/component: objectstorage-provisioner-{{ $clientName }}
app.kubernetes.io/instance: seaweedfs
app.kubernetes.io/name: {{ $.Release.Name }}
stringData:
kubeconfig: |
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: {{ $client.ca | b64enc | quote }}
server: {{ $client.cluster | quote }}
name: {{ $clientName }}
contexts:
- context:
cluster: {{ $clientName }}
namespace: {{ $client.namespace }}
user: {{ $clientName }}
name: {{ $clientName }}
current-context: {{ $clientName }}
kind: Config
preferences: {}
users:
- name: {{ $clientName }}
user:
token: {{ $client.token | quote }}
{{- end }}

View File

@@ -0,0 +1,16 @@
{{- if eq .Values.topology "External" }}
---
kind: BucketClass
apiVersion: objectstorage.k8s.io/v1alpha1
metadata:
name: {{ .Release.Namespace }}
driverName: {{ .Release.Namespace }}.seaweedfs.objectstorage.k8s.io
deletionPolicy: Delete
---
kind: BucketAccessClass
apiVersion: objectstorage.k8s.io/v1alpha1
metadata:
name: {{ .Release.Namespace }}
driverName: {{ .Release.Namespace }}
authenticationType: KEY
{{- end }}

View File

@@ -0,0 +1,59 @@
{{- if eq .Values.topology "External" }}
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: {{ .Release.Namespace }}-seaweedfs-objectstorage-provisioner
rules:
- apiGroups: ["objectstorage.k8s.io"]
resources:
- "buckets"
- "bucketaccesses"
- "bucketclaims"
- "bucketaccessclasses"
- "buckets/status"
- "bucketaccesses/status"
- "bucketclaims/status"
- "bucketaccessclasses/status"
verbs:
- "get"
- "list"
- "watch"
- "update"
- "create"
- "delete"
- apiGroups: ["coordination.k8s.io"]
resources: ["leases"]
verbs:
- "get"
- "watch"
- "list"
- "delete"
- "update"
- "create"
- apiGroups: [""]
resources:
- "secrets"
- "events"
verbs:
- "get"
- "list"
- "watch"
- "update"
- "create"
- "delete"
- "patch"
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: {{ .Release.Namespace }}-seaweedfs-objectstorage-provisioner
subjects:
- kind: ServiceAccount
name: {{ .Release.Namespace }}-seaweedfs-objectstorage-provisioner
namespace: {{ .Release.Namespace }}
roleRef:
kind: ClusterRole
name: {{ .Release.Namespace }}-seaweedfs-objectstorage-provisioner
apiGroup: rbac.authorization.k8s.io
{{- end }}

View File

@@ -0,0 +1,88 @@
{{- if eq .Values.topology "Client" }}
{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }}
{{- $ingress := index $myNS.metadata.annotations "namespace.cozystack.io/ingress" }}
{{- $host := index $myNS.metadata.annotations "namespace.cozystack.io/host" }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ $.Release.Name }}-objectstorage-provisioner
namespace: {{ $.Release.Namespace }}
labels:
app.kubernetes.io/component: objectstorage-provisioner
app.kubernetes.io/instance: seaweedfs
app.kubernetes.io/name: {{ $.Release.Name }}
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/component: objectstorage-provisioner
app.kubernetes.io/instance: seaweedfs
app.kubernetes.io/name: {{ $.Release.Name }}
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
policy.cozystack.io/allow-to-apiserver: "true"
app.kubernetes.io/component: objectstorage-provisioner
app.kubernetes.io/instance: seaweedfs
app.kubernetes.io/name: {{ $.Release.Name }}
spec:
containers:
- name: seaweedfs-cosi-driver
image: ghcr.io/seaweedfs/seaweedfs-cosi-driver:v0.1.2
imagePullPolicy: IfNotPresent
env:
- name: DRIVERNAME
value: {{ .Release.Namespace }}.seaweedfs.objectstorage.k8s.io
- name: ENDPOINT
value: https://{{ .Values.host | default (printf "s3.%s" $host) }}
- name: SEAWEEDFS_FILER
value: {{ .Values.remoteEndpoint }}
- name: WEED_GRPC_CLIENT_KEY
value: /usr/local/share/ca-certificates/client/tls.key
- name: WEED_GRPC_CLIENT_CERT
value: /usr/local/share/ca-certificates/client/tls.crt
- name: WEED_GRPC_CA
value: /usr/local/share/ca-certificates/client/ca.crt
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
volumeMounts:
- mountPath: /var/lib/cosi
name: socket
- mountPath: /usr/local/share/ca-certificates/client/
name: client-cert
readOnly: true
- name: seaweedfs-cosi-sidecar
image: ghcr.io/cozystack/cozystack/objectstorage-sidecar
imagePullPolicy: IfNotPresent
args:
- --v=5
env:
- name: POD_NAMESPACE
value: {{ .Release.Namespace }}
volumeMounts:
- mountPath: /var/lib/cosi
name: socket
enableServiceLinks: false
restartPolicy: Always
terminationGracePeriodSeconds: 10
serviceAccountName: {{ .Release.Name }}-objectstorage-provisioner
volumes:
- name: socket
emptyDir: {}
- name: client-cert
secret:
defaultMode: 420
secretName: seaweedfs-client-cert
{{- end }}

View File

@@ -0,0 +1,17 @@
{{- if eq .Values.topology "External" }}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Release.Namespace }}-seaweedfs-objectstorage-provisioner
namespace: {{ .Release.Namespace }}
automountServiceAccountToken: true
---
apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
metadata:
name: {{ .Release.Namespace }}-seaweedfs-objectstorage-provisioner
namespace: {{ .Release.Namespace }}
annotations:
kubernetes.io/service-account.name: {{ .Release.Namespace }}-seaweedfs-objectstorage-provisioner
{{- end }}

View File

@@ -1,6 +1,6 @@
{{- /* Preflight checks for Helm template */ -}}
{{- if not (has .Values.topology (list "Simple" "MultiZone")) }}
{{- fail "Invalid value for .Values.topology. Must be one of 'Simple' or 'MultiZone'." }}
{{- if not (has .Values.topology (list "Simple" "MultiZone" "External")) }}
{{- fail "Invalid value for .Values.topology. Must be one of 'Simple', 'MultiZone' or 'External'." }}
{{- end }}
{{- if lt (int .Values.replicationFactor) 1 }}
{{- fail "Invalid value for .Values.replicationFactor. Must be at least 1." }}
@@ -13,16 +13,24 @@
{{- fail "replicationFactor must be less than or equal to the number of zones defined in .Values.zones." }}
{{- end }}
{{- end }}
{{- if lookup "v1" "PersistentVolumeClaim" "" (printf "%s-data1-seaweedfs-volume-0" .Release.Name) }}
{{- if eq .Values.topology "MultiZone" }}
{{- fail "Not allowed to switch between Simple and MultiZone topologies after the first deployment." }}
{{- end }}
{{- $detectedTopology := "Unknown" }}
{{- $configMap := lookup "v1" "ConfigMap" .Release.Namespace (printf "%s-deployed-topology" .Release.Name) }}
{{- if $configMap }}
{{- $detectedTopology = dig "data" "topology" "Unknown" $configMap }}
{{- else }}
{{- if and (eq .Values.topology "Simple") (.Release.IsUpgrade) }}
{{- fail "Not allowed to switch between Simple and MultiZone topologies after the first deployment." }}
{{- if lookup "v1" "PersistentVolumeClaim" .Release.Namespace (printf "data1-%s-volume-0" .Release.Name) }}
{{- $detectedTopology = "Simple" }}
{{- else if lookup "apps/v1" "StatefulSet" .Release.Namespace (printf "%s-master" .Release.Name) }}
{{- $detectedTopology = "MultiZone" }}
{{- end }}
{{- end }}
{{- if not (has $detectedTopology (list .Values.topology "Unknown")) }}
{{- fail "Not allowed to switch between topologies after the first deployment." }}
{{- end }}
{{- if not (eq .Values.topology "External") }}
{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }}
{{- $ingress := index $myNS.metadata.annotations "namespace.cozystack.io/ingress" }}
{{- $host := index $myNS.metadata.annotations "namespace.cozystack.io/host" }}
@@ -198,3 +206,4 @@ spec:
cnpg.io/cluster: seaweedfs-db
cnpg.io/podRole: instance
version: {{ $.Chart.Version }}
{{- end }}

View File

@@ -1,3 +1,4 @@
{{- if not (eq .Values.topology "External") }}
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
@@ -64,3 +65,4 @@ spec:
maxAllowed:
cpu: "1"
memory: 2048Mi
{{- end }}

View File

@@ -1,5 +1,10 @@
{
"properties": {
"externalClients": {
"default": {},
"description": "A map of external clusters for managing bucketclaims.",
"type": "object"
},
"host": {
"default": "",
"description": "The hostname used to access the SeaweedFS externally (defaults to 's3' subdomain for the tenant host).",
@@ -27,11 +32,12 @@
},
"topology": {
"default": "Simple",
"description": "The topology of the SeaweedFS cluster. (allowed values: Simple, MultiZone)",
"description": "The topology of the SeaweedFS cluster. (allowed values: Simple, MultiZone, External)",
"type": "string",
"enum": [
"Simple",
"MultiZone"
"MultiZone",
"External"
]
},
"zones": {

View File

@@ -3,7 +3,7 @@
## @param host The hostname used to access the SeaweedFS externally (defaults to 's3' subdomain for the tenant host).
host: ""
## @param topology The topology of the SeaweedFS cluster. (allowed values: Simple, MultiZone)
## @param topology The topology of the SeaweedFS cluster. (allowed values: Simple, MultiZone, External)
##
topology: Simple
@@ -31,3 +31,24 @@ storageClass: ""
## replicas: 2
## size: 10Gi
zones: {}
## @param externalClients A map of external clusters for managing bucketclaims.
## Example:
## externalClients:
## cluster1:
## cluster: "https://api.example.org"
## namespace: "tenant-root"
## ca: |
## -----BEGIN CERTIFICATE-----
## MIIBijCCATCgAwIBAgIRAMg6nrACqxg97aDuhzrpqGMwCgYIKoZIzj0EAwIwFTET
## MBEGA1UEChMKa3ViZXJuZXRlczAeFw0yNTA1MTUwNzE5MTBaFw0zNTA1MTMwNzE5
## MTBaMBUxEzARBgNVBAoTCmt1YmVybmV0ZXMwWTATBgcqhkjOPQIBBggqhkjOPQMB
## BwNCAATh2nTL8tglw2cHO3/QSMqsiP4Ws8UIxNxjG5MtmIx4E7EH9fJjcpx6Ncfg
## Ekq6lbeTi6KpQOksAvhiq/EcwLLUo2EwXzAOBgNVHQ8BAf8EBAMCAoQwHQYDVR0l
## BBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
## BBYEFEwtnxdCdotUI1cbgsszGHM/nbUuMAoGCCqGSM49BAMCA0gAMEUCIQCA785y
## 6H6l8T1r0HjhAkHqWWYpuPj8lhiSqG0aXinWPgIgPksSKIRTnVyiQ/w5pWMnKuBD
## QFq6nUXbcO+ABY439FU=
## -----END CERTIFICATE-----
## token: "eyJhbGciOiJSUzI1NiIsImtpZCI6IlJMaEMyMTVvdDVVSENqZ1EySEFtVUhobnp2Mi02RXJ4bWM0bDJVenJmYzgifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJ0ZW5hbnQtcm9vdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJ0ZW5hbnQtcm9vdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJ0ZW5hbnQtcm9vdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImQ4OWRmN2M0LTI1OTctNGQ0NS1hM2U4LTIzMWVhYTJiN2U5NCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDp0ZW5hbnQtcm9vdDp0ZW5hbnQtcm9vdCJ9.eIFJwuT5q4nxgsTraxTw7cnaMiKaOOTpFazSf_LKS_gE9KO6AC5WvitFFIMudnm_0XrYMrMbjoBvtNf3khzmy-n5WSmmlxyZj0RKUUgoWnVjwy2pq0xLiZWSzbrHKnDpfyYUgz6Qz-LcqNK5O6Mnkd5xpz2FrY3vdjLJJ90f3cKFewI3GksvI6F7NG7ANSdhI0xBVKCCsIGoQL1m7ieTQtJoS9p4HtlfTBFOHj6NCu4Sai-2z-CC_cVxYQLCPD3fsYTpF4MZGMunDHaHadMVmvBZrHyBdH4OcrCGJ2ZtXBOsxxYRq8SHsGP7M5T_jwZ8j66BtJ_d3-3Aij-lorX2dpmwBLcc1yR5Pxgh1bwjRn_04oM30s6xlVcpupAfTvs4WpApADKsV3RixLTYNf18mTCpZL6phbw3TWSRSD0Gz-08QkROWvB2IJqsya65BPnSfab5eFumYvYiZVn84mHhh7BxSuRXBTTGAYzQ-D6t1V7W2sSPF191lrSkYkxwQ_rtyi4EfDqayD2D4pFGWJJhqSM7Ra4ZsAy9TpSTfKtWsIwngwnGn7zXsWKPIYGP8Xyt-97nWum4qvgHQAPBa0SofxI7dKHiZ1KKo-vOuKdL0Qka0_E-h_3w1w3Gn6-q1X8qp5YNB-fHooYYGeL8kFiBjRacB1QSAGAuBwaKl37xB4U"
externalClients: {}