Stage 7 · Master
Phase 5 — API Gateway
Deployment
Give gateway-service the platform's sole Ingress and LoadBalancer, lock every backend service to ClusterIP-only via NetworkPolicy, and provision a PodDisruptionBudget and HorizontalPodAutoscaler because unlike any backend, a gateway outage takes down literally everything at once.
Every Backend Service Failing Independently Is Recoverable; The Gateway Failing Is Not
user-service going down affects profile and membership operations; auth-service going down affects login and token refresh, though already-issued access tokens keep working for up to 15 minutes. gateway-service going down means nothing reaches any backend at all — every single request through it fails simultaneously. This asymmetry is why this deployment lesson exists as its own lesson rather than being a copy-paste of the User module's deployment lesson: the gateway needs deliberately more redundancy than every backend service behind it.
Ingress and LoadBalancer Belong to Exactly One Service
apiVersion: v1
kind: Service
metadata:
name: gateway-service
namespace: hoa-platform
spec:
type: LoadBalancer
selector:
app: gateway-service
ports:
- port: 443
targetPort: 8080type: LoadBalancer here is deliberate and singular — user-service and auth-service, by contrast, are provisioned exclusively as type: ClusterIP (shown below), never reachable from outside the cluster network at all.
This phase establishes the ownership rule: only Gateway receives public traffic. The Production Deployment Ingress lesson later implements TLS, controller annotations, DNS, and webhook exceptions once the complete platform exists. Printing that manifest here would teach the same object twice.
NetworkPolicy Makes the Gateway-Only Rule Enforced, Not Just Documented
Setting a backend Service's type to ClusterIP prevents an external load balancer from being provisioned for it, but it does nothing to stop another pod inside the same cluster from reaching user-service directly, bypassing the gateway's JWT verification and rate limiting entirely. A NetworkPolicy is what actually closes that gap — a default-deny policy on ingress traffic to backend pods, with an explicit allow rule for traffic originating only from gateway-service's own pods.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: user-service-ingress
namespace: hoa-platform
spec:
podSelector:
matchLabels: { app: user-service }
policyTypes: ["Ingress"]
ingress:
- from:
- podSelector:
matchLabels: { app: gateway-service }
ports:
- port: 8081
- from:
- namespaceSelector:
matchLabels: { name: monitoring }
ports:
- port: 8081 # /metrics scrape access for Prometheus, distinct from application trafficThis policy's egress is left unset (defaulting to allow-all) deliberately — user-service still needs to reach its own Postgres database and any external services; only ingress is locked down, since this policy's entire purpose is preventing other pods from calling in directly, not restricting what user-service itself can call out to.
Before this NetworkPolicy exists, any compromised pod anywhere in the cluster — a supply-chain-compromised dependency in an entirely unrelated service, say — could reach user-service on its ClusterIP address directly, skipping every gateway-side defense from this entire module. The jwt-validation lesson's tenant-header injection, the rate-limiting lesson's fair budgets, none of it applies to traffic that never passes through the gateway. This NetworkPolicy is what makes 'the gateway is the only path in' an enforced fact rather than a hopeful assumption.
PodDisruptionBudget and HorizontalPodAutoscaler — Non-Negotiable Here
A backend service can tolerate a brief dip to zero replicas during a bad rollout with only that service's features degrading. The gateway cannot — zero gateway replicas means the entire platform is unreachable. A PodDisruptionBudget guarantees Kubernetes never voluntarily evicts enough gateway pods (during a node drain or cluster upgrade) to drop below a safe minimum, and a HorizontalPodAutoscaler ensures capacity actually tracks load, since the gateway sees 100% of platform traffic while any single backend sees only its own slice.
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: gateway-service
namespace: hoa-platform
spec:
minAvailable: 2
selector:
matchLabels: { app: gateway-service }minAvailable: 2 rather than a percentage is deliberate at this replica count — a percentage-based budget on a small replica count can round down to a minimum of zero or one, which for the platform's single point of entry is not an acceptable floor during a voluntary disruption like a node drain.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: gateway-service
namespace: hoa-platform
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: gateway-service
minReplicas: 3
maxReplicas: 12
metrics:
- type: Resource
resource:
name: cpu
target: { type: Utilization, averageUtilization: 60 }minReplicas: 3 (not 2, matching the PDB's minAvailable) leaves headroom for a rolling update to proceed without ever violating the PodDisruptionBudget — with only 2 minReplicas equal to the PDB floor, a voluntary rollout would have nowhere safe to evict from.
Confirming Backend Isolation Actually Holds
Applied exercise
Decide the gateway's behavior during a zero-downtime TLS certificate rotation
The Ingress's TLS certificate (hoa-platform-tls) is approaching expiry and needs rotation, and unlike the Authentication module's signing-key rotation, this rotation is entirely handled by the Ingress controller and cert-manager rather than application code.
- State what, if anything, gateway-service's own application code needs to change or be aware of during a TLS certificate rotation, given the Ingress terminates TLS before traffic reaches the gateway pod.
- Explain why a TLS certificate rotation is a fundamentally different operational event from the Authentication module's JWT signing-key rotation, despite both being described as 'key rotations'.
- Propose one monitoring signal that would catch a certificate rotation that silently failed (e.g., an expired cert being served) before it caused a client-visible outage.
Deliverable
A short written explanation distinguishing TLS certificate rotation from JWT signing-key rotation, plus a proposed monitoring signal.
Completion checks
- The answer correctly identifies that TLS rotation, unlike JWT key rotation, requires no gateway application code changes since termination happens at the Ingress layer.
- The proposed monitoring signal would actually detect an expiring or already-expired certificate before clients are affected.
Why is a NetworkPolicy required in addition to setting backend services' type to ClusterIP, rather than ClusterIP alone being sufficient isolation?
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.