this repo has no description

Migrate PDS from Node.js to Tranquil PDS (Rust)

HA Postgres namespace with primary + streaming standby, each on
JuiceFS-backed PVCs. Tranquil PDS serves sans-self.org with 2
replicas (1Gi limit vs 5Gi Node.js), blobs on shared JuiceFS PVC.

Email via msmtp → Resend SMTP (port 587). Hetzner firewall opened
for outbound SMTP. Resend domain verified with DKIM/SPF/DMARC DNS.

Old Node.js PDS scaled to 0, kept for rollback.

+725 -7
+26 -2
Makefile
··· 1 1 # Derived secrets — generated from source secrets before kustomize build 2 2 JUICEFS_METAURL := k8s/juicefs/metaurl.secret 3 + TRANQUIL_DB_URL := k8s/pds/tranquil-database-url.secret 3 4 4 5 .PHONY: secrets clean-secrets build 5 6 6 - secrets: $(JUICEFS_METAURL) 7 + secrets: $(JUICEFS_METAURL) $(TRANQUIL_DB_URL) 7 8 8 9 $(JUICEFS_METAURL): k8s/juicefs/redis-password.secret 9 10 @pw=$$(cat $< | tr -d '\n') && \ 10 11 printf 'redis://:%s@redis.juicefs.svc.cluster.local:6379/0' "$$pw" > $@ 11 12 13 + $(TRANQUIL_DB_URL): k8s/postgres/postgres-password.secret 14 + @pw=$$(cat $< | tr -d '\n' | python3 -c 'import sys,urllib.parse; print(urllib.parse.quote(sys.stdin.read(),safe=""),end="")') && \ 15 + printf 'postgres://tranquil:%s@postgres.postgres.svc.cluster.local:5432/pds' "$$pw" > $@ 16 + 12 17 build: secrets 13 18 kustomize build k8s/ 14 19 15 20 clean-secrets: 16 - rm -f $(JUICEFS_METAURL) 21 + rm -f $(JUICEFS_METAURL) $(TRANQUIL_DB_URL) 17 22 18 23 # Spindle CI runner 19 24 # Full flow: build-spindle → push-spindle → update-spindle → start-spindle (first time only) ··· 44 49 start-spindle: 45 50 kubectl delete job spindle-start --ignore-not-found 46 51 kubectl apply -f spindle/start-job.yaml 52 + 53 + # Tranquil PDS 54 + TRANQUIL_REPO ?= /tmp/tranquil-pds 55 + 56 + .PHONY: build-tranquil push-tranquil build-tranquil-frontend push-tranquil-frontend 57 + 58 + build-tranquil: 59 + @test -d "$(TRANQUIL_REPO)" || { echo "error: tranquil-pds repo not found at $(TRANQUIL_REPO)"; exit 1; } 60 + docker buildx build --platform linux/arm64 -t zot.sans-self.org/infra/tranquil-pds:latest "$(TRANQUIL_REPO)" 61 + 62 + push-tranquil: 63 + docker push zot.sans-self.org/infra/tranquil-pds:latest 64 + 65 + build-tranquil-frontend: 66 + @test -d "$(TRANQUIL_REPO)" || { echo "error: tranquil-pds repo not found at $(TRANQUIL_REPO)"; exit 1; } 67 + docker buildx build --platform linux/arm64 -t zot.sans-self.org/infra/tranquil-frontend:latest "$(TRANQUIL_REPO)/frontend" 68 + 69 + push-tranquil-frontend: 70 + docker push zot.sans-self.org/infra/tranquil-frontend:latest 47 71 48 72 logs-spindle: 49 73 @NODE_NAME=$$(kubectl get configmap spindle-leader -o jsonpath='{.data.node}' 2>/dev/null) && \
+69
dns.tf
··· 61 61 ] 62 62 } 63 63 64 + # opake.app — external project hosted on this cluster 65 + resource "hcloud_zone" "opake" { 66 + name = "opake.app" 67 + mode = "primary" 68 + ttl = 3600 69 + } 70 + 71 + resource "hcloud_zone_rrset" "opake_issues_a" { 72 + zone = hcloud_zone.opake.id 73 + name = "issues" 74 + type = "A" 75 + ttl = 300 76 + records = [ 77 + { value = local.cluster_ip }, 78 + ] 79 + } 80 + 81 + # Resend email verification (DKIM, SPF, DMARC, bounce MX) 82 + resource "hcloud_zone_rrset" "resend_dkim" { 83 + zone = hcloud_zone.sans_self.id 84 + name = "resend._domainkey" 85 + type = "TXT" 86 + ttl = 3600 87 + records = [ 88 + { value = "\"p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDP8jNxshLK+VTWmFWEveQkS6qJ7K88w5SmC28a86PMBrhsoN7stkdvMx8bDpoj694rUn7L3Ua7QRTnR0G2Kj7fRCJkgEwIdjtSoWxEfi+KTkO0epj5aptWA4gRK5nmcO1qPaW+X7SIv3Xu0ucr9Bw0bSoImRk5O3dodhSVNvGPzQIDAQAB\"" }, 89 + ] 90 + } 91 + 92 + resource "hcloud_zone_rrset" "resend_send_mx" { 93 + zone = hcloud_zone.sans_self.id 94 + name = "send" 95 + type = "MX" 96 + ttl = 3600 97 + records = [ 98 + { value = "10 feedback-smtp.eu-west-1.amazonses.com." }, 99 + ] 100 + } 101 + 102 + resource "hcloud_zone_rrset" "resend_send_spf" { 103 + zone = hcloud_zone.sans_self.id 104 + name = "send" 105 + type = "TXT" 106 + ttl = 3600 107 + records = [ 108 + { value = "\"v=spf1 include:amazonses.com ~all\"" }, 109 + ] 110 + } 111 + 112 + resource "hcloud_zone_rrset" "resend_dmarc" { 113 + zone = hcloud_zone.sans_self.id 114 + name = "_dmarc" 115 + type = "TXT" 116 + ttl = 3600 117 + records = [ 118 + { value = "\"v=DMARC1; p=none;\"" }, 119 + ] 120 + } 121 + 122 + # Staging endpoint for Tranquil PDS migration 123 + resource "hcloud_zone_rrset" "pds_next_a" { 124 + zone = hcloud_zone.sans_self.id 125 + name = "pds-next" 126 + type = "A" 127 + ttl = 300 128 + records = [ 129 + { value = local.cluster_ip }, 130 + ] 131 + } 132 + 64 133 resource "hcloud_zone_rrset" "atproto_pds_test" { 65 134 zone = hcloud_zone.sans_self.id 66 135 name = "_atproto.pds-test"
+24
k8s/kustomization.yaml
··· 4 4 resources: 5 5 - shared/cluster-issuer.yaml 6 6 - juicefs 7 + - postgres 7 8 - pds 8 9 - knot 9 10 - registry 10 11 - alerting 12 + - opake 11 13 12 14 generatorOptions: 13 15 disableNameSuffixHash: true ··· 43 45 files: 44 46 - access-key=shared/s3-access-key.secret 45 47 - secret-key=shared/s3-secret-key.secret 48 + - name: tranquil-secrets 49 + namespace: pds 50 + type: Opaque 51 + files: 52 + - jwt_secret=pds/tranquil-jwt.secret 53 + - dpop_secret=pds/tranquil-dpop.secret 54 + - master_key=pds/tranquil-master-key.secret 55 + - name: msmtp-config 56 + namespace: pds 57 + type: Opaque 58 + files: 59 + - msmtprc=pds/msmtprc.secret 60 + - name: tranquil-database-url 61 + namespace: pds 62 + type: Opaque 63 + files: 64 + - url=pds/tranquil-database-url.secret 65 + - name: postgres-credentials 66 + namespace: pds 67 + type: Opaque 68 + files: 69 + - password=postgres/postgres-password.secret 46 70 - name: juicefs-secret 47 71 namespace: juicefs 48 72 type: Opaque
+1
k8s/pds/cert.yaml
··· 10 10 kind: ClusterIssuer 11 11 dnsNames: 12 12 - sans-self.org 13 + - pds-next.sans-self.org 13 14 - vesper.sans-self.org 14 15 - nyx.sans-self.org
+2 -2
k8s/pds/deployment.yaml
··· 4 4 name: pds 5 5 namespace: pds 6 6 spec: 7 - replicas: 1 7 + replicas: 0 # Replaced by Tranquil PDS — kept for rollback 8 8 strategy: 9 9 type: Recreate 10 10 selector: ··· 84 84 memory: 256Mi 85 85 limits: 86 86 cpu: 500m 87 - memory: 1Gi 87 + memory: 1536Mi 88 88 volumes: 89 89 - name: data 90 90 persistentVolumeClaim:
+23 -3
k8s/pds/ingress.yaml
··· 43 43 middlewares: 44 44 - name: block-external 45 45 services: 46 - - name: pds 46 + - name: tranquil-pds 47 47 port: 3000 48 48 # Everything else (xrpc, OAuth, .well-known, etc) 49 49 - match: Host(`sans-self.org`) ··· 51 51 middlewares: 52 52 - name: strip-server-headers 53 53 services: 54 - - name: pds 54 + - name: tranquil-pds 55 55 port: 3000 56 56 # Subdomain handle resolution (well-known + xrpc) 57 57 - match: Host(`pds-test.sans-self.org`) || Host(`vesper.sans-self.org`) || Host(`nyx.sans-self.org`) ··· 59 59 middlewares: 60 60 - name: strip-server-headers 61 61 services: 62 - - name: pds 62 + - name: tranquil-pds 63 + port: 3000 64 + --- 65 + # Staging route — keep for now as fallback reference 66 + apiVersion: traefik.io/v1alpha1 67 + kind: IngressRoute 68 + metadata: 69 + name: tranquil-pds 70 + namespace: pds 71 + spec: 72 + entryPoints: 73 + - websecure 74 + tls: 75 + secretName: sans-self-org-tls 76 + routes: 77 + - match: Host(`pds-next.sans-self.org`) 78 + kind: Rule 79 + middlewares: 80 + - name: strip-server-headers 81 + services: 82 + - name: tranquil-pds 63 83 port: 3000
+5
k8s/pds/kustomization.yaml
··· 12 12 - backup-cronjob.yaml 13 13 - network-policy.yaml 14 14 - tarpit-deployment.yaml 15 + - tranquil-pvc.yaml 16 + - tranquil-deployment.yaml 17 + - tranquil-frontend-deployment.yaml 18 + - tranquil-service.yaml 19 + - tranquil-frontend-service.yaml 15 20 16 21 generatorOptions: 17 22 disableNameSuffixHash: true
k8s/pds/msmtprc.secret

This is a binary file and will not be displayed.

+79
k8s/pds/network-policy.yaml
··· 41 41 kubernetes.io/metadata.name: traefik 42 42 ports: 43 43 - port: 8080 44 + --- 45 + apiVersion: networking.k8s.io/v1 46 + kind: NetworkPolicy 47 + metadata: 48 + name: tranquil-pds-ingress 49 + namespace: pds 50 + spec: 51 + podSelector: 52 + matchLabels: 53 + app: tranquil-pds 54 + policyTypes: 55 + - Ingress 56 + ingress: 57 + - from: 58 + - namespaceSelector: 59 + matchLabels: 60 + kubernetes.io/metadata.name: traefik 61 + ports: 62 + - port: 3000 63 + --- 64 + apiVersion: networking.k8s.io/v1 65 + kind: NetworkPolicy 66 + metadata: 67 + name: tranquil-pds-egress 68 + namespace: pds 69 + spec: 70 + podSelector: 71 + matchLabels: 72 + app: tranquil-pds 73 + policyTypes: 74 + - Egress 75 + egress: 76 + # Postgres 77 + - to: 78 + - namespaceSelector: 79 + matchLabels: 80 + kubernetes.io/metadata.name: postgres 81 + ports: 82 + - port: 5432 83 + # DNS 84 + - to: 85 + - namespaceSelector: 86 + matchLabels: 87 + kubernetes.io/metadata.name: kube-system 88 + ports: 89 + - port: 53 90 + protocol: UDP 91 + - port: 53 92 + protocol: TCP 93 + # External (S3, bsky.network, plc.directory, etc) 94 + - to: 95 + - ipBlock: 96 + cidr: 0.0.0.0/0 97 + except: 98 + - 10.0.0.0/8 99 + - 172.16.0.0/12 100 + - 192.168.0.0/16 101 + ports: 102 + - port: 443 103 + - port: 587 104 + --- 105 + apiVersion: networking.k8s.io/v1 106 + kind: NetworkPolicy 107 + metadata: 108 + name: tranquil-frontend-ingress 109 + namespace: pds 110 + spec: 111 + podSelector: 112 + matchLabels: 113 + app: tranquil-frontend 114 + policyTypes: 115 + - Ingress 116 + ingress: 117 + - from: 118 + - namespaceSelector: 119 + matchLabels: 120 + kubernetes.io/metadata.name: traefik 121 + ports: 122 + - port: 80
k8s/pds/tranquil-database-url.secret

This is a binary file and will not be displayed.

+122
k8s/pds/tranquil-deployment.yaml
··· 1 + apiVersion: apps/v1 2 + kind: Deployment 3 + metadata: 4 + name: tranquil-pds 5 + namespace: pds 6 + spec: 7 + replicas: 2 8 + strategy: 9 + type: RollingUpdate 10 + rollingUpdate: 11 + maxUnavailable: 1 12 + maxSurge: 1 13 + selector: 14 + matchLabels: 15 + app: tranquil-pds 16 + template: 17 + metadata: 18 + labels: 19 + app: tranquil-pds 20 + spec: 21 + affinity: 22 + podAntiAffinity: 23 + preferredDuringSchedulingIgnoredDuringExecution: 24 + - weight: 100 25 + podAffinityTerm: 26 + labelSelector: 27 + matchLabels: 28 + app: tranquil-pds 29 + topologyKey: kubernetes.io/hostname 30 + securityContext: 31 + runAsNonRoot: true 32 + runAsUser: 1000 33 + runAsGroup: 1000 34 + fsGroup: 1000 35 + containers: 36 + - name: tranquil-pds 37 + image: zot.sans-self.org/infra/tranquil-pds:latest 38 + ports: 39 + - name: http 40 + containerPort: 3000 41 + env: 42 + - name: PDS_HOSTNAME 43 + value: sans-self.org 44 + - name: DATABASE_URL 45 + valueFrom: 46 + secretKeyRef: 47 + name: tranquil-database-url 48 + key: url 49 + - name: BLOB_STORAGE_PATH 50 + value: /data/blobs 51 + - name: BACKUP_STORAGE_PATH 52 + value: /data/backups 53 + - name: CRAWLERS 54 + value: https://bsky.network 55 + - name: MAIL_FROM_ADDRESS 56 + value: noreply@sans-self.org 57 + - name: MAIL_FROM_NAME 58 + value: Sans Self PDS 59 + - name: SENDMAIL_PATH 60 + value: /usr/bin/msmtp 61 + - name: JWT_SECRET 62 + valueFrom: 63 + secretKeyRef: 64 + name: tranquil-secrets 65 + key: jwt_secret 66 + - name: DPOP_SECRET 67 + valueFrom: 68 + secretKeyRef: 69 + name: tranquil-secrets 70 + key: dpop_secret 71 + - name: MASTER_KEY 72 + valueFrom: 73 + secretKeyRef: 74 + name: tranquil-secrets 75 + key: master_key 76 + volumeMounts: 77 + - name: data 78 + mountPath: /data 79 + - name: msmtp-config 80 + mountPath: /etc/msmtprc 81 + subPath: msmtprc 82 + readOnly: true 83 + startupProbe: 84 + httpGet: 85 + path: /xrpc/_health 86 + port: 3000 87 + failureThreshold: 30 88 + periodSeconds: 2 89 + livenessProbe: 90 + httpGet: 91 + path: /xrpc/_health 92 + port: 3000 93 + periodSeconds: 30 94 + timeoutSeconds: 5 95 + failureThreshold: 3 96 + readinessProbe: 97 + httpGet: 98 + path: /xrpc/_health 99 + port: 3000 100 + periodSeconds: 10 101 + timeoutSeconds: 5 102 + failureThreshold: 2 103 + securityContext: 104 + allowPrivilegeEscalation: false 105 + capabilities: 106 + drop: 107 + - ALL 108 + resources: 109 + requests: 110 + cpu: 100m 111 + memory: 256Mi 112 + limits: 113 + cpu: 500m 114 + memory: 1Gi 115 + volumes: 116 + - name: data 117 + persistentVolumeClaim: 118 + claimName: tranquil-data 119 + - name: msmtp-config 120 + secret: 121 + secretName: msmtp-config 122 + defaultMode: 0400
k8s/pds/tranquil-dpop.secret

This is a binary file and will not be displayed.

+53
k8s/pds/tranquil-frontend-deployment.yaml
··· 1 + apiVersion: apps/v1 2 + kind: Deployment 3 + metadata: 4 + name: tranquil-frontend 5 + namespace: pds 6 + spec: 7 + replicas: 1 8 + selector: 9 + matchLabels: 10 + app: tranquil-frontend 11 + template: 12 + metadata: 13 + labels: 14 + app: tranquil-frontend 15 + spec: 16 + securityContext: 17 + runAsNonRoot: true 18 + runAsUser: 101 19 + runAsGroup: 101 20 + containers: 21 + - name: frontend 22 + image: zot.sans-self.org/infra/tranquil-frontend:latest 23 + # nginx needs writable cache/pid dirs 24 + volumeMounts: 25 + - name: cache 26 + mountPath: /var/cache/nginx 27 + - name: run 28 + mountPath: /var/run 29 + ports: 30 + - containerPort: 80 31 + readinessProbe: 32 + httpGet: 33 + path: / 34 + port: 80 35 + periodSeconds: 10 36 + failureThreshold: 2 37 + securityContext: 38 + allowPrivilegeEscalation: false 39 + capabilities: 40 + drop: 41 + - ALL 42 + resources: 43 + requests: 44 + cpu: 10m 45 + memory: 32Mi 46 + limits: 47 + cpu: 100m 48 + memory: 64Mi 49 + volumes: 50 + - name: cache 51 + emptyDir: {} 52 + - name: run 53 + emptyDir: {}
+11
k8s/pds/tranquil-frontend-service.yaml
··· 1 + apiVersion: v1 2 + kind: Service 3 + metadata: 4 + name: tranquil-frontend 5 + namespace: pds 6 + spec: 7 + selector: 8 + app: tranquil-frontend 9 + ports: 10 + - port: 80 11 + targetPort: 80
k8s/pds/tranquil-jwt.secret

This is a binary file and will not be displayed.

k8s/pds/tranquil-master-key.secret

This is a binary file and will not be displayed.

+12
k8s/pds/tranquil-pvc.yaml
··· 1 + apiVersion: v1 2 + kind: PersistentVolumeClaim 3 + metadata: 4 + name: tranquil-data 5 + namespace: pds 6 + spec: 7 + accessModes: 8 + - ReadWriteMany 9 + storageClassName: juicefs-sc 10 + resources: 11 + requests: 12 + storage: 20Gi
+11
k8s/pds/tranquil-service.yaml
··· 1 + apiVersion: v1 2 + kind: Service 3 + metadata: 4 + name: tranquil-pds 5 + namespace: pds 6 + spec: 7 + selector: 8 + app: tranquil-pds 9 + ports: 10 + - port: 3000 11 + targetPort: 3000
+29
k8s/postgres/initdb-configmap.yaml
··· 1 + apiVersion: v1 2 + kind: ConfigMap 3 + metadata: 4 + name: postgres-initdb 5 + namespace: postgres 6 + data: 7 + # Runs once on first init (only on primary), in alphabetical order. 8 + 01-hba.sh: | 9 + #!/bin/sh 10 + set -e 11 + echo "host all all all scram-sha-256" >> "$PGDATA/pg_hba.conf" 12 + echo "host replication replicator all scram-sha-256" >> "$PGDATA/pg_hba.conf" 13 + 14 + 02-replication.sh: | 15 + #!/bin/sh 16 + set -e 17 + psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname postgres <<EOSQL 18 + CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD '$REPLICATION_PASSWORD'; 19 + EOSQL 20 + 21 + 03-databases.sh: | 22 + #!/bin/sh 23 + set -e 24 + psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname postgres <<EOSQL 25 + CREATE USER tranquil WITH PASSWORD '$TRANQUIL_PASSWORD'; 26 + CREATE DATABASE pds OWNER tranquil; 27 + EOSQL 28 + 29 + # Future services: add 04-another-service.sh following the same pattern
+22
k8s/postgres/kustomization.yaml
··· 1 + apiVersion: kustomize.config.k8s.io/v1beta1 2 + kind: Kustomization 3 + 4 + resources: 5 + - namespace.yaml 6 + - statefulset.yaml 7 + - scripts-configmap.yaml 8 + - initdb-configmap.yaml 9 + - service.yaml 10 + - network-policy.yaml 11 + 12 + generatorOptions: 13 + disableNameSuffixHash: true 14 + 15 + secretGenerator: 16 + - name: postgres-credentials 17 + namespace: postgres 18 + type: Opaque 19 + files: 20 + - superuser-password=superuser-password.secret 21 + - tranquil-password=postgres-password.secret 22 + - replication-password=replication-password.secret
+4
k8s/postgres/namespace.yaml
··· 1 + apiVersion: v1 2 + kind: Namespace 3 + metadata: 4 + name: postgres
+26
k8s/postgres/network-policy.yaml
··· 1 + apiVersion: networking.k8s.io/v1 2 + kind: NetworkPolicy 3 + metadata: 4 + name: postgres-ingress 5 + namespace: postgres 6 + spec: 7 + podSelector: 8 + matchLabels: 9 + app: postgres 10 + policyTypes: 11 + - Ingress 12 + ingress: 13 + # PDS namespace → postgres 14 + - from: 15 + - namespaceSelector: 16 + matchLabels: 17 + kubernetes.io/metadata.name: pds 18 + ports: 19 + - port: 5432 20 + # Replication between primary and standby 21 + - from: 22 + - podSelector: 23 + matchLabels: 24 + app: postgres 25 + ports: 26 + - port: 5432
k8s/postgres/postgres-password.secret

This is a binary file and will not be displayed.

k8s/postgres/replication-password.secret

This is a binary file and will not be displayed.

+37
k8s/postgres/scripts-configmap.yaml
··· 1 + apiVersion: v1 2 + kind: ConfigMap 3 + metadata: 4 + name: postgres-scripts 5 + namespace: postgres 6 + data: 7 + entrypoint.sh: | 8 + #!/bin/sh 9 + set -e 10 + 11 + ORDINAL="${HOSTNAME##*-}" 12 + 13 + if [ "$ORDINAL" = "0" ]; then 14 + # Primary — delegate entirely to upstream entrypoint. 15 + # Initdb scripts in /docker-entrypoint-initdb.d/ handle user/db creation + replication setup. 16 + exec docker-entrypoint.sh postgres \ 17 + -c wal_level=replica \ 18 + -c max_wal_senders=3 \ 19 + -c hot_standby=on 20 + else 21 + # Standby — pg_basebackup from primary, then run as hot standby 22 + if [ ! -s "$PGDATA/PG_VERSION" ]; then 23 + until pg_isready -h postgres-0.postgres-headless.postgres.svc.cluster.local -U postgres -q; do 24 + echo "Waiting for primary..." 25 + sleep 2 26 + done 27 + 28 + PGPASSWORD="$REPLICATION_PASSWORD" pg_basebackup \ 29 + -h postgres-0.postgres-headless.postgres.svc.cluster.local \ 30 + -U replicator \ 31 + -D "$PGDATA" \ 32 + -Fp -Xs -R -P 33 + fi 34 + 35 + exec postgres \ 36 + -c hot_standby=on 37 + fi
+41
k8s/postgres/service.yaml
··· 1 + # Primary service — tranquil connects here for reads+writes 2 + apiVersion: v1 3 + kind: Service 4 + metadata: 5 + name: postgres 6 + namespace: postgres 7 + spec: 8 + selector: 9 + app: postgres 10 + statefulset.kubernetes.io/pod-name: postgres-0 11 + ports: 12 + - port: 5432 13 + targetPort: 5432 14 + --- 15 + # Headless service for StatefulSet pod DNS (postgres-0.postgres-headless, etc) 16 + apiVersion: v1 17 + kind: Service 18 + metadata: 19 + name: postgres-headless 20 + namespace: postgres 21 + spec: 22 + clusterIP: None 23 + selector: 24 + app: postgres 25 + ports: 26 + - port: 5432 27 + targetPort: 5432 28 + --- 29 + # Read-only service — hits the standby for read scaling if needed 30 + apiVersion: v1 31 + kind: Service 32 + metadata: 33 + name: postgres-ro 34 + namespace: postgres 35 + spec: 36 + selector: 37 + app: postgres 38 + statefulset.kubernetes.io/pod-name: postgres-1 39 + ports: 40 + - port: 5432 41 + targetPort: 5432
+120
k8s/postgres/statefulset.yaml
··· 1 + apiVersion: apps/v1 2 + kind: StatefulSet 3 + metadata: 4 + name: postgres 5 + namespace: postgres 6 + spec: 7 + serviceName: postgres-headless 8 + replicas: 2 9 + selector: 10 + matchLabels: 11 + app: postgres 12 + template: 13 + metadata: 14 + labels: 15 + app: postgres 16 + spec: 17 + securityContext: 18 + fsGroup: 70 19 + runAsUser: 70 20 + runAsGroup: 70 21 + runAsNonRoot: true 22 + affinity: 23 + podAntiAffinity: 24 + preferredDuringSchedulingIgnoredDuringExecution: 25 + - weight: 100 26 + podAffinityTerm: 27 + labelSelector: 28 + matchLabels: 29 + app: postgres 30 + topologyKey: kubernetes.io/hostname 31 + containers: 32 + - name: postgres 33 + image: postgres:18-alpine 34 + command: ["/scripts/entrypoint.sh"] 35 + ports: 36 + - containerPort: 5432 37 + env: 38 + - name: POSTGRES_PASSWORD 39 + valueFrom: 40 + secretKeyRef: 41 + name: postgres-credentials 42 + key: superuser-password 43 + - name: PGDATA 44 + value: /var/lib/postgresql/data/pgdata 45 + - name: REPLICATION_PASSWORD 46 + valueFrom: 47 + secretKeyRef: 48 + name: postgres-credentials 49 + key: replication-password 50 + - name: TRANQUIL_PASSWORD 51 + valueFrom: 52 + secretKeyRef: 53 + name: postgres-credentials 54 + key: tranquil-password 55 + volumeMounts: 56 + - name: data 57 + mountPath: /var/lib/postgresql/data 58 + - name: scripts 59 + mountPath: /scripts 60 + readOnly: true 61 + - name: initdb 62 + mountPath: /docker-entrypoint-initdb.d 63 + readOnly: true 64 + startupProbe: 65 + exec: 66 + command: 67 + - pg_isready 68 + - -U 69 + - postgres 70 + periodSeconds: 5 71 + failureThreshold: 60 72 + livenessProbe: 73 + exec: 74 + command: 75 + - pg_isready 76 + - -U 77 + - postgres 78 + periodSeconds: 30 79 + timeoutSeconds: 5 80 + failureThreshold: 3 81 + readinessProbe: 82 + exec: 83 + command: 84 + - pg_isready 85 + - -U 86 + - postgres 87 + periodSeconds: 10 88 + timeoutSeconds: 5 89 + failureThreshold: 2 90 + securityContext: 91 + allowPrivilegeEscalation: false 92 + capabilities: 93 + drop: 94 + - ALL 95 + resources: 96 + requests: 97 + cpu: 100m 98 + memory: 128Mi 99 + limits: 100 + cpu: 500m 101 + memory: 512Mi 102 + volumes: 103 + - name: scripts 104 + configMap: 105 + name: postgres-scripts 106 + defaultMode: 0555 107 + - name: initdb 108 + configMap: 109 + name: postgres-initdb 110 + defaultMode: 0555 111 + volumeClaimTemplates: 112 + - metadata: 113 + name: data 114 + spec: 115 + accessModes: 116 + - ReadWriteOnce 117 + storageClassName: juicefs-sc 118 + resources: 119 + requests: 120 + storage: 10Gi
k8s/postgres/superuser-password.secret

This is a binary file and will not be displayed.

+8
kube.tf
··· 92 92 port = "22" 93 93 source_ips = ["0.0.0.0/0", "::/0"] 94 94 destination_ips = [] 95 + }, 96 + { 97 + description = "Outbound SMTP (Resend)" 98 + direction = "out" 99 + protocol = "tcp" 100 + port = "587" 101 + source_ips = [] 102 + destination_ips = ["0.0.0.0/0", "::/0"] 95 103 } 96 104 ] 97 105