All skills
Skillintermediate
Kubernetes — Architecture Checklist
Declarative-resource shape, separation of concerns, and cluster-vs-application boundaries. Applied whenever the `k8s` profile is active.
Claude Code Knowledge Pack7/10/2026
Overview
Kubernetes — Architecture Checklist
Declarative-resource shape, separation of concerns, and cluster-vs-application boundaries. Applied whenever the k8s profile is active.
Contents
- One primary concern per resource
- Config injection vs hardcoded values
- No hardcoded cluster assumptions
- Explicit selectors and labels
- Cluster vs application separation
One primary concern per resource
- Each resource manifest addresses a single concern. A
Deploymentthat bundles two unrelated workloads (e.g., API server + batch worker) under one Pod template is a P1 finding — split into two Deployments. - Init containers are for setup that the main container cannot do itself (schema migration, secret templating, permission fix-ups). An init container doing actual application work should be its own Pod.
- Sidecars carry one cross-cutting concern each (log shipper, metrics exporter, proxy). A sidecar that shares application business logic is a design smell.
Serviceresources expose one logical endpoint. AServicerouting traffic to Pods from multiple unrelated workloads via clever label selectors is fragile.ConfigMapandSecretresources are scoped to one consumer when possible. A "shared config"ConfigMapmounted by many unrelated workloads creates hidden coupling.
Config injection vs hardcoded values
- Configuration values (URLs, flags, tuning parameters, connection strings) flow through
env,envFrom, or volume-mountedConfigMap/Secret— never hardcoded in the container image or in static manifest literals that should differ per environment. - Hostnames: use cluster-local DNS (
svc.cluster.local) by name, not by IP. IP literals in manifests are a P1 finding — they break on cluster migration. - URLs pointing to cluster-internal services use cluster DNS, not hardcoded FQDNs tied to a specific cluster domain. Distinguish same-namespace from cross-namespace: within the same namespace, the bare name (
myservice) resolves via the Pod's DNS search domains. For cross-namespace, the namespace must be qualified (myservice.other-nsormyservice.other-ns.svc) — a baremyservicestring resolves only in the caller's local namespace and silently fails (NXDOMAIN) when the target lives elsewhere. IP literals are always a P1 finding. - Boolean/enum flags that change per environment (debug vs prod, feature toggles) come from
ConfigMap, not compile-time constants. ConfigMapvsSecretchoice: secret material →Secret; non-secret config →ConfigMap. Mixing sensitive values intoConfigMapis a security finding (see security-checklist.md) but also an architectural one: it conflates two concerns.
No hardcoded cluster assumptions
- No hardcoded namespace strings in resource
spec(outsidemetadata.namespacewhich is the resource's own namespace). If a Pod references another workload, useServiceDNS, nothttp://other-svc.prod-env/.... - No hardcoded node names, node-pool identifiers, or availability-zone names — use
nodeSelector/nodeAffinity/topologySpreadConstraintswith semantic labels (topology.kubernetes.io/zone,node.kubernetes.io/instance-type). - No hardcoded image registry hosts in multiple places; prefer a single templated or generated value (Helm
.Values.image.registry, Kustomize image transformer). - Cluster domain (
cluster.local) not hardcoded in FQDNs — workloads that need the full domain should read it from the Pod's resolv.conf or thespec.dnsConfig. - PersistentVolume / StorageClass names pulled from a template value or an overlay, not literal
gp2-us-east-1strings inside generic workload manifests.
Explicit selectors and labels
Deployment.spec.selector.matchLabelsmatchesDeployment.spec.template.metadata.labelsexactly. A drift between the two is accepted by the API server in some versions and rejected in others — always a finding.Service.spec.selectornames the labels that identify the intended Pods — no wildcard-ish matching by overly broad selectors (e.g., selecting only onapp.kubernetes.io/part-of).- Selectors pin to immutable labels on the Pod template. Labels that change over time (version, build hash) should not be in the selector — they'd break the Deployment's own rollout.
- Recommended label set applied consistently (see quality-checklist.md for the full set):
app.kubernetes.io/name,app.kubernetes.io/instance,app.kubernetes.io/version,app.kubernetes.io/component,app.kubernetes.io/part-of,app.kubernetes.io/managed-by. - No reliance on auto-generated labels (e.g.,
pod-template-hash) in selectors authored by humans — they are controller-managed.
Cluster vs application separation
- Application code does not assume it is running in Kubernetes — the manifests inject the Kubernetes-specific concerns (service discovery via env, secrets via files, config via mounts). Code that calls the Kubernetes API from the application path is a design decision that needs rationale.
- Conversely, Kubernetes-specific operational concerns (probes, lifecycle hooks, graceful shutdown) are driven by the manifests, not by hardcoding cluster topology into application config files.
- Infrastructure resources (CRDs, operators, storage classes, network policies that apply cluster-wide) live in their own manifests, not mixed with application workload manifests. A
Deploymentnext to aCustomResourceDefinitionin the same file is a composition smell. - Don't conflate platform concerns (ingress controller, cert-manager, monitoring stack) with application concerns in the same chart or kustomization — platform lifecycle and application lifecycle differ.
Questions to ask
- "If I applied this manifest to a different cluster, what would break?" — surfaces hardcoded assumptions.
- "If I split this resource into two, would each half still make sense?" — surfaces violated single-concern scope.
- "What does the application know about Kubernetes, and what does Kubernetes know about the application?" — clarifies the boundary.