Initial commit - production deployment
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
# Create a root CA certificate for local development
|
||||
# NOTE: This certificate must be ready before the local-ca-issuer can be used
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: local-ca-cert
|
||||
namespace: cert-manager # This ensures the secret is created in the cert-manager namespace
|
||||
spec:
|
||||
isCA: true
|
||||
commonName: bakery-ia-local-ca
|
||||
subject:
|
||||
organizationalUnits:
|
||||
- "Bakery IA Local CA"
|
||||
organizations:
|
||||
- "Bakery IA"
|
||||
countries:
|
||||
- "US"
|
||||
secretName: local-ca-key-pair
|
||||
privateKey:
|
||||
algorithm: ECDSA
|
||||
size: 256
|
||||
issuerRef:
|
||||
name: selfsigned-issuer
|
||||
kind: ClusterIssuer
|
||||
group: cert-manager.io
|
||||
duration: 8760h # 1 year
|
||||
renewBefore: 720h # 30 days
|
||||
23
infrastructure/platform/cert-manager/cert-manager.yaml
Normal file
23
infrastructure/platform/cert-manager/cert-manager.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: cert-manager
|
||||
---
|
||||
# NOTE: Do NOT define cert-manager ServiceAccounts here!
|
||||
# The ServiceAccounts (cert-manager, cert-manager-cainjector, cert-manager-webhook)
|
||||
# are created by the upstream cert-manager installation (kubernetes_restart.sh).
|
||||
# Redefining them here would strip their RBAC bindings and break authentication.
|
||||
---
|
||||
# Self-signed ClusterIssuer for bootstrapping the CA certificate chain
|
||||
# This issuer is used to create the root CA certificate which then
|
||||
# becomes the issuer for all other certificates in the cluster
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: selfsigned-issuer
|
||||
spec:
|
||||
selfSigned: {}
|
||||
---
|
||||
# Cert-manager installation using Helm repository
|
||||
# This will be installed via kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.2/cert-manager.yaml
|
||||
# The actual installation will be done via command line, this file documents the resources
|
||||
@@ -0,0 +1,23 @@
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: letsencrypt-production
|
||||
namespace: cert-manager
|
||||
spec:
|
||||
acme:
|
||||
# The ACME server URL (Let's Encrypt production)
|
||||
server: https://acme-v02.api.letsencrypt.org/directory
|
||||
# Email address used for ACME registration
|
||||
email: admin@bakewise.ai
|
||||
# Name of a secret used to store the ACME account private key
|
||||
privateKeySecretRef:
|
||||
name: letsencrypt-production
|
||||
# Enable the HTTP-01 challenge provider
|
||||
solvers:
|
||||
- http01:
|
||||
ingress:
|
||||
class: public
|
||||
podTemplate:
|
||||
spec:
|
||||
nodeSelector:
|
||||
"kubernetes.io/os": linux
|
||||
@@ -0,0 +1,24 @@
|
||||
# Let's Encrypt Staging ClusterIssuer
|
||||
# Use this for testing before switching to production
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: letsencrypt-staging
|
||||
spec:
|
||||
acme:
|
||||
# The ACME server URL (Let's Encrypt staging)
|
||||
server: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
# Email address used for ACME registration
|
||||
email: admin@bakery-ia.local # Change this to your email
|
||||
# Name of a secret used to store the ACME account private key
|
||||
privateKeySecretRef:
|
||||
name: letsencrypt-staging
|
||||
# Enable the HTTP-01 challenge provider
|
||||
solvers:
|
||||
- http01:
|
||||
ingress:
|
||||
class: public
|
||||
podTemplate:
|
||||
spec:
|
||||
nodeSelector:
|
||||
"kubernetes.io/os": linux
|
||||
9
infrastructure/platform/cert-manager/kustomization.yaml
Normal file
9
infrastructure/platform/cert-manager/kustomization.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- cert-manager.yaml
|
||||
- ca-root-certificate.yaml
|
||||
- local-ca-issuer.yaml
|
||||
- cluster-issuer-staging.yaml
|
||||
- cluster-issuer-production.yaml
|
||||
@@ -0,0 +1,7 @@
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: local-ca-issuer
|
||||
spec:
|
||||
ca:
|
||||
secretName: local-ca-key-pair
|
||||
@@ -0,0 +1,8 @@
|
||||
# Self-signed ClusterIssuer for local development certificates
|
||||
# This issuer can generate self-signed certificates without needing external CA
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: selfsigned-issuer
|
||||
spec:
|
||||
selfSigned: {}
|
||||
104
infrastructure/platform/gateway/gateway-service.yaml
Normal file
104
infrastructure/platform/gateway/gateway-service.yaml
Normal file
@@ -0,0 +1,104 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: gateway
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app.kubernetes.io/name: gateway
|
||||
app.kubernetes.io/component: gateway
|
||||
app.kubernetes.io/part-of: bakery-ia
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: gateway
|
||||
app.kubernetes.io/component: gateway
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: gateway
|
||||
app.kubernetes.io/component: gateway
|
||||
spec:
|
||||
containers:
|
||||
- name: gateway
|
||||
image: bakery/gateway:latest
|
||||
ports:
|
||||
- containerPort: 8000
|
||||
name: http
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: bakery-config
|
||||
- secretRef:
|
||||
name: database-secrets
|
||||
- secretRef:
|
||||
name: redis-secrets
|
||||
- secretRef:
|
||||
name: rabbitmq-secrets
|
||||
- secretRef:
|
||||
name: jwt-secrets
|
||||
- secretRef:
|
||||
name: external-api-secrets
|
||||
- secretRef:
|
||||
name: payment-secrets
|
||||
- secretRef:
|
||||
name: email-secrets
|
||||
- secretRef:
|
||||
name: monitoring-secrets
|
||||
- secretRef:
|
||||
name: pos-integration-secrets
|
||||
- secretRef:
|
||||
name: whatsapp-secrets
|
||||
env:
|
||||
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: bakery-config
|
||||
key: OTEL_EXPORTER_OTLP_ENDPOINT
|
||||
- name: SIGNOZ_OTEL_COLLECTOR_URL
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: bakery-config
|
||||
key: SIGNOZ_OTEL_COLLECTOR_URL
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8000
|
||||
initialDelaySeconds: 30
|
||||
timeoutSeconds: 10
|
||||
periodSeconds: 30
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8000
|
||||
initialDelaySeconds: 5
|
||||
timeoutSeconds: 5
|
||||
periodSeconds: 10
|
||||
failureThreshold: 3
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: gateway-service
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app.kubernetes.io/name: gateway
|
||||
app.kubernetes.io/component: gateway
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 8000
|
||||
targetPort: 8000
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
app.kubernetes.io/name: gateway
|
||||
app.kubernetes.io/component: gateway
|
||||
5
infrastructure/platform/gateway/kustomization.yaml
Normal file
5
infrastructure/platform/gateway/kustomization.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- gateway-service.yaml
|
||||
45
infrastructure/platform/hpa/forecasting-hpa.yaml
Normal file
45
infrastructure/platform/hpa/forecasting-hpa.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: forecasting-service-hpa
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app.kubernetes.io/name: forecasting-service
|
||||
app.kubernetes.io/component: autoscaling
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: forecasting-service
|
||||
minReplicas: 1
|
||||
maxReplicas: 3
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 70
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 75
|
||||
behavior:
|
||||
scaleDown:
|
||||
stabilizationWindowSeconds: 300
|
||||
policies:
|
||||
- type: Percent
|
||||
value: 50
|
||||
periodSeconds: 60
|
||||
scaleUp:
|
||||
stabilizationWindowSeconds: 60
|
||||
policies:
|
||||
- type: Percent
|
||||
value: 100
|
||||
periodSeconds: 30
|
||||
- type: Pods
|
||||
value: 1
|
||||
periodSeconds: 60
|
||||
selectPolicy: Max
|
||||
45
infrastructure/platform/hpa/notification-hpa.yaml
Normal file
45
infrastructure/platform/hpa/notification-hpa.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: notification-service-hpa
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app.kubernetes.io/name: notification-service
|
||||
app.kubernetes.io/component: autoscaling
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: notification-service
|
||||
minReplicas: 1
|
||||
maxReplicas: 3
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 70
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 80
|
||||
behavior:
|
||||
scaleDown:
|
||||
stabilizationWindowSeconds: 300
|
||||
policies:
|
||||
- type: Percent
|
||||
value: 50
|
||||
periodSeconds: 60
|
||||
scaleUp:
|
||||
stabilizationWindowSeconds: 60
|
||||
policies:
|
||||
- type: Percent
|
||||
value: 100
|
||||
periodSeconds: 30
|
||||
- type: Pods
|
||||
value: 1
|
||||
periodSeconds: 60
|
||||
selectPolicy: Max
|
||||
45
infrastructure/platform/hpa/orders-hpa.yaml
Normal file
45
infrastructure/platform/hpa/orders-hpa.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: orders-service-hpa
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app.kubernetes.io/name: orders-service
|
||||
app.kubernetes.io/component: autoscaling
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: orders-service
|
||||
minReplicas: 1
|
||||
maxReplicas: 3
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 70
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 80
|
||||
behavior:
|
||||
scaleDown:
|
||||
stabilizationWindowSeconds: 300
|
||||
policies:
|
||||
- type: Percent
|
||||
value: 50
|
||||
periodSeconds: 60
|
||||
scaleUp:
|
||||
stabilizationWindowSeconds: 60
|
||||
policies:
|
||||
- type: Percent
|
||||
value: 100
|
||||
periodSeconds: 30
|
||||
- type: Pods
|
||||
value: 1
|
||||
periodSeconds: 60
|
||||
selectPolicy: Max
|
||||
198
infrastructure/platform/mail/mailu-helm/MIGRATION_GUIDE.md
Normal file
198
infrastructure/platform/mail/mailu-helm/MIGRATION_GUIDE.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# Mailu Migration Guide: From Kustomize to Helm
|
||||
|
||||
This document outlines the migration process from the Kustomize-based Mailu deployment to the Helm-based deployment.
|
||||
|
||||
## Overview
|
||||
|
||||
The Mailu email server has been migrated from a Kustomize-based deployment to a Helm chart-based deployment. This change provides better maintainability, easier upgrades, and standardized configuration management.
|
||||
|
||||
## Key Changes
|
||||
|
||||
### 1. Service Names
|
||||
- **Old**: `mailu-smtp`, `email-smtp`, `mailu-front`, `mailu-admin`, `mailu-imap`, `mailu-antispam`
|
||||
- **New**: `mailu-postfix`, `mailu-front`, `mailu-admin`, `mailu-dovecot`, `mailu-rspamd`
|
||||
|
||||
### 2. Configuration Method
|
||||
- **Old**: Individual YAML manifests with Kustomize overlays
|
||||
- **New**: Helm chart with values files for environment-specific configuration
|
||||
|
||||
### 3. Directory Structure
|
||||
- **Old**: `infrastructure/platform/mail/mailu/{base,overlays/{dev,prod}}`
|
||||
- **New**: `infrastructure/platform/mail/mailu-helm/{dev,prod}`
|
||||
|
||||
### 4. Ingress Configuration
|
||||
- **Old**: Ingress resources created as part of the Kustomize setup
|
||||
- **New**: Built-in ingress disabled in Helm chart to work with existing ingress controller
|
||||
|
||||
## Updated Service References
|
||||
|
||||
The following configurations have been updated to use the new Helm service names:
|
||||
|
||||
## Ingress Configuration
|
||||
|
||||
The Mailu Helm chart has been configured to work with your existing ingress setup:
|
||||
|
||||
- **ingress.enabled: false**: Disables the chart's built-in Ingress creation
|
||||
- **tlsFlavorOverride: notls**: Tells Mailu's internal NGINX not to enforce TLS, as your Ingress handles TLS termination
|
||||
- **realIpHeader: X-Forwarded-For**: Ensures Mailu's NGINX logs and processes the correct client IPs from behind your Ingress
|
||||
- **realIpFrom: 0.0.0.0/0**: Trusts all proxies (restrict to your Ingress pod CIDR for security)
|
||||
|
||||
### Required Ingress Resource
|
||||
|
||||
You need to create an Ingress resource to route traffic to Mailu. Here's an example:
|
||||
|
||||
```yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: mailu-ingress
|
||||
namespace: bakery-ia # Same as Mailu's namespace
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: nginx # Or your Ingress class
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "100m" # Allow larger email attachments
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" # For long connections
|
||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
|
||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true" # Redirect HTTP to HTTPS
|
||||
# If using Cert-Manager: cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
spec:
|
||||
tls:
|
||||
- hosts:
|
||||
- mail.bakery-ia.dev # or mail.bakewise.ai for prod
|
||||
secretName: mail-tls-secret # Your TLS Secret
|
||||
rules:
|
||||
- host: mail.bakery-ia.dev # or mail.bakewise.ai for prod
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: mailu-front-http # Mailu's front service (check with kubectl get svc -n bakery-ia)
|
||||
port:
|
||||
number: 80
|
||||
```
|
||||
|
||||
Apply it: `kubectl apply -f ingress.yaml`.
|
||||
|
||||
This routes all traffic from https://mail.[domain]/ to Mailu's internal NGINX, which proxies to webmail (/webmail), admin (/admin), etc.
|
||||
|
||||
## Updated Service References
|
||||
|
||||
The following configurations have been updated to use the new Helm service names:
|
||||
|
||||
### Common ConfigMap
|
||||
- `SMTP_HOST` changed from `email-smtp.bakery-ia.svc.cluster.local` to `mailu-postfix.bakery-ia.svc.cluster.local`
|
||||
|
||||
### SigNoz Configuration
|
||||
- `signoz_smtp_host` changed from `email-smtp.bakery-ia.svc.cluster.local` to `mailu-postfix.bakery-ia.svc.cluster.local`
|
||||
- `smtp_smarthost` changed from `email-smtp.bakery-ia.svc.cluster.local:587` to `mailu-postfix.bakery-ia.svc.cluster.local:587`
|
||||
|
||||
## Deployment Process
|
||||
|
||||
### Prerequisites
|
||||
1. Helm 3.x installed
|
||||
2. Access to Kubernetes cluster
|
||||
3. Namespace `bakery-ia` exists
|
||||
|
||||
### Deployment Commands
|
||||
|
||||
#### For Development:
|
||||
```bash
|
||||
# Add Mailu Helm repository
|
||||
helm repo add mailu https://mailu.github.io/helm-charts/
|
||||
helm repo update
|
||||
|
||||
# Install Mailu for development
|
||||
helm upgrade --install mailu-dev mailu/mailu \
|
||||
--namespace bakery-ia \
|
||||
--create-namespace \
|
||||
--values infrastructure/platform/mail/mailu-helm/values.yaml \
|
||||
--values infrastructure/platform/mail/mailu-helm/dev/values.yaml
|
||||
```
|
||||
|
||||
#### For Production:
|
||||
```bash
|
||||
# Add Mailu Helm repository
|
||||
helm repo add mailu https://mailu.github.io/helm-charts/
|
||||
helm repo update
|
||||
|
||||
# Install Mailu for production
|
||||
helm upgrade --install mailu-prod mailu/mailu \
|
||||
--namespace bakery-ia \
|
||||
--create-namespace \
|
||||
--values infrastructure/platform/mail/mailu-helm/values.yaml \
|
||||
--values infrastructure/platform/mail/mailu-helm/prod/values.yaml
|
||||
```
|
||||
|
||||
## Critical Configuration Preservation
|
||||
|
||||
All critical configurations from the original Kustomize setup have been preserved:
|
||||
|
||||
- Domain and hostname settings
|
||||
- External SMTP relay configuration (Mailgun)
|
||||
- Redis integration with shared cluster
|
||||
- Database connection settings
|
||||
- TLS certificate management
|
||||
- Resource limits and requests
|
||||
- Network policies
|
||||
- Storage configuration (10Gi PVC)
|
||||
|
||||
## Rollback Procedure
|
||||
|
||||
If rollback to the Kustomize setup is needed:
|
||||
|
||||
1. Uninstall the Helm release:
|
||||
```bash
|
||||
helm uninstall mailu-dev -n bakery-ia # or mailu-prod
|
||||
```
|
||||
|
||||
2. Revert the configuration changes in `infrastructure/environments/common/configs/configmap.yaml` and `infrastructure/monitoring/signoz/signoz-values-prod.yaml`
|
||||
|
||||
3. Deploy the old Kustomize manifests:
|
||||
```bash
|
||||
kubectl apply -k infrastructure/platform/mail/mailu/overlays/dev
|
||||
# or
|
||||
kubectl apply -k infrastructure/platform/mail/mailu/overlays/prod
|
||||
```
|
||||
|
||||
## Verification Steps
|
||||
|
||||
After deployment, verify the following:
|
||||
|
||||
1. Check that all Mailu pods are running:
|
||||
```bash
|
||||
kubectl get pods -n bakery-ia | grep mailu
|
||||
```
|
||||
|
||||
2. Verify SMTP connectivity from other services:
|
||||
```bash
|
||||
# Test from a pod in the same namespace
|
||||
kubectl run test-smtp --image=curlimages/curl -n bakery-ia --rm -it -- \
|
||||
nc -zv mailu-postfix.bakery-ia.svc.cluster.local 587
|
||||
```
|
||||
|
||||
3. Check that notification service can send emails:
|
||||
```bash
|
||||
kubectl logs -n bakery-ia deployment/notification-service | grep -i smtp
|
||||
```
|
||||
|
||||
4. Verify web interface accessibility:
|
||||
```bash
|
||||
kubectl port-forward -n bakery-ia svc/mailu-front 8080:80
|
||||
# Then visit http://localhost:8080/admin
|
||||
```
|
||||
|
||||
## Known Issues
|
||||
|
||||
1. During migration, existing email data should be backed up before uninstalling the old deployment
|
||||
2. DNS records may need to be updated to point to the new service endpoints
|
||||
3. Some custom configurations may need to be reapplied after Helm installation
|
||||
|
||||
## Support
|
||||
|
||||
For issues with the new Helm-based deployment:
|
||||
|
||||
1. Check the [official Mailu Helm chart documentation](https://github.com/Mailu/helm-charts)
|
||||
2. Review Helm release status: `helm status mailu-[dev|prod] -n bakery-ia`
|
||||
3. Check pod logs: `kubectl logs -n bakery-ia deployment/[mailu-postfix|mailu-front|etc.]`
|
||||
4. Verify network connectivity between services
|
||||
171
infrastructure/platform/mail/mailu-helm/README.md
Normal file
171
infrastructure/platform/mail/mailu-helm/README.md
Normal file
@@ -0,0 +1,171 @@
|
||||
# Mailu Helm Chart for Bakery-IA
|
||||
|
||||
This directory contains the Helm chart configuration for Mailu, replacing the previous Kustomize-based setup.
|
||||
|
||||
## Overview
|
||||
|
||||
The Mailu email server is now deployed using the official Mailu Helm chart instead of Kustomize manifests. This provides better maintainability, easier upgrades, and standardized configuration. The setup is configured to work behind your existing Ingress controller (NGINX), with the internal Mailu NGINX acting as a proxy for services like webmail while your existing Ingress handles traffic routing, TLS termination, and forwarding to Mailu's internal NGINX on HTTP (port 80).
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
mailu-helm/
|
||||
├── values.yaml # Base configuration values
|
||||
├── dev/
|
||||
│ └── values.yaml # Development-specific overrides
|
||||
├── prod/
|
||||
│ └── values.yaml # Production-specific overrides
|
||||
└── mailu-ingress.yaml # Sample ingress configuration for use with existing ingress
|
||||
```
|
||||
|
||||
## Critical Configuration Preservation
|
||||
|
||||
The following critical configurations from the original Kustomize setup have been preserved:
|
||||
|
||||
- **Domain settings**: Domain and hostnames for both dev and prod
|
||||
- **External relay**: Mailgun SMTP relay configuration
|
||||
- **Redis integration**: Connection to shared Redis cluster (database 15)
|
||||
- **Database settings**: PostgreSQL connection details
|
||||
- **Resource limits**: CPU and memory requests/limits matching original setup
|
||||
- **Network policies**: Security policies restricting access to authorized services
|
||||
- **Storage**: 10Gi persistent volume for mail data
|
||||
- **Ingress configuration**: Built-in ingress disabled to work with existing ingress
|
||||
|
||||
## Deployment
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. Helm 3.x installed
|
||||
2. Kubernetes cluster with storage provisioner
|
||||
3. Ingress controller (NGINX) - already deployed in your cluster
|
||||
4. Cert-manager for TLS certificates (optional, depends on your ingress setup)
|
||||
5. External SMTP relay account (Mailgun)
|
||||
|
||||
### Deployment Commands
|
||||
|
||||
#### For Development:
|
||||
```bash
|
||||
helm repo add mailu https://mailu.github.io/helm-charts/
|
||||
helm repo update
|
||||
helm install mailu-dev mailu/mailu \
|
||||
--namespace bakery-ia \
|
||||
--create-namespace \
|
||||
--values mailu-helm/values.yaml \
|
||||
--values mailu-helm/dev/values.yaml
|
||||
```
|
||||
|
||||
#### For Production:
|
||||
```bash
|
||||
helm repo add mailu https://mailu.github.io/helm-charts/
|
||||
helm repo update
|
||||
helm install mailu-prod mailu/mailu \
|
||||
--namespace bakery-ia \
|
||||
--create-namespace \
|
||||
--values mailu-helm/values.yaml \
|
||||
--values mailu-helm/prod/values.yaml
|
||||
```
|
||||
|
||||
### Upgrading
|
||||
|
||||
To upgrade to a newer version of the Mailu Helm chart:
|
||||
```bash
|
||||
helm repo update
|
||||
helm upgrade mailu-dev mailu/mailu \
|
||||
--namespace bakery-ia \
|
||||
--values mailu-helm/values.yaml \
|
||||
--values mailu-helm/dev/values.yaml
|
||||
```
|
||||
|
||||
## Ingress Configuration
|
||||
|
||||
The Mailu Helm chart is configured to work with your existing Ingress setup:
|
||||
|
||||
- **ingress.enabled: false**: Disables the chart's built-in Ingress creation
|
||||
- **tlsFlavorOverride: notls**: Tells Mailu's internal NGINX not to enforce TLS, as your Ingress handles TLS termination
|
||||
- **realIpHeader: X-Forwarded-For**: Ensures Mailu's NGINX logs and processes the correct client IPs from behind your Ingress
|
||||
- **realIpFrom: 0.0.0.0/0**: Trusts all proxies (restrict to your Ingress pod CIDR for security)
|
||||
|
||||
### Required Ingress Resource
|
||||
|
||||
You need to create an Ingress resource to route traffic to Mailu. Here's an example:
|
||||
|
||||
```yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: mailu-ingress
|
||||
namespace: bakery-ia # Same as Mailu's namespace
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: nginx # Or your Ingress class
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "100m" # Allow larger email attachments
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" # For long connections
|
||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
|
||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true" # Redirect HTTP to HTTPS
|
||||
# If using Cert-Manager: cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
spec:
|
||||
tls:
|
||||
- hosts:
|
||||
- mail.bakery-ia.dev # or mail.bakewise.ai for prod
|
||||
secretName: mail-tls-secret # Your TLS Secret
|
||||
rules:
|
||||
- host: mail.bakery-ia.dev # or mail.bakewise.ai for prod
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: mailu-front-http # Mailu's front service (check with kubectl get svc -n bakery-ia)
|
||||
port:
|
||||
number: 80
|
||||
```
|
||||
|
||||
Apply it: `kubectl apply -f ingress.yaml`.
|
||||
|
||||
This routes all traffic from https://mail.[domain]/ to Mailu's internal NGINX, which proxies to webmail (/webmail), admin (/admin), etc.
|
||||
|
||||
## Configuration Details
|
||||
|
||||
### Environment-Specific Values
|
||||
|
||||
- **Development** (`dev/values.yaml`):
|
||||
- Domain: `bakery-ia.local`
|
||||
- No TLS enforcement internally (handled by ingress)
|
||||
- Disabled antivirus to save resources
|
||||
- Debug logging level
|
||||
|
||||
- **Production** (`prod/values.yaml`):
|
||||
- Domain: `bakewise.ai`
|
||||
- No TLS enforcement internally (handled by ingress)
|
||||
- Enabled antivirus
|
||||
- Warning logging level
|
||||
|
||||
### Secrets Management
|
||||
|
||||
Sensitive values like passwords and API keys should be managed through Kubernetes secrets rather than being stored in the values files. The Helm chart supports referencing existing secrets for:
|
||||
|
||||
- Database passwords
|
||||
- Redis passwords
|
||||
- External relay credentials
|
||||
- Mailu secret key
|
||||
|
||||
## Integration with Notification Service
|
||||
|
||||
The notification service continues to connect to Mailu via the internal service name `mailu-postfix.bakery-ia.svc.cluster.local` on port 587 with STARTTLS.
|
||||
|
||||
## Access Information
|
||||
|
||||
- **Admin Panel**: `https://mail.[domain]/admin`
|
||||
- **Webmail**: `https://mail.[domain]/webmail`
|
||||
- **SMTP**: `mail.[domain]:587` (STARTTLS) - handled via separate TCP services if needed
|
||||
- **IMAP**: `mail.[domain]:993` (SSL/TLS) - handled via separate TCP services if needed
|
||||
|
||||
## Migration Notes
|
||||
|
||||
When migrating from the Kustomize setup to Helm:
|
||||
|
||||
1. Ensure all existing PVCs are preserved during migration
|
||||
2. Export any existing mail data before migration if needed
|
||||
3. Update any hardcoded service references in other deployments
|
||||
4. Verify that network policies still allow necessary communications
|
||||
5. Configure your existing ingress to route traffic to the Mailu services
|
||||
@@ -0,0 +1,38 @@
|
||||
# CoreDNS ConfigMap patch to forward external DNS queries to Unbound for DNSSEC validation
|
||||
# This is required for Mailu Admin which requires DNSSEC-validating DNS resolver
|
||||
#
|
||||
# Apply with: kubectl apply -f coredns-unbound-patch.yaml
|
||||
# Then restart CoreDNS: kubectl rollout restart deployment coredns -n kube-system
|
||||
#
|
||||
# Note: The Unbound service IP (10.104.127.213) may change when the cluster is recreated.
|
||||
# The setup script will automatically update this based on the actual Unbound service IP.
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: coredns
|
||||
namespace: kube-system
|
||||
data:
|
||||
Corefile: |
|
||||
.:53 {
|
||||
errors
|
||||
health {
|
||||
lameduck 5s
|
||||
}
|
||||
ready
|
||||
kubernetes cluster.local in-addr.arpa ip6.arpa {
|
||||
pods insecure
|
||||
fallthrough in-addr.arpa ip6.arpa
|
||||
ttl 30
|
||||
}
|
||||
prometheus :9153
|
||||
forward . UNBOUND_SERVICE_IP {
|
||||
max_concurrent 1000
|
||||
}
|
||||
cache 30 {
|
||||
disable success cluster.local
|
||||
disable denial cluster.local
|
||||
}
|
||||
loop
|
||||
reload
|
||||
loadbalance
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
# Mailgun SMTP Credentials Secret for Mailu
|
||||
#
|
||||
# This secret stores Mailgun credentials for outbound email relay.
|
||||
# Mailu uses Mailgun as an external SMTP relay to send all outbound emails.
|
||||
#
|
||||
# ============================================================================
|
||||
# HOW TO CONFIGURE:
|
||||
# ============================================================================
|
||||
#
|
||||
# 1. Go to https://www.mailgun.com and create an account
|
||||
#
|
||||
# 2. Add and verify your domain:
|
||||
# - For dev: bakery-ia.dev
|
||||
# - For prod: bakewise.ai
|
||||
#
|
||||
# 3. Go to Domain Settings > SMTP credentials in Mailgun dashboard
|
||||
#
|
||||
# 4. Note your SMTP credentials:
|
||||
# - SMTP hostname: smtp.mailgun.org
|
||||
# - Port: 587 (TLS/STARTTLS)
|
||||
# - Username: typically postmaster@yourdomain.com
|
||||
# - Password: your Mailgun SMTP password (NOT the API key)
|
||||
#
|
||||
# 5. Base64 encode your credentials:
|
||||
# echo -n 'postmaster@bakewise.ai' | base64
|
||||
# echo -n 'your-mailgun-smtp-password' | base64
|
||||
#
|
||||
# 6. Replace the placeholder values below with your encoded credentials
|
||||
#
|
||||
# 7. Apply this secret:
|
||||
# kubectl apply -f mailgun-credentials-secret.yaml -n bakery-ia
|
||||
#
|
||||
# ============================================================================
|
||||
# IMPORTANT NOTES:
|
||||
# ============================================================================
|
||||
#
|
||||
# - Use the SMTP password from Mailgun, NOT the API key
|
||||
# - The username format is: postmaster@yourdomain.com
|
||||
# - For sandbox domains, Mailgun requires adding authorized recipients
|
||||
# - Production domains need DNS verification (SPF, DKIM records)
|
||||
#
|
||||
# ============================================================================
|
||||
# DNS RECORDS REQUIRED FOR MAILGUN:
|
||||
# ============================================================================
|
||||
#
|
||||
# Add these DNS records to your domain for proper email delivery:
|
||||
#
|
||||
# 1. SPF Record (TXT):
|
||||
# Name: @
|
||||
# Value: v=spf1 include:mailgun.org ~all
|
||||
#
|
||||
# 2. DKIM Records (TXT):
|
||||
# Mailgun will provide two DKIM keys to add as TXT records
|
||||
# (check your Mailgun domain settings for exact values)
|
||||
#
|
||||
# 3. MX Records (optional, only if receiving via Mailgun):
|
||||
# Priority 10: mxa.mailgun.org
|
||||
# Priority 10: mxb.mailgun.org
|
||||
#
|
||||
# ============================================================================
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: mailu-mailgun-credentials
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app: mailu
|
||||
component: external-relay
|
||||
annotations:
|
||||
description: "Mailgun SMTP credentials for Mailu external relay"
|
||||
type: Opaque
|
||||
stringData:
|
||||
# ============================================================================
|
||||
# REPLACE THESE VALUES WITH YOUR MAILGUN CREDENTIALS
|
||||
# ============================================================================
|
||||
#
|
||||
# Option 1: Use stringData (plain text - Kubernetes will encode automatically)
|
||||
# This is easier for initial setup but shows credentials in the file
|
||||
#
|
||||
RELAY_USERNAME: "postmaster@sandboxc1bff891532b4f0c83056a68ae080b4c.mailgun.org"
|
||||
RELAY_PASSWORD: "2e47104abadad8eb820d00042ea6d5eb-77c6c375-89c7ea55"
|
||||
#
|
||||
# ============================================================================
|
||||
# ALTERNATIVE: Use pre-encoded values (more secure for version control)
|
||||
# ============================================================================
|
||||
# Comment out stringData above and uncomment data below:
|
||||
#
|
||||
# data:
|
||||
# # Base64 encoded values
|
||||
# # echo -n 'postmaster@bakewise.ai' | base64
|
||||
# RELAY_USERNAME: cG9zdG1hc3RlckBiYWtld2lzZS5haQ==
|
||||
# # echo -n 'your-password' | base64
|
||||
# RELAY_PASSWORD: WU9VUl9NQUlMR1VOX1NNVFBfUEFTU1dPUkQ=
|
||||
@@ -0,0 +1,34 @@
|
||||
# Mailu Admin Credentials Secret
|
||||
# This secret stores the initial admin account password for Mailu
|
||||
#
|
||||
# The password is used by the Helm chart's initialAccount feature to create
|
||||
# the admin user automatically during deployment.
|
||||
#
|
||||
# IMPORTANT: Replace the base64-encoded password before applying!
|
||||
#
|
||||
# To generate a secure password and encode it:
|
||||
# PASSWORD=$(openssl rand -base64 16 | tr -d '/+=' | head -c 16)
|
||||
# echo -n "$PASSWORD" | base64
|
||||
#
|
||||
# To apply this secret:
|
||||
# kubectl apply -f mailu-admin-credentials-secret.yaml -n bakery-ia
|
||||
#
|
||||
# After deployment, you can log in to the Mailu admin panel at:
|
||||
# https://mail.<domain>/admin
|
||||
# Username: admin@<domain>
|
||||
# Password: <the password you set>
|
||||
#
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: mailu-admin-credentials
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app.kubernetes.io/name: mailu
|
||||
app.kubernetes.io/component: admin
|
||||
type: Opaque
|
||||
data:
|
||||
# Base64-encoded password
|
||||
# Example: "changeme123" = Y2hhbmdlbWUxMjM=
|
||||
# IMPORTANT: Replace with your own secure password!
|
||||
password: "Y2hhbmdlbWUxMjM="
|
||||
@@ -0,0 +1,26 @@
|
||||
# Self-signed TLS certificate secret for Mailu Front
|
||||
# This is required by the Mailu Helm chart even when TLS is disabled (tls.flavor: notls)
|
||||
# The Front pod mounts this secret for internal certificate handling
|
||||
#
|
||||
# For production, replace with proper certificates from cert-manager or Let's Encrypt
|
||||
# This script generates a self-signed certificate valid for 365 days
|
||||
#
|
||||
# To regenerate manually:
|
||||
# openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
|
||||
# -keyout tls.key -out tls.crt \
|
||||
# -subj "/CN=mail.bakery-ia.dev/O=bakery-ia"
|
||||
# kubectl create secret tls mailu-certificates \
|
||||
# --cert=tls.crt --key=tls.key -n bakery-ia
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: mailu-certificates
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app.kubernetes.io/name: mailu
|
||||
app.kubernetes.io/component: certificates
|
||||
type: kubernetes.io/tls
|
||||
data:
|
||||
# Generated certificate for mail.bakery-ia.dev
|
||||
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURRekNDQWl1Z0F3SUJBZ0lVVWg1Rlg5cWlPRDdkc2FmVi9KemlKWWh1WUZJd0RRWUpLb1pJaHZjTkFRRUwKQlFBd01URWJNQmtHQTFVRUF3d1NiV0ZwYkM1aVlXdGxjbmt0YVdFdVpHVjJNUkl3RUFZRFZRUUtEQWxDWVd0bApjbmtnU1VFd0hoY05Nall3TVRFNU1qQTBOakkwV2hjTk1qY3dNVEU1TWpBME5qSTBXakF4TVJzd0dRWURWUVFECkRCSnRZV2xzTG1KaGEyVnllUzFwWVM1a1pYWXhFakFRQmdOVkJBb01DVUpoYTJWeWVTQkpRVENDQVNJd0RRWUoKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTDJlbXM2YW5DSjV5N0JQNm9KdTQ2TldQSXJ3Zlg3Mgp3WmgxZERJaVlIMmNsalBESldsb3ROU0JFTngxUkZZSEc3Z0VSRVk1MHpFQ3UwSC9Vc0YzRFlPTFhobkYwdVRXCkNSTmJFRjFoYjZNT2lqanVmOWJHKzdsVkJ5NmZkMXZRTzJpOTA1VktxRTdEZllraWIwVkpxN0duVUo5RWFtOFgKSWxTaUphY1F6Mm11WXd6QjBPN3hZeVV3VFFWTDcvSnRNTWs5ZjZDY1ZENXFRMGJuWEJNM2hqcVVGWTlnbEF5dApZZHBUUUhPdms1WXgrZk1nL2JZVlBjQ0VhZFhVVkhBdHoxYlJybGIwenlMc3FXeHd2OXlWN0pCM210TkNmbFdsCkRCWWRIb3J0ZlROTHVSNFhhRTNXT2pnbzkwT1ltbi9PYll6Mld0SXUwMnp5MkhrTnBNYUFvVmtDQXdFQUFhTlQKTUZFd0hRWURWUjBPQkJZRUZMS2hPc254WnpXQ1RyMFFuSTdjaE1hbWtTb2pNQjhHQTFVZEl3UVlNQmFBRkxLaApPc254WnpXQ1RyMFFuSTdjaE1hbWtTb2pNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdEUVlKS29aSWh2Y05BUUVCCkJRQURnZ0VCQUFMQ3hGV1VnY3Z3ZVpoRjFHdlNnR3R3VW9WakJtcG1GYnFPMC93S2lqMlhDRmZ6L0FqanZaOHMKOGVIUEc5Z3crbjlpaGNSN016Q2V5ZldRd1FsaTBXZkcySzBvUDFGeUxoYU9aMlhtdU9nNnhNRG5EVzBVZWtqMwpCYWdHc3RFVXpqQlR1UlJ3WS9uck5vb1ZCOVFoYnhoeW9mbXkrVzVmczhZMDNTZG9paTFpWG1iSEhaemMyL21ICmF2UDE0Z3BzWUNDZVl6aklyWm05WWE4Rzhpc2tYelNnZU0vSEhpRzhJOWhKRkJYaHRYYWRjeGkvbU5hNHRKcWgKM1crTEIzaEQ4NFVkZ3MrR3pCZ0hHdnIwdWxMMTQvaUxVRXFySXZaWjN2VTlvNlZ4MlBvRjQ3cjBQNXpOZXVTNwpkRk5xT3JJT2phSm5yMXFVb0tMeWd3RUhqdVRNbUk0PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
|
||||
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQzlucHJPbXB3aWVjdXcKVCtxQ2J1T2pWanlLOEgxKzlzR1lkWFF5SW1COW5KWXp3eVZwYUxUVWdSRGNkVVJXQnh1NEJFUkdPZE14QXJ0QgovMUxCZHcyRGkxNFp4ZExrMWdrVFd4QmRZVytqRG9vNDduL1d4dnU1VlFjdW4zZGIwRHRvdmRPVlNxaE93MzJKCkltOUZTYXV4cDFDZlJHcHZGeUpVb2lXbkVNOXBybU1Nd2REdThXTWxNRTBGUysveWJUREpQWCtnbkZRK2FrTkcKNTF3VE40WTZsQldQWUpRTXJXSGFVMEJ6cjVPV01mbnpJUDIyRlQzQWhHblYxRlJ3TGM5VzBhNVc5TThpN0tscwpjTC9jbGV5UWQ1clRRbjVWcFF3V0hSNks3WDB6UzdrZUYyaE4xam80S1BkRG1KcC96bTJNOWxyU0x0TnM4dGg1CkRhVEdnS0ZaQWdNQkFBRUNnZ0VBSW51TFQzTVNYYnFrYmdXNmNjblVuOGw0N1JOYTN4SGtsdU1WSkdEWUJ6L0kKbU5VdUlvTW1EMWNCUi9ZVFhVbWhvczh6MDBtRXZHN3d1c25CdE9qL2ppSjBGRi9EUUZZa0JGOFZGTVk1VlArNQo1eXlJRnZqTW9pRnlVdW93L0lOYnFtcUs1YVZVQWk3T3ozZHhvTG9LL1IyZUxiaDFXb3BzZGRPZTRValBUenBVCnU1TVl4NXlMVnVZc1A3U09TSHRrd2UvMDN5RFJLckl2V3k1QlBtYzJRVEhUcEJPVUJHNC9DcFJWR1ozZjhLa0QKN2QrNlZlNzd1TWV1eERPOG1HZ1paNTRpd0NuMStYR2NFcVFVR1Z1WngrcVpodVhTZks0ajR3eWVtbndlRUFCdgptTlNZSXQ2OG91SSs0cEFyV1ZONEFjaXhWRUxIV1d6MDRYTm56WFUyNFFLQmdRRDBlc0JZenVkRzJaU2t5SWJRCnU4SXhwT2RzRjRnU1lpekNkMTNLQktGbm9obTFrVzlYemRmS3ZObnJxcFRPRnJIYkRXUTdpaUhKM2NqVjlBVTUKTlEwMVUzWXY0SzhkdWtnb2MvRUFhbnQvRjhvMG5qc0pJZ2Z2WTFuUHNPVFVFcGtRQk1QSGpraGpyM3FBNkh4dgp4b0I2OEdVdU1OVHRkQitBV0Y0dXR1T2JoUUtCZ1FER2pnNmJnbGpXRVR4TnhBalQxc21reEpzQ2xmbFFteXRmCmNiaDVWempzdGNad2lLSjh1b0xjT0d4b05HWDJIaGJRQU5wRWhUR3FuMEZIbGxFc1BYbXBoeUJVY01JUFZTWEkKRUlLeU9kL3ZMYjhjWG9ydDZMaDNNS0FoakVLbExENVZOcDhXbVlQM3dCVE1ia3BrM0NDdWxDSEJLcEJXV2Y2NgpQWFp0RUZKa3hRS0JnQjNSTHM1bUJhME5jbVNhbEY2MjE1dG9hbFV6bFlQd2QxY01hZUx1cDZUVkQxK21xamJDClF6UlZ6aHBCQnI4UDQ0YzgzZUdwR2kvZG5kWUNXZlM5Tkt3eFRyUE9LbTFzdjhvM1FjaDBORFd1K0Jsc3h2UjUKTXhDT1JIRGhPVGRvUVVURDRBRGhxSkNINFdBQmV0UERHUDVsZldHaDBRWlk2RktsOUc2c0haeGxBb0dBWnlLLwpIN1B6WlM2S3ZuSkhpNUlVSjh3Z0lKVzZiVTVNbDBWQTUzYVJFUlBTd2YyWE9XYkFOcGZ3WjZoZ0ZobkhDOENGCm4vWDN1SU1FcTZTL0FWWGxibFBNVFZCTTNSNERoQXBmZVNocTA1aFZudXpWQ1lOSzNrNlp2eE5XUXVuYWJ2VHkKYWhEUDVjOFdmcUlEYnFTUkxWMndzdC9qSFplZG95dnQ2ZlVDZDJrQ2dZRUFsbzRZelRabC8vays0WGlpeHVMQQpnZ2ZieTBoS3M1QWlLcFY0Q3pVZVE1Y0tZT2k5SXpvQzJMckxTWCtVckgvd0w3MGdCRzZneUNSZ1dLaW1RbmFWCnRZTy8xM1NyUFVnbm51R2o2Q0I1YUVreXYyTGFPVmV2WEZFcmlFbWQ1cWJKSXJYMENmZ1FuRnI2dm5RZDRwUFMKOGRVMkdhaDRiNVdNSjVJdzgwU3BjR0k9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
|
||||
171
infrastructure/platform/mail/mailu-helm/dev/values.yaml
Normal file
171
infrastructure/platform/mail/mailu-helm/dev/values.yaml
Normal file
@@ -0,0 +1,171 @@
|
||||
# Development-tuned Mailu configuration
|
||||
global:
|
||||
# Using Unbound DNS for DNSSEC validation (required by Mailu admin)
|
||||
# Unbound service is available at unbound-dns.bakery-ia.svc.cluster.local
|
||||
# Static ClusterIP configured in unbound-helm/values.yaml
|
||||
custom_dns_servers: "10.96.53.53" # Unbound DNS static ClusterIP
|
||||
|
||||
# Redis configuration - use built-in Mailu Redis (no authentication needed)
|
||||
externalRedis:
|
||||
enabled: false
|
||||
|
||||
# Component-specific DNS configuration
|
||||
# Admin requires DNSSEC validation - use Unbound DNS (forwards cluster.local to kube-dns)
|
||||
admin:
|
||||
dnsPolicy: "None"
|
||||
dnsConfig:
|
||||
nameservers:
|
||||
- "10.96.53.53" # Unbound DNS static ClusterIP (forwards cluster.local to kube-dns)
|
||||
searches:
|
||||
- "bakery-ia.svc.cluster.local"
|
||||
- "svc.cluster.local"
|
||||
- "cluster.local"
|
||||
options:
|
||||
- name: ndots
|
||||
value: "5"
|
||||
|
||||
# RSPAMD needs Unbound for DNSSEC validation (DKIM/SPF/DMARC checks)
|
||||
# Using ClusterFirst with search domains + Kubernetes DNS which can forward to Unbound
|
||||
rspamd:
|
||||
dnsPolicy: "ClusterFirst"
|
||||
|
||||
# Domain configuration for dev
|
||||
# NOTE: Using .dev TLD instead of .local because email-validator library
|
||||
# rejects .local domains as "special-use or reserved names" (RFC 6761)
|
||||
domain: "bakery-ia.dev"
|
||||
hostnames:
|
||||
- "mail.bakery-ia.dev"
|
||||
|
||||
# Initial admin account for dev environment
|
||||
# Password is stored in mailu-admin-credentials secret
|
||||
initialAccount:
|
||||
enabled: true
|
||||
username: "admin"
|
||||
domain: "bakery-ia.dev"
|
||||
existingSecret: "mailu-admin-credentials"
|
||||
existingSecretPasswordKey: "password"
|
||||
mode: "ifmissing"
|
||||
|
||||
# External relay configuration for dev (Mailgun)
|
||||
# All outbound emails will be relayed through Mailgun SMTP
|
||||
# To configure:
|
||||
# 1. Register at mailgun.com and verify your domain (bakery-ia.dev)
|
||||
# 2. Get your SMTP credentials from Mailgun dashboard
|
||||
# 3. Update the secret in configs/mailgun-credentials-secret.yaml
|
||||
# 4. Apply the secret: kubectl apply -f configs/mailgun-credentials-secret.yaml -n bakery-ia
|
||||
externalRelay:
|
||||
host: "[smtp.mailgun.org]:587"
|
||||
# Credentials loaded from Kubernetes secret
|
||||
secretName: "mailu-mailgun-credentials"
|
||||
usernameKey: "RELAY_USERNAME"
|
||||
passwordKey: "RELAY_PASSWORD"
|
||||
|
||||
# Environment-specific configurations
|
||||
persistence:
|
||||
enabled: true
|
||||
# Development: use default storage class
|
||||
storageClass: "standard"
|
||||
size: "5Gi"
|
||||
|
||||
# Resource optimizations for development
|
||||
resources:
|
||||
admin:
|
||||
requests:
|
||||
cpu: "100m"
|
||||
memory: "128Mi"
|
||||
limits:
|
||||
cpu: "500m"
|
||||
memory: "256Mi"
|
||||
front:
|
||||
requests:
|
||||
cpu: "50m"
|
||||
memory: "64Mi"
|
||||
limits:
|
||||
cpu: "200m"
|
||||
memory: "128Mi"
|
||||
postfix:
|
||||
requests:
|
||||
cpu: "100m"
|
||||
memory: "128Mi"
|
||||
limits:
|
||||
cpu: "300m"
|
||||
memory: "256Mi"
|
||||
dovecot:
|
||||
requests:
|
||||
cpu: "100m"
|
||||
memory: "128Mi"
|
||||
limits:
|
||||
cpu: "300m"
|
||||
memory: "256Mi"
|
||||
rspamd:
|
||||
requests:
|
||||
cpu: "50m"
|
||||
memory: "64Mi"
|
||||
limits:
|
||||
cpu: "200m"
|
||||
memory: "128Mi"
|
||||
webmail:
|
||||
requests:
|
||||
cpu: "50m"
|
||||
memory: "64Mi"
|
||||
limits:
|
||||
cpu: "200m"
|
||||
memory: "128Mi"
|
||||
clamav:
|
||||
requests:
|
||||
cpu: "100m"
|
||||
memory: "256Mi"
|
||||
limits:
|
||||
cpu: "300m"
|
||||
memory: "512Mi"
|
||||
|
||||
replicaCount: 1 # Single replica for development
|
||||
|
||||
# Security settings
|
||||
secretKey: "generate-strong-key-here-for-development"
|
||||
|
||||
# Ingress configuration for development - disabled to use with existing ingress
|
||||
ingress:
|
||||
enabled: false # Disable chart's Ingress; use existing one
|
||||
tls: false # Disable TLS in chart since ingress handles it
|
||||
tlsFlavorOverride: notls # No TLS on internal NGINX; expect external proxy to handle TLS
|
||||
realIpHeader: X-Forwarded-For # Header for client IP from your Ingress
|
||||
realIpFrom: 0.0.0.0/0 # Trust all proxies (restrict to your Ingress pod CIDR for security)
|
||||
path: /
|
||||
pathType: ImplementationSpecific
|
||||
|
||||
# TLS flavor for dev (may use self-signed)
|
||||
tls:
|
||||
flavor: "notls" # Disable TLS for development
|
||||
|
||||
# Welcome message (disabled in dev)
|
||||
welcomeMessage:
|
||||
enabled: false
|
||||
|
||||
# Log level for dev
|
||||
logLevel: "DEBUG"
|
||||
|
||||
# Development-specific overrides
|
||||
env:
|
||||
DEBUG: "true"
|
||||
LOG_LEVEL: "INFO"
|
||||
|
||||
# Disable or simplify monitoring in development
|
||||
monitoring:
|
||||
enabled: false
|
||||
|
||||
# Network Policy for dev
|
||||
networkPolicy:
|
||||
enabled: true
|
||||
ingressController:
|
||||
namespace: ingress-nginx
|
||||
podSelector: |
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: ingress-nginx
|
||||
app.kubernetes.io/instance: ingress-nginx
|
||||
app.kubernetes.io/component: controller
|
||||
monitoring:
|
||||
namespace: monitoring
|
||||
podSelector: |
|
||||
matchLabels:
|
||||
app: signoz-prometheus
|
||||
31
infrastructure/platform/mail/mailu-helm/mailu-ingress.yaml
Normal file
31
infrastructure/platform/mail/mailu-helm/mailu-ingress.yaml
Normal file
@@ -0,0 +1,31 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: mailu-ingress
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app.kubernetes.io/name: mailu
|
||||
app.kubernetes.io/component: ingress
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
|
||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
|
||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
tls:
|
||||
- hosts:
|
||||
- mail.bakery-ia.dev
|
||||
secretName: bakery-dev-tls-cert
|
||||
rules:
|
||||
- host: mail.bakery-ia.dev
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: mailu-front # Helm release name 'mailu' + component 'front'
|
||||
port:
|
||||
number: 80
|
||||
164
infrastructure/platform/mail/mailu-helm/prod/values.yaml
Normal file
164
infrastructure/platform/mail/mailu-helm/prod/values.yaml
Normal file
@@ -0,0 +1,164 @@
|
||||
# Production-tuned Mailu configuration
|
||||
global:
|
||||
# Using Kubernetes cluster DNS for name resolution
|
||||
custom_dns_servers: "10.96.0.10" # Kubernetes cluster DNS IP
|
||||
|
||||
# Redis configuration - use built-in Mailu Redis (no authentication needed for internal)
|
||||
externalRedis:
|
||||
enabled: false
|
||||
|
||||
# DNS configuration for production
|
||||
# Use Kubernetes DNS (ClusterFirst) which forwards to Unbound via CoreDNS
|
||||
# This is configured automatically by the mailu-helm Tilt resource
|
||||
admin:
|
||||
dnsPolicy: "ClusterFirst"
|
||||
|
||||
rspamd:
|
||||
dnsPolicy: "ClusterFirst"
|
||||
|
||||
# Domain configuration for production
|
||||
domain: "bakewise.ai"
|
||||
hostnames:
|
||||
- "mail.bakewise.ai"
|
||||
|
||||
# Initial admin account for production environment
|
||||
# Password is stored in mailu-admin-credentials secret
|
||||
initialAccount:
|
||||
enabled: true
|
||||
username: "admin"
|
||||
domain: "bakewise.ai"
|
||||
existingSecret: "mailu-admin-credentials"
|
||||
existingSecretPasswordKey: "password"
|
||||
mode: "ifmissing"
|
||||
|
||||
# External relay configuration for production (Mailgun)
|
||||
# All outbound emails will be relayed through Mailgun SMTP
|
||||
# To configure:
|
||||
# 1. Register at mailgun.com and verify your domain (bakewise.ai)
|
||||
# 2. Get your SMTP credentials from Mailgun dashboard
|
||||
# 3. Update the secret in configs/mailgun-credentials-secret.yaml
|
||||
# 4. Apply the secret: kubectl apply -f configs/mailgun-credentials-secret.yaml -n bakery-ia
|
||||
externalRelay:
|
||||
host: "[smtp.mailgun.org]:587"
|
||||
# Credentials loaded from Kubernetes secret
|
||||
secretName: "mailu-mailgun-credentials"
|
||||
usernameKey: "RELAY_USERNAME"
|
||||
passwordKey: "RELAY_PASSWORD"
|
||||
|
||||
# Environment-specific configurations
|
||||
persistence:
|
||||
enabled: true
|
||||
# Production: use microk8s-hostpath or longhorn
|
||||
storageClass: "longhorn" # Assuming Longhorn is available in production
|
||||
size: "20Gi" # Larger storage for production email volume
|
||||
|
||||
# Resource allocations for production
|
||||
resources:
|
||||
admin:
|
||||
requests:
|
||||
cpu: "200m"
|
||||
memory: "256Mi"
|
||||
limits:
|
||||
cpu: "1"
|
||||
memory: "512Mi"
|
||||
front:
|
||||
requests:
|
||||
cpu: "100m"
|
||||
memory: "128Mi"
|
||||
limits:
|
||||
cpu: "500m"
|
||||
memory: "256Mi"
|
||||
postfix:
|
||||
requests:
|
||||
cpu: "200m"
|
||||
memory: "256Mi"
|
||||
limits:
|
||||
cpu: "1"
|
||||
memory: "512Mi"
|
||||
dovecot:
|
||||
requests:
|
||||
cpu: "200m"
|
||||
memory: "256Mi"
|
||||
limits:
|
||||
cpu: "1"
|
||||
memory: "512Mi"
|
||||
rspamd:
|
||||
requests:
|
||||
cpu: "100m"
|
||||
memory: "128Mi"
|
||||
limits:
|
||||
cpu: "500m"
|
||||
memory: "256Mi"
|
||||
clamav:
|
||||
requests:
|
||||
cpu: "200m"
|
||||
memory: "512Mi"
|
||||
limits:
|
||||
cpu: "1"
|
||||
memory: "1Gi"
|
||||
|
||||
replicaCount: 1 # Can be increased in production as needed
|
||||
|
||||
# Security settings
|
||||
secretKey: "generate-strong-key-here-for-production"
|
||||
|
||||
# Ingress configuration for production - disabled to use with existing ingress
|
||||
ingress:
|
||||
enabled: false # Disable chart's Ingress; use existing one
|
||||
tls: false # Disable TLS in chart since ingress handles it
|
||||
tlsFlavorOverride: notls # No TLS on internal NGINX; expect external proxy to handle TLS
|
||||
realIpHeader: X-Forwarded-For # Header for client IP from your Ingress
|
||||
realIpFrom: 0.0.0.0/0 # Trust all proxies (restrict to your Ingress pod CIDR for security)
|
||||
path: /
|
||||
pathType: ImplementationSpecific
|
||||
|
||||
# TLS flavor for production (uses Let's Encrypt)
|
||||
tls:
|
||||
flavor: "cert"
|
||||
|
||||
# Welcome message (enabled in production)
|
||||
welcomeMessage:
|
||||
enabled: true
|
||||
subject: "Welcome to Bakewise.ai Email Service"
|
||||
body: "Welcome to our email service. Please change your password and update your profile."
|
||||
|
||||
# Log level for production
|
||||
logLevel: "WARNING"
|
||||
|
||||
# Enable antivirus in production
|
||||
antivirus:
|
||||
enabled: true
|
||||
flavor: "clamav"
|
||||
|
||||
# Production-specific settings
|
||||
env:
|
||||
DEBUG: "false"
|
||||
LOG_LEVEL: "WARNING"
|
||||
TLS_FLAVOR: "cert"
|
||||
REDIS_PASSWORD: "secure-redis-password"
|
||||
|
||||
# Enable monitoring in production
|
||||
monitoring:
|
||||
enabled: true
|
||||
|
||||
# Production-specific security settings
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
fsGroup: 1000
|
||||
|
||||
# Network policies for production
|
||||
networkPolicy:
|
||||
enabled: true
|
||||
ingressController:
|
||||
namespace: ingress-nginx
|
||||
podSelector: |
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: ingress-nginx
|
||||
app.kubernetes.io/instance: ingress-nginx
|
||||
app.kubernetes.io/component: controller
|
||||
monitoring:
|
||||
namespace: monitoring
|
||||
podSelector: |
|
||||
matchLabels:
|
||||
app: signoz-prometheus
|
||||
269
infrastructure/platform/mail/mailu-helm/scripts/deploy-mailu-prod.sh
Executable file
269
infrastructure/platform/mail/mailu-helm/scripts/deploy-mailu-prod.sh
Executable file
@@ -0,0 +1,269 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# Mailu Production Deployment Script
|
||||
# =============================================================================
|
||||
# This script automates the deployment of Mailu mail server for production.
|
||||
# It handles:
|
||||
# 1. Unbound DNS deployment (for DNSSEC validation)
|
||||
# 2. CoreDNS configuration (forward to Unbound)
|
||||
# 3. TLS certificate secret creation
|
||||
# 4. Admin credentials secret creation
|
||||
# 5. Mailu Helm deployment (admin user created automatically via initialAccount)
|
||||
#
|
||||
# Usage:
|
||||
# ./deploy-mailu-prod.sh [--domain DOMAIN] [--admin-password PASSWORD]
|
||||
#
|
||||
# Example:
|
||||
# ./deploy-mailu-prod.sh --domain bakewise.ai --admin-password 'SecurePass123!'
|
||||
# =============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Default values
|
||||
DOMAIN="${DOMAIN:-bakewise.ai}"
|
||||
ADMIN_PASSWORD="${ADMIN_PASSWORD:-}"
|
||||
NAMESPACE="bakery-ia"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
MAILU_HELM_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--domain)
|
||||
DOMAIN="$2"
|
||||
shift 2
|
||||
;;
|
||||
--admin-password)
|
||||
ADMIN_PASSWORD="$2"
|
||||
shift 2
|
||||
;;
|
||||
--help)
|
||||
echo "Usage: $0 [--domain DOMAIN] [--admin-password PASSWORD]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --domain Domain for Mailu (default: bakewise.ai)"
|
||||
echo " --admin-password Password for admin@DOMAIN user"
|
||||
echo ""
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo -e "${RED}Unknown option: $1${NC}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
print_step() {
|
||||
echo -e "\n${BLUE}==>${NC} ${GREEN}$1${NC}"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}WARNING:${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}ERROR:${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}✓${NC} $1"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Step 0: Prerequisites Check
|
||||
# =============================================================================
|
||||
print_step "Step 0: Checking prerequisites..."
|
||||
|
||||
if ! command -v kubectl &> /dev/null; then
|
||||
print_error "kubectl not found. Please install kubectl."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v helm &> /dev/null; then
|
||||
print_error "helm not found. Please install helm."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! kubectl get namespace "$NAMESPACE" &>/dev/null; then
|
||||
print_warning "Namespace $NAMESPACE does not exist. Creating..."
|
||||
kubectl create namespace "$NAMESPACE"
|
||||
fi
|
||||
|
||||
print_success "Prerequisites check passed"
|
||||
|
||||
# =============================================================================
|
||||
# Step 1: Deploy Unbound DNS Resolver
|
||||
# =============================================================================
|
||||
print_step "Step 1: Deploying Unbound DNS resolver..."
|
||||
|
||||
if kubectl get deployment unbound -n "$NAMESPACE" &>/dev/null; then
|
||||
print_success "Unbound already deployed"
|
||||
else
|
||||
helm upgrade --install unbound "$MAILU_HELM_DIR/../../networking/dns/unbound-helm" \
|
||||
-n "$NAMESPACE" \
|
||||
-f "$MAILU_HELM_DIR/../../networking/dns/unbound-helm/values.yaml" \
|
||||
-f "$MAILU_HELM_DIR/../../networking/dns/unbound-helm/prod/values.yaml" \
|
||||
--timeout 5m \
|
||||
--wait
|
||||
|
||||
print_success "Unbound deployed"
|
||||
fi
|
||||
|
||||
# Wait for Unbound to be ready
|
||||
kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=unbound -n "$NAMESPACE" --timeout=120s
|
||||
|
||||
# Get Unbound service IP
|
||||
UNBOUND_IP=$(kubectl get svc unbound-dns -n "$NAMESPACE" -o jsonpath='{.spec.clusterIP}')
|
||||
echo "Unbound DNS service IP: $UNBOUND_IP"
|
||||
|
||||
# =============================================================================
|
||||
# Step 2: Configure CoreDNS to Forward to Unbound
|
||||
# =============================================================================
|
||||
print_step "Step 2: Configuring CoreDNS for DNSSEC validation..."
|
||||
|
||||
# Check current CoreDNS forward configuration
|
||||
CURRENT_FORWARD=$(kubectl get configmap coredns -n kube-system -o jsonpath='{.data.Corefile}' | grep -o 'forward \. [0-9.]*' | awk '{print $3}' || echo "")
|
||||
|
||||
if [ "$CURRENT_FORWARD" != "$UNBOUND_IP" ]; then
|
||||
echo "Updating CoreDNS to forward to Unbound ($UNBOUND_IP)..."
|
||||
|
||||
kubectl patch configmap coredns -n kube-system --type merge -p "{
|
||||
\"data\": {
|
||||
\"Corefile\": \".:53 {\\n errors\\n health {\\n lameduck 5s\\n }\\n ready\\n kubernetes cluster.local in-addr.arpa ip6.arpa {\\n pods insecure\\n fallthrough in-addr.arpa ip6.arpa\\n ttl 30\\n }\\n prometheus :9153\\n forward . $UNBOUND_IP {\\n max_concurrent 1000\\n }\\n cache 30 {\\n disable success cluster.local\\n disable denial cluster.local\\n }\\n loop\\n reload\\n loadbalance\\n}\\n\"
|
||||
}
|
||||
}"
|
||||
|
||||
# Restart CoreDNS
|
||||
kubectl rollout restart deployment coredns -n kube-system
|
||||
kubectl rollout status deployment coredns -n kube-system --timeout=60s
|
||||
|
||||
print_success "CoreDNS configured to forward to Unbound"
|
||||
else
|
||||
print_success "CoreDNS already configured for Unbound"
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# Step 3: Create TLS Certificate Secret
|
||||
# =============================================================================
|
||||
print_step "Step 3: Creating TLS certificate secret..."
|
||||
|
||||
if kubectl get secret mailu-certificates -n "$NAMESPACE" &>/dev/null; then
|
||||
print_success "TLS certificate secret already exists"
|
||||
else
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
cd "$TEMP_DIR"
|
||||
|
||||
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
|
||||
-keyout tls.key -out tls.crt \
|
||||
-subj "/CN=mail.$DOMAIN/O=$DOMAIN" 2>/dev/null
|
||||
|
||||
kubectl create secret tls mailu-certificates \
|
||||
--cert=tls.crt \
|
||||
--key=tls.key \
|
||||
-n "$NAMESPACE"
|
||||
|
||||
rm -rf "$TEMP_DIR"
|
||||
print_success "TLS certificate secret created"
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# Step 4: Create Admin Credentials Secret
|
||||
# =============================================================================
|
||||
print_step "Step 4: Creating admin credentials secret..."
|
||||
|
||||
if kubectl get secret mailu-admin-credentials -n "$NAMESPACE" &>/dev/null; then
|
||||
print_success "Admin credentials secret already exists"
|
||||
# Retrieve existing password for summary output
|
||||
if [ -z "$ADMIN_PASSWORD" ]; then
|
||||
ADMIN_PASSWORD=$(kubectl get secret mailu-admin-credentials -n "$NAMESPACE" -o jsonpath='{.data.password}' | base64 -d)
|
||||
fi
|
||||
else
|
||||
if [ -z "$ADMIN_PASSWORD" ]; then
|
||||
# Generate a random password
|
||||
ADMIN_PASSWORD=$(openssl rand -base64 16 | tr -d '/+=' | head -c 16)
|
||||
echo -e "${YELLOW}Generated admin password: $ADMIN_PASSWORD${NC}"
|
||||
echo -e "${YELLOW}Please save this password securely!${NC}"
|
||||
fi
|
||||
|
||||
kubectl create secret generic mailu-admin-credentials \
|
||||
--from-literal=password="$ADMIN_PASSWORD" \
|
||||
-n "$NAMESPACE"
|
||||
|
||||
print_success "Admin credentials secret created"
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# Step 5: Deploy Mailu via Helm
|
||||
# =============================================================================
|
||||
print_step "Step 5: Deploying Mailu via Helm..."
|
||||
|
||||
# Add Mailu Helm repository
|
||||
helm repo add mailu https://mailu.github.io/helm-charts 2>/dev/null || true
|
||||
helm repo update mailu
|
||||
|
||||
# Deploy Mailu
|
||||
helm upgrade --install mailu mailu/mailu \
|
||||
-n "$NAMESPACE" \
|
||||
-f "$MAILU_HELM_DIR/values.yaml" \
|
||||
-f "$MAILU_HELM_DIR/prod/values.yaml" \
|
||||
--timeout 10m
|
||||
|
||||
print_success "Mailu Helm release deployed (admin user will be created automatically)"
|
||||
|
||||
# =============================================================================
|
||||
# Step 6: Wait for Pods to be Ready
|
||||
# =============================================================================
|
||||
print_step "Step 6: Waiting for Mailu pods to be ready..."
|
||||
|
||||
echo "This may take 5-10 minutes (ClamAV takes time to initialize)..."
|
||||
|
||||
# Wait for admin pod first (it's the key dependency)
|
||||
kubectl wait --for=condition=ready pod -l app.kubernetes.io/component=admin -n "$NAMESPACE" --timeout=300s || {
|
||||
print_error "Admin pod failed to start. Checking logs..."
|
||||
kubectl logs -n "$NAMESPACE" -l app.kubernetes.io/component=admin --tail=50
|
||||
exit 1
|
||||
}
|
||||
|
||||
print_success "Admin pod is ready"
|
||||
|
||||
# Show pod status
|
||||
echo ""
|
||||
echo "Mailu Pod Status:"
|
||||
kubectl get pods -n "$NAMESPACE" | grep mailu
|
||||
|
||||
print_success "Admin user created automatically via Helm initialAccount"
|
||||
|
||||
# =============================================================================
|
||||
# Summary
|
||||
# =============================================================================
|
||||
echo ""
|
||||
echo "=============================================="
|
||||
echo -e "${GREEN}Mailu Deployment Complete!${NC}"
|
||||
echo "=============================================="
|
||||
echo ""
|
||||
echo "Admin Credentials:"
|
||||
echo " Email: admin@$DOMAIN"
|
||||
echo " Password: $ADMIN_PASSWORD"
|
||||
echo ""
|
||||
echo "Access URLs (configure Ingress/DNS first):"
|
||||
echo " Admin Panel: https://mail.$DOMAIN/admin"
|
||||
echo " Webmail: https://mail.$DOMAIN/webmail"
|
||||
echo " SMTP: mail.$DOMAIN:587 (STARTTLS)"
|
||||
echo " IMAP: mail.$DOMAIN:993 (SSL)"
|
||||
echo ""
|
||||
echo "Next Steps:"
|
||||
echo " 1. Configure DNS records (A, MX, SPF, DMARC)"
|
||||
echo " 2. Get DKIM key: kubectl exec -n $NAMESPACE deployment/mailu-admin -- cat /dkim/$DOMAIN.dkim.pub"
|
||||
echo " 3. Add DKIM TXT record to DNS"
|
||||
echo " 4. Configure Ingress for mail.$DOMAIN"
|
||||
echo ""
|
||||
echo "To check pod status:"
|
||||
echo " kubectl get pods -n $NAMESPACE | grep mailu"
|
||||
echo ""
|
||||
235
infrastructure/platform/mail/mailu-helm/values.yaml
Normal file
235
infrastructure/platform/mail/mailu-helm/values.yaml
Normal file
@@ -0,0 +1,235 @@
|
||||
# Base Mailu Helm values for Bakery-IA
|
||||
# Preserves critical configurations from the original Kustomize setup
|
||||
|
||||
# Global DNS configuration for DNSSEC validation
|
||||
global:
|
||||
# Using Unbound DNS resolver directly for DNSSEC validation
|
||||
# Unbound service is available at unbound-dns.bakery-ia.svc.cluster.local
|
||||
# Static ClusterIP configured in unbound-helm/values.yaml
|
||||
custom_dns_servers: "10.96.53.53" # Unbound DNS static ClusterIP
|
||||
|
||||
# Domain configuration
|
||||
domain: "DOMAIN_PLACEHOLDER"
|
||||
hostnames:
|
||||
- "mail.DOMAIN_PLACEHOLDER"
|
||||
|
||||
# Mailu version to match the original setup
|
||||
mailuVersion: "2024.06"
|
||||
|
||||
# Secret key for authentication cookies
|
||||
secretKey: "cb61b934d47029a64117c0e4110c93f66bbcf5eaa15c84c42727fad78f7"
|
||||
|
||||
# Timezone
|
||||
timezone: "Etc/UTC"
|
||||
|
||||
# Postmaster configuration
|
||||
postmaster: "admin"
|
||||
|
||||
# Initial admin account configuration
|
||||
# This creates an admin user as part of the Helm deployment
|
||||
# Credentials can be provided directly or via Kubernetes secret
|
||||
initialAccount:
|
||||
enabled: true
|
||||
username: "admin"
|
||||
domain: "" # Set in environment-specific values (dev/prod)
|
||||
password: "" # Leave empty to use existingSecret
|
||||
existingSecret: "mailu-admin-credentials"
|
||||
existingSecretPasswordKey: "password"
|
||||
mode: "ifmissing" # Only create if account doesn't exist
|
||||
|
||||
# TLS configuration
|
||||
tls:
|
||||
flavor: "notls" # Disable TLS for development
|
||||
|
||||
# Limits configuration
|
||||
limits:
|
||||
messageSizeLimitInMegabytes: 50
|
||||
authRatelimit:
|
||||
ip: "60/hour"
|
||||
user: "100/day"
|
||||
messageRatelimit:
|
||||
value: "200/day"
|
||||
|
||||
# External relay configuration (Mailgun)
|
||||
# Mailu will relay all outbound emails through Mailgun SMTP
|
||||
# Credentials are loaded from Kubernetes secret for security
|
||||
externalRelay:
|
||||
host: "[smtp.mailgun.org]:587"
|
||||
# Use existing secret for credentials (recommended for security)
|
||||
secretName: "mailu-mailgun-credentials"
|
||||
usernameKey: "RELAY_USERNAME"
|
||||
passwordKey: "RELAY_PASSWORD"
|
||||
|
||||
# Webmail configuration
|
||||
webmail:
|
||||
enabled: true
|
||||
type: "roundcube"
|
||||
|
||||
# Antivirus and antispam configuration
|
||||
antivirus:
|
||||
enabled: false # Disabled in dev to save resources
|
||||
antispam:
|
||||
enabled: true
|
||||
flavor: "rspamd"
|
||||
|
||||
# Welcome message
|
||||
welcomeMessage:
|
||||
enabled: false # Disabled during development
|
||||
|
||||
# Logging
|
||||
logLevel: "INFO"
|
||||
|
||||
# Network configuration
|
||||
subnet: "10.42.0.0/16"
|
||||
|
||||
# Redis configuration - using internal Redis (built-in)
|
||||
externalRedis:
|
||||
enabled: false
|
||||
# host: "redis-service.bakery-ia.svc.cluster.local"
|
||||
# port: 6380
|
||||
adminQuotaDbId: 15
|
||||
adminRateLimitDbId: 15
|
||||
rspamdDbId: 15
|
||||
|
||||
# Database configuration - using default SQLite (built-in)
|
||||
externalDatabase:
|
||||
enabled: false
|
||||
# type: "postgresql"
|
||||
# host: "postgres-service.bakery-ia.svc.cluster.local"
|
||||
# port: 5432
|
||||
# database: "mailu"
|
||||
# username: "mailu"
|
||||
# password: "E8Kz47YmVzDlHGs1M9wAbJzxcKnGONCT"
|
||||
|
||||
# Persistence configuration
|
||||
persistence:
|
||||
single_pvc: true
|
||||
size: 10Gi
|
||||
storageClass: ""
|
||||
accessModes: [ReadWriteOnce]
|
||||
|
||||
# Ingress configuration - disabled to use with existing ingress
|
||||
ingress:
|
||||
enabled: false # Disable chart's Ingress; use existing one
|
||||
tls: false # Disable TLS in chart since ingress handles it
|
||||
tlsFlavorOverride: notls # No TLS on internal NGINX; expect external proxy to handle TLS
|
||||
realIpHeader: X-Forwarded-For # Header for client IP from your Ingress
|
||||
realIpFrom: 0.0.0.0/0 # Trust all proxies (restrict to your Ingress pod CIDR for security)
|
||||
path: /
|
||||
pathType: ImplementationSpecific
|
||||
|
||||
# Optional: Enable PROXY protocol for mail protocols if your Ingress supports TCP proxying
|
||||
proxyProtocol:
|
||||
smtp: false
|
||||
smtps: false
|
||||
submission: false
|
||||
imap: false
|
||||
imaps: false
|
||||
pop3: false
|
||||
pop3s: false
|
||||
manageSieve: false
|
||||
|
||||
# Front configuration
|
||||
front:
|
||||
image:
|
||||
tag: "2024.06"
|
||||
replicaCount: 1
|
||||
service:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
http: 80
|
||||
https: 443
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 256Mi
|
||||
|
||||
# Admin configuration
|
||||
admin:
|
||||
image:
|
||||
tag: "2024.06"
|
||||
replicaCount: 1
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: 300m
|
||||
memory: 512Mi
|
||||
|
||||
# Postfix configuration
|
||||
postfix:
|
||||
image:
|
||||
tag: "2024.06"
|
||||
replicaCount: 1
|
||||
service:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
smtp: 25
|
||||
submission: 587
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
|
||||
# Dovecot configuration
|
||||
dovecot:
|
||||
image:
|
||||
tag: "2024.06"
|
||||
replicaCount: 1
|
||||
service:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
imap: 143
|
||||
imaps: 993
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
|
||||
# Rspamd configuration
|
||||
rspamd:
|
||||
image:
|
||||
tag: "2024.06"
|
||||
replicaCount: 1
|
||||
service:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
rspamd: 11333
|
||||
rspamd-admin: 11334
|
||||
resources:
|
||||
requests:
|
||||
cpu: 200m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 1Gi
|
||||
|
||||
# Network Policy
|
||||
networkPolicy:
|
||||
enabled: true
|
||||
ingressController:
|
||||
namespace: ingress-nginx
|
||||
podSelector: |
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: ingress-nginx
|
||||
app.kubernetes.io/instance: ingress-nginx
|
||||
app.kubernetes.io/component: controller
|
||||
|
||||
# DNS Policy Configuration
|
||||
# Use Kubernetes DNS (ClusterFirst) for internal service resolution
|
||||
# DNSSEC validation for email is handled by rspamd component
|
||||
# Note: For production with DNSSEC needs, configure CoreDNS to forward to Unbound
|
||||
dnsPolicy: "ClusterFirst"
|
||||
@@ -0,0 +1,18 @@
|
||||
apiVersion: v2
|
||||
name: unbound
|
||||
description: A Helm chart for deploying Unbound DNS resolver for Bakery-IA
|
||||
type: application
|
||||
version: 0.1.0
|
||||
appVersion: "1.19.1"
|
||||
maintainers:
|
||||
- name: Bakery-IA Team
|
||||
email: devops@bakery-ia.com
|
||||
keywords:
|
||||
- dns
|
||||
- resolver
|
||||
- caching
|
||||
- unbound
|
||||
home: https://www.nlnetlabs.nl/projects/unbound/
|
||||
sources:
|
||||
- https://github.com/NLnetLabs/unbound
|
||||
- https://hub.docker.com/r/mvance/unbound
|
||||
@@ -0,0 +1,64 @@
|
||||
# Development values for unbound DNS resolver
|
||||
# Using same configuration as production for consistency
|
||||
|
||||
# Use official image for development (same as production)
|
||||
image:
|
||||
repository: "mvance/unbound"
|
||||
tag: "latest"
|
||||
pullPolicy: "IfNotPresent"
|
||||
|
||||
# Resource settings (slightly lower than production for dev)
|
||||
resources:
|
||||
requests:
|
||||
cpu: "100m"
|
||||
memory: "128Mi"
|
||||
limits:
|
||||
cpu: "300m"
|
||||
memory: "384Mi"
|
||||
|
||||
# Single replica for development (can be scaled if needed)
|
||||
replicaCount: 1
|
||||
|
||||
# Development annotations
|
||||
podAnnotations:
|
||||
environment: "development"
|
||||
managed-by: "helm"
|
||||
|
||||
# Probe settings (same as production but slightly faster)
|
||||
probes:
|
||||
readiness:
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 30
|
||||
command: "drill @127.0.0.1 -p 53 example.org || echo 'DNS query test'"
|
||||
liveness:
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 60
|
||||
command: "drill @127.0.0.1 -p 53 example.org || echo 'DNS query test'"
|
||||
|
||||
# Custom Unbound forward records for Kubernetes DNS
|
||||
config:
|
||||
enabled: true
|
||||
# The mvance/unbound image includes forward-records.conf
|
||||
# We need to add Kubernetes-specific forwarding zones
|
||||
forwardRecords: |
|
||||
# Forward all queries to Cloudflare with DNSSEC (catch-all)
|
||||
forward-zone:
|
||||
name: "."
|
||||
forward-tls-upstream: yes
|
||||
forward-addr: 1.1.1.1@853#cloudflare-dns.com
|
||||
forward-addr: 1.0.0.1@853#cloudflare-dns.com
|
||||
|
||||
# Additional server config to mark cluster.local as insecure (no DNSSEC)
|
||||
# and use stub zones for Kubernetes internal DNS (more reliable than forward)
|
||||
serverConfig: |
|
||||
domain-insecure: "cluster.local."
|
||||
private-domain: "cluster.local."
|
||||
local-zone: "10.in-addr.arpa." nodefault
|
||||
|
||||
stub-zone:
|
||||
name: "cluster.local."
|
||||
stub-addr: 10.96.0.10
|
||||
|
||||
stub-zone:
|
||||
name: "10.in-addr.arpa."
|
||||
stub-addr: 10.96.0.10
|
||||
@@ -0,0 +1,50 @@
|
||||
# Production-specific values for unbound DNS resolver
|
||||
# Overrides for the production environment
|
||||
|
||||
# Use official image for production
|
||||
image:
|
||||
repository: "mvance/unbound"
|
||||
tag: "latest"
|
||||
pullPolicy: "IfNotPresent"
|
||||
|
||||
# Production resource settings (higher limits for reliability)
|
||||
resources:
|
||||
requests:
|
||||
cpu: "200m"
|
||||
memory: "256Mi"
|
||||
limits:
|
||||
cpu: "500m"
|
||||
memory: "512Mi"
|
||||
|
||||
# Production-specific settings
|
||||
replicaCount: 2
|
||||
|
||||
# Production annotations
|
||||
podAnnotations:
|
||||
environment: "production"
|
||||
critical: "true"
|
||||
|
||||
# Anti-affinity for high availability in production
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
podAffinityTerm:
|
||||
labelSelector:
|
||||
matchExpressions:
|
||||
- key: app.kubernetes.io/name
|
||||
operator: In
|
||||
values:
|
||||
- unbound
|
||||
topologyKey: "kubernetes.io/hostname"
|
||||
|
||||
# Production probe settings (more conservative)
|
||||
probes:
|
||||
readiness:
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 30
|
||||
command: "drill @127.0.0.1 -p 53 example.org || echo 'DNS query test'"
|
||||
liveness:
|
||||
initialDelaySeconds: 45
|
||||
periodSeconds: 60
|
||||
command: "drill @127.0.0.1 -p 53 example.org || echo 'DNS query test'"
|
||||
@@ -0,0 +1,63 @@
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "unbound.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
*/}}
|
||||
{{- define "unbound.fullname" -}}
|
||||
{{- if .Values.fullnameOverride -}}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
||||
{{- if contains $name .Release.Name -}}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "unbound.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "unbound.labels" -}}
|
||||
helm.sh/chart: {{ include "unbound.chart" . }}
|
||||
{{ include "unbound.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "unbound.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "unbound.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
app.kubernetes.io/component: dns
|
||||
app.kubernetes.io/part-of: bakery-ia
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "unbound.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create -}}
|
||||
{{ default (include "unbound.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else -}}
|
||||
{{ default "default" .Values.serviceAccount.name }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,22 @@
|
||||
{{- if .Values.config.enabled }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "unbound.fullname" . }}-config
|
||||
namespace: {{ .Values.global.namespace }}
|
||||
labels:
|
||||
{{- include "unbound.labels" . | nindent 4 }}
|
||||
data:
|
||||
{{- if .Values.config.forwardRecords }}
|
||||
forward-records.conf: |
|
||||
{{ .Values.config.forwardRecords | indent 4 }}
|
||||
{{- end }}
|
||||
{{- if .Values.config.serverConfig }}
|
||||
a-records.conf: |
|
||||
{{ .Values.config.serverConfig | indent 4 }}
|
||||
{{- end }}
|
||||
{{- if .Values.config.content }}
|
||||
unbound.conf: |
|
||||
{{ .Values.config.content | indent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,117 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "unbound.fullname" . }}
|
||||
namespace: {{ .Values.global.namespace }}
|
||||
labels:
|
||||
{{- include "unbound.labels" . | nindent 4 }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "unbound.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "unbound.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "unbound.serviceAccountName" . }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
- name: dns-udp
|
||||
containerPort: {{ .Values.service.ports.dnsUdp }}
|
||||
protocol: UDP
|
||||
- name: dns-tcp
|
||||
containerPort: {{ .Values.service.ports.dnsTcp }}
|
||||
protocol: TCP
|
||||
{{- if .Values.probes.readiness.enabled }}
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- {{ .Values.probes.readiness.command | quote }}
|
||||
initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }}
|
||||
periodSeconds: {{ .Values.probes.readiness.periodSeconds }}
|
||||
{{- end }}
|
||||
{{- if .Values.probes.liveness.enabled }}
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- {{ .Values.probes.liveness.command | quote }}
|
||||
initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }}
|
||||
periodSeconds: {{ .Values.probes.liveness.periodSeconds }}
|
||||
{{- end }}
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
volumeMounts:
|
||||
{{- if .Values.config.enabled }}
|
||||
{{- if .Values.config.forwardRecords }}
|
||||
- name: unbound-config
|
||||
mountPath: /opt/unbound/etc/unbound/forward-records.conf
|
||||
subPath: forward-records.conf
|
||||
{{- end }}
|
||||
{{- if .Values.config.serverConfig }}
|
||||
- name: unbound-config
|
||||
mountPath: /opt/unbound/etc/unbound/a-records.conf
|
||||
subPath: a-records.conf
|
||||
{{- end }}
|
||||
{{- if .Values.config.content }}
|
||||
- name: unbound-config
|
||||
mountPath: /opt/unbound/etc/unbound/unbound.conf
|
||||
subPath: unbound.conf
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- with .Values.volumeMounts }}
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- with .Values.env }}
|
||||
env:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
{{- if .Values.config.enabled }}
|
||||
- name: unbound-config
|
||||
configMap:
|
||||
name: {{ include "unbound.fullname" . }}-config
|
||||
{{- end }}
|
||||
{{- with .Values.volumes }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.extraInitContainers }}
|
||||
initContainers:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.extraContainers }}
|
||||
containers:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,27 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ .Values.global.dnsServiceName }}
|
||||
namespace: {{ .Values.global.namespace }}
|
||||
labels:
|
||||
{{- include "unbound.labels" . | nindent 4 }}
|
||||
{{- with .Values.serviceAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
{{- if .Values.service.clusterIP }}
|
||||
clusterIP: {{ .Values.service.clusterIP }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: dns-udp
|
||||
port: {{ .Values.service.ports.dnsUdp }}
|
||||
targetPort: {{ .Values.service.ports.dnsUdp }}
|
||||
protocol: UDP
|
||||
- name: dns-tcp
|
||||
port: {{ .Values.service.ports.dnsTcp }}
|
||||
targetPort: {{ .Values.service.ports.dnsTcp }}
|
||||
protocol: TCP
|
||||
selector:
|
||||
{{- include "unbound.selectorLabels" . | nindent 4 }}
|
||||
@@ -0,0 +1,13 @@
|
||||
{{- if .Values.serviceAccount.create -}}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "unbound.serviceAccountName" . }}
|
||||
namespace: {{ .Values.global.namespace }}
|
||||
labels:
|
||||
{{- include "unbound.labels" . | nindent 4 }}
|
||||
{{- with .Values.serviceAccount.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,99 @@
|
||||
# Default values for unbound DNS resolver
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
# Global settings
|
||||
global:
|
||||
# DNS service name for other services to reference
|
||||
dnsServiceName: "unbound-dns"
|
||||
namespace: "bakery-ia"
|
||||
|
||||
# Unbound image configuration
|
||||
image:
|
||||
repository: "mvance/unbound"
|
||||
tag: "latest"
|
||||
pullPolicy: "IfNotPresent"
|
||||
|
||||
# Deployment configuration
|
||||
replicaCount: 1
|
||||
|
||||
# Resource limits and requests
|
||||
resources:
|
||||
requests:
|
||||
cpu: "100m"
|
||||
memory: "128Mi"
|
||||
limits:
|
||||
cpu: "300m"
|
||||
memory: "384Mi"
|
||||
|
||||
# Security context
|
||||
securityContext:
|
||||
capabilities:
|
||||
add: ["NET_BIND_SERVICE"]
|
||||
|
||||
# Service configuration
|
||||
service:
|
||||
type: "ClusterIP"
|
||||
# Static ClusterIP for predictable DNS configuration
|
||||
# This allows other services (like Mailu) to reference a stable IP
|
||||
# Must be within the cluster's service CIDR range (typically 10.96.0.0/12)
|
||||
clusterIP: "10.96.53.53"
|
||||
ports:
|
||||
dnsUdp: 53
|
||||
dnsTcp: 53
|
||||
|
||||
# Health probes configuration
|
||||
probes:
|
||||
readiness:
|
||||
enabled: true
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 30
|
||||
command: "drill @127.0.0.1 -p 53 example.org || echo 'DNS query test'"
|
||||
liveness:
|
||||
enabled: true
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 60
|
||||
command: "drill @127.0.0.1 -p 53 example.org || echo 'DNS query test'"
|
||||
|
||||
# Additional environment variables
|
||||
env: {}
|
||||
|
||||
# Additional volume mounts
|
||||
volumeMounts: []
|
||||
|
||||
# Additional volumes
|
||||
volumes: []
|
||||
|
||||
# Node selector
|
||||
nodeSelector: {}
|
||||
|
||||
# Tolerations
|
||||
tolerations: []
|
||||
|
||||
# Affinity
|
||||
affinity: {}
|
||||
|
||||
# Pod annotations
|
||||
podAnnotations: {}
|
||||
|
||||
# Service annotations
|
||||
serviceAnnotations: {}
|
||||
|
||||
# Custom unbound configuration
|
||||
config:
|
||||
enabled: false
|
||||
|
||||
# Additional containers (sidecars)
|
||||
extraContainers: []
|
||||
|
||||
# Additional init containers
|
||||
extraInitContainers: []
|
||||
|
||||
# Service account configuration
|
||||
serviceAccount:
|
||||
create: false
|
||||
annotations: {}
|
||||
name: ""
|
||||
|
||||
# Pod security context
|
||||
podSecurityContext: {}
|
||||
58
infrastructure/platform/networking/ingress/base/ingress.yaml
Normal file
58
infrastructure/platform/networking/ingress/base/ingress.yaml
Normal file
@@ -0,0 +1,58 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: bakery-ingress
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app.kubernetes.io/name: bakery-ia
|
||||
app.kubernetes.io/component: ingress
|
||||
annotations:
|
||||
# Nginx ingress controller annotations
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "500m"
|
||||
nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
|
||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
|
||||
# SSE and WebSocket configuration for long-lived connections
|
||||
nginx.ingress.kubernetes.io/proxy-buffering: "off"
|
||||
nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
|
||||
nginx.ingress.kubernetes.io/upstream-keepalive-timeout: "3600"
|
||||
# WebSocket upgrade support
|
||||
nginx.ingress.kubernetes.io/websocket-services: "gateway-service"
|
||||
# CORS configuration
|
||||
nginx.ingress.kubernetes.io/enable-cors: "true"
|
||||
nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, DELETE, OPTIONS, PATCH"
|
||||
nginx.ingress.kubernetes.io/cors-allow-headers: "Content-Type, Authorization, X-Requested-With, Accept, Origin, Cache-Control"
|
||||
nginx.ingress.kubernetes.io/cors-allow-credentials: "true"
|
||||
|
||||
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
tls:
|
||||
- hosts:
|
||||
- DOMAIN_PLACEHOLDER # To be replaced by kustomize
|
||||
secretName: TLS_SECRET_PLACEHOLDER # To be replaced by kustomize
|
||||
rules:
|
||||
# Main application routes
|
||||
- host: DOMAIN_PLACEHOLDER # To be replaced by kustomize
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: frontend-service
|
||||
port:
|
||||
number: 3000
|
||||
- path: /api
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: gateway-service
|
||||
port:
|
||||
number: 8000
|
||||
# NOTE: Gitea and Registry ingresses are managed by Gitea Helm chart
|
||||
# See infrastructure/cicd/gitea/values.yaml for ingress configuration
|
||||
# NOTE: Mail ingress is deployed separately via mailu-helm resource
|
||||
# to avoid 503 errors when Mailu is not running
|
||||
@@ -0,0 +1,5 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- ingress.yaml
|
||||
@@ -0,0 +1,5 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- base/
|
||||
@@ -0,0 +1,27 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- ../../base
|
||||
|
||||
namePrefix: dev-
|
||||
|
||||
patches:
|
||||
- target:
|
||||
kind: Ingress
|
||||
name: bakery-ingress
|
||||
patch: |-
|
||||
- op: replace
|
||||
path: /spec/tls/0/hosts/0
|
||||
value: bakery-ia.local
|
||||
- op: replace
|
||||
path: /spec/tls/0/secretName
|
||||
value: bakery-dev-tls-cert
|
||||
- op: replace
|
||||
path: /spec/rules/0/host
|
||||
value: bakery-ia.local
|
||||
- op: replace
|
||||
path: /metadata/annotations/nginx.ingress.kubernetes.io~1cors-allow-origin
|
||||
value: "https://localhost,https://localhost:3000,https://localhost:3001,https://127.0.0.1,https://127.0.0.1:3000,https://127.0.0.1:3001,https://bakery-ia.local,https://registry.bakery-ia.local,https://gitea.bakery-ia.local,http://localhost,http://localhost:3000,http://localhost:3001,http://127.0.0.1,http://127.0.0.1:3000"
|
||||
# NOTE: Gitea and Registry ingresses are managed by Gitea Helm chart (infrastructure/cicd/gitea/values.yaml)
|
||||
# NOTE: Mail ingress (mail.bakery-ia.dev) is deployed separately via mailu-helm Tilt resource
|
||||
@@ -0,0 +1,40 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- ../../base
|
||||
|
||||
namePrefix: prod-
|
||||
|
||||
patches:
|
||||
- target:
|
||||
kind: Ingress
|
||||
name: bakery-ingress
|
||||
patch: |-
|
||||
- op: replace
|
||||
path: /spec/tls/0/hosts/0
|
||||
value: bakewise.ai
|
||||
- op: replace
|
||||
path: /spec/tls/0/secretName
|
||||
value: bakery-ia-prod-tls-cert
|
||||
- op: replace
|
||||
path: /spec/rules/0/host
|
||||
value: bakewise.ai
|
||||
- op: add
|
||||
path: /metadata/annotations/nginx.ingress.kubernetes.io~1cors-allow-origin
|
||||
value: "https://bakewise.ai,https://www.bakewise.ai,https://mail.bakewise.ai,https://registry.bakewise.ai,https://gitea.bakewise.ai"
|
||||
- op: add
|
||||
path: /metadata/annotations/nginx.ingress.kubernetes.io~1limit-rps
|
||||
value: "100"
|
||||
- op: add
|
||||
path: /metadata/annotations/nginx.ingress.kubernetes.io~1limit-connections
|
||||
value: "50"
|
||||
- op: add
|
||||
path: /metadata/annotations/cert-manager.io~1cluster-issuer
|
||||
value: "letsencrypt-production"
|
||||
- op: add
|
||||
path: /metadata/annotations/cert-manager.io~1acme-challenge-type
|
||||
value: "http01"
|
||||
# NOTE: Gitea and Registry ingresses are managed by Gitea Helm chart
|
||||
# See infrastructure/cicd/gitea/values-prod.yaml for production ingress configuration
|
||||
# NOTE: mail.bakewise.ai is handled by separate mailu ingress
|
||||
19
infrastructure/platform/nominatim/nominatim-helm/Chart.yaml
Normal file
19
infrastructure/platform/nominatim/nominatim-helm/Chart.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
apiVersion: v2
|
||||
name: nominatim
|
||||
description: A Helm chart for deploying Nominatim geocoding service for Bakery-IA
|
||||
type: application
|
||||
version: 0.1.0
|
||||
appVersion: "4.4"
|
||||
maintainers:
|
||||
- name: Bakery-IA Team
|
||||
email: devops@bakery-ia.com
|
||||
keywords:
|
||||
- geocoding
|
||||
- nominatim
|
||||
- openstreetmap
|
||||
- maps
|
||||
- address
|
||||
home: https://nominatim.org/
|
||||
sources:
|
||||
- https://github.com/mediagis/nominatim-docker
|
||||
- https://hub.docker.com/r/mediagis/nominatim
|
||||
@@ -0,0 +1,38 @@
|
||||
# Development values for Nominatim geocoding service
|
||||
# Disabled by default in dev to save resources
|
||||
|
||||
# Use local registry image for development
|
||||
image:
|
||||
repository: "localhost:5000/mediagis_nominatim_4.4"
|
||||
tag: "latest"
|
||||
pullPolicy: "IfNotPresent"
|
||||
|
||||
# Disabled in dev (set to 0 replicas)
|
||||
replicaCount: 0
|
||||
|
||||
# Init job disabled in dev
|
||||
initJob:
|
||||
enabled: false
|
||||
|
||||
# Lower resources for dev (when enabled)
|
||||
resources:
|
||||
requests:
|
||||
cpu: "500m"
|
||||
memory: "1Gi"
|
||||
limits:
|
||||
cpu: "1"
|
||||
memory: "2Gi"
|
||||
|
||||
# Smaller PVCs for dev
|
||||
persistence:
|
||||
data:
|
||||
enabled: true
|
||||
size: "10Gi"
|
||||
flatnode:
|
||||
enabled: true
|
||||
size: "5Gi"
|
||||
|
||||
# Development annotations
|
||||
podAnnotations:
|
||||
environment: "development"
|
||||
managed-by: "helm"
|
||||
@@ -0,0 +1,45 @@
|
||||
# Production values for Nominatim geocoding service
|
||||
# Full configuration for production deployment
|
||||
|
||||
# Use official Docker Hub image for production
|
||||
image:
|
||||
repository: "mediagis/nominatim"
|
||||
tag: "4.4"
|
||||
pullPolicy: "IfNotPresent"
|
||||
|
||||
# Single replica for production (can be scaled if needed)
|
||||
replicaCount: 1
|
||||
|
||||
# Init job enabled in production
|
||||
initJob:
|
||||
enabled: true
|
||||
resources:
|
||||
requests:
|
||||
cpu: "4"
|
||||
memory: "8Gi"
|
||||
limits:
|
||||
cpu: "8"
|
||||
memory: "16Gi"
|
||||
|
||||
# Production resources
|
||||
resources:
|
||||
requests:
|
||||
cpu: "1"
|
||||
memory: "2Gi"
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: "4Gi"
|
||||
|
||||
# Full-size PVCs for production
|
||||
persistence:
|
||||
data:
|
||||
enabled: true
|
||||
size: "50Gi"
|
||||
flatnode:
|
||||
enabled: true
|
||||
size: "20Gi"
|
||||
|
||||
# Production annotations
|
||||
podAnnotations:
|
||||
environment: "production"
|
||||
managed-by: "helm"
|
||||
@@ -0,0 +1,87 @@
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "nominatim.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
*/}}
|
||||
{{- define "nominatim.fullname" -}}
|
||||
{{- if .Values.fullnameOverride -}}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
||||
{{- if contains $name .Release.Name -}}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "nominatim.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "nominatim.labels" -}}
|
||||
helm.sh/chart: {{ include "nominatim.chart" . }}
|
||||
{{ include "nominatim.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "nominatim.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "nominatim.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
app.kubernetes.io/component: geocoding
|
||||
app.kubernetes.io/part-of: bakery-ia
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
ConfigMap name
|
||||
*/}}
|
||||
{{- define "nominatim.configMapName" -}}
|
||||
{{- printf "%s-config" (include "nominatim.fullname" .) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Service name
|
||||
*/}}
|
||||
{{- define "nominatim.serviceName" -}}
|
||||
{{- default (printf "%s-service" (include "nominatim.fullname" .)) .Values.service.name -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Data PVC name
|
||||
*/}}
|
||||
{{- define "nominatim.dataPvcName" -}}
|
||||
{{- printf "%s-data" (include "nominatim.fullname" .) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Flatnode PVC name
|
||||
*/}}
|
||||
{{- define "nominatim.flatnodePvcName" -}}
|
||||
{{- printf "%s-flatnode" (include "nominatim.fullname" .) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Init job name
|
||||
*/}}
|
||||
{{- define "nominatim.initJobName" -}}
|
||||
{{- printf "%s-init" (include "nominatim.fullname" .) -}}
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,13 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "nominatim.configMapName" . }}
|
||||
namespace: {{ .Values.global.namespace }}
|
||||
labels:
|
||||
{{- include "nominatim.labels" . | nindent 4 }}
|
||||
data:
|
||||
NOMINATIM_PBF_URL: {{ .Values.config.pbfUrl | quote }}
|
||||
NOMINATIM_REPLICATION_URL: {{ .Values.config.replicationUrl | quote }}
|
||||
NOMINATIM_IMPORT_STYLE: {{ .Values.config.importStyle | quote }}
|
||||
NOMINATIM_THREADS: {{ .Values.config.threads | quote }}
|
||||
NOMINATIM_FLATNODE_FILE: {{ .Values.config.flatnodeFile | quote }}
|
||||
@@ -0,0 +1,80 @@
|
||||
{{- if .Values.initJob.enabled }}
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: {{ include "nominatim.initJobName" . }}
|
||||
namespace: {{ .Values.global.namespace }}
|
||||
labels:
|
||||
{{- include "nominatim.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: data-init
|
||||
spec:
|
||||
ttlSecondsAfterFinished: {{ .Values.initJob.ttlSecondsAfterFinished }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ include "nominatim.initJobName" . }}
|
||||
app.kubernetes.io/component: data-init
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
containers:
|
||||
- name: nominatim-import
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
echo "Checking if Nominatim database is already initialized..."
|
||||
|
||||
if psql -lqt | cut -d \| -f 1 | grep -qw nominatim; then
|
||||
echo "Nominatim database already exists. Skipping import."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Downloading Spain OSM data..."
|
||||
wget -O /tmp/spain-latest.osm.pbf "${NOMINATIM_PBF_URL}"
|
||||
|
||||
echo "Importing OSM data into Nominatim (this may take 30-60 minutes)..."
|
||||
nominatim import --osm-file /tmp/spain-latest.osm.pbf
|
||||
|
||||
echo "Building search indices..."
|
||||
nominatim refresh --website --importance
|
||||
|
||||
echo "Nominatim initialization complete!"
|
||||
volumeMounts:
|
||||
- name: nominatim-data
|
||||
mountPath: /var/lib/postgresql
|
||||
- name: nominatim-flatnode
|
||||
mountPath: /nominatim-flatnode
|
||||
env:
|
||||
- name: NOMINATIM_PBF_URL
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: {{ include "nominatim.configMapName" . }}
|
||||
key: NOMINATIM_PBF_URL
|
||||
- name: NOMINATIM_IMPORT_STYLE
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: {{ include "nominatim.configMapName" . }}
|
||||
key: NOMINATIM_IMPORT_STYLE
|
||||
- name: NOMINATIM_THREADS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: {{ include "nominatim.configMapName" . }}
|
||||
key: NOMINATIM_THREADS
|
||||
- name: NOMINATIM_FLATNODE_FILE
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: {{ include "nominatim.configMapName" . }}
|
||||
key: NOMINATIM_FLATNODE_FILE
|
||||
resources:
|
||||
{{- toYaml .Values.initJob.resources | nindent 10 }}
|
||||
volumes:
|
||||
- name: nominatim-data
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ include "nominatim.dataPvcName" . }}
|
||||
- name: nominatim-flatnode
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ include "nominatim.flatnodePvcName" . }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,37 @@
|
||||
{{- if .Values.persistence.data.enabled }}
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: {{ include "nominatim.dataPvcName" . }}
|
||||
namespace: {{ .Values.global.namespace }}
|
||||
labels:
|
||||
{{- include "nominatim.labels" . | nindent 4 }}
|
||||
spec:
|
||||
accessModes:
|
||||
- {{ .Values.persistence.data.accessMode }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.persistence.data.size }}
|
||||
{{- if .Values.persistence.data.storageClassName }}
|
||||
storageClassName: {{ .Values.persistence.data.storageClassName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
---
|
||||
{{- if .Values.persistence.flatnode.enabled }}
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: {{ include "nominatim.flatnodePvcName" . }}
|
||||
namespace: {{ .Values.global.namespace }}
|
||||
labels:
|
||||
{{- include "nominatim.labels" . | nindent 4 }}
|
||||
spec:
|
||||
accessModes:
|
||||
- {{ .Values.persistence.flatnode.accessMode }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.persistence.flatnode.size }}
|
||||
{{- if .Values.persistence.flatnode.storageClassName }}
|
||||
storageClassName: {{ .Values.persistence.flatnode.storageClassName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "nominatim.serviceName" . }}
|
||||
namespace: {{ .Values.global.namespace }}
|
||||
labels:
|
||||
{{- include "nominatim.labels" . | nindent 4 }}
|
||||
{{- with .Values.serviceAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
selector:
|
||||
{{- include "nominatim.selectorLabels" . | nindent 4 }}
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
targetPort: {{ .Values.service.port }}
|
||||
protocol: TCP
|
||||
name: http
|
||||
type: {{ .Values.service.type }}
|
||||
@@ -0,0 +1,113 @@
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: {{ include "nominatim.fullname" . }}
|
||||
namespace: {{ .Values.global.namespace }}
|
||||
labels:
|
||||
{{- include "nominatim.labels" . | nindent 4 }}
|
||||
spec:
|
||||
serviceName: {{ include "nominatim.serviceName" . }}
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "nominatim.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "nominatim.selectorLabels" . | nindent 8 }}
|
||||
{{- with .Values.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- with .Values.podSecurityContext }}
|
||||
securityContext:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: nominatim
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
{{- with .Values.securityContext }}
|
||||
securityContext:
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- containerPort: {{ .Values.service.port }}
|
||||
name: http
|
||||
volumeMounts:
|
||||
- name: nominatim-data
|
||||
mountPath: /var/lib/postgresql
|
||||
- name: nominatim-flatnode
|
||||
mountPath: /nominatim-flatnode
|
||||
env:
|
||||
- name: NOMINATIM_PBF_URL
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: {{ include "nominatim.configMapName" . }}
|
||||
key: NOMINATIM_PBF_URL
|
||||
- name: NOMINATIM_REPLICATION_URL
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: {{ include "nominatim.configMapName" . }}
|
||||
key: NOMINATIM_REPLICATION_URL
|
||||
- name: NOMINATIM_IMPORT_STYLE
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: {{ include "nominatim.configMapName" . }}
|
||||
key: NOMINATIM_IMPORT_STYLE
|
||||
- name: NOMINATIM_THREADS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: {{ include "nominatim.configMapName" . }}
|
||||
key: NOMINATIM_THREADS
|
||||
- name: NOMINATIM_FLATNODE_FILE
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: {{ include "nominatim.configMapName" . }}
|
||||
key: NOMINATIM_FLATNODE_FILE
|
||||
{{- range $key, $value := .Values.env }}
|
||||
- name: {{ $key }}
|
||||
value: {{ $value | quote }}
|
||||
{{- end }}
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 10 }}
|
||||
{{- if .Values.probes.liveness.enabled }}
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: {{ .Values.probes.liveness.path }}
|
||||
port: {{ .Values.probes.liveness.port }}
|
||||
initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }}
|
||||
periodSeconds: {{ .Values.probes.liveness.periodSeconds }}
|
||||
timeoutSeconds: {{ .Values.probes.liveness.timeoutSeconds }}
|
||||
failureThreshold: {{ .Values.probes.liveness.failureThreshold }}
|
||||
{{- end }}
|
||||
{{- if .Values.probes.readiness.enabled }}
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: {{ .Values.probes.readiness.path }}
|
||||
port: {{ .Values.probes.readiness.port }}
|
||||
initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }}
|
||||
periodSeconds: {{ .Values.probes.readiness.periodSeconds }}
|
||||
timeoutSeconds: {{ .Values.probes.readiness.timeoutSeconds }}
|
||||
failureThreshold: {{ .Values.probes.readiness.failureThreshold }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
- name: nominatim-data
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ include "nominatim.dataPvcName" . }}
|
||||
- name: nominatim-flatnode
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ include "nominatim.flatnodePvcName" . }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
113
infrastructure/platform/nominatim/nominatim-helm/values.yaml
Normal file
113
infrastructure/platform/nominatim/nominatim-helm/values.yaml
Normal file
@@ -0,0 +1,113 @@
|
||||
# Default values for Nominatim geocoding service
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
# Global settings
|
||||
global:
|
||||
namespace: "bakery-ia"
|
||||
|
||||
# Nominatim image configuration
|
||||
image:
|
||||
repository: "mediagis/nominatim"
|
||||
tag: "4.4"
|
||||
pullPolicy: "IfNotPresent"
|
||||
|
||||
# StatefulSet configuration
|
||||
replicaCount: 1
|
||||
|
||||
# Nominatim configuration
|
||||
config:
|
||||
# Spain OSM data source
|
||||
pbfUrl: "http://download.geofabrik.de/europe/spain-latest.osm.pbf"
|
||||
# Updates replication source
|
||||
replicationUrl: "https://download.geofabrik.de/europe/spain-updates"
|
||||
# Import style (address for geocoding-focused usage)
|
||||
importStyle: "address"
|
||||
# Number of threads for indexing
|
||||
threads: "4"
|
||||
# Flatnode file path
|
||||
flatnodeFile: "/nominatim-flatnode/flatnode.bin"
|
||||
|
||||
# Service configuration
|
||||
service:
|
||||
type: "ClusterIP"
|
||||
port: 8080
|
||||
name: "nominatim-service"
|
||||
|
||||
# Resource limits and requests for main service
|
||||
resources:
|
||||
requests:
|
||||
cpu: "1"
|
||||
memory: "2Gi"
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: "4Gi"
|
||||
|
||||
# Init job resource limits (higher for initial import)
|
||||
initJob:
|
||||
enabled: true
|
||||
resources:
|
||||
requests:
|
||||
cpu: "4"
|
||||
memory: "8Gi"
|
||||
limits:
|
||||
cpu: "8"
|
||||
memory: "16Gi"
|
||||
# Time to keep job after completion (86400 = 1 day)
|
||||
ttlSecondsAfterFinished: 86400
|
||||
|
||||
# Persistent Volume Claims
|
||||
persistence:
|
||||
data:
|
||||
enabled: true
|
||||
size: "50Gi"
|
||||
accessMode: "ReadWriteOnce"
|
||||
# storageClassName: "" # Use default storage class
|
||||
flatnode:
|
||||
enabled: true
|
||||
size: "20Gi"
|
||||
accessMode: "ReadWriteOnce"
|
||||
# storageClassName: "" # Use default storage class
|
||||
|
||||
# Health probes configuration
|
||||
probes:
|
||||
liveness:
|
||||
enabled: true
|
||||
path: "/status"
|
||||
port: 8080
|
||||
initialDelaySeconds: 120
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 3
|
||||
readiness:
|
||||
enabled: true
|
||||
path: "/status"
|
||||
port: 8080
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 5
|
||||
|
||||
# Additional environment variables
|
||||
env: {}
|
||||
|
||||
# Node selector
|
||||
nodeSelector: {}
|
||||
|
||||
# Tolerations
|
||||
tolerations: []
|
||||
|
||||
# Affinity
|
||||
affinity: {}
|
||||
|
||||
# Pod annotations
|
||||
podAnnotations: {}
|
||||
|
||||
# Service annotations
|
||||
serviceAnnotations: {}
|
||||
|
||||
# Pod security context
|
||||
podSecurityContext: {}
|
||||
|
||||
# Container security context
|
||||
securityContext: {}
|
||||
55
infrastructure/platform/security/encryption/README.md
Normal file
55
infrastructure/platform/security/encryption/README.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Kubernetes Secrets Encryption
|
||||
|
||||
This directory contains configuration for encrypting Kubernetes secrets at rest.
|
||||
|
||||
## What is this for?
|
||||
|
||||
Kubernetes secrets are stored in etcd, and by default they are stored as plaintext. This encryption configuration ensures that secrets are encrypted when stored in etcd, providing an additional layer of security.
|
||||
|
||||
## Files
|
||||
|
||||
- `encryption-config.yaml` - Main encryption configuration file
|
||||
|
||||
## How it works
|
||||
|
||||
1. The API server uses this configuration to encrypt secrets before storing them in etcd
|
||||
2. When secrets are retrieved, they are automatically decrypted by the API server
|
||||
3. This provides encryption at rest for all Kubernetes secrets
|
||||
|
||||
## Security Notes
|
||||
|
||||
- The encryption key is stored in this file (base64 encoded)
|
||||
- This file should be protected and not committed to version control in production
|
||||
- For development, this provides basic encryption at rest
|
||||
- In production, consider using a proper key management system
|
||||
|
||||
## Generating a new key
|
||||
|
||||
```bash
|
||||
openssl rand -base64 32
|
||||
```
|
||||
|
||||
## Configuration Details
|
||||
|
||||
- **Algorithm**: AES-CBC with 256-bit keys
|
||||
- **Provider**: `aescbc` - AES-CBC encryption provider
|
||||
- **Fallback**: `identity` - Allows reading unencrypted secrets during migration
|
||||
|
||||
## Usage
|
||||
|
||||
This configuration is automatically used by the Kind cluster configuration in `kind-config.yaml`. The file is mounted into the Kubernetes control plane container and referenced by the API server configuration.
|
||||
|
||||
## Rotation
|
||||
|
||||
To rotate keys:
|
||||
1. Add a new key to the `keys` array
|
||||
2. Make the new key the first in the array
|
||||
3. Restart the API server
|
||||
4. Old keys can be removed after all secrets have been re-encrypted with the new key
|
||||
|
||||
## Compliance
|
||||
|
||||
This encryption helps satisfy:
|
||||
- GDPR Article 32 - Security of processing
|
||||
- PCI DSS Requirement 3.4 - Encryption of sensitive data
|
||||
- ISO 27001:2022 - Cryptographic controls
|
||||
@@ -0,0 +1,17 @@
|
||||
# Kubernetes Secrets Encryption Configuration
|
||||
# This file configures encryption at rest for Kubernetes secrets
|
||||
# Used by the API server to encrypt secret data stored in etcd
|
||||
|
||||
apiVersion: apiserver.config.k8s.io/v1
|
||||
kind: EncryptionConfiguration
|
||||
resources:
|
||||
- resources:
|
||||
- secrets
|
||||
providers:
|
||||
- aescbc:
|
||||
keys:
|
||||
- name: key1
|
||||
# 32-byte (256-bit) AES key encoded in base64
|
||||
# Generated using: openssl rand -base64 32
|
||||
secret: 62um3zP5aidjVSIB0ckAxF/Ms8EDy/Z8LyMGTdMuoSM=
|
||||
- identity: {}
|
||||
@@ -0,0 +1,108 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: default-deny-all
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app: global
|
||||
component: network-policy
|
||||
tier: security
|
||||
spec:
|
||||
podSelector: {}
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress: []
|
||||
egress: []
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: allow-kube-dns
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app: global
|
||||
component: network-policy
|
||||
tier: security
|
||||
spec:
|
||||
podSelector: {}
|
||||
policyTypes:
|
||||
- Egress
|
||||
egress:
|
||||
# Allow DNS resolution to kube-system namespace
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
name: kube-system
|
||||
ports:
|
||||
- port: 53
|
||||
protocol: UDP
|
||||
- port: 53
|
||||
protocol: TCP
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: allow-ingress-controller
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app: global
|
||||
component: network-policy
|
||||
tier: security
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
# This label should match your ingress controller's namespace
|
||||
# Adjust as needed for your specific ingress controller
|
||||
app: nginx-ingress-microk8s
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
# Allow all traffic to ingress controller
|
||||
- from:
|
||||
- ipBlock:
|
||||
cidr: 0.0.0.0/0
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: allow-internal-communication
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app: global
|
||||
component: network-policy
|
||||
tier: security
|
||||
spec:
|
||||
podSelector: {}
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
# Allow communication between pods in the same namespace
|
||||
- from:
|
||||
- podSelector: {}
|
||||
egress:
|
||||
# Allow communication to pods in the same namespace
|
||||
- to:
|
||||
- podSelector: {}
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: allow-egress-external
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app: global
|
||||
component: network-policy
|
||||
tier: security
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: external-egress-allowed
|
||||
policyTypes:
|
||||
- Egress
|
||||
egress:
|
||||
# Allow external communication for services that need it
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 0.0.0.0/0
|
||||
@@ -0,0 +1,159 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: project-default-deny
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app: project-global
|
||||
component: network-policy
|
||||
tier: security
|
||||
spec:
|
||||
podSelector: {}
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress: []
|
||||
egress: []
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: project-allow-dns
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app: project-global
|
||||
component: network-policy
|
||||
tier: security
|
||||
spec:
|
||||
podSelector: {}
|
||||
policyTypes:
|
||||
- Egress
|
||||
egress:
|
||||
# Allow DNS resolution to kube-system namespace
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
name: kube-system
|
||||
ports:
|
||||
- port: 53
|
||||
protocol: UDP
|
||||
- port: 53
|
||||
protocol: TCP
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: project-allow-ingress-access
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app: project-global
|
||||
component: network-policy
|
||||
tier: security
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: ingress-nginx
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
# Allow all traffic to ingress controller
|
||||
- from:
|
||||
- ipBlock:
|
||||
cidr: 0.0.0.0/0
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: project-allow-internal-comm
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app: project-global
|
||||
component: network-policy
|
||||
tier: security
|
||||
spec:
|
||||
podSelector: {}
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
# Allow communication between project services
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
name: bakery-ia
|
||||
egress:
|
||||
# Allow communication to project services
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
name: bakery-ia
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: project-allow-monitoring
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app: project-global
|
||||
component: network-policy
|
||||
tier: security
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: signoz
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
# Allow monitoring access from project services
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
name: bakery-ia
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: project-allow-database-access
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app: project-global
|
||||
component: network-policy
|
||||
tier: security
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: postgres
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
# Allow database access from application services
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
name: bakery-ia
|
||||
ports:
|
||||
- port: 5432
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: project-allow-cache-access
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app: project-global
|
||||
component: network-policy
|
||||
tier: security
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: redis
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
# Allow cache access from application services
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
name: bakery-ia
|
||||
ports:
|
||||
- port: 6379
|
||||
19
infrastructure/platform/storage/kustomization.yaml
Normal file
19
infrastructure/platform/storage/kustomization.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
# Storage infrastructure
|
||||
- minio/minio-deployment.yaml
|
||||
- minio/minio-pvc.yaml
|
||||
- minio/minio-secrets.yaml
|
||||
- minio/minio-bucket-init-job.yaml
|
||||
- minio/secrets/minio-tls-secret.yaml
|
||||
|
||||
# Cache infrastructure
|
||||
- redis/redis.yaml
|
||||
- redis/secrets/redis-tls-secret.yaml
|
||||
|
||||
# Database infrastructure
|
||||
- postgres/secrets/postgres-tls-secret.yaml
|
||||
- postgres/configs/postgres-logging-config.yaml
|
||||
- postgres/configs/postgres-init-config.yaml
|
||||
193
infrastructure/platform/storage/minio/minio-bucket-init-job.yaml
Normal file
193
infrastructure/platform/storage/minio/minio-bucket-init-job.yaml
Normal file
@@ -0,0 +1,193 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: minio-bucket-init
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app.kubernetes.io/name: minio-bucket-init
|
||||
app.kubernetes.io/component: storage-init
|
||||
app.kubernetes.io/part-of: bakery-ia
|
||||
spec:
|
||||
ttlSecondsAfterFinished: 300
|
||||
backoffLimit: 3
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: minio-bucket-init
|
||||
app.kubernetes.io/component: storage-init
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
initContainers:
|
||||
# Wait for MinIO to be ready
|
||||
- name: wait-for-minio
|
||||
image: busybox:1.36
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
echo "Waiting for MinIO to be ready..."
|
||||
until nc -z minio.bakery-ia.svc.cluster.local 9000; do
|
||||
echo "MinIO not ready, waiting..."
|
||||
sleep 5
|
||||
done
|
||||
echo "MinIO is ready!"
|
||||
containers:
|
||||
- name: bucket-init
|
||||
image: minio/mc:RELEASE.2024-11-17T19-35-25Z
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
|
||||
echo "Configuring MinIO client..."
|
||||
|
||||
# Configure mc alias with TLS (skip cert verification for self-signed)
|
||||
mc alias set myminio https://minio.bakery-ia.svc.cluster.local:9000 \
|
||||
${MINIO_ROOT_USER} ${MINIO_ROOT_PASSWORD} --insecure
|
||||
|
||||
echo "Creating buckets..."
|
||||
|
||||
# Create training-models bucket if not exists
|
||||
if ! mc ls myminio/training-models --insecure 2>/dev/null; then
|
||||
mc mb myminio/training-models --insecure
|
||||
echo "Created bucket: training-models"
|
||||
else
|
||||
echo "Bucket already exists: training-models"
|
||||
fi
|
||||
|
||||
# Set bucket policy (private by default)
|
||||
mc anonymous set none myminio/training-models --insecure
|
||||
|
||||
# Enable versioning for model backups
|
||||
mc version enable myminio/training-models --insecure
|
||||
echo "Enabled versioning on training-models bucket"
|
||||
|
||||
# Set lifecycle policy to expire old versions after 90 days
|
||||
cat > /tmp/lifecycle.json << 'EOF'
|
||||
{
|
||||
"Rules": [
|
||||
{
|
||||
"ID": "expire-old-versions",
|
||||
"Status": "Enabled",
|
||||
"Filter": {
|
||||
"Prefix": "models/"
|
||||
},
|
||||
"NoncurrentVersionExpiration": {
|
||||
"NoncurrentDays": 90
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "expire-old-metadata",
|
||||
"Status": "Enabled",
|
||||
"Filter": {
|
||||
"Prefix": "models/"
|
||||
},
|
||||
"Expiration": {
|
||||
"ExpiredObjectDeleteMarker": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
mc ilm import myminio/training-models < /tmp/lifecycle.json --insecure || true
|
||||
echo "Lifecycle policy configured"
|
||||
|
||||
# Create service accounts with limited permissions
|
||||
echo "Creating service accounts..."
|
||||
|
||||
# Training service policy (read/write models)
|
||||
cat > /tmp/training-policy.json << 'EOF'
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"s3:GetObject",
|
||||
"s3:PutObject",
|
||||
"s3:DeleteObject",
|
||||
"s3:ListBucket",
|
||||
"s3:GetBucketLocation",
|
||||
"s3:ListBucketMultipartUploads"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::training-models",
|
||||
"arn:aws:s3:::training-models/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
|
||||
# Forecasting service policy (read-only models)
|
||||
cat > /tmp/forecasting-policy.json << 'EOF'
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"s3:GetObject",
|
||||
"s3:ListBucket"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::training-models",
|
||||
"arn:aws:s3:::training-models/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
|
||||
# Create service accounts using credentials from secrets
|
||||
echo "Creating service accounts..."
|
||||
mc admin user add myminio ${TRAINING_MINIO_USER} ${TRAINING_MINIO_PASSWORD} --insecure 2>/dev/null || true
|
||||
mc admin user add myminio ${FORECASTING_MINIO_USER} ${FORECASTING_MINIO_PASSWORD} --insecure 2>/dev/null || true
|
||||
|
||||
# Apply policies (ignore errors if already exists)
|
||||
mc admin policy create myminio training-policy /tmp/training-policy.json --insecure 2>/dev/null || true
|
||||
mc admin policy attach myminio training-policy --user=${TRAINING_MINIO_USER} --insecure 2>/dev/null || true
|
||||
|
||||
mc admin policy create myminio forecasting-policy /tmp/forecasting-policy.json --insecure 2>/dev/null || true
|
||||
mc admin policy attach myminio forecasting-policy --user=${FORECASTING_MINIO_USER} --insecure 2>/dev/null || true
|
||||
|
||||
echo "MinIO bucket initialization complete!"
|
||||
|
||||
# List buckets for verification
|
||||
echo "Current buckets:"
|
||||
mc ls myminio --insecure
|
||||
|
||||
env:
|
||||
- name: MINIO_ROOT_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: minio-secrets
|
||||
key: MINIO_ROOT_USER
|
||||
- name: MINIO_ROOT_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: minio-secrets
|
||||
key: MINIO_ROOT_PASSWORD
|
||||
# Training service MinIO credentials
|
||||
- name: TRAINING_MINIO_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: minio-secrets
|
||||
key: MINIO_ACCESS_KEY
|
||||
- name: TRAINING_MINIO_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: minio-secrets
|
||||
key: MINIO_SECRET_KEY
|
||||
# Forecasting service MinIO credentials
|
||||
- name: FORECASTING_MINIO_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: minio-secrets
|
||||
key: FORECASTING_MINIO_ACCESS_KEY
|
||||
- name: FORECASTING_MINIO_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: minio-secrets
|
||||
key: FORECASTING_MINIO_SECRET_KEY
|
||||
154
infrastructure/platform/storage/minio/minio-deployment.yaml
Normal file
154
infrastructure/platform/storage/minio/minio-deployment.yaml
Normal file
@@ -0,0 +1,154 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: minio
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app.kubernetes.io/name: minio
|
||||
app.kubernetes.io/component: storage
|
||||
app.kubernetes.io/part-of: bakery-ia
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: minio
|
||||
app.kubernetes.io/component: storage
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: minio
|
||||
app.kubernetes.io/component: storage
|
||||
spec:
|
||||
# Init container to set up TLS certificates with correct permissions
|
||||
initContainers:
|
||||
- name: init-certs
|
||||
image: busybox:1.36
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
mkdir -p /certs/CAs
|
||||
cp /certs-secret/minio-cert.pem /certs/public.crt
|
||||
cp /certs-secret/minio-key.pem /certs/private.key
|
||||
cp /certs-secret/ca-cert.pem /certs/CAs/ca.crt
|
||||
chmod 600 /certs/private.key
|
||||
chmod 644 /certs/public.crt /certs/CAs/ca.crt
|
||||
volumeMounts:
|
||||
- name: certs-secret
|
||||
mountPath: /certs-secret
|
||||
readOnly: true
|
||||
- name: certs
|
||||
mountPath: /certs
|
||||
containers:
|
||||
- name: minio
|
||||
image: minio/minio:RELEASE.2024-11-07T00-52-20Z
|
||||
args:
|
||||
- server
|
||||
- /data
|
||||
- --console-address
|
||||
- :9001
|
||||
- --address
|
||||
- :9000
|
||||
- --certs-dir
|
||||
- /certs
|
||||
env:
|
||||
- name: MINIO_ROOT_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: minio-secrets
|
||||
key: MINIO_ROOT_USER
|
||||
- name: MINIO_ROOT_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: minio-secrets
|
||||
key: MINIO_ROOT_PASSWORD
|
||||
# Enable TLS for MinIO
|
||||
- name: MINIO_SERVER_URL
|
||||
value: "https://minio.bakery-ia.svc.cluster.local:9000"
|
||||
- name: MINIO_BROWSER_REDIRECT_URL
|
||||
value: "https://minio-console.bakery-ia.svc.cluster.local:9001"
|
||||
ports:
|
||||
- containerPort: 9000
|
||||
name: api
|
||||
- containerPort: 9001
|
||||
name: console
|
||||
volumeMounts:
|
||||
- name: minio-data
|
||||
mountPath: /data
|
||||
- name: certs
|
||||
mountPath: /certs
|
||||
readOnly: true
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "200m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "1000m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /minio/health/live
|
||||
port: 9000
|
||||
scheme: HTTPS
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /minio/health/ready
|
||||
port: 9000
|
||||
scheme: HTTPS
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 15
|
||||
volumes:
|
||||
- name: minio-data
|
||||
persistentVolumeClaim:
|
||||
claimName: minio-data
|
||||
- name: certs-secret
|
||||
secret:
|
||||
secretName: minio-tls
|
||||
- name: certs
|
||||
emptyDir: {}
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: minio
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app.kubernetes.io/name: minio
|
||||
app.kubernetes.io/component: storage
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 9000
|
||||
targetPort: 9000
|
||||
protocol: TCP
|
||||
name: api
|
||||
- port: 9001
|
||||
targetPort: 9001
|
||||
protocol: TCP
|
||||
name: console
|
||||
selector:
|
||||
app.kubernetes.io/name: minio
|
||||
app.kubernetes.io/component: storage
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: minio-console
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app.kubernetes.io/name: minio
|
||||
app.kubernetes.io/component: storage
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 9001
|
||||
targetPort: 9001
|
||||
protocol: TCP
|
||||
name: console
|
||||
selector:
|
||||
app.kubernetes.io/name: minio
|
||||
app.kubernetes.io/component: storage
|
||||
16
infrastructure/platform/storage/minio/minio-pvc.yaml
Normal file
16
infrastructure/platform/storage/minio/minio-pvc.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: minio-data
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app.kubernetes.io/name: minio-data
|
||||
app.kubernetes.io/component: storage
|
||||
app.kubernetes.io/part-of: bakery-ia
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 100Gi
|
||||
storageClassName: standard
|
||||
22
infrastructure/platform/storage/minio/minio-secrets.yaml
Normal file
22
infrastructure/platform/storage/minio/minio-secrets.yaml
Normal file
@@ -0,0 +1,22 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: minio-secrets
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app.kubernetes.io/name: minio-secrets
|
||||
app.kubernetes.io/component: storage
|
||||
app.kubernetes.io/part-of: bakery-ia
|
||||
type: Opaque
|
||||
data:
|
||||
# MinIO Root Credentials (base64 encoded)
|
||||
MINIO_ROOT_USER: YWRtaW4= # admin
|
||||
MINIO_ROOT_PASSWORD: c2VjdXJlLXBhc3N3b3Jk # secure-password
|
||||
|
||||
# Service Account Credentials for applications
|
||||
MINIO_ACCESS_KEY: dHJhaW5pbmctc2VydmljZQ== # training-service
|
||||
MINIO_SECRET_KEY: dHJhaW5pbmctc2VjcmV0LWtleQ== # training-secret-key
|
||||
|
||||
# Forecasting Service Credentials
|
||||
FORECASTING_MINIO_ACCESS_KEY: Zm9yZWNhc3Rpbmctc2VydmljZQ== # forecasting-service
|
||||
FORECASTING_MINIO_SECRET_KEY: Zm9yZWNhc3Rpbmctc2VjcmV0LWtleQ== # forecasting-secret-key
|
||||
@@ -0,0 +1,28 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: minio-tls
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app.kubernetes.io/name: bakery-ia
|
||||
app.kubernetes.io/component: minio-tls
|
||||
app.kubernetes.io/part-of: bakery-ia
|
||||
type: Opaque
|
||||
data:
|
||||
# MinIO TLS certificates (base64 encoded)
|
||||
# Generated using infrastructure/tls/generate-minio-certificates.sh
|
||||
# Valid for 3 years from generation date
|
||||
#
|
||||
# Certificate details:
|
||||
# Subject: CN=minio.bakery-ia.svc.cluster.local, O=BakeryIA, OU=Storage
|
||||
# Issuer: CN=BakeryIA-CA, O=BakeryIA, OU=Security
|
||||
#
|
||||
# To regenerate:
|
||||
# 1. Run: infrastructure/tls/generate-minio-certificates.sh
|
||||
# 2. Run: scripts/create-tls-secrets.sh
|
||||
|
||||
ca-cert.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZ5ekNDQTdPZ0F3SUJBZ0lVUGdPcU5ZK1pvS0J5UTFNZk84bGtpR2hPbXhJd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2RURUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2tOaGJHbG1iM0p1YVdFeEZUQVRCZ05WQkFjTQpERk5oYmtaeVlXNWphWE5qYnpFUk1BOEdBMVVFQ2d3SVFtRnJaWEo1U1VFeEVUQVBCZ05WQkFzTUNGTmxZM1Z5CmFYUjVNUlF3RWdZRFZRUUREQXRDWVd0bGNubEpRUzFEUVRBZUZ3MHlOVEV3TVRneE5ESXlNVFJhRncwek5URXcKTVRZeE5ESXlNVFJhTUhVeEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlEQXBEWVd4cFptOXlibWxoTVJVdwpFd1lEVlFRSERBeFRZVzVHY21GdVkybHpZMjh4RVRBUEJnTlZCQW9NQ0VKaGEyVnllVWxCTVJFd0R3WURWUVFMCkRBaFRaV04xY21sMGVURVVNQklHQTFVRUF3d0xRbUZyWlhKNVNVRXRRMEV3Z2dJaU1BMEdDU3FHU0liM0RRRUIKQVFVQUE0SUNEd0F3Z2dJS0FvSUNBUURSRDVPMmVna1lnOUhOUlI1U1UwYkxuR0hqcHYvUmFnck03ZGh1c2FXbgpyZkRGNVZwVFo0czkvOXNPRUowTnlqdW9LWGFtb3VUd1IxbncxOUZkSDhmMWVvbWNRNGVLdzJIa3hveHFSMzR0ClJEYUFHejNiV08rcmFUUTRTeU1LN1hGTW92VVVpTGwrR08yM2wxQk5QZmh6a2NEa1o5N200MzRmMVFWbzk5dGIKaFY0YklMYW9GSXFmMDlNMEUxL2ZhQitKQ1I4WWtsN0xvWGd1ejNWUi9CVW5kMHZNc1RNV3VlRC8yblZ1VVpPMAowcFVtVFVCUTJRZDc2NTdrL0hXZC8xd2NFQUw5ZFhOUmJ4aEROZkdnYzNXdFFoZ2djcFlMUWFmTGE4MXRseHljCndEZ042UGRFbFVseGdYL091b1oxeWxNWkU3eHBzTXRwbjFBd2VvZFZibTNRcDVBMXlkeWJFNjF1MXVyWXoxTHQKV05aOWVPZkFxZXdpWVFIVlpXTUM0YTRTYSsyeU02cTVQWC80ZytUYklUaDhoWkp3WFBLNUVEaWc3dkYxNEpQbApsRVJOcHdpYTNuNmEwUDcwM0hQTjZya1FPNWtWVGRpVXNmaWJNdGNVSkhMeVdXUUFSQm15ZVZma0lDYWFlWUVsCkVMa3N3YTlOVkVTS3ZRYUhLU2lIWkZoRUkwYUF2Y3BBam0xRU9oRWEraFNSaE9vRnlVT3ZHK2NNT2ZjQlNtTDAKVW1sRC9sZmFuVFQwems1YXFzcEVrWEdlQnczMXJtWi8wQVpPalYycHBSeFdXZWt6bzlCZjdnNmVMVFk0VUNDNQpNeVB0em14OVRiWHJOQW5YaGlGNkxnNWgyOFI0MkdUZTVBZDZUSGtGOVMvS2hxOHUwZFk1U0EyR1VGMUViUU84Ckt3SURBUUFCbzFNd1VUQWRCZ05WSFE0RUZnUVVBKzZxL2tjOGZUUVUxRURxekdSZktRcHE2bTB3SHdZRFZSMGoKQkJnd0ZvQVVBKzZxL2tjOGZUUVUxRURxekdSZktRcHE2bTB3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFOQmdrcQpoa2lHOXcwQkFRc0ZBQU9DQWdFQVF1dkZoMitIUUZ5OFZUY1VnYWxFVmlheXQxelFHdjRySVNtaXEzRzZJZVhQClhTNGd3cUhrRnpUd1p2bW9oVHdtT0N3Vy94RjRLZ3htRmJ5V05yRUpKRXFjYmVkcVVXVi8wQkNhRm1KdlVkZEkKK2V4L2lEM0ZlYnU4QUZJK0o4bEJIL0NlbkRpU0xIaGd5c2VZOHV3Um5Yc3NoWDVSbkRpckYxdUtyMUo2MzVhbgpHbHlGSU5Vcm5RbGd1RXZ0cjBlbkdVbHpUNXJXajR5MEFXVWRiWGk4dlJzaldvUThKYTBCeFRyWVloL2tPL0ZJClB0cVg3d3N4b0pNREVRNzF6aHdhN1dMUWMyZGZiMnJBcjF1QmgzcU53aVZCSU5CK3QzSkZ2NzJ4cXNXZ3VySUIKSWYyc29SVEkybk1lNWdURzFEZmQrVjI0amZhL3lJZ0FzTWpDem1HUUsyMHZvYlg0c0FWbm1QVmJaZzlTTEZaaQpNaWRrbjlPOVU2OE1FT2UzSWFzY2xkN2ZwNUprK0hyYkpVNi9zMTZFRVIvQWdEM09vajN3UmdqVENTK0FERCtqCnhvMk84Vlgya1BvMDNBTitpWWEzbkptbE1GekNyelQrOFp4U25QNUZxR2cyRUNFYnFxQTBCLzVuYVZwbWRZYVYKNDFvRkxzd2NGbTJpcUdhd2JzTE45eDN0dklDdUU5M0hZazFqNzJQelhhaVNMdHB2YW1IMWRSWUMrSFVNMUwwTwo0OUNOTVlKZUwvTmx5UXVaSm0yWDBxRE5TWG1STUw4SFU5c093V1g2cFBQSk96dXF0Z2R4Lytsa0dBZDJ3WkpVCklWYm1MNlF2emRidGEvY1NWd3NMdEJ6RzQ4YTFiNEtCYzdXTEhUd2JyZEJSVGcwVGtMWTRrdkNaZTVuTmw0RT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
|
||||
|
||||
minio-cert.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUdyVENDQkpXZ0F3SUJBZ0lVRytCME0ycnhucWpHZHRmbzBCaGV2S0N4MGdBd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2RURUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2tOaGJHbG1iM0p1YVdFeEZUQVRCZ05WQkFjTQpERk5oYmtaeVlXNWphWE5qYnpFUk1BOEdBMVVFQ2d3SVFtRnJaWEo1U1VFeEVUQVBCZ05WQkFzTUNGTmxZM1Z5CmFYUjVNUlF3RWdZRFZRUUREQXRDWVd0bGNubEpRUzFEUVRBZUZ3MHlOakF4TVRjeE5EVTBORGhhRncweU9UQXgKTVRZeE5EVTBORGhhTUlHS01Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFVgpNQk1HQTFVRUJ3d01VMkZ1Um5KaGJtTnBjMk52TVJFd0R3WURWUVFLREFoQ1lXdGxjbmxKUVRFUU1BNEdBMVVFCkN3d0hVM1J2Y21GblpURXFNQ2dHQTFVRUF3d2hiV2x1YVc4dVltRnJaWEo1TFdsaExuTjJZeTVqYkhWemRHVnkKTG14dlkyRnNNSUlDSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQWc4QU1JSUNDZ0tDQWdFQW5qdTd0cFF3dkYvVgprL011UmhySllWME1KcXRyRkovTlgrMU9MSmFNaEZYL0tZMTBMUCtCNjV3L3BsWkd5SnRidFVkV2o1d1pMclpRCm1KYjNwNFR0dUs0QlQxZ3UzYlNaS0lIUU5lQWc4MUtzTUdxKzV1WE9vUFdOckFoaDRoWU9KNDVtSXNZYmEwRGQKTzJNRnY5V3VXVm4zVDZGenpNN3FMZENKelpOamVhQjdtVEpqZEhHcjg0aVQ4NkFFQStIeXd2c3FPb2paZStVagpLdThYcmp4VUdSL2VQRnZRQ3lNZFdnRmJqd2lqSi9CbjhSQ0FSSXVpRXNzalNMUVdPZ1FncklBVHZFRi9jeVVkClpLR2hhYzMvNEk3MXhEV2hYNzFYV1l3T05FbXJRNmNHelhtdmNVTVY4SHZFV016YjA1UnBPWXp5bUtyYnhOTDQKZVdOYUt2cnZjWnpjTXpwSU00UmVHS3cyTjlzQUdzM1lCVFI3V1hMS1dnbkxZYnNvSHgzZGRadXlRK0hKd0RUWApxcFh1dFloYW9DZmZIMjNuTU1GaUFLMWltZWJCSTFoVWNBaVB2cFN4N2RJM21nTlA0YWZOL29xaE1PUGc4VHhtCndNZWt2cHovN2NXYkNPTmprZDlkcTBWTExTVyt0cUlmZlZRajBMT1VQdlhyTE9tUG1jTDZsU2xSTzg4NVRWdngKSkRidDJYVVJtaHFKenBhcklmTmhGOUVscEhtYnNkc2xtWVBvLzlKV1VtcmtiSjZBYWZkbEpuckNUR3hKcGl3TAowbEpveEl3dnFZdDhEQnVjMWNORktKSVNMWkl5bzZ1WFJ1TlZvTnByeGdmVXZsOENscDNnUyttSVNGZzMzdTJrCkpjYnF6bnZ2YzN0YmxIZTB4ZzJNSE1JVlRkWmlSamNDQXdFQUFhT0NBUjB3Z2dFWk1Bc0dBMVVkRHdRRUF3SUUKTURBZEJnTlZIU1VFRmpBVUJnZ3JCZ0VGQlFjREFRWUlLd1lCQlFVSEF3SXdnYW9HQTFVZEVRU0JvakNCbjRJaApiV2x1YVc4dVltRnJaWEo1TFdsaExuTjJZeTVqYkhWemRHVnlMbXh2WTJGc2dnOXRhVzVwYnk1aVlXdGxjbmt0CmFXR0NLVzFwYm1sdkxXTnZibk52YkdVdVltRnJaWEo1TFdsaExuTjJZeTVqYkhWemRHVnlMbXh2WTJGc2doZHQKYVc1cGJ5MWpiMjV6YjJ4bExtSmhhMlZ5ZVMxcFlZSUZiV2x1YVcrQ0RXMXBibWx2TFdOdmJuTnZiR1dDQ1d4dgpZMkZzYUc5emRJY0Vmd0FBQVRBZEJnTlZIUTRFRmdRVXJXMzNxOWkreE5MdVZjcGUrKzlxUE56dVF4VXdId1lEClZSMGpCQmd3Rm9BVUErNnEva2M4ZlRRVTFFRHF6R1JmS1FwcTZtMHdEUVlKS29aSWh2Y05BUUVMQlFBRGdnSUIKQUlTT0NieFJWd2xtaWdjNldLM3hUaUJxNlJGMGNzdnV5NjJNYnI3N0h0Q3VPNHgxOTI5QjAxMXd1djdnWEhmawpPQm9qa3ZwZnFQUXlRZTk2dGFwRGJqYWZpeStlSHBPSm1lQjFNN2lQKzEzTGJJRjN3alE5SXZ1TWtnN3FQczZXCk15cnBvd1ZwK1BPeDU2SlJRK3lPcm5nakgxRG9FMW45NDBJR0lTZkRmb2g3cTljMkNvSlA2cWo3YWxid1U4RU0KYlB5d3B4WkFTNjYydUtBR0VNcFNLK2NuMXdUU3ZWSDN6NDVrMk9yUmwvQ05PZ0Fad1dyNzdQK1A3bW9FSHlmUQplR0dpclJTWWswUkJtYzdOTGd0Ry9iV0JQTEt4dHIyQmZidDFwZFZXakd4TmlwaDR4c1Z0YldpNnVOeUxYNE1qCllyK0FVUjd1MHlCVWxSc1VUL1dDbkFYdnRmNzRwcWJaNDZ3YjFnajEreU1GWHRNUldVV2NFcU1GVXRJdEsrUngKSlA4bUErbW9qdEdOcGdJZG53b1pPMTBsQkZ2U0ZKL1hGUFlsbHFKOGJpWmJ3RDZtWElzei9WQmdDRHlyQ3kybwpQeVhzR29HNDdTZkovQldvdHUwRkNaZERreCtQU0k2bkdKdyt2empSVzJ3TU9tdzJiZ0xkK3dsVDNpTXp4V3VOCkNidk0wSmpTQ2J3YVMvdE84emtrNGROeVhkWWNQbkJPNVJlM1IrQUV3T0RxV2F4T0ZXYmVUWW10bHlOTXdNT04Kd2lpR3pLWjkwaHM5QSt6M2x0QldNNmxNOFBJaFplcHB1TEZNTDRMSjZ0Ti93anJrOEVVMFBNT2ZlUTVjWXprZAp3QXdiRjVXaVhDd2JtaERCbW4xVVBrMjdPQUV0TzRSM3luaXM0eGNJbmVTQwotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
|
||||
|
||||
minio-key.pem: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKS2dJQkFBS0NBZ0VBbmp1N3RwUXd2Ri9Way9NdVJockpZVjBNSnF0ckZKL05YKzFPTEphTWhGWC9LWTEwCkxQK0I2NXcvcGxaR3lKdGJ0VWRXajV3WkxyWlFtSmIzcDRUdHVLNEJUMWd1M2JTWktJSFFOZUFnODFLc01HcSsKNXVYT29QV05yQWhoNGhZT0o0NW1Jc1liYTBEZE8yTUZ2OVd1V1ZuM1Q2Rnp6TTdxTGRDSnpaTmplYUI3bVRKagpkSEdyODRpVDg2QUVBK0h5d3ZzcU9valplK1VqS3U4WHJqeFVHUi9lUEZ2UUN5TWRXZ0ZiandpakovQm44UkNBClJJdWlFc3NqU0xRV09nUWdySUFUdkVGL2N5VWRaS0doYWMzLzRJNzF4RFdoWDcxWFdZd09ORW1yUTZjR3pYbXYKY1VNVjhIdkVXTXpiMDVScE9ZenltS3JieE5MNGVXTmFLdnJ2Y1p6Y016cElNNFJlR0t3Mk45c0FHczNZQlRSNwpXWExLV2duTFlic29IeDNkZFp1eVErSEp3RFRYcXBYdXRZaGFvQ2ZmSDIzbk1NRmlBSzFpbWViQkkxaFVjQWlQCnZwU3g3ZEkzbWdOUDRhZk4vb3FoTU9QZzhUeG13TWVrdnB6LzdjV2JDT05qa2Q5ZHEwVkxMU1crdHFJZmZWUWoKMExPVVB2WHJMT21QbWNMNmxTbFJPODg1VFZ2eEpEYnQyWFVSbWhxSnpwYXJJZk5oRjlFbHBIbWJzZHNsbVlQbwovOUpXVW1ya2JKNkFhZmRsSm5yQ1RHeEpwaXdMMGxKb3hJd3ZxWXQ4REJ1YzFjTkZLSklTTFpJeW82dVhSdU5WCm9OcHJ4Z2ZVdmw4Q2xwM2dTK21JU0ZnMzN1MmtKY2Jxem52dmMzdGJsSGUweGcyTUhNSVZUZFppUmpjQ0F3RUEKQVFLQ0FnQVhHQWE4amdKUzYvWERBeUlFejFJRzZNcW1OaXlKdFEwSGJCNFZ1ZDlHVFRyUmVMaTAvSkdjcnBCSAptWjM1RjF1YUtKQkVvM2ExYjV4eHVNN3FYeWRHNWZhQSt4RFVBTkM5cmJ5U3NHUit2dGtzczllcTRXMTM1bjdICjFlMWJUdmEvNVRPWTdhc0F5MVcrbmlRdnJHTW0zVStRQ3JOWTkvWUx1N3p4Q1FyaXJINTlqSEloZzVtaUVKUHYKWWJKVVVyellva20yZzFTaWxYMjlmV25LWHpteTlRaTliSFQvdXg5RWpLQXRUd2hwQXRoWXdaekc1RTVDU2UyYgpaZFU4b0crWVhaVUR5OWRyR2NhaGNrbVpwSndzelJDbmsyQTdGZXBTd25Nc1JIZy9obmdpc3hqZEFmcUl2N2VYCmNrYS9LWkQxK2xGSjROMzBhd29peFZKYXBZY2VwZk1hMS83dE1vZFFsOXdaOVZLWTZ6YlEwL1U0QndlMGQ0OEYKQ1graVlOZ2t4UWRmdVdwMFU2RkVlUTluR2tPMndZQUJxMCtzSDIxU2puRTQvTXh5anpLZCtjR08zUkdkTktxUwo5QTVubkh4MUwxVDN6Z0hOR2ZHS1F6Tzg5L09sVDBWVE80OEhkamxva0hmc3VTVG03N2tkZkU1TVFwamF2WktaCmo0QXoyWENGWkM2WkJxYm9wZlA1amVNWmI1WDU0aXVtclIwcHpRRGloQ3ZZWmYxTlVDa3hFdFZmaTF1eUtvLzYKMzhQK0pDcEtWSk1mYzhyYTFlWVRTV0ZaZDc1UXVMK1FtblpPVUNqQktXMnNQQTVGbERyTkVTdTQrREhCVVFtOApxdUxDUGdLaHA1TmVJRDVjcm5iVElYclVCb2tQdHpsWm10SEs5TFRYeTNPWkdXUmt5UUtDQVFFQTF0OFRhdWdCCmpMUVI2NXBTbGRXTDdVSnVGVlZUVW9DSlB5cHlOQjkvc1VsTC9Nd1RBbHlhWHoveU15Q2VCdWt3cnBMT1M0NHMKaG5kQlJOL3ZsdkRCaEovVjdYaDBEUWUvMGlqczRJdGNYQ1lpN3hFcWZOd1FQTUJEKzVyWkdKeU1iOEtLV3YwSwpBUnhES0k0YytLUkQwemQ1d1ZtelZSTjdLZlUzT3FXbGV1TjNMTFZqN3R6YU9kT2xSU0E3YWlCTS9odWQ1VFE5CkUwcEF3SDhIaGMxYW1qaUM4dEJsYUZlZ0lodXpJenhNU1hIUkJVcDNsaDMvb2UzNjM4Mm5zRUxjbE4xaFVWRGsKdDNUQVpjdHlYRkIzSEUydHpJdm9xRUpRN0Zkd3MwNUVQZXFIODFOekdjRlRNS1NieVJzNmtYYzhFQ0hPc2lYSAp6TDd5dlI3S1BmVHZhd0tDQVFFQXZJVlZRV3lpcU5ScTdTQkd3czg3WjVjZFlJOGdwSkI4bFlySklqaTRyVUVFCk14MmdVeCtYaHM5QTJSczQxZ1hsYXdvRWNqUDliZXJ2ZTYzMVZOV0M0K3Q5cFR2Vm9qcVhtcnZaNVVEN3V2Q0kKRlFPLy9JSUdqa0tFZkRwSUgvcWxEUlZlbEZTU1JjOVEvY0piZlNwS2JsYnJYZ1FtdG5KOWpsQkpFL1NMSW14UAo3OURVdGlmWmx5cFVRbDl5YzhSZzFSYmpyQWtjQVZhOVBHMXQ3cGhTanJkZHRKbXRVUmtFdGhYWTc3R3c5WHJUCjgwWlJHdkpIS0lsWlBmaHF2WlNGQzg4MVJJZ0lpRitCdWxobm16TUo0dmdYeXEwVCtRY1VGN0FBdFBRU0hyMHIKQm5wN1JlUDF5R201UDd0MjNmRU00Z0R1RENBUHQ0R1lZeUxFY2dpelpRS0NBUUVBaE9MVGJITnR1ZW9IaHpFYQowQ1dRY3p4NVBtSlZ0SmxmeUJ2bEkwMHp1SjMvQzZuZU84Q3ZqQ2JORUVlazA5dFZ5ekZwdWhxRWVPaTZDZkdBCmlGWC9LSmw5UVc4VVBwYkRVQ01WVkUxNzRsV0hsMWlEY1ZMY0MrWlFaUVBBTGROcm14YXlZRkZMNWFIbit1WGgKRHZqd0pXbVN1RHhVaDFJVUFyL3YxeXBvckJhUE5xdzcwSmJ2czRHc0haTXdpNUxNYXY4RGFLUWsvWkFYZWJWVwpIcThBMEk0UWxrREI1b1VDdVBWdWxXVU9QUUhSNWpiR3ZLVnkybCtHbnZEZU8wa3VpRFpkb0YrcUE3ZUY0YTZ2CjNGMjdQRnJpR0xXU1ByVTh2TjNiQ2xsbUpQQ3VBWk5qaE5NbU10Z3FySFpWZzI4OVN6RE5WeW04Wm1qVlVKY0IKTnM0TFh3S0NBUUVBdDRua0tBOFpDZC9NdmxJbk1qREorQit5ZFRqRG9oUWRod1lZcmgybEJ1QitzemxMeHNIQwpKM2lOL1JFNHMzNElEcjh3OXZMUThIdkRicGs5ZWJ0cGRIYm4yNysyVFB4WWIwZ21hc0ZxazJUc1IvRmZyL256CllmczJ1eStPMnJ1T2gzOWZsbkFEL0wxTGI5TVNlWGg4QUpMVkViSmU4ay9qRjNQb3dlbmFyOGZkeDNCOE4xL3kKd3U1dUhEU0szRlM3cFpwa1REQ09PR3QzVDJhR21iMW8yeE9Bd255L3RXM3pIVWVGN2s4RUp1clBnVkRiVTYyLwpRNkw4NUkxL2RsVXJkd1RrS25WNlFUTWl2UWFtei8zUHlVNmE4ekt3ZUVuQThSTGtqVWYyZ0VEUnE3d0JXbGtICkNIaU41NU9ldFpPaVpFSmRnQ2FTeHFrQWNMdi9uN29DMVFLQ0FRRUFxRkNHVDFWWG4yUGEwdFQ2ZCtvRnZYYTkKSENVMTFEbG9ad1hUOTY4cmhGOEJSazdLRVVvZXpFdjZiTUZsdUwzak9jMDNkUUs1WlF0anZUQkZKYlc3NVZMVgphcnR1U0xiVS9CVytnRGtZWmszQ241Z1B6QzlIbGRDa3MrS0lDOHJBcUNPdW9NRzc3SFlOVys3ckJLS3did2w1CmtDQW1uSmE2NWZZczdDWXpEOThmb0crVmxsc25VWCttMUxMZUtjclBEZWlpcW5kQmFTWi9NRVJnWmE2SXZid2kKMDVtNnFqL3ZXL1ZiV05iNVR4Z2N5MWpOOXpRbWJONFJ0Zmdzc3NKRmZzS3JNS0lxVnp1NkNMcEJ4eXBOUXZHYQo0S3UzVFZGcm9zaFlxWUpMVm1xVklYT1dWZk9IQTRMT2VpNmtDZTlHaTQydjdqS014M0dEK25CK1BWbVFXZz09Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==
|
||||
@@ -0,0 +1,33 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: postgres-init-config
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app.kubernetes.io/component: database
|
||||
app.kubernetes.io/part-of: bakery-ia
|
||||
data:
|
||||
init.sql: |
|
||||
-- Create required extensions
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
CREATE EXTENSION IF NOT EXISTS "pg_stat_statements";
|
||||
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
||||
|
||||
-- Create monitoring user for SigNoz metrics collection
|
||||
-- This user will be created only if it doesn't already exist
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT FROM pg_catalog.pg_user WHERE usename = 'monitoring') THEN
|
||||
CREATE USER monitoring WITH PASSWORD 'monitoring_369f9c001f242b07ef9e2826e17169ca';
|
||||
GRANT pg_monitor TO monitoring;
|
||||
GRANT SELECT ON pg_stat_database TO monitoring;
|
||||
RAISE NOTICE 'Created monitoring user for SigNoz metrics collection';
|
||||
ELSE
|
||||
-- User already exists, ensure it has the correct password and permissions
|
||||
ALTER USER monitoring WITH PASSWORD 'monitoring_369f9c001f242b07ef9e2826e17169ca';
|
||||
GRANT pg_monitor TO monitoring;
|
||||
GRANT SELECT ON pg_stat_database TO monitoring;
|
||||
RAISE NOTICE 'Updated monitoring user permissions for SigNoz metrics collection';
|
||||
END IF;
|
||||
END $$
|
||||
;
|
||||
@@ -0,0 +1,60 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: postgres-logging-config
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app.kubernetes.io/name: bakery-ia
|
||||
app.kubernetes.io/component: database-logging
|
||||
data:
|
||||
postgresql.conf: |
|
||||
# PostgreSQL Configuration for Kubernetes
|
||||
# Generated for security compliance and monitoring
|
||||
|
||||
# Network Configuration
|
||||
listen_addresses = '*'
|
||||
port = 5432
|
||||
|
||||
# Connection Logging
|
||||
log_connections = on
|
||||
log_disconnections = on
|
||||
log_hostname = off
|
||||
|
||||
# Query Logging
|
||||
log_statement = 'all'
|
||||
log_duration = on
|
||||
log_min_duration_statement = 1000
|
||||
|
||||
# Log Destination
|
||||
log_destination = 'stderr'
|
||||
logging_collector = off
|
||||
|
||||
# Log Output Format
|
||||
log_line_prefix = '%t [%p]: user=%u,db=%d,app=%a,client=%h '
|
||||
log_timezone = 'UTC'
|
||||
|
||||
# Error Logging
|
||||
log_error_verbosity = default
|
||||
log_min_messages = warning
|
||||
log_min_error_statement = error
|
||||
|
||||
# Checkpoints
|
||||
log_checkpoints = on
|
||||
|
||||
# Lock Waits
|
||||
log_lock_waits = on
|
||||
deadlock_timeout = 1s
|
||||
|
||||
# Temporary Files
|
||||
log_temp_files = 0
|
||||
|
||||
# Autovacuum Logging
|
||||
log_autovacuum_min_duration = 0
|
||||
|
||||
# SSL/TLS Configuration
|
||||
ssl = on
|
||||
ssl_cert_file = '/tls/server-cert.pem'
|
||||
ssl_key_file = '/tls/server-key.pem'
|
||||
ssl_ca_file = '/tls/ca-cert.pem'
|
||||
ssl_prefer_server_ciphers = on
|
||||
ssl_min_protocol_version = 'TLSv1.2'
|
||||
124
infrastructure/platform/storage/postgres/postgres-template.yaml
Normal file
124
infrastructure/platform/storage/postgres/postgres-template.yaml
Normal file
@@ -0,0 +1,124 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{SERVICE_NAME}}-db
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app.kubernetes.io/name: {{SERVICE_NAME}}-db
|
||||
app.kubernetes.io/component: database
|
||||
app.kubernetes.io/part-of: bakery-ia
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: {{SERVICE_NAME}}-db
|
||||
app.kubernetes.io/component: database
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: {{SERVICE_NAME}}-db
|
||||
app.kubernetes.io/component: database
|
||||
spec:
|
||||
containers:
|
||||
- name: postgres
|
||||
image: postgres:17-alpine
|
||||
ports:
|
||||
- containerPort: 5432
|
||||
name: postgres
|
||||
env:
|
||||
- name: POSTGRES_DB
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: bakery-config
|
||||
key: {{SERVICE_NAME_UPPER}}_DB_NAME
|
||||
- name: POSTGRES_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: database-secrets
|
||||
key: {{SERVICE_NAME_UPPER}}_DB_USER
|
||||
- name: POSTGRES_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: database-secrets
|
||||
key: {{SERVICE_NAME_UPPER}}_DB_PASSWORD
|
||||
- name: POSTGRES_INITDB_ARGS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: bakery-config
|
||||
key: POSTGRES_INITDB_ARGS
|
||||
- name: PGDATA
|
||||
value: /var/lib/postgresql/data/pgdata
|
||||
volumeMounts:
|
||||
- name: postgres-data
|
||||
mountPath: /var/lib/postgresql/data
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- pg_isready
|
||||
- -U
|
||||
- $(POSTGRES_USER)
|
||||
- -d
|
||||
- $(POSTGRES_DB)
|
||||
initialDelaySeconds: 30
|
||||
timeoutSeconds: 5
|
||||
periodSeconds: 10
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- pg_isready
|
||||
- -U
|
||||
- $(POSTGRES_USER)
|
||||
- -d
|
||||
- $(POSTGRES_DB)
|
||||
initialDelaySeconds: 5
|
||||
timeoutSeconds: 1
|
||||
periodSeconds: 5
|
||||
failureThreshold: 3
|
||||
volumes:
|
||||
- name: postgres-data
|
||||
persistentVolumeClaim:
|
||||
claimName: {{SERVICE_NAME}}-db-pvc
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{SERVICE_NAME}}-db-service
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app.kubernetes.io/name: {{SERVICE_NAME}}-db
|
||||
app.kubernetes.io/component: database
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 5432
|
||||
targetPort: 5432
|
||||
protocol: TCP
|
||||
name: postgres
|
||||
selector:
|
||||
app.kubernetes.io/name: {{SERVICE_NAME}}-db
|
||||
app.kubernetes.io/component: database
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: {{SERVICE_NAME}}-db-pvc
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app.kubernetes.io/name: {{SERVICE_NAME}}-db
|
||||
app.kubernetes.io/component: database
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
@@ -0,0 +1,25 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: postgres-tls
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app.kubernetes.io/name: bakery-ia
|
||||
app.kubernetes.io/component: database-tls
|
||||
type: Opaque
|
||||
data:
|
||||
# PostgreSQL TLS certificates (base64 encoded)
|
||||
# Generated using infrastructure/tls/generate-certificates.sh
|
||||
# Valid for 3 years from generation date
|
||||
#
|
||||
# Certificate details:
|
||||
# Subject: CN=*.bakery-ia.svc.cluster.local, O=BakeryIA, OU=Database
|
||||
# Issuer: CN=BakeryIA-CA, O=BakeryIA, OU=Security
|
||||
#
|
||||
# To regenerate:
|
||||
# 1. Run: infrastructure/tls/generate-certificates.sh
|
||||
# 2. Run: scripts/create-tls-secrets.sh
|
||||
|
||||
ca-cert.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZ5ekNDQTdPZ0F3SUJBZ0lVUGdPcU5ZK1pvS0J5UTFNZk84bGtpR2hPbXhJd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2RURUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2tOaGJHbG1iM0p1YVdFeEZUQVRCZ05WQkFjTQpERk5oYmtaeVlXNWphWE5qYnpFUk1BOEdBMVVFQ2d3SVFtRnJaWEo1U1VFeEVUQVBCZ05WQkFzTUNGTmxZM1Z5CmFYUjVNUlF3RWdZRFZRUUREQXRDWVd0bGNubEpRUzFEUVRBZUZ3MHlOVEV3TVRneE5ESXlNVFJhRncwek5URXcKTVRZeE5ESXlNVFJhTUhVeEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlEQXBEWVd4cFptOXlibWxoTVJVdwpFd1lEVlFRSERBeFRZVzVHY21GdVkybHpZMjh4RVRBUEJnTlZCQW9NQ0VKaGEyVnllVWxCTVJFd0R3WURWUVFMCkRBaFRaV04xY21sMGVURVVNQklHQTFVRUF3d0xRbUZyWlhKNVNVRXRRMEV3Z2dJaU1BMEdDU3FHU0liM0RRRUIKQVFVQUE0SUNEd0F3Z2dJS0FvSUNBUURSRDVPMmVna1lnOUhOUlI1U1UwYkxuR0hqcHYvUmFnck03ZGh1c2FXbgpyZkRGNVZwVFo0czkvOXNPRUowTnlqdW9LWGFtb3VUd1IxbncxOUZkSDhmMWVvbWNRNGVLdzJIa3hveHFSMzR0ClJEYUFHejNiV08rcmFUUTRTeU1LN1hGTW92VVVpTGwrR08yM2wxQk5QZmh6a2NEa1o5N200MzRmMVFWbzk5dGIKaFY0YklMYW9GSXFmMDlNMEUxL2ZhQitKQ1I4WWtsN0xvWGd1ejNWUi9CVW5kMHZNc1RNV3VlRC8yblZ1VVpPMAowcFVtVFVCUTJRZDc2NTdrL0hXZC8xd2NFQUw5ZFhOUmJ4aEROZkdnYzNXdFFoZ2djcFlMUWFmTGE4MXRseHljCndEZ042UGRFbFVseGdYL091b1oxeWxNWkU3eHBzTXRwbjFBd2VvZFZibTNRcDVBMXlkeWJFNjF1MXVyWXoxTHQKV05aOWVPZkFxZXdpWVFIVlpXTUM0YTRTYSsyeU02cTVQWC80ZytUYklUaDhoWkp3WFBLNUVEaWc3dkYxNEpQbApsRVJOcHdpYTNuNmEwUDcwM0hQTjZya1FPNWtWVGRpVXNmaWJNdGNVSkhMeVdXUUFSQm15ZVZma0lDYWFlWUVsCkVMa3N3YTlOVkVTS3ZRYUhLU2lIWkZoRUkwYUF2Y3BBam0xRU9oRWEraFNSaE9vRnlVT3ZHK2NNT2ZjQlNtTDAKVW1sRC9sZmFuVFQwems1YXFzcEVrWEdlQnczMXJtWi8wQVpPalYycHBSeFdXZWt6bzlCZjdnNmVMVFk0VUNDNQpNeVB0em14OVRiWHJOQW5YaGlGNkxnNWgyOFI0MkdUZTVBZDZUSGtGOVMvS2hxOHUwZFk1U0EyR1VGMUViUU84Ckt3SURBUUFCbzFNd1VUQWRCZ05WSFE0RUZnUVVBKzZxL2tjOGZUUVUxRURxekdSZktRcHE2bTB3SHdZRFZSMGoKQkJnd0ZvQVVBKzZxL2tjOGZUUVUxRURxekdSZktRcHE2bTB3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFOQmdrcQpoa2lHOXcwQkFRc0ZBQU9DQWdFQVF1dkZoMitIUUZ5OFZUY1VnYWxFVmlheXQxelFHdjRySVNtaXEzRzZJZVhQClhTNGd3cUhrRnpUd1p2bW9oVHdtT0N3Vy94RjRLZ3htRmJ5V05yRUpKRXFjYmVkcVVXVi8wQkNhRm1KdlVkZEkKK2V4L2lEM0ZlYnU4QUZJK0o4bEJIL0NlbkRpU0xIaGd5c2VZOHV3Um5Yc3NoWDVSbkRpckYxdUtyMUo2MzVhbgpHbHlGSU5Vcm5RbGd1RXZ0cjBlbkdVbHpUNXJXajR5MEFXVWRiWGk4dlJzaldvUThKYTBCeFRyWVloL2tPL0ZJClB0cVg3d3N4b0pNREVRNzF6aHdhN1dMUWMyZGZiMnJBcjF1QmgzcU53aVZCSU5CK3QzSkZ2NzJ4cXNXZ3VySUIKSWYyc29SVEkybk1lNWdURzFEZmQrVjI0amZhL3lJZ0FzTWpDem1HUUsyMHZvYlg0c0FWbm1QVmJaZzlTTEZaaQpNaWRrbjlPOVU2OE1FT2UzSWFzY2xkN2ZwNUprK0hyYkpVNi9zMTZFRVIvQWdEM09vajN3UmdqVENTK0FERCtqCnhvMk84Vlgya1BvMDNBTitpWWEzbkptbE1GekNyelQrOFp4U25QNUZxR2cyRUNFYnFxQTBCLzVuYVZwbWRZYVYKNDFvRkxzd2NGbTJpcUdhd2JzTE45eDN0dklDdUU5M0hZazFqNzJQelhhaVNMdHB2YW1IMWRSWUMrSFVNMUwwTwo0OUNOTVlKZUwvTmx5UXVaSm0yWDBxRE5TWG1STUw4SFU5c093V1g2cFBQSk96dXF0Z2R4Lytsa0dBZDJ3WkpVCklWYm1MNlF2emRidGEvY1NWd3NMdEJ6RzQ4YTFiNEtCYzdXTEhUd2JyZEJSVGcwVGtMWTRrdkNaZTVuTmw0RT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
|
||||
server-cert.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUhjakNDQlZxZ0F3SUJBZ0lVRytCME0ycnhucWpHZHRmbzBCaGV2S0N4MGY0d0RRWUpLb1pJaHZjTkFRRUwKQlFBd2RURUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2tOaGJHbG1iM0p1YVdFeEZUQVRCZ05WQkFjTQpERk5oYmtaeVlXNWphWE5qYnpFUk1BOEdBMVVFQ2d3SVFtRnJaWEo1U1VFeEVUQVBCZ05WQkFzTUNGTmxZM1Z5CmFYUjVNUlF3RWdZRFZRUUREQXRDWVd0bGNubEpRUzFEUVRBZUZ3MHlOVEV3TVRneE5ESXlNVFJhRncweU9ERXcKTVRjeE5ESXlNVFJhTUlHSE1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFVgpNQk1HQTFVRUJ3d01VMkZ1Um5KaGJtTnBjMk52TVJFd0R3WURWUVFLREFoQ1lXdGxjbmxKUVRFUk1BOEdBMVVFCkN3d0lSR0YwWVdKaGMyVXhKakFrQmdOVkJBTU1IU291WW1GclpYSjVMV2xoTG5OMll5NWpiSFZ6ZEdWeUxteHYKWTJGc01JSUNJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBZzhBTUlJQ0NnS0NBZ0VBMWIvVlNmdS9QTXZZb3JiTAoyOTVWMlpBR1JSTld1cEhIM0s5eERBUG00NVR1ZGdQV0x4bnlBOUhWejVqbUtnV0hRS1ZyU0kwNDZ1THVFWUErClJtdGg3RkVWQ0x0OWk1aWZoYVhtQWZTb3VHOTFuQzJOQ3NobUVoWHRaQkpYMG9tYU5oaUREb3R4NzhrakthTFIKQTIybVFvQ2NQdmt6RXFPRUNwaVZGVTlVSEIzQzV1bm10SFNDNDhiQitBUnlpRTJ6N1JyYUcxWUVLa2lsamlsRgptSlRTNk4zNkJxYWJGNkF4cVNwSWFub0VnRmdXQzZhSVh0QStmbzNFejFtSkVGd2Z6UUJXY0t0L09OM254M3hECmJSTnNtb3J4SHBzUGluT0E0aEhWdzdUY1U0THFxVVJZZGROb2NtYmtLaVZYSlpFRmdMZW5nQjFsbS9sQVlXcVoKUWRQYlQxVWNDZlFMdlN0NmxWaytWQjA2ZVo0WktmaS9rb2ZsRlAwZisyU0IyaFE2YWo5N0cvUmJya0NHYUlGWApDeDVkNjlBb3FTd3VFeHRYL1FtMVVLME8yeHBMdjM1S2RTY3krWjFJRk9jWXpjWHEyOGZ4bXUrVERETnlTU2NLCmxzYmp3ZnU0RUdLR0xza3RHdlRBR0gxRXlLdktrc3F4MEV4OXMvOHZBaS8yVDQrRkMxQmwyNUI1ZnpERUQ1RHAKS0h0SmF0eHdqV2lpRGxheXJrOFdnMDNSeUZTZjVuNEY3UmJwMytvRm1zU1NuRUVaK1JDT25DZ3FDWlkxTXM5cgpGVDlmejdoQXMyK1hQZXB1MHZ3RktCVXdseGlXZER6SDZzRElRQ2VTM3hTMjQzdnlpYXRFdTZLOEM3eDBlV2xzCjU5SUJRcXY1eDJUYkZ0VHdEWGdiK1NKMGsyVUNBd0VBQWFPQ0FlVXdnZ0hoTUFzR0ExVWREd1FFQXdJRU1EQWQKQmdOVkhTVUVGakFVQmdnckJnRUZCUWNEQVFZSUt3WUJCUVVIQXdJd2dnRnhCZ05WSFJFRWdnRm9NSUlCWklJZApLaTVpWVd0bGNua3RhV0V1YzNaakxtTnNkWE4wWlhJdWJHOWpZV3lDQ3lvdVltRnJaWEo1TFdsaGdnOWhkWFJvCkxXUmlMWE5sY25acFkyV0NFWFJsYm1GdWRDMWtZaTF6WlhKMmFXTmxnaE4wY21GcGJtbHVaeTFrWWkxelpYSjIKYVdObGdoWm1iM0psWTJGemRHbHVaeTFrWWkxelpYSjJhV05sZ2hCellXeGxjeTFrWWkxelpYSjJhV05sZ2hObAplSFJsY201aGJDMWtZaTF6WlhKMmFXTmxnaGR1YjNScFptbGpZWFJwYjI0dFpHSXRjMlZ5ZG1salpZSVVhVzUyClpXNTBiM0o1TFdSaUxYTmxjblpwWTJXQ0VuSmxZMmx3WlhNdFpHSXRjMlZ5ZG1salpZSVVjM1Z3Y0d4cFpYSnoKTFdSaUxYTmxjblpwWTJXQ0RuQnZjeTFrWWkxelpYSjJhV05sZ2hGdmNtUmxjbk10WkdJdGMyVnlkbWxqWllJVgpjSEp2WkhWamRHbHZiaTFrWWkxelpYSjJhV05sZ2hwaGJHVnlkQzF3Y205alpYTnpiM0l0WkdJdGMyVnlkbWxqClpZSUpiRzlqWVd4b2IzTjBod1IvQUFBQk1CMEdBMVVkRGdRV0JCUitaeU1BTUNNeUN2NTBNSlRjSFN3MTNWVjkKM1RBZkJnTlZIU01FR0RBV2dCUUQ3cXIrUnp4OU5CVFVRT3JNWkY4cENtcnFiVEFOQmdrcWhraUc5dzBCQVFzRgpBQU9DQWdFQUM3V0NOM2FKdzR2VDNOcjVmV3Fqa3p4Y2wrc3BUUnlCREViSlpZcDNIZEszUU9peGhUZDBCM2JGCkZ6V1NJWDc5R3Z2cy9yajhTWkRYUDNCZHNTcG9JeFRKZitpbnpQbThoUFJvMmN1U05qTzl5aGYxdTFBQnliMmcKZVdtMkw1OGRMTElZbmdjc2wvQWFUaGlmT3VLZlZjN2tYNUY1K3BwSGxXRTRJTkdhT0tsMlpxQkxwT20rNG5YcAo3OGlCQXRmSEtWTG1NQmtJRHNZZ1g5RURVNGdZWWVyU0V1WTNUYWM5NGVhOW5FY0dwdkhEaEdSYk5SUzQ2RmwvCk8zVmoxOE9TK0tkZE1lckF1ZU5qdm9wNXZzSzBUNk1DZWxMT2hscnRvTWVOSEVjd3prQkx3anZEbzJHV2FIbU8KU3lKTndTRUFqbHlVMXJyYTBUWHRweU1nNi9jbldicjlnS2hybmYrTzBDTUdMdVYwOEZpUEN3YTYvcW1QYWlLQQppMCs2VGJ1c2JGTEdrZVdDUEszanlaRmFsU1puV1BINWRMSkV3dVNZZTlTRnhaVlpTSFNNV24weXR2NUh1Wk5qClpJbnh2YmpqNlMrYTVTZVJjNXB2ako1Vk1Ea2tRSjM0bUJsMjJub3lCaXA4Y3J1UWdBN3l6SG45c3ljYkF5VGsKWWdOWEpIbmI0UW11dHJiTTd6YnFrR1BORlhFQnl5VmFZL203WnJsRTNvRzRHUmxOc3NtS3lVZ3ZMUHhVbWdZSwpwNFg1eERoUlFsNE1WNDkvL0E1RjYrcVM2dXIrQitwOXhIb0xKZm9CUlRTVytTNlB1YmI0d1FINDl6cDNObW05Cjk0YVRoaktISzhUaU1iSkErYlEva0YyT25KWXVua3VjWWpZek52ald3ZjFTL3JlQmcyRT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
|
||||
server-key.pem: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRUUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQ1Nzd2dna25BZ0VBQW9JQ0FRRFZ2OVZKKzc4OHk5aWkKdHN2YjNsWFprQVpGRTFhNmtjZmNyM0VNQStiamxPNTJBOVl2R2ZJRDBkWFBtT1lxQllkQXBXdElqVGpxNHU0UgpnRDVHYTJIc1VSVUl1MzJMbUorRnBlWUI5S2k0YjNXY0xZMEt5R1lTRmUxa0VsZlNpWm8yR0lNT2kzSHZ5U01wCm90RURiYVpDZ0p3KytUTVNvNFFLbUpVVlQxUWNIY0xtNmVhMGRJTGp4c0g0QkhLSVRiUHRHdG9iVmdRcVNLV08KS1VXWWxOTG8zZm9HcHBzWG9ER3BLa2hxZWdTQVdCWUxwb2hlMEQ1K2pjVFBXWWtRWEIvTkFGWndxMzg0M2VmSApmRU50RTJ5YWl2RWVtdytLYzREaUVkWER0TnhUZ3VxcFJGaDEwMmh5WnVRcUpWY2xrUVdBdDZlQUhXV2IrVUJoCmFwbEIwOXRQVlJ3SjlBdTlLM3FWV1Q1VUhUcDVuaGtwK0wrU2grVVUvUi83WklIYUZEcHFQM3NiOUZ1dVFJWm8KZ1ZjTEhsM3IwQ2lwTEM0VEcxZjlDYlZRclE3YkdrdS9ma3AxSnpMNW5VZ1U1eGpOeGVyYngvR2E3NU1NTTNKSgpKd3FXeHVQQis3Z1FZb1l1eVMwYTlNQVlmVVRJcThxU3lySFFUSDJ6L3k4Q0wvWlBqNFVMVUdYYmtIbC9NTVFQCmtPa29lMGxxM0hDTmFLSU9Wckt1VHhhRFRkSElWSi9tZmdYdEZ1bmY2Z1dheEpLY1FSbjVFSTZjS0NvSmxqVXkKejJzVlAxL1B1RUN6YjVjOTZtN1MvQVVvRlRDWEdKWjBQTWZxd01oQUo1TGZGTGJqZS9LSnEwUzdvcndMdkhSNQphV3puMGdGQ3EvbkhaTnNXMVBBTmVCdjVJblNUWlFJREFRQUJBb0lDQUFYcG5nZnVDa2xhRVg2Q3Z2Q0YzY0JuCkR2MVhLUnJWRUg4UmJVenZTTEk2a0Q2OGRzUVdjWG1GNkR1clY1RTBWa2J3QWNxS2VZSVVlcEJmczBQMXZCK0gKZmZwS3NXWXNvbkVBZUo4aU9qazJnQkxYWWJqa0lvcXFoNXdHaVRPemh3d0FXK2tKbGhlK0ZtdSs2MkxadVhQSwplZktncUdIWGNHakRTcnNYQVgvR1JQb1NpMFZVN3cveVBnaFRHeXRHWWFLVDVSSkUxcTJvRlIyY2FsRkJBSi9jCnVyU2lEdFUxb3dTeVo0Njd4dnh1aUt3KzFEUGNpbllCWVpSNHZoQUdud0huMmZ4RGlpdmVXNGNnUlZTSVBvU24KTU9udlVSdm1lN0N2M0p3TmJkdHpoTWswMjV1UUdGU3pJRDd0aWRPL1hMUndTc0VDVHlsa0xpYzVCYzgvMXllZwpKcmxyRU1hNW8va3hvRGtSWjNQMHhZd29mS3gxMWVlRXA3SmNoaHRmajRzYUNWeEw3aHlmTzVQRTFSWTV2UHVmCjlqcEVGUTNJbDBsMjRRUTU4TWthN0gzZCtSdzNjbk1MYkpTSEE3MUdSOWFqZS9WUVVPcmF5MG1XZnRkVjYrVGEKWlAvdDBrL2pqcWxxUmlxek9ZMDhrMGY4dGptamhzdHgxQ1laaVJQQVhydWlFb1N3UzRGQVV2VHdSeW8wODdJMgprZ3NYbTlGd2NFWkRVbXpsMGxURy8xYk5sSEVsdWx5cVJMd1plUXdMcEF0ZTNFdmpNQzE3TnVCbkZUOFd4bHRjCjhzVGRUczNNSEZNdnZiM3IvaXJjelkwTncvdzd3czhOWkZ1VWxXMm4xTE9iWkNrTUNIaVc2RldPRUFhSmNzbXkKMXlQbEFaMXB0cGJ3b3IxdzAvMTdBb0lCQVFEOEFJMlpvNktMOTRRMGpPYmltcm5yQlRXUW5aSFp4U240L1ZQTQpTQ2hOZ245bHR0QlJxMkYyYXZlU0xHMlRhRjRCUWRjR0tSQTV1ODZPdnp3N2VFWUpkdEsvQnBQRjZFa0hERlR2Ci9EVXBTaGEvZ2JCa3FtUmFESmZGamc5cE1QWmR2Z0VWeHlMWW1zUlliLy9FOFI3dUxlbDA0aXlwQ1UwSVNsMmMKZlVOTGZXa0NBNGk0Y21kSE1xdEd0bm9LbnNXcVYzVWsybUVRSkpZSTY2UERtcjNLVndvUk1vcVpNUDRwcjE3NQpSSG5rQTZmOWxFVzh0a1VYbnV0Vmk0MW5zOEpoRlpmblFaREtmWGR1VDQxN0dDSGVHa2tXblhpenQ1ejZNdVdtCmhMbFErUDY5UzZpVlNRUU5uS3JaWnVFdUZOVE1ublRTZ1ZPdWZuUkxWWDFjZDRFTEFvSUJBUURaSSt6aWFxenAKRktqdmxaUnlCa3A3aUYrSmpkUDc5QmRmZUswU0pWT3QrRzROVU5RT1BMVXhjdUIxeWVYd3Z2NVhWaWtkOHR4SgpGbVZMcUNNNjlhODFSbkhHNnVOTlNVMW1vRTFWQVgwd2p3ajFoaStuQUdjZWdJcGRpdHpKckJpUDhsZGtWNStyClpIaUM1L1F2SDcrUVphQXVRNnMwZmdoUEk3cXNmWFNucU5TNVcxNEFzYWJNcVBZUHhHcjRQMEJPaEVjZ2R4dFIKRjY1SFB6OXY5clFkOUxtT2JJWTMxOENrTTdtY2ZzRys2Y2tBd3RRVWdGdmVmZ3RTOG4vMGR0Rm1Ca0RUZkF4cApBU2ZENWk2Nkw1Y3g2Qm5VTzFnc2dNUHBMamtzaDVEMXFaK2d5Tldrd2xRbERSTHM2SXVCRVc0dkVuSWMxYVNsCi9BUE95MnBNMWVOUEFvSUJBQkVIeElvR2lmeWxqSlMwbFFIcGJQa2FFQVdtOEcxa0tyTCtBOFRCZDUvTld1aTMKMHhwQjE4TlY5VWMybzIwYjE0YUVPWkRjQTVHelJJRlhJUzN2c2VQLzJMdzZLSkJ1WTBrTHAwM1VvSThheDdESApoZkUzcHJLRE9WcUxnRFVlcnZlazJKUHRNa2lySk92SkhlTGtYSy9DQUkzNm53UUpjZUJHams3K0ZDY3M0WVRXClVrNE14VGdGajVlbXkxYWVaa05keDdmbTNqcG1EcEdwd3haOEJhbC8rbGt4TGphdUhlOFpQL1Rla05JOUFRUmQKR2Qxb0FBRlpweFBQNjQxL2szcFdLRDdqcW5KVXlsWjFIOTJhd3Vjc3BaWFdySXFRdFJZZmpHK1ZkcVNuUHlmeAp6Z0hRdm1waEZSYStJaWVvRnIyQlUrbkovYXJFTnYzRVdFV0FlZ01DZ2dFQVQxVVl6d0E2ZkUzWUN2Q1RjN1ZvCnNRbDZIajk3RzZwcWY2OFBUSG5td01Eck5HSTdsNWdHZXpLRlg0T01SeEVBeTlmbTNkSkZPVTY5WTQ3aWtFQUMKNjJ2NVZidXJvQ2tQNWxiYTZodkpLVnlZNFZ0TlBhNmYvanpvVUpUVFpidENuaFRrYVB5NmtWdjd5NWdEVnRRNgpvUDhBTHViNlBndHQ3YndZRDcwbVNic2RQVHRzZE1Sek5JTG1vNHdYcU9zekMzeTRuOXZrVnhSWDBDQURoVnlWCklmeXZicUdueCs5RHFycGJMaG9CbjBhNjhWUTlOK0JOc0ZSTXZ0bHFkbDZTMHJ1bUk1NUd5blpwbU9FWVlWM1IKMTZIOURkVkF1Y0d4MGhmWk83T3IrcFVtaFEvYlBuN2hUMGdmaWY3TU9UT3RGZldmUzNtaTFpSGxJa0NmYmNNWApjUUtDQVFCY25sMFRDVU1JZFhiTW5JRzEwQ29MOW15SFdsMUpqSFYrTDdJOVRRL09rdnV4dUlvSlBSYnBQVURLCmRuQkNXM05ZODZ6c2dMKytJNWIyTFdoWHpTamxBZ1pTRDVySklCenY1Lzh5ekdoNUVaSzUxOXdnL2JDYkpMREUKTFFsUTcrai9CS1VsbG1ySUtENHZva2lyOXJvbkdKblROSjlhU09kdEQ1ZDd1M1ZCZkpkRGltS1J0M3VVcHdabQpCbkxyTFlaS21SVW5aK0k3SGdyd0NPNSs4MTVXNlh1dDlQaGR6NnBwOXJUK2Z5b1VoeTFWK3VpdTJhVDFQbHJTCkhTdUFvdFdBa0lZS2I1ZWlQd1NBeXRvbWdmYnA3R2JBRTRtY1A2d0l1eFhMbkJneVpIbzBhM3FCY3drRnlXYjYKMStBR3cyMFcyaHZnY3dKNDRjTEgySUUyOGR5NAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==
|
||||
177
infrastructure/platform/storage/redis/redis.yaml
Normal file
177
infrastructure/platform/storage/redis/redis.yaml
Normal file
@@ -0,0 +1,177 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: redis
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app.kubernetes.io/name: redis
|
||||
app.kubernetes.io/component: cache
|
||||
app.kubernetes.io/part-of: bakery-ia
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: redis
|
||||
app.kubernetes.io/component: cache
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: redis
|
||||
app.kubernetes.io/component: cache
|
||||
spec:
|
||||
securityContext:
|
||||
fsGroup: 999 # redis group
|
||||
initContainers:
|
||||
- name: fix-tls-permissions
|
||||
image: busybox:1.36
|
||||
securityContext:
|
||||
runAsUser: 0
|
||||
command: ['sh', '-c']
|
||||
args:
|
||||
- |
|
||||
cp /tls-source/* /tls/
|
||||
chmod 600 /tls/redis-key.pem
|
||||
chmod 644 /tls/redis-cert.pem /tls/ca-cert.pem
|
||||
chown 999:999 /tls/*
|
||||
ls -la /tls/
|
||||
volumeMounts:
|
||||
- name: tls-certs-source
|
||||
mountPath: /tls-source
|
||||
readOnly: true
|
||||
- name: tls-certs-writable
|
||||
mountPath: /tls
|
||||
containers:
|
||||
- name: redis
|
||||
image: redis:7.4-alpine
|
||||
ports:
|
||||
- containerPort: 6379
|
||||
name: redis-tls
|
||||
- containerPort: 6380
|
||||
name: redis-plain
|
||||
env:
|
||||
- name: REDIS_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: redis-secrets
|
||||
key: REDIS_PASSWORD
|
||||
command:
|
||||
- redis-server
|
||||
- --appendonly
|
||||
- "yes"
|
||||
- --requirepass
|
||||
- $(REDIS_PASSWORD)
|
||||
- --maxmemory
|
||||
- "512mb"
|
||||
- --databases
|
||||
- "16"
|
||||
# TLS port for external/secure connections
|
||||
- --tls-port
|
||||
- "6379"
|
||||
# Plain TCP port for internal cluster services (Mailu)
|
||||
- --port
|
||||
- "6380"
|
||||
- --tls-cert-file
|
||||
- /tls/redis-cert.pem
|
||||
- --tls-key-file
|
||||
- /tls/redis-key.pem
|
||||
- --tls-ca-cert-file
|
||||
- /tls/ca-cert.pem
|
||||
- --tls-auth-clients
|
||||
- "no"
|
||||
volumeMounts:
|
||||
- name: redis-data
|
||||
mountPath: /data
|
||||
- name: tls-certs-writable
|
||||
mountPath: /tls
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- redis-cli
|
||||
- --tls
|
||||
- --cert
|
||||
- /tls/redis-cert.pem
|
||||
- --key
|
||||
- /tls/redis-key.pem
|
||||
- --cacert
|
||||
- /tls/ca-cert.pem
|
||||
- -a
|
||||
- $(REDIS_PASSWORD)
|
||||
- ping
|
||||
initialDelaySeconds: 30
|
||||
timeoutSeconds: 5
|
||||
periodSeconds: 10
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- redis-cli
|
||||
- --tls
|
||||
- --cert
|
||||
- /tls/redis-cert.pem
|
||||
- --key
|
||||
- /tls/redis-key.pem
|
||||
- --cacert
|
||||
- /tls/ca-cert.pem
|
||||
- -a
|
||||
- $(REDIS_PASSWORD)
|
||||
- ping
|
||||
initialDelaySeconds: 5
|
||||
timeoutSeconds: 1
|
||||
periodSeconds: 5
|
||||
failureThreshold: 3
|
||||
volumes:
|
||||
- name: redis-data
|
||||
persistentVolumeClaim:
|
||||
claimName: redis-pvc
|
||||
- name: tls-certs-source
|
||||
secret:
|
||||
secretName: redis-tls-secret
|
||||
- name: tls-certs-writable
|
||||
emptyDir: {}
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: redis-service
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app.kubernetes.io/name: redis
|
||||
app.kubernetes.io/component: cache
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 6379
|
||||
targetPort: 6379
|
||||
protocol: TCP
|
||||
name: redis-tls
|
||||
- port: 6380
|
||||
targetPort: 6380
|
||||
protocol: TCP
|
||||
name: redis-plain
|
||||
selector:
|
||||
app.kubernetes.io/name: redis
|
||||
app.kubernetes.io/component: cache
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: redis-pvc
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app.kubernetes.io/name: redis
|
||||
app.kubernetes.io/component: cache
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
@@ -0,0 +1,25 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: redis-tls-secret
|
||||
namespace: bakery-ia
|
||||
labels:
|
||||
app.kubernetes.io/name: bakery-ia
|
||||
app.kubernetes.io/component: redis-tls
|
||||
type: Opaque
|
||||
data:
|
||||
# Redis TLS certificates (base64 encoded)
|
||||
# Generated using infrastructure/tls/generate-certificates.sh
|
||||
# Valid for 3 years from generation date
|
||||
#
|
||||
# Certificate details:
|
||||
# Subject: CN=redis-service.bakery-ia.svc.cluster.local, O=BakeryIA, OU=Cache
|
||||
# Issuer: CN=BakeryIA-CA, O=BakeryIA, OU=Security
|
||||
#
|
||||
# To regenerate:
|
||||
# 1. Run: infrastructure/tls/generate-certificates.sh
|
||||
# 2. Run: scripts/create-tls-secrets.sh
|
||||
|
||||
ca-cert.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZ5ekNDQTdPZ0F3SUJBZ0lVUGdPcU5ZK1pvS0J5UTFNZk84bGtpR2hPbXhJd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2RURUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2tOaGJHbG1iM0p1YVdFeEZUQVRCZ05WQkFjTQpERk5oYmtaeVlXNWphWE5qYnpFUk1BOEdBMVVFQ2d3SVFtRnJaWEo1U1VFeEVUQVBCZ05WQkFzTUNGTmxZM1Z5CmFYUjVNUlF3RWdZRFZRUUREQXRDWVd0bGNubEpRUzFEUVRBZUZ3MHlOVEV3TVRneE5ESXlNVFJhRncwek5URXcKTVRZeE5ESXlNVFJhTUhVeEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlEQXBEWVd4cFptOXlibWxoTVJVdwpFd1lEVlFRSERBeFRZVzVHY21GdVkybHpZMjh4RVRBUEJnTlZCQW9NQ0VKaGEyVnllVWxCTVJFd0R3WURWUVFMCkRBaFRaV04xY21sMGVURVVNQklHQTFVRUF3d0xRbUZyWlhKNVNVRXRRMEV3Z2dJaU1BMEdDU3FHU0liM0RRRUIKQVFVQUE0SUNEd0F3Z2dJS0FvSUNBUURSRDVPMmVna1lnOUhOUlI1U1UwYkxuR0hqcHYvUmFnck03ZGh1c2FXbgpyZkRGNVZwVFo0czkvOXNPRUowTnlqdW9LWGFtb3VUd1IxbncxOUZkSDhmMWVvbWNRNGVLdzJIa3hveHFSMzR0ClJEYUFHejNiV08rcmFUUTRTeU1LN1hGTW92VVVpTGwrR08yM2wxQk5QZmh6a2NEa1o5N200MzRmMVFWbzk5dGIKaFY0YklMYW9GSXFmMDlNMEUxL2ZhQitKQ1I4WWtsN0xvWGd1ejNWUi9CVW5kMHZNc1RNV3VlRC8yblZ1VVpPMAowcFVtVFVCUTJRZDc2NTdrL0hXZC8xd2NFQUw5ZFhOUmJ4aEROZkdnYzNXdFFoZ2djcFlMUWFmTGE4MXRseHljCndEZ042UGRFbFVseGdYL091b1oxeWxNWkU3eHBzTXRwbjFBd2VvZFZibTNRcDVBMXlkeWJFNjF1MXVyWXoxTHQKV05aOWVPZkFxZXdpWVFIVlpXTUM0YTRTYSsyeU02cTVQWC80ZytUYklUaDhoWkp3WFBLNUVEaWc3dkYxNEpQbApsRVJOcHdpYTNuNmEwUDcwM0hQTjZya1FPNWtWVGRpVXNmaWJNdGNVSkhMeVdXUUFSQm15ZVZma0lDYWFlWUVsCkVMa3N3YTlOVkVTS3ZRYUhLU2lIWkZoRUkwYUF2Y3BBam0xRU9oRWEraFNSaE9vRnlVT3ZHK2NNT2ZjQlNtTDAKVW1sRC9sZmFuVFQwems1YXFzcEVrWEdlQnczMXJtWi8wQVpPalYycHBSeFdXZWt6bzlCZjdnNmVMVFk0VUNDNQpNeVB0em14OVRiWHJOQW5YaGlGNkxnNWgyOFI0MkdUZTVBZDZUSGtGOVMvS2hxOHUwZFk1U0EyR1VGMUViUU84Ckt3SURBUUFCbzFNd1VUQWRCZ05WSFE0RUZnUVVBKzZxL2tjOGZUUVUxRURxekdSZktRcHE2bTB3SHdZRFZSMGoKQkJnd0ZvQVVBKzZxL2tjOGZUUVUxRURxekdSZktRcHE2bTB3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFOQmdrcQpoa2lHOXcwQkFRc0ZBQU9DQWdFQVF1dkZoMitIUUZ5OFZUY1VnYWxFVmlheXQxelFHdjRySVNtaXEzRzZJZVhQClhTNGd3cUhrRnpUd1p2bW9oVHdtT0N3Vy94RjRLZ3htRmJ5V05yRUpKRXFjYmVkcVVXVi8wQkNhRm1KdlVkZEkKK2V4L2lEM0ZlYnU4QUZJK0o4bEJIL0NlbkRpU0xIaGd5c2VZOHV3Um5Yc3NoWDVSbkRpckYxdUtyMUo2MzVhbgpHbHlGSU5Vcm5RbGd1RXZ0cjBlbkdVbHpUNXJXajR5MEFXVWRiWGk4dlJzaldvUThKYTBCeFRyWVloL2tPL0ZJClB0cVg3d3N4b0pNREVRNzF6aHdhN1dMUWMyZGZiMnJBcjF1QmgzcU53aVZCSU5CK3QzSkZ2NzJ4cXNXZ3VySUIKSWYyc29SVEkybk1lNWdURzFEZmQrVjI0amZhL3lJZ0FzTWpDem1HUUsyMHZvYlg0c0FWbm1QVmJaZzlTTEZaaQpNaWRrbjlPOVU2OE1FT2UzSWFzY2xkN2ZwNUprK0hyYkpVNi9zMTZFRVIvQWdEM09vajN3UmdqVENTK0FERCtqCnhvMk84Vlgya1BvMDNBTitpWWEzbkptbE1GekNyelQrOFp4U25QNUZxR2cyRUNFYnFxQTBCLzVuYVZwbWRZYVYKNDFvRkxzd2NGbTJpcUdhd2JzTE45eDN0dklDdUU5M0hZazFqNzJQelhhaVNMdHB2YW1IMWRSWUMrSFVNMUwwTwo0OUNOTVlKZUwvTmx5UXVaSm0yWDBxRE5TWG1STUw4SFU5c093V1g2cFBQSk96dXF0Z2R4Lytsa0dBZDJ3WkpVCklWYm1MNlF2emRidGEvY1NWd3NMdEJ6RzQ4YTFiNEtCYzdXTEhUd2JyZEJSVGcwVGtMWTRrdkNaZTVuTmw0RT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
|
||||
redis-cert.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUdjekNDQkZ1Z0F3SUJBZ0lVRytCME0ycnhucWpHZHRmbzBCaGV2S0N4MGY4d0RRWUpLb1pJaHZjTkFRRUwKQlFBd2RURUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2tOaGJHbG1iM0p1YVdFeEZUQVRCZ05WQkFjTQpERk5oYmtaeVlXNWphWE5qYnpFUk1BOEdBMVVFQ2d3SVFtRnJaWEo1U1VFeEVUQVBCZ05WQkFzTUNGTmxZM1Z5CmFYUjVNUlF3RWdZRFZRUUREQXRDWVd0bGNubEpRUzFEUVRBZUZ3MHlOVEV3TVRneE5ESXlNVFJhRncweU9ERXcKTVRjeE5ESXlNVFJhTUlHUU1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFVgpNQk1HQTFVRUJ3d01VMkZ1Um5KaGJtTnBjMk52TVJFd0R3WURWUVFLREFoQ1lXdGxjbmxKUVRFT01Bd0dBMVVFCkN3d0ZRMkZqYUdVeE1qQXdCZ05WQkFNTUtYSmxaR2x6TFhObGNuWnBZMlV1WW1GclpYSjVMV2xoTG5OMll5NWoKYkhWemRHVnlMbXh2WTJGc01JSUNJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBZzhBTUlJQ0NnS0NBZ0VBdnNhMgo1MUdFR0VuaW81NHUxdGtNeFNCTGQ4Mm9ML3VsYWIxYXdxREJqcUJkVUFpZzJsMWpScFFyNUxHNVh3UzVoNzM5ClkrdnlQbFpWZW16dVRiNmszbjhPckxNcnUvUFBSRytpUWc3cXlUR1orYmF3YWY2YVhFZUNLOEExWW5xSy9ONEsKQTFIUkxXRXNXRzBKQ2VZakZPZnFzempWTEtydFJhSDd6S2lBREZxREJCbXhScWsvUDJvSjZmK1hXaWpwNE5KdQpPaVdoQmNoYmpjRi9mTTZ2MGVsQlMvOGs1cHVpOGtFdWRNZWExSVFLNXFTSll3TjZZNXRNT3BKcm1IdTFFN05vCkJRZWduakJvMWJaUkFkMWgrL2NxOHAwWWt3amE5dTJnSk5jczMxcWY4MUFtNitxNklXMExqTHFUMnlINVU1aW8KS2hTa0FuczNwcUFPNFZrSWRuM3l0Y2tkK00wQmNNcTRKQm5iYk0vZ1ZPV3I1RXorSERKOWsyckFSbzBWWFB5cQpnT1JxMnNXU2N0eVFiV0pPdExOUWVVbUN0dXZ4d0RyVVNoQWlYZGhhM3ptejdYOWJiNCtWUXA2elJaM3h2bXBnCnFFeG1Pc05zMDBMak9sMHBsalVmR0ZBR2Rmb21JSXpSWmxnVkN6TVVsWkQ0cGNQVnNhSGJuR1ovNi9ZbXhNZGUKOUxjbjRrYmlrNjVmZEFJbnhmVFAySU1NZER3TUZkYkZpcy9SbDIwZWo3QUJ0YTNLdVhvZFluMXkwbitYTFIyTAo3YWJUcW9xSXRnUW1BY2lITlBVYWNnREMvbFBRSk95ckRaVTloQ3NMdDJJVVZKTUN6U2QzR3JDQzA4d2dSb2U1CjZRNUh0NEUyWG5kV3NlWWZxVnRRM2c4WktDaVUrUU1JQmt4SzdHOENBd0VBQWFPQjNqQ0IyekFMQmdOVkhROEUKQkFNQ0JEQXdIUVlEVlIwbEJCWXdGQVlJS3dZQkJRVUhBd0VHQ0NzR0FRVUZCd01DTUcwR0ExVWRFUVJtTUdTQwpLWEpsWkdsekxYTmxjblpwWTJVdVltRnJaWEo1TFdsaExuTjJZeTVqYkhWemRHVnlMbXh2WTJGc2doZHlaV1JwCmN5MXpaWEoyYVdObExtSmhhMlZ5ZVMxcFlZSU5jbVZrYVhNdGMyVnlkbWxqWllJSmJHOWpZV3hvYjNOMGh3Ui8KQUFBQk1CMEdBMVVkRGdRV0JCU2RJV1V6Q2gvNE9SZmJLR2JYTVJ2eXhXTFdyekFmQmdOVkhTTUVHREFXZ0JRRAo3cXIrUnp4OU5CVFVRT3JNWkY4cENtcnFiVEFOQmdrcWhraUc5dzBCQVFzRkFBT0NBZ0VBaEd2cFBSSlpqQkZpCnBaNDNVaFVGTGFIeCtRMHZncy96eXlxVzVqSys3ZWZwY3Z0Sk9CbXVrRUtMaXUwWGFrZit5VDhWRlp4R2tzZkYKcVZyL1Vvb2x3bTVEamlHOE9FT29PYTJCZTlqb0dTdmI3c0JzZ24wYS9pdElGUnpEUXdadGJQZmdpMGFndkJmTQpxczFCNm9IempBMkdSNmlMUDFXYzg4VXRNWFJwV1c0VnZSclZIallzWHVuM2ZHdGYxR1J3ZndBWFFJNys5YldpClNPQ2hEOWVJNk1xdUNYQmRCQVgvQ3FueGs4aHVia3dXY3NIeVNGQkRMcVFoUHM1dU04bGkzK01QZ3BMWENmYVkKWDYvWnpIM05nSjNEK1BJSDU5WllwaEczenZsRnBHRDRvRzNRMkFvbHhxd01SMytQNmM5SWYxRGZNTW9TZ1YzKwptZnZnUmpONXRuZ0IrL29CaXVtYk00K0VGOGFNUmsxR095V3BmM2VSZkc1NStPVEpsTHNEWE9TQlcrSzFOQ3o0CnlOWVR5c2h3eGpWU1BYcWZhdGVBWnpDNVNqRk1SZHJkSEQxME0wZ2w1L2RYY3AreDVocFFTWTNNK2dNMndXZEkKem83SkJPdDlRMUZRRGdUM2pJVldRNVB0TmhkOW9UOVdkYzBFZEppenlObDN2aTltMi9iSktWcEhPMnltZG5IWQpoUG12UVlWdGZzTWxOdmtzdkRMcFhlbUc3czR2akhmMTJZRVA4VFQ1REpnRDQ2TlZvZTM5S2E0N0lmYVRXdWdOCkZXb1YvUGFqUkl4L0lPL2tPcDROQnBlQjY5TytudVlVVU5jQ3cwZmlsQSttRmdXUWpxRkdQK2ZxV05hSmorcFAKNTByelBOc3hwK3FpdzZGVm9aNTVjY3hFMjdrbnZlWT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
|
||||
redis-key.pem: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRUUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQ1Nzd2dna25BZ0VBQW9JQ0FRQyt4cmJuVVlRWVNlS2oKbmk3VzJRekZJRXQzemFndis2VnB2VnJDb01HT29GMVFDS0RhWFdOR2xDdmtzYmxmQkxtSHZmMWo2L0krVmxWNgpiTzVOdnFUZWZ3NnNzeXU3ODg5RWI2SkNEdXJKTVpuNXRyQnAvcHBjUjRJcndEVmllb3I4M2dvRFVkRXRZU3hZCmJRa0o1aU1VNStxek9OVXNxdTFGb2Z2TXFJQU1Xb01FR2JGR3FUOC9hZ25wLzVkYUtPbmcwbTQ2SmFFRnlGdU4Kd1g5OHpxL1I2VUZML3lUbW02THlRUzUweDVyVWhBcm1wSWxqQTNwam0wdzZrbXVZZTdVVHMyZ0ZCNkNlTUdqVgp0bEVCM1dINzl5cnluUmlUQ05yMjdhQWsxeXpmV3AvelVDYnI2cm9oYlF1TXVwUGJJZmxUbUtncUZLUUNlemVtCm9BN2hXUWgyZmZLMXlSMzR6UUZ3eXJna0dkdHN6K0JVNWF2a1RQNGNNbjJUYXNCR2pSVmMvS3FBNUdyYXhaSnkKM0pCdFlrNjBzMUI1U1lLMjYvSEFPdFJLRUNKZDJGcmZPYlB0ZjF0dmo1VkNuck5GbmZHK2FtQ29UR1k2dzJ6VApRdU02WFNtV05SOFlVQVoxK2lZZ2pORm1XQlVMTXhTVmtQaWx3OVd4b2R1Y1puL3I5aWJFeDE3MHR5ZmlSdUtUCnJsOTBBaWZGOU0vWWd3eDBQQXdWMXNXS3o5R1hiUjZQc0FHMXJjcTVlaDFpZlhMU2Y1Y3RIWXZ0cHRPcWlvaTIKQkNZQnlJYzA5UnB5QU1MK1U5QWs3S3NObFQyRUt3dTNZaFJVa3dMTkozY2FzSUxUekNCR2g3bnBEa2UzZ1RaZQpkMWF4NWgrcFcxRGVEeGtvS0pUNUF3Z0dURXJzYndJREFRQUJBb0lDQUFGdjRtMTlwTFFXSW1TVWRYVXkyZ1liCmNkWVdNTlVqc25iekc5MlVIbXZNODNHb2p2cjJISFdwK2hGVlJyaUdMWlpETFJ4MVBqUTZyRUYrMCtZTUJldm8KZUhEVDdLNit3eFNZanExV3RXMWg0cG9KOFVHVnp3M2JrQW5LVklkSVlGeFA3b2dMTkJDQkhJeThvdHZMT3YvQQorM2ljSTFHY2ZBQm1uRXlmWEUrUTJFOGpRNzJYaFhMSExBbnlNMFAvbU9ZVHBRdy92NlhEMWtTMndoZHJsZEYyCm8xZWM0Qkh6VEMxQ1VScEV3cVY2ZjlFd1NNU21nR1BZVzB1a1VndlZBQTZFN3h5bjY3Z2xWSW9xUHhQM2hKeHUKOFRPTFVXVzh6d0Z3Z0NDbTZrbnpGeVN3WkRWVXV2cmVKUlIxOTFVb1BWdU8yU2dhcUYyZHdLazYvV3hmSWxHQgpoRndkbmN1Q1UwdVV5QXp3VUh2bGlEWndWUFFxaVBMbXFYWEp3WjY5RjUzMEZlVHM4L2hUU0Y1UTAwaUFqTmhlClhRbzhJQjA0U1N2VDdMQno1OVg4Y3M0Mkh5VG80YWZ6bWhLK051OEsvQ0ZxOERMT1orRTFtYnhYRE9DM1ZWVHAKaDFFaXd1a0Z0ekpxRzVRSEJjTTlNNVlTK3EzaUw4YXY2N052M29wTm0vUG5YWkdYenFtVjRzK1FwMDdtSUhiVQpsamFCcWVzNGN4RTZZRUtkS1NOSnJ6Y09EVFNFT2hOYUJXN2RNSFRmay8zbXBpODIyNENBdEVJcmVlZy9Ua2VBCjJLWVBmTzJEd3hYZHZJd1NvajBSM0JDbkdVOWVRKzl2L2c5WVU3SXRyS2UxQjlFZTAxNjNUOC9tbnFlZy9QenEKOFNDSFA3Yk1Zb1gxaUlmbjk3MXhBb0lCQVFEZWE2YlY5blQ1dVJHL21FK2FLd0pFTHdkTzFCQTdwc2RIcnV4UApjSW5Hcjdqa3g1S21KV3gvU3c4RXdRZjR1dThEcjYxcC9QUDZLSTZoSzVtQlJhOUpWeVdVbUhTaFFDb0g5TGhPCk5mMkxtMEVOalZVZkdOb2JHMzhsbmhLd082QnNKS3JxTzc2SW5rc3hrN0htaGZ6emlBbFVtTDF5dFhFb0s2Qm4KM3BHZHNRZzEzYjlnWCt6NXZVcGlEOHI5R0U1Rm56cDhNa1BsTWhqcWsvVmp3VXNKcGluSDhMY1B3aEMyZlM5Zwpac2dYdmt6MVR5R2FZVHU5LytBazBMZzJqMU5kNFY0SmIyR0Fvc1NDRUtGQnJrZVNVMTVLK2YrOEtIdFFtMVVBCjBqaExWQWpUTkx1U3d4elB1VUpEaGF4K3kvRFpRRmJPRG1kQmtRWXFBWFpDL0pKNUFvSUJBUURibEFwTGg3c1QKcjhtbjdFcUxEU0ZyVDlQSitsQnhqL210bWd0QVE0MjhBNXVhMFVzbGJ4NGNid0pzcktlejVldkhjWGdmL1Y4cwpBaTFtNnJLcmFBOWlMaFFXSk1wRkFoOEZvRnlIK0pFN1l6N0F3elY2WXRha1h0ZVlrNVIzSlg0UmRZQ0xSeHpDCkpBY25ZMUZDSWRrRzhWcFZPSkZFVnBnWkNFMGRQTldEdHM5cTRyaUR3NXNodWVHd2RldXdoSytwenhQNmlDUmsKNEdER3hzT0hnUERkNy9vVUxzYm9EaEJCT3lOb0VyL2kvWjVQOHpzc1psR20rY2FnTTJETG1oNkxONUlVaTUzWgptNEdHTi81NEN5Zk5pMUFFUitWazlMOTNzOWNkODJuZnlEMkZ3QXNZdkZRcEFRL2c1ekROZ3NsUHZYeUR6OGo1CnNLQmRzcXdnVG53bkFvSUJBQXkxdUIzbjdIMU1ydy8wd3krN0gzRUlBdkhsT2x3K1JvcjVHdlhiSjNSY0hFT3UKaDluSXI2K0NlWVE3QjVxV0RBeDQ0SDc2L25JZ0dTNXFrR1lMdGwySmhsTThkd1d6NWZMNGNBUEFJQkgzT0R0dgpCUnMyejFmWE5XZlA1WjkrZU1kVlBSTVBnTzdMcE41YlkwSWFDLzlhbWJYazJJYVNpYm5TN0dLakhFMFhqYkdPClQxNVJmUGcwY2VpeW9GWGdLckRkelhqRllvM1pWQVVybVUwdkFYdTJyQktKMWR3bnFjN1R6bjVDd1ZKaUJJSE0KR001Nm1mQmNpOUZ1ditnV1BweFJ3WTdtZDNyalVqbGdlK2FGNy84VGxvTFFVR1hQSm1UUHk0YTFmSlFKWkV1MQphcmFUUWJVNUQrbE4zVEtOc3VDblJZNlcwaDIwRE5jZnFFTmhyWGtDZ2dFQVdIN1FxMkkzdnBaeGNwRWo5ZWpECjJFa2k5VnRDQXBMaE1OdE52NGU2WHRVaGFJTURnMEhHWS9WRmgrRUo4ZEl2ZFlGQXhidkxHS1NFQWQrRFJOdTYKbjNvc3RFUDlsVlJtaGxEOEdmelBJNTA3RkZ0WWVVdk9jQTZkVzZ2WEFUSUdIaWs2Tm1maHFrajA3U1gxQU84OQpWYlArRVN5c04xdWpEeXV1VUtOTTlqbStYTGlsWHMxOS8xaTRJZk5VbXg3TzRXUkpEQWJFakRkMktZYkFGU09kCmNBVWd4L09XVEw0bVJQUDlzQnNtWk9pTVhuS01IYmZiSHEyNkpLU3dWVDUzSXVxeG9FQW96U1FFVHNEUWVUY2QKd3BSc0dsMlRrVjJtc1NxMC95ZzBPbkdzZ2ZSRlJLSGFWWEJOSXZwcVM5bHpJd1VlWXMxaWxXZGZLb1F4SlJBYwpyd0tDQVFCemdWeFZxYTV0T0ZudzhRbWZVWU1lN0RIQ1U0cjNSUzFPTndtR29YSTFSTHp6M0k4U1JHSWJOcFYxCnlJczRnRldXd0l1WG40ekxvMCtZZExwT2prRmg1S2FrMEVya2g3QjUvWm01OWZkR013dWpBMnZpUUdZalJyek8Ka1RTQ1hQZ3JHd0s5QmxqWWZlbFM5cVd1aTl2RHVSaEFXUVpPT0NDeVB0eEVjT3ZyOXFmOUtoT2MweEVFTnRVagp6L01CSDc4NnJwckJFQVhuT0FGRkpibWZ0TFhZeTlSaEFhdTJTTURYMGc5dWRIRE1RTk9Cb1dPN2RoLzVBNXZhCkxMa3BWZ3ZvWWtjU1NjRGFKSUtzb2RQTGNManFYWGQ1MVhOV3BDOWNPWkJaUVM4RXVOMVZmR3JqT0RZOW1SOGIKakNvbUgxUDBGenlQVm1MU2JvV21qRGJzMFNGZQotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==
|
||||
Reference in New Issue
Block a user