From 2b22c90a566f09611e6a530824d78fc673fdc21e Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Sat, 28 Feb 2026 19:44:08 +0000 Subject: [PATCH] [ci skip] Phase 2: migrate Redis from NFS to local disk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Switch from redis/redis-stack:latest to redis:7-alpine (modules were completely unused — zero module commands in stats) - Move data from NFS (/mnt/main/redis) to local-path PVC (RDB saves: 39s on NFS → <1s on local disk) - Start fresh (old RDB had redis-stack module data incompatible with plain redis; all Redis data is transient — queues and caches rebuild automatically) - Add hourly redis-backup CronJob: redis-cli --rdb to NFS for backup pipeline - Remove RedisInsight UI ingress (port 8001, only in redis-stack) - Add redis-backup to NFS exports - 110 clients reconnected immediately after switchover - Memory savings: ~100MB from dropping unused modules --- secrets/nfs_directories.txt | Bin 1760 -> 1773 bytes stacks/platform/modules/redis/main.tf | 103 ++++++++++++++++++++------ 2 files changed, 80 insertions(+), 23 deletions(-) diff --git a/secrets/nfs_directories.txt b/secrets/nfs_directories.txt index 9b854c7c93723724a3bd217f07b94f05572d94b8..0c57a0f0ce48aea47335e382cd6e9a203a3a3b7e 100644 GIT binary patch literal 1773 zcmV!LPolgCOquHcSNHV}cs@GGnoW zQ%P7A?F22iKhg8;wueyB*I-vFchiHfL2_(Oa!+Eed^1@ojHa^m5O&TmMC=t`h}F?q zGM{9#)G5P!)pqJd<19+I4*UbGUV&xYe@kwZ;eOi=?+8-SM}(m_{BYKe$h8sq(|}}sm?jcy=xJXu6SKp zKhwb>J}_eWD)04e8E3@;g`w#iGAB4Sw)D5;>1Ku~&zP6f=l&U==ZSRdYSGA}>9%eU zg}R3#mHcxXHgm?z(u~Pz%%VS6`3RJBbXogE>(tT15XsYoH~j-wa(-!b#aIYci2&&0)Lg33DTVLQvnE zn*CIqcSbqVZm0Gw)URJcG8+nb*V|v8f{Dx&1o!&mBr>QRZt}sQ^T$KIP~06VTT8$A zXZ6W!0QNvA*-@)KdLvY*W3{(oSwe>0%f!ROv0X8HEPygd7C;2OjFg(CWI((I1Lku0 zXB1%iW9~VwGSkd0Wr*|jV^wZ-&|+KMC&^{AXuTv1#El1v7@BiQZSAjI@QrS|LMr{r zIEVNm?f>1qoO40Koj3c@)3gbdddwC%I|?34^S6TS7Q@25%m+Vz1d5VPX6b%Mj^xLB z5e3g?2%8yT^wyg2QZ##HnjEW#&9AoZn4(S=qH&d8>ED~9i&a;eRAHf5ik(MpHm!Eq zKcy!U=t-(CNME`qJ&FL#1yS_UOeTbUAk05xNJrm(!C;kHVkcm=5`<*Fqu{{IKs&xxX# z&JDhdK%PejEWlj0l_v4FT7$`Kx@u1-DsyFprF+^YAL@%}wE|ZJPJgjHdb_+j6Z8FY{vZ%eK{1ZfgXS2FscQ_xz6pJ=v4?whfj~04u0K^#3jvWo!X$UjAzJcCGiv z<>I^_EWV%wbc+kGr&0M6rl`zcjexHN_(eECV~6i&3|^Qv6x*uMb^E~#>S6~x`y>tN zo(JC={i4&tZ%cKx-ew8^&noGX6!rruMlnMUP>mp#!-Jad=a%BZ?!H(1-)o%{pJnuf zX(a-Og;Xq^vl3-{>N`{t#;ReUvGU#SdfgTG&nU9p7lRp1&WG8d6-qkp_N7kJ7%!7< zduOO?vvO}JqMdBdyX1DQ4ZmgnEdBx`A zzYNB0*YvldFG-srB_H*+vs>4)^X8`AkrPu#V9cfn8%jgsk$d6#yk6X>W zz;7Cgp&w(+KX!%6#+NTyfg2iO=3T~W+R7*I7@Q=s zzp6SydD1eyT`g#1oK#g5DewBk>{(Z3Lv9n(L(;}8<;?Z19n>&sn3(Jtz)9as9;AjN zvdpwCGCzP5T*nO=l_jf|eZB|PE&$?Syq%7iwe>)XKsOtj`kC{CvdPTZlP?#?qi1Aq zGXyM6zYKfl{g)Kn*jjJIIZ|b90Ow>;K^BzJqG1S6I_{Et(*&@)*((0v~{cN zm<4^TPHm641C4zBrMX|a6(X#ZYRfNmsIX*by)!0`_Nf&ZFT2p4G6lbH-=1c(=_7-P zB{UmsPu4>$VCr+nsz~S|9vA3Fi P9`7)}X#jllh)-%D3=)Ay literal 1760 zcmV<61|RtVM@dveQdv+`0I{_B*xa^GrUL+0yVd&s9mCOiKGoeAPXQK{oo={B1EqA>xW;Q1KG-<-g<9;MTlrBdK$cxdLH?W`XXheSTrMZrKKs6Uc0qmr=1o6>R|F8g0&t41)$qmqTOA|VHzccvj+nl zs^0#$u%?jhDuM(VcK+$$Qqu7gZPtL6!oqg7Fa3F82Ibau3Cx$G{a#PpurpgUhd1;9W6_m1Qj^illnps|3d!6q%vlNDg9y&3$$64MU3D zZxz31P5Sf|H^!6X?LgLoNZ6inGq!j1SEdie$Pfe~V&nb@`>i*3p-6boVtt@r8eTQ`f`03pOZd%pW2h~t-X)aI8sdhQbg6vDSkm*QzI9p zY3PQ|2LL%hsJz0qJncbWRRnCqp;J8u-*!N*+0kQNJc|NFy6c4y9QNg3m-KPzA+w{o ztTD7sIGpV>E=BRx`>SyRy57bjpe`s*Xba_kt5q~3d#a)L(9*A>>67^knbFMMcjVwe zPyo5!jAY(Rsm2KZ3O~L)l&%1jO(Khp*#)(a6wd~xdZE}aLzcC3i+l2Fdm(&P5(nZS z{y-?eZ$fr~z&PTJN>n_^{Yr+Wb2DC7^O!RIss;Bo&gAx`e-A&36>>HrvMD~ZF6UUQ zz4Vq15V9rW@^QM&R#{3ZyKBdHEU3tLEH7$fL`IueO--AN<$1~kDvp}PQ_X|(D;R0YPqDP} zDEOVQu{kbd-FFeDvl31TQj&_66C%@4hiP^#TuSb!04T;dsG}o>uOD|(NxEw^mU)(d z^$yGGizi#Izv*H&kjF-JH3XXB;&A)lK<*D^;7ZyPC1D7TBl%IcTPpJ?z zoOs!kuLY{zG+SHBWQ@q)`$dpo!ffE#S%#U)j7DO;ayX}B?)m6;J4U1wwQ|7N$sm3( zNf4(F?_-)lR~_3{^j$p-o@iDmjU2dPtxFYgAOE(C2Jg;lj`eZ^>w1CyPax3q*W=G+ zksW!7Xg7>+*!E;J&!GXC6&xfvRl)G4+o-OSMd#(+3ChrtZk6d~B)+eHhO(#ak4DY3 zM!K2+mfzYsUe(@~pm;DwRn)ut#~%{o4HwsU$k_O&vH~T2Nztd+OyEr^mf!R*C75A zUsv(&&MA@j5Nrb3Wfx^WpbXu5I`jna@D$BI!3gUt2AOp-Klb@Z6+JY>+Qm%RpK7Ej zyA9FSXo(r8%nV)%wqm47&nl45@;Q9168Q0$DP$AU4LGsrFp%+HudXrL6+tfmbXESp zm|4XPOCM)4Zn2bfmSisj=UH5Olha-MJ&&EvINVkEx1Jjc0#v2~=&zj$q1Op>)mtvt zijC^QefHT%&fhC#i?u6y1Hcq^aX&Sa6|H!KRGLP<7?SQ$*Ib!7cCpl3PSe!lL2Fc# zYK;{iC|-keZ)f~DO@9b7xck%X3Nrps(*=zG05Vk;nosD2)b0Qzw3x$3IA$T-BtMny z6(4|5P8UJNcQ1dU)x^t(E0t?xbwNn%&|T4AYvN@i_DblHyxidDST6ABaBlVy6ziv8 CQG5;n diff --git a/stacks/platform/modules/redis/main.tf b/stacks/platform/modules/redis/main.tf index 257dd7fe..fc3a3d2f 100644 --- a/stacks/platform/modules/redis/main.tf +++ b/stacks/platform/modules/redis/main.tf @@ -17,6 +17,25 @@ module "tls_secret" { tls_secret_name = var.tls_secret_name } +# Local PVC for Redis data — proper fsync, fast RDB saves +resource "kubernetes_persistent_volume_claim" "redis" { + metadata { + name = "redis-data" + namespace = kubernetes_namespace.redis.metadata[0].name + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "local-path" + resources { + requests = { + storage = "2Gi" + } + } + } + + wait_until_bound = false +} + resource "kubernetes_deployment" "redis" { metadata { name = "redis" @@ -25,9 +44,6 @@ resource "kubernetes_deployment" "redis" { app = "redis" tier = var.tier } - annotations = { - "reloader.stakater.com/search" = "true" - } } spec { replicas = 1 @@ -46,14 +62,17 @@ resource "kubernetes_deployment" "redis" { } } spec { + # No init container needed — all Redis data is transient (queues, caches). + # Starting fresh is safe; services rebuild their state automatically. + container { - image = "redis/redis-stack:latest" + image = "redis:7-alpine" name = "redis" resources { requests = { cpu = "200m" - memory = "1Gi" + memory = "512Mi" } limits = { cpu = "1" @@ -64,21 +83,19 @@ resource "kubernetes_deployment" "redis" { port { container_port = 6379 } - port { - container_port = 8001 - } volume_mount { name = "data" mount_path = "/data" } } + volume { name = "data" - nfs { - path = "/mnt/main/redis" - server = var.nfs_server + persistent_volume_claim { + claim_name = kubernetes_persistent_volume_claim.redis.metadata[0].name } } + dns_config { option { name = "ndots" @@ -89,6 +106,7 @@ resource "kubernetes_deployment" "redis" { } } } + resource "kubernetes_service" "redis" { metadata { name = "redis" @@ -97,7 +115,6 @@ resource "kubernetes_service" "redis" { app = "redis" } } - spec { selector = { app = "redis" @@ -106,17 +123,57 @@ resource "kubernetes_service" "redis" { name = "redis" port = 6379 } - port { - name = "http" - port = 8001 + } +} + +# Hourly backup: copy RDB snapshot to NFS for the TrueNAS → backup NAS pipeline +resource "kubernetes_cron_job_v1" "redis-backup" { + metadata { + name = "redis-backup" + namespace = kubernetes_namespace.redis.metadata[0].name + } + spec { + concurrency_policy = "Replace" + failed_jobs_history_limit = 3 + schedule = "0 * * * *" + starting_deadline_seconds = 10 + successful_jobs_history_limit = 3 + job_template { + metadata {} + spec { + backoff_limit = 2 + ttl_seconds_after_finished = 60 + template { + metadata {} + spec { + container { + name = "redis-backup" + image = "redis:7-alpine" + command = ["/bin/sh", "-c", <<-EOT + set -eux + # Trigger a fresh RDB save + redis-cli -h redis.redis BGSAVE + sleep 5 + # Copy the RDB from the running pod's data via redis + redis-cli -h redis.redis --rdb /backup/dump.rdb + echo "Backup complete: $(ls -lh /backup/dump.rdb)" + EOT + ] + volume_mount { + name = "backup" + mount_path = "/backup" + } + } + volume { + name = "backup" + nfs { + path = "/mnt/main/redis-backup" + server = var.nfs_server + } + } + } + } + } } } } -module "ingress" { - source = "../../../../modules/kubernetes/ingress_factory" - namespace = kubernetes_namespace.redis.metadata[0].name - name = "redis" - tls_secret_name = var.tls_secret_name - protected = true - port = 8001 -}