name: k8s-deployments description: >- Create and modify Kubernetes Deployment and Service manifests following this repo's conventions. Use when adding a new workload, updating container images, changing resource limits, or configuring environment variables for deployments.
Kubernetes Deployments and Services
File Conventions
- Filename:
<app-name>-deployment.yaml - Location:
clusters/test-deployment/apps/ - Structure: Deployment and Service in the same file, separated by
--- - Registration: Add the filename to
clusters/test-deployment/apps/kustomization.yamlunderresources
Namespace
All app workloads use namespace test.
Template
apiVersion: apps/v1
kind: Deployment
metadata:
name: <app-name>
namespace: test
spec:
replicas: 1
selector:
matchLabels:
app: <app-name>
template:
metadata:
labels:
app: <app-name>
spec:
containers:
- name: <app-name>
image: ghcr.io/hwinther/<project>/<component>:<semver>
imagePullPolicy: Always
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
env:
- name: OTEL_SERVICE_NAME
value: "<app-name>"
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://obs-otel-collector.test.svc.cluster.local:4317"
ports:
- name: http
containerPort: 8080
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: <app-name>
namespace: test
spec:
type: ClusterIP
ports:
- port: 8080
targetPort: http
protocol: TCP
name: http
selector:
app: <app-name>
Patterns
Labels
Use a single app: <name> label on selector.matchLabels and template.metadata.labels. The Deployment name, container name, Service name, and label value should all match.
Images
- Registry:
ghcr.io/hwinther/<project>/<component>:<semver> - Always use a semver tag (e.g.
1.0.0,0.53.1), neverlatest. - Set
imagePullPolicy: Alwaysso re-pushed tags (e.g. during development) are pulled.
Resource Requests and Limits
Every container must have resources.requests and resources.limits. Typical starting values:
| CPU | Memory | |
|---|---|---|
| Request | 100m | 256Mi |
| Limit | 500m | 512Mi |
Adjust based on actual workload profiling.
OTEL Instrumentation
All app containers include OpenTelemetry environment variables:
OTEL_SERVICE_NAME-- matches the app name for trace/metric identity.OTEL_EXPORTER_OTLP_ENDPOINT-- points to the in-cluster OTEL Collector gRPC endpoint:http://obs-otel-collector.test.svc.cluster.local:4317.
For apps that also need trace-specific config (e.g. .NET), add:
- name: OTEL_TRACES_EXPORTER
value: "otlp"
- name: OTEL_EXPORTER_OTLP_PROTOCOL
value: "grpc"
Ports
- Container port name:
http - Default port:
8080 - Service type:
ClusterIP(Traefik Ingress handles external access) - Service
targetPortreferences the named porthttp, not the number
PostgreSQL (production k0s)
For clusters/production/apps/ workloads, relational data should use CloudNative-PG with tier-correct namespaces: test-line apps → postgres-test Clusters; production-line apps → postgres-production Clusters. secretKeyRef targets the Kyverno-cloned <cluster>-app Secret in the app namespace (see .cursor/skills/flux-gitops/SKILL.md → PostgreSQL (CloudNative-PG)). The standalone clusters/test-deployment/ cluster has its own CNPG Cluster in namespace test.
Checklist for New Deployments
- Create
<app>-deployment.yamlfrom the template above. - Replace all
<app-name>,<project>,<component>,<semver>placeholders. - Add the filename to
clusters/test-deployment/apps/kustomization.yaml. - Add an Ingress entry in
clusters/test-deployment/apps/ingress.yamlif external access is needed. - Commit to
mainfor Flux to reconcile.
For production or any cluster that applies Kyverno wsh-* policies and Kubescape scans, read .cursor/skills/k8s-kyverno-kubescape-compliance/SKILL.md end-to-end — especially “Before the first commit” and “PolicyExceptions”: decide image tag vs :latest exception, runAsNonRoot vs image USER, Cilium + Traefik host ingress, and any init-only root exceptions before the first merge so the first PR already includes the workload and any narrowly scoped PolicyException files in the same change.