Kubernetes primer
If you remember seven words from this page — pod, deployment, service, ingress, namespace, CRD, operator — you'll understand ~90% of what you see in this cluster. Here they are in plain English, with a link to the official Kubernetes docs for each.
What a Kubernetes manifest looks like
Before we get to the concepts, a visual anchor. Nearly everything you'll read or edit in this repo is a YAML file describing a Kubernetes resource. Every resource, no matter the kind, has the same four-block shape:
apiVersion: apps/v1 # ← which API is this resource part of?
kind: Deployment # ← which kind within that API?
metadata: # ← identity: name, namespace, labels, annotations
name: app-web
namespace: ecommercen-clients-wecare
labels:
app.kubernetes.io/name: app-web
spec: # ← desired state: the "what should exist"
replicas: 4
selector:
matchLabels:
app.kubernetes.io/name: app-web
template:
metadata:
labels:
app.kubernetes.io/name: app-web
spec:
containers:
- name: app-php-fpm
image: ecommercen/adveshop4-php_fpm:4.99.10.0-wecare
# status: (read-only, written back by controllers)
# replicas: 4
# readyReplicas: 4
# conditions: [...]Four boxes, always:
apiVersion+kindtogether identify the resource type. Controllers watch specificapiVersion/kindcombinations. When you see a YAML, those two lines tell you "what program runs against this."metadata.name+metadata.namespacetogether uniquely identify the resource. No two resources of the same kind can share a name within a namespace.labelsare free-form key/value tags used by other resources to find this one (e.g., a Service selects pods by label). Everything uses them.specis where the real configuration lives. This is what you edit. The shape differs per kind — aDeployment.spechasreplicasandtemplate; anIngress.spechasrules[].host; etc.statusis Kubernetes writing back: "here's what I actually see right now." You read it viakubectl get/kubectl describe; you never edit it.
One YAML file can hold multiple resources, separated by ---:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
key: value
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-web
spec:
...When you see a multi-doc file, each block is its own resource. kubectl apply -f file.yaml applies all of them.
Mental model: a YAML file is a wish. You commit the wish to the repo, ArgoCD reads it, Kubernetes' controllers do their best to make reality match. If the wish is impossible (a container image that doesn't exist, a port that conflicts, a node that doesn't meet affinity), the status block will tell you why.
Pod
The smallest thing Kubernetes runs. A pod is one or more containers that live together, share a network address, and share some disk. Think of it like a single "virtual machine" — except cheaper, smaller, and short-lived.
Example: redis-cache-0 is a pod that runs two containers — the Redis server itself and a metrics exporter sidecar that watches it.
Deployment
A Deployment is a recipe for N identical pods. "I want 4 copies of app-web running at all times; if one dies, replace it; if I update the recipe, roll them over one by one." Deployments are the right shape for most stateless workloads (web apps, API servers, workers).
For databases and things that really care which pod is which (redis-0 vs redis-1), we use a StatefulSet instead — same idea, but pods keep their identity and their storage across restarts.
Service
A Service is a stable virtual IP + DNS name that points at "whichever pods are currently alive for this role". Pods come and go; their IPs change; a Service gives the rest of the cluster a hostname that doesn't.
Example: app-svc.ecommercen-clients-wecare.svc.cluster.local resolves to whichever app-web pods are currently healthy. You'd connect to it from any other pod and not care which backend you land on.
Ingress
A Service only gives you a hostname inside the cluster. An Ingress is what exposes it to the public internet on a specific domain + path. In our setup Ingress is handled by Traefik, and TLS certificates are fetched automatically by cert-manager.
Example: www.wecare.gr routes via Ingress → Traefik → the app-svc Service → one of the app-web pods. See Ingress, TLS & Cloudflare for the fuller flow including Cloudflare tunnel.
Namespace
A namespace is a folder that groups related resources. ecommercen-clients-wecare holds wecare's app pods, ecommercen-clients-wecare-infrastructure holds its MariaDB and Redis, observability holds Prometheus/Grafana/Loki, etc.
Every command you run can be scoped to a namespace (kubectl get pods -n <namespace>). If you forget to specify one, kubectl uses the default namespace — which is almost always the wrong one here.
CRD (Custom Resource Definition)
Kubernetes ships with built-in resource kinds: Pod, Deployment, Service, etc. A CRD lets an extension add new kinds. That's how we have things like RedisReplication (the Redis operator's CRD), MariaDB (the MariaDB operator's), IngressRoute (Traefik's), SealedSecret (the sealed-secrets controller's), Probe (Prometheus operator's), etc.
When you see a YAML file with apiVersion: redis.redis.opstreelabs.in/v1beta2, that's a custom resource from the Redis operator. Same pattern for all the others.
📖 Custom Resources — kubernetes.io
Operator
An operator is a controller that watches a CRD and does something when it changes. The Redis operator watches RedisReplication resources and creates/manages the underlying StatefulSets, Services, ConfigMaps, and PVCs. The MariaDB operator does the same for MariaDB CRs.
Operators are how "install this cluster-scale software and make it production-ready" becomes a few YAML lines instead of pages of manual setup. We use:
- redis-operator (opstree) — Redis master/replica + sentinel
- mariadb-operator — MariaDB with HA + MaxScale + backups
- cert-manager — Let's Encrypt TLS certificates
- sealed-secrets-controller — encrypted secrets in git
- KEDA — auto-scale pods based on external metrics
- prometheus-operator — manage Prometheus, Alertmanager, ServiceMonitor, PrometheusRule resources
- postgres/rabbitmq/keycloak operators — ditto for their respective engines
📖 Operator pattern — kubernetes.io
Where does it all run?
- Masters: 3 Hetzner VMs (
ecnv4k8s-hel1-1..3). Run Kubernetes itself (etcd, apiserver, scheduler, controller-manager) plus our observability stack. - Tenant-dedicated workers: per-client node pools — wecare has
wecare-web-1..4(app-web) +wecare-db-1..2(MariaDB, Redis-session) +wecare-cache-1..2(Redis-cache). Labels and taints keep each tenant's workloads on their own hardware. - Autoscaler worker pool: spun up on demand by the cluster-autoscaler when tenant pools fill up.
Further reading
📺 Watch first — 20 minutes, covers everything above at the "why" level
Kubernetes, the Control Loop (YouTube) — the single best intro I've seen. Explains that Kubernetes isn't a deployment tool but a declarative state management system, then builds every core concept from first principles around the reconciliation loop.
"Why Kubernetes is not a 'deploy code to servers' tool; the reconciliation loop; what pods actually are (and why containers aren't the real unit); how Services solve the networking problem containers create; Deployments, rolling updates, and zero-downtime releases; how the scheduler decides where your workloads run; Ingress: routing external traffic into your cluster."
Chapters:
- 0:00 Intro
- 1:42 The Control Loop
- 5:49 Pods
- 8:59 Services
- 12:19 Deployments
- 14:32 The Scheduler
- 16:54 Ingress
- 20:30 Closing
📖 Written references (10 min each)
- Learnk8s — visual Kubernetes troubleshooting illustrations — great when something's wrong.
- Kubernetes the hard way (Kelsey Hightower) — only if you ever want to see what's under the covers. Not required.
- ArgoCD overview — how the GitOps side works.