Kubernetes Cluster Setup
Setup guide for UpCloud Kubernetes cluster with Traefik, cert-manager, and Argo CD.
Prerequisites
- UpCloud Kubernetes cluster
- kubectl configured with cluster access
- Helm installed
- DNS wildcard record:
*.app.domain.no→ LoadBalancer IP
Setup Script
sh
# 1. Set KUBECONFIG (fish shell)set -gx KUBECONFIG /path/to/your/kubeconfig.yaml# 2. Install Gateway API CRDs (v1.4.0)kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/standard-install.yaml# 3. Create traefik namespace and install official RBACkubectl create namespace traefikkubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.6/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml# 4. Add Traefik Helm repohelm repo add traefik https://traefik.github.io/chartshelm repo update# 5. Install Traefik via Helm with Gateway API# Uses high ports (8000/8443) to avoid running as root# LoadBalancer translates: 80→8000, 443→8443cat <<'EOF' > /tmp/traefik-values.yamlproviders:kubernetesGateway:enabled: truekubernetesIngress:enabled: falsedeployment:kind: DaemonSetservice:type: ClusterIPhostNetwork: trueupdateStrategy:rollingUpdate:maxUnavailable: 1maxSurge: 0gateway:enabled: falseports:web:port: 8000websecure:port: 8443EOFhelm install traefik traefik/traefik -n traefik -f /tmp/traefik-values.yaml# 6. Install cert-manager with Gateway API supporthelm repo add jetstack https://charts.jetstack.iohelm repo updatehelm install cert-manager jetstack/cert-manager \-n cert-manager --create-namespace \--set crds.enabled=true \--set extraArgs="{--enable-gateway-api}"# 7. Create Cloudflare API token secret for DNS01 challenge# Get token from https://dash.cloudflare.com/profile/api-tokens# Required permissions: Zone:DNS:Edit, Zone:Zone:Read for your domainkubectl create secret generic cloudflare-api-token \--namespace cert-manager \--from-literal=api-token=<YOUR-CLOUDFLARE-API-TOKEN># 8. Create ClusterIssuer with Cloudflare DNS01 (enables wildcard certs)kubectl apply -f - <<'EOF'apiVersion: cert-manager.io/v1kind: ClusterIssuermetadata:name: letsencrypt-prodspec:acme:server: https://acme-v02.api.letsencrypt.org/directoryemail: user@domainprivateKeySecretRef:name: letsencrypt-prod-keysolvers:- dns01:cloudflare:apiTokenSecretRef:name: cloudflare-api-tokenkey: api-tokenselector:dnsZones:- "yourdomain.no"EOF# 9. Create wildcard certificate (in traefik namespace for easy Gateway access)kubectl apply -f - <<'EOF'apiVersion: cert-manager.io/v1kind: Certificatemetadata:name: wildcard-app-domainnamespace: traefikspec:secretName: wildcard-app-domain-tlsissuerRef:name: letsencrypt-prodkind: ClusterIssuerdnsNames:- "*.app.domain.no"- "app.domain.no"EOF# 10. Create Gateway with wildcard HTTPS listenerkubectl apply -f - <<'EOF'apiVersion: gateway.networking.k8s.io/v1kind: Gatewaymetadata:name: traefik-gatewaynamespace: traefikspec:gatewayClassName: traefiklisteners:- name: httpport: 8000protocol: HTTPallowedRoutes:namespaces:from: All- name: httpsport: 8443protocol: HTTPShostname: "*.app.domain.no"tls:mode: TerminatecertificateRefs:- kind: Secretname: wildcard-app-domain-tlsallowedRoutes:namespaces:from: AllEOF# 11. Install Argo CDkubectl create namespace argocdkubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml# 12. Patch Argo CD for insecure mode (TLS handled by gateway)kubectl -n argocd patch deployment argocd-server --type='json' \-p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--insecure"}]'# 13. Create Argo CD HTTPRoute (uses wildcard cert, no ReferenceGrant needed)kubectl apply -f - <<'EOF'apiVersion: gateway.networking.k8s.io/v1kind: HTTPRoutemetadata:name: argocdnamespace: argocdspec:parentRefs:- name: traefik-gatewaynamespace: traefiksectionName: httpshostnames:- argo.app.domain.norules:- matches:- path:type: PathPrefixvalue: /backendRefs:- name: argocd-serverport: 80EOF# 14. Get Argo CD admin passwordkubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d && echo
Manual Steps After Script
- DNS: Ensure
*.app.domain.nopoints to the LoadBalancer IP - LoadBalancer: Configure port translation:
- Frontend 80 → Backend 8000
- Frontend 443 → Backend 8443
- Wait for certificate:
kubectl get certificate -n traefik -w(wait for READY=True) - Argo CD: Log in at https://argo.app.domain.no with username
admin - GitHub App: Add repository connection via GitHub App in Argo CD Settings → Repositories
- Applications: Create Argo CD Applications for each environment
Adding New Applications
1. Create Namespace
sh
kubectl create namespace my-app-dev
2. Create HTTPRoute
With the wildcard certificate (*.app.domain.no), new applications only need an HTTPRoute - no per-app certificates or ReferenceGrants required:
yaml
apiVersion: gateway.networking.k8s.io/v1kind: HTTPRoutemetadata:name: my-appnamespace: my-app-devspec:parentRefs:- name: traefik-gatewaynamespace: traefiksectionName: https # Uses wildcard listenerhostnames:- my-app.app.domain.norules:- matches:- path:type: PathPrefixvalue: /backendRefs:- name: my-appport: 80
4. Create HTTPRoute
yaml
apiVersion: gateway.networking.k8s.io/v1kind: HTTPRoutemetadata:name: my-appnamespace: my-app-devspec:parentRefs:- name: traefik-gatewaynamespace: traefiksectionName: https-my-apphostnames:- my-app.app.domain.norules:- matches:- path:type: PathPrefixvalue: /backendRefs:- name: my-app-serviceport: 80
5. Configure Environment Variables
Use ConfigMaps for non-sensitive configuration and Secrets for sensitive values:
yaml
apiVersion: v1kind: ConfigMapmetadata:name: my-app-confignamespace: my-app-devdata:NODE_ENV: "production"APP_URL: "https://my-app.app.domain.no"---apiVersion: v1kind: Secretmetadata:name: my-app-secretsnamespace: my-app-devtype: OpaquestringData:DATABASE_URL: "postgresql://user:password@host:5432/database"
Then reference them in the Deployment:
yaml
apiVersion: apps/v1kind: Deploymentmetadata:name: my-appnamespace: my-app-devspec:template:spec:containers:- name: my-appenvFrom:- configMapRef:name: my-app-config- secretRef:name: my-app-secrets# Or reference individual keys:env:- name: DATABASE_URLvalueFrom:secretKeyRef:name: my-app-secretskey: DATABASE_URL
HTTP to HTTPS Redirect
All HTTP traffic is automatically redirected to HTTPS via an HTTPRoute:
yaml
apiVersion: gateway.networking.k8s.io/v1kind: HTTPRoutemetadata:name: http-to-https-redirectnamespace: traefikspec:parentRefs:- name: traefik-gatewaynamespace: traefiksectionName: httprules:- filters:- type: RequestRedirectrequestRedirect:scheme: httpsstatusCode: 301
Architecture
- Traefik: Ingress controller using Gateway API (Helm, DaemonSet with hostNetwork on ports 8000/8443)
- cert-manager: Automatic TLS certificates from Let's Encrypt
- Argo CD: GitOps deployments from this repository
- LoadBalancer: Cloud provider translates 80→8000, 443→8443 to Traefik
- Per-hostname listeners: Each application gets its own HTTPS listener with dedicated TLS certificate