Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions charts/memos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ The default chart topology is intentionally conservative:

When `database.driver` is `mysql` or `postgres`, the chart renders `MEMOS_DSN` from a Kubernetes Secret.
The data volume remains required because Memos can still store local assets and instance data outside the external database.
Ingress class rendering is optional. Set `ingress.ingressClassName: ""` to omit `spec.ingressClassName`.
When `networkPolicy.enabled=true`, ingress is restricted to the configured peers.
Set `networkPolicy.egressIsolation=true` to add baseline DNS and HTTPS egress rules without custom egress rules. DNS egress defaults to
`kube-system` pods labeled `k8s-app: kube-dns`; override `networkPolicy.dnsEgress` for clusters with different DNS labels. HTTPS egress
defaults to IPv4 and IPv6 internet destinations on port 443; override `networkPolicy.httpsEgress` to restrict webhook and integration
destinations. Setting `networkPolicy.extraEgress` also enables egress isolation and appends the supplied database, webhook, or proxy rules.

## Install

Expand Down
98 changes: 70 additions & 28 deletions charts/memos/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -1,46 +1,88 @@
{{/* SPDX-License-Identifier: Apache-2.0 */}}
Memos release: {{ .Release.Name }}
Namespace: {{ .Release.Namespace }}
Memos has been deployed.

Workload:
1. Access

- Service DNS: http://{{ include "memos.fullname" . }}.{{ .Release.Namespace }}.svc:{{ .Values.service.port }}
- Port-forward:
kubectl port-forward svc/{{ include "memos.fullname" . }} 5230:{{ .Values.service.port }} -n {{ .Release.Namespace }}
- Browser URL:
http://127.0.0.1:5230

2. Workload

- StatefulSet:
kubectl get statefulset {{ include "memos.fullname" . }} -n {{ .Release.Namespace }}
- Rollout:
kubectl rollout status statefulset/{{ include "memos.fullname" . }} -n {{ .Release.Namespace }}
- Replicas: {{ .Values.replicaCount }}
- Image: {{ include "memos.image" . }}

Service:
kubectl get svc {{ include "memos.fullname" . }} -n {{ .Release.Namespace }}
3. Runtime Configuration

Local access:
kubectl port-forward svc/{{ include "memos.fullname" . }} 5230:{{ .Values.service.port }} -n {{ .Release.Namespace }}
Open http://127.0.0.1:5230

Runtime configuration:
Database driver: {{ .Values.database.driver }}
Data directory: {{ .Values.persistence.mountPath }}
Public instance URL: {{ default "not set" .Values.memos.instanceUrl }}
Private webhooks: {{ .Values.memos.allowPrivateWebhooks }}

Persistence:
{{- if .Values.persistence.enabled }}
PVC is enabled. Back up the data volume before upgrades.
{{- else if .Values.persistence.existingClaim }}
Existing PVC {{ .Values.persistence.existingClaim }} is mounted.
- Database driver: {{ .Values.database.driver }}
- Data directory: {{ .Values.persistence.mountPath }}
- Public instance URL: {{ default "not set" .Values.memos.instanceUrl }}
- Private webhooks: {{ .Values.memos.allowPrivateWebhooks }}
- Log level: {{ .Values.memos.logLevel }}

4. Persistence

{{- if .Values.persistence.existingClaim }}
- Existing PVC {{ .Values.persistence.existingClaim }} is mounted.
{{- else if .Values.persistence.enabled }}
- A chart-managed volumeClaimTemplate is enabled.
- PVC size: {{ .Values.persistence.size }}
- Access modes: {{ join "," .Values.persistence.accessModes }}
{{- else }}
Persistence is disabled and Memos data is stored in an emptyDir.
- Persistence is disabled and Memos data is stored in emptyDir.
{{- end }}
- Back up the data volume before upgrades and before database migrations.

5. Database

Database notes:
{{- if eq .Values.database.driver "sqlite" }}
SQLite is stored under {{ .Values.persistence.mountPath }}. Keep replicaCount=1 and back up the PVC.
- SQLite is stored under {{ .Values.persistence.mountPath }}.
- Keep replicaCount=1 when using SQLite.
{{- else }}
- External {{ .Values.database.driver }} is enabled through MEMOS_DSN.
- Back up both the external database and the Memos data volume.
{{- end }}

6. Exposure

{{- if .Values.ingress.enabled }}
- Ingress is enabled.
{{- else }}
- Ingress is disabled.
{{- end }}
{{- if .Values.gateway.enabled }}
- Gateway API HTTPRoute is enabled.
{{- else }}
External {{ .Values.database.driver }} is enabled. Back up both the external database and the Memos data PVC.
- Gateway API HTTPRoute is disabled.
{{- end }}
{{- if .Values.networkPolicy.enabled }}
- NetworkPolicy is enabled for inbound Memos traffic.
{{- else }}
- NetworkPolicy is disabled.
{{- end }}

Troubleshooting:
7. Troubleshooting

- Pods:
kubectl get pod -l app.kubernetes.io/instance={{ .Release.Name }} -n {{ .Release.Namespace }}
- Logs:
kubectl logs statefulset/{{ include "memos.fullname" . }} -n {{ .Release.Namespace }}
- Describe pods:
kubectl describe pod -l app.kubernetes.io/instance={{ .Release.Name }} -n {{ .Release.Namespace }}
- Service endpoints:
kubectl get endpoints {{ include "memos.fullname" . }} -n {{ .Release.Namespace }}

8. Resources

Validation:
helm test {{ .Release.Name }} -n {{ .Release.Namespace }}
make validate-chart CHART=memos
- Chart documentation: https://helmforge.dev/docs/charts/memos
- Memos Docker deployment: https://usememos.com/docs/deploy/docker
- Memos source: https://github.com/usememos/memos
- Validation command: make validate-chart CHART=memos

Happy Helmforging :)
21 changes: 21 additions & 0 deletions charts/memos/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,24 @@ app.kubernetes.io/part-of: helmforge
{{- define "memos.serviceAccountName" -}}{{- if .Values.serviceAccount.create -}}{{- default (include "memos.fullname" .) .Values.serviceAccount.name -}}{{- else -}}{{- default "default" .Values.serviceAccount.name -}}{{- end -}}{{- end -}}
{{- define "memos.image" -}}{{- printf "%s:%s" .Values.image.repository .Values.image.tag -}}{{- end -}}
{{- define "memos.databaseSecretName" -}}{{- default (printf "%s-database" (include "memos.fullname" .)) .Values.database.existingSecret -}}{{- end -}}

{{- define "memos.validate" -}}
{{- if and (gt (int .Values.replicaCount) 1) (eq .Values.database.driver "sqlite") -}}
{{- fail "replicaCount > 1 requires database.driver=mysql or database.driver=postgres because SQLite cannot safely share state across Memos pods" -}}
{{- end -}}
{{- if and (ne .Values.database.driver "sqlite") (not (or .Values.database.dsn .Values.database.existingSecret)) -}}
{{- fail "database.dsn or database.existingSecret is required when database.driver is mysql or postgres" -}}
{{- end -}}
{{- if and (ne .Values.database.driver "sqlite") (not .Values.persistence.enabled) (not .Values.persistence.existingClaim) -}}
{{- fail "persistence.enabled or persistence.existingClaim is required with external databases because Memos still stores local assets in MEMOS_DATA" -}}
{{- end -}}
{{- if and (gt (int .Values.replicaCount) 1) (ne .Values.database.driver "sqlite") (not .Values.persistence.existingClaim) -}}
{{- fail "replicaCount > 1 with mysql or postgres requires persistence.existingClaim backed by shared storage because generated StatefulSet PVCs are per-pod and Memos can store local assets in MEMOS_DATA" -}}
{{- end -}}
{{- $podLabels := .Values.podLabels | default dict -}}
{{- range $key := (list "app.kubernetes.io/name" "app.kubernetes.io/instance") -}}
{{- if hasKey $podLabels $key -}}
{{- fail (printf "podLabels must not override the selector label %s" $key) -}}
{{- end -}}
{{- end -}}
{{- end -}}
4 changes: 3 additions & 1 deletion charts/memos/templates/ingress.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ metadata:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
ingressClassName: {{ .Values.ingress.ingressClassName | quote }}
{{- with .Values.ingress.ingressClassName }}
ingressClassName: {{ . | quote }}
{{- end }}
{{- with .Values.ingress.tls }}
tls:
{{- toYaml . | nindent 4 }}
Expand Down
29 changes: 29 additions & 0 deletions charts/memos/templates/networkpolicy.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{{/* SPDX-License-Identifier: Apache-2.0 */}}
{{- if .Values.networkPolicy.enabled }}
{{- $extraEgress := .Values.networkPolicy.extraEgress | default list }}
{{- $dnsEgress := .Values.networkPolicy.dnsEgress | default list }}
{{- $httpsEgress := .Values.networkPolicy.httpsEgress | default list }}
{{- $egressEnabled := or .Values.networkPolicy.egressIsolation (gt (len $extraEgress) 0) }}
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
Expand All @@ -12,6 +16,9 @@ spec:
{{- include "memos.selectorLabels" . | nindent 6 }}
policyTypes:
- Ingress
{{- if $egressEnabled }}
- Egress
{{- end }}
ingress:
- from:
{{- if .Values.networkPolicy.ingressFrom }}
Expand All @@ -22,4 +29,26 @@ spec:
ports:
- protocol: TCP
port: http
{{- if $egressEnabled }}
egress:
{{- if gt (len $dnsEgress) 0 }}
- to:
{{- toYaml $dnsEgress | nindent 8 }}
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
{{- end }}
{{- if gt (len $httpsEgress) 0 }}
- to:
{{- toYaml $httpsEgress | nindent 8 }}
ports:
- protocol: TCP
port: 443
{{- end }}
{{- with $extraEgress }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}
{{- end }}
2 changes: 1 addition & 1 deletion charts/memos/templates/statefulset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ spec:
template:
metadata:
labels:
{{- include "memos.labels" . | nindent 8 }}
{{- include "memos.selectorLabels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
Expand Down
13 changes: 1 addition & 12 deletions charts/memos/templates/validate.yaml
Original file line number Diff line number Diff line change
@@ -1,13 +1,2 @@
{{/* SPDX-License-Identifier: Apache-2.0 */}}
{{- if and (gt (int .Values.replicaCount) 1) (eq .Values.database.driver "sqlite") -}}
{{- fail "replicaCount > 1 requires database.driver=mysql or database.driver=postgres because SQLite cannot safely share state across Memos pods" -}}
{{- end -}}
{{- if and (ne .Values.database.driver "sqlite") (not (or .Values.database.dsn .Values.database.existingSecret)) -}}
{{- fail "database.dsn or database.existingSecret is required when database.driver is mysql or postgres" -}}
{{- end -}}
{{- if and (ne .Values.database.driver "sqlite") (not .Values.persistence.enabled) (not .Values.persistence.existingClaim) -}}
{{- fail "persistence.enabled or persistence.existingClaim is required with external databases because Memos still stores local assets in MEMOS_DATA" -}}
{{- end -}}
{{- if and (gt (int .Values.replicaCount) 1) (ne .Values.database.driver "sqlite") (not .Values.persistence.existingClaim) -}}
{{- fail "replicaCount > 1 with mysql or postgres requires persistence.existingClaim backed by shared storage because generated StatefulSet PVCs are per-pod and Memos can store local assets in MEMOS_DATA" -}}
{{- end -}}
{{- include "memos.validate" . -}}
73 changes: 73 additions & 0 deletions charts/memos/tests/networkpolicy_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,76 @@ tests:
content:
protocol: TCP
port: http
- notContains:
path: spec.policyTypes
content: Egress
- notExists:
path: spec.egress
- it: enables baseline DNS and HTTPS egress isolation
set:
networkPolicy:
enabled: true
egressIsolation: true
asserts:
- contains:
path: spec.policyTypes
content: Egress
- equal:
path: spec.egress[0].to[0].namespaceSelector.matchLabels["kubernetes.io/metadata.name"]
value: kube-system
- equal:
path: spec.egress[0].to[0].podSelector.matchLabels["k8s-app"]
value: kube-dns
- equal:
path: spec.egress[1].ports[0].port
value: 443
- equal:
path: spec.egress[1].to[0].ipBlock.cidr
value: 0.0.0.0/0
- equal:
path: spec.egress[1].to[1].ipBlock.cidr
value: ::/0
- it: renders custom baseline HTTPS egress peers
set:
networkPolicy:
enabled: true
egressIsolation: true
httpsEgress:
- ipBlock:
cidr: 203.0.113.0/24
asserts:
- equal:
path: spec.egress[1].to[0].ipBlock.cidr
value: 203.0.113.0/24
- notContains:
path: spec.egress[1].to
content:
ipBlock:
cidr: 0.0.0.0/0
- it: appends extra egress rules after default DNS and HTTPS
set:
networkPolicy:
enabled: true
extraEgress:
- to:
- ipBlock:
cidr: 10.0.0.0/8
ports:
- protocol: TCP
port: 5432
asserts:
- contains:
path: spec.policyTypes
content: Egress
- equal:
path: spec.egress[0].ports[0].port
value: 53
- equal:
path: spec.egress[0].to[0].podSelector.matchLabels["k8s-app"]
value: kube-dns
- equal:
path: spec.egress[1].ports[0].port
value: 443
- equal:
path: spec.egress[2].ports[0].port
value: 5432
16 changes: 16 additions & 0 deletions charts/memos/tests/templates_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ tests:
- equal:
path: spec.template.spec.containers[0].image
value: docker.io/neosmemo/memos:0.29.1
- equal:
path: spec.template.metadata.labels["app.kubernetes.io/name"]
value: memos
- notExists:
path: spec.template.metadata.labels["helm.sh/chart"]
- it: renders the default ClusterIP service
template: templates/service.yaml
asserts:
Expand Down Expand Up @@ -40,6 +45,17 @@ tests:
- equal:
path: spec.tls[0].hosts[0]
value: memos.example.com
- it: omits empty ingress class name
template: templates/ingress.yaml
set:
ingress.enabled: true
ingress.ingressClassName: ""
ingress.hosts[0].host: memos.example.com
ingress.hosts[0].paths[0].path: /
ingress.hosts[0].paths[0].pathType: Prefix
asserts:
- notExists:
path: spec.ingressClassName
- it: uses emptyDir when persistence is disabled
template: templates/statefulset.yaml
set:
Expand Down
14 changes: 14 additions & 0 deletions charts/memos/tests/validation_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,17 @@ tests:
asserts:
- failedTemplate:
errorMessage: "replicaCount > 1 with mysql or postgres requires persistence.existingClaim backed by shared storage because generated StatefulSet PVCs are per-pod and Memos can store local assets in MEMOS_DATA"
- it: fails when podLabels override selector labels
set:
podLabels:
app.kubernetes.io/instance: custom
asserts:
- failedTemplate:
errorMessage: "podLabels must not override the selector label app.kubernetes.io/instance"
- it: fails when podLabels override the name selector label
set:
podLabels:
app.kubernetes.io/name: custom
asserts:
- failedTemplate:
errorMessage: "podLabels must not override the selector label app.kubernetes.io/name"
22 changes: 21 additions & 1 deletion charts/memos/values.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,27 @@
"type": "object",
"properties": {
"enabled": { "type": "boolean", "default": false },
"ingressFrom": { "type": "array", "items": { "type": "object" } }
"ingressFrom": { "type": "array", "items": { "type": "object" } },
"egressIsolation": {
"type": "boolean",
"default": false,
"description": "Enable baseline egress isolation without requiring custom extraEgress rules"
},
"dnsEgress": {
"type": "array",
"description": "DNS peers allowed by baseline egress isolation",
"items": { "type": "object" }
},
"httpsEgress": {
"type": "array",
"description": "HTTPS peers allowed by baseline egress isolation",
"items": { "type": "object" }
},
"extraEgress": {
"type": "array",
"description": "Additional egress rules appended after the built-in DNS and HTTPS allowances",
"items": { "type": "object" }
}
}
}
}
Expand Down
Loading