Compare commits
26 Commits
feature/ba
...
5879c84a05
| Author | SHA1 | Date | |
|---|---|---|---|
| 5879c84a05 | |||
| c7cbfc712e | |||
| ddccdacd6d | |||
| 65598c9297 | |||
| 3f0f70699b | |||
| a89f2f30ce | |||
| 9a7e03b794 | |||
| f1dd61cece | |||
| acc9bb1a85 | |||
| c8c2dedea5 | |||
| a471f11740 | |||
| 92ddc22322 | |||
| 7d2fb8bc0c | |||
| 79f9c62012 | |||
| dea54e469e | |||
| 333acdea26 | |||
| 03d526208b | |||
| 458f7b23ad | |||
| 41c8b85bf8 | |||
| c3f723333b | |||
| 4144b1c1ac | |||
| 16eadbe181 | |||
| 4e6a84785a | |||
| e0bdaab422 | |||
| 230ea7ebeb | |||
| cab0866e14 |
40
README.md
40
README.md
@@ -1,9 +1,9 @@
|
||||
# Kubernetes Cluster - GitOps Configuration
|
||||
|
||||
> **Kubernetes cluster bootstrapping and GitOps configuration repository** using ArgoCD for UpCloud Managed Kubernetes
|
||||
> **Kubernetes cluster bootstrapping and GitOps configuration repository** using ArgoCD for multi-cloud Kubernetes (UpCloud, AWS EKS, Azure AKS, GCP GKE)
|
||||
|
||||
[](https://argoproj.github.io/cd/)
|
||||
[](https://upcloud.com/)
|
||||
[]()
|
||||
|
||||
---
|
||||
|
||||
@@ -95,14 +95,26 @@ This repository contains the complete GitOps configuration for our Kubernetes cl
|
||||
│ │ ├── renovate.yaml
|
||||
│ │ ├── ... # All other Application manifests
|
||||
│ │ └── secrets.yaml
|
||||
│ ├── overlays/ # Per-cluster overrides
|
||||
│ │ ├── upc-dev/ # UpCloud Dev cluster (uses base as-is)
|
||||
│ │ └── upc-prod/ # UpCloud Prod cluster (patches value paths)
|
||||
│ ├── overlays/ # Per-cluster overrides (Kustomize)
|
||||
│ │ ├── upc-dev/ # UpCloud Dev (uses base as-is)
|
||||
│ │ ├── upc-prod/ # UpCloud Prod (patches value paths)
|
||||
│ │ ├── eks-dev/ # AWS EKS Dev
|
||||
│ │ ├── eks-prod/ # AWS EKS Prod
|
||||
│ │ ├── aks-dev/ # Azure AKS Dev
|
||||
│ │ ├── aks-prod/ # Azure AKS Prod
|
||||
│ │ ├── gke-dev/ # GCP GKE Dev
|
||||
│ │ └── gke-prod/ # GCP GKE Prod
|
||||
│ ├── dashboards/ # Grafana dashboard ConfigMaps
|
||||
│ └── values/ # Helm value overrides
|
||||
│ ├── base/ # Shared values (all clusters)
|
||||
│ ├── upc-dev/ # UpCloud Dev-specific values
|
||||
│ └── upc-prod/ # UpCloud Prod-specific values
|
||||
│ ├── base/ # Shared cloud-agnostic values
|
||||
│ ├── upc-dev/ # UpCloud Dev (storage, LB, pricing)
|
||||
│ ├── upc-prod/ # UpCloud Prod
|
||||
│ ├── eks-dev/ # AWS EKS Dev
|
||||
│ ├── eks-prod/ # AWS EKS Prod
|
||||
│ ├── aks-dev/ # Azure AKS Dev
|
||||
│ ├── aks-prod/ # Azure AKS Prod
|
||||
│ ├── gke-dev/ # GCP GKE Dev
|
||||
│ └── gke-prod/ # GCP GKE Prod
|
||||
│
|
||||
├── apps/ # Business Applications
|
||||
│ ├── mcp10x.yaml
|
||||
@@ -361,7 +373,7 @@ kubectl patch application myapp -n argocd \
|
||||
## 📖 Key Concepts
|
||||
|
||||
### App-of-Apps Pattern
|
||||
`_app-of-apps.yaml` is the root Application that manages all other Applications in `infra/`. Kustomize overlays in `infra/overlays/{upc-dev,upc-prod}/` render the base Applications with per-cluster patches (e.g., swapping value file paths from `upc-dev` to `upc-prod`).
|
||||
`_app-of-apps-{cluster}.yaml` is the root Application that manages all other Applications in `infra/`. Kustomize overlays in `infra/overlays/{cluster}/` render the base Applications with per-cluster patches (e.g., swapping value file paths). Supported clusters: `upc-dev`, `upc-prod`, `eks-dev`, `eks-prod`, `aks-dev`, `aks-prod`, `gke-dev`, `gke-prod`.
|
||||
|
||||
### Multi-Source Pattern
|
||||
Applications reference both:
|
||||
@@ -458,16 +470,14 @@ Documentation lives in `docs/`. To update:
|
||||
## 📝 Notes
|
||||
|
||||
### Current Environment
|
||||
- **Provider**: UpCloud Managed Kubernetes
|
||||
- **Provider**: Multi-cloud (UpCloud, AWS EKS, Azure AKS, GCP GKE)
|
||||
- **Active clusters**: UpCloud (upc-dev, upc-prod)
|
||||
- **Environment**: Production (internal use only)
|
||||
- **Clusters**: Multi-cluster (upc-dev, upc-prod) via Kustomize overlays
|
||||
- **Auth**: Disabled for ArgoCD (internal access)
|
||||
- **Backup**: None (cluster rebuildable via GitOps)
|
||||
- **Backup**: Gitea daily backup to S3-compatible storage
|
||||
|
||||
### Known Limitations
|
||||
- No automated backups (yet)
|
||||
- Secret rotation not automated
|
||||
- Multi-cluster limited to upc-dev and upc-prod environments
|
||||
- DNS management is manual
|
||||
|
||||
**Future improvements**: See [Operations Runbook - Disaster Recovery](docs/OPERATIONS-RUNBOOK.md#disaster-recovery)
|
||||
@@ -504,7 +514,7 @@ Internal use only. Not for public distribution.
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2026-03-16
|
||||
**Last Updated**: 2026-04-22
|
||||
**Documentation Version**: 1.0.0
|
||||
|
||||
**🚀 Ready to get started? Check out the [Documentation Index](docs/README.md)!**
|
||||
|
||||
32
_app-of-apps-aks-dev.yaml
Normal file
32
_app-of-apps-aks-dev.yaml
Normal file
@@ -0,0 +1,32 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: monitoring
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "-1"
|
||||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: infrastructure-apps
|
||||
namespace: argocd
|
||||
labels:
|
||||
app.kubernetes.io/name: infrastructure-apps
|
||||
app.kubernetes.io/part-of: platform
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
|
||||
targetRevision: HEAD
|
||||
path: infra/overlays/aks-dev
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: default
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
32
_app-of-apps-aks-prod.yaml
Normal file
32
_app-of-apps-aks-prod.yaml
Normal file
@@ -0,0 +1,32 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: monitoring
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "-1"
|
||||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: infrastructure-apps
|
||||
namespace: argocd
|
||||
labels:
|
||||
app.kubernetes.io/name: infrastructure-apps
|
||||
app.kubernetes.io/part-of: platform
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
|
||||
targetRevision: HEAD
|
||||
path: infra/overlays/aks-prod
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: default
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
32
_app-of-apps-eks-dev.yaml
Normal file
32
_app-of-apps-eks-dev.yaml
Normal file
@@ -0,0 +1,32 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: monitoring
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "-1"
|
||||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: infrastructure-apps
|
||||
namespace: argocd
|
||||
labels:
|
||||
app.kubernetes.io/name: infrastructure-apps
|
||||
app.kubernetes.io/part-of: platform
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
|
||||
targetRevision: HEAD
|
||||
path: infra/overlays/eks-dev
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: default
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
32
_app-of-apps-eks-prod.yaml
Normal file
32
_app-of-apps-eks-prod.yaml
Normal file
@@ -0,0 +1,32 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: monitoring
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "-1"
|
||||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: infrastructure-apps
|
||||
namespace: argocd
|
||||
labels:
|
||||
app.kubernetes.io/name: infrastructure-apps
|
||||
app.kubernetes.io/part-of: platform
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
|
||||
targetRevision: HEAD
|
||||
path: infra/overlays/eks-prod
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: default
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
32
_app-of-apps-gke-dev.yaml
Normal file
32
_app-of-apps-gke-dev.yaml
Normal file
@@ -0,0 +1,32 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: monitoring
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "-1"
|
||||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: infrastructure-apps
|
||||
namespace: argocd
|
||||
labels:
|
||||
app.kubernetes.io/name: infrastructure-apps
|
||||
app.kubernetes.io/part-of: platform
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
|
||||
targetRevision: HEAD
|
||||
path: infra/overlays/gke-dev
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: default
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
32
_app-of-apps-gke-prod.yaml
Normal file
32
_app-of-apps-gke-prod.yaml
Normal file
@@ -0,0 +1,32 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: monitoring
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "-1"
|
||||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: infrastructure-apps
|
||||
namespace: argocd
|
||||
labels:
|
||||
app.kubernetes.io/name: infrastructure-apps
|
||||
app.kubernetes.io/part-of: platform
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
|
||||
targetRevision: HEAD
|
||||
path: infra/overlays/gke-prod
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: default
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
@@ -18,7 +18,7 @@ metadata:
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: git@github.com:fortedigital/sturdy-adventure.git
|
||||
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
|
||||
targetRevision: HEAD
|
||||
path: infra/overlays/upc-prod
|
||||
destination:
|
||||
|
||||
@@ -37,7 +37,7 @@ spec:
|
||||
- $values/infra/values/base/dot-ai-stack-values.yaml
|
||||
- $values/infra/values/upc-dev/dot-ai-stack-values.yaml
|
||||
|
||||
- repoURL: git@github.com:fortedigital/sturdy-adventure.git
|
||||
- repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
|
||||
targetRevision: HEAD
|
||||
ref: values
|
||||
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
---
|
||||
# Namespace must be created first (sync-wave: -1)
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: ts-mcp
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "-1"
|
||||
---
|
||||
# ArgoCD Application syncs last (sync-wave: 11)
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# in case of $'\r': command not found error, run command below first
|
||||
# sed -i 's/\r$//' ./bootstrap.sh
|
||||
|
||||
CLUSTER="${1:?Usage: ./bootstrap.sh <cluster> (upc-dev|upc-prod)}"
|
||||
CLUSTER="${1:?Usage: ./bootstrap.sh <cluster> (upc-dev|upc-prod|aks-dev|aks-prod|eks-dev|eks-prod|gke-dev|gke-prod)}"
|
||||
|
||||
echo "running $0 for cluster: ${CLUSTER}..."
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
# Self-service Keycloak client config for Backstage.
|
||||
# Kyverno clones this to the keycloak namespace, where the
|
||||
# keycloak-client-registrar CronJob processes it and creates
|
||||
# the backstage-oidc-credentials Secret in the backstage namespace.
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: keycloak-client-backstage
|
||||
namespace: backstage
|
||||
labels:
|
||||
keycloak.forteapps.net/client-config: "true"
|
||||
stringData:
|
||||
client.json: |
|
||||
{
|
||||
"clientId": "backstage",
|
||||
"name": "Backstage Developer Portal",
|
||||
"redirectUris": ["https://backstage.forteapps.net/api/auth/oidc/handler/frame"],
|
||||
"webOrigins": ["https://backstage.forteapps.net"],
|
||||
"defaultClientScopes": ["openid", "email", "profile"],
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "email_verified",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-hardcoded-claim-mapper",
|
||||
"config": {
|
||||
"claim.name": "email_verified",
|
||||
"claim.value": "true",
|
||||
"jsonType.label": "boolean",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"userinfo.token.claim": "true"
|
||||
}
|
||||
}
|
||||
],
|
||||
"secret": {
|
||||
"namespace": "backstage",
|
||||
"name": "backstage-oidc-credentials",
|
||||
"keys": {
|
||||
"clientId": "AUTH_OIDC_CLIENT_ID",
|
||||
"clientSecret": "AUTH_OIDC_CLIENT_SECRET"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,17 +57,17 @@ spec:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
mc alias set upcloud "${S3_ENDPOINT}" "${AWS_ACCESS_KEY_ID}" "${AWS_SECRET_ACCESS_KEY}"
|
||||
mc alias set s3 "${S3_ENDPOINT}" "${AWS_ACCESS_KEY_ID}" "${AWS_SECRET_ACCESS_KEY}"
|
||||
|
||||
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
|
||||
KEY="gitea-dump-${TIMESTAMP}.zip"
|
||||
echo "Uploading ${KEY}..."
|
||||
mc cp /backup/gitea-dump.zip "upcloud/${S3_BUCKET}/${KEY}" && \
|
||||
mc cp /backup/gitea-dump.zip "s3/${S3_BUCKET}/${KEY}" && \
|
||||
echo "Upload complete."
|
||||
|
||||
# Prune backups older than 7 days
|
||||
echo "Pruning backups older than 7 days..."
|
||||
mc rm --older-than 7d --force "upcloud/${S3_BUCKET}/" 2>&1 || true
|
||||
mc rm --older-than 7d --force "s3/${S3_BUCKET}/" 2>&1 || true
|
||||
echo "Pruning complete."
|
||||
envFrom:
|
||||
- secretRef:
|
||||
|
||||
41
cluster-resources/policies/label-checker.yaml
Normal file
41
cluster-resources/policies/label-checker.yaml
Normal file
@@ -0,0 +1,41 @@
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: require-labels
|
||||
annotations:
|
||||
policies.kyverno.io/title: Require Labels
|
||||
policies.kyverno.io/category: Best Practices
|
||||
policies.kyverno.io/minversion: 1.6.0
|
||||
policies.kyverno.io/severity: medium
|
||||
policies.kyverno.io/subject: Pod, Label
|
||||
policies.kyverno.io/description: Define and use labels that identify semantic attributes of your application or Deployment. A common set of labels allows tools to work collaboratively, describing objects in a common manner that all tools can understand. The recommended labels describe applications in a way that can be queried. This policy validates that the label `app.kubernetes.io/name` is specified with some value.
|
||||
spec:
|
||||
validationFailureAction: Audit
|
||||
background: true
|
||||
rules:
|
||||
- name: check-for-labels
|
||||
skipBackgroundRequests: true
|
||||
exclude:
|
||||
any:
|
||||
- resources:
|
||||
namespaces:
|
||||
- kube-system
|
||||
- istio-system
|
||||
- argocd
|
||||
- cert-manager
|
||||
- monitoring
|
||||
- secrets
|
||||
- kyverno
|
||||
- trivy-system
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Pod
|
||||
validate:
|
||||
message: The label `app.kubernetes.io/name` is required.
|
||||
allowExistingViolations: true
|
||||
pattern:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: "?*"
|
||||
12
clusters/aks-dev.yaml
Normal file
12
clusters/aks-dev.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
# Cluster config reference — values must match the corresponding overlay files.
|
||||
# Read by bootstrap.sh at install time; NOT auto-propagated to ArgoCD value files.
|
||||
clusterName: dev-aks # → infra/values/aks-dev/argocd-values.yaml (notifications.context.clusterName)
|
||||
domain: example.com # → infra/values/base/gitea-values.yaml, renovate-values.yaml, keycloak-values.yaml (subdomains)
|
||||
argocdDomain: argocd.example.com # → infra/values/aks-dev/argocd-values.yaml (global.domain)
|
||||
grafanaDomain: grafana.example.com # → infra/values/aks-dev/grafana-values.yaml (ingress.hosts)
|
||||
keycloakDomain: id.example.com # → infra/values/aks-dev/keycloak-values.yaml (ingress.hostname)
|
||||
dotaiDomain: kubemcp.example.com # → infra/values/aks-dev/dot-ai-stack-values.yaml (dot-ai.ingress.host) — create if needed
|
||||
dotaiUiDomain: kubemcpui.example.com # → infra/values/aks-dev/dot-ai-stack-values.yaml (dot-ai-ui.ingress.host) — create if needed
|
||||
letsencryptEmail: admin@example.com # → cluster-resources/letsencrypt-issuer.yaml (spec.acme.email)
|
||||
trustedIPs: "10.0.0.0/8,168.63.129.16/32" # → infra/values/aks-dev/traefik-values.yaml (ports.*.trustedIPs) — VNet CIDR + Azure health probe
|
||||
cloudProvider: azure # → determines overlay directory and cloud-specific LB/storage annotations
|
||||
12
clusters/aks-prod.yaml
Normal file
12
clusters/aks-prod.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
# Cluster config reference — values must match the corresponding overlay files.
|
||||
# Read by bootstrap.sh at install time; NOT auto-propagated to ArgoCD value files.
|
||||
clusterName: prod-aks # → infra/values/aks-prod/argocd-values.yaml (notifications.context.clusterName)
|
||||
domain: example.com # → infra/values/base/gitea-values.yaml, renovate-values.yaml, keycloak-values.yaml (subdomains)
|
||||
argocdDomain: argocd.example.com # → infra/values/aks-prod/argocd-values.yaml (global.domain)
|
||||
grafanaDomain: grafana.example.com # → infra/values/aks-prod/grafana-values.yaml (ingress.hosts)
|
||||
keycloakDomain: id.example.com # → infra/values/aks-prod/keycloak-values.yaml (ingress.hostname)
|
||||
dotaiDomain: kubemcp.example.com # → infra/values/aks-prod/dot-ai-stack-values.yaml (dot-ai.ingress.host) — create if needed
|
||||
dotaiUiDomain: kubemcpui.example.com # → infra/values/aks-prod/dot-ai-stack-values.yaml (dot-ai-ui.ingress.host) — create if needed
|
||||
letsencryptEmail: admin@example.com # → cluster-resources/letsencrypt-issuer.yaml (spec.acme.email)
|
||||
trustedIPs: "10.0.0.0/8,168.63.129.16/32" # → infra/values/aks-prod/traefik-values.yaml (ports.*.trustedIPs) — VNet CIDR + Azure health probe
|
||||
cloudProvider: azure # → determines overlay directory and cloud-specific LB/storage annotations
|
||||
12
clusters/eks-dev.yaml
Normal file
12
clusters/eks-dev.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
# Cluster config reference — values must match the corresponding overlay files.
|
||||
# Read by bootstrap.sh at install time; NOT auto-propagated to ArgoCD value files.
|
||||
clusterName: dev-eks # → infra/values/eks-dev/argocd-values.yaml (notifications.context.clusterName)
|
||||
domain: example.com # → infra/values/base/gitea-values.yaml, renovate-values.yaml, keycloak-values.yaml (subdomains)
|
||||
argocdDomain: argocd.example.com # → infra/values/eks-dev/argocd-values.yaml (global.domain)
|
||||
grafanaDomain: grafana.example.com # → infra/values/eks-dev/grafana-values.yaml (ingress.hosts)
|
||||
keycloakDomain: id.example.com # → infra/values/eks-dev/keycloak-values.yaml (ingress.hostname)
|
||||
dotaiDomain: kubemcp.example.com # → infra/values/eks-dev/dot-ai-stack-values.yaml (dot-ai.ingress.host) — create if needed
|
||||
dotaiUiDomain: kubemcpui.example.com # → infra/values/eks-dev/dot-ai-stack-values.yaml (dot-ai-ui.ingress.host) — create if needed
|
||||
letsencryptEmail: admin@example.com # → cluster-resources/letsencrypt-issuer.yaml (spec.acme.email)
|
||||
trustedIPs: "10.0.0.0/8" # → infra/values/eks-dev/traefik-values.yaml (ports.*.trustedIPs) — VPC CIDR
|
||||
cloudProvider: eks # → determines overlay directory and cloud-specific LB/storage annotations
|
||||
12
clusters/eks-prod.yaml
Normal file
12
clusters/eks-prod.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
# Cluster config reference — values must match the corresponding overlay files.
|
||||
# Read by bootstrap.sh at install time; NOT auto-propagated to ArgoCD value files.
|
||||
clusterName: prod-eks # → infra/values/eks-prod/argocd-values.yaml (notifications.context.clusterName)
|
||||
domain: example.com # → infra/values/base/gitea-values.yaml, renovate-values.yaml, keycloak-values.yaml (subdomains)
|
||||
argocdDomain: argocd.example.com # → infra/values/eks-prod/argocd-values.yaml (global.domain)
|
||||
grafanaDomain: grafana.example.com # → infra/values/eks-prod/grafana-values.yaml (ingress.hosts)
|
||||
keycloakDomain: id.example.com # → infra/values/eks-prod/keycloak-values.yaml (ingress.hostname)
|
||||
dotaiDomain: kubemcp.example.com # → infra/values/eks-prod/dot-ai-stack-values.yaml (dot-ai.ingress.host) — create if needed
|
||||
dotaiUiDomain: kubemcpui.example.com # → infra/values/eks-prod/dot-ai-stack-values.yaml (dot-ai-ui.ingress.host) — create if needed
|
||||
letsencryptEmail: admin@example.com # → cluster-resources/letsencrypt-issuer.yaml (spec.acme.email)
|
||||
trustedIPs: "10.0.0.0/8" # → infra/values/eks-prod/traefik-values.yaml (ports.*.trustedIPs) — VPC CIDR
|
||||
cloudProvider: eks # → determines overlay directory and cloud-specific LB/storage annotations
|
||||
12
clusters/gke-dev.yaml
Normal file
12
clusters/gke-dev.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
# Cluster config reference — values must match the corresponding overlay files.
|
||||
# Read by bootstrap.sh at install time; NOT auto-propagated to ArgoCD value files.
|
||||
clusterName: dev-gke # → infra/values/gke-dev/argocd-values.yaml (notifications.context.clusterName)
|
||||
domain: example.com # → infra/values/base/gitea-values.yaml, renovate-values.yaml, keycloak-values.yaml (subdomains)
|
||||
argocdDomain: argocd.example.com # → infra/values/gke-dev/argocd-values.yaml (global.domain)
|
||||
grafanaDomain: grafana.example.com # → infra/values/gke-dev/grafana-values.yaml (ingress.hosts)
|
||||
keycloakDomain: id.example.com # → infra/values/gke-dev/keycloak-values.yaml (ingress.hostname)
|
||||
dotaiDomain: kubemcp.example.com # → infra/values/gke-dev/dot-ai-stack-values.yaml (dot-ai.ingress.host) — create if needed
|
||||
dotaiUiDomain: kubemcpui.example.com # → infra/values/gke-dev/dot-ai-stack-values.yaml (dot-ai-ui.ingress.host) — create if needed
|
||||
letsencryptEmail: admin@example.com # → cluster-resources/letsencrypt-issuer.yaml (spec.acme.email)
|
||||
trustedIPs: "10.0.0.0/8,35.191.0.0/16,130.211.0.0/22" # → infra/values/gke-dev/traefik-values.yaml (ports.*.trustedIPs) — subnet + GCP health checks
|
||||
cloudProvider: gke # → determines overlay directory and cloud-specific LB/storage annotations
|
||||
12
clusters/gke-prod.yaml
Normal file
12
clusters/gke-prod.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
# Cluster config reference — values must match the corresponding overlay files.
|
||||
# Read by bootstrap.sh at install time; NOT auto-propagated to ArgoCD value files.
|
||||
clusterName: prod-gke # → infra/values/gke-prod/argocd-values.yaml (notifications.context.clusterName)
|
||||
domain: example.com # → infra/values/base/gitea-values.yaml, renovate-values.yaml, keycloak-values.yaml (subdomains)
|
||||
argocdDomain: argocd.example.com # → infra/values/gke-prod/argocd-values.yaml (global.domain)
|
||||
grafanaDomain: grafana.example.com # → infra/values/gke-prod/grafana-values.yaml (ingress.hosts)
|
||||
keycloakDomain: id.example.com # → infra/values/gke-prod/keycloak-values.yaml (ingress.hostname)
|
||||
dotaiDomain: kubemcp.example.com # → infra/values/gke-prod/dot-ai-stack-values.yaml (dot-ai.ingress.host) — create if needed
|
||||
dotaiUiDomain: kubemcpui.example.com # → infra/values/gke-prod/dot-ai-stack-values.yaml (dot-ai-ui.ingress.host) — create if needed
|
||||
letsencryptEmail: admin@example.com # → cluster-resources/letsencrypt-issuer.yaml (spec.acme.email)
|
||||
trustedIPs: "10.0.0.0/8,35.191.0.0/16,130.211.0.0/22" # → infra/values/gke-prod/traefik-values.yaml (ports.*.trustedIPs) — subnet + GCP health checks
|
||||
cloudProvider: gke # → determines overlay directory and cloud-specific LB/storage annotations
|
||||
@@ -1,10 +1,12 @@
|
||||
clusterName: dev-fd-no-svg1
|
||||
domain: forteapps.net
|
||||
argocdDomain: argocd.127.0.0.1.nip.io
|
||||
grafanaDomain: grafana.forteapps.net
|
||||
keycloakDomain: id.forteapps.net
|
||||
dotaiDomain: kubemcp.forteapps.net
|
||||
dotaiUiDomain: kubemcpui.forteapps.net
|
||||
letsencryptEmail: danijels@gmail.com
|
||||
trustedIPs: "172.16.1.0/24"
|
||||
cloudProvider: upcloud
|
||||
# Cluster config reference — values must match the corresponding overlay files.
|
||||
# Read by bootstrap.sh at install time; NOT auto-propagated to ArgoCD value files.
|
||||
clusterName: dev-fd-no-svg1 # → infra/values/upc-dev/argocd-values.yaml (notifications.context.clusterName)
|
||||
domain: forteapps.net # → infra/values/base/gitea-values.yaml, renovate-values.yaml, keycloak-values.yaml (subdomains)
|
||||
argocdDomain: argocd.127.0.0.1.nip.io # → infra/values/upc-dev/argocd-values.yaml (global.domain)
|
||||
grafanaDomain: grafana.forteapps.net # → infra/values/upc-dev/grafana-values.yaml (ingress.hosts)
|
||||
keycloakDomain: id.forteapps.net # → infra/values/upc-dev/keycloak-values.yaml (ingress.hostname)
|
||||
dotaiDomain: kubemcp.forteapps.net # → infra/values/upc-dev/dot-ai-stack-values.yaml (dot-ai.ingress.host)
|
||||
dotaiUiDomain: kubemcpui.forteapps.net # → infra/values/upc-dev/dot-ai-stack-values.yaml (dot-ai-ui.ingress.host)
|
||||
letsencryptEmail: danijels@gmail.com # → cluster-resources/letsencrypt-issuer.yaml (spec.acme.email)
|
||||
trustedIPs: "172.16.1.0/24" # → infra/values/upc-dev/traefik-values.yaml (ports.*.trustedIPs)
|
||||
cloudProvider: upcloud # → determines overlay directory and cloud-specific LB/storage annotations
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
clusterName: prod-fd-no-svg1
|
||||
domain: fortedigital.com
|
||||
argocdDomain: argocd.127.0.0.1.nip.io
|
||||
grafanaDomain: grafana.fortedigital.com
|
||||
keycloakDomain: id.fortedigital.com
|
||||
dotaiDomain: kubemcp.fortedigital.com
|
||||
dotaiUiDomain: kubemcpui.fortedigital.com
|
||||
letsencryptEmail: danijel.simeunovic@fortedigital.com
|
||||
trustedIPs: "172.16.1.0/24"
|
||||
cloudProvider: upcloud
|
||||
# Cluster config reference — values must match the corresponding overlay files.
|
||||
# Read by bootstrap.sh at install time; NOT auto-propagated to ArgoCD value files.
|
||||
clusterName: prod-fd-no-svg1 # → infra/values/upc-prod/argocd-values.yaml (notifications.context.clusterName)
|
||||
domain: fortedigital.com # → infra/values/base/gitea-values.yaml, renovate-values.yaml, keycloak-values.yaml (subdomains)
|
||||
argocdDomain: argocd.127.0.0.1.nip.io # → infra/values/upc-prod/argocd-values.yaml (global.domain)
|
||||
grafanaDomain: grafana.fortedigital.com # → infra/values/upc-prod/grafana-values.yaml (ingress.hosts)
|
||||
keycloakDomain: id.fortedigital.com # → infra/values/upc-prod/keycloak-values.yaml (ingress.hostname)
|
||||
dotaiDomain: kubemcp.fortedigital.com # → infra/values/upc-prod/dot-ai-stack-values.yaml (dot-ai.ingress.host)
|
||||
dotaiUiDomain: kubemcpui.fortedigital.com # → infra/values/upc-prod/dot-ai-stack-values.yaml (dot-ai-ui.ingress.host)
|
||||
letsencryptEmail: danijel.simeunovic@fortedigital.com # → cluster-resources/letsencrypt-issuer.yaml (spec.acme.email)
|
||||
trustedIPs: "172.16.1.0/24" # → infra/values/upc-prod/traefik-values.yaml (ports.*.trustedIPs)
|
||||
cloudProvider: upcloud # → determines overlay directory and cloud-specific LB/storage annotations
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
|
||||
## Overview
|
||||
|
||||
This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where Git repositories serve as the single source of truth for both infrastructure and application deployments. The cluster is running on **UpCloud Managed Kubernetes** but is designed to be cloud-agnostic.
|
||||
This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where Git repositories serve as the single source of truth for both infrastructure and application deployments. The cluster setup is **cloud-agnostic**, with ready-to-use configurations for **UpCloud**, **AWS EKS**, **Azure AKS**, and **GCP GKE**.
|
||||
|
||||
### Key Characteristics
|
||||
- **Environment**: Production (internal use only)
|
||||
- **Cluster Type**: Multi-cluster (upc-dev, upc-prod) via Kustomize overlays
|
||||
- **Cluster Type**: Multi-cloud, multi-cluster via Kustomize overlays (UpCloud, AWS, Azure, GCP)
|
||||
- **GitOps Tool**: ArgoCD
|
||||
- **Deployment Pattern**: App-of-Apps
|
||||
- **Secret Management**: Sealed Secrets (kubeseal)
|
||||
@@ -63,7 +63,7 @@ This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where
|
||||
▼
|
||||
┌────────────────────────────────┐
|
||||
│ Kubernetes Clusters │
|
||||
│ (UpCloud: upc-dev, upc-prod) │
|
||||
│ (UpCloud, AWS, Azure, GCP) │
|
||||
│ │
|
||||
│ ┌──────────────────────────┐ │
|
||||
│ │ ArgoCD │ │
|
||||
@@ -131,26 +131,22 @@ launchpad/
|
||||
│ │ ├── renovate.yaml
|
||||
│ │ ├── ... # All other Application manifests
|
||||
│ │ └── secrets.yaml
|
||||
│ ├── overlays/ # Per-cluster overrides
|
||||
│ ├── overlays/ # Per-cluster Kustomize overrides
|
||||
│ │ ├── upc-dev/ # UpCloud Dev (uses base as-is)
|
||||
│ │ └── upc-prod/ # UpCloud Prod (patches value paths)
|
||||
│ │ ├── upc-prod/ # UpCloud Prod (patches value paths)
|
||||
│ │ ├── eks-dev/ # AWS EKS Dev
|
||||
│ │ ├── eks-prod/ # AWS EKS Prod
|
||||
│ │ ├── aks-dev/ # Azure AKS Dev
|
||||
│ │ ├── aks-prod/ # Azure AKS Prod
|
||||
│ │ ├── gke-dev/ # GCP GKE Dev
|
||||
│ │ └── gke-prod/ # GCP GKE Prod
|
||||
│ ├── dashboards/ # Grafana dashboard ConfigMaps
|
||||
│ └── values/ # Helm value overrides for infra
|
||||
│ ├── base/ # Shared values (all clusters)
|
||||
│ │ ├── traefik-values.yaml
|
||||
│ │ ├── keycloak-values.yaml
|
||||
│ │ ├── grafana-values.yaml
|
||||
│ │ ├── prometheus-values.yaml
|
||||
│ │ ├── gitea-values.yaml
|
||||
│ │ └── ...
|
||||
│ ├── upc-dev/ # upc-dev cluster-specific values
|
||||
│ │ ├── traefik-values.yaml
|
||||
│ │ ├── keycloak-values.yaml
|
||||
│ │ └── grafana-values.yaml
|
||||
│ └── upc-prod/ # upc-prod cluster-specific values
|
||||
│ ├── traefik-values.yaml
|
||||
│ ├── keycloak-values.yaml
|
||||
│ └── grafana-values.yaml
|
||||
│ ├── base/ # Cloud-agnostic shared values
|
||||
│ ├── upc-{dev,prod}/ # UpCloud: storage class, LB, pricing
|
||||
│ ├── aws-{dev,prod}/ # AWS: gp3, NLB, CUR pricing
|
||||
│ ├── aks-{dev,prod}/ # Azure: managed-csi-premium, Standard LB
|
||||
│ └── gcp-{dev,prod}/ # GCP: premium-rwo, L4 LB
|
||||
│
|
||||
├── apps/ # Business Application ArgoCD manifests (Kustomize)
|
||||
│ ├── base/ # Base app manifests
|
||||
@@ -287,7 +283,7 @@ app-repository/
|
||||
### The App-of-Apps Pattern
|
||||
|
||||
```
|
||||
_app-of-apps-{upc-dev,upc-prod}.yaml (Root, per cluster)
|
||||
_app-of-apps-{cluster}.yaml (Root, per cluster — e.g. upc-dev, eks-prod, gke-dev)
|
||||
│
|
||||
├── infrastructure-apps (manages infra/)
|
||||
│ ├── cluster-resources-application
|
||||
@@ -377,6 +373,15 @@ patches:
|
||||
value: $values/infra/values/upc-prod/traefik-values.yaml
|
||||
```
|
||||
|
||||
Cloud-specific values (storage classes, load balancer annotations, cost model) are isolated in per-cluster value files. Base values are fully cloud-agnostic:
|
||||
|
||||
| Cloud | Storage Class | Load Balancer | OpenCost Provider |
|
||||
|-------|--------------|---------------|-------------------|
|
||||
| **UpCloud** | `upcloud-block-storage-maxiops` | UpCloud LB (ProxyProtocol v2) | Custom pricing |
|
||||
| **AWS EKS** | `gp3` (EBS CSI) | NLB (ProxyProtocol v2) | AWS CUR |
|
||||
| **Azure AKS** | `managed-csi-premium` | Standard LB (`externalTrafficPolicy: Local`) | Azure Billing API |
|
||||
| **GCP GKE** | `premium-rwo` (PD CSI) | L4 passthrough NLB | GCP Cloud Billing |
|
||||
|
||||
**Benefits**:
|
||||
- Single source of truth for Application definitions
|
||||
- Cluster-specific values isolated per overlay
|
||||
@@ -658,6 +663,6 @@ Notifications include:
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2026-03-16
|
||||
**Last Updated**: 2026-04-22
|
||||
**Maintained By**: Platform Team
|
||||
**Questions?**: Contact #platform-support on Slack
|
||||
|
||||
@@ -37,7 +37,7 @@ Bootstrap a new cluster from scratch:
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
1. **Kubernetes cluster running** (UpCloud or any K8s cluster)
|
||||
1. **Kubernetes cluster running** (UpCloud, AWS EKS, Azure AKS, GCP GKE, or any K8s cluster)
|
||||
2. **kubectl configured** with admin access
|
||||
3. **Repositories cloned** locally
|
||||
|
||||
@@ -54,11 +54,13 @@ kubectl get nodes
|
||||
git clone https://git.forteapps.net/Forte/launchpad
|
||||
cd launchpad
|
||||
|
||||
# 2. Set cluster name (optional)
|
||||
export CLUSTER_NAME="prod-cluster-01"
|
||||
# 2. Run bootstrap script with cluster target
|
||||
# Available clusters: upc-dev, upc-prod, eks-dev, eks-prod,
|
||||
# aks-dev, aks-prod, gke-dev, gke-prod
|
||||
./bootstrap.sh upc-dev
|
||||
|
||||
# 3. Run bootstrap script
|
||||
./bootstrap.sh
|
||||
# Cluster config is loaded from clusters/<cluster>.yaml
|
||||
# (cloudProvider, trustedIPs, domain, etc.)
|
||||
```
|
||||
|
||||
**What Happens:**
|
||||
@@ -1262,13 +1264,21 @@ spec:
|
||||
|
||||
### Backup Strategy
|
||||
|
||||
**Current State**: No automated backups
|
||||
**Current State**: Gitea daily backups to S3-compatible storage
|
||||
|
||||
**What Needs Backup**:
|
||||
- ❌ Cluster state (not backed up - recreate via GitOps)
|
||||
- ❌ Persistent volumes (currently not critical)
|
||||
- ✅ Git repositories (Gitea provides backup)
|
||||
- ⚠️ Secrets (sealed secrets in Git, unseal keys need safekeeping)
|
||||
**What Is Backed Up**:
|
||||
- ✅ Gitea repositories + database: Daily CronJob (`cluster-resources/gitea-backup-cronjob.yaml`) uploads to S3-compatible storage with 7-day retention
|
||||
- ✅ Git repositories: Full cluster config recoverable from Git
|
||||
- ⚠️ Secrets: Sealed secrets in Git; unseal keys need safekeeping
|
||||
|
||||
**What Is NOT Backed Up**:
|
||||
- ❌ Cluster state (recreate via GitOps)
|
||||
- ❌ Other persistent volumes (Prometheus, Loki, Tempo data)
|
||||
|
||||
**Per-cloud backup scripts** (manual restore helpers):
|
||||
- UpCloud/AWS: `scripts/gitea-backup.sh` / `scripts/gitea-backup-eks.sh` (MinIO CLI, S3-compatible)
|
||||
- Azure: `scripts/gitea-backup-aks.sh` (Azure CLI + Blob Storage)
|
||||
- GCP: `scripts/gitea-backup-gke.sh` (gsutil + GCS)
|
||||
|
||||
### Cluster Rebuild
|
||||
|
||||
@@ -1370,6 +1380,9 @@ kubectl get pods -n argocd
|
||||
|
||||
```bash
|
||||
# UpCloud: Upgrade via control panel or CLI
|
||||
# AWS EKS: eksctl upgrade cluster / AWS Console
|
||||
# Azure AKS: az aks upgrade / Azure Portal
|
||||
# GCP GKE: gcloud container clusters upgrade / Cloud Console
|
||||
|
||||
# After upgrade, verify cluster
|
||||
kubectl version
|
||||
@@ -1507,18 +1520,35 @@ git push
|
||||
|
||||
### Multi-Cluster Setup
|
||||
|
||||
The repository supports multiple clusters via Kustomize overlays:
|
||||
The repository supports multiple clusters across multiple clouds via Kustomize overlays:
|
||||
|
||||
**Active clusters:**
|
||||
- **upc-dev** (default): `infra/overlays/upc-dev/` — uses base Applications as-is
|
||||
- **upc-prod**: `infra/overlays/upc-prod/` — patches value file paths from `upc-dev` to `upc-prod`
|
||||
|
||||
Each cluster has its own:
|
||||
- Root app-of-apps file: `_app-of-apps-upc-dev.yaml` / `_app-of-apps-upc-prod.yaml`
|
||||
- Cluster-specific Helm values: `infra/values/upc-dev/` / `infra/values/upc-prod/`
|
||||
- Sealed secrets: `secrets/upc-dev/` (others as needed)
|
||||
- Apps overlay: `apps/overlays/upc-dev/` / `apps/overlays/upc-prod/`
|
||||
**Cloud-ready templates (fill in `clusters/*.yaml` before use):**
|
||||
- **eks-dev** / **eks-prod**: AWS EKS with NLB, gp3 storage, AWS CUR pricing
|
||||
- **aks-dev** / **aks-prod**: Azure AKS with Standard LB, managed-csi-premium storage
|
||||
- **gke-dev** / **gke-prod**: GCP GKE with L4 LB, premium-rwo storage
|
||||
|
||||
To add a new cluster, create a new overlay directory (e.g., `infra/overlays/upc-staging/`) with patches that swap the value file paths.
|
||||
Each cluster has its own:
|
||||
- Root app-of-apps: `_app-of-apps-{cluster}.yaml`
|
||||
- Cluster config: `clusters/{cluster}.yaml` (domain, trustedIPs, cloudProvider)
|
||||
- Kustomize overlay: `infra/overlays/{cluster}/kustomization.yaml`
|
||||
- Helm value overrides: `infra/values/{cluster}/` (traefik, gitea, opencost)
|
||||
- Sealed secrets: `secrets/{cluster}/` (as needed)
|
||||
- Apps overlay: `apps/overlays/{cluster}/`
|
||||
|
||||
Cloud-specific values handled per-cluster:
|
||||
|
||||
| Concern | UpCloud | AWS EKS | Azure AKS | GCP GKE |
|
||||
|---------|---------|---------|-----------|---------|
|
||||
| **Storage class** | `upcloud-block-storage-maxiops` | `gp3` | `managed-csi-premium` | `premium-rwo` |
|
||||
| **Load balancer** | UpCloud LB + ProxyProtocol v2 | NLB + ProxyProtocol v2 | Standard LB + `externalTrafficPolicy: Local` | L4 passthrough NLB |
|
||||
| **Cost monitoring** | Custom pricing | AWS CUR | Azure Billing API | GCP Cloud Billing |
|
||||
| **Backup storage** | UpCloud S3-compat | AWS S3 (native) | Azure Blob Storage | GCS |
|
||||
|
||||
To add a new cluster, create a new overlay directory (e.g., `infra/overlays/eks-staging/`) with patches that swap the value file paths, and a matching `clusters/eks-staging.yaml`.
|
||||
|
||||
### Blue-Green Deployments
|
||||
|
||||
@@ -1661,6 +1691,6 @@ echo "Remember to delete: $SECRET_FILE"
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2026-03-16
|
||||
**Last Updated**: 2026-04-22
|
||||
**Maintained By**: Platform Team
|
||||
**Emergency Contact**: #platform-support on Slack
|
||||
|
||||
@@ -180,7 +180,7 @@ Reference for:
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Kubernetes Clusters (UpCloud: upc-dev, upc-prod) │
|
||||
│ Kubernetes Clusters (UpCloud, AWS, Azure, GCP) │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ Infrastructure: Traefik, Cert-Manager, Kyverno │ │
|
||||
│ ├──────────────────────────────────────────────────────┤ │
|
||||
@@ -194,7 +194,7 @@ Reference for:
|
||||
### Key Technologies
|
||||
|
||||
- **GitOps**: ArgoCD
|
||||
- **Kubernetes**: UpCloud Managed Kubernetes (multi-cluster: upc-dev, upc-prod)
|
||||
- **Kubernetes**: Multi-cloud (UpCloud, AWS EKS, Azure AKS, GCP GKE)
|
||||
- **Ingress**: Traefik v2
|
||||
- **Certificates**: Cert-Manager + Let's Encrypt
|
||||
- **Policies**: Kyverno
|
||||
@@ -299,11 +299,16 @@ docs/
|
||||
## 🔄 Documentation Versions
|
||||
|
||||
**Current Version**: 1.0.0
|
||||
**Last Updated**: 2026-03-16
|
||||
**Last Updated**: 2026-04-22
|
||||
**Maintained By**: Platform Team
|
||||
|
||||
### Changelog
|
||||
|
||||
- **v1.1.0 (2026-04-22)**: Multi-cloud support
|
||||
- Cloud-agnostic base values (storage, LB, pricing moved to per-cluster overlays)
|
||||
- Added AWS EKS, Azure AKS, GCP GKE configurations
|
||||
- Per-cloud backup scripts
|
||||
- Updated all documentation
|
||||
- **v1.0.0 (2026-03-16)**: Initial comprehensive documentation release
|
||||
- GitOps Architecture guide
|
||||
- Developer Onboarding guide
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
- [Kyverno Policies](#kyverno-policies)
|
||||
- [Configuration Reference](#configuration-reference)
|
||||
- [API Endpoints](#api-endpoints)
|
||||
- [Cloud Overlay Pattern](#cloud-overlay-pattern)
|
||||
- [Glossary](#glossary)
|
||||
|
||||
---
|
||||
@@ -19,9 +20,10 @@
|
||||
|
||||
| Component | Value |
|
||||
|-----------|-------|
|
||||
| **Provider** | UpCloud Managed Kubernetes |
|
||||
| **Environment** | Production (internal use) |
|
||||
| **Cluster Count** | Multi-cluster (upc-dev, upc-prod) |
|
||||
| **Provider** | Multi-cloud (UpCloud, AWS EKS, Azure AKS, GCP GKE) |
|
||||
| **Environment** | Dev + Production per cloud |
|
||||
| **Active clusters** | UpCloud (upc-dev, upc-prod) |
|
||||
| **Cloud-ready templates** | EKS, AKS, GKE (dev + prod each) |
|
||||
| **GitOps Tool** | ArgoCD |
|
||||
| **Ingress Controller** | Traefik v2 |
|
||||
| **Certificate Management** | Cert-Manager + Let's Encrypt |
|
||||
@@ -42,7 +44,7 @@ Internet
|
||||
[DNS: *.forteapps.net]
|
||||
│
|
||||
▼
|
||||
[UpCloud LoadBalancer]
|
||||
[Cloud Load Balancer]
|
||||
│
|
||||
▼
|
||||
[Traefik Ingress Controller]
|
||||
@@ -92,16 +94,34 @@ launchpad/
|
||||
│ ├── sealedsecrets.yaml
|
||||
│ ├── secrets.yaml
|
||||
│ ├── renovate.yaml
|
||||
│ ├── base/ # ArgoCD Application manifests (Kustomize base)
|
||||
│ │ ├── gitea.yaml
|
||||
│ │ ├── opencost.yaml
|
||||
│ │ ├── traefik-application.yaml
|
||||
│ │ ├── keycloak.yaml
|
||||
│ │ ├── grafana.yaml
|
||||
│ │ └── ...
|
||||
│ ├── overlays/
|
||||
│ │ └── upc-prod/
|
||||
│ │ └── kustomization.yaml # Patches upc-dev → upc-prod valueFile paths
|
||||
│ └── values/
|
||||
│ ├── argocd-values.yaml
|
||||
│ ├── prometheus-values.yaml
|
||||
│ ├── grafana-values.yaml
|
||||
│ ├── loki-values.yaml
|
||||
│ ├── tempo-values.yaml
|
||||
│ ├── gitea-values.yaml
|
||||
│ ├── gitea-actions-values.yaml
|
||||
│ ├── fluent-bit-values.yaml
|
||||
│ └── renovate-values.yaml
|
||||
│ ├── base/ # Cloud-agnostic Helm values
|
||||
│ │ ├── gitea-values.yaml
|
||||
│ │ ├── opencost-values.yaml
|
||||
│ │ ├── prometheus-values.yaml
|
||||
│ │ └── ...
|
||||
│ ├── upc-dev/ # UpCloud dev overlay values
|
||||
│ │ ├── traefik-values.yaml
|
||||
│ │ ├── keycloak-values.yaml
|
||||
│ │ ├── grafana-values.yaml
|
||||
│ │ ├── gitea-values.yaml
|
||||
│ │ └── opencost-values.yaml
|
||||
│ └── upc-prod/ # UpCloud prod overlay values
|
||||
│ ├── traefik-values.yaml
|
||||
│ ├── keycloak-values.yaml
|
||||
│ ├── grafana-values.yaml
|
||||
│ ├── gitea-values.yaml
|
||||
│ └── opencost-values.yaml
|
||||
│
|
||||
├── apps/ # Business applications
|
||||
│ ├── mcp10x.yaml
|
||||
@@ -128,12 +148,39 @@ launchpad/
|
||||
│ └── auth-sidecar-injector.yaml
|
||||
│
|
||||
├── secrets/ # Application secrets (sealed)
|
||||
│ ├── argocd-mcp-credentials.yaml
|
||||
│ ├── dot-ai-secrets.yaml
|
||||
│ ├── gitea-credentials-sealed.yaml
|
||||
│ ├── gitea-runner-token-sealed.yaml
|
||||
│ ├── mcp10x-credentials-sealed.yaml
|
||||
│ └── musicman-credentials.yaml
|
||||
│ ├── base/ # All SealedSecrets (shared across clouds)
|
||||
│ │ ├── kustomization.yaml
|
||||
│ │ ├── argocd-forte-helm-secret-sealed.yaml
|
||||
│ │ ├── argocd-mcp-credentials.yaml
|
||||
│ │ ├── argocdmcp-auth-oidc-sealed.yaml
|
||||
│ │ ├── dot-ai-secrets.yaml
|
||||
│ │ ├── forte10x-app-credentials-sealed.yaml
|
||||
│ │ ├── gitea-backup-s3-sealed.yaml
|
||||
│ │ ├── gitea-credentials-sealed.yaml
|
||||
│ │ ├── gitea-runner-token-sealed.yaml
|
||||
│ │ ├── gitea-smtp-secret-sealed.yaml
|
||||
│ │ ├── keycloak-credentials-sealed.yaml
|
||||
│ │ ├── musicman-auth-oidc-sealed.yaml
|
||||
│ │ ├── musicman-credentials.yaml
|
||||
│ │ └── renovate-env-sealed.yaml
|
||||
│ └── overlays/ # Per-cloud overlays (reference base)
|
||||
│ ├── aks-dev/kustomization.yaml
|
||||
│ ├── aks-prod/kustomization.yaml
|
||||
│ ├── eks-dev/kustomization.yaml
|
||||
│ ├── eks-prod/kustomization.yaml
|
||||
│ ├── gke-dev/kustomization.yaml
|
||||
│ ├── gke-prod/kustomization.yaml
|
||||
│ ├── upc-dev/kustomization.yaml
|
||||
│ └── upc-prod/kustomization.yaml
|
||||
│
|
||||
├── scripts/ # Operational helper scripts
|
||||
│ ├── gitea-backup.sh # S3 backup helper (list/download)
|
||||
│ ├── gitea-restore.sh
|
||||
│ └── backup/ # Per-cloud backup reference scripts
|
||||
│ ├── s3-minio.sh # S3-compatible (UpCloud, MinIO, Wasabi)
|
||||
│ ├── aws-s3.sh # Native AWS S3
|
||||
│ ├── azure-blob.sh # Azure Blob Storage
|
||||
│ └── gcp-gcs.sh # GCP Cloud Storage
|
||||
│
|
||||
├── private/ # Local-only (Git-ignored)
|
||||
│ ├── *.yaml
|
||||
@@ -686,6 +733,10 @@ spec:
|
||||
**Chart**: `sealed-secrets/sealed-secrets-controller`
|
||||
**Namespace**: `kube-system`
|
||||
|
||||
**Directory Structure**: `secrets/base/` contains all SealedSecrets with a `kustomization.yaml`. Per-cloud overlays in `secrets/overlays/<cloud>/` reference the base via Kustomize. The ArgoCD `secrets` Application points to the active overlay (e.g., `secrets/overlays/upc-dev`), and `infra/overlays/upc-prod` patches the path to `secrets/overlays/upc-prod`.
|
||||
|
||||
To add cloud-specific secrets, create a new SealedSecret in the overlay directory and add it to the overlay's `kustomization.yaml`.
|
||||
|
||||
**Public Certificate**:
|
||||
```bash
|
||||
kubeseal --fetch-cert \
|
||||
@@ -965,83 +1016,6 @@ ignore:
|
||||
- Check Gitea Actions tab for workflow run status and logs
|
||||
- Monitor Anthropic usage dashboard for token consumption
|
||||
|
||||
### Backstage / RHDH (Developer Portal)
|
||||
|
||||
**Chart**: `backstage` (RHDH — Red Hat Developer Hub)
|
||||
**Version**: `5.8.0`
|
||||
**Namespace**: `backstage`
|
||||
**Helm Repo**: `https://redhat-developer.github.io/rhdh-chart`
|
||||
**Image**: `quay.io/rhdh-community/rhdh:next`
|
||||
|
||||
**Purpose**: Internal developer portal where teams register and broadcast themselves, their applications, APIs, and systems. Provides a unified catalog, templates, and documentation hub.
|
||||
|
||||
**Why RHDH over vanilla Backstage**: Ships 27+ plugins pre-bundled (ArgoCD, Kubernetes, Keycloak, GitHub, GitLab, Jira, SonarQube, Tekton, Jenkins, Quay, and more). Supports dynamic plugin installation at runtime — no image rebuilds needed.
|
||||
|
||||
**Configuration** (`infra/values/base/backstage-values.yaml`):
|
||||
- OpenShift Route disabled (`route.enabled: false`) — uses Traefik ingress instead
|
||||
- PostgreSQL subchart enabled for persistence (2Gi)
|
||||
- SecurityContext configured for vanilla Kubernetes (non-OpenShift)
|
||||
- Traefik ingress with `websecure` entrypoint
|
||||
- App title: "Forte Developer Portal"
|
||||
- Dynamic plugins: loads `dynamic-plugins.default.yaml` (all 27+ bundled plugins)
|
||||
- Catalog rules: Component, System, API, Resource, Location, Template, Group, User, Domain
|
||||
|
||||
**Authentication** (Keycloak OIDC):
|
||||
- Uses the self-service registrar pattern (see [Keycloak Client Registrar](#keycloak-client-registrar))
|
||||
- Config Secret: `cluster-resources/backstage-keycloak-client-config.yaml`
|
||||
- Kyverno clones it → registrar creates `backstage-oidc-credentials` Secret in `backstage` namespace
|
||||
- Credential keys: `AUTH_OIDC_CLIENT_ID`, `AUTH_OIDC_CLIENT_SECRET` (loaded via `extraEnvVarsSecrets`)
|
||||
- Redirect URI: `https://backstage.forteapps.net/api/auth/oidc/handler/frame`
|
||||
- Sign-in resolver: `emailMatchingUserEntityProfileEmail`
|
||||
|
||||
**Catalog Discovery** (Gitea):
|
||||
- Auto-discovers `catalog-info.yaml` from all repos in the `Forte` organization
|
||||
- Scans every 30 minutes via the Gitea catalog provider plugin
|
||||
- Gitea SCM integration configured for URL resolution (`git.forteapps.net`)
|
||||
|
||||
**Catalog Registration**:
|
||||
Teams register services by adding a `catalog-info.yaml` to their repo root:
|
||||
```yaml
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: my-service
|
||||
description: My service description
|
||||
annotations:
|
||||
backstage.io/source-location: url:https://git.forteapps.net/Forte/my-service
|
||||
spec:
|
||||
type: service
|
||||
lifecycle: production
|
||||
owner: team-name
|
||||
```
|
||||
|
||||
Repos with this file are auto-discovered — no manual registration needed.
|
||||
|
||||
**Dynamic Plugins**:
|
||||
Add plugins at runtime via `global.dynamic.plugins` in values — no image rebuild:
|
||||
```yaml
|
||||
global:
|
||||
dynamic:
|
||||
plugins:
|
||||
- package: "@scope/my-plugin@1.0.0"
|
||||
integrity: "sha512-..."
|
||||
```
|
||||
|
||||
**Per-cluster Configuration** (`infra/values/upc-dev/backstage-values.yaml`):
|
||||
```yaml
|
||||
global:
|
||||
host: backstage.forteapps.net
|
||||
upstream:
|
||||
backstage:
|
||||
appConfig:
|
||||
app:
|
||||
baseUrl: https://backstage.forteapps.net
|
||||
backend:
|
||||
baseUrl: https://backstage.forteapps.net
|
||||
ingress:
|
||||
host: backstage.forteapps.net
|
||||
```
|
||||
|
||||
### Keycloak Client Registrar
|
||||
|
||||
**Type**: CronJob (deployed via Keycloak Helm chart `extraDeploy`)
|
||||
@@ -1155,6 +1129,33 @@ kubectl get secret keycloak-client-<app> -n keycloak -o jsonpath='{.metadata.ann
|
||||
|
||||
**See**: [Developer Guide - Adding a New Keycloak Client](DEVELOPER-GUIDE.md#adding-a-new-keycloak-client)
|
||||
|
||||
### Karpor
|
||||
|
||||
**Chart**: `karpor` from `https://kusionstack.github.io/charts`
|
||||
**Version**: 0.7.6 (app v0.6.4)
|
||||
**Namespace**: `karpor`
|
||||
**Sync Wave**: 1
|
||||
|
||||
**Purpose**: Kubernetes visualization and intelligence tool. Provides cross-cluster resource search, compliance checking, and topology visualization. Gives platform engineers a unified view of all cluster resources and their relationships.
|
||||
|
||||
**Architecture** (4 components):
|
||||
- **Server** — main Karpor API/UI (port 7443)
|
||||
- **Syncer** — syncs cluster state into the search index
|
||||
- **ElasticSearch** — search backend for resource indexing
|
||||
- **etcd** — persistent key-value store (10Gi PVC)
|
||||
|
||||
**Configuration** (`infra/values/base/karpor-values.yaml`):
|
||||
- `namespaceEnabled: false` — ArgoCD manages namespace creation
|
||||
- Default resource limits tuned for small clusters
|
||||
- ElasticSearch: 2 CPU / 4Gi memory (the heaviest component)
|
||||
- AI features available but not enabled (requires `server.ai.authToken` + backend config)
|
||||
|
||||
**Access**: Port-forward to reach the UI:
|
||||
```bash
|
||||
kubectl port-forward svc/karpor-release-server -n karpor 7443:7443
|
||||
# Open https://localhost:7443
|
||||
```
|
||||
|
||||
### Renovate
|
||||
|
||||
**Chart**: `renovate` (OCI: `ghcr.io/renovatebot/charts`)
|
||||
@@ -1652,14 +1653,22 @@ Recommended resource allocation:
|
||||
|
||||
### Storage Classes
|
||||
|
||||
Default storage class used: **UpCloud default** (varies by provider)
|
||||
Storage classes are cloud-specific and configured in per-cluster value overrides (`infra/values/{cluster}/gitea-values.yaml`):
|
||||
|
||||
| Cloud | Storage Class | Driver |
|
||||
|-------|--------------|--------|
|
||||
| **UpCloud** | `upcloud-block-storage-maxiops` | UpCloud CSI |
|
||||
| **AWS EKS** | `gp3` | EBS CSI |
|
||||
| **Azure AKS** | `managed-csi-premium` | Azure Disk CSI |
|
||||
| **GCP GKE** | `premium-rwo` | PD CSI |
|
||||
|
||||
```yaml
|
||||
# Example: base values omit storageClass (set in per-cluster overlay)
|
||||
persistence:
|
||||
enabled: true
|
||||
storageClass: "" # Uses default
|
||||
accessMode: ReadWriteOnce
|
||||
size: 5Gi
|
||||
# storageClass set by infra/values/{cluster}/gitea-values.yaml
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1723,6 +1732,88 @@ POST /loki/api/v1/push
|
||||
|
||||
---
|
||||
|
||||
## Cloud Overlay Pattern
|
||||
|
||||
### Overview
|
||||
|
||||
Cloud-specific configuration (StorageClass, LoadBalancer annotations, pricing models, etc.) lives in per-cloud overlay value files, **not** in `base/`. Adding a new cloud provider only requires a new overlay directory — no base changes.
|
||||
|
||||
### Supported Clouds
|
||||
|
||||
| Cloud | Dev overlay | Prod overlay | StorageClass | LB type |
|
||||
|-------|-----------|-------------|-------------|---------|
|
||||
| **UpCloud** | `upc-dev` | `upc-prod` | `upcloud-block-storage-maxiops` | UpCloud LB (proxy protocol v2) |
|
||||
| **Azure AKS** | `aks-dev` | `aks-prod` | `managed-csi-premium` | Azure LB |
|
||||
| **AWS EKS** | `eks-dev` | `eks-prod` | `gp3` | AWS NLB (proxy protocol) |
|
||||
| **GCP GKE** | `gke-dev` | `gke-prod` | `premium-rwo` | GCP NEG |
|
||||
|
||||
Bootstrap any cluster with: `./bootstrap.sh <cluster>` (e.g., `./bootstrap.sh aks-dev`)
|
||||
|
||||
### How It Works
|
||||
|
||||
Each ArgoCD Application uses **multi-source Helm values** with two value files:
|
||||
|
||||
```yaml
|
||||
# infra/base/gitea.yaml (example)
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/infra/values/base/gitea-values.yaml # [0] cloud-agnostic
|
||||
- $values/infra/values/upc-dev/gitea-values.yaml # [1] cloud-specific (default: upc-dev)
|
||||
```
|
||||
|
||||
The `upc-prod` Kustomize overlay patches index `[1]` to swap the cloud-specific file:
|
||||
|
||||
```yaml
|
||||
# infra/overlays/upc-prod/kustomization.yaml
|
||||
- target:
|
||||
kind: Application
|
||||
name: gitea
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/upc-prod/gitea-values.yaml
|
||||
```
|
||||
|
||||
### Components Using Cloud Overlays
|
||||
|
||||
| Component | Cloud-specific config | Overlay value file |
|
||||
|-----------|----------------------|-------------------|
|
||||
| **Traefik** | LB annotations, proxy protocol IPs | `traefik-values.yaml` |
|
||||
| **Keycloak** | Hostname, TLS settings | `keycloak-values.yaml` |
|
||||
| **Grafana** | Hostname, datasource URLs | `grafana-values.yaml` |
|
||||
| **Gitea** | StorageClass (persistence + PostgreSQL) | `gitea-values.yaml` |
|
||||
| **OpenCost** | Custom pricing model (CPU/RAM/storage rates) | `opencost-values.yaml` |
|
||||
|
||||
### Backup CronJob
|
||||
|
||||
The `gitea-backup` CronJob uses a generic `s3` alias for `minio/mc`. The actual endpoint and credentials come from the `gitea-backup-s3` Sealed Secret, which is per-cloud. Reference scripts for different cloud providers are in `scripts/backup/`:
|
||||
|
||||
| Script | Provider | Tool |
|
||||
|--------|----------|------|
|
||||
| `s3-minio.sh` | S3-compatible (UpCloud, MinIO, Wasabi) | `minio/mc` |
|
||||
| `aws-s3.sh` | AWS S3 | `aws` CLI |
|
||||
| `azure-blob.sh` | Azure Blob Storage | `az` CLI |
|
||||
| `gcp-gcs.sh` | GCP Cloud Storage | `gsutil` |
|
||||
|
||||
### Adding a New Cloud Provider
|
||||
|
||||
To add support for a new cloud (e.g., `oci-dev` for Oracle Cloud):
|
||||
|
||||
1. **Cluster config**: `clusters/oci-dev.yaml` — clusterName, domain, trustedIPs, cloudProvider
|
||||
2. **Overlay value files** in `infra/values/oci-dev/`:
|
||||
- `traefik-values.yaml` — LB annotations, proxy protocol config
|
||||
- `keycloak-values.yaml` — hostname
|
||||
- `grafana-values.yaml` — hostname
|
||||
- `gitea-values.yaml` — `storageClass` for persistence + PostgreSQL
|
||||
- `opencost-values.yaml` — pricing model or cloud billing integration
|
||||
3. **Kustomize overlay**: `infra/overlays/oci-dev/kustomization.yaml` — patch `valueFiles[1]` for each Application
|
||||
4. **App-of-apps**: `_app-of-apps-oci-dev.yaml` — points to `infra/overlays/oci-dev`
|
||||
5. **Secrets overlay**: `secrets/overlays/oci-dev/kustomization.yaml` — references `../../base`, add cloud-specific SealedSecrets if needed
|
||||
6. **Secrets patch**: Add patch to `infra/overlays/oci-dev/kustomization.yaml` to swap secrets path to `secrets/overlays/oci-dev`
|
||||
7. **Bootstrap**: `./bootstrap.sh oci-dev`
|
||||
|
||||
---
|
||||
|
||||
## Glossary
|
||||
|
||||
### Terms
|
||||
@@ -1855,6 +1946,6 @@ team: platform
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2026-04-16
|
||||
**Last Updated**: 2026-04-22
|
||||
**Maintained By**: Platform Team
|
||||
**Version**: 1.0.0
|
||||
|
||||
@@ -22,6 +22,7 @@ spec:
|
||||
releaseName: gitea
|
||||
valueFiles:
|
||||
- $values/infra/values/base/gitea-values.yaml
|
||||
- $values/infra/values/upc-dev/gitea-values.yaml
|
||||
|
||||
- repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
|
||||
targetRevision: HEAD
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: backstage
|
||||
name: karpor
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "1"
|
||||
labels:
|
||||
app.kubernetes.io/name: backstage
|
||||
app.kubernetes.io/name: karpor
|
||||
app.kubernetes.io/part-of: developer-portal
|
||||
app.kubernetes.io/managed-by: argocd
|
||||
finalizers:
|
||||
@@ -15,14 +15,13 @@ spec:
|
||||
project: default
|
||||
|
||||
sources:
|
||||
- repoURL: https://redhat-developer.github.io/rhdh-chart
|
||||
chart: backstage
|
||||
targetRevision: "5.8.0"
|
||||
- repoURL: https://kusionstack.github.io/charts
|
||||
chart: karpor
|
||||
targetRevision: "0.7.6"
|
||||
helm:
|
||||
releaseName: backstage
|
||||
releaseName: karpor
|
||||
valueFiles:
|
||||
- $values/infra/values/base/backstage-values.yaml
|
||||
- $values/infra/values/upc-dev/backstage-values.yaml
|
||||
- $values/infra/values/base/karpor-values.yaml
|
||||
|
||||
- repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
|
||||
targetRevision: HEAD
|
||||
@@ -30,7 +29,7 @@ spec:
|
||||
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: backstage
|
||||
namespace: karpor
|
||||
|
||||
syncPolicy:
|
||||
automated:
|
||||
@@ -41,3 +40,9 @@ spec:
|
||||
- CreateNamespace=true
|
||||
- Validate=true
|
||||
- ServerSideApply=true
|
||||
|
||||
ignoreDifferences:
|
||||
- group: apps
|
||||
kind: StatefulSet
|
||||
jsonPointers:
|
||||
- /spec/volumeClaimTemplates
|
||||
@@ -22,4 +22,4 @@ resources:
|
||||
- tempo.yaml
|
||||
- grafana-dashboards.yaml
|
||||
- network-policies-application.yaml
|
||||
- backstage.yaml
|
||||
- karpor.yaml
|
||||
|
||||
@@ -22,8 +22,9 @@ spec:
|
||||
releaseName: opencost
|
||||
valueFiles:
|
||||
- $values/infra/values/base/opencost-values.yaml
|
||||
- $values/infra/values/upc-dev/opencost-values.yaml
|
||||
|
||||
- repoURL: git@github.com:fortedigital/sturdy-adventure.git
|
||||
- repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
|
||||
targetRevision: HEAD
|
||||
ref: values
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
|
||||
path: secrets/upc-dev
|
||||
path: secrets/overlays/upc-dev
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: secrets
|
||||
|
||||
@@ -31,7 +31,7 @@ spec:
|
||||
- $values/infra/values/base/traefik-values.yaml
|
||||
- $values/infra/values/upc-dev/traefik-values.yaml
|
||||
|
||||
- repoURL: git@github.com:fortedigital/sturdy-adventure.git
|
||||
- repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
|
||||
targetRevision: HEAD
|
||||
ref: values
|
||||
|
||||
|
||||
68
infra/overlays/aks-dev/kustomization.yaml
Normal file
68
infra/overlays/aks-dev/kustomization.yaml
Normal file
@@ -0,0 +1,68 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- ../../base
|
||||
|
||||
patches:
|
||||
# Traefik: swap upc-dev → aks-dev
|
||||
- target:
|
||||
kind: Application
|
||||
name: traefik
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/aks-dev/traefik-values.yaml
|
||||
|
||||
# Keycloak: swap upc-dev → aks-dev
|
||||
- target:
|
||||
kind: Application
|
||||
name: keycloak
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/aks-dev/keycloak-values.yaml
|
||||
|
||||
# Grafana: swap upc-dev → aks-dev
|
||||
- target:
|
||||
kind: Application
|
||||
name: grafana
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/aks-dev/grafana-values.yaml
|
||||
|
||||
# Gitea: swap upc-dev → aks-dev
|
||||
- target:
|
||||
kind: Application
|
||||
name: gitea
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/aks-dev/gitea-values.yaml
|
||||
|
||||
# OpenCost: swap upc-dev → aks-dev
|
||||
- target:
|
||||
kind: Application
|
||||
name: opencost
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/aks-dev/opencost-values.yaml
|
||||
|
||||
# Secrets: change path to aks-dev
|
||||
- target:
|
||||
kind: Application
|
||||
name: secrets
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/source/path
|
||||
value: secrets/aks-dev
|
||||
|
||||
# Enterprise-apps: point to aks-dev overlay
|
||||
- target:
|
||||
kind: Application
|
||||
name: enterprise-apps
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/source/path
|
||||
value: apps/overlays/aks-dev
|
||||
68
infra/overlays/aks-prod/kustomization.yaml
Normal file
68
infra/overlays/aks-prod/kustomization.yaml
Normal file
@@ -0,0 +1,68 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- ../../base
|
||||
|
||||
patches:
|
||||
# Traefik: swap upc-dev → aks-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: traefik
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/aks-prod/traefik-values.yaml
|
||||
|
||||
# Keycloak: swap upc-dev → aks-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: keycloak
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/aks-prod/keycloak-values.yaml
|
||||
|
||||
# Grafana: swap upc-dev → aks-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: grafana
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/aks-prod/grafana-values.yaml
|
||||
|
||||
# Gitea: swap upc-dev → aks-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: gitea
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/aks-prod/gitea-values.yaml
|
||||
|
||||
# OpenCost: swap upc-dev → aks-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: opencost
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/aks-prod/opencost-values.yaml
|
||||
|
||||
# Secrets: change path to aks-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: secrets
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/source/path
|
||||
value: secrets/aks-prod
|
||||
|
||||
# Enterprise-apps: point to aks-prod overlay
|
||||
- target:
|
||||
kind: Application
|
||||
name: enterprise-apps
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/source/path
|
||||
value: apps/overlays/aks-prod
|
||||
68
infra/overlays/eks-dev/kustomization.yaml
Normal file
68
infra/overlays/eks-dev/kustomization.yaml
Normal file
@@ -0,0 +1,68 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- ../../base
|
||||
|
||||
patches:
|
||||
# Traefik: swap upc-dev → eks-dev
|
||||
- target:
|
||||
kind: Application
|
||||
name: traefik
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/eks-dev/traefik-values.yaml
|
||||
|
||||
# Keycloak: swap upc-dev → eks-dev
|
||||
- target:
|
||||
kind: Application
|
||||
name: keycloak
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/eks-dev/keycloak-values.yaml
|
||||
|
||||
# Grafana: swap upc-dev → eks-dev
|
||||
- target:
|
||||
kind: Application
|
||||
name: grafana
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/eks-dev/grafana-values.yaml
|
||||
|
||||
# Gitea: swap upc-dev → eks-dev
|
||||
- target:
|
||||
kind: Application
|
||||
name: gitea
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/eks-dev/gitea-values.yaml
|
||||
|
||||
# OpenCost: swap upc-dev → eks-dev
|
||||
- target:
|
||||
kind: Application
|
||||
name: opencost
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/eks-dev/opencost-values.yaml
|
||||
|
||||
# Secrets: change path to eks-dev
|
||||
- target:
|
||||
kind: Application
|
||||
name: secrets
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/source/path
|
||||
value: secrets/eks-dev
|
||||
|
||||
# Enterprise-apps: point to eks-dev overlay
|
||||
- target:
|
||||
kind: Application
|
||||
name: enterprise-apps
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/source/path
|
||||
value: apps/overlays/eks-dev
|
||||
68
infra/overlays/eks-prod/kustomization.yaml
Normal file
68
infra/overlays/eks-prod/kustomization.yaml
Normal file
@@ -0,0 +1,68 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- ../../base
|
||||
|
||||
patches:
|
||||
# Traefik: swap upc-dev → eks-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: traefik
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/eks-prod/traefik-values.yaml
|
||||
|
||||
# Keycloak: swap upc-dev → eks-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: keycloak
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/eks-prod/keycloak-values.yaml
|
||||
|
||||
# Grafana: swap upc-dev → eks-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: grafana
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/eks-prod/grafana-values.yaml
|
||||
|
||||
# Gitea: swap upc-dev → eks-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: gitea
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/eks-prod/gitea-values.yaml
|
||||
|
||||
# OpenCost: swap upc-dev → eks-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: opencost
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/eks-prod/opencost-values.yaml
|
||||
|
||||
# Secrets: change path to eks-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: secrets
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/source/path
|
||||
value: secrets/eks-prod
|
||||
|
||||
# Enterprise-apps: point to eks-prod overlay
|
||||
- target:
|
||||
kind: Application
|
||||
name: enterprise-apps
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/source/path
|
||||
value: apps/overlays/eks-prod
|
||||
68
infra/overlays/gke-dev/kustomization.yaml
Normal file
68
infra/overlays/gke-dev/kustomization.yaml
Normal file
@@ -0,0 +1,68 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- ../../base
|
||||
|
||||
patches:
|
||||
# Traefik: swap upc-dev → gke-dev
|
||||
- target:
|
||||
kind: Application
|
||||
name: traefik
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/gke-dev/traefik-values.yaml
|
||||
|
||||
# Keycloak: swap upc-dev → gke-dev
|
||||
- target:
|
||||
kind: Application
|
||||
name: keycloak
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/gke-dev/keycloak-values.yaml
|
||||
|
||||
# Grafana: swap upc-dev → gke-dev
|
||||
- target:
|
||||
kind: Application
|
||||
name: grafana
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/gke-dev/grafana-values.yaml
|
||||
|
||||
# Gitea: swap upc-dev → gke-dev
|
||||
- target:
|
||||
kind: Application
|
||||
name: gitea
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/gke-dev/gitea-values.yaml
|
||||
|
||||
# OpenCost: swap upc-dev → gke-dev
|
||||
- target:
|
||||
kind: Application
|
||||
name: opencost
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/gke-dev/opencost-values.yaml
|
||||
|
||||
# Secrets: change path to gke-dev
|
||||
- target:
|
||||
kind: Application
|
||||
name: secrets
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/source/path
|
||||
value: secrets/gke-dev
|
||||
|
||||
# Enterprise-apps: point to gke-dev overlay
|
||||
- target:
|
||||
kind: Application
|
||||
name: enterprise-apps
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/source/path
|
||||
value: apps/overlays/gke-dev
|
||||
68
infra/overlays/gke-prod/kustomization.yaml
Normal file
68
infra/overlays/gke-prod/kustomization.yaml
Normal file
@@ -0,0 +1,68 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- ../../base
|
||||
|
||||
patches:
|
||||
# Traefik: swap upc-dev → gke-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: traefik
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/gke-prod/traefik-values.yaml
|
||||
|
||||
# Keycloak: swap upc-dev → gke-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: keycloak
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/gke-prod/keycloak-values.yaml
|
||||
|
||||
# Grafana: swap upc-dev → gke-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: grafana
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/gke-prod/grafana-values.yaml
|
||||
|
||||
# Gitea: swap upc-dev → gke-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: gitea
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/gke-prod/gitea-values.yaml
|
||||
|
||||
# OpenCost: swap upc-dev → gke-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: opencost
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/gke-prod/opencost-values.yaml
|
||||
|
||||
# Secrets: change path to gke-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: secrets
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/source/path
|
||||
value: secrets/gke-prod
|
||||
|
||||
# Enterprise-apps: point to gke-prod overlay
|
||||
- target:
|
||||
kind: Application
|
||||
name: enterprise-apps
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/source/path
|
||||
value: apps/overlays/gke-prod
|
||||
@@ -31,6 +31,24 @@ patches:
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/upc-prod/grafana-values.yaml
|
||||
|
||||
# Gitea: swap upc-dev → upc-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: gitea
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/upc-prod/gitea-values.yaml
|
||||
|
||||
# OpenCost: swap upc-dev → upc-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: opencost
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/upc-prod/opencost-values.yaml
|
||||
|
||||
# Secrets: change path to upc-prod
|
||||
- target:
|
||||
kind: Application
|
||||
@@ -38,7 +56,7 @@ patches:
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/source/path
|
||||
value: secrets/upc-prod
|
||||
value: secrets/overlays/upc-prod
|
||||
|
||||
# Enterprise-apps: point to upc-prod overlay
|
||||
- target:
|
||||
|
||||
7
infra/values/aks-dev/gitea-values.yaml
Normal file
7
infra/values/aks-dev/gitea-values.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
# AKS-specific: Azure managed disk storage class
|
||||
persistence:
|
||||
storageClass: managed-csi-premium
|
||||
postgresql:
|
||||
primary:
|
||||
persistence:
|
||||
storageClass: managed-csi-premium
|
||||
4
infra/values/aks-dev/grafana-values.yaml
Normal file
4
infra/values/aks-dev/grafana-values.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
# AKS-specific: Grafana hostname
|
||||
ingress:
|
||||
hosts:
|
||||
- grafana.forteapps.net
|
||||
3
infra/values/aks-dev/keycloak-values.yaml
Normal file
3
infra/values/aks-dev/keycloak-values.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
# AKS-specific: Keycloak hostname
|
||||
ingress:
|
||||
hostname: id.forteapps.net
|
||||
8
infra/values/aks-dev/opencost-values.yaml
Normal file
8
infra/values/aks-dev/opencost-values.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
# AKS-specific: Azure pricing via Cloud Billing API
|
||||
opencost:
|
||||
exporter:
|
||||
cloudProviderApiKey: ""
|
||||
customPricing:
|
||||
enabled: false
|
||||
azure:
|
||||
secretName: opencost-azure-billing
|
||||
11
infra/values/aks-dev/traefik-values.yaml
Normal file
11
infra/values/aks-dev/traefik-values.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
# AKS-specific: Azure Load Balancer for Traefik
|
||||
service:
|
||||
annotations:
|
||||
service.beta.kubernetes.io/azure-load-balancer-health-probe-request-path: /ping
|
||||
ports:
|
||||
web:
|
||||
forwardedHeaders:
|
||||
trustedIPs: "10.0.0.0/8"
|
||||
websecure:
|
||||
forwardedHeaders:
|
||||
trustedIPs: "10.0.0.0/8"
|
||||
7
infra/values/aks-prod/gitea-values.yaml
Normal file
7
infra/values/aks-prod/gitea-values.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
# AKS-specific: Azure managed disk storage class (prod)
|
||||
persistence:
|
||||
storageClass: managed-csi-premium
|
||||
postgresql:
|
||||
primary:
|
||||
persistence:
|
||||
storageClass: managed-csi-premium
|
||||
4
infra/values/aks-prod/grafana-values.yaml
Normal file
4
infra/values/aks-prod/grafana-values.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
# AKS-specific: Grafana hostname (prod)
|
||||
ingress:
|
||||
hosts:
|
||||
- grafana.fortedigital.com
|
||||
3
infra/values/aks-prod/keycloak-values.yaml
Normal file
3
infra/values/aks-prod/keycloak-values.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
# AKS-specific: Keycloak hostname (prod)
|
||||
ingress:
|
||||
hostname: id.fortedigital.com
|
||||
8
infra/values/aks-prod/opencost-values.yaml
Normal file
8
infra/values/aks-prod/opencost-values.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
# AKS-specific: Azure pricing via Cloud Billing API (prod)
|
||||
opencost:
|
||||
exporter:
|
||||
cloudProviderApiKey: ""
|
||||
customPricing:
|
||||
enabled: false
|
||||
azure:
|
||||
secretName: opencost-azure-billing
|
||||
12
infra/values/aks-prod/traefik-values.yaml
Normal file
12
infra/values/aks-prod/traefik-values.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
# AKS-specific: Azure Load Balancer for Traefik (prod)
|
||||
service:
|
||||
annotations:
|
||||
service.beta.kubernetes.io/azure-load-balancer-health-probe-request-path: /ping
|
||||
service.beta.kubernetes.io/azure-load-balancer-internal: "false"
|
||||
ports:
|
||||
web:
|
||||
forwardedHeaders:
|
||||
trustedIPs: "10.0.0.0/8"
|
||||
websecure:
|
||||
forwardedHeaders:
|
||||
trustedIPs: "10.0.0.0/8"
|
||||
@@ -1,150 +0,0 @@
|
||||
# Red Hat Developer Hub (RHDH) - Internal Developer Portal
|
||||
# Helm chart: https://github.com/redhat-developer/rhdh-chart
|
||||
# Includes 27+ plugins out of the box: ArgoCD, Kubernetes, Keycloak,
|
||||
# GitHub, GitLab, Jira, SonarQube, Tekton, Jenkins, and more.
|
||||
|
||||
global:
|
||||
auth:
|
||||
backend:
|
||||
enabled: true
|
||||
dynamic:
|
||||
includes:
|
||||
- dynamic-plugins.default.yaml
|
||||
plugins: []
|
||||
|
||||
# Disable OpenShift Route (not on OpenShift)
|
||||
route:
|
||||
enabled: false
|
||||
|
||||
upstream:
|
||||
backstage:
|
||||
image:
|
||||
registry: quay.io
|
||||
repository: rhdh-community/rhdh
|
||||
tag: next
|
||||
|
||||
podSecurityContext:
|
||||
runAsUser: 1001
|
||||
runAsGroup: 1001
|
||||
fsGroup: 1001
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 1Gi
|
||||
|
||||
extraEnvVarsSecrets:
|
||||
- backstage-oidc-credentials
|
||||
- backstage-session-secret
|
||||
|
||||
appConfig:
|
||||
app:
|
||||
title: "Forte Backstage"
|
||||
baseUrl: http://localhost:7007
|
||||
|
||||
backend:
|
||||
baseUrl: http://localhost:7007
|
||||
|
||||
# -- Keycloak OIDC authentication
|
||||
signInPage: oidc
|
||||
auth:
|
||||
session:
|
||||
secret: ${AUTH_SESSION_SECRET}
|
||||
environment: production
|
||||
providers:
|
||||
oidc:
|
||||
production:
|
||||
metadataUrl: https://id.forteapps.net/realms/forte/.well-known/openid-configuration
|
||||
clientId: ${AUTH_OIDC_CLIENT_ID}
|
||||
clientSecret: ${AUTH_OIDC_CLIENT_SECRET}
|
||||
prompt: auto
|
||||
# Allow login before User entities exist in the catalog.
|
||||
# Remove once org data is populated.
|
||||
dangerouslyAllowSignInWithoutUserInCatalog: true
|
||||
signIn:
|
||||
resolvers:
|
||||
- resolver: emailMatchingUserEntityProfileEmail
|
||||
|
||||
# -- Gitea SCM integration (for catalog URL resolution)
|
||||
integrations:
|
||||
gitea:
|
||||
- host: git.forteapps.net
|
||||
|
||||
# -- Software catalog
|
||||
catalog:
|
||||
rules:
|
||||
- allow:
|
||||
- Component
|
||||
- System
|
||||
- API
|
||||
- Resource
|
||||
- Location
|
||||
- Template
|
||||
- Group
|
||||
- User
|
||||
- Domain
|
||||
providers:
|
||||
# Auto-import users and groups from Keycloak
|
||||
keycloakOrg:
|
||||
default:
|
||||
baseUrl: https://id.forteapps.net
|
||||
realm: forte
|
||||
clientId: ${AUTH_OIDC_CLIENT_ID}
|
||||
clientSecret: ${AUTH_OIDC_CLIENT_SECRET}
|
||||
schedule:
|
||||
frequency: { minutes: 30 }
|
||||
timeout: { minutes: 3 }
|
||||
initialDelay: { seconds: 15 }
|
||||
# Auto-discover catalog-info.yaml from all Forte org repos
|
||||
gitea:
|
||||
forte:
|
||||
organization: Forte
|
||||
host: git.forteapps.net
|
||||
catalogPath: catalog-info.yaml
|
||||
schedule:
|
||||
frequency: { minutes: 30 }
|
||||
timeout: { minutes: 3 }
|
||||
locations:
|
||||
# Backstage's own org data (bootstrap teams, systems, domains)
|
||||
# - type: url
|
||||
# target: https://git.forteapps.net/Forte/backstage-catalog/raw/branch/main/org.yaml
|
||||
# rules:
|
||||
# - allow: [Group, User, System, Domain]
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
className: traefik
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
tls:
|
||||
enabled: true
|
||||
secretName: backstage-tls
|
||||
|
||||
postgresql:
|
||||
enabled: true
|
||||
auth:
|
||||
# Fixed passwords prevent Helm from regenerating the Secret on
|
||||
# each sync, which would mismatch with the PVC-persisted data.
|
||||
password: backstage-db-pw
|
||||
postgresPassword: backstage-admin-pw
|
||||
primary:
|
||||
persistence:
|
||||
enabled: true
|
||||
size: 2Gi
|
||||
podSecurityContext:
|
||||
enabled: true
|
||||
fsGroup: 26
|
||||
runAsUser: 26
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 250m
|
||||
memory: 512Mi
|
||||
volumePermissions:
|
||||
enabled: false
|
||||
@@ -130,7 +130,6 @@ persistence:
|
||||
size: 10Gi
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClass: upcloud-block-storage-maxiops
|
||||
|
||||
# -- Recreate strategy to avoid Multi-Attach errors with RWO volumes
|
||||
strategy:
|
||||
@@ -156,7 +155,6 @@ postgresql:
|
||||
persistence:
|
||||
enabled: true
|
||||
size: 8Gi
|
||||
storageClass: upcloud-block-storage-maxiops
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
|
||||
44
infra/values/base/karpor-values.yaml
Normal file
44
infra/values/base/karpor-values.yaml
Normal file
@@ -0,0 +1,44 @@
|
||||
# Karpor - Kubernetes Visualization & Intelligence Tool
|
||||
# Helm chart: https://github.com/KusionStack/charts/tree/master/charts/karpor
|
||||
|
||||
# Let the ArgoCD Application manage the namespace
|
||||
namespaceEnabled: false
|
||||
|
||||
server:
|
||||
replicas: 1
|
||||
port: 7443
|
||||
resources:
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 1Gi
|
||||
|
||||
syncer:
|
||||
replicas: 1
|
||||
port: 7443
|
||||
resources:
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 1Gi
|
||||
|
||||
elasticsearch:
|
||||
replicas: 1
|
||||
port: 9200
|
||||
resources:
|
||||
requests:
|
||||
cpu: 500m
|
||||
memory: 2Gi
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 4Gi
|
||||
|
||||
etcd:
|
||||
replicas: 1
|
||||
port: 2379
|
||||
persistence:
|
||||
size: 5Gi
|
||||
@@ -116,12 +116,12 @@ extraDeploy:
|
||||
metadata:
|
||||
name: keycloak-client-registrar
|
||||
rules:
|
||||
- apiGroups: [ "" ]
|
||||
resources: [ "secrets" ]
|
||||
verbs: [ "get", "list", "create", "update", "patch" ]
|
||||
- apiGroups: [ "" ]
|
||||
resources: [ "namespaces" ]
|
||||
verbs: [ "get", "list" ]
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
verbs: ["get", "list", "create", "update", "patch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["namespaces"]
|
||||
verbs: ["get", "list"]
|
||||
|
||||
# -- ClusterRoleBinding for the registrar ServiceAccount
|
||||
- apiVersion: rbac.authorization.k8s.io/v1
|
||||
@@ -158,7 +158,7 @@ extraDeploy:
|
||||
containers:
|
||||
- name: registrar
|
||||
image: alpine:3.20
|
||||
command: [ "/bin/sh", "-c" ]
|
||||
command: ["/bin/sh", "-c"]
|
||||
args:
|
||||
- |
|
||||
set -e
|
||||
|
||||
@@ -10,18 +10,8 @@ opencost:
|
||||
serviceName: prometheus-server
|
||||
namespaceName: monitoring
|
||||
port: 80
|
||||
customPricing:
|
||||
enabled: true
|
||||
provider: custom
|
||||
costModel:
|
||||
description: "UpCloud 4-node cluster pricing"
|
||||
CPU: "5.86"
|
||||
RAM: "1.46"
|
||||
GPU: "0"
|
||||
storage: "0.34"
|
||||
zoneNetworkEgress: "0"
|
||||
regionNetworkEgress: "0"
|
||||
internetNetworkEgress: "0"
|
||||
# Cloud-specific pricing is in per-cluster value overrides
|
||||
# (e.g. infra/values/upc-dev/opencost-values.yaml)
|
||||
ui:
|
||||
enabled: false
|
||||
service:
|
||||
|
||||
7
infra/values/eks-dev/gitea-values.yaml
Normal file
7
infra/values/eks-dev/gitea-values.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
# EKS-specific: gp3 storage class
|
||||
persistence:
|
||||
storageClass: gp3
|
||||
postgresql:
|
||||
primary:
|
||||
persistence:
|
||||
storageClass: gp3
|
||||
4
infra/values/eks-dev/grafana-values.yaml
Normal file
4
infra/values/eks-dev/grafana-values.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
# EKS-specific: Grafana hostname
|
||||
ingress:
|
||||
hosts:
|
||||
- grafana.forteapps.net
|
||||
3
infra/values/eks-dev/keycloak-values.yaml
Normal file
3
infra/values/eks-dev/keycloak-values.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
# EKS-specific: Keycloak hostname
|
||||
ingress:
|
||||
hostname: id.forteapps.net
|
||||
11
infra/values/eks-dev/opencost-values.yaml
Normal file
11
infra/values/eks-dev/opencost-values.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
# EKS-specific: AWS pricing via Cost and Usage Report
|
||||
opencost:
|
||||
exporter:
|
||||
cloudProviderApiKey: ""
|
||||
customPricing:
|
||||
enabled: false
|
||||
aws:
|
||||
spot_data_region: ""
|
||||
spot_data_bucket: ""
|
||||
spot_data_prefix: ""
|
||||
account_id: ""
|
||||
17
infra/values/eks-dev/traefik-values.yaml
Normal file
17
infra/values/eks-dev/traefik-values.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
# EKS-specific: AWS NLB for Traefik
|
||||
service:
|
||||
annotations:
|
||||
service.beta.kubernetes.io/aws-load-balancer-type: nlb
|
||||
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
|
||||
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"
|
||||
ports:
|
||||
web:
|
||||
proxyProtocol:
|
||||
trustedIPs: "10.0.0.0/8"
|
||||
forwardedHeaders:
|
||||
trustedIPs: "10.0.0.0/8"
|
||||
websecure:
|
||||
proxyProtocol:
|
||||
trustedIPs: "10.0.0.0/8"
|
||||
forwardedHeaders:
|
||||
trustedIPs: "10.0.0.0/8"
|
||||
7
infra/values/eks-prod/gitea-values.yaml
Normal file
7
infra/values/eks-prod/gitea-values.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
# EKS-specific: gp3 storage class (prod)
|
||||
persistence:
|
||||
storageClass: gp3
|
||||
postgresql:
|
||||
primary:
|
||||
persistence:
|
||||
storageClass: gp3
|
||||
4
infra/values/eks-prod/grafana-values.yaml
Normal file
4
infra/values/eks-prod/grafana-values.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
# EKS-specific: Grafana hostname (prod)
|
||||
ingress:
|
||||
hosts:
|
||||
- grafana.fortedigital.com
|
||||
3
infra/values/eks-prod/keycloak-values.yaml
Normal file
3
infra/values/eks-prod/keycloak-values.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
# EKS-specific: Keycloak hostname (prod)
|
||||
ingress:
|
||||
hostname: id.fortedigital.com
|
||||
11
infra/values/eks-prod/opencost-values.yaml
Normal file
11
infra/values/eks-prod/opencost-values.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
# EKS-specific: AWS pricing via Cost and Usage Report (prod)
|
||||
opencost:
|
||||
exporter:
|
||||
cloudProviderApiKey: ""
|
||||
customPricing:
|
||||
enabled: false
|
||||
aws:
|
||||
spot_data_region: ""
|
||||
spot_data_bucket: ""
|
||||
spot_data_prefix: ""
|
||||
account_id: ""
|
||||
18
infra/values/eks-prod/traefik-values.yaml
Normal file
18
infra/values/eks-prod/traefik-values.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
# EKS-specific: AWS NLB for Traefik (prod)
|
||||
service:
|
||||
annotations:
|
||||
service.beta.kubernetes.io/aws-load-balancer-type: nlb
|
||||
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
|
||||
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"
|
||||
service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
|
||||
ports:
|
||||
web:
|
||||
proxyProtocol:
|
||||
trustedIPs: "10.0.0.0/8"
|
||||
forwardedHeaders:
|
||||
trustedIPs: "10.0.0.0/8"
|
||||
websecure:
|
||||
proxyProtocol:
|
||||
trustedIPs: "10.0.0.0/8"
|
||||
forwardedHeaders:
|
||||
trustedIPs: "10.0.0.0/8"
|
||||
7
infra/values/gke-dev/gitea-values.yaml
Normal file
7
infra/values/gke-dev/gitea-values.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
# GKE-specific: SSD persistent disk storage class
|
||||
persistence:
|
||||
storageClass: premium-rwo
|
||||
postgresql:
|
||||
primary:
|
||||
persistence:
|
||||
storageClass: premium-rwo
|
||||
4
infra/values/gke-dev/grafana-values.yaml
Normal file
4
infra/values/gke-dev/grafana-values.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
# GKE-specific: Grafana hostname
|
||||
ingress:
|
||||
hosts:
|
||||
- grafana.forteapps.net
|
||||
3
infra/values/gke-dev/keycloak-values.yaml
Normal file
3
infra/values/gke-dev/keycloak-values.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
# GKE-specific: Keycloak hostname
|
||||
ingress:
|
||||
hostname: id.forteapps.net
|
||||
10
infra/values/gke-dev/opencost-values.yaml
Normal file
10
infra/values/gke-dev/opencost-values.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
# GKE-specific: GCP pricing via BigQuery billing export
|
||||
opencost:
|
||||
exporter:
|
||||
cloudProviderApiKey: ""
|
||||
customPricing:
|
||||
enabled: false
|
||||
google:
|
||||
key: ""
|
||||
project_id: ""
|
||||
billing_account: ""
|
||||
12
infra/values/gke-dev/traefik-values.yaml
Normal file
12
infra/values/gke-dev/traefik-values.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
# GKE-specific: Google Cloud Load Balancer for Traefik
|
||||
service:
|
||||
annotations:
|
||||
cloud.google.com/neg: '{"ingress":true}'
|
||||
networking.gke.io/load-balancer-type: External
|
||||
ports:
|
||||
web:
|
||||
forwardedHeaders:
|
||||
trustedIPs: "10.0.0.0/8"
|
||||
websecure:
|
||||
forwardedHeaders:
|
||||
trustedIPs: "10.0.0.0/8"
|
||||
7
infra/values/gke-prod/gitea-values.yaml
Normal file
7
infra/values/gke-prod/gitea-values.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
# GKE-specific: SSD persistent disk storage class (prod)
|
||||
persistence:
|
||||
storageClass: premium-rwo
|
||||
postgresql:
|
||||
primary:
|
||||
persistence:
|
||||
storageClass: premium-rwo
|
||||
4
infra/values/gke-prod/grafana-values.yaml
Normal file
4
infra/values/gke-prod/grafana-values.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
# GKE-specific: Grafana hostname (prod)
|
||||
ingress:
|
||||
hosts:
|
||||
- grafana.fortedigital.com
|
||||
3
infra/values/gke-prod/keycloak-values.yaml
Normal file
3
infra/values/gke-prod/keycloak-values.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
# GKE-specific: Keycloak hostname (prod)
|
||||
ingress:
|
||||
hostname: id.fortedigital.com
|
||||
10
infra/values/gke-prod/opencost-values.yaml
Normal file
10
infra/values/gke-prod/opencost-values.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
# GKE-specific: GCP pricing via BigQuery billing export (prod)
|
||||
opencost:
|
||||
exporter:
|
||||
cloudProviderApiKey: ""
|
||||
customPricing:
|
||||
enabled: false
|
||||
google:
|
||||
key: ""
|
||||
project_id: ""
|
||||
billing_account: ""
|
||||
12
infra/values/gke-prod/traefik-values.yaml
Normal file
12
infra/values/gke-prod/traefik-values.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
# GKE-specific: Google Cloud Load Balancer for Traefik (prod)
|
||||
service:
|
||||
annotations:
|
||||
cloud.google.com/neg: '{"ingress":true}'
|
||||
networking.gke.io/load-balancer-type: External
|
||||
ports:
|
||||
web:
|
||||
forwardedHeaders:
|
||||
trustedIPs: "10.0.0.0/8"
|
||||
websecure:
|
||||
forwardedHeaders:
|
||||
trustedIPs: "10.0.0.0/8"
|
||||
@@ -1,12 +0,0 @@
|
||||
global:
|
||||
host: backstage.forteapps.net
|
||||
|
||||
upstream:
|
||||
backstage:
|
||||
appConfig:
|
||||
app:
|
||||
baseUrl: https://backstage.forteapps.net
|
||||
backend:
|
||||
baseUrl: https://backstage.forteapps.net
|
||||
ingress:
|
||||
host: backstage.forteapps.net
|
||||
7
infra/values/upc-dev/gitea-values.yaml
Normal file
7
infra/values/upc-dev/gitea-values.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
# UpCloud storage class for Gitea and its embedded PostgreSQL
|
||||
persistence:
|
||||
storageClass: upcloud-block-storage-maxiops
|
||||
postgresql:
|
||||
primary:
|
||||
persistence:
|
||||
storageClass: upcloud-block-storage-maxiops
|
||||
15
infra/values/upc-dev/opencost-values.yaml
Normal file
15
infra/values/upc-dev/opencost-values.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
# UpCloud custom pricing (no native OpenCost integration)
|
||||
opencost:
|
||||
exporter:
|
||||
customPricing:
|
||||
enabled: true
|
||||
provider: custom
|
||||
costModel:
|
||||
description: "UpCloud 4-node cluster pricing"
|
||||
CPU: "5.86"
|
||||
RAM: "1.46"
|
||||
GPU: "0"
|
||||
storage: "0.34"
|
||||
zoneNetworkEgress: "0"
|
||||
regionNetworkEgress: "0"
|
||||
internetNetworkEgress: "0"
|
||||
7
infra/values/upc-prod/gitea-values.yaml
Normal file
7
infra/values/upc-prod/gitea-values.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
# UpCloud storage class for Gitea and its embedded PostgreSQL
|
||||
persistence:
|
||||
storageClass: upcloud-block-storage-maxiops
|
||||
postgresql:
|
||||
primary:
|
||||
persistence:
|
||||
storageClass: upcloud-block-storage-maxiops
|
||||
15
infra/values/upc-prod/opencost-values.yaml
Normal file
15
infra/values/upc-prod/opencost-values.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
# UpCloud custom pricing (no native OpenCost integration)
|
||||
opencost:
|
||||
exporter:
|
||||
customPricing:
|
||||
enabled: true
|
||||
provider: custom
|
||||
costModel:
|
||||
description: "UpCloud 4-node cluster pricing"
|
||||
CPU: "5.86"
|
||||
RAM: "1.46"
|
||||
GPU: "0"
|
||||
storage: "0.34"
|
||||
zoneNetworkEgress: "0"
|
||||
regionNetworkEgress: "0"
|
||||
internetNetworkEgress: "0"
|
||||
23
scripts/backup/aws-s3.sh
Normal file
23
scripts/backup/aws-s3.sh
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# AWS S3 backup upload (native AWS CLI)
|
||||
# Uses: aws cli v2
|
||||
# Env: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION, S3_BUCKET
|
||||
|
||||
BACKUP_FILE="${1:?Usage: $0 <backup-file>}"
|
||||
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
|
||||
KEY="gitea-dump-${TIMESTAMP}.zip"
|
||||
|
||||
echo "Uploading ${KEY}..."
|
||||
aws s3 cp "$BACKUP_FILE" "s3://${S3_BUCKET}/${KEY}"
|
||||
echo "Upload complete."
|
||||
|
||||
# Prune backups older than 7 days
|
||||
echo "Pruning backups older than 7 days..."
|
||||
CUTOFF=$(date -d '7 days ago' +%Y-%m-%dT%H:%M:%S 2>/dev/null || date -v-7d +%Y-%m-%dT%H:%M:%S)
|
||||
aws s3api list-objects-v2 --bucket "${S3_BUCKET}" --query "Contents[?LastModified<'${CUTOFF}'].Key" --output text \
|
||||
| tr '\t' '\n' \
|
||||
| while read -r key; do
|
||||
[ -n "$key" ] && aws s3 rm "s3://${S3_BUCKET}/${key}" && echo "Deleted: ${key}"
|
||||
done
|
||||
echo "Pruning complete."
|
||||
36
scripts/backup/azure-blob.sh
Normal file
36
scripts/backup/azure-blob.sh
Normal file
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# Azure Blob Storage backup upload
|
||||
# Uses: az cli
|
||||
# Env: AZURE_STORAGE_ACCOUNT, AZURE_STORAGE_KEY, AZURE_CONTAINER
|
||||
|
||||
BACKUP_FILE="${1:?Usage: $0 <backup-file>}"
|
||||
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
|
||||
KEY="gitea-dump-${TIMESTAMP}.zip"
|
||||
|
||||
echo "Uploading ${KEY}..."
|
||||
az storage blob upload \
|
||||
--account-name "${AZURE_STORAGE_ACCOUNT}" \
|
||||
--account-key "${AZURE_STORAGE_KEY}" \
|
||||
--container-name "${AZURE_CONTAINER}" \
|
||||
--name "${KEY}" \
|
||||
--file "$BACKUP_FILE" \
|
||||
--overwrite
|
||||
echo "Upload complete."
|
||||
|
||||
# Prune backups older than 7 days
|
||||
echo "Pruning backups older than 7 days..."
|
||||
CUTOFF=$(date -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-7d +%Y-%m-%dT%H:%M:%SZ)
|
||||
az storage blob list \
|
||||
--account-name "${AZURE_STORAGE_ACCOUNT}" \
|
||||
--account-key "${AZURE_STORAGE_KEY}" \
|
||||
--container-name "${AZURE_CONTAINER}" \
|
||||
--query "[?properties.lastModified<'${CUTOFF}'].name" -o tsv \
|
||||
| while read -r name; do
|
||||
[ -n "$name" ] && az storage blob delete \
|
||||
--account-name "${AZURE_STORAGE_ACCOUNT}" \
|
||||
--account-key "${AZURE_STORAGE_KEY}" \
|
||||
--container-name "${AZURE_CONTAINER}" \
|
||||
--name "$name" && echo "Deleted: ${name}"
|
||||
done
|
||||
echo "Pruning complete."
|
||||
26
scripts/backup/gcp-gcs.sh
Normal file
26
scripts/backup/gcp-gcs.sh
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# GCP Cloud Storage backup upload
|
||||
# Uses: gsutil (gcloud SDK)
|
||||
# Env: GCS_BUCKET (e.g. gs://my-bucket)
|
||||
|
||||
BACKUP_FILE="${1:?Usage: $0 <backup-file>}"
|
||||
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
|
||||
KEY="gitea-dump-${TIMESTAMP}.zip"
|
||||
|
||||
echo "Uploading ${KEY}..."
|
||||
gsutil cp "$BACKUP_FILE" "${GCS_BUCKET}/${KEY}"
|
||||
echo "Upload complete."
|
||||
|
||||
# Prune backups older than 7 days — GCS lifecycle rules are preferred,
|
||||
# but this works as a manual fallback
|
||||
echo "Pruning backups older than 7 days..."
|
||||
CUTOFF=$(date -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-7d +%Y-%m-%dT%H:%M:%SZ)
|
||||
gsutil ls -l "${GCS_BUCKET}/" \
|
||||
| grep 'gitea-dump-' \
|
||||
| while read -r size date name; do
|
||||
if [[ "$date" < "$CUTOFF" ]]; then
|
||||
gsutil rm "$name" && echo "Deleted: ${name}"
|
||||
fi
|
||||
done
|
||||
echo "Pruning complete."
|
||||
20
scripts/backup/s3-minio.sh
Normal file
20
scripts/backup/s3-minio.sh
Normal file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# S3-compatible backup upload (UpCloud Objects, MinIO, Wasabi, etc.)
|
||||
# Uses: minio/mc
|
||||
# Env: S3_ENDPOINT, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, S3_BUCKET
|
||||
|
||||
BACKUP_FILE="${1:?Usage: $0 <backup-file>}"
|
||||
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
|
||||
KEY="gitea-dump-${TIMESTAMP}.zip"
|
||||
|
||||
mc alias set s3 "${S3_ENDPOINT}" "${AWS_ACCESS_KEY_ID}" "${AWS_SECRET_ACCESS_KEY}"
|
||||
|
||||
echo "Uploading ${KEY}..."
|
||||
mc cp "$BACKUP_FILE" "s3/${S3_BUCKET}/${KEY}"
|
||||
echo "Upload complete."
|
||||
|
||||
# Prune backups older than 7 days
|
||||
echo "Pruning backups older than 7 days..."
|
||||
mc rm --older-than 7d --force "s3/${S3_BUCKET}/" 2>&1 || true
|
||||
echo "Pruning complete."
|
||||
100
scripts/gitea-backup-aks.sh
Normal file
100
scripts/gitea-backup-aks.sh
Normal file
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Gitea backup helper for Azure Blob Storage
|
||||
# Uses the gitea-backup-aks secret in the gitea namespace
|
||||
# Required secret keys:
|
||||
# AZURE_STORAGE_ACCOUNT — storage account name
|
||||
# AZURE_STORAGE_KEY — storage account key
|
||||
# AZURE_CONTAINER — blob container name
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/gitea-backup-aks.sh list # list all backups
|
||||
# ./scripts/gitea-backup-aks.sh download <filename> # download a backup
|
||||
# ./scripts/gitea-backup-aks.sh download latest # download the most recent backup
|
||||
|
||||
NAMESPACE="gitea"
|
||||
SECRET="gitea-backup-aks"
|
||||
IMAGE="mcr.microsoft.com/azure-cli:latest"
|
||||
POD_NAME="gitea-backup-helper"
|
||||
|
||||
cleanup() {
|
||||
kubectl -n "$NAMESPACE" delete pod "$POD_NAME" --ignore-not-found --grace-period=0 > /dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
az_run() {
|
||||
cleanup
|
||||
kubectl -n "$NAMESPACE" run "$POD_NAME" --restart=Never \
|
||||
--image="$IMAGE" \
|
||||
--overrides="{
|
||||
\"spec\":{\"containers\":[{
|
||||
\"name\":\"$POD_NAME\",
|
||||
\"image\":\"$IMAGE\",
|
||||
\"env\":[{\"name\":\"HOME\",\"value\":\"/tmp\"}],
|
||||
\"command\":[\"sh\",\"-c\",\"$1\"],
|
||||
\"envFrom\":[{\"secretRef\":{\"name\":\"$SECRET\"}}]
|
||||
}]}
|
||||
}" > /dev/null 2>&1
|
||||
|
||||
kubectl -n "$NAMESPACE" wait --for=jsonpath='{.status.phase}'=Succeeded "pod/$POD_NAME" --timeout=120s > /dev/null 2>&1
|
||||
kubectl -n "$NAMESPACE" logs "$POD_NAME"
|
||||
cleanup
|
||||
}
|
||||
|
||||
case "${1:-help}" in
|
||||
list)
|
||||
echo "Listing backups..."
|
||||
az_run 'az storage blob list --account-name ${AZURE_STORAGE_ACCOUNT} --account-key ${AZURE_STORAGE_KEY} --container-name ${AZURE_CONTAINER} --output table --query "[].{Name:name, Size:properties.contentLength, Modified:properties.lastModified}"'
|
||||
;;
|
||||
|
||||
download)
|
||||
FILE="${2:?Usage: $0 download <filename|latest>}"
|
||||
|
||||
if [ "$FILE" = "latest" ]; then
|
||||
echo "Finding latest backup..."
|
||||
FILE=$(az_run 'az storage blob list --account-name ${AZURE_STORAGE_ACCOUNT} --account-key ${AZURE_STORAGE_KEY} --container-name ${AZURE_CONTAINER} --query "sort_by([], &properties.lastModified)[-1].name" -o tsv' | tr -d '[:space:]')
|
||||
if [ -z "$FILE" ]; then
|
||||
echo "No backups found."
|
||||
exit 1
|
||||
fi
|
||||
echo "Latest: $FILE"
|
||||
fi
|
||||
|
||||
echo "Downloading $FILE..."
|
||||
cleanup
|
||||
kubectl -n "$NAMESPACE" run "$POD_NAME" --restart=Never \
|
||||
--image="$IMAGE" \
|
||||
--overrides="{
|
||||
\"spec\":{\"containers\":[{
|
||||
\"name\":\"$POD_NAME\",
|
||||
\"image\":\"$IMAGE\",
|
||||
\"env\":[{\"name\":\"HOME\",\"value\":\"/tmp\"}],
|
||||
\"command\":[\"sh\",\"-c\",\"sleep 300\"],
|
||||
\"envFrom\":[{\"secretRef\":{\"name\":\"$SECRET\"}}]
|
||||
}]}
|
||||
}" > /dev/null 2>&1
|
||||
|
||||
kubectl -n "$NAMESPACE" wait --for=condition=Ready "pod/$POD_NAME" --timeout=60s > /dev/null 2>&1
|
||||
|
||||
echo "Saving to ./$FILE ..."
|
||||
kubectl -n "$NAMESPACE" exec "$POD_NAME" -- \
|
||||
az storage blob download \
|
||||
--account-name "\${AZURE_STORAGE_ACCOUNT}" \
|
||||
--account-key "\${AZURE_STORAGE_KEY}" \
|
||||
--container-name "\${AZURE_CONTAINER}" \
|
||||
--name "$FILE" \
|
||||
--file /dev/stdout 2>/dev/null > "./$FILE"
|
||||
cleanup
|
||||
|
||||
echo "Downloaded: ./$FILE"
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Gitea backup helper (Azure Blob Storage)"
|
||||
echo ""
|
||||
echo "Usage:"
|
||||
echo " $0 list List all backups in Azure Blob"
|
||||
echo " $0 download <filename> Download a specific backup"
|
||||
echo " $0 download latest Download the most recent backup"
|
||||
;;
|
||||
esac
|
||||
94
scripts/gitea-backup-eks.sh
Normal file
94
scripts/gitea-backup-eks.sh
Normal file
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Gitea backup helper for AWS S3
|
||||
# Uses the gitea-backup-s3 secret in the gitea namespace
|
||||
# (same secret schema: S3_ENDPOINT, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, S3_BUCKET)
|
||||
#
|
||||
# For AWS, S3_ENDPOINT is typically https://s3.<region>.amazonaws.com
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/gitea-backup-eks.sh list # list all backups
|
||||
# ./scripts/gitea-backup-eks.sh download <filename> # download a backup to current dir
|
||||
# ./scripts/gitea-backup-eks.sh download latest # download the most recent backup
|
||||
|
||||
NAMESPACE="gitea"
|
||||
SECRET="gitea-backup-s3"
|
||||
IMAGE="minio/mc:latest"
|
||||
POD_NAME="gitea-backup-helper"
|
||||
ALIAS_CMD='mc alias set s3 ${S3_ENDPOINT} ${AWS_ACCESS_KEY_ID} ${AWS_SECRET_ACCESS_KEY} > /dev/null'
|
||||
|
||||
cleanup() {
|
||||
kubectl -n "$NAMESPACE" delete pod "$POD_NAME" --ignore-not-found --grace-period=0 > /dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
mc_run() {
|
||||
cleanup
|
||||
kubectl -n "$NAMESPACE" run "$POD_NAME" --restart=Never \
|
||||
--image="$IMAGE" \
|
||||
--overrides="{
|
||||
\"spec\":{\"containers\":[{
|
||||
\"name\":\"$POD_NAME\",
|
||||
\"image\":\"$IMAGE\",
|
||||
\"env\":[{\"name\":\"HOME\",\"value\":\"/tmp\"}],
|
||||
\"command\":[\"sh\",\"-c\",\"${ALIAS_CMD}; $1\"],
|
||||
\"envFrom\":[{\"secretRef\":{\"name\":\"$SECRET\"}}]
|
||||
}]}
|
||||
}" > /dev/null 2>&1
|
||||
|
||||
kubectl -n "$NAMESPACE" wait --for=jsonpath='{.status.phase}'=Succeeded "pod/$POD_NAME" --timeout=120s > /dev/null 2>&1
|
||||
kubectl -n "$NAMESPACE" logs "$POD_NAME"
|
||||
cleanup
|
||||
}
|
||||
|
||||
case "${1:-help}" in
|
||||
list)
|
||||
echo "Listing backups..."
|
||||
mc_run 'mc ls s3/${S3_BUCKET}/'
|
||||
;;
|
||||
|
||||
download)
|
||||
FILE="${2:?Usage: $0 download <filename|latest>}"
|
||||
|
||||
if [ "$FILE" = "latest" ]; then
|
||||
echo "Finding latest backup..."
|
||||
FILE=$(mc_run 'mc ls s3/${S3_BUCKET}/' | sort | tail -1 | awk '{print $NF}' | tr -d '[:space:]')
|
||||
if [ -z "$FILE" ]; then
|
||||
echo "No backups found."
|
||||
exit 1
|
||||
fi
|
||||
echo "Latest: $FILE"
|
||||
fi
|
||||
|
||||
echo "Downloading $FILE..."
|
||||
cleanup
|
||||
kubectl -n "$NAMESPACE" run "$POD_NAME" --restart=Never \
|
||||
--image="$IMAGE" \
|
||||
--overrides="{
|
||||
\"spec\":{\"containers\":[{
|
||||
\"name\":\"$POD_NAME\",
|
||||
\"image\":\"$IMAGE\",
|
||||
\"env\":[{\"name\":\"HOME\",\"value\":\"/tmp\"}],
|
||||
\"command\":[\"sh\",\"-c\",\"sleep 300\"],
|
||||
\"envFrom\":[{\"secretRef\":{\"name\":\"$SECRET\"}}]
|
||||
}]}
|
||||
}" > /dev/null 2>&1
|
||||
|
||||
kubectl -n "$NAMESPACE" wait --for=condition=Ready "pod/$POD_NAME" --timeout=60s > /dev/null 2>&1
|
||||
|
||||
echo "Saving to ./$FILE ..."
|
||||
kubectl -n "$NAMESPACE" exec "$POD_NAME" -- sh -c "${ALIAS_CMD} && mc cat s3/\${S3_BUCKET}/$FILE" > "./$FILE"
|
||||
cleanup
|
||||
|
||||
echo "Downloaded: ./$FILE"
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Gitea backup helper (AWS S3)"
|
||||
echo ""
|
||||
echo "Usage:"
|
||||
echo " $0 list List all backups in S3"
|
||||
echo " $0 download <filename> Download a specific backup"
|
||||
echo " $0 download latest Download the most recent backup"
|
||||
;;
|
||||
esac
|
||||
95
scripts/gitea-backup-gke.sh
Normal file
95
scripts/gitea-backup-gke.sh
Normal file
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Gitea backup helper for Google Cloud Storage
|
||||
# Uses the gitea-backup-gcs secret in the gitea namespace
|
||||
# Required secret keys:
|
||||
# GCS_BUCKET — bucket name (without gs:// prefix)
|
||||
# GOOGLE_APPLICATION_CREDENTIALS_JSON — service account key JSON
|
||||
# (alternatively, use Workload Identity and omit the key)
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/gitea-backup-gke.sh list # list all backups
|
||||
# ./scripts/gitea-backup-gke.sh download <filename> # download a backup
|
||||
# ./scripts/gitea-backup-gke.sh download latest # download the most recent backup
|
||||
|
||||
NAMESPACE="gitea"
|
||||
SECRET="gitea-backup-gcs"
|
||||
IMAGE="gcr.io/google.com/cloudsdktool/google-cloud-cli:slim"
|
||||
POD_NAME="gitea-backup-helper"
|
||||
AUTH_CMD='if [ -n "${GOOGLE_APPLICATION_CREDENTIALS_JSON:-}" ]; then echo "${GOOGLE_APPLICATION_CREDENTIALS_JSON}" > /tmp/gcs-key.json && gcloud auth activate-service-account --key-file=/tmp/gcs-key.json > /dev/null 2>&1; fi'
|
||||
|
||||
cleanup() {
|
||||
kubectl -n "$NAMESPACE" delete pod "$POD_NAME" --ignore-not-found --grace-period=0 > /dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
gcs_run() {
|
||||
cleanup
|
||||
kubectl -n "$NAMESPACE" run "$POD_NAME" --restart=Never \
|
||||
--image="$IMAGE" \
|
||||
--overrides="{
|
||||
\"spec\":{\"containers\":[{
|
||||
\"name\":\"$POD_NAME\",
|
||||
\"image\":\"$IMAGE\",
|
||||
\"env\":[{\"name\":\"HOME\",\"value\":\"/tmp\"}],
|
||||
\"command\":[\"sh\",\"-c\",\"${AUTH_CMD}; $1\"],
|
||||
\"envFrom\":[{\"secretRef\":{\"name\":\"$SECRET\"}}]
|
||||
}]}
|
||||
}" > /dev/null 2>&1
|
||||
|
||||
kubectl -n "$NAMESPACE" wait --for=jsonpath='{.status.phase}'=Succeeded "pod/$POD_NAME" --timeout=120s > /dev/null 2>&1
|
||||
kubectl -n "$NAMESPACE" logs "$POD_NAME"
|
||||
cleanup
|
||||
}
|
||||
|
||||
case "${1:-help}" in
|
||||
list)
|
||||
echo "Listing backups..."
|
||||
gcs_run 'gsutil ls -l gs://${GCS_BUCKET}/'
|
||||
;;
|
||||
|
||||
download)
|
||||
FILE="${2:?Usage: $0 download <filename|latest>}"
|
||||
|
||||
if [ "$FILE" = "latest" ]; then
|
||||
echo "Finding latest backup..."
|
||||
FILE=$(gcs_run 'gsutil ls gs://${GCS_BUCKET}/' | grep -v '^$' | grep -v 'TOTAL' | sort | tail -1 | xargs -I{} basename {} | tr -d '[:space:]')
|
||||
if [ -z "$FILE" ]; then
|
||||
echo "No backups found."
|
||||
exit 1
|
||||
fi
|
||||
echo "Latest: $FILE"
|
||||
fi
|
||||
|
||||
echo "Downloading $FILE..."
|
||||
cleanup
|
||||
kubectl -n "$NAMESPACE" run "$POD_NAME" --restart=Never \
|
||||
--image="$IMAGE" \
|
||||
--overrides="{
|
||||
\"spec\":{\"containers\":[{
|
||||
\"name\":\"$POD_NAME\",
|
||||
\"image\":\"$IMAGE\",
|
||||
\"env\":[{\"name\":\"HOME\",\"value\":\"/tmp\"}],
|
||||
\"command\":[\"sh\",\"-c\",\"sleep 300\"],
|
||||
\"envFrom\":[{\"secretRef\":{\"name\":\"$SECRET\"}}]
|
||||
}]}
|
||||
}" > /dev/null 2>&1
|
||||
|
||||
kubectl -n "$NAMESPACE" wait --for=condition=Ready "pod/$POD_NAME" --timeout=60s > /dev/null 2>&1
|
||||
|
||||
echo "Saving to ./$FILE ..."
|
||||
kubectl -n "$NAMESPACE" exec "$POD_NAME" -- sh -c "${AUTH_CMD} && gsutil cat gs://\${GCS_BUCKET}/$FILE" > "./$FILE"
|
||||
cleanup
|
||||
|
||||
echo "Downloaded: ./$FILE"
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Gitea backup helper (Google Cloud Storage)"
|
||||
echo ""
|
||||
echo "Usage:"
|
||||
echo " $0 list List all backups in GCS"
|
||||
echo " $0 download <filename> Download a specific backup"
|
||||
echo " $0 download latest Download the most recent backup"
|
||||
;;
|
||||
esac
|
||||
@@ -13,7 +13,7 @@ NAMESPACE="gitea"
|
||||
SECRET="gitea-backup-s3"
|
||||
IMAGE="minio/mc:latest"
|
||||
POD_NAME="gitea-backup-helper"
|
||||
ALIAS_CMD='mc alias set upcloud ${S3_ENDPOINT} ${AWS_ACCESS_KEY_ID} ${AWS_SECRET_ACCESS_KEY} > /dev/null'
|
||||
ALIAS_CMD='mc alias set s3 ${S3_ENDPOINT} ${AWS_ACCESS_KEY_ID} ${AWS_SECRET_ACCESS_KEY} > /dev/null'
|
||||
|
||||
cleanup() {
|
||||
kubectl -n "$NAMESPACE" delete pod "$POD_NAME" --ignore-not-found --grace-period=0 > /dev/null 2>&1 || true
|
||||
@@ -41,7 +41,7 @@ mc_run() {
|
||||
case "${1:-help}" in
|
||||
list)
|
||||
echo "Listing backups..."
|
||||
mc_run 'mc ls upcloud/${S3_BUCKET}/'
|
||||
mc_run 'mc ls s3/${S3_BUCKET}/'
|
||||
;;
|
||||
|
||||
download)
|
||||
@@ -49,7 +49,7 @@ case "${1:-help}" in
|
||||
|
||||
if [ "$FILE" = "latest" ]; then
|
||||
echo "Finding latest backup..."
|
||||
FILE=$(mc_run 'mc ls upcloud/${S3_BUCKET}/' | sort | tail -1 | awk '{print $NF}' | tr -d '[:space:]')
|
||||
FILE=$(mc_run 'mc ls s3/${S3_BUCKET}/' | sort | tail -1 | awk '{print $NF}' | tr -d '[:space:]')
|
||||
if [ -z "$FILE" ]; then
|
||||
echo "No backups found."
|
||||
exit 1
|
||||
@@ -74,7 +74,7 @@ case "${1:-help}" in
|
||||
kubectl -n "$NAMESPACE" wait --for=condition=Ready "pod/$POD_NAME" --timeout=60s > /dev/null 2>&1
|
||||
|
||||
echo "Saving to ./$FILE ..."
|
||||
kubectl -n "$NAMESPACE" exec "$POD_NAME" -- sh -c "${ALIAS_CMD} && mc cat upcloud/\${S3_BUCKET}/$FILE" > "./$FILE"
|
||||
kubectl -n "$NAMESPACE" exec "$POD_NAME" -- sh -c "${ALIAS_CMD} && mc cat s3/\${S3_BUCKET}/$FILE" > "./$FILE"
|
||||
cleanup
|
||||
|
||||
echo "Downloaded: ./$FILE"
|
||||
|
||||
16
secrets/base/kustomization.yaml
Normal file
16
secrets/base/kustomization.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- argocd-forte-helm-secret-sealed.yaml
|
||||
- argocd-mcp-credentials.yaml
|
||||
- argocdmcp-auth-oidc-sealed.yaml
|
||||
- dot-ai-secrets.yaml
|
||||
- forte10x-app-credentials-sealed.yaml
|
||||
- gitea-backup-s3-sealed.yaml
|
||||
- gitea-credentials-sealed.yaml
|
||||
- gitea-runner-token-sealed.yaml
|
||||
- gitea-smtp-secret-sealed.yaml
|
||||
- keycloak-credentials-sealed.yaml
|
||||
- musicman-auth-oidc-sealed.yaml
|
||||
- musicman-credentials.yaml
|
||||
- renovate-env-sealed.yaml
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user