Solutions — Homework 11
Lesson 11 · Cloud Infrastructure & DevOps
Task 1 — Rate-limit annotations
values.yaml additions (already included in the shipped chart; shown here for reference):
ingress:
...
rateLimitRps: 0 # 0 = disabled
rateLimitBurst: 0 # 0 = nginx default of 5
Patch to templates/ingress.yaml — inside the annotations: block:
{{- if gt (int .Values.ingress.rateLimitRps) 0 }}
nginx.ingress.kubernetes.io/limit-rps: {{ .Values.ingress.rateLimitRps | quote }}
nginx.ingress.kubernetes.io/limit-burst-multiplier: {{ .Values.ingress.rateLimitBurst | default 5 | quote }}
{{- end }}
Common mistake — type mismatch in Go templates
Setting rateLimitRps as a string ("5") in a values overlay and then evaluating it with if .Values.ingress.rateLimitRps will always render the annotation because non-empty strings are truthy in Go templates — even "0" is truthy. Always cast to int and compare greater-than-zero: gt (int .Values.ingress.rateLimitRps) 0. This gives a clear disabled=0 / enabled=N contract that survives typos in values files and --set overrides.
Task 2 — Wildcard cert via dns-01
dns-01 ClusterIssuer (Cloudflare):
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod-dns
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: ops@example.com
privateKeySecretRef:
name: letsencrypt-prod-dns-key
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token
key: token
Wildcard Certificate resource:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: example-com-wildcard
namespace: shortener
spec:
secretName: example-com-wildcard-tls
issuerRef:
name: letsencrypt-prod-dns
kind: ClusterIssuer
dnsNames:
- "*.example.com"
- "example.com"
Why dns-01 is required for wildcards: http-01 challenges require the ACME server to fetch a token from a specific URL at http://<host>/.well-known/acme-challenge/<token>. A wildcard like *.example.com covers infinite possible subdomains — there is no single host to serve the challenge from, and serving it from example.com itself does not validate the wildcard. dns-01 places a TXT record at _acme-challenge.example.com instead; one TXT record covers every name under the domain, so the wildcard validation works. The trade-off is that dns-01 requires API credentials for your DNS provider stored in the cluster as a Secret, and the credential scope must be narrow (ideally scoped to the single zone, not your full account).
Common mistake — pairing wildcards with http-01
cert-manager will accept a Certificate requesting *.example.com with an http-01 issuer without error at admission time. The Order will be created and will then fail permanently with reason: Failed because the ACME server rejects http-01 for wildcards per RFC 8555. Always pair wildcard dnsNames with a dns-01 solver.
Task 3 — Second app sharing the Ingress controller
Greeter namespace + service (quick scaffold):
kubectl create namespace greeter
kubectl -n greeter create deployment greeter \
--image=hashicorp/http-echo:latest \
-- -text="Hello from greeter"
kubectl -n greeter expose deployment greeter --port=80 --target-port=5678
Greeter Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: greeter
namespace: greeter
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts: [greeter.example.com]
secretName: greeter-tls
rules:
- host: greeter.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: greeter
port: {number: 80}
Cluster-wide vs per-namespace boundary: ingress-nginx is cluster-wide — one Deployment and one Service handle every Ingress in every namespace, routing by Host header. The ClusterIssuer is also cluster-wide — both apps reference letsencrypt-prod by name from any namespace without needing a local copy. However, Certificate, Order, and Challenge resources, and the resulting TLS Secret, are all namespaced: each app’s cert artefacts live alongside the app in its own namespace. This means shortener’s shortener-tls Secret in namespace shortener cannot be mounted directly by greeter’s Ingress in namespace greeter — Secrets do not cross namespace boundaries. The standard pattern for sharing a wildcard cert across namespaces is to use a Secret mirroring operator (such as Reflector or Kubernetes Replicator), or to use External Secrets Operator to inject the cert from a central vault into each namespace that needs it.
Common mistake — referencing another namespace’s Secret
Attempting to use secretName: shortener-tls in greeter’s Ingress fails silently: ingress-nginx will look for the Secret in the greeter namespace, not find it, and serve an empty certificate. The browser shows “NET::ERR_CERT_INVALID”. Each app needs its own Certificate (issued from the same ClusterIssuer) into its own namespace, or a mirroring mechanism for wildcards.
Module 5 — observability (Prometheus + Grafana + Loki for the four golden signals), HA + autoscaling (HPA + PDB + topology constraints), and GitOps with ArgoCD. The platform you built in M4 is now ready to host whatever production scrutiny it needs.