From 9288d0640b90774de464f7f67b6f3242dd3112a5 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Mon, 23 Feb 2026 21:58:05 +0330 Subject: [PATCH] =?UTF-8?q?feat:=20externalize=20appsettings=20to=20K8s=20?= =?UTF-8?q?Secret=20=E2=80=94=20config=20persists=20independently=20of=20i?= =?UTF-8?q?mage=20rebuilds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add cms-config.yaml (K8s Secret) for staging and production - Mount appsettings.{Environment}.json from Secret into /app/ - Update CI/CD pipelines to apply Secret before Deployment - Remove redundant env vars (Kestrel, FileStorage) — now in config file --- .gitea/workflows/kub-deploy.yml | 6 +- .gitea/workflows/prod-deploy.yml | 6 +- k8s/production/cms-config.yaml | 54 ++++++++++++++ k8s/production/cms-deployment.yaml | 11 +-- k8s/staging/cms-config.yaml | 111 +++++++++++++++++++++++++++++ k8s/staging/cms-deployment.yaml | 11 +-- 6 files changed, 187 insertions(+), 12 deletions(-) create mode 100644 k8s/production/cms-config.yaml create mode 100644 k8s/staging/cms-config.yaml diff --git a/.gitea/workflows/kub-deploy.yml b/.gitea/workflows/kub-deploy.yml index 5ec5fb4..afef447 100644 --- a/.gitea/workflows/kub-deploy.yml +++ b/.gitea/workflows/kub-deploy.yml @@ -88,13 +88,15 @@ jobs: export SSHPASS="${{ secrets.SERVER_PASSWORD }}" # Copy K8s manifests to server + sshpass -e scp -o StrictHostKeyChecking=no k8s/staging/cms-config.yaml root@${{ env.K8S_SERVER }}:/tmp/cms-config.yaml sshpass -e scp -o StrictHostKeyChecking=no k8s/staging/cms-deployment.yaml root@${{ env.K8S_SERVER }}:/tmp/cms-deployment.yaml - # Apply manifests and restart + # Apply config (Secret) first, then deployment sshpass -e ssh -o StrictHostKeyChecking=no root@${{ env.K8S_SERVER }} " + kubectl apply -f /tmp/cms-config.yaml && kubectl apply -f /tmp/cms-deployment.yaml && kubectl rollout restart deployment/cms && kubectl rollout status deployment/cms --timeout=180s && - rm -f /tmp/cms-deployment.yaml + rm -f /tmp/cms-config.yaml /tmp/cms-deployment.yaml " echo "✅ Deployed!" diff --git a/.gitea/workflows/prod-deploy.yml b/.gitea/workflows/prod-deploy.yml index decefb7..b090926 100644 --- a/.gitea/workflows/prod-deploy.yml +++ b/.gitea/workflows/prod-deploy.yml @@ -88,13 +88,15 @@ jobs: export SSHPASS="${{ secrets.SERVER_PASSWORD }}" # Copy K8s manifests to server + sshpass -e scp -o StrictHostKeyChecking=no k8s/production/cms-config.yaml root@${{ env.K8S_SERVER }}:/tmp/cms-config.yaml sshpass -e scp -o StrictHostKeyChecking=no k8s/production/cms-deployment.yaml root@${{ env.K8S_SERVER }}:/tmp/cms-deployment.yaml - # Apply manifests and update image + # Apply config (Secret) first, then deployment and update image sshpass -e ssh -o StrictHostKeyChecking=no root@${{ env.K8S_SERVER }} " + kubectl apply -f /tmp/cms-config.yaml && kubectl apply -f /tmp/cms-deployment.yaml && kubectl set image deployment/cms cms=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} && kubectl rollout status deployment/cms --timeout=300s && - rm -f /tmp/cms-deployment.yaml + rm -f /tmp/cms-config.yaml /tmp/cms-deployment.yaml " echo "✅ Deployed to Production!" diff --git a/k8s/production/cms-config.yaml b/k8s/production/cms-config.yaml new file mode 100644 index 0000000..b96af17 --- /dev/null +++ b/k8s/production/cms-config.yaml @@ -0,0 +1,54 @@ +--- +# CMS Application Configuration Secret (Production) +# This Secret stores appsettings.Production.json externally in K8s +# so configuration persists independently of Docker image rebuilds. +# +# To edit config without redeploying: +# kubectl edit secret cms-appsettings +# kubectl rollout restart deployment/cms +# +apiVersion: v1 +kind: Secret +metadata: + name: cms-appsettings + namespace: default + labels: + app: cms + environment: production +type: Opaque +stringData: + appsettings.Production.json: | + { + "ZarinPal": { + "MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8", + "UseSandbox": false + }, + "CmsBaseUrl": "https://cms.kbs1.ir", + "FrontOfficeBaseUrl": "https://kbs1.ir", + "ConnectionStrings": { + "DefaultConnection": "Server=mssql-svc;Database=KBS;User Id=sa;Password=YourStrong@Passw0rd;TrustServerCertificate=True;" + }, + "SeedWorkers": { + "MagicWalletCycleSeed": { + "Enabled": true + } + }, + "FileStorage": { + "UploadPath": "/app/Uploads" + }, + "Kestrel": { + "EndpointDefaults": { + "Protocols": "Http2" + } + }, + "Seq": { + "ServerUrl": "http://seq-svc:5341", + "ApiKey": "oxpvpUzU1pZxMS4s3Fqq" + }, + "Logging": { + "LogLevel": { + "Default": "Warning", + "Microsoft.AspNetCore": "Warning" + } + } + } diff --git a/k8s/production/cms-deployment.yaml b/k8s/production/cms-deployment.yaml index a5f3188..9fdec43 100644 --- a/k8s/production/cms-deployment.yaml +++ b/k8s/production/cms-deployment.yaml @@ -45,13 +45,13 @@ spec: value: "Production" - name: ASPNETCORE_URLS value: "http://+:8080" - - name: Kestrel__EndpointDefaults__Protocols - value: "Http1AndHttp2" - - name: FileStorage__UploadPath - value: "/app/Uploads" volumeMounts: - name: cms-uploads mountPath: /app/Uploads + - name: cms-config + mountPath: /app/appsettings.Production.json + subPath: appsettings.Production.json + readOnly: true resources: requests: memory: "512Mi" @@ -79,6 +79,9 @@ spec: - name: cms-uploads persistentVolumeClaim: claimName: cms-uploads-pvc + - name: cms-config + secret: + secretName: cms-appsettings --- # CMS Service (Production) diff --git a/k8s/staging/cms-config.yaml b/k8s/staging/cms-config.yaml new file mode 100644 index 0000000..138f927 --- /dev/null +++ b/k8s/staging/cms-config.yaml @@ -0,0 +1,111 @@ +--- +# CMS Application Configuration Secret (Staging) +# This Secret stores appsettings.Staging.json externally in K8s +# so configuration persists independently of Docker image rebuilds. +# +# To edit config without redeploying: +# kubectl edit secret cms-appsettings +# kubectl rollout restart deployment/cms +# +apiVersion: v1 +kind: Secret +metadata: + name: cms-appsettings + namespace: default + labels: + app: cms + environment: staging +type: Opaque +stringData: + appsettings.Staging.json: | + { + "PaymentProvider": "zarinpal", + "ZarinPal": { + "MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8", + "UseSandbox": true + }, + "FMS": { + "Address": "https://dl.afrino.co" + }, + "JwtSecurityKey": "TvlZVx5TJaHs8e9HgUdGzhGP2CIidoI444nAj+8+g7c=", + "JwtIssuer": "https://localhost", + "JwtAudience": "https://localhost", + "JwtExpiryInDays": 5, + "ConnectionStrings": { + "DefaultConnection": "Data Source=194.5.195.53,31433; Initial Catalog=Foursat;User ID=sa;Password=87zH26nbqT;Connection Timeout=300000;MultipleActiveResultSets=True;Encrypt=False", + "providerName": "System.Data.SqlClient" + }, + "Otp": { + "Secret": "K2w8k1h1mH2Qz1kqWk0c8kQ2Pq8q9H1eE2nqN1qQ8x7M=" + }, + "Monitoring": { + "SentryEnabled": false, + "SentryDsn": "", + "SlackEnabled": false, + "SlackWebhookUrl": "", + "EmailAlertsEnabled": false, + "AdminEmails": ["admin@example.com"], + "SmsNotificationsEnabled": false, + "SmsApiKey": "", + "SmsGatewayUrl": "" + }, + "Email": { + "Enabled": true, + "SmtpHost": "smtp.gmail.com", + "SmtpPort": 587, + "SmtpUsername": "your-email@gmail.com", + "SmtpPassword": "your-app-password", + "FromEmail": "noreply@foursat.com", + "FromName": "FourSat CMS", + "EnableSsl": true + }, + "Sms": { + "Enabled": true, + "Provider": "Kavenegar", + "KavenegarApiKey": "497263626F32626A48685A6137524C4F78575A766E4C74694A556B79317648424964655030682B554545413D", + "Sender": "1000001110100" + }, + "DayaPayment": { + "BaseUrl": "https://api.daya.ir", + "ApiKey": "YOUR_DAYA_API_KEY" + }, + "DayaApi": { + "UseMock": false, + "BaseAddress": "https://Dayadiamond.ir", + "MerchantPermissionKey": "56146364$04sXjethI5WxhItR1Q9xnmFdJzl2BB8Bclsq8dAy7YVSZp3vtt-wP7ivrcCvmKLq", + "CacheDurationMinutes": 20 + }, + "Chatika": { + "Enabled": true, + "BaseUrl": "https://api.chatika.ir", + "ApiKey": "tIukvL8dnV4cB3yVWcCD9Xyfbj8rBxm5wPt2mLyJCgTsBBoMTWjt6mFEqQwpw-er" + }, + "BackgroundJobs": { + "WeeklyCommissionCalculation": { + "Enabled": true, + "CronExpression": "5 0 * * 0" + } + }, + "SeedWorkers": { + "MagicWalletCycleSeed": { + "Enabled": true + } + }, + "FileStorage": { + "UploadPath": "/app/Uploads" + }, + "AllowedHosts": "*", + "Kestrel": { + "EndpointDefaults": { + "Protocols": "Http1AndHttp2" + } + }, + "Authentication": { + "Authority": "https://ids.domain.com/", + "Audience": "domain_api" + }, + "Seq": { + "ServerUrl": "https://seq.afrino.co", + "ApiKey": "oxpvpUzU1pZxMS4s3Fqq" + } + } diff --git a/k8s/staging/cms-deployment.yaml b/k8s/staging/cms-deployment.yaml index 59a1e50..b65aade 100644 --- a/k8s/staging/cms-deployment.yaml +++ b/k8s/staging/cms-deployment.yaml @@ -45,13 +45,13 @@ spec: value: "Staging" - name: ASPNETCORE_URLS value: "http://+:8080" - - name: Kestrel__EndpointDefaults__Protocols - value: "Http1AndHttp2" - - name: FileStorage__UploadPath - value: "/app/Uploads" volumeMounts: - name: cms-uploads mountPath: /app/Uploads + - name: cms-config + mountPath: /app/appsettings.Staging.json + subPath: appsettings.Staging.json + readOnly: true resources: requests: memory: "512Mi" @@ -79,6 +79,9 @@ spec: - name: cms-uploads persistentVolumeClaim: claimName: cms-uploads-pvc + - name: cms-config + secret: + secretName: cms-appsettings --- # CMS Service