Add new infra architecture 3

This commit is contained in:
Urtzi Alfaro
2026-01-19 13:57:50 +01:00
parent 8461226a97
commit 9edcc8c231
110 changed files with 2568 additions and 4636 deletions

View File

@@ -0,0 +1,42 @@
# Mailu Migration Summary
This document summarizes the migration from the old Kustomize-based Mailu setup to the new Helm-based setup.
## Files Removed
- `infrastructure/platform/mail/mailu/` - Complete removal of old Kustomize-based Mailu configuration
- `infrastructure/security/certificates/mailu/` - Removal of old certificate generation scripts
## Files Updated
### Infrastructure Configuration
- `infrastructure/environments/dev/k8s-manifests/kustomization.yaml` - Removed Mailu resource reference and patches
- `infrastructure/environments/prod/k8s-manifests/kustomization.yaml` - Removed Mailu resource reference and patches
- `infrastructure/platform/networking/ingress/base/ingress.yaml` - Removed Mailu-specific ingress rules and TLS entries
- `infrastructure/monitoring/signoz/README.md` - Updated to reflect Helm-based Mailu deployment
- `infrastructure/environments/common/configs/secrets.yaml` - Updated comments to reflect new service name
### Service Integration
- `infrastructure/environments/common/configs/configmap.yaml` - Updated SMTP_HOST to use new Helm service name
- `infrastructure/monitoring/signoz/signoz-values-prod.yaml` - Updated SMTP configuration to use new service name
## New Files Created
- `infrastructure/platform/mail/mailu-helm/` - New Helm-based Mailu configuration
- `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
- `README.md` - Comprehensive documentation
- `MIGRATION_GUIDE.md` - Migration guide with rollback procedures
## Key Changes
1. **Service Names**: Changed from `mailu-smtp` to `mailu-postfix` (Helm chart service naming)
2. **Deployment Method**: Switched from Kustomize manifests to Helm chart
3. **Ingress Configuration**: Disabled built-in ingress to work with existing ingress controller
4. **Configuration**: All configurations now use Helm values files instead of individual YAML manifests
## Verification
The new configuration has been tested and verified to work with the existing ingress setup, maintaining all critical functionality while improving maintainability.

194
Tiltfile
View File

@@ -239,11 +239,10 @@ local_resource(
echo "Creating namespaces..."
kubectl apply -f infrastructure/namespaces/bakery-ia.yaml
kubectl apply -f infrastructure/namespaces/tekton-pipelines.yaml
kubectl apply -f infrastructure/namespaces/flux-system.yaml
# Wait for namespaces to be ready
echo "Waiting for namespaces to be ready..."
for ns in bakery-ia tekton-pipelines flux-system; do
for ns in bakery-ia tekton-pipelines; do
until kubectl get namespace $ns 2>/dev/null; do
echo "Waiting for namespace $ns to be created..."
sleep 2
@@ -267,11 +266,10 @@ local_resource(
kubectl apply -f infrastructure/platform/storage/minio/minio-secrets.yaml
kubectl apply -f infrastructure/platform/storage/minio/secrets/minio-tls-secret.yaml
# Apply Mail/SMTP secrets
kubectl apply -f infrastructure/platform/mail/mailu/mailu-secrets.yaml
# Apply Mail/SMTP secrets (already included in common/configs/secrets.yaml)
# Apply CI/CD secrets
kubectl apply -f infrastructure/cicd/tekton/secrets/secrets.yaml
kubectl apply -f infrastructure/cicd/tekton-helm/templates/secrets.yaml
echo "Security configurations applied"
''',
@@ -482,8 +480,67 @@ k8s_resource('nominatim', labels=['01-infrastructure'])
k8s_resource('minio', resource_deps=['security-setup'], labels=['01-infrastructure'])
k8s_resource('minio-bucket-init', resource_deps=['minio'], labels=['01-infrastructure'])
# Mail Infrastructure (Mailu)
k8s_resource('mailu-front', resource_deps=['security-setup'], labels=['01-infrastructure'])
# Mail Infrastructure (Mailu) - Manual trigger for Helm deployment
local_resource(
'mailu-helm',
cmd='''
echo "Deploying Mailu via Helm..."
echo ""
# Check if Mailu is already deployed
if helm list -n bakery-ia | grep -q mailu; then
echo "Mailu already deployed, checking status..."
helm status mailu -n bakery-ia
else
echo "Installing Mailu..."
# Add Mailu Helm repository if not already added
helm repo add mailu https://mailu.github.io/helm-charts 2>/dev/null || true
helm repo update mailu
# Determine environment (dev or prod) based on context
ENVIRONMENT="dev"
if [[ "$(kubectl config current-context)" == *"prod"* ]]; then
ENVIRONMENT="prod"
fi
echo "Environment detected: $ENVIRONMENT"
# Install Mailu with appropriate values
if [ "$ENVIRONMENT" = "dev" ]; then
helm upgrade --install mailu mailu/mailu \
-n bakery-ia \
--create-namespace \
-f infrastructure/platform/mail/mailu-helm/values.yaml \
-f infrastructure/platform/mail/mailu-helm/dev/values.yaml \
--timeout 10m \
--wait
else
helm upgrade --install mailu mailu/mailu \
-n bakery-ia \
--create-namespace \
-f infrastructure/platform/mail/mailu-helm/values.yaml \
-f infrastructure/platform/mail/mailu-helm/prod/values.yaml \
--timeout 10m \
--wait
fi
echo ""
echo "Mailu deployment completed"
fi
echo ""
echo "Mailu Access Information:"
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/TLS)"
echo ""
echo "To check pod status: kubectl get pods -n bakery-ia | grep mailu"
''',
labels=['01-infrastructure'],
auto_init=False, # Manual trigger only
)
# =============================================================================
# MONITORING RESOURCES - SigNoz (Unified Observability)
@@ -535,6 +592,53 @@ local_resource(
auto_init=False,
)
# Deploy Flux CD using Helm with automatic deployment and progress tracking
local_resource(
'flux-cd-deploy',
cmd='''
echo "Deploying Flux CD GitOps Toolkit..."
echo ""
# Check if Flux is already deployed
if helm list -n flux-system | grep -q flux-cd; then
echo "Flux CD already deployed, checking status..."
helm status flux-cd -n flux-system
else
echo "Installing Flux CD..."
# Install Flux CRDs first if not already installed
if ! kubectl get crd gitrepositories.source.toolkit.fluxcd.io >/dev/null 2>&1; then
echo "Installing Flux CRDs..."
curl -sL https://fluxcd.io/install.sh | sudo bash
flux install --namespace=flux-system --network-policy=false
fi
# Create the namespace if it doesn't exist
kubectl create namespace flux-system --dry-run=client -o yaml | kubectl apply -f -
# Install Flux CD with custom values using the local chart
helm upgrade --install flux-cd infrastructure/cicd/flux \
-n flux-system \
--create-namespace \
--timeout 10m \
--wait
echo ""
echo "Flux CD deployment completed"
fi
echo ""
echo "Flux CD Access Information:"
echo "To check status: flux check"
echo "To check GitRepository: kubectl get gitrepository -n flux-system"
echo "To check Kustomization: kubectl get kustomization -n flux-system"
echo ""
echo "To check pod status: kubectl get pods -n flux-system"
''',
labels=['99-cicd'],
auto_init=False,
)
# Optional exporters (in monitoring namespace) - DISABLED since using SigNoz
# k8s_resource('node-exporter', labels=['05-monitoring'])
@@ -708,11 +812,11 @@ watch_settings(
# CI/CD INFRASTRUCTURE - MANUAL TRIGGERS
# =============================================================================
# Tekton Pipelines - Manual trigger for local development
# Tekton Pipelines - Manual trigger for local development using Helm
local_resource(
'tekton-pipelines',
cmd='''
echo "Setting up Tekton Pipelines for CI/CD..."
echo "Setting up Tekton Pipelines for CI/CD using Helm..."
echo ""
# Check if Tekton CRDs are already installed
@@ -730,45 +834,29 @@ local_resource(
fi
echo ""
echo "Applying Tekton configurations..."
kubectl apply -f infrastructure/cicd/tekton/kustomization.yaml
kubectl apply -f infrastructure/cicd/tekton/rbac/
kubectl apply -f infrastructure/cicd/tekton/tasks/
kubectl apply -f infrastructure/cicd/tekton/pipelines/
echo "Installing Tekton configurations via Helm..."
# Check if Tekton Helm release is already deployed
if helm list -n tekton-pipelines | grep -q tekton-cicd; then
echo " Updating existing Tekton CICD deployment..."
helm upgrade --install tekton-cicd infrastructure/cicd/tekton-helm \
-n tekton-pipelines \
--create-namespace \
--timeout 10m \
--wait
else
echo " Installing new Tekton CICD deployment..."
helm upgrade --install tekton-cicd infrastructure/cicd/tekton-helm \
-n tekton-pipelines \
--create-namespace \
--timeout 10m \
--wait
fi
echo ""
echo "Tekton setup complete!"
echo "To check status: kubectl get pods -n tekton-pipelines"
''',
labels=['99-cicd'],
auto_init=False, # Manual trigger only
)
# Flux CD - Manual trigger for GitOps
local_resource(
'flux-cd',
cmd='''
echo "Setting up Flux CD for GitOps..."
echo ""
# Check if Flux CRDs are already installed
if kubectl get crd gitrepositories.source.toolkit.fluxcd.io >/dev/null 2>&1; then
echo " Flux CRDs already installed"
else
echo " Installing Flux v2.2.3..."
curl -sL https://fluxcd.io/install.sh | sudo bash
flux install --version=latest
echo " Flux installed and ready"
fi
echo ""
echo "Applying Flux configurations..."
kubectl apply -f infrastructure/cicd/flux/
echo ""
echo "Flux setup complete!"
echo "To check status: flux check"
echo "To check Helm release: helm status tekton-cicd -n tekton-pipelines"
''',
labels=['99-cicd'],
auto_init=False, # Manual trigger only
@@ -781,15 +869,23 @@ local_resource(
echo "Setting up Gitea for local Git server..."
echo ""
# Apply Gitea configurations
# Create namespace
kubectl create namespace gitea || true
kubectl apply -f infrastructure/cicd/gitea/
# Create admin secret first
chmod +x infrastructure/cicd/gitea/setup-admin-secret.sh
./infrastructure/cicd/gitea/setup-admin-secret.sh
# Install Gitea using Helm
helm repo add gitea https://dl.gitea.io/charts || true
helm upgrade --install gitea gitea/gitea -n gitea -f infrastructure/cicd/gitea/values.yaml
echo ""
echo "Gitea setup complete!"
echo "Access Gitea at: http://gitea.local (add to /etc/hosts)"
echo "Default credentials: admin/admin123 (change after first login)"
echo "To check status: kubectl get pods -n gitea"
echo "Access Gitea at: http://gitea.bakery-ia.local (for dev) or http://gitea.bakewise.ai (for prod)"
echo "Make sure to add the appropriate hostname to /etc/hosts or configure DNS"
echo "Check status: kubectl get pods -n gitea"
echo "To uninstall: helm uninstall gitea -n gitea"
''',
labels=['99-cicd'],
auto_init=False, # Manual trigger only

View File

@@ -0,0 +1,26 @@
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 . 8.8.8.8 8.8.4.4 {
force_tcp
max_concurrent 1000
}
cache 30 {
disable success cluster.local
disable denial cluster.local
}
loop
reload
loadbalance
}

28
coredns-dnssec-patch.yaml Normal file
View File

@@ -0,0 +1,28 @@
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 . /etc/resolv.conf {
max_concurrent 1000
}
dnssec {
enable
}
cache 30 {
disable success cluster.local
disable denial cluster.local
}
loop
reload
loadbalance
}

View File

@@ -20,7 +20,7 @@ The Bakery-IA platform uses the following namespaces:
3. **`flux-system`** - GitOps namespace
- Contains Flux CD components for GitOps deployments
- Defined in: `infrastructure/namespaces/flux-system.yaml`
- Now defined in Helm chart: `infrastructure/cicd/flux/templates/namespace.yaml`
### Infrastructure Namespaces
@@ -45,7 +45,7 @@ kubectl apply -f infrastructure/environments/common/configs/
# 3. Apply platform components
kubectl apply -f infrastructure/platform/
# 4. Apply CI/CD components (depends on tekton-pipelines and flux-system)
# 4. Apply CI/CD components (depends on tekton-pipelines)
kubectl apply -f infrastructure/cicd/
# 5. Apply monitoring components

View File

@@ -33,9 +33,14 @@ infrastructure/ci-cd/
│ ├── trigger-binding.yaml
│ ├── event-listener.yaml
│ └── gitlab-interceptor.yaml
├── flux/ # Flux CD GitOps configuration
│ ├── git-repository.yaml # Git repository source
── kustomization.yaml # Deployment kustomization
├── flux/ # Flux CD GitOps Helm chart configuration
│ ├── Chart.yaml # Helm chart definition
── values.yaml # Default configuration values
│ ├── templates/ # Kubernetes manifest templates
│ │ ├── gitrepository.yaml
│ │ ├── kustomization.yaml
│ │ └── namespace.yaml
│ └── values/ # Additional value files
├── monitoring/ # Monitoring configuration
│ └── otel-collector.yaml # OpenTelemetry collector
└── README.md # This file

View File

@@ -0,0 +1,6 @@
apiVersion: v2
name: flux-cd
description: A Helm chart for deploying Flux CD GitOps toolkit for Bakery-IA
type: application
version: 0.1.0
appVersion: "2.2.3"

View File

@@ -1,16 +0,0 @@
# Flux GitRepository for Bakery-IA
# This resource tells Flux where to find the Git repository
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: bakery-ia
namespace: flux-system
spec:
interval: 1m
url: http://gitea.bakery-ia.local/bakery/bakery-ia.git
ref:
branch: main
secretRef:
name: gitea-credentials
timeout: 60s

View File

@@ -1,25 +0,0 @@
# Kustomize build configuration for Flux resources
# This file is used to build and apply the Flux resources
#
# IMPORTANT: Apply resources in this order:
# 1. Install Flux CD first: flux install
# 2. Apply this kustomization: kubectl apply -k infrastructure/cicd/flux/
#
# The GitRepository must be ready before the Flux Kustomization can reconcile.
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
# Resources to apply in order (namespace and secrets first, then sources, then kustomizations)
resources:
- namespace.yaml
- git-repository.yaml
- flux-kustomization.yaml
# Common labels for all resources
commonLabels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: flux
app.kubernetes.io/managed-by: kustomize
# Note: Do NOT set namespace here as resources already have explicit namespaces

View File

@@ -1,15 +0,0 @@
# Flux System Namespace
# This namespace is required for Flux CD components
# It should be created before any Flux resources are applied
apiVersion: v1
kind: Namespace
metadata:
name: flux-system
labels:
app.kubernetes.io/name: flux
app.kubernetes.io/component: system
kubernetes.io/metadata.name: flux-system
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted

View File

@@ -0,0 +1,15 @@
{{- if .Values.gitRepository }}
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: {{ .Values.gitRepository.name }}
namespace: {{ .Values.gitRepository.namespace }}
spec:
interval: {{ .Values.gitRepository.interval }}
url: {{ .Values.gitRepository.url }}
ref:
branch: {{ .Values.gitRepository.ref.branch }}
secretRef:
name: {{ .Values.gitRepository.secretRef.name }}
timeout: {{ .Values.gitRepository.timeout }}
{{- end }}

View File

@@ -0,0 +1,43 @@
{{- if .Values.kustomization }}
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: {{ .Values.kustomization.name }}
namespace: {{ .Values.kustomization.namespace }}
labels:
app.kubernetes.io/name: bakery-ia
app.kubernetes.io/component: flux
spec:
# Wait for GitRepository to be ready before reconciling
dependsOn: []
interval: {{ .Values.kustomization.interval }}
path: {{ .Values.kustomization.path }}
prune: {{ .Values.kustomization.prune }}
sourceRef:
kind: {{ .Values.kustomization.sourceRef.kind }}
name: {{ .Values.kustomization.sourceRef.name }}
targetNamespace: {{ .Values.kustomization.targetNamespace }}
timeout: {{ .Values.kustomization.timeout }}
retryInterval: {{ .Values.kustomization.retryInterval }}
wait: {{ .Values.kustomization.wait }}
{{- if .Values.kustomization.healthChecks }}
healthChecks:
{{- range .Values.kustomization.healthChecks }}
- apiVersion: {{ .apiVersion }}
kind: {{ .kind }}
name: {{ .name }}
namespace: {{ .namespace }}
{{- end }}
{{- end }}
{{- if .Values.kustomization.postBuild }}
postBuild:
substituteFrom:
{{- range .Values.kustomization.postBuild.substituteFrom }}
- kind: {{ .kind }}
name: {{ .name }}
{{- if .optional }}
optional: {{ .optional }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,7 @@
apiVersion: v1
kind: Namespace
metadata:
name: {{ .Values.gitRepository.namespace }}
labels:
app.kubernetes.io/name: flux
kubernetes.io/metadata.name: {{ .Values.gitRepository.namespace }}

View File

@@ -1,22 +1,21 @@
# Flux Kustomization for Bakery-IA Production Deployment
# This resource tells Flux how to deploy the application
#
# Prerequisites:
# 1. Flux CD must be installed: flux install
# 2. GitRepository 'bakery-ia' must be created and ready
# 3. Secret 'gitea-credentials' must exist in flux-system namespace
# Default values for flux-cd
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
gitRepository:
name: bakery-ia
namespace: flux-system
interval: 1m
url: http://gitea.bakery-ia.local/bakery/bakery-ia.git
ref:
branch: main
secretRef:
name: gitea-credentials
timeout: 60s
kustomization:
name: bakery-ia-prod
namespace: flux-system
labels:
app.kubernetes.io/name: bakery-ia
app.kubernetes.io/component: flux
spec:
# Wait for GitRepository to be ready before reconciling
dependsOn: []
interval: 5m
path: ./infrastructure/environments/prod
prune: true
@@ -27,7 +26,6 @@ spec:
timeout: 10m
retryInterval: 1m
wait: true
# Health checks for critical services
healthChecks:
# Core Infrastructure
- apiVersion: apps/v1
@@ -65,7 +63,6 @@ spec:
kind: Deployment
name: notification-service
namespace: bakery-ia
# Post-build variable substitution
postBuild:
substituteFrom:
- kind: ConfigMap

View File

@@ -16,6 +16,9 @@ service:
type: ClusterIP
port: 2222
ingress:
enabled: false
persistence:
enabled: true
size: 10Gi

View File

@@ -1,10 +1,9 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- tekton/
- ../../namespaces/flux-system.yaml
# Tekton is now managed via Helm, so we don't include it directly here
# The Tekton Helm chart is deployed separately via Tilt
# Gitea is managed via Helm, so we don't include it directly here
# The Gitea Helm chart is deployed separately and referenced in the ingress
# Flux configuration is a Flux Kustomization resource, not a kustomize config
# Flux is now managed via Helm chart located in this directory, so we don't include it directly here

View File

@@ -0,0 +1,15 @@
apiVersion: v2
name: tekton-cicd
description: Tekton CI/CD infrastructure for Bakery-IA
type: application
version: 0.1.0
appVersion: "0.57.0"
maintainers:
- name: Bakery-IA Team
email: team@bakery-ia.local
annotations:
category: Infrastructure
app.kubernetes.io/name: tekton-cicd
app.kubernetes.io/instance: tekton-cicd
app.kubernetes.io/version: "0.57.0"
app.kubernetes.io/part-of: bakery-ia

View File

@@ -0,0 +1,63 @@
# Tekton CI/CD Helm Chart
This Helm chart deploys the Tekton CI/CD infrastructure for the Bakery-IA project.
## Prerequisites
- Kubernetes 1.20+
- Tekton Pipelines installed (v0.57.0 or later)
- Helm 3.0+
## Installation
Before installing this chart, Tekton Pipelines must be installed separately:
```bash
kubectl apply -f https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml
```
Then install the chart:
```bash
helm repo add tekton-pipelines https://tekton.dev/charts
helm repo update
helm install tekton-cicd infrastructure/helm/tekton --namespace tekton-pipelines --create-namespace
```
## Configuration
The following table lists the configurable parameters of the tekton-cicd chart and their default values.
| Parameter | Description | Default |
|-----------|-------------|---------|
| `global.registry.url` | Container registry URL | `"gitea.bakery-ia.local:5000"` |
| `global.git.branch` | Git branch name | `"main"` |
| `global.git.userName` | Git user name | `"bakery-ia-ci"` |
| `global.git.userEmail` | Git user email | `"ci@bakery-ia.local"` |
| `pipeline.build.cacheTTL` | Build cache TTL | `"24h"` |
| `pipeline.build.verbosity` | Build verbosity level | `"info"` |
| `pipeline.test.skipTests` | Skip tests flag | `"false"` |
| `pipeline.test.skipLint` | Skip lint flag | `"false"` |
| `pipeline.deployment.namespace` | Deployment namespace | `"bakery-ia"` |
| `pipeline.deployment.fluxNamespace` | Flux namespace | `"flux-system"` |
| `pipeline.workspace.size` | Workspace size | `"5Gi"` |
| `pipeline.workspace.storageClass` | Workspace storage class | `"standard"` |
| `secrets.webhook.token` | Webhook validation token | `"example-webhook-token-do-not-use-in-production"` |
| `secrets.registry.username` | Registry username | `"example-user"` |
| `secrets.registry.password` | Registry password | `"example-password"` |
| `secrets.registry.registryUrl` | Registry URL | `"gitea.bakery-ia.local:5000"` |
| `secrets.git.username` | Git username | `"example-user"` |
| `secrets.git.password` | Git password | `"example-password"` |
| `namespace` | Namespace for Tekton resources | `"tekton-pipelines"` |
## Uninstallation
To uninstall/delete the `tekton-cicd` release:
```bash
helm delete tekton-cicd --namespace tekton-pipelines
```
## Values
For a detailed list of configurable values, see the `values.yaml` file.

View File

@@ -0,0 +1,22 @@
Thank you for installing {{ .Chart.Name }}.
This chart deploys the Tekton CI/CD infrastructure for Bakery-IA.
IMPORTANT: Tekton Pipelines must be installed separately before deploying this chart.
To install Tekton Pipelines, run:
kubectl apply -f https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml
To verify Tekton is running:
kubectl get pods -n tekton-pipelines
After Tekton is installed, this chart will deploy:
- ConfigMaps with pipeline configuration
- RBAC resources for triggers and pipelines
- Secrets for registry and Git credentials
- Tasks, Pipelines, and Triggers for CI/CD
To check the status of deployed resources:
kubectl get all -n {{ .Values.namespace }}
For more information about Tekton, visit: https://tekton.dev/

View File

@@ -1,36 +1,10 @@
# Tekton RBAC Configuration for Bakery-IA CI/CD
# This file defines ServiceAccounts, Roles, and RoleBindings for Tekton
---
# ServiceAccount for Tekton Triggers EventListener
apiVersion: v1
kind: ServiceAccount
metadata:
name: tekton-triggers-sa
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: triggers
---
# ServiceAccount for Pipeline execution
apiVersion: v1
kind: ServiceAccount
metadata:
name: tekton-pipeline-sa
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: pipeline
---
# ClusterRole for Tekton Triggers to create PipelineRuns
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: tekton-triggers-role
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/name: {{ .Values.labels.app.name }}
app.kubernetes.io/component: triggers
rules:
# Ability to create PipelineRuns from triggers
@@ -57,25 +31,6 @@ rules:
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "patch"]
---
# ClusterRoleBinding for Tekton Triggers
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: tekton-triggers-binding
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: triggers
subjects:
- kind: ServiceAccount
name: tekton-triggers-sa
namespace: tekton-pipelines
roleRef:
kind: ClusterRole
name: tekton-triggers-role
apiGroup: rbac.authorization.k8s.io
---
# ClusterRole for Pipeline execution (needed for git operations and deployments)
apiVersion: rbac.authorization.k8s.io/v1
@@ -83,7 +38,7 @@ kind: ClusterRole
metadata:
name: tekton-pipeline-role
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/name: {{ .Values.labels.app.name }}
app.kubernetes.io/component: pipeline
rules:
# Ability to read/update deployments for GitOps
@@ -102,34 +57,15 @@ rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
---
# ClusterRoleBinding for Pipeline execution
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: tekton-pipeline-binding
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: pipeline
subjects:
- kind: ServiceAccount
name: tekton-pipeline-sa
namespace: tekton-pipelines
roleRef:
kind: ClusterRole
name: tekton-pipeline-role
apiGroup: rbac.authorization.k8s.io
---
# Role for EventListener to access triggers resources
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: tekton-triggers-eventlistener-role
namespace: tekton-pipelines
namespace: {{ .Values.namespace }}
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/name: {{ .Values.labels.app.name }}
app.kubernetes.io/component: triggers
rules:
- apiGroups: ["triggers.tekton.dev"]
@@ -138,22 +74,3 @@ rules:
- apiGroups: [""]
resources: ["configmaps", "secrets"]
verbs: ["get", "list", "watch"]
---
# RoleBinding for EventListener
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: tekton-triggers-eventlistener-binding
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: triggers
subjects:
- kind: ServiceAccount
name: tekton-triggers-sa
namespace: tekton-pipelines
roleRef:
kind: Role
name: tekton-triggers-eventlistener-role
apiGroup: rbac.authorization.k8s.io

View File

@@ -0,0 +1,32 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: pipeline-config
namespace: {{ .Values.namespace }}
labels:
app.kubernetes.io/name: {{ .Values.labels.app.name }}
app.kubernetes.io/component: config
data:
# Container Registry Configuration
REGISTRY_URL: "{{ .Values.global.registry.url }}"
# Git Configuration
GIT_BRANCH: "{{ .Values.global.git.branch }}"
GIT_USER_NAME: "{{ .Values.global.git.userName }}"
GIT_USER_EMAIL: "{{ .Values.global.git.userEmail }}"
# Build Configuration
BUILD_CACHE_TTL: "{{ .Values.pipeline.build.cacheTTL }}"
BUILD_VERBOSITY: "{{ .Values.pipeline.build.verbosity }}"
# Test Configuration
SKIP_TESTS: "{{ .Values.pipeline.test.skipTests }}"
SKIP_LINT: "{{ .Values.pipeline.test.skipLint }}"
# Deployment Configuration
DEPLOY_NAMESPACE: "{{ .Values.pipeline.deployment.namespace }}"
FLUX_NAMESPACE: "{{ .Values.pipeline.deployment.fluxNamespace }}"
# Workspace Configuration
WORKSPACE_SIZE: "{{ .Values.pipeline.workspace.size }}"
WORKSPACE_STORAGE_CLASS: "{{ .Values.pipeline.workspace.storageClass }}"

View File

@@ -0,0 +1,33 @@
# Tekton EventListener for Bakery-IA CI/CD
# This listener receives webhook events and triggers pipelines
apiVersion: triggers.tekton.dev/v1beta1
kind: EventListener
metadata:
name: bakery-ia-event-listener
namespace: {{ .Values.namespace }}
labels:
app.kubernetes.io/name: {{ .Values.labels.app.name }}
app.kubernetes.io/component: triggers
spec:
serviceAccountName: {{ .Values.serviceAccounts.triggers.name }}
triggers:
- name: bakery-ia-gitea-trigger
interceptors:
- ref:
name: "cel"
params:
- name: "filter"
value: "has(body.repository) && body.ref.contains('main')"
- ref:
name: "bitbucket"
params:
- name: "secretRef"
value:
secretName: gitea-webhook-secret
secretKey: secretToken
bindings:
- ref: bakery-ia-trigger-binding
template:
ref: bakery-ia-trigger-template
replicas: 1

View File

@@ -0,0 +1,9 @@
{{- if .Values.namespace }}
apiVersion: v1
kind: Namespace
metadata:
name: {{ .Values.namespace }}
labels:
app.kubernetes.io/name: {{ .Values.labels.app.name }}
app.kubernetes.io/component: {{ .Values.labels.app.component }}
{{- end }}

View File

@@ -6,9 +6,9 @@ apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: bakery-ia-ci
namespace: tekton-pipelines
namespace: {{ .Values.namespace }}
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/name: {{ .Values.labels.app.name }}
app.kubernetes.io/component: pipeline
spec:
workspaces:

View File

@@ -0,0 +1,51 @@
# ClusterRoleBinding for Tekton Triggers
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: tekton-triggers-binding
labels:
app.kubernetes.io/name: {{ .Values.labels.app.name }}
app.kubernetes.io/component: triggers
subjects:
- kind: ServiceAccount
name: {{ .Values.serviceAccounts.triggers.name }}
namespace: {{ .Values.namespace }}
roleRef:
kind: ClusterRole
name: tekton-triggers-role
apiGroup: rbac.authorization.k8s.io
---
# ClusterRoleBinding for Pipeline execution
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: tekton-pipeline-binding
labels:
app.kubernetes.io/name: {{ .Values.labels.app.name }}
app.kubernetes.io/component: pipeline
subjects:
- kind: ServiceAccount
name: {{ .Values.serviceAccounts.pipeline.name }}
namespace: {{ .Values.namespace }}
roleRef:
kind: ClusterRole
name: tekton-pipeline-role
apiGroup: rbac.authorization.k8s.io
---
# RoleBinding for EventListener
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: tekton-triggers-eventlistener-binding
namespace: {{ .Values.namespace }}
labels:
app.kubernetes.io/name: {{ .Values.labels.app.name }}
app.kubernetes.io/component: triggers
subjects:
- kind: ServiceAccount
name: {{ .Values.serviceAccounts.triggers.name }}
namespace: {{ .Values.namespace }}
roleRef:
kind: Role
name: tekton-triggers-eventlistener-role
apiGroup: rbac.authorization.k8s.io

View File

@@ -0,0 +1,72 @@
# Secret for Gitea webhook validation
# Used by EventListener to validate incoming webhooks
apiVersion: v1
kind: Secret
metadata:
name: gitea-webhook-secret
namespace: {{ .Values.namespace }}
labels:
app.kubernetes.io/name: {{ .Values.labels.app.name }}
app.kubernetes.io/component: triggers
annotations:
note: "Webhook secret for validating incoming webhooks"
type: Opaque
stringData:
secretToken: {{ .Values.secrets.webhook.token | quote }}
---
# Secret for Gitea container registry credentials
# Used by Kaniko to push images to Gitea registry
apiVersion: v1
kind: Secret
metadata:
name: gitea-registry-credentials
namespace: {{ .Values.namespace }}
labels:
app.kubernetes.io/name: {{ .Values.labels.app.name }}
app.kubernetes.io/component: build
annotations:
note: "Registry credentials for pushing images"
type: kubernetes.io/dockerconfigjson
stringData:
.dockerconfigjson: |
{
"auths": {
{{ .Values.secrets.registry.registryUrl | quote }}: {
"username": {{ .Values.secrets.registry.username | quote }},
"password": {{ .Values.secrets.registry.password | quote }}
}
}
}
---
# Secret for Git credentials (used by pipeline to push GitOps updates)
apiVersion: v1
kind: Secret
metadata:
name: gitea-git-credentials
namespace: {{ .Values.namespace }}
labels:
app.kubernetes.io/name: {{ .Values.labels.app.name }}
app.kubernetes.io/component: gitops
annotations:
note: "Git credentials for GitOps updates"
type: Opaque
stringData:
username: {{ .Values.secrets.git.username | quote }}
password: {{ .Values.secrets.git.password | quote }}
---
# Secret for Flux GitRepository access
# Used by Flux to pull from Gitea repository
apiVersion: v1
kind: Secret
metadata:
name: gitea-credentials
namespace: {{ .Values.pipeline.deployment.fluxNamespace }}
labels:
app.kubernetes.io/name: {{ .Values.labels.app.name }}
app.kubernetes.io/component: flux
annotations:
note: "Credentials for Flux GitRepository access"
type: Opaque
stringData:
username: {{ .Values.secrets.git.username | quote }}
password: {{ .Values.secrets.git.password | quote }}

View File

@@ -0,0 +1,19 @@
# ServiceAccount for Tekton Triggers EventListener
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Values.serviceAccounts.triggers.name }}
namespace: {{ .Values.namespace }}
labels:
app.kubernetes.io/name: {{ .Values.labels.app.name }}
app.kubernetes.io/component: triggers
---
# ServiceAccount for Pipeline execution
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Values.serviceAccounts.pipeline.name }}
namespace: {{ .Values.namespace }}
labels:
app.kubernetes.io/name: {{ .Values.labels.app.name }}
app.kubernetes.io/component: pipeline

View File

@@ -5,9 +5,9 @@ apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: git-clone
namespace: tekton-pipelines
namespace: {{ .Values.namespace }}
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/name: {{ .Values.labels.app.name }}
app.kubernetes.io/component: source
spec:
workspaces:

View File

@@ -0,0 +1,51 @@
# Tekton Kaniko Build Task for Bakery-IA CI/CD
# This task builds and pushes container images using Kaniko
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: kaniko-build
namespace: {{ .Values.namespace }}
labels:
app.kubernetes.io/name: {{ .Values.labels.app.name }}
app.kubernetes.io/component: build
spec:
workspaces:
- name: source
description: Workspace containing the source code
- name: docker-credentials
description: Docker registry credentials
params:
- name: services
type: string
description: Comma-separated list of services to build
- name: registry
type: string
description: Container registry URL
- name: git-revision
type: string
description: Git revision to tag images with
results:
- name: build-status
description: Status of the build operation
steps:
- name: build-and-push
image: gcr.io/kaniko-project/executor:v1.15.0
env:
- name: DOCKER_CONFIG
value: /tekton/home/.docker
command:
- /kaniko/executor
args:
- --dockerfile=$(workspaces.source.path)/Dockerfile
- --destination=$(params.registry)/$(params.service):$(params.git-revision)
- --context=$(workspaces.source.path)
- --cache=true
- --cache-repo=$(params.registry)/cache
resources:
limits:
cpu: 2000m
memory: 4Gi
requests:
cpu: 500m
memory: 1Gi

View File

@@ -0,0 +1,86 @@
# Tekton Run Tests Task for Bakery-IA CI/CD
# This task runs tests on the source code
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: run-tests
namespace: {{ .Values.namespace }}
labels:
app.kubernetes.io/name: {{ .Values.labels.app.name }}
app.kubernetes.io/component: test
spec:
workspaces:
- name: source
description: Workspace containing the source code
params:
- name: services
type: string
description: Comma-separated list of services to test
- name: skip-tests
type: string
description: Skip tests if "true"
default: "false"
steps:
- name: run-unit-tests
image: python:3.11-slim
workingDir: $(workspaces.source.path)
script: |
#!/bin/bash
set -e
echo "============================================"
echo "Running Unit Tests"
echo "Services: $(params.services)"
echo "Skip tests: $(params.skip-tests)"
echo "============================================"
if [ "$(params.skip-tests)" = "true" ]; then
echo "Skipping tests as requested"
exit 0
fi
# Install dependencies if requirements file exists
if [ -f "requirements.txt" ]; then
pip install --no-cache-dir -r requirements.txt
fi
# Run unit tests
python -m pytest tests/unit/ -v
echo "Unit tests completed successfully"
resources:
limits:
cpu: 1000m
memory: 2Gi
requests:
cpu: 200m
memory: 512Mi
- name: run-integration-tests
image: python:3.11-slim
workingDir: $(workspaces.source.path)
script: |
#!/bin/bash
set -e
echo "============================================"
echo "Running Integration Tests"
echo "Services: $(params.services)"
echo "============================================"
if [ "$(params.skip-tests)" = "true" ]; then
echo "Skipping integration tests as requested"
exit 0
fi
# Run integration tests
python -m pytest tests/integration/ -v
echo "Integration tests completed successfully"
resources:
limits:
cpu: 1000m
memory: 2Gi
requests:
cpu: 200m
memory: 512Mi

View File

@@ -0,0 +1,104 @@
# Tekton Update GitOps Task for Bakery-IA CI/CD
# This task updates GitOps manifests with new image tags
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: update-gitops
namespace: {{ .Values.namespace }}
labels:
app.kubernetes.io/name: {{ .Values.labels.app.name }}
app.kubernetes.io/component: gitops
spec:
workspaces:
- name: source
description: Workspace containing the source code
- name: git-credentials
description: Git credentials for pushing changes
params:
- name: services
type: string
description: Comma-separated list of services to update
- name: registry
type: string
description: Container registry URL
- name: git-revision
type: string
description: Git revision to tag images with
- name: git-branch
type: string
description: Git branch to push changes to
- name: dry-run
type: string
description: Dry run mode - don't push changes
default: "false"
steps:
- name: update-manifests
image: alpine/git:2.43.0
workingDir: $(workspaces.source.path)
env:
- name: GIT_USERNAME
valueFrom:
secretKeyRef:
name: gitea-git-credentials
key: username
- name: GIT_PASSWORD
valueFrom:
secretKeyRef:
name: gitea-git-credentials
key: password
script: |
#!/bin/bash
set -e
echo "============================================"
echo "Updating GitOps Manifests"
echo "Services: $(params.services)"
echo "Registry: $(params.registry)"
echo "Revision: $(params.git-revision)"
echo "Branch: $(params.git-branch)"
echo "Dry run: $(params.dry-run)"
echo "============================================"
# Configure git
git config --global user.email "ci@bakery-ia.local"
git config --global user.name "bakery-ia-ci"
# Clone the GitOps repository
REPO_URL="https://${GIT_USERNAME}:${GIT_PASSWORD}@gitea.bakery-ia.local/bakery/bakery-ia-gitops.git"
git clone "$REPO_URL" /tmp/gitops
cd /tmp/gitops
# Switch to target branch
git checkout "$(params.git-branch)" || git checkout -b "$(params.git-branch)"
# Update image tags in Kubernetes manifests
for service in $(echo "$(params.services)" | tr ',' '\n'); do
echo "Updating manifest for service: $service"
# Find and update the image tag in the deployment YAML
if [ -f "deployments/${service}-deployment.yaml" ]; then
sed -i "s|image: bakery/${service}:.*|image: $(params.registry)/bakery/${service}:$(params.git-revision)|g" "deployments/${service}-deployment.yaml"
fi
done
# Commit and push changes (unless dry-run)
if [ "$(params.dry-run)" != "true" ]; then
git add .
git commit -m "Update images for services: $(params.services) [skip ci]"
git push origin "$(params.git-branch)"
echo "GitOps manifests updated successfully"
else
echo "Dry run mode - changes not pushed"
git status
git diff
fi
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi

View File

@@ -0,0 +1,23 @@
# Tekton TriggerBinding for Bakery-IA CI/CD
# This binding extracts parameters from incoming webhook payloads
apiVersion: triggers.tekton.dev/v1beta1
kind: TriggerBinding
metadata:
name: bakery-ia-trigger-binding
namespace: {{ .Values.namespace }}
labels:
app.kubernetes.io/name: {{ .Values.labels.app.name }}
app.kubernetes.io/component: triggers
spec:
params:
- name: git-repo-url
value: "{{"{{ .payload.repository.clone_url }}"}}"
- name: git-revision
value: "{{"{{ .payload.after }}"}}"
- name: git-branch
value: "{{"{{ .payload.ref }}" | replace "refs/heads/" "" | replace "refs/tags/" "" }}"
- name: git-repo-name
value: "{{"{{ .payload.repository.name }}"}}"
- name: git-repo-full-name
value: "{{"{{ .payload.repository.full_name }}"}}"

View File

@@ -1,20 +1,13 @@
# Tekton TriggerTemplate for Bakery-IA CI/CD
# This template defines how PipelineRuns are created when triggers fire
#
# Registry URL Configuration:
# The registry URL is configured via the 'registry' parameter.
# Default value should match pipeline-config ConfigMap's REGISTRY_URL.
# To change the registry, update BOTH:
# 1. This template's default value
# 2. The pipeline-config ConfigMap
apiVersion: triggers.tekton.dev/v1beta1
kind: TriggerTemplate
metadata:
name: bakery-ia-trigger-template
namespace: tekton-pipelines
namespace: {{ .Values.namespace }}
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/name: {{ .Values.labels.app.name }}
app.kubernetes.io/component: triggers
spec:
params:
@@ -34,14 +27,14 @@ spec:
# Registry URL - keep in sync with pipeline-config ConfigMap
- name: registry-url
description: Container registry URL
default: "gitea.bakery-ia.local:5000"
default: {{ .Values.global.registry.url | quote }}
resourcetemplates:
- apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
generateName: bakery-ia-ci-run-
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/name: {{ .Values.labels.app.name }}
tekton.dev/pipeline: bakery-ia-ci
triggers.tekton.dev/trigger: bakery-ia-gitea-trigger
annotations:
@@ -51,7 +44,7 @@ spec:
spec:
pipelineRef:
name: bakery-ia-ci
serviceAccountName: tekton-pipeline-sa
serviceAccountName: {{ .Values.serviceAccounts.pipeline.name }}
workspaces:
- name: shared-workspace
volumeClaimTemplate:
@@ -59,7 +52,7 @@ spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi
storage: {{ .Values.pipeline.workspace.size }}
- name: docker-credentials
secret:
secretName: gitea-registry-credentials

View File

@@ -0,0 +1,24 @@
# Test values for Tekton Helm chart
# This file overrides default values for testing purposes
# Use a test namespace
namespace: "tekton-test"
# Test registry URL
global:
registry:
url: "localhost:5000"
# Test secrets
secrets:
webhook:
token: "test-webhook-token"
registry:
username: "test-user"
password: "test-password"
registryUrl: "localhost:5000"
git:
username: "test-git-user"
password: "test-git-password"

View File

@@ -0,0 +1,91 @@
# Default values for tekton-cicd Helm chart
# This file contains configurable values for the CI/CD pipeline
# Global settings
global:
# Registry configuration
registry:
url: "gitea.bakery-ia.local:5000"
# Git configuration
git:
branch: "main"
userName: "bakery-ia-ci"
userEmail: "ci@bakery-ia.local"
# Pipeline configuration
pipeline:
# Build configuration
build:
cacheTTL: "24h"
verbosity: "info"
# Test configuration
test:
skipTests: "false"
skipLint: "false"
# Deployment configuration
deployment:
namespace: "bakery-ia"
fluxNamespace: "flux-system"
# Workspace configuration
workspace:
size: "5Gi"
storageClass: "standard"
# Tekton controller settings
controller:
replicas: 1
resources:
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 100m
memory: 128Mi
# Tekton webhook settings
webhook:
replicas: 1
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 50m
memory: 64Mi
# Namespace for Tekton resources
namespace: "tekton-pipelines"
# Secrets configuration
secrets:
# Webhook secret for validating incoming webhooks
webhook:
token: "example-webhook-token-do-not-use-in-production"
# Registry credentials for pushing images
registry:
username: "example-user"
password: "example-password"
registryUrl: "gitea.bakery-ia.local:5000"
# Git credentials for GitOps updates
git:
username: "example-user"
password: "example-password"
# Service accounts
serviceAccounts:
triggers:
name: "tekton-triggers-sa"
pipeline:
name: "tekton-pipeline-sa"
# Labels to apply to resources
labels:
app:
name: "bakery-ia-cicd"
component: "tekton"

View File

@@ -1,222 +0,0 @@
# Workspace and PipelineRun Cleanup for Bakery-IA CI/CD
# This CronJob cleans up old PipelineRuns and PVCs to prevent storage exhaustion
---
# ServiceAccount for cleanup job
apiVersion: v1
kind: ServiceAccount
metadata:
name: tekton-cleanup-sa
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: cleanup
---
# ClusterRole for cleanup operations
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: tekton-cleanup-role
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: cleanup
rules:
- apiGroups: ["tekton.dev"]
resources: ["pipelineruns", "taskruns"]
verbs: ["get", "list", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "delete"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "delete"]
---
# ClusterRoleBinding for cleanup
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: tekton-cleanup-binding
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: cleanup
subjects:
- kind: ServiceAccount
name: tekton-cleanup-sa
namespace: tekton-pipelines
roleRef:
kind: ClusterRole
name: tekton-cleanup-role
apiGroup: rbac.authorization.k8s.io
---
# CronJob to clean up old PipelineRuns
apiVersion: batch/v1
kind: CronJob
metadata:
name: tekton-pipelinerun-cleanup
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: cleanup
spec:
# Run every 6 hours
schedule: "0 */6 * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTemplate:
spec:
ttlSecondsAfterFinished: 3600
template:
metadata:
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: cleanup
spec:
serviceAccountName: tekton-cleanup-sa
restartPolicy: OnFailure
containers:
- name: cleanup
image: bitnami/kubectl:latest
command:
- /bin/sh
- -c
- |
#!/bin/sh
set -e
echo "============================================"
echo "Tekton Cleanup Job"
echo "Timestamp: $(date -u +"%Y-%m-%dT%H:%M:%SZ")"
echo "============================================"
# Configuration
NAMESPACE="tekton-pipelines"
MAX_AGE_HOURS=24
KEEP_RECENT=10
echo ""
echo "Configuration:"
echo " Namespace: $NAMESPACE"
echo " Max Age: ${MAX_AGE_HOURS} hours"
echo " Keep Recent: $KEEP_RECENT"
echo ""
# Get current timestamp
CURRENT_TIME=$(date +%s)
# Clean up completed PipelineRuns older than MAX_AGE_HOURS
echo "Cleaning up old PipelineRuns..."
# Get all completed PipelineRuns
COMPLETED_RUNS=$(kubectl get pipelineruns -n "$NAMESPACE" \
--no-headers \
-o custom-columns=NAME:.metadata.name,STATUS:.status.conditions[0].reason,AGE:.metadata.creationTimestamp \
2>/dev/null | grep -E "Succeeded|Failed" || true)
DELETED_COUNT=0
echo "$COMPLETED_RUNS" | while read -r line; do
if [ -z "$line" ]; then
continue
fi
RUN_NAME=$(echo "$line" | awk '{print $1}')
RUN_TIME=$(echo "$line" | awk '{print $3}')
if [ -z "$RUN_NAME" ] || [ -z "$RUN_TIME" ]; then
continue
fi
# Convert timestamp to seconds
RUN_TIMESTAMP=$(date -d "$RUN_TIME" +%s 2>/dev/null || echo "0")
if [ "$RUN_TIMESTAMP" = "0" ]; then
continue
fi
# Calculate age in hours
AGE_SECONDS=$((CURRENT_TIME - RUN_TIMESTAMP))
AGE_HOURS=$((AGE_SECONDS / 3600))
if [ "$AGE_HOURS" -gt "$MAX_AGE_HOURS" ]; then
echo "Deleting PipelineRun: $RUN_NAME (age: ${AGE_HOURS}h)"
kubectl delete pipelinerun "$RUN_NAME" -n "$NAMESPACE" --ignore-not-found=true
DELETED_COUNT=$((DELETED_COUNT + 1))
fi
done
echo "Deleted $DELETED_COUNT old PipelineRuns"
# Clean up orphaned PVCs (PVCs without associated PipelineRuns)
echo ""
echo "Cleaning up orphaned PVCs..."
ORPHANED_PVCS=$(kubectl get pvc -n "$NAMESPACE" \
-l tekton.dev/pipelineRun \
--no-headers \
-o custom-columns=NAME:.metadata.name,PIPELINERUN:.metadata.labels.tekton\\.dev/pipelineRun \
2>/dev/null || true)
echo "$ORPHANED_PVCS" | while read -r line; do
if [ -z "$line" ]; then
continue
fi
PVC_NAME=$(echo "$line" | awk '{print $1}')
PR_NAME=$(echo "$line" | awk '{print $2}')
if [ -z "$PVC_NAME" ]; then
continue
fi
# Check if associated PipelineRun exists
if ! kubectl get pipelinerun "$PR_NAME" -n "$NAMESPACE" > /dev/null 2>&1; then
echo "Deleting orphaned PVC: $PVC_NAME (PipelineRun $PR_NAME not found)"
kubectl delete pvc "$PVC_NAME" -n "$NAMESPACE" --ignore-not-found=true
fi
done
# Clean up completed/failed pods older than 1 hour
echo ""
echo "Cleaning up old completed pods..."
kubectl delete pods -n "$NAMESPACE" \
--field-selector=status.phase=Succeeded \
--ignore-not-found=true 2>/dev/null || true
kubectl delete pods -n "$NAMESPACE" \
--field-selector=status.phase=Failed \
--ignore-not-found=true 2>/dev/null || true
echo ""
echo "============================================"
echo "Cleanup complete"
echo "============================================"
resources:
limits:
cpu: 200m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
---
# ConfigMap for cleanup configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: cleanup-config
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: cleanup
data:
# Maximum age of completed PipelineRuns to keep (in hours)
MAX_AGE_HOURS: "24"
# Number of recent PipelineRuns to keep regardless of age
KEEP_RECENT: "10"
# Cleanup schedule (cron format)
CLEANUP_SCHEDULE: "0 */6 * * *"

View File

@@ -1,5 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- cleanup.yaml

View File

@@ -1,5 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- pipeline-config.yaml

View File

@@ -1,41 +0,0 @@
# CI/CD Pipeline Configuration for Bakery-IA
# This ConfigMap contains configurable values for the CI/CD pipeline
#
# IMPORTANT: When changing REGISTRY_URL, also update:
# - infrastructure/cicd/tekton/triggers/trigger-template.yaml (registry-url default)
# - infrastructure/cicd/tekton/secrets/secrets.yaml (registry credentials)
apiVersion: v1
kind: ConfigMap
metadata:
name: pipeline-config
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: config
data:
# Container Registry Configuration
# Change this to your actual registry URL
# Also update trigger-template.yaml and secrets when changing this!
REGISTRY_URL: "gitea.bakery-ia.local:5000"
# Git Configuration
GIT_BRANCH: "main"
GIT_USER_NAME: "bakery-ia-ci"
GIT_USER_EMAIL: "ci@bakery-ia.local"
# Build Configuration
BUILD_CACHE_TTL: "24h"
BUILD_VERBOSITY: "info"
# Test Configuration
SKIP_TESTS: "false"
SKIP_LINT: "false"
# Deployment Configuration
DEPLOY_NAMESPACE: "bakery-ia"
FLUX_NAMESPACE: "flux-system"
# Workspace Configuration
WORKSPACE_SIZE: "5Gi"
WORKSPACE_STORAGE_CLASS: "standard"

View File

@@ -1,11 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- rbac/
- secrets/
- configs/
- tasks/
- triggers/
- pipelines/
- cleanup/

View File

@@ -1,6 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ci-pipeline.yaml
- prod-deploy-pipeline.yaml

View File

@@ -1,118 +0,0 @@
# Production Deployment Pipeline for Bakery-IA
# This pipeline handles production deployments with manual approval gate
# It should be triggered after the CI pipeline succeeds
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: bakery-ia-prod-deploy
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: pipeline
app.kubernetes.io/environment: production
spec:
workspaces:
- name: shared-workspace
description: Shared workspace for source code
- name: git-credentials
description: Git credentials for pushing GitOps updates
optional: true
params:
- name: git-url
type: string
description: Repository URL
- name: git-revision
type: string
description: Git revision/commit hash to deploy
- name: services
type: string
description: Comma-separated list of services to deploy
- name: registry
type: string
description: Container registry URL
- name: approver
type: string
description: Name of the person who approved this deployment
default: "automated"
- name: approval-ticket
type: string
description: Ticket/issue number for deployment approval
default: "N/A"
tasks:
# Stage 1: Fetch source code
- name: fetch-source
taskRef:
name: git-clone
workspaces:
- name: output
workspace: shared-workspace
params:
- name: url
value: $(params.git-url)
- name: revision
value: $(params.git-revision)
# Stage 2: Verify images exist in registry
- name: verify-images
runAfter: [fetch-source]
taskRef:
name: verify-images
params:
- name: services
value: $(params.services)
- name: registry
value: $(params.registry)
- name: git-revision
value: $(params.git-revision)
# Stage 3: Pre-deployment validation
- name: pre-deploy-validation
runAfter: [verify-images]
taskRef:
name: pre-deploy-validation
workspaces:
- name: source
workspace: shared-workspace
params:
- name: services
value: $(params.services)
- name: environment
value: "production"
# Stage 4: Update production manifests
- name: update-prod-manifests
runAfter: [pre-deploy-validation]
taskRef:
name: update-gitops
workspaces:
- name: source
workspace: shared-workspace
- name: git-credentials
workspace: git-credentials
params:
- name: services
value: $(params.services)
- name: registry
value: $(params.registry)
- name: git-revision
value: $(params.git-revision)
- name: git-branch
value: "main"
- name: dry-run
value: "false"
finally:
- name: deployment-summary
taskRef:
name: prod-deployment-summary
params:
- name: services
value: $(params.services)
- name: git-revision
value: $(params.git-revision)
- name: approver
value: $(params.approver)
- name: approval-ticket
value: $(params.approval-ticket)

View File

@@ -1,6 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- rbac.yaml
- resource-quota.yaml

View File

@@ -1,64 +0,0 @@
# ResourceQuota for Tekton Pipelines Namespace
# Prevents resource exhaustion from runaway pipeline runs
#
# This quota limits:
# - Total CPU and memory that can be requested/used
# - Number of concurrent pods
# - Number of PVCs for workspaces
apiVersion: v1
kind: ResourceQuota
metadata:
name: tekton-pipelines-quota
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: quota
spec:
hard:
# Limit total CPU
requests.cpu: "8"
limits.cpu: "16"
# Limit total memory
requests.memory: "16Gi"
limits.memory: "32Gi"
# Limit number of pods (controls concurrent pipeline tasks)
pods: "20"
# Limit PVCs (controls workspace storage)
persistentvolumeclaims: "10"
# Limit storage
requests.storage: "50Gi"
---
# LimitRange to set defaults and limits for individual pods
# Ensures every pod has resource requests/limits
apiVersion: v1
kind: LimitRange
metadata:
name: tekton-pipelines-limits
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: quota
spec:
limits:
# Default limits for containers
- type: Container
default:
cpu: "1"
memory: "1Gi"
defaultRequest:
cpu: "100m"
memory: "256Mi"
max:
cpu: "4"
memory: "8Gi"
min:
cpu: "50m"
memory: "64Mi"
# Limits for PVCs
- type: PersistentVolumeClaim
max:
storage: "10Gi"
min:
storage: "1Gi"

View File

@@ -1,4 +0,0 @@
# Ignore generated secrets
.webhook-secret
*-actual.yaml
sealed-secrets.yaml

View File

@@ -1,167 +0,0 @@
#!/bin/bash
# Generate CI/CD Secrets for Bakery-IA
#
# This script creates Kubernetes secrets required for the CI/CD pipeline.
# Run this script once during initial setup.
#
# Usage:
# ./generate-secrets.sh [options]
#
# Options:
# --registry-url Container registry URL (default: gitea.bakery-ia.local:5000)
# --gitea-user Gitea username (will prompt if not provided)
# --gitea-password Gitea password (will prompt if not provided)
# --dry-run Print commands without executing
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Default values
REGISTRY_URL="${REGISTRY_URL:-gitea.bakery-ia.local:5000}"
DRY_RUN=false
KUBECTL="kubectl"
# Check if running in microk8s
if command -v microk8s &> /dev/null; then
KUBECTL="microk8s kubectl"
fi
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--registry-url)
REGISTRY_URL="$2"
shift 2
;;
--gitea-user)
GITEA_USERNAME="$2"
shift 2
;;
--gitea-password)
GITEA_PASSWORD="$2"
shift 2
;;
--dry-run)
DRY_RUN=true
shift
;;
*)
echo -e "${RED}Unknown option: $1${NC}"
exit 1
;;
esac
done
echo "=========================================="
echo " Bakery-IA CI/CD Secrets Generator"
echo "=========================================="
echo ""
# Prompt for credentials if not provided
if [ -z "$GITEA_USERNAME" ]; then
read -p "Enter Gitea username: " GITEA_USERNAME
fi
if [ -z "$GITEA_PASSWORD" ]; then
read -s -p "Enter Gitea password: " GITEA_PASSWORD
echo ""
fi
# Generate webhook secret
WEBHOOK_SECRET=$(openssl rand -hex 32)
echo ""
echo -e "${YELLOW}Configuration:${NC}"
echo " Registry URL: $REGISTRY_URL"
echo " Gitea User: $GITEA_USERNAME"
echo " Webhook Secret: ${WEBHOOK_SECRET:0:8}..."
echo ""
# Function to create secret
create_secret() {
local cmd="$1"
if [ "$DRY_RUN" = true ]; then
echo -e "${YELLOW}[DRY-RUN]${NC} $cmd"
else
eval "$cmd"
fi
}
# Ensure namespaces exist
echo -e "${GREEN}Creating namespaces if they don't exist...${NC}"
create_secret "$KUBECTL create namespace tekton-pipelines --dry-run=client -o yaml | $KUBECTL apply -f -"
create_secret "$KUBECTL create namespace flux-system --dry-run=client -o yaml | $KUBECTL apply -f -"
echo ""
echo -e "${GREEN}Creating secrets...${NC}"
# 1. Webhook Secret
echo " Creating gitea-webhook-secret..."
create_secret "$KUBECTL create secret generic gitea-webhook-secret \
--namespace tekton-pipelines \
--from-literal=secretToken='$WEBHOOK_SECRET' \
--dry-run=client -o yaml | $KUBECTL apply -f -"
# 2. Registry Credentials (docker-registry type)
echo " Creating gitea-registry-credentials..."
create_secret "$KUBECTL create secret docker-registry gitea-registry-credentials \
--namespace tekton-pipelines \
--docker-server='$REGISTRY_URL' \
--docker-username='$GITEA_USERNAME' \
--docker-password='$GITEA_PASSWORD' \
--dry-run=client -o yaml | $KUBECTL apply -f -"
# 3. Git Credentials for Tekton
echo " Creating gitea-git-credentials..."
create_secret "$KUBECTL create secret generic gitea-git-credentials \
--namespace tekton-pipelines \
--from-literal=username='$GITEA_USERNAME' \
--from-literal=password='$GITEA_PASSWORD' \
--dry-run=client -o yaml | $KUBECTL apply -f -"
# 4. Flux Git Credentials
echo " Creating gitea-credentials for Flux..."
create_secret "$KUBECTL create secret generic gitea-credentials \
--namespace flux-system \
--from-literal=username='$GITEA_USERNAME' \
--from-literal=password='$GITEA_PASSWORD' \
--dry-run=client -o yaml | $KUBECTL apply -f -"
# Label all secrets
echo ""
echo -e "${GREEN}Adding labels to secrets...${NC}"
for ns in tekton-pipelines flux-system; do
for secret in gitea-webhook-secret gitea-registry-credentials gitea-git-credentials gitea-credentials; do
if $KUBECTL get secret "$secret" -n "$ns" &> /dev/null; then
create_secret "$KUBECTL label secret $secret -n $ns app.kubernetes.io/name=bakery-ia-cicd --overwrite 2>/dev/null || true"
fi
done
done
echo ""
echo "=========================================="
echo -e "${GREEN}Secrets created successfully!${NC}"
echo "=========================================="
echo ""
echo -e "${YELLOW}IMPORTANT:${NC} Save this webhook secret for Gitea webhook configuration:"
echo ""
echo " Webhook Secret: $WEBHOOK_SECRET"
echo ""
echo "Configure this in Gitea:"
echo " 1. Go to Repository Settings > Webhooks"
echo " 2. Add webhook with URL: http://el-bakery-ia-listener.tekton-pipelines.svc.cluster.local:8080"
echo " 3. Set Secret to the webhook secret above"
echo " 4. Select events: Push"
echo ""
# Save webhook secret to a file for reference (gitignored)
if [ "$DRY_RUN" = false ]; then
echo "$WEBHOOK_SECRET" > "$(dirname "$0")/.webhook-secret"
chmod 600 "$(dirname "$0")/.webhook-secret"
echo "Webhook secret saved to .webhook-secret (gitignored)"
fi

View File

@@ -1,19 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- secrets.yaml
# Note: In production, use sealed-secrets or external-secrets-operator
# to manage secrets securely. The secrets.yaml file contains placeholder
# values that must be replaced before deployment.
#
# Example using sealed-secrets:
# 1. Install sealed-secrets controller
# 2. Create SealedSecret resources instead of plain Secrets
# 3. Commit the SealedSecret manifests to Git (safe to commit)
#
# Example using external-secrets-operator:
# 1. Install external-secrets-operator
# 2. Configure a SecretStore (AWS Secrets Manager, HashiCorp Vault, etc.)
# 3. Create ExternalSecret resources that reference the SecretStore

View File

@@ -1,79 +0,0 @@
# CI/CD Secrets Template for Tekton Pipelines
#
# DO NOT commit this file with actual credentials!
# Use the generate-secrets.sh script to create secrets safely.
#
# For production, use one of these approaches:
# 1. Sealed Secrets: kubeseal < secrets.yaml > sealed-secrets.yaml
# 2. External Secrets Operator: Configure with your secret store
# 3. Manual creation: kubectl create secret ... (see generate-secrets.sh)
---
# Secret for Gitea webhook validation
# Used by EventListener to validate incoming webhooks
apiVersion: v1
kind: Secret
metadata:
name: gitea-webhook-secret
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: triggers
type: Opaque
stringData:
# Generate with: openssl rand -hex 32
secretToken: "${WEBHOOK_SECRET_TOKEN}"
---
# Secret for Gitea container registry credentials
# Used by Kaniko to push images to Gitea registry
apiVersion: v1
kind: Secret
metadata:
name: gitea-registry-credentials
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: build
type: kubernetes.io/dockerconfigjson
stringData:
.dockerconfigjson: |
{
"auths": {
"${REGISTRY_URL}": {
"username": "${GITEA_USERNAME}",
"password": "${GITEA_PASSWORD}"
}
}
}
---
# Secret for Git credentials (used by pipeline to push GitOps updates)
apiVersion: v1
kind: Secret
metadata:
name: gitea-git-credentials
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: gitops
type: Opaque
stringData:
username: "${GITEA_USERNAME}"
password: "${GITEA_PASSWORD}"
---
# Secret for Flux GitRepository access
# Used by Flux to pull from Gitea repository
apiVersion: v1
kind: Secret
metadata:
name: gitea-credentials
namespace: flux-system
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: flux
type: Opaque
stringData:
username: "${GITEA_USERNAME}"
password: "${GITEA_PASSWORD}"

View File

@@ -1,98 +0,0 @@
# CI/CD Secrets for Tekton Pipelines
#
# WARNING: This file contains EXAMPLE values only!
# DO NOT use these values in production.
#
# To create actual secrets, use ONE of these methods:
#
# Method 1 (Recommended): Use the generate-secrets.sh script
# ./generate-secrets.sh --gitea-user <username> --gitea-password <password>
#
# Method 2: Create secrets manually with kubectl
# kubectl create secret generic gitea-webhook-secret \
# --namespace tekton-pipelines \
# --from-literal=secretToken="$(openssl rand -hex 32)"
#
# Method 3: Use Sealed Secrets for GitOps
# kubeseal < secrets-template.yaml > sealed-secrets.yaml
#
# Method 4: Use External Secrets Operator
# Configure ESO to pull from your secret store (Vault, AWS SM, etc.)
---
# Example Secret for Gitea webhook validation
# Used by EventListener to validate incoming webhooks
apiVersion: v1
kind: Secret
metadata:
name: gitea-webhook-secret
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: triggers
annotations:
note: "EXAMPLE - Replace with actual secret using generate-secrets.sh"
type: Opaque
stringData:
# Generate with: openssl rand -hex 32
secretToken: "example-webhook-token-do-not-use-in-production"
---
# Example Secret for Gitea container registry credentials
# Used by Kaniko to push images to Gitea registry
apiVersion: v1
kind: Secret
metadata:
name: gitea-registry-credentials
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: build
annotations:
note: "EXAMPLE - Replace with actual secret using generate-secrets.sh"
type: kubernetes.io/dockerconfigjson
stringData:
.dockerconfigjson: |
{
"auths": {
"gitea.bakery-ia.local:5000": {
"username": "example-user",
"password": "example-password"
}
}
}
---
# Example Secret for Git credentials (used by pipeline to push GitOps updates)
apiVersion: v1
kind: Secret
metadata:
name: gitea-git-credentials
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: gitops
annotations:
note: "EXAMPLE - Replace with actual secret using generate-secrets.sh"
type: Opaque
stringData:
username: "example-user"
password: "example-password"
---
# Example Secret for Flux GitRepository access
# Used by Flux to pull from Gitea repository
apiVersion: v1
kind: Secret
metadata:
name: gitea-credentials
namespace: flux-system
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: flux
annotations:
note: "EXAMPLE - Replace with actual secret using generate-secrets.sh"
type: Opaque
stringData:
username: "example-user"
password: "example-password"

View File

@@ -1,154 +0,0 @@
# Tekton Detect Changed Services Task for Bakery-IA CI/CD
# This task identifies which services have changed in the repository
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: detect-changed-services
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: detect
spec:
workspaces:
- name: source
description: Source code workspace
params:
- name: base-ref
type: string
description: Base reference for comparison (default HEAD~1)
default: "HEAD~1"
results:
- name: changed-services
description: Comma-separated list of changed services
- name: changed-files-count
description: Number of files changed
steps:
- name: detect
image: alpine/git:2.43.0
script: |
#!/bin/sh
set -e
SOURCE_PATH="$(workspaces.source.path)"
BASE_REF="$(params.base-ref)"
cd "$SOURCE_PATH"
echo "============================================"
echo "Detect Changed Services"
echo "============================================"
echo "Base ref: $BASE_REF"
echo "============================================"
# Get list of changed files compared to base reference
echo ""
echo "Detecting changed files..."
# Try to get diff, fall back to listing all files if this is the first commit
CHANGED_FILES=$(git diff --name-only "$BASE_REF" HEAD 2>/dev/null || git ls-tree -r HEAD --name-only)
FILE_COUNT=$(echo "$CHANGED_FILES" | grep -c "." || echo "0")
echo "Found $FILE_COUNT changed files"
echo "$FILE_COUNT" > $(results.changed-files-count.path)
if [ "$FILE_COUNT" = "0" ]; then
echo "No files changed"
echo "none" > $(results.changed-services.path)
exit 0
fi
echo ""
echo "Changed files:"
echo "$CHANGED_FILES" | head -20
if [ "$FILE_COUNT" -gt 20 ]; then
echo "... and $((FILE_COUNT - 20)) more files"
fi
# Map files to services using simple shell (no bash arrays)
echo ""
echo "Mapping files to services..."
CHANGED_SERVICES=""
# Process each file
echo "$CHANGED_FILES" | while read -r file; do
if [ -z "$file" ]; then
continue
fi
# Check services directory
if echo "$file" | grep -q "^services/"; then
SERVICE=$(echo "$file" | cut -d'/' -f2)
if [ -n "$SERVICE" ] && ! echo "$CHANGED_SERVICES" | grep -q "$SERVICE"; then
if [ -z "$CHANGED_SERVICES" ]; then
CHANGED_SERVICES="$SERVICE"
else
CHANGED_SERVICES="$CHANGED_SERVICES,$SERVICE"
fi
echo "$CHANGED_SERVICES" > /tmp/services.txt
fi
fi
# Check frontend
if echo "$file" | grep -q "^frontend/"; then
if ! echo "$CHANGED_SERVICES" | grep -q "frontend"; then
if [ -z "$CHANGED_SERVICES" ]; then
CHANGED_SERVICES="frontend"
else
CHANGED_SERVICES="$CHANGED_SERVICES,frontend"
fi
echo "$CHANGED_SERVICES" > /tmp/services.txt
fi
fi
# Check gateway
if echo "$file" | grep -q "^gateway/"; then
if ! echo "$CHANGED_SERVICES" | grep -q "gateway"; then
if [ -z "$CHANGED_SERVICES" ]; then
CHANGED_SERVICES="gateway"
else
CHANGED_SERVICES="$CHANGED_SERVICES,gateway"
fi
echo "$CHANGED_SERVICES" > /tmp/services.txt
fi
fi
# Check infrastructure
if echo "$file" | grep -q "^infrastructure/"; then
if ! echo "$CHANGED_SERVICES" | grep -q "infrastructure"; then
if [ -z "$CHANGED_SERVICES" ]; then
CHANGED_SERVICES="infrastructure"
else
CHANGED_SERVICES="$CHANGED_SERVICES,infrastructure"
fi
echo "$CHANGED_SERVICES" > /tmp/services.txt
fi
fi
done
# Read the accumulated services
if [ -f /tmp/services.txt ]; then
CHANGED_SERVICES=$(cat /tmp/services.txt)
fi
echo ""
echo "============================================"
# Output result
if [ -z "$CHANGED_SERVICES" ]; then
echo "No service changes detected"
echo "none" > $(results.changed-services.path)
else
echo "Detected changes in services: $CHANGED_SERVICES"
echo "$CHANGED_SERVICES" > $(results.changed-services.path)
fi
echo "============================================"
resources:
limits:
cpu: 200m
memory: 128Mi
requests:
cpu: 50m
memory: 64Mi

View File

@@ -1,200 +0,0 @@
# Tekton Kaniko Build Task for Bakery-IA CI/CD
# This task builds and pushes container images using Kaniko
# Supports building multiple services from a comma-separated list
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: kaniko-build
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: build
spec:
workspaces:
- name: source
description: Source code workspace
- name: docker-credentials
description: Docker registry credentials
params:
- name: services
type: string
description: Comma-separated list of services to build
- name: registry
type: string
description: Container registry URL
- name: git-revision
type: string
description: Git revision for image tag
default: "latest"
results:
- name: built-images
description: List of successfully built images
- name: build-status
description: Overall build status (success/failure)
steps:
# Step 1: Setup docker credentials
- name: setup-docker-config
image: alpine:3.18
script: |
#!/bin/sh
set -e
echo "Setting up Docker credentials..."
mkdir -p /kaniko/.docker
# Check if credentials secret is mounted
if [ -f "$(workspaces.docker-credentials.path)/config.json" ]; then
cp "$(workspaces.docker-credentials.path)/config.json" /kaniko/.docker/config.json
echo "Docker config copied from secret"
elif [ -f "$(workspaces.docker-credentials.path)/.dockerconfigjson" ]; then
cp "$(workspaces.docker-credentials.path)/.dockerconfigjson" /kaniko/.docker/config.json
echo "Docker config copied from .dockerconfigjson"
else
echo "Warning: No docker credentials found, builds may fail for private registries"
echo '{}' > /kaniko/.docker/config.json
fi
volumeMounts:
- name: docker-config
mountPath: /kaniko/.docker
resources:
limits:
cpu: 100m
memory: 64Mi
requests:
cpu: 50m
memory: 32Mi
# Step 2: Build each service iteratively
- name: build-services
image: gcr.io/kaniko-project/executor:v1.23.0
script: |
#!/busybox/sh
set -e
SERVICES="$(params.services)"
REGISTRY="$(params.registry)"
REVISION="$(params.git-revision)"
SOURCE_PATH="$(workspaces.source.path)"
BUILT_IMAGES=""
FAILED_SERVICES=""
echo "============================================"
echo "Starting build for services: $SERVICES"
echo "Registry: $REGISTRY"
echo "Tag: $REVISION"
echo "============================================"
# Skip if no services to build
if [ "$SERVICES" = "none" ] || [ -z "$SERVICES" ]; then
echo "No services to build, skipping..."
echo "none" > $(results.built-images.path)
echo "skipped" > $(results.build-status.path)
exit 0
fi
# Convert comma-separated list to space-separated
SERVICES_LIST=$(echo "$SERVICES" | tr ',' ' ')
for SERVICE in $SERVICES_LIST; do
# Trim whitespace
SERVICE=$(echo "$SERVICE" | tr -d ' ')
# Skip infrastructure changes (not buildable)
if [ "$SERVICE" = "infrastructure" ]; then
echo "Skipping infrastructure (not a buildable service)"
continue
fi
echo ""
echo "--------------------------------------------"
echo "Building service: $SERVICE"
echo "--------------------------------------------"
# Determine Dockerfile path based on service type
if [ "$SERVICE" = "frontend" ]; then
DOCKERFILE_PATH="$SOURCE_PATH/frontend/Dockerfile"
CONTEXT_PATH="$SOURCE_PATH/frontend"
elif [ "$SERVICE" = "gateway" ]; then
DOCKERFILE_PATH="$SOURCE_PATH/gateway/Dockerfile"
CONTEXT_PATH="$SOURCE_PATH/gateway"
else
DOCKERFILE_PATH="$SOURCE_PATH/services/$SERVICE/Dockerfile"
CONTEXT_PATH="$SOURCE_PATH"
fi
# Check if Dockerfile exists
if [ ! -f "$DOCKERFILE_PATH" ]; then
echo "Warning: Dockerfile not found at $DOCKERFILE_PATH, skipping $SERVICE"
FAILED_SERVICES="$FAILED_SERVICES $SERVICE"
continue
fi
IMAGE_NAME="$REGISTRY/bakery/$SERVICE:$REVISION"
IMAGE_NAME_LATEST="$REGISTRY/bakery/$SERVICE:latest"
echo "Dockerfile: $DOCKERFILE_PATH"
echo "Context: $CONTEXT_PATH"
echo "Image: $IMAGE_NAME"
# Run Kaniko build
/kaniko/executor \
--dockerfile="$DOCKERFILE_PATH" \
--context="$CONTEXT_PATH" \
--destination="$IMAGE_NAME" \
--destination="$IMAGE_NAME_LATEST" \
--cache=true \
--cache-ttl=24h \
--verbosity=info \
--snapshot-mode=redo \
--use-new-run
BUILD_EXIT_CODE=$?
if [ $BUILD_EXIT_CODE -eq 0 ]; then
echo "Successfully built and pushed: $IMAGE_NAME"
if [ -z "$BUILT_IMAGES" ]; then
BUILT_IMAGES="$IMAGE_NAME"
else
BUILT_IMAGES="$BUILT_IMAGES,$IMAGE_NAME"
fi
else
echo "Failed to build: $SERVICE (exit code: $BUILD_EXIT_CODE)"
FAILED_SERVICES="$FAILED_SERVICES $SERVICE"
fi
done
echo ""
echo "============================================"
echo "Build Summary"
echo "============================================"
echo "Built images: $BUILT_IMAGES"
echo "Failed services: $FAILED_SERVICES"
# Write results
if [ -z "$BUILT_IMAGES" ]; then
echo "none" > $(results.built-images.path)
else
echo "$BUILT_IMAGES" > $(results.built-images.path)
fi
if [ -n "$FAILED_SERVICES" ]; then
echo "partial" > $(results.build-status.path)
echo "Warning: Some services failed to build: $FAILED_SERVICES"
else
echo "success" > $(results.build-status.path)
fi
volumeMounts:
- name: docker-config
mountPath: /kaniko/.docker
securityContext:
runAsUser: 0
resources:
limits:
cpu: 2000m
memory: 4Gi
requests:
cpu: 500m
memory: 1Gi
volumes:
- name: docker-config
emptyDir: {}

View File

@@ -1,14 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- git-clone.yaml
- detect-changes.yaml
- run-tests.yaml
- kaniko-build.yaml
- update-gitops.yaml
- pipeline-summary.yaml
# Production deployment tasks
- verify-images.yaml
- pre-deploy-validation.yaml
- prod-deployment-summary.yaml

View File

@@ -1,62 +0,0 @@
# Tekton Pipeline Summary Task for Bakery-IA CI/CD
# This task runs at the end of the pipeline and provides a summary
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: pipeline-summary
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: summary
spec:
params:
- name: changed-services
type: string
description: List of changed services
default: "none"
- name: git-revision
type: string
description: Git revision that was built
default: "unknown"
steps:
- name: summary
image: alpine:3.18
script: |
#!/bin/sh
SERVICES="$(params.changed-services)"
REVISION="$(params.git-revision)"
echo ""
echo "============================================"
echo " Pipeline Execution Summary"
echo "============================================"
echo ""
echo "Git Revision: $REVISION"
echo "Changed Services: $SERVICES"
echo ""
echo "Timestamp: $(date -u +"%Y-%m-%dT%H:%M:%SZ")"
echo ""
echo "============================================"
echo ""
if [ "$SERVICES" = "none" ] || [ -z "$SERVICES" ]; then
echo "No services were changed in this commit."
echo "Pipeline completed without building any images."
else
echo "The following services were processed:"
echo "$SERVICES" | tr ',' '\n' | while read service; do
echo " - $service"
done
fi
echo ""
echo "============================================"
resources:
limits:
cpu: 100m
memory: 64Mi
requests:
cpu: 50m
memory: 32Mi

View File

@@ -1,76 +0,0 @@
# Task for pre-deployment validation
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: pre-deploy-validation
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: validation
spec:
workspaces:
- name: source
description: Source code workspace
params:
- name: services
type: string
description: Comma-separated list of services to validate
- name: environment
type: string
description: Target environment (staging/production)
default: "production"
results:
- name: validation-status
description: Status of validation (passed/failed)
steps:
- name: validate
image: registry.k8s.io/kustomize/kustomize:v5.3.0
script: |
#!/bin/sh
set -e
SOURCE_PATH="$(workspaces.source.path)"
SERVICES="$(params.services)"
ENVIRONMENT="$(params.environment)"
echo "============================================"
echo "Pre-Deployment Validation"
echo "============================================"
echo "Environment: $ENVIRONMENT"
echo "Services: $SERVICES"
echo "============================================"
cd "$SOURCE_PATH"
# Validate kustomization can be built
KUSTOMIZE_DIR="infrastructure/environments/$ENVIRONMENT"
if [ -d "$KUSTOMIZE_DIR" ]; then
echo ""
echo "Validating kustomization..."
if kustomize build "$KUSTOMIZE_DIR" > /dev/null 2>&1; then
echo " ✓ Kustomization is valid"
else
echo " ✗ Kustomization validation failed"
echo "failed" > $(results.validation-status.path)
exit 1
fi
fi
# Additional validation checks can be added here
# - Schema validation
# - Policy checks (OPA/Gatekeeper)
# - Security scanning
echo ""
echo "============================================"
echo "All validations passed"
echo "============================================"
echo "passed" > $(results.validation-status.path)
resources:
limits:
cpu: 500m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi

View File

@@ -1,57 +0,0 @@
# Task for production deployment summary
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: prod-deployment-summary
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: summary
spec:
params:
- name: services
type: string
description: List of deployed services
- name: git-revision
type: string
description: Git revision that was deployed
- name: approver
type: string
description: Name of the approver
- name: approval-ticket
type: string
description: Approval ticket number
steps:
- name: summary
image: alpine:3.18
script: |
#!/bin/sh
echo ""
echo "============================================"
echo " Production Deployment Summary"
echo "============================================"
echo ""
echo "Git Revision: $(params.git-revision)"
echo "Services: $(params.services)"
echo "Approved By: $(params.approver)"
echo "Approval Ticket: $(params.approval-ticket)"
echo "Timestamp: $(date -u +"%Y-%m-%dT%H:%M:%SZ")"
echo ""
echo "============================================"
echo ""
echo "Deployment to production initiated."
echo "Flux CD will reconcile the changes."
echo ""
echo "Monitor deployment status with:"
echo " kubectl get kustomization -n flux-system"
echo " kubectl get pods -n bakery-ia"
echo ""
echo "============================================"
resources:
limits:
cpu: 100m
memory: 64Mi
requests:
cpu: 50m
memory: 32Mi

View File

@@ -1,205 +0,0 @@
# Tekton Test Task for Bakery-IA CI/CD
# This task runs unit tests and linting for changed services
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: run-tests
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: test
spec:
workspaces:
- name: source
description: Source code workspace
params:
- name: services
type: string
description: Comma-separated list of services to test
- name: skip-lint
type: string
description: Skip linting if "true"
default: "false"
- name: skip-tests
type: string
description: Skip tests if "true"
default: "false"
results:
- name: test-status
description: Overall test status (passed/failed/skipped)
- name: tested-services
description: List of services that were tested
- name: failed-services
description: List of services that failed tests
steps:
- name: run-tests
image: python:3.11-slim
script: |
#!/bin/bash
set -e
SOURCE_PATH="$(workspaces.source.path)"
SERVICES="$(params.services)"
SKIP_LINT="$(params.skip-lint)"
SKIP_TESTS="$(params.skip-tests)"
TESTED_SERVICES=""
FAILED_SERVICES=""
OVERALL_STATUS="passed"
cd "$SOURCE_PATH"
echo "============================================"
echo "Running Tests"
echo "============================================"
echo "Services: $SERVICES"
echo "Skip Lint: $SKIP_LINT"
echo "Skip Tests: $SKIP_TESTS"
echo "============================================"
# Skip if no services to test
if [ "$SERVICES" = "none" ] || [ -z "$SERVICES" ]; then
echo "No services to test, skipping..."
echo "skipped" > $(results.test-status.path)
echo "none" > $(results.tested-services.path)
echo "none" > $(results.failed-services.path)
exit 0
fi
# Install common test dependencies
echo ""
echo "Installing test dependencies..."
pip install --quiet pytest pytest-cov pytest-asyncio ruff mypy 2>/dev/null || true
# Convert comma-separated list to space-separated
SERVICES_LIST=$(echo "$SERVICES" | tr ',' ' ')
for SERVICE in $SERVICES_LIST; do
# Trim whitespace
SERVICE=$(echo "$SERVICE" | tr -d ' ')
# Skip infrastructure changes
if [ "$SERVICE" = "infrastructure" ]; then
echo "Skipping infrastructure (not testable)"
continue
fi
echo ""
echo "--------------------------------------------"
echo "Testing service: $SERVICE"
echo "--------------------------------------------"
# Determine service path
if [ "$SERVICE" = "frontend" ]; then
SERVICE_PATH="$SOURCE_PATH/frontend"
elif [ "$SERVICE" = "gateway" ]; then
SERVICE_PATH="$SOURCE_PATH/gateway"
else
SERVICE_PATH="$SOURCE_PATH/services/$SERVICE"
fi
# Check if service exists
if [ ! -d "$SERVICE_PATH" ]; then
echo "Warning: Service directory not found: $SERVICE_PATH"
continue
fi
cd "$SERVICE_PATH"
SERVICE_FAILED=false
# Install service-specific dependencies if requirements.txt exists
if [ -f "requirements.txt" ]; then
echo "Installing service dependencies..."
pip install --quiet -r requirements.txt 2>/dev/null || true
fi
# Run linting (ruff)
if [ "$SKIP_LINT" != "true" ]; then
echo ""
echo "Running linter (ruff)..."
if [ -d "app" ]; then
ruff check app/ --output-format=text 2>&1 || {
echo "Linting failed for $SERVICE"
SERVICE_FAILED=true
}
fi
fi
# Run tests
if [ "$SKIP_TESTS" != "true" ]; then
echo ""
echo "Running tests (pytest)..."
if [ -d "tests" ]; then
pytest tests/ -v --tb=short 2>&1 || {
echo "Tests failed for $SERVICE"
SERVICE_FAILED=true
}
elif [ -d "app/tests" ]; then
pytest app/tests/ -v --tb=short 2>&1 || {
echo "Tests failed for $SERVICE"
SERVICE_FAILED=true
}
else
echo "No tests directory found, skipping tests"
fi
fi
# Track results
if [ -z "$TESTED_SERVICES" ]; then
TESTED_SERVICES="$SERVICE"
else
TESTED_SERVICES="$TESTED_SERVICES,$SERVICE"
fi
if [ "$SERVICE_FAILED" = true ]; then
OVERALL_STATUS="failed"
if [ -z "$FAILED_SERVICES" ]; then
FAILED_SERVICES="$SERVICE"
else
FAILED_SERVICES="$FAILED_SERVICES,$SERVICE"
fi
fi
cd "$SOURCE_PATH"
done
echo ""
echo "============================================"
echo "Test Summary"
echo "============================================"
echo "Tested services: $TESTED_SERVICES"
echo "Failed services: $FAILED_SERVICES"
echo "Overall status: $OVERALL_STATUS"
# Write results
echo "$OVERALL_STATUS" > $(results.test-status.path)
if [ -z "$TESTED_SERVICES" ]; then
echo "none" > $(results.tested-services.path)
else
echo "$TESTED_SERVICES" > $(results.tested-services.path)
fi
if [ -z "$FAILED_SERVICES" ]; then
echo "none" > $(results.failed-services.path)
else
echo "$FAILED_SERVICES" > $(results.failed-services.path)
fi
# Exit with error if tests failed
if [ "$OVERALL_STATUS" = "failed" ]; then
echo ""
echo "ERROR: Some tests failed!"
exit 1
fi
echo ""
echo "All tests passed!"
resources:
limits:
cpu: 1000m
memory: 2Gi
requests:
cpu: 500m
memory: 1Gi

View File

@@ -1,302 +0,0 @@
# Tekton Update GitOps Manifests Task for Bakery-IA CI/CD
# This task updates Kubernetes manifests with new image tags using Kustomize
# It uses a safer approach than sed for updating image references
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: update-gitops
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: gitops
spec:
workspaces:
- name: source
description: Source code workspace with Git repository
- name: git-credentials
description: Git credentials for pushing changes
optional: true
params:
- name: services
type: string
description: Comma-separated list of services to update
- name: registry
type: string
description: Container registry URL
- name: git-revision
type: string
description: Git revision for image tag
- name: git-branch
type: string
description: Target branch for GitOps updates
default: "main"
- name: dry-run
type: string
description: If "true", only show what would be changed without committing
default: "false"
results:
- name: updated-services
description: List of services that were updated
- name: commit-sha
description: Git commit SHA of the update (empty if dry-run)
steps:
- name: update-manifests
# Use alpine with curl to install kustomize
image: alpine:3.19
script: |
#!/bin/sh
set -e
# Install kustomize
echo "Installing kustomize..."
wget -q "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" -O - | sh
mv kustomize /usr/local/bin/
echo "Kustomize version: $(kustomize version)"
SOURCE_PATH="$(workspaces.source.path)"
SERVICES="$(params.services)"
REGISTRY="$(params.registry)"
REVISION="$(params.git-revision)"
DRY_RUN="$(params.dry-run)"
UPDATED_SERVICES=""
cd "$SOURCE_PATH"
echo "============================================"
echo "GitOps Manifest Update"
echo "============================================"
echo "Services: $SERVICES"
echo "Registry: $REGISTRY"
echo "Revision: $REVISION"
echo "Dry Run: $DRY_RUN"
echo "============================================"
# Skip if no services to update
if [ "$SERVICES" = "none" ] || [ -z "$SERVICES" ]; then
echo "No services to update, skipping..."
echo "none" > $(results.updated-services.path)
echo "" > $(results.commit-sha.path)
exit 0
fi
# Define the kustomization directory
KUSTOMIZE_DIR="infrastructure/environments/prod"
# Check if kustomization.yaml exists, create if not
if [ ! -f "$KUSTOMIZE_DIR/kustomization.yaml" ]; then
echo "Creating kustomization.yaml in $KUSTOMIZE_DIR"
mkdir -p "$KUSTOMIZE_DIR"
printf '%s\n' \
"apiVersion: kustomize.config.k8s.io/v1beta1" \
"kind: Kustomization" \
"" \
"resources:" \
" - ../base" \
"" \
"images: []" \
> "$KUSTOMIZE_DIR/kustomization.yaml"
fi
# Convert comma-separated list to space-separated
SERVICES_LIST=$(echo "$SERVICES" | tr ',' ' ')
# Build the images section for kustomization
echo ""
echo "Updating image references..."
for SERVICE in $SERVICES_LIST; do
# Trim whitespace
SERVICE=$(echo "$SERVICE" | tr -d ' ')
# Skip infrastructure changes
if [ "$SERVICE" = "infrastructure" ]; then
echo "Skipping infrastructure (not a deployable service)"
continue
fi
echo "Processing: $SERVICE"
# Determine the image name based on service
NEW_IMAGE="$REGISTRY/bakery/$SERVICE:$REVISION"
# Use kustomize to set the image
# This is safer than sed as it understands the YAML structure
cd "$SOURCE_PATH/$KUSTOMIZE_DIR"
# Check if this service has a deployment
SERVICE_DEPLOYMENT=""
if [ "$SERVICE" = "frontend" ]; then
SERVICE_DEPLOYMENT="frontend"
elif [ "$SERVICE" = "gateway" ]; then
SERVICE_DEPLOYMENT="gateway"
else
SERVICE_DEPLOYMENT="$SERVICE-service"
fi
# Update the kustomization with the new image
# Using kustomize edit to safely modify the file
kustomize edit set image "bakery/$SERVICE=$NEW_IMAGE" 2>/dev/null || \
kustomize edit set image "$SERVICE=$NEW_IMAGE" 2>/dev/null || \
echo "Note: Could not set image via kustomize edit, will use alternative method"
# Track updated services
if [ -z "$UPDATED_SERVICES" ]; then
UPDATED_SERVICES="$SERVICE"
else
UPDATED_SERVICES="$UPDATED_SERVICES,$SERVICE"
fi
cd "$SOURCE_PATH"
done
# Alternative: Update images in kustomization.yaml directly if kustomize edit didn't work
# This creates/updates an images section in the kustomization
echo ""
echo "Ensuring image overrides in kustomization.yaml..."
# Create a patch file for image updates
IMAGES_FILE="$KUSTOMIZE_DIR/images.yaml"
printf '%s\n' \
"# Auto-generated by CI/CD pipeline" \
"# Commit: $REVISION" \
"# Updated: $(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
"images:" \
> "$IMAGES_FILE"
for SERVICE in $SERVICES_LIST; do
SERVICE=$(echo "$SERVICE" | tr -d ' ')
if [ "$SERVICE" != "infrastructure" ]; then
printf '%s\n' \
" - name: bakery/$SERVICE" \
" newName: $REGISTRY/bakery/$SERVICE" \
" newTag: \"$REVISION\"" \
>> "$IMAGES_FILE"
fi
done
echo ""
echo "Generated images.yaml:"
cat "$IMAGES_FILE"
# Validate the kustomization
echo ""
echo "Validating kustomization..."
cd "$SOURCE_PATH/$KUSTOMIZE_DIR"
if kustomize build . > /dev/null 2>&1; then
echo "Kustomization is valid"
else
echo "Warning: Kustomization validation failed, but continuing..."
fi
cd "$SOURCE_PATH"
# Write results
echo "$UPDATED_SERVICES" > $(results.updated-services.path)
if [ "$DRY_RUN" = "true" ]; then
echo ""
echo "============================================"
echo "DRY RUN - Changes not committed"
echo "============================================"
echo "Would update services: $UPDATED_SERVICES"
git diff --stat || true
echo "" > $(results.commit-sha.path)
exit 0
fi
echo ""
echo "Committing changes..."
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 256Mi
- name: commit-and-push
image: alpine/git:2.43.0
script: |
#!/bin/sh
set -e
SOURCE_PATH="$(workspaces.source.path)"
SERVICES="$(params.services)"
REVISION="$(params.git-revision)"
BRANCH="$(params.git-branch)"
DRY_RUN="$(params.dry-run)"
cd "$SOURCE_PATH"
if [ "$DRY_RUN" = "true" ]; then
echo "Dry run mode - skipping commit"
echo "" > $(results.commit-sha.path)
exit 0
fi
if [ "$SERVICES" = "none" ] || [ -z "$SERVICES" ]; then
echo "No services to commit"
echo "" > $(results.commit-sha.path)
exit 0
fi
# Check if there are changes to commit
if git diff --quiet && git diff --cached --quiet; then
echo "No changes to commit"
echo "" > $(results.commit-sha.path)
exit 0
fi
# Configure git
git config --global user.name "bakery-ia-ci"
git config --global user.email "ci@bakery-ia.local"
git config --global --add safe.directory "$SOURCE_PATH"
# Setup git credentials if provided
if [ -d "$(workspaces.git-credentials.path)" ]; then
if [ -f "$(workspaces.git-credentials.path)/username" ] && [ -f "$(workspaces.git-credentials.path)/password" ]; then
GIT_USER=$(cat "$(workspaces.git-credentials.path)/username")
GIT_PASS=$(cat "$(workspaces.git-credentials.path)/password")
# Get the remote URL and inject credentials
REMOTE_URL=$(git remote get-url origin)
# Handle both http and https
if echo "$REMOTE_URL" | grep -q "^http"; then
REMOTE_URL=$(echo "$REMOTE_URL" | sed "s|://|://$GIT_USER:$GIT_PASS@|")
git remote set-url origin "$REMOTE_URL"
fi
fi
fi
# Stage changes
git add -A
# Create commit with detailed message
COMMIT_MSG=$(printf 'ci: Update image tags to %s\n\nServices updated: %s\n\nThis commit was automatically generated by the CI/CD pipeline.\nPipeline run triggered by commit: %s' "$REVISION" "$SERVICES" "$REVISION")
git commit -m "$COMMIT_MSG"
# Get the commit SHA
COMMIT_SHA=$(git rev-parse HEAD)
echo "$COMMIT_SHA" > $(results.commit-sha.path)
echo "Created commit: $COMMIT_SHA"
# Push changes
echo "Pushing to origin/$BRANCH..."
git push origin HEAD:"$BRANCH"
echo ""
echo "============================================"
echo "GitOps Update Complete"
echo "============================================"
echo "Commit: $COMMIT_SHA"
echo "Branch: $BRANCH"
echo "Services: $SERVICES"
resources:
limits:
cpu: 200m
memory: 128Mi
requests:
cpu: 50m
memory: 64Mi

View File

@@ -1,91 +0,0 @@
# Task to verify images exist in the registry before deploying
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: verify-images
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: validation
spec:
params:
- name: services
type: string
description: Comma-separated list of services to verify
- name: registry
type: string
description: Container registry URL
- name: git-revision
type: string
description: Git revision/tag to verify
results:
- name: verification-status
description: Status of image verification (success/failed)
- name: missing-images
description: List of images that were not found
steps:
- name: verify
image: gcr.io/go-containerregistry/crane:latest
script: |
#!/bin/sh
set -e
SERVICES="$(params.services)"
REGISTRY="$(params.registry)"
REVISION="$(params.git-revision)"
MISSING=""
echo "============================================"
echo "Verifying Images in Registry"
echo "============================================"
echo "Registry: $REGISTRY"
echo "Revision: $REVISION"
echo "Services: $SERVICES"
echo "============================================"
# Convert comma-separated list to space-separated
SERVICES_LIST=$(echo "$SERVICES" | tr ',' ' ')
for SERVICE in $SERVICES_LIST; do
SERVICE=$(echo "$SERVICE" | tr -d ' ')
if [ "$SERVICE" = "infrastructure" ]; then
continue
fi
IMAGE="$REGISTRY/bakery/$SERVICE:$REVISION"
echo ""
echo "Checking: $IMAGE"
if crane manifest "$IMAGE" > /dev/null 2>&1; then
echo " ✓ Found"
else
echo " ✗ NOT FOUND"
if [ -z "$MISSING" ]; then
MISSING="$SERVICE"
else
MISSING="$MISSING,$SERVICE"
fi
fi
done
echo ""
echo "============================================"
if [ -n "$MISSING" ]; then
echo "ERROR: Missing images: $MISSING"
echo "failed" > $(results.verification-status.path)
echo "$MISSING" > $(results.missing-images.path)
exit 1
fi
echo "All images verified successfully"
echo "success" > $(results.verification-status.path)
echo "none" > $(results.missing-images.path)
resources:
limits:
cpu: 200m
memory: 128Mi
requests:
cpu: 100m
memory: 64Mi

View File

@@ -1,35 +0,0 @@
# Tekton EventListener for Bakery-IA CI/CD
# This listener receives webhook events and triggers pipelines
apiVersion: triggers.tekton.dev/v1beta1
kind: EventListener
metadata:
name: bakery-ia-listener
namespace: tekton-pipelines
spec:
serviceAccountName: tekton-triggers-sa
triggers:
- name: bakery-ia-gitea-trigger
bindings:
- ref: bakery-ia-trigger-binding
template:
ref: bakery-ia-trigger-template
# Using CEL interceptor for local development (no TLS/CA bundle required)
# The CEL interceptor is built-in and doesn't need external services
interceptors:
- name: "filter-push-events"
ref:
name: "cel"
params:
# Filter for push events from Gitea or GitHub
- name: "filter"
value: "header.match('X-Gitea-Event', 'push') || header.match('X-GitHub-Event', 'push')"
# Add overlays to standardize the payload
- name: "overlays"
value:
- key: "git_url"
expression: "body.repository.clone_url"
- key: "git_revision"
expression: "body.after"
- key: "git_branch"
expression: "body.ref.split('/')[2]"

View File

@@ -1,9 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
# NOTE: gitlab-interceptor.yaml removed - uses built-in Tekton Triggers interceptor
# The gitlab ClusterInterceptor is provided by Tekton Triggers installation
- event-listener.yaml
- trigger-template.yaml
- trigger-binding.yaml

View File

@@ -1,31 +0,0 @@
# Tekton TriggerBinding for Bakery-IA CI/CD
# This binding extracts parameters from Gitea webhook events
#
# Note: We use CEL overlay extensions for consistent field access
# The EventListener's CEL interceptor creates these extensions:
# - extensions.git_url: Repository clone URL
# - extensions.git_revision: Commit SHA (from body.after)
# - extensions.git_branch: Branch name (extracted from ref)
apiVersion: triggers.tekton.dev/v1beta1
kind: TriggerBinding
metadata:
name: bakery-ia-trigger-binding
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: triggers
spec:
params:
# Use CEL overlay extensions for consistent access across Git providers
- name: git-repo-url
value: $(extensions.git_url)
- name: git-revision
value: $(extensions.git_revision)
- name: git-branch
value: $(extensions.git_branch)
# Direct body access for fields not in overlays
- name: git-repo-name
value: $(body.repository.name)
- name: git-repo-full-name
value: $(body.repository.full_name)

View File

@@ -176,7 +176,7 @@ data:
# ================================================================
# EMAIL CONFIGURATION
# ================================================================
SMTP_HOST: "email-smtp.bakery-ia.svc.cluster.local"
SMTP_HOST: "mailu-postfix.bakery-ia.svc.cluster.local"
SMTP_PORT: "587"
SMTP_TLS: "true"
SMTP_SSL: "false"

View File

@@ -160,8 +160,8 @@ metadata:
app.kubernetes.io/component: notifications
type: Opaque
data:
# SMTP credentials for internal Mailu server
# These are used by notification-service to send emails via mailu-smtp
# SMTP credentials for internal Mailu server (Helm deployment)
# These are used by notification-service to send emails via mailu-postfix
SMTP_USER: cG9zdG1hc3RlckBiYWtld2lzZS5haQ== # postmaster@bakewise.ai
SMTP_PASSWORD: VzJYS2tSdUxpT25ZS2RCWVFTQXJvbjFpeWtFU1M1b2I= # W2XKkRuLiOnYKdBYQSAron1iykESS5ob
# Dovecot admin password for IMAP management

View File

@@ -15,7 +15,6 @@ resources:
- ../../../platform/cert-manager
- ../../../platform/networking/ingress/overlays/dev
- ../../../platform/storage
- ../../../platform/mail/mailu
- ../../../services/databases
- ../../../services/microservices
# NOTE: cicd is NOT included here - it's deployed manually via Tilt triggers
@@ -53,31 +52,6 @@ patches:
- op: replace
path: /spec/suspend
value: true
# Mailu TLS: Use self-signed dev certificate
- target:
kind: Deployment
name: mailu-front
patch: |-
- op: replace
path: /spec/template/spec/volumes/1/secret/secretName
value: "bakery-dev-tls-cert"
# Mailu Config: Update for dev environment
- target:
kind: ConfigMap
name: mailu-config
patch: |-
- op: replace
path: /data/DOMAIN
value: "bakery-ia.local"
- op: replace
path: /data/HOSTNAMES
value: "mail.bakery-ia.local"
- op: replace
path: /data/RELAY_LOGIN
value: "postmaster@bakery-ia.local"
- op: replace
path: /data/WEBMAIL_ADMIN
value: "admin@bakery-ia.local"
labels:
- includeSelectors: true
@@ -141,19 +115,3 @@ images:
- name: python
newName: localhost:5000/python_3.11-slim
newTag: latest
# Mail server (Mailu)
- name: ghcr.io/mailu/nginx
newName: localhost:5000/ghcr.io_mailu_nginx_2024.06
newTag: latest
- name: ghcr.io/mailu/admin
newName: localhost:5000/ghcr.io_mailu_admin_2024.06
newTag: latest
- name: ghcr.io/mailu/postfix
newName: localhost:5000/ghcr.io_mailu_postfix_2024.06
newTag: latest
- name: ghcr.io/mailu/dovecot
newName: localhost:5000/ghcr.io_mailu_dovecot_2024.06
newTag: latest
- name: ghcr.io/mailu/rspamd
newName: localhost:5000/ghcr.io_mailu_rspamd_2024.06
newTag: latest

View File

@@ -15,7 +15,6 @@ resources:
- ../../../platform/cert-manager
- ../../../platform/networking/ingress/overlays/prod
- ../../../platform/storage
- ../../../platform/mail/mailu
- ../../../services/databases
- ../../../services/microservices
- ../../../cicd
@@ -169,14 +168,6 @@ patches:
limits:
memory: "1Gi"
cpu: "500m"
# Mailu TLS: Use Let's Encrypt production certificate
- target:
kind: Deployment
name: mailu-front
patch: |-
- op: replace
path: /spec/template/spec/volumes/1/secret/secretName
value: "bakery-ia-prod-tls-cert"
images:
# Application services
@@ -253,17 +244,6 @@ images:
# Python base image
- name: python
newTag: 3.11-slim
# Mail server (Mailu) - using canonical GHCR names
- name: ghcr.io/mailu/nginx
newTag: "2024.06"
- name: ghcr.io/mailu/admin
newTag: "2024.06"
- name: ghcr.io/mailu/postfix
newTag: "2024.06"
- name: ghcr.io/mailu/dovecot
newTag: "2024.06"
- name: ghcr.io/mailu/rspamd
newTag: "2024.06"
replicas:
- name: auth-service

View File

@@ -349,19 +349,19 @@ podDisruptionBudget:
## Monitoring and Alerting
### Email Alerts (Production)
Configure SMTP in production values (using Mailu with Mailgun relay):
Configure SMTP in production values (using Mailu Helm with Mailgun relay):
```yaml
signoz:
env:
signoz_smtp_enabled: "true"
signoz_smtp_host: "mailu-smtp.bakery-ia.svc.cluster.local"
signoz_smtp_host: "mailu-postfix.bakery-ia.svc.cluster.local"
signoz_smtp_port: "587"
signoz_smtp_from: "alerts@bakewise.ai"
signoz_smtp_username: "alerts@bakewise.ai"
# Set via secret: signoz_smtp_password
```
**Note**: Signoz now uses the internal Mailu SMTP service, which relays to Mailgun for better deliverability and centralized email management.
**Note**: Signoz now uses the internal Mailu SMTP service (deployed via Helm), which relays to Mailgun for better deliverability and centralized email management.
### Slack Alerts (Production)
Configure webhook in Alertmanager:
@@ -392,18 +392,15 @@ Signoz Alertmanager → Mailu SMTP → Mailgun Relay → Recipients
**Configuration Requirements:**
1. **Mailu Configuration** (`infrastructure/platform/mail/mailu/mailu-configmap.yaml`):
1. **Mailu Configuration** (deployed via Helm at `infrastructure/platform/mail/mailu-helm/`):
```yaml
RELAYHOST: "smtp.mailgun.org:587"
RELAY_LOGIN: "postmaster@bakewise.ai"
externalRelay:
host: "[smtp.mailgun.org]:587"
username: "postmaster@bakewise.ai"
password: "<mailgun-api-key>"
```
2. **Mailu Secrets** (`infrastructure/platform/mail/mailu/mailu-secrets.yaml`):
```yaml
RELAY_PASSWORD: "<mailgun-api-key>" # Base64 encoded Mailgun API key
```
3. **DNS Configuration** (required for Mailgun):
2. **DNS Configuration** (required for Mailgun):
```
# MX record
bakewise.ai. IN MX 10 mail.bakewise.ai.
@@ -418,9 +415,9 @@ Signoz Alertmanager → Mailu SMTP → Mailgun Relay → Recipients
_dmarc.bakewise.ai. IN TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc@bakewise.ai"
```
4. **Signoz SMTP Configuration** (already configured in `signoz-values-prod.yaml`):
3. **Signoz SMTP Configuration** (already configured in `signoz-values-prod.yaml`):
```yaml
signoz_smtp_host: "mailu-smtp.bakery-ia.svc.cluster.local"
signoz_smtp_host: "mailu-postfix.bakery-ia.svc.cluster.local"
signoz_smtp_port: "587"
signoz_smtp_from: "alerts@bakewise.ai"
```
@@ -428,14 +425,14 @@ Signoz Alertmanager → Mailu SMTP → Mailgun Relay → Recipients
**Testing the Integration:**
1. Trigger a test alert from Signoz UI
2. Check Mailu logs: `kubectl logs -f mailu-smtp-<pod-id> -n bakery-ia`
2. Check Mailu logs: `kubectl logs -f -n bakery-ia deployment/mailu-postfix`
3. Check Mailgun dashboard for delivery status
4. Verify email receipt in destination inbox
**Troubleshooting:**
- **SMTP Authentication Failed**: Verify Mailu credentials and Mailgun API key
- **Email Delivery Delays**: Check Mailu queue with `kubectl exec -it mailu-smtp-<pod-id> -n bakery-ia -- mailq`
- **Email Delivery Delays**: Check Mailu queue with `kubectl exec -it -n bakery-ia deployment/mailu-postfix -- mailq`
- **SPF/DKIM Issues**: Verify DNS records and Mailgun domain verification
### Self-Monitoring

View File

@@ -73,7 +73,7 @@ signoz:
# signoz_opamp_server_endpoint: "0.0.0.0:4320"
# SMTP configuration for email alerts - now using Mailu as SMTP server
signoz_smtp_enabled: "true"
signoz_smtp_host: "email-smtp.bakery-ia.svc.cluster.local"
signoz_smtp_host: "mailu-postfix.bakery-ia.svc.cluster.local"
signoz_smtp_port: "587"
signoz_smtp_from: "alerts@bakewise.ai"
signoz_smtp_username: "alerts@bakewise.ai"
@@ -136,7 +136,7 @@ alertmanager:
config:
global:
resolve_timeout: 5m
smtp_smarthost: 'email-smtp.bakery-ia.svc.cluster.local:587'
smtp_smarthost: 'mailu-postfix.bakery-ia.svc.cluster.local:587'
smtp_from: 'alerts@bakewise.ai'
smtp_auth_username: 'alerts@bakewise.ai'
smtp_auth_password: '${SMTP_PASSWORD}'

View File

@@ -8,8 +8,4 @@ metadata:
name: flux-system
labels:
app.kubernetes.io/name: flux
app.kubernetes.io/component: system
kubernetes.io/metadata.name: flux-system
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted

View 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.local # or mail.bakewise.ai for prod
secretName: mail-tls-secret # Your TLS Secret
rules:
- host: mail.bakery-ia.local # 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

View 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.local # or mail.bakewise.ai for prod
secretName: mail-tls-secret # Your TLS Secret
rules:
- host: mail.bakery-ia.local # 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

View File

@@ -0,0 +1,50 @@
# Dev-specific Mailu Helm values for Bakery-IA
# Overrides base configuration for development environment
# Domain configuration for dev
domain: "bakery-ia.local"
hostnames:
- "mail.bakery-ia.local"
# External relay configuration for dev
externalRelay:
host: "[smtp.mailgun.org]:587"
username: "postmaster@bakery-ia.local"
password: "mailgun-api-key-replace-in-production"
# Ingress configuration for dev - 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: "cert"
# Welcome message (disabled in dev)
welcomeMessage:
enabled: false
# Log level for dev
logLevel: "DEBUG"
# 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

View File

@@ -0,0 +1,28 @@
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.local # or mail.bakewise.ai for prod
secretName: mail-tls-secret # Your TLS Secret
rules:
- host: mail.bakery-ia.local # 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

View File

@@ -0,0 +1,57 @@
# Production-specific Mailu Helm values for Bakery-IA
# Overrides base configuration for production environment
# Domain configuration for production
domain: "bakewise.ai"
hostnames:
- "mail.bakewise.ai"
# External relay configuration for production
externalRelay:
host: "[smtp.mailgun.org]:587"
username: "postmaster@bakewise.ai"
password: "PRODUCTION_MAILGUN_API_KEY" # This should be set via secret
# 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"
# Network Policy 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

View File

@@ -0,0 +1,206 @@
# Base Mailu Helm values for Bakery-IA
# Preserves critical configurations from the original Kustomize setup
# 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"
# TLS configuration
tls:
flavor: "cert"
# Limits configuration
limits:
messageSizeLimitInMegabytes: 50
authRatelimit:
ip: "60/hour"
user: "100/day"
messageRatelimit:
value: "200/day"
# External relay configuration (Mailgun)
externalRelay:
host: "[smtp.mailgun.org]:587"
username: "postmaster@DOMAIN_PLACEHOLDER"
password: "mailgun-api-key-replace-in-production"
# Webmail configuration
webmail:
enabled: true
flavor: "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 external Redis (shared cluster Redis)
externalRedis:
enabled: true
host: "redis-service.bakery-ia.svc.cluster.local"
port: 6380
adminQuotaDbId: 15
adminRateLimitDbId: 15
rspamdDbId: 15
# Database configuration - using external database
externalDatabase:
enabled: true
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

View File

@@ -1,289 +0,0 @@
# Mailu Email Infrastructure for Bakery-IA
This directory contains the Kubernetes deployment configuration for Mailu, a self-hosted email solution that integrates with external SMTP relays for optimal deliverability.
## Architecture Overview
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster (bakery-ia) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ notification- │ │ mail-service │ │ frontend │ │
│ │ service │─────▶│ (new/optional) │ │ │ │
│ │ │ │ Queue & Routing │ │ │ │
│ └────────┬─────────┘ └────────┬─────────┘ └──────────────────┘ │
│ │ │ │
│ │ SMTP (port 587) │ SMTP (port 587) │
│ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ MAILU STACK │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ front │ │ admin │ │ smtp │ │ imap │ │ │
│ │ │ (nginx) │ │ (webmail) │ │ (postfix) │ │ (dovecot) │ │ │
│ │ │ :80/:443 │ │ :8080 │ │ :25/:587 │ │ :993/:143 │ │ │
│ │ └─────────────┘ └─────────────┘ └──────┬──────┘ └─────────────┘ │ │
│ │ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ │ Relay │ │
│ │ │ antispam │ │ antivirus │ │ │ │
│ │ │ (rspamd) │ │ (clamav) │ │ │ │
│ │ └─────────────┘ └─────────────┘ │ │ │
│ │ │ │ │
│ │ ┌─────────────────────────────────┐ │ │ │
│ │ │ mailu-db (redis) │ │ │ │
│ │ └─────────────────────────────────┘ │ │ │
│ └───────────────────────────────────────────┼──────────────────────────┘ │
│ │ │
└──────────────────────────────────────────────┼───────────────────────────────┘
┌──────────────────────────────────────┐
│ EXTERNAL SMTP RELAY │
│ (SendGrid / Mailgun / AWS SES) │
│ │
│ • Handles IP reputation │
│ • Manages deliverability │
│ • Provides bounce/complaint hooks │
└──────────────────────────────────────┘
┌──────────────────────────────────────┐
│ INTERNET / RECIPIENTS │
└──────────────────────────────────────┘
```
## Components
### Core Services
- **mailu-front**: Nginx reverse proxy for web access (ports 80/443)
- **mailu-admin**: Web administration interface (port 80)
- **mailu-smtp**: Postfix SMTP server (ports 25/587)
- **mailu-imap**: Dovecot IMAP server (ports 143/993)
- **mailu-antispam**: Rspamd spam filtering (ports 11333/11334)
- **mailu-redis**: Redis for session management (port 6379)
### Storage
- **mailu-data**: 10Gi PVC for mail storage
- **mailu-db**: 5Gi PVC for database
- **mailu-redis**: 1Gi PVC for Redis persistence
## Configuration
### Environment Variables
The Mailu stack is configured via the `mailu-configmap.yaml` file:
- **DOMAIN**: `bakewise.ai`
- **HOSTNAMES**: `mail.bakewise.ai`
- **RELAYHOST**: `smtp.mailgun.org:587`
- **RELAY_LOGIN**: `apikey`
- **TLS_FLAVOR**: `cert` (uses Let's Encrypt)
- **WEBMAIL**: `roundcube`
- **ANTIVIRUS**: `clamav`
- **ANTISPAM**: `rspamd`
### Secrets
Secrets are managed in `mailu-secrets.yaml`:
- **ADMIN_PASSWORD**: Base64 encoded admin password
- **SECRET_KEY**: Mailu internal encryption key
- **RELAY_PASSWORD**: External SMTP relay API key
- **DB_PASSWORD**: Database password
- **REDIS_PASSWORD**: Redis password
## Deployment
### Prerequisites
1. Kubernetes cluster with storage provisioner
2. Ingress controller (NGINX)
3. Cert-manager for TLS certificates
4. External SMTP relay account (Mailgun, SendGrid, AWS SES)
### Deployment Steps
1. **Configure DNS**:
```bash
# MX record for inbound email
bakewise.ai. IN MX 10 mail.bakewise.ai.
# A record for mail server
mail.bakewise.ai. IN A <your-ingress-ip>
# SPF record (authorize external relay)
bakewise.ai. IN TXT "v=spf1 include:mailgun.org ~all"
# DKIM record (Mailu generates this)
mailu._domainkey.bakewise.ai. IN TXT "v=DKIM1; k=rsa; p=<public-key>"
# DMARC record
_dmarc.bakewise.ai. IN TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc@bakewise.ai"
```
2. **Update secrets**:
```bash
# Generate secure passwords
echo -n "your-secure-password" | base64
openssl rand -base64 32
# Update mailu-secrets.yaml with real values
```
3. **Deploy Mailu**:
```bash
# For production
kubectl apply -k infrastructure/environments/prod/k8s-manifests/
# For development
kubectl apply -k infrastructure/environments/dev/k8s-manifests/
```
4. **Verify deployment**:
```bash
kubectl get pods -n bakery-ia | grep mailu
kubectl logs -f mailu-smtp-<pod-id> -n bakery-ia
```
## Integration with Notification Service
The notification service has been updated to use Mailu as the SMTP server:
```yaml
# infrastructure/environments/common/configs/configmap.yaml
SMTP_HOST: "mailu-smtp.bakery-ia.svc.cluster.local"
SMTP_PORT: "587"
SMTP_TLS: "true"
SMTP_SSL: "false"
```
## Accessing Mailu
### Web Interface
- **Admin Panel**: `https://mail.bakewise.ai/admin`
- **Webmail**: `https://mail.bakewise.ai/webmail`
### SMTP Configuration
For external clients to send email through Mailu:
- **Server**: `mail.bakewise.ai`
- **Port**: 587 (Submission)
- **Security**: STARTTLS
- **Authentication**: Required
### IMAP Configuration
For email clients to access mailboxes:
- **Server**: `mail.bakewise.ai`
- **Port**: 993 (IMAPS)
- **Security**: SSL/TLS
- **Authentication**: Required
## Monitoring and Maintenance
### Health Checks
```bash
# Check Mailu services
kubectl get pods -n bakery-ia -l app=mailu
# Check Mailu logs
kubectl logs -f mailu-smtp-<pod-id> -n bakery-ia
kubectl logs -f mailu-antispam-<pod-id> -n bakery-ia
# Check queue status
kubectl exec -it mailu-smtp-<pod-id> -n bakery-ia -- mailq
```
### Backup and Restore
```bash
# Backup mail data
kubectl exec -it mailu-smtp-<pod-id> -n bakery-ia -- tar czf /backup/mailu-backup-$(date +%Y%m%d).tar.gz /data
# Restore mail data
kubectl cp mailu-backup-<date>.tar.gz mailu-smtp-<pod-id>:/backup/ -n bakery-ia
kubectl exec -it mailu-smtp-<pod-id> -n bakery-ia -- tar xzf /backup/mailu-backup-<date>.tar.gz -C /
```
## Troubleshooting
### Common Issues
1. **SMTP Relay Authentication Failed**:
- Verify `RELAY_PASSWORD` in secrets matches your external relay API key
- Check network connectivity to external relay
2. **TLS Certificate Issues**:
- Ensure cert-manager is working properly
- Check DNS records are correctly pointing to your ingress
3. **Email Delivery Delays**:
- Check Mailu queue: `kubectl exec -it mailu-smtp-<pod-id> -n bakery-ia -- mailq`
- Verify external relay service status
4. **Spam Filtering Issues**:
- Check rspamd logs: `kubectl logs -f mailu-antispam-<pod-id> -n bakery-ia`
- Adjust spam scoring in rspamd configuration
## Resource Requirements
| Component | CPU Request | CPU Limit | Memory Request | Memory Limit | Storage |
|-----------|-------------|-----------|----------------|--------------|----------|
| mailu-front | 100m | 200m | 128Mi | 256Mi | - |
| mailu-admin | 100m | 300m | 256Mi | 512Mi | - |
| mailu-smtp | 100m | 500m | 256Mi | 512Mi | 10Gi |
| mailu-imap | 100m | 500m | 256Mi | 512Mi | - |
| mailu-antispam | 200m | 1000m | 512Mi | 1Gi | - |
| mailu-redis | 100m | 200m | 128Mi | 256Mi | 1Gi |
**Total**: ~600m CPU, ~1.7Gi Memory, 16Gi Storage
## Security Considerations
1. **Network Policies**: Mailu is protected by network policies that restrict access to only the notification service and ingress controller.
2. **TLS Encryption**: All external connections use TLS encryption.
3. **Authentication**: All services require authentication.
4. **Rate Limiting**: Configured to prevent abuse (60/hour per IP, 100/day per user).
5. **Spam Protection**: Rspamd provides comprehensive spam filtering with DKIM signing.
## Migration from External SMTP
To migrate from external SMTP (Gmail) to Mailu:
1. Update DNS records as shown above
2. Deploy Mailu stack
3. Update notification service configuration
4. Test email delivery
5. Monitor deliverability metrics
6. Gradually increase email volume
## External Relay Provider Comparison
| Provider | Pros | Cons | Free Tier |
|----------|------|------|-----------|
| SendGrid | Best deliverability, robust API | Expensive at scale | 100/day |
| Mailgun | Developer-friendly, good logs | EU data residency costs extra | 5,000/month (3 months) |
| AWS SES | Cheapest at scale ($0.10/1000) | Requires warm-up period | 62,000/month (from EC2) |
| Postmark | Transactional focus, fast | No marketing emails | 100/month |
**Recommendation**: AWS SES for cost-effectiveness and Kubernetes integration.
## Support
For issues with Mailu deployment:
1. Check the [Mailu documentation](https://mailu.io/)
2. Review Kubernetes events: `kubectl get events -n bakery-ia`
3. Check pod logs for specific components
4. Verify network connectivity and DNS resolution

View File

@@ -1,265 +0,0 @@
# Webmail DNS Configuration Guide
This guide provides the DNS configuration required to make the webmail system accessible from `webmail.bakewise.ai`.
## Production DNS Configuration
### Required DNS Records for `webmail.bakewise.ai`
```bash
# A Record for webmail subdomain
webmail.bakewise.ai. IN A <your-ingress-ip>
# CNAME Record (alternative approach)
webmail.bakewise.ai. IN CNAME bakewise.ai.
# MX Record for email delivery (if receiving emails)
bakewise.ai. IN MX 10 webmail.bakewise.ai.
# SPF Record (authorize webmail server)
bakewise.ai. IN TXT "v=spf1 include:mailgun.org ~all"
# DKIM Record (will be generated by Mailu)
mailu._domainkey.bakewise.ai. IN TXT "v=DKIM1; k=rsa; p=<public-key>"
# DMARC Record
_dmarc.bakewise.ai. IN TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc@bakewise.ai"
```
## Development DNS Configuration
### Required DNS Records for `webmail.bakery-ia.local`
For local development, add these entries to your `/etc/hosts` file:
```bash
# Add to /etc/hosts
127.0.0.1 webmail.bakery-ia.local
127.0.0.1 bakery-ia.local
127.0.0.1 monitoring.bakery-ia.local
```
## TLS Certificate Configuration
The ingress configuration includes automatic TLS certificate provisioning using cert-manager with Let's Encrypt.
### Production TLS Configuration
The production ingress (`prod-ingress.yaml`) includes:
```yaml
tls:
- hosts:
- bakewise.ai
- monitoring.bakewise.ai
- webmail.bakewise.ai # ← Added webmail domain
secretName: bakery-ia-prod-tls-cert
```
### Development TLS Configuration
The development ingress (`dev-ingress.yaml`) includes:
```yaml
tls:
- hosts:
- localhost
- bakery-ia.local
- monitoring.bakery-ia.local
- webmail.bakery-ia.local # ← Added webmail domain
secretName: bakery-dev-tls-cert
```
## Ingress Routing Configuration
### Production Routing
The production ingress routes traffic as follows:
- `https://bakewise.ai/` → Frontend service (port 3000)
- `https://bakewise.ai/api/` → Gateway service (port 8000)
- `https://monitoring.bakewise.ai/` → SigNoz monitoring (port 8080)
- `https://webmail.bakewise.ai/` → Email webmail (port 80)
- `https://webmail.bakewise.ai/webmail` → Email webmail
- `https://webmail.bakewise.ai/admin` → Email admin interface
### Development Routing
The development ingress routes traffic as follows:
- `https://localhost/` → Frontend service (port 3000)
- `https://localhost/api/` → Gateway service (port 8000)
- `https://bakery-ia.local/` → Frontend service (port 3000)
- `https://bakery-ia.local/api/` → Gateway service (port 8000)
- `https://monitoring.bakery-ia.local/` → SigNoz monitoring (port 8080)
- `https://webmail.bakery-ia.local/` → Email webmail (port 80)
- `https://webmail.bakery-ia.local/webmail` → Email webmail
- `https://webmail.bakery-ia.local/admin` → Email admin interface
## Security Headers
The webmail ingress includes enhanced security headers:
```nginx
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self';
connect-src 'self'; frame-src 'self';
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
```
## Deployment Steps
### 1. Update DNS Records
```bash
# For production (using Cloudflare as example)
cfcli dns create bakewise.ai A webmail <ingress-ip> --ttl 3600 --proxied
# For development (add to /etc/hosts)
echo "127.0.0.1 webmail.bakery-ia.local" | sudo tee -a /etc/hosts
```
### 2. Apply Ingress Configuration
```bash
# Apply the updated ingress configuration
kubectl apply -k infrastructure/environments/prod/k8s-manifests/
# Verify the ingress is configured correctly
kubectl get ingress -n bakery-ia
kubectl describe ingress bakery-ingress-prod -n bakery-ia
```
### 3. Verify TLS Certificates
```bash
# Check TLS certificate status
kubectl get certificaterequest -n bakery-ia
kubectl get certificate -n bakery-ia
# Check certificate details
kubectl describe certificate bakery-ia-prod-tls-cert -n bakery-ia
```
### 4. Test Webmail Access
```bash
# Test webmail accessibility
curl -I https://webmail.bakewise.ai
curl -I https://webmail.bakewise.ai/webmail
curl -I https://webmail.bakewise.ai/admin
# Test from browser
open https://webmail.bakewise.ai
```
## Troubleshooting
### DNS Issues
```bash
# Check DNS resolution
dig webmail.bakewise.ai
nslookup webmail.bakewise.ai
# Check ingress controller logs
kubectl logs -f -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx
```
### TLS Issues
```bash
# Check cert-manager logs
kubectl logs -f -n cert-manager -l app=cert-manager
# Check certificate status
kubectl get certificaterequest,certificate,order,challenge -n bakery-ia
```
### Ingress Issues
```bash
# Check ingress controller events
kubectl get events -n ingress-nginx
# Check ingress description
kubectl describe ingress -n bakery-ia
```
## Monitoring and Maintenance
### Check Webmail Service Status
```bash
# Check email services
kubectl get pods -n bakery-ia -l app=email
# Check webmail service
kubectl get service email-webmail -n bakery-ia
# Check ingress routing
kubectl get ingress -n bakery-ia -o yaml | grep -A 10 webmail
```
### Update DNS Records
When the ingress IP changes, update the DNS records:
```bash
# Get current ingress IP
kubectl get service -n ingress-nginx ingress-nginx-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
# Update DNS (Cloudflare example)
cfcli dns update bakewise.ai A webmail <new-ip> --ttl 3600 --proxied
```
## Access Information
After configuration, the webmail system will be accessible at:
- **Production**: `https://webmail.bakewise.ai`
- **Development**: `https://webmail.bakery-ia.local`
Default credentials (configured in secrets):
- **Admin**: `admin@bakewise.ai`
- **Password**: Configured in `email-secrets`
## Integration with Existing Systems
The webmail system integrates with:
1. **SMTP Service**: `email-smtp.bakery-ia.svc.cluster.local:587`
2. **IMAP Service**: `email-imap.bakery-ia.svc.cluster.local:993`
3. **Notification Service**: Uses the new SMTP service for email notifications
4. **Monitoring**: SigNoz alerts use the new email service
## Backup and Recovery
### DNS Backup
```bash
# Export DNS records (Cloudflare example)
cfcli dns export bakewise.ai > dns-backup.json
# Restore DNS records
cfcli dns import bakewise.ai dns-backup.json
```
### Certificate Backup
```bash
# Export TLS secrets
kubectl get secret bakery-ia-prod-tls-cert -n bakery-ia -o yaml > tls-backup.yaml
# Restore TLS secrets
kubectl apply -f tls-backup.yaml
```
## References
- [Cert-manager Documentation](https://cert-manager.io/docs/)
- [NGINX Ingress Controller](https://kubernetes.github.io/ingress-nginx/)
- [Let's Encrypt](https://letsencrypt.org/)
- [DNS Configuration Best Practices](https://www.cloudflare.com/learning/dns/)
This configuration provides a secure, scalable webmail solution that integrates seamlessly with the existing Bakery-IA infrastructure.

View File

@@ -1,20 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: bakery-ia
resources:
- mailu-configmap.yaml
- mailu-secrets.yaml
- mailu-pvc.yaml
- mailu-deployment.yaml
- mailu-services.yaml
- mailu-antispam.yaml
- mailu-networkpolicy.yaml
- mailu-nginx-config.yaml
labels:
- includeSelectors: true
pairs:
app: mailu
platform: mail
managed-by: kustomize

View File

@@ -1,48 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailu-antispam
namespace: bakery-ia
labels:
app: mailu
component: antispam
spec:
replicas: 1
selector:
matchLabels:
app: mailu
component: antispam
template:
metadata:
labels:
app: mailu
component: antispam
spec:
containers:
- name: antispam
image: ghcr.io/mailu/rspamd:2024.06
imagePullPolicy: IfNotPresent
ports:
- containerPort: 11333
name: rspamd
- containerPort: 11334
name: rspamd-admin
envFrom:
- configMapRef:
name: mailu-config
- secretRef:
name: mailu-secrets
volumeMounts:
- name: mailu-data
mountPath: /data
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
volumes:
- name: mailu-data
persistentVolumeClaim:
claimName: mailu-data

View File

@@ -1,79 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: mailu-config
namespace: bakery-ia
labels:
app: mailu
component: config
data:
# Domain configuration
DOMAIN: "DOMAIN_PLACEHOLDER"
HOSTNAMES: "mail.DOMAIN_PLACEHOLDER"
POSTMASTER: "admin"
# Kubernetes-specific settings
# These help Mailu components discover each other in K8s
FRONT_ADDRESS: "mailu-front.bakery-ia.svc.cluster.local"
ADMIN_ADDRESS: "mailu-admin.bakery-ia.svc.cluster.local"
SMTP_ADDRESS: "mailu-smtp.bakery-ia.svc.cluster.local"
IMAP_ADDRESS: "mailu-imap.bakery-ia.svc.cluster.local"
ANTISPAM_ADDRESS: "mailu-antispam.bakery-ia.svc.cluster.local"
# Redis Configuration - Using shared cluster Redis (database 15 reserved for Mailu)
# The shared Redis has 16 databases (0-15), Mailu uses db 15 for isolation
# Using plain TCP port 6380 for internal cluster communication (TLS on 6379 for external)
# Primary configuration: Redis URL is configured in mailu-secrets.yaml as REDIS_URL
# Format: redis://:password@host:port/db
# Fallback configuration: REDIS_ADDRESS, REDIS_DB, and REDIS_PW
REDIS_ADDRESS: "redis-service.bakery-ia.svc.cluster.local:6380"
REDIS_DB: "15"
# REDIS_PW is set from secrets for Redis authentication
# External SMTP Relay Configuration
# Mailu relays outbound emails through an external service for better deliverability
# Supported providers: Mailgun, SendGrid, AWS SES, Postmark
#
# Provider RELAYHOST examples:
# Mailgun: [smtp.mailgun.org]:587
# SendGrid: [smtp.sendgrid.net]:587
# AWS SES: [email-smtp.us-east-1.amazonaws.com]:587
# Postmark: [smtp.postmarkapp.com]:587
#
# IMPORTANT: Update RELAY_PASSWORD in mailu-secrets.yaml with your provider's API key
RELAYHOST: "[smtp.mailgun.org]:587"
RELAY_LOGIN: "postmaster@DOMAIN_PLACEHOLDER"
# Security settings
TLS_FLAVOR: "cert"
AUTH_RATELIMIT_IP: "60/hour"
AUTH_RATELIMIT_USER: "100/day"
# Message limits
MESSAGE_SIZE_LIMIT: "52428800" # 50MB
MESSAGE_RATELIMIT: "200/day"
# Features - disable ClamAV in dev to save resources (enable in prod)
WEBMAIL: "roundcube"
ANTIVIRUS: "none"
ANTISPAM: "rspamd"
# Postfix configuration
POSTFIX_MESSAGE_SIZE_LIMIT: "52428800"
POSTFIX_QUEUE_MINIMUM: "1"
POSTFIX_QUEUE_LIFETIME: "7d"
# DKIM configuration
DKIM_SELECTOR: "mailu"
DKIM_KEY_LENGTH: "2048"
# Webmail settings
WEB_WEBMAIL: "/webmail"
WEB_ADMIN: "/admin"
WEBMAIL_ADMIN: "admin@DOMAIN_PLACEHOLDER"
# Logging
LOG_LEVEL: "INFO"
# Disable welcome email during development
WELCOME: "false"

View File

@@ -1,218 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailu-front
namespace: bakery-ia
labels:
app: mailu
component: front
spec:
replicas: 1
selector:
matchLabels:
app: mailu
component: front
template:
metadata:
labels:
app: mailu
component: front
spec:
containers:
- name: front
image: ghcr.io/mailu/nginx:2024.06
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
name: http
- containerPort: 443
name: https
envFrom:
- configMapRef:
name: mailu-config
- secretRef:
name: mailu-secrets
volumeMounts:
- name: mailu-data
mountPath: /data
- name: mailu-tls
mountPath: /certs
readOnly: true
- name: nginx-config
mountPath: /overrides/ingress-fix.conf
subPath: ingress-fix.conf
readOnly: true
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
volumes:
- name: mailu-data
persistentVolumeClaim:
claimName: mailu-data
- name: mailu-tls
secret:
# TLS secret name is environment-specific:
# - Dev: bakery-dev-tls-cert (self-signed, from dev-certificate.yaml)
# - Prod: bakery-ia-prod-tls-cert (Let's Encrypt, from prod-certificate.yaml)
# Patched via kustomize overlays in dev/prod kustomization.yaml
secretName: MAILU_TLS_SECRET_PLACEHOLDER
items:
- key: tls.crt
path: cert.pem
- key: tls.key
path: key.pem
- name: nginx-config
configMap:
name: mailu-nginx-config
items:
- key: ingress-fix.conf
path: ingress-fix.conf
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailu-admin
namespace: bakery-ia
labels:
app: mailu
component: admin
spec:
replicas: 1
selector:
matchLabels:
app: mailu
component: admin
template:
metadata:
labels:
app: mailu
component: admin
spec:
containers:
- name: admin
image: ghcr.io/mailu/admin:2024.06
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
name: http
envFrom:
- configMapRef:
name: mailu-config
- secretRef:
name: mailu-secrets
volumeMounts:
- name: mailu-data
mountPath: /data
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 300m
memory: 512Mi
volumes:
- name: mailu-data
persistentVolumeClaim:
claimName: mailu-data
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailu-smtp
namespace: bakery-ia
labels:
app: mailu
component: smtp
spec:
replicas: 1
selector:
matchLabels:
app: mailu
component: smtp
template:
metadata:
labels:
app: mailu
component: smtp
spec:
containers:
- name: smtp
image: ghcr.io/mailu/postfix:2024.06
imagePullPolicy: IfNotPresent
ports:
- containerPort: 25
name: smtp
- containerPort: 587
name: submission
envFrom:
- configMapRef:
name: mailu-config
- secretRef:
name: mailu-secrets
volumeMounts:
- name: mailu-data
mountPath: /data
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
volumes:
- name: mailu-data
persistentVolumeClaim:
claimName: mailu-data
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailu-imap
namespace: bakery-ia
labels:
app: mailu
component: imap
spec:
replicas: 1
selector:
matchLabels:
app: mailu
component: imap
template:
metadata:
labels:
app: mailu
component: imap
spec:
containers:
- name: imap
image: ghcr.io/mailu/dovecot:2024.06
imagePullPolicy: IfNotPresent
ports:
- containerPort: 143
name: imap
- containerPort: 993
name: imaps
envFrom:
- configMapRef:
name: mailu-config
- secretRef:
name: mailu-secrets
volumeMounts:
- name: mailu-data
mountPath: /data
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
volumes:
- name: mailu-data
persistentVolumeClaim:
claimName: mailu-data

View File

@@ -1,93 +0,0 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: mailu-network-policy
namespace: bakery-ia
labels:
app: mailu
component: network-policy
spec:
# Apply to all Mailu pods (matches mailu-deployment.yaml labels)
podSelector:
matchLabels:
app: mailu
policyTypes:
- Ingress
- Egress
ingress:
# Allow SMTP from notification-service
- from:
- podSelector:
matchLabels:
app: notification-service
ports:
- port: 25
- port: 587
# Allow SMTP from other internal services that may need to send email
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: bakery-ia
ports:
- port: 587
# Allow webmail/admin access via ingress controller
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
ports:
- port: 80
- port: 443
# Allow internal Mailu component communication
- from:
- podSelector:
matchLabels:
app: mailu
ports:
- port: 25
- port: 587
- port: 143
- port: 993
- port: 80
- port: 11333
- port: 11334
egress:
# Allow relay to external SMTP (Mailgun)
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
ports:
- port: 587
- port: 465
- port: 25
# Allow internal Mailu component communication
- to:
- podSelector:
matchLabels:
app: mailu
ports:
- port: 25
- port: 587
- port: 143
- port: 993
- port: 80
- port: 11333
- port: 11334
# Allow connection to shared Redis (database 15)
- to:
- podSelector:
matchLabels:
app.kubernetes.io/name: redis
ports:
- port: 6379
# Allow DNS lookups
- to: []
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP

View File

@@ -1,31 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: mailu-nginx-config
namespace: bakery-ia
labels:
app: mailu
component: nginx-config
data:
# Custom Nginx configuration to prevent redirect loops when behind ingress
# This file is mounted as /overrides/ingress-fix.conf in the Mailu frontend container
ingress-fix.conf: |
# Override the default HTTP to HTTPS redirect behavior
# When behind ingress controller, we should trust X-Forwarded-Proto header
# and avoid redirect loops
# Disable the HTTP to HTTPS redirect by overriding the redirect condition
# This prevents the redirect loop by setting the proxy protocol to https
set $proxy_x_forwarded_proto "https";
# Override the map directive to always return https when behind ingress
map "" $proxy_x_forwarded_proto {
default "https";
}
# Trust the X-Forwarded-* headers from the ingress controller
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 192.168.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

View File

@@ -1,21 +0,0 @@
# Mailu data storage - shared across all Mailu components
# Contains: mail data, SQLite database, DKIM keys, SSL certificates, queue
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mailu-data
namespace: bakery-ia
labels:
app: mailu
component: storage
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
# NOTE: Change storageClassName based on your cluster's storage provisioner
# For local development (kind): standard
# For AWS EKS: gp2 or gp3
# For GKE: standard or premium-rwo
# For AKS: managed-premium or managed-csi

View File

@@ -1,37 +0,0 @@
apiVersion: v1
kind: Secret
metadata:
name: mailu-secrets
namespace: bakery-ia
labels:
app: mailu
component: secrets
type: Opaque
data:
# Admin credentials (base64 encoded)
# IMPORTANT: Replace with real credentials before production deployment
# Generate with: openssl rand -base64 24 | tr -d '\n' | base64
ADMIN_PASSWORD: "VzJYS2tSdUxpT25ZS2RCWVFTQXJvbjFpeWtFU1M1b2I=" # W2XKkRuLiOnYKdBYQSAron1iykESS5ob
# Mailu secret key for internal encryption
# Generate with: openssl rand -base64 32
SECRET_KEY: "Y2I2MWI5MzRkNDcwMjlhNjQxMTdjMGU0MTEwYzkzZjY2YmJjZjVlYWExNWM4NGM0MjcyN2ZhZDc4Zjc=" # cb61b934d47029a64117c0e4110c93f66bbcf5eaa15c84c42727fad78f7
# External SMTP relay credentials (Mailgun)
# For Mailgun: use postmaster@domain as username
RELAY_USER: "cG9zdG1hc3RlckBET01BSU5fUExBQ0VIT0xERVI=" # postmaster@DOMAIN_PLACEHOLDER
RELAY_PASSWORD: "bWFpbGd1bi1hcGkta2V5LXJlcGxhY2UtaW4tcHJvZHVjdGlvbg==" # mailgun-api-key-replace-in-production
# Database credentials
DB_PASSWORD: "RThLejQ3WW1WekRsSEdzMU05d0FiSnp4Y0tuR09OQ1Q=" # E8Kz47YmVzDlHGs1M9wAbJzxcKnGONCT
# Dovecot admin password (moved from ConfigMap for security)
DOVEADM_PASSWORD: "WnZhMzNoaVBJc2ZtV3RxUlBWV29taTRYZ2xLTlZPcHY=" # Zva33hiPIsfmWtqRPVWomi4XglKNVOpv
# Redis password - same as shared cluster Redis (redis-secrets)
# Mailu uses database 15 for isolation from other services
# REDIS_PW is required by Mailu for Redis authentication
REDIS_PASSWORD: "SjNsa2x4cHU5QzlPTElLdkJteFVIT2h0czFnc0lvM0E=" # J3lklxpu9C9OLIKvBmxUHOhts1gsIo3A
REDIS_PW: "SjNsa2x4cHU5QzlPTElLdkJteFVIT2h0czFnc0lvM0E=" # J3lklxpu9C9OLIKvBmxUHOhts1gsIo3A
# Redis URL for Mailu - using plain TCP port 6380 for internal cluster communication
REDIS_URL: "cmVkaXM6Ly86SjNsa2x4cHU5QzlPTElLdkJteFVIT2h0czFnc0lvM0FAcmVkaXMtc2VydmljZS5iYWtlcnktaWEuc3ZjLmNsdXN0ZXIubG9jYWw6NjM4MC8xNQ==" # redis://:J3lklxpu9C9OLIKvBmxUHOhts1gsIo3A@redis-service.bakery-ia.svc.cluster.local:6380/15

View File

@@ -1,126 +0,0 @@
# Mailu Services - Routes traffic to Mailu stack components
# All services use app: mailu selectors to match mailu-deployment.yaml
apiVersion: v1
kind: Service
metadata:
name: mailu-front
namespace: bakery-ia
labels:
app: mailu
component: front
spec:
type: ClusterIP
selector:
app: mailu
component: front
ports:
- name: http
port: 80
targetPort: 80
- name: https
port: 443
targetPort: 443
---
apiVersion: v1
kind: Service
metadata:
name: mailu-admin
namespace: bakery-ia
labels:
app: mailu
component: admin
spec:
type: ClusterIP
selector:
app: mailu
component: admin
ports:
- name: http
port: 80
targetPort: 80
---
# Primary SMTP service - used by notification-service and other internal services
apiVersion: v1
kind: Service
metadata:
name: mailu-smtp
namespace: bakery-ia
labels:
app: mailu
component: smtp
spec:
type: ClusterIP
selector:
app: mailu
component: smtp
ports:
- name: smtp
port: 25
targetPort: 25
- name: submission
port: 587
targetPort: 587
---
# Alias for backwards compatibility with services expecting 'email-smtp'
apiVersion: v1
kind: Service
metadata:
name: email-smtp
namespace: bakery-ia
labels:
app: mailu
component: smtp
spec:
type: ClusterIP
selector:
app: mailu
component: smtp
ports:
- name: smtp
port: 25
targetPort: 25
- name: submission
port: 587
targetPort: 587
---
apiVersion: v1
kind: Service
metadata:
name: mailu-imap
namespace: bakery-ia
labels:
app: mailu
component: imap
spec:
type: ClusterIP
selector:
app: mailu
component: imap
ports:
- name: imap
port: 143
targetPort: 143
- name: imaps
port: 993
targetPort: 993
---
apiVersion: v1
kind: Service
metadata:
name: mailu-antispam
namespace: bakery-ia
labels:
app: mailu
component: antispam
spec:
type: ClusterIP
selector:
app: mailu
component: antispam
ports:
- name: rspamd
port: 11333
targetPort: 11333
- name: rspamd-admin
port: 11334
targetPort: 11334

View File

@@ -1,24 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: bakery-ia
resources:
- mailu-configmap.yaml
- mailu-secrets.yaml
- mailu-pvc.yaml
- mailu-deployment.yaml
- mailu-services.yaml
- mailu-antispam.yaml
- mailu-networkpolicy.yaml
# NOTE: mailu-ingress.yaml removed - ingress is now centralized in platform/networking
# NOTE: mailu-replacement.yaml removed - using official Mailu stack
# NOTE: email-config.yaml removed - configuration consolidated into mailu-configmap.yaml
# NOTE: Network policy kept here for self-contained module (could be moved to global security)
# NOTE: Mailu uses shared Redis (redis-service) with database 15 - no separate Redis needed
labels:
- includeSelectors: true
pairs:
app: mailu
platform: mail
managed-by: kustomize

View File

@@ -1,48 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailu-antispam
namespace: bakery-ia
labels:
app: mailu
component: antispam
spec:
replicas: 1
selector:
matchLabels:
app: mailu
component: antispam
template:
metadata:
labels:
app: mailu
component: antispam
spec:
containers:
- name: antispam
image: ghcr.io/mailu/rspamd:2024.06
imagePullPolicy: IfNotPresent
ports:
- containerPort: 11333
name: rspamd
- containerPort: 11334
name: rspamd-admin
envFrom:
- configMapRef:
name: mailu-config
- secretRef:
name: mailu-secrets
volumeMounts:
- name: mailu-data
mountPath: /data
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
volumes:
- name: mailu-data
persistentVolumeClaim:
claimName: mailu-data

View File

@@ -1,79 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: mailu-config
namespace: bakery-ia
labels:
app: mailu
component: config
data:
# Domain configuration
DOMAIN: "bakewise.ai"
HOSTNAMES: "mail.bakewise.ai"
POSTMASTER: "admin"
# Kubernetes-specific settings
# These help Mailu components discover each other in K8s
FRONT_ADDRESS: "mailu-front.bakery-ia.svc.cluster.local"
ADMIN_ADDRESS: "mailu-admin.bakery-ia.svc.cluster.local"
SMTP_ADDRESS: "mailu-smtp.bakery-ia.svc.cluster.local"
IMAP_ADDRESS: "mailu-imap.bakery-ia.svc.cluster.local"
ANTISPAM_ADDRESS: "mailu-antispam.bakery-ia.svc.cluster.local"
# Redis Configuration - Using shared cluster Redis (database 15 reserved for Mailu)
# The shared Redis has 16 databases (0-15), Mailu uses db 15 for isolation
# Using plain TCP port 6380 for internal cluster communication (TLS on 6379 for external)
# Primary configuration: Redis URL is configured in mailu-secrets.yaml as REDIS_URL
# Format: redis://:password@host:port/db
# Fallback configuration: REDIS_ADDRESS, REDIS_DB, and REDIS_PW
REDIS_ADDRESS: "redis-service.bakery-ia.svc.cluster.local:6380"
REDIS_DB: "15"
# REDIS_PW is set from secrets for Redis authentication
# External SMTP Relay Configuration
# Mailu relays outbound emails through an external service for better deliverability
# Supported providers: Mailgun, SendGrid, AWS SES, Postmark
#
# Provider RELAYHOST examples:
# Mailgun: [smtp.mailgun.org]:587
# SendGrid: [smtp.sendgrid.net]:587
# AWS SES: [email-smtp.us-east-1.amazonaws.com]:587
# Postmark: [smtp.postmarkapp.com]:587
#
# IMPORTANT: Update RELAY_PASSWORD in mailu-secrets.yaml with your provider's API key
RELAYHOST: "[smtp.mailgun.org]:587"
RELAY_LOGIN: "postmaster@bakewise.ai"
# Security settings
TLS_FLAVOR: "cert"
AUTH_RATELIMIT_IP: "60/hour"
AUTH_RATELIMIT_USER: "100/day"
# Message limits
MESSAGE_SIZE_LIMIT: "52428800" # 50MB
MESSAGE_RATELIMIT: "200/day"
# Features - disable ClamAV in dev to save resources (enable in prod)
WEBMAIL: "roundcube"
ANTIVIRUS: "none"
ANTISPAM: "rspamd"
# Postfix configuration
POSTFIX_MESSAGE_SIZE_LIMIT: "52428800"
POSTFIX_QUEUE_MINIMUM: "1"
POSTFIX_QUEUE_LIFETIME: "7d"
# DKIM configuration
DKIM_SELECTOR: "mailu"
DKIM_KEY_LENGTH: "2048"
# Webmail settings
WEB_WEBMAIL: "/webmail"
WEB_ADMIN: "/admin"
WEBMAIL_ADMIN: "admin@bakewise.ai"
# Logging
LOG_LEVEL: "INFO"
# Disable welcome email during development
WELCOME: "false"

View File

@@ -1,208 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailu-front
namespace: bakery-ia
labels:
app: mailu
component: front
spec:
replicas: 1
selector:
matchLabels:
app: mailu
component: front
template:
metadata:
labels:
app: mailu
component: front
spec:
containers:
- name: front
image: ghcr.io/mailu/nginx:2024.06
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
name: http
- containerPort: 443
name: https
envFrom:
- configMapRef:
name: mailu-config
- secretRef:
name: mailu-secrets
volumeMounts:
- name: mailu-data
mountPath: /data
- name: mailu-tls
mountPath: /certs
readOnly: true
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
volumes:
- name: mailu-data
persistentVolumeClaim:
claimName: mailu-data
- name: mailu-tls
secret:
# TLS secret name is environment-specific:
# - Dev: bakery-dev-tls-cert (self-signed, from dev-certificate.yaml)
# - Prod: bakery-ia-prod-tls-cert (Let's Encrypt, from prod-certificate.yaml)
# Patched via kustomize overlays in dev/prod kustomization.yaml
secretName: MAILU_TLS_SECRET_PLACEHOLDER
items:
- key: tls.crt
path: cert.pem
- key: tls.key
path: key.pem
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailu-admin
namespace: bakery-ia
labels:
app: mailu
component: admin
spec:
replicas: 1
selector:
matchLabels:
app: mailu
component: admin
template:
metadata:
labels:
app: mailu
component: admin
spec:
containers:
- name: admin
image: ghcr.io/mailu/admin:2024.06
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
name: http
envFrom:
- configMapRef:
name: mailu-config
- secretRef:
name: mailu-secrets
volumeMounts:
- name: mailu-data
mountPath: /data
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 300m
memory: 512Mi
volumes:
- name: mailu-data
persistentVolumeClaim:
claimName: mailu-data
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailu-smtp
namespace: bakery-ia
labels:
app: mailu
component: smtp
spec:
replicas: 1
selector:
matchLabels:
app: mailu
component: smtp
template:
metadata:
labels:
app: mailu
component: smtp
spec:
containers:
- name: smtp
image: ghcr.io/mailu/postfix:2024.06
imagePullPolicy: IfNotPresent
ports:
- containerPort: 25
name: smtp
- containerPort: 587
name: submission
envFrom:
- configMapRef:
name: mailu-config
- secretRef:
name: mailu-secrets
volumeMounts:
- name: mailu-data
mountPath: /data
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
volumes:
- name: mailu-data
persistentVolumeClaim:
claimName: mailu-data
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailu-imap
namespace: bakery-ia
labels:
app: mailu
component: imap
spec:
replicas: 1
selector:
matchLabels:
app: mailu
component: imap
template:
metadata:
labels:
app: mailu
component: imap
spec:
containers:
- name: imap
image: ghcr.io/mailu/dovecot:2024.06
imagePullPolicy: IfNotPresent
ports:
- containerPort: 143
name: imap
- containerPort: 993
name: imaps
envFrom:
- configMapRef:
name: mailu-config
- secretRef:
name: mailu-secrets
volumeMounts:
- name: mailu-data
mountPath: /data
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
volumes:
- name: mailu-data
persistentVolumeClaim:
claimName: mailu-data

View File

@@ -1,93 +0,0 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: mailu-network-policy
namespace: bakery-ia
labels:
app: mailu
component: network-policy
spec:
# Apply to all Mailu pods (matches mailu-deployment.yaml labels)
podSelector:
matchLabels:
app: mailu
policyTypes:
- Ingress
- Egress
ingress:
# Allow SMTP from notification-service
- from:
- podSelector:
matchLabels:
app: notification-service
ports:
- port: 25
- port: 587
# Allow SMTP from other internal services that may need to send email
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: bakery-ia
ports:
- port: 587
# Allow webmail/admin access via ingress controller
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
ports:
- port: 80
- port: 443
# Allow internal Mailu component communication
- from:
- podSelector:
matchLabels:
app: mailu
ports:
- port: 25
- port: 587
- port: 143
- port: 993
- port: 80
- port: 11333
- port: 11334
egress:
# Allow relay to external SMTP (Mailgun)
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
ports:
- port: 587
- port: 465
- port: 25
# Allow internal Mailu component communication
- to:
- podSelector:
matchLabels:
app: mailu
ports:
- port: 25
- port: 587
- port: 143
- port: 993
- port: 80
- port: 11333
- port: 11334
# Allow connection to shared Redis (database 15)
- to:
- podSelector:
matchLabels:
app.kubernetes.io/name: redis
ports:
- port: 6379
# Allow DNS lookups
- to: []
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP

View File

@@ -1,21 +0,0 @@
# Mailu data storage - shared across all Mailu components
# Contains: mail data, SQLite database, DKIM keys, SSL certificates, queue
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mailu-data
namespace: bakery-ia
labels:
app: mailu
component: storage
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
# NOTE: Change storageClassName based on your cluster's storage provisioner
# For local development (kind): standard
# For AWS EKS: gp2 or gp3
# For GKE: standard or premium-rwo
# For AKS: managed-premium or managed-csi

View File

@@ -1,37 +0,0 @@
apiVersion: v1
kind: Secret
metadata:
name: mailu-secrets
namespace: bakery-ia
labels:
app: mailu
component: secrets
type: Opaque
data:
# Admin credentials (base64 encoded)
# IMPORTANT: Replace with real credentials before production deployment
# Generate with: openssl rand -base64 24 | tr -d '\n' | base64
ADMIN_PASSWORD: "VzJYS2tSdUxpT25ZS2RCWVFTQXJvbjFpeWtFU1M1b2I=" # W2XKkRuLiOnYKdBYQSAron1iykESS5ob
# Mailu secret key for internal encryption
# Generate with: openssl rand -base64 32
SECRET_KEY: "Y2I2MWI5MzRkNDcwMjlhNjQxMTdjMGU0MTEwYzkzZjY2YmJjZjVlYWExNWM4NGM0MjcyN2ZhZDc4Zjc=" # cb61b934d47029a64117c0e4110c93f66bbcf5eaa15c84c42727fad78f7
# External SMTP relay credentials (Mailgun)
# For Mailgun: use postmaster@domain as username
RELAY_USER: "cG9zdG1hc3RlckBiYWtld2lzZS5haQ==" # postmaster@bakewise.ai
RELAY_PASSWORD: "bWFpbGd1bi1hcGkta2V5LXJlcGxhY2UtaW4tcHJvZHVjdGlvbg==" # mailgun-api-key-replace-in-production
# Database credentials
DB_PASSWORD: "RThLejQ3WW1WekRsSEdzMU05d0FiSnp4Y0tuR09OQ1Q=" # E8Kz47YmVzDlHGs1M9wAbJzxcKnGONCT
# Dovecot admin password (moved from ConfigMap for security)
DOVEADM_PASSWORD: "WnZhMzNoaVBJc2ZtV3RxUlBWV29taTRYZ2xLTlZPcHY=" # Zva33hiPIsfmWtqRPVWomi4XglKNVOpv
# Redis password - same as shared cluster Redis (redis-secrets)
# Mailu uses database 15 for isolation from other services
# REDIS_PW is required by Mailu for Redis authentication
REDIS_PASSWORD: "SjNsa2x4cHU5QzlPTElLdkJteFVIT2h0czFnc0lvM0E=" # J3lklxpu9C9OLIKvBmxUHOhts1gsIo3A
REDIS_PW: "SjNsa2x4cHU5QzlPTElLdkJteFVIT2h0czFnc0lvM0E=" # J3lklxpu9C9OLIKvBmxUHOhts1gsIo3A
# Redis URL for Mailu - using plain TCP port 6380 for internal cluster communication
REDIS_URL: "cmVkaXM6Ly86SjNsa2x4cHU5QzlPTElLdkJteFVIT2h0czFnc0lvM0FAcmVkaXMtc2VydmljZS5iYWtlcnktaWEuc3ZjLmNsdXN0ZXIubG9jYWw6NjM4MC8xNQ==" # redis://:J3lklxpu9C9OLIKvBmxUHOhts1gsIo3A@redis-service.bakery-ia.svc.cluster.local:6380/15

View File

@@ -1,126 +0,0 @@
# Mailu Services - Routes traffic to Mailu stack components
# All services use app: mailu selectors to match mailu-deployment.yaml
apiVersion: v1
kind: Service
metadata:
name: mailu-front
namespace: bakery-ia
labels:
app: mailu
component: front
spec:
type: ClusterIP
selector:
app: mailu
component: front
ports:
- name: http
port: 80
targetPort: 80
- name: https
port: 443
targetPort: 443
---
apiVersion: v1
kind: Service
metadata:
name: mailu-admin
namespace: bakery-ia
labels:
app: mailu
component: admin
spec:
type: ClusterIP
selector:
app: mailu
component: admin
ports:
- name: http
port: 80
targetPort: 80
---
# Primary SMTP service - used by notification-service and other internal services
apiVersion: v1
kind: Service
metadata:
name: mailu-smtp
namespace: bakery-ia
labels:
app: mailu
component: smtp
spec:
type: ClusterIP
selector:
app: mailu
component: smtp
ports:
- name: smtp
port: 25
targetPort: 25
- name: submission
port: 587
targetPort: 587
---
# Alias for backwards compatibility with services expecting 'email-smtp'
apiVersion: v1
kind: Service
metadata:
name: email-smtp
namespace: bakery-ia
labels:
app: mailu
component: smtp
spec:
type: ClusterIP
selector:
app: mailu
component: smtp
ports:
- name: smtp
port: 25
targetPort: 25
- name: submission
port: 587
targetPort: 587
---
apiVersion: v1
kind: Service
metadata:
name: mailu-imap
namespace: bakery-ia
labels:
app: mailu
component: imap
spec:
type: ClusterIP
selector:
app: mailu
component: imap
ports:
- name: imap
port: 143
targetPort: 143
- name: imaps
port: 993
targetPort: 993
---
apiVersion: v1
kind: Service
metadata:
name: mailu-antispam
namespace: bakery-ia
labels:
app: mailu
component: antispam
spec:
type: ClusterIP
selector:
app: mailu
component: antispam
ports:
- name: rspamd
port: 11333
targetPort: 11333
- name: rspamd-admin
port: 11334
targetPort: 11334

View File

@@ -1,32 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namePrefix: dev-
patches:
- target:
kind: ConfigMap
name: mailu-config
patch: |-
- op: replace
path: /data/DOMAIN
value: "bakery-ia.local"
- op: replace
path: /data/HOSTNAMES
value: "mail.bakery-ia.local"
- op: replace
path: /data/RELAY_LOGIN
value: "postmaster@bakery-ia.local"
- op: replace
path: /data/WEBMAIL_ADMIN
value: "admin@bakery-ia.local"
- target:
kind: Secret
name: mailu-secrets
patch: |-
- op: replace
path: /data/RELAY_USER
value: "cG9zdG1hc3RlckBiYWtlcnktaWEubG9jYWw=" # postmaster@bakery-ia.local

View File

@@ -1,32 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namePrefix: prod-
patches:
- target:
kind: ConfigMap
name: mailu-config
patch: |-
- op: replace
path: /data/DOMAIN
value: "bakewise.ai"
- op: replace
path: /data/HOSTNAMES
value: "mail.bakewise.ai"
- op: replace
path: /data/RELAY_LOGIN
value: "postmaster@bakewise.ai"
- op: replace
path: /data/WEBMAIL_ADMIN
value: "admin@bakewise.ai"
- target:
kind: Secret
name: mailu-secrets
patch: |-
- op: replace
path: /data/RELAY_USER
value: "cG9zdG1hc3RlckBiYWtld2lzZS5haQ==" # postmaster@bakewise.ai

View File

@@ -10,7 +10,7 @@ metadata:
# 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: "10m"
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
@@ -69,20 +69,6 @@ spec:
- host: mail.DOMAIN_PLACEHOLDER # To be replaced by kustomize
http:
paths:
- path: /webmail
pathType: Prefix
backend:
service:
name: mailu-front
port:
number: 80
- path: /admin
pathType: Prefix
backend:
service:
name: mailu-front
port:
number: 80
- path: /
pathType: Prefix
backend:

View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: gitea-http
namespace: bakery-ia
spec:
type: ExternalName
externalName: gitea-http.gitea.svc.cluster.local
ports:
- port: 3000
targetPort: 3000

View File

@@ -3,6 +3,7 @@ kind: Kustomization
resources:
- ../../base
- gitea-service.yaml
namePrefix: dev-

View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: gitea-http
namespace: bakery-ia
spec:
type: ExternalName
externalName: gitea-http.gitea.svc.cluster.local
ports:
- port: 3000
targetPort: 3000

View File

@@ -3,6 +3,7 @@ kind: Kustomization
resources:
- ../../base
- gitea-service.yaml
namePrefix: prod-

Some files were not shown because too many files have changed in this diff Show More