Reference
Cluster Capabilities¶
Target Audience: Developers writing CDK8S charts and ArgoCD application deployments
This document describes the capabilities, services, and features available in the kup6s.com Kubernetes cluster for application developers. If you’re deploying applications via ArgoCD or writing CDK8S charts, this is your reference guide.
Cluster Overview¶
Platform: K3S on Hetzner (hybrid: cloud control plane + dedicated bare-metal workers)
Architecture: General workloads run on AMD64 (x86) EX workers; one tainted ARM64 node for CI only
High Availability: 3 control plane nodes across 3 data centers
Deployment Method: GitOps via ArgoCD
Kubernetes Version: v1.31.x (automatically managed)
Compute Resources¶
Node Pools¶
Control Plane Nodes (3 nodes - not for workloads):
1x CAX21 (ARM64, 4 vCPU, 8GB RAM) in fsn1
2x CX33 (AMD64, 4 vCPU, 8GB RAM) in hel1 and nbg1
Cloud nodes, embedded etcd (3 members)
Taints: Workloads not scheduled here by default
Worker Nodes:
AMD64 Workers (default scheduling target - 2 dedicated bare-metal nodes):
2x EX63 (
kup6s-ex-hel-1,kup6s-ex-hel-2): Intel Core Ultra 7 265 (20 cores), 64GB RAM, 2x 1TB NVMe eachHetzner Robot bare-metal, Helsinki; joined as external K3S agents over a private vSwitch
Untainted - all general workloads (and CNPG databases) schedule here unless specified otherwise
ARM64 CI Node (1 node, tainted):
1x CAX31 (
kup6s-agent-cax31-fsn1-yim: 8 vCPU, 16GB RAM), cloud, fsn1Tainted
dedicated=arm-ci:NoSchedule- runs only the dedicated ARM64 CI build runnerNot a general scheduling target; requires both the
dedicated=arm-citoleration andnodeSelector: kubernetes.io/arch=arm64
Architecture Support¶
General workloads run on AMD64 (x86) by default:
✅
linux/amd64(default - the EX workers)✅
linux/arm64(only via the tainted CI node; use multi-arch images for portability)
Scheduling Behavior:
EX (AMD64) workers: Workloads schedule here by default (untainted)
ARM64 CI node: Tainted with
dedicated=arm-ci:NoSchedule- requires explicit toleration + nodeSelector
Best Practice:
Use AMD64 or multi-arch images: They run on the EX workers with no special config
Avoid ARM64-only images: Rebuild them as multi-arch instead of pinning to the CI node
Multi-platform builds: Build images for both architectures:
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest --push .
Storage Options¶
1. Longhorn (Default Persistent Storage)¶
Use for: Stateful applications, databases, persistent volumes
StorageClass:
longhorn(default)Access Modes: ReadWriteOnce (RWO), ReadWriteMany (RWX), ReadOnlyMany (ROX)
File System: XFS
Replication: 2 replicas across nodes with hard anti-affinity (configurable; CNPG DB volumes use 1 replica since CNPG self-replicates HA)
Backup: Automatic backup to Hetzner Storage Box (CIFS, via SMB CSI)
Snapshots: Supported
Capacity: ~2 TB usable across the two EX workers (2x 1TB NVMe each)
Example PVC:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-app-data
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 10Gi
Special Use Cases:
For Kafka workloads: Use
longhorn-kafkaStorageClass (dedicated for high-throughput workloads)
2. SMB/CIFS Storage (Hetzner Storage Box)¶
Use for: Shared file storage, backups, multi-pod read/write
StorageClass:
hetzner-smbAccess Modes: ReadWriteMany (RWX)
Capacity: Large (Hetzner Storage Box)
Performance: Network-based (slower than Longhorn)
Example PVC:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: shared-uploads
spec:
accessModes:
- ReadWriteMany
storageClassName: hetzner-smb
resources:
requests:
storage: 100Gi
3. S3 Object Storage (via Crossplane)¶
Use for: Object storage, backups, log storage, static assets
Provider: Hetzner Object Storage (S3-compatible)
Management: Crossplane-managed buckets
Access: Via S3 API (AWS SDK compatible)
How to Request a Bucket:
Create a Crossplane Bucket resource (see How-To: Create S3 Bucket)
Networking & Ingress¶
Ingress Controller: Traefik¶
Default ingress controller for HTTP/HTTPS traffic
Version: v3.4.1 (pinned)
Features:
Automatic HTTPS via Let’s Encrypt (cert-manager)
HTTP to HTTPS redirect (enabled by default)
Access logs enabled
Proxy protocol support
Creating an Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: traefik
tls:
- hosts:
- myapp.sites.kup6s.com
secretName: myapp-tls
rules:
- host: myapp.sites.kup6s.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app
port:
number: 80
TLS/SSL Certificates (cert-manager)¶
Automatic certificate management via Let’s Encrypt
Cluster Issuer:
letsencrypt-prodDNS Challenge: Not configured (use HTTP-01 challenge)
Renewal: Automatic (30 days before expiry)
Usage: Add annotation to Ingress (see example above)
Domain Structure¶
Available domain patterns:
*.sites.kup6s.com- Customer/project websites*.ops.kup6s.net- Infrastructure tools (ArgoCD, Grafana, etc.)*.nodes.kup6s.com- Node-level DNS (internal only)
Network Policy & Observability¶
CNI: Cilium (eBPF-based) with native routing mode
Pod-to-Pod Traffic: High-performance eBPF networking
Network Policies: Supported (standard Kubernetes NetworkPolicy + Cilium NetworkPolicy for L7)
Hubble Observability: ✅ Enabled
Service dependency mapping (automatic service maps)
Flow visibility (L3/L4/L7 traffic inspection)
Network troubleshooting (DNS, HTTP, TCP flows)
Hubble UI available for graphical network visualization
Metrics exported to Prometheus
Databases¶
CloudNativePG (PostgreSQL Operator)¶
Managed PostgreSQL databases via Kubernetes operator
Operator: CloudNativePG (CNPG) v1.27.0
Backup Plugin: Barman Cloud Plugin v0.7.0 (installed)
High Availability: Supported (with replication)
Backups: Integrated with S3/Longhorn via Barman Cloud Plugin
Monitoring: Prometheus metrics
Creating a PostgreSQL Cluster:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: myapp-db
spec:
instances: 3
storage:
storageClass: longhorn
size: 20Gi
postgresql:
parameters:
max_connections: "100"
Connection: Use generated secrets for connection strings
Note: For backup configuration using the Barman Cloud Plugin, create an ObjectStore resource and reference it in your cluster’s plugins section. The plugin is deployed via 60-B-barman-plugin.yaml.tpl. See CloudNativePG documentation for details.
Monitoring & Observability¶
Prometheus + Grafana (kube-prometheus-stack)¶
Full observability stack pre-installed
Access:
Grafana:
https://grafana.ops.kup6s.netPrometheus: Internal cluster access only
Metrics Collection:
All cluster components monitored by default
Your apps: Add Prometheus annotations to expose metrics
ServiceMonitor Example:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: my-app
spec:
selector:
matchLabels:
app: my-app
endpoints:
- port: metrics
interval: 30s
Loki (Log Aggregation)¶
Centralized logging with S3 backend
Storage: Hetzner S3 Object Storage (Crossplane-managed)
Access: Via Grafana (Explore → Loki)
Retention: Configurable (check with cluster admin)
Log Collection:
Container logs automatically collected
Query via LogQL in Grafana
Example Query:
{namespace="my-namespace", pod=~"my-app-.*"}
Security Features¶
Secrets Encryption at Rest¶
✅ Kubernetes secrets encrypted in etcd (AES-CBC)
✅ Automatic encryption for all Secret resources
No action required from developers
Pod Security¶
Pod Security Standards: Baseline enforced
Security Contexts: Supported and recommended
Example:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
Network Encryption¶
✅ Pod-to-pod traffic secured (Cilium eBPF with native routing)
✅ Ingress traffic encrypted (TLS via cert-manager)
✅ Secrets encrypted at rest (etcd encryption enabled)
GitOps Deployment (ArgoCD)¶
ArgoCD Access¶
Dashboard: https://argocd.ops.kup6s.net
Deployment Workflow¶
Write CDK8S Chart in
argoapps/directoryRegister in registry (
apps/registry.ts)Generate manifests:
npm run buildDeploy:
git add apps/ dist/ && git commit && git push(argocd-apps-root auto-syncsdist/)ArgoCD syncs your application automatically
ArgoCD Application Structure¶
Example CDK8S Chart:
import { Chart } from 'cdk8s';
import { ArgoCdApplication } from '@opencdk8s/cdk8s-argocd-resources';
export class MyAppChart extends Chart {
constructor(scope: Construct, id: string) {
super(scope, id);
new ArgoCdApplication(this, 'myapp', {
metadata: {
name: 'myapp',
namespace: 'argocd',
},
spec: {
project: 'default',
source: {
repoUrl: 'https://github.com/your-org/your-repo',
path: 'k8s/myapp',
targetRevision: 'main',
},
destination: {
server: 'https://kubernetes.default.svc',
namespace: 'myapp',
},
syncPolicy: {
automated: {
prune: true,
selfHeal: true,
},
},
},
});
}
}
Resource Quotas & Limits¶
No Hard Quotas (Currently)¶
No namespace-level resource quotas configured
Best Practice: Always set resource requests/limits in your pods
Recommended:
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
Node Capacity (Total)¶
Worker Node Resources:
EX63 worker #1 (
kup6s-ex-hel-1): 20 cores, 64GB RAM, 2x 1TB NVMe (AMD64)EX63 worker #2 (
kup6s-ex-hel-2): 20 cores, 64GB RAM, 2x 1TB NVMe (AMD64)ARM64 CI node (
...-yim): 8 vCPU, 16GB RAM — tainteddedicated=arm-ci, CI onlyGeneral-workload total: 40 cores, 128GB RAM (the 2x EX63)
Storage: ~2 TB usable Longhorn pool across the EX NVMe disks
Recommended Allocation:
All applications and databases → EX (AMD64) workers (default, no special config)
ARM64 CI builds only → the tainted
...-yimnode (toleration + nodeSelector)
Plan accordingly for your application’s resource needs.
Service Mesh & Advanced Networking¶
Cilium Advanced Features¶
The cluster uses Cilium CNI which provides service mesh-like capabilities without a separate service mesh:
✅ L7 Network Policies: HTTP/gRPC/Kafka protocol-aware policies
✅ Service Mesh Lite: Cilium provides observability and L7 policies without sidecar proxies
✅ Hubble Observability: Service dependency maps, flow visualization, network troubleshooting
✅ High Performance: eBPF-based networking bypasses iptables for better performance
Accessing Hubble UI:
# Via Cilium CLI (recommended)
cilium hubble ui
# Or via kubectl port-forward
kubectl port-forward -n kube-system service/hubble-ui 12000:80
# Then open http://localhost:12000
Not Available¶
❌ Full service mesh (Istio/Linkerd) with sidecar proxies
❌ Advanced traffic splitting/canary deployments (use ArgoCD Rollouts instead)
Use Traefik features for:
Load balancing
Path-based routing
Header-based routing
Rate limiting (via middleware)
Backup & Disaster Recovery¶
Automatic Backups¶
etcd: Daily S3 backups (cluster state) Longhorn: Recurring backups to Storage Box PostgreSQL: Configure per-database (CNPG backup)
Application Backups¶
Your responsibility:
Application data backup strategy
Database backup verification
Backup testing
Limitations & Considerations¶
Architecture Constraints¶
✅ AMD64 default: General workloads run on the EX (AMD64) workers with no special config
✅ Multi-arch images work: They simply schedule onto the EX workers
⚠️ ARM64 node is tainted for CI: The only ARM64 node carries
dedicated=arm-ci:NoScheduleand runs only the CI runner - not a general scheduling target⚠️ ARM64-only images: Avoid them; rebuild as multi-arch rather than pinning to the CI node
Storage Performance¶
Longhorn: Good for general workloads
Longhorn-Kafka: Optimized for high-throughput
SMB/CIFS: Slower, best for shared/backup use
Scaling¶
Node scaling: Contact cluster admin
HPA (Horizontal Pod Autoscaler): Supported
VPA (Vertical Pod Autoscaler): Not configured
External Services¶
External databases: Not directly supported (use port-forward or VPN)
Outbound traffic: Unrestricted (no egress filtering)
Quick Reference: Common Tasks¶
Deploy an Application¶
Default (AMD64 / multi-arch):
Create namespace (if needed)
Create ArgoCD Application (CDK8S or YAML)
Deploy:
git add apps/ dist/ && git commit && git push(argocd-apps-root auto-syncs)Monitor in ArgoCD dashboard
Workload automatically schedules to the EX (AMD64) workers (no special config needed)
ARM64 CI builder (special case - the only reason to target the ARM node):
apiVersion: apps/v1
kind: Deployment
metadata:
name: arm-ci-builder
spec:
template:
spec:
nodeSelector:
kubernetes.io/arch: arm64
tolerations:
- key: dedicated
operator: Equal
value: arm-ci
effect: NoSchedule
containers:
- name: builder
image: myorg/arm-builder:arm64
Request a PersistentVolume¶
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-data
spec:
accessModes: [ReadWriteOnce]
storageClassName: longhorn
resources:
requests:
storage: 10Gi
Expose an Application (Ingress)¶
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: traefik
tls:
- hosts: [myapp.sites.kup6s.com]
secretName: myapp-tls
rules:
- host: myapp.sites.kup6s.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app
port:
number: 80
Create a PostgreSQL Database¶
There is no dedicated database node — CNPG runs on the EX (AMD64) workers and stores its volumes on Longhorn. CNPG’s own anti-affinity spreads replicas across the EX hosts for HA, so no nodeSelector or toleration is needed:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: myapp-db
namespace: databases
spec:
instances: 3 # HA with replication (CNPG spreads across the EX workers)
storage:
size: 10Gi
storageClass: longhorn
postgresql:
parameters:
max_connections: "100"
shared_buffers: "256MB"
Single instance (no HA):
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: simple-db
spec:
instances: 1
storage:
size: 10Gi
storageClass: longhorn
View Logs (Loki)¶
Open Grafana:
https://grafana.ops.kup6s.netGo to Explore
Select Loki data source
Query:
{namespace="your-namespace"}
Monitor Application Metrics¶
Add Prometheus annotations to Service
Create ServiceMonitor (optional)
View in Grafana dashboards
Getting Help¶
Cluster Administration Issues¶
Contact: Cluster admin team
Topics: Node issues, cluster upgrades, infrastructure
Application Deployment Issues¶
ArgoCD dashboard for sync status
Logs via
kubectl logsor Grafana/LokiMetrics via Grafana
CDK8S Development¶
See: argoapps/README.md
Examples:
argoapps/apps/directory