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
189 changes: 189 additions & 0 deletions apps/filehosting/nextcloud/MIGRATION-off-bitnami.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# Nextcloud: Migrate Off Bitnami (MariaDB + Redis)

This runbook migrates Nextcloud from the embedded Bitnami subcharts to externally managed services.

Current state in `apps/filehosting/nextcloud/app.yaml`:
- `mariadb.enabled: true` (Bitnami subchart)
- `redis.enabled: true` (Bitnami subchart)
- `externalDatabase.enabled: true` already set
- `externalDatabase.host` is not set explicitly
- `externalRedis.enabled` is not set

## Recommendation

- Redis: move to Valkey.
- Database: move to PostgreSQL on CNPG (since operator is already installed).

Reason: with CNPG already available in your cluster, running PostgreSQL as a first-class managed database is a strong long-term fit. The migration does require a maintenance window.

## Important Notes About Live Data

- MariaDB data is critical and must be migrated.
- Redis data is mostly cache/locks for Nextcloud and is usually safe to rebuild.
- If you still want Redis key migration, do it during a short maintenance window and expect reconnect churn.

## Phase 0: Preflight

1. Backup before anything.
2. Record current pod/service names.
3. Confirm secret keys exist:
- `nextcloud-mariadb-auth` with at least `mariadb-username`, `mariadb-password`
- `nextcloud-redis-auth` with `redis-password`

Useful checks:

```bash
kubectl -n nextcloud get secret nextcloud-mariadb-auth -o yaml
kubectl -n nextcloud get secret nextcloud-redis-auth -o yaml
kubectl -n nextcloud get pods,svc,pvc
```

## Phase 1: Deploy New Targets In Parallel

Deploy these first without changing Nextcloud yet:

- `nextcloud-postgresql` (CNPG cluster, `-rw` service endpoint)
- `nextcloud-valkey` service/statefulset

Keep old Bitnami services running while data is copied.

## Phase 2: Migrate MariaDB Data To PostgreSQL (CNPG)

### 2A) Enable maintenance mode

```bash
NEXTCLOUD_POD=$(kubectl -n nextcloud get pod -l app.kubernetes.io/name=nextcloud -o jsonpath='{.items[0].metadata.name}')
kubectl -n nextcloud exec "$NEXTCLOUD_POD" -- su -s /bin/sh www-data -c "php occ maintenance:mode --on"
```

### 2B) Create CNPG cluster and credentials

Create a CNPG cluster and app user/database before conversion. Example resources (adapt storage/tls to your cluster):

```yaml
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: nextcloud-postgresql
namespace: nextcloud
spec:
instances: 1
storage:
size: 20Gi
bootstrap:
initdb:
database: nextcloud
owner: nextcloud
secret:
name: nextcloud-postgresql-auth
```

`nextcloud-postgresql-auth` should contain keys expected by CNPG bootstrap (`username`, `password`).

### 2C) Convert with Nextcloud OCC

Run conversion from your Nextcloud pod while in maintenance mode:

```bash
NEXTCLOUD_POD=$(kubectl -n nextcloud get pod -l app.kubernetes.io/name=nextcloud -o jsonpath='{.items[0].metadata.name}')
PGUSER=$(kubectl -n nextcloud get secret nextcloud-postgresql-auth -o jsonpath='{.data.username}' | base64 -d)
PGPASS=$(kubectl -n nextcloud get secret nextcloud-postgresql-auth -o jsonpath='{.data.password}' | base64 -d)

kubectl -n nextcloud exec "$NEXTCLOUD_POD" -- sh -ec "
su -s /bin/sh www-data -c \"php occ db:convert-type --all-apps --password '${PGPASS}' pgsql '${PGUSER}' nextcloud-postgresql-rw.nextcloud.svc.cluster.local nextcloud\"
"
```

### 2D) Quick validation

```bash
kubectl -n nextcloud exec "$NEXTCLOUD_POD" -- su -s /bin/sh www-data -c "php occ status"
kubectl -n nextcloud exec "$NEXTCLOUD_POD" -- su -s /bin/sh www-data -c "php occ db:add-missing-indices"
```

## Phase 3: Redis -> Valkey

### Preferred: No key migration (recommended)

- Start Valkey empty.
- Cut Nextcloud to Valkey.
- Old cache/locks are rebuilt.

### Optional: Key migration (if required)

If you must migrate keys exactly:

1. Keep maintenance mode ON.
2. Trigger RDB export from old Redis.
3. Start Valkey from that RDB (same major protocol family).
4. Cut traffic to Valkey.

For Nextcloud, this usually does not provide meaningful long-term benefit compared to a clean cache start.

## Phase 4: Cutover In `app.yaml`

In `apps/filehosting/nextcloud/app.yaml` update values under `helm.valuesObject`:

```yaml
externalDatabase:
enabled: true
type: postgresql
host: nextcloud-postgresql-rw.nextcloud.svc.cluster.local:5432
database: nextcloud
existingSecret:
enabled: true
secretName: "nextcloud-postgresql-auth"
usernameKey: username
passwordKey: password

mariadb:
enabled: false

externalRedis:
enabled: true
host: nextcloud-valkey
port: "6379"
existingSecret:
enabled: true
secretName: "nextcloud-redis-auth"
passwordKey: redis-password

redis:
enabled: false
```

Then sync Argo app.

## Phase 5: Post-cutover

1. Run Nextcloud checks:

```bash
NEXTCLOUD_POD=$(kubectl -n nextcloud get pod -l app.kubernetes.io/name=nextcloud -o jsonpath='{.items[0].metadata.name}')
kubectl -n nextcloud exec "$NEXTCLOUD_POD" -- su -s /bin/sh www-data -c "php occ status"
kubectl -n nextcloud exec "$NEXTCLOUD_POD" -- su -s /bin/sh www-data -c "php occ db:add-missing-indices"
```

2. Disable maintenance mode:

```bash
kubectl -n nextcloud exec "$NEXTCLOUD_POD" -- su -s /bin/sh www-data -c "php occ maintenance:mode --off"
```

3. Observe logs and login/file ops for at least one full user cycle.

## Cleanup

After stable operation:

- Remove old Bitnami PVCs/pods/services.
- Keep SQL dump backup until you are confident rollback is unnecessary.

## Rollback

If cutover fails:

1. Re-enable Bitnami `mariadb/redis` in `app.yaml`.
2. Disable `externalRedis` and point DB host back to old service.
3. Sync Argo and validate.
4. Keep maintenance mode on until stable.
10 changes: 9 additions & 1 deletion apps/filehosting/nextcloud/app.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,16 @@ spec:
persistence:
enabled: true
# https://github.com/bitnami/charts/tree/main/bitnami/redis
redis:
externalRedis:
enabled: true
host: nextcloud-valkey
port: "6379"
existingSecret:
enabled: true
secretName: "nextcloud-redis-auth"
passwordKey: redis-password
redis:
enabled: false
image:
tag: 7.2.5-debian-12-r4
auth:
Expand Down
1 change: 1 addition & 0 deletions apps/filehosting/nextcloud/deploy/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- valkey.yaml
- install-apps.yaml
- cronjob.yaml
# - nextcloud-assistant.yaml
78 changes: 78 additions & 0 deletions apps/filehosting/nextcloud/deploy/valkey.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
apiVersion: v1
kind: Service
metadata:
name: nextcloud-valkey
spec:
selector:
app: nextcloud-valkey
ports:
- name: valkey
port: 6379
targetPort: 6379

---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nextcloud-valkey
labels:
app: nextcloud-valkey
spec:
replicas: 1
selector:
matchLabels:
app: nextcloud-valkey
template:
metadata:
labels:
app: nextcloud-valkey
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1001
runAsGroup: 1001
seccompProfile:
type: RuntimeDefault
containers:
- name: valkey
image: valkey/valkey:8.1-alpine
imagePullPolicy: IfNotPresent
command:
- sh
- -ec
- |
exec valkey-server \
--port 6379 \
--bind 0.0.0.0 \
--protected-mode yes \
--requirepass "$REDIS_PASSWORD" \
--appendonly no
env:
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: nextcloud-redis-auth
key: redis-password
ports:
- name: valkey
containerPort: 6379
readinessProbe:
tcpSocket:
port: 6379
initialDelaySeconds: 3
periodSeconds: 5
livenessProbe:
tcpSocket:
port: 6379
initialDelaySeconds: 10
periodSeconds: 10
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
runAsNonRoot: true
runAsUser: 1001
runAsGroup: 1001
seccompProfile:
type: RuntimeDefault
102 changes: 102 additions & 0 deletions apps/productivity/kitchenowl/app.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: kitchenowl
namespace: "{{ .customer.name }}-reserved"
spec:
syncPolicy:
automated:
prune: true
selfHeal: false
allowEmpty: false
syncOptions:
- CreateNamespace=true
project: "{{ .customer.name }}-{{ .customer.project }}"
destination:
server: https://kubernetes.default.svc
namespace: "{{ .customer.name }}-kitchenowl-{{ .customer.stage }}"
source:
repoURL: https://bedag.github.io/helm-charts
targetRevision: 2.0.0
chart: raw
helm:
valuesObject:
resources:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: kitchenowl
labels:
app: kitchenowl
spec:
replicas: 1
selector:
matchLabels:
app: kitchenowl
template:
metadata:
labels:
app: kitchenowl
spec:
containers:
- name: kitchenowl
image: tombursch/kitchenowl:latest
ports:
- name: http
containerPort: 8080
env:
- name: JWT_SECRET_KEY
value: "{{ .ejson.kitchenowl.jwtSecretKey }}"
volumeMounts:
- name: kitchenowl-data
mountPath: /data
volumes:
- name: kitchenowl-data
persistentVolumeClaim:
claimName: kitchenowl-data
- apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: kitchenowl-data
spec:
accessModes:
- ReadWriteOnce
storageClassName: "{{ .cluster.defaultStorageClass }}"
resources:
requests:
storage: 10Gi
- apiVersion: v1
kind: Service
metadata:
name: kitchenowl
spec:
selector:
app: kitchenowl
ports:
- name: http
port: 8080
targetPort: http
type: ClusterIP
- apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: kitchenowl
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod-nginx
spec:
ingressClassName: "{{ .cluster.defaultIngressClass }}"
rules:
- host: kitchenowl.{{ .customer.primaryDomain }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: kitchenowl
port:
number: 8080
tls:
- hosts:
- kitchenowl.{{ .customer.primaryDomain }}
secretName: kitchenowl-tls
Loading