Homework 10 Solutions — Helm Chart for the Two Services
Lesson 10 · Cloud Infrastructure & DevOps · Reference solutions
Solution to Task 1 — Chart-test hook
Create sample-app/helm/templates/tests/test-api-healthz.yaml:
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ "{{" }} .Release.Name {{ "}}" }}-test-healthz"
annotations:
"helm.sh/hook": test
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: curl
image: curlimages/curl:8.5.0
command: ["sh", "-c"]
args:
- |
set -eu
curl -fsS "http://{{ "{{" }} include "shortener.api.fullname" . {{ "}}" }}/healthz" \
&& echo "healthz ok"
Run the test against the dev release:
helm test shortener -n shortener --logs
Expected output includes:
NAME: shortener-test-healthz
LAST DEPLOYED: ...
NAMESPACE: shortener
STATUS: deployed
...
TEST SUITE: shortener-test-healthz
Last Started: ...
Last Completed: ...
Phase: Succeeded
Common mistake: omitting hook-delete-policy
Without hook-delete-policy: hook-succeeded, the test Job’s Pod persists in the namespace after it passes. On subsequent test runs, helm test will find an old pod in Completed state and may report a confusing result. The before-hook-creation policy deletes the old pod before creating a new one, keeping the namespace clean. Both policies together are the canonical pattern: clean up before creating, clean up after success.
Solution to Task 2 — Values schema
Create sample-app/helm/values.schema.json:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "shortener chart values",
"type": "object",
"required": ["image", "api", "worker", "postgres"],
"properties": {
"image": {
"type": "object",
"required": ["repository"],
"properties": {
"repository": {"type": "string", "minLength": 1},
"pullPolicy": {"type": "string", "enum": ["Always", "IfNotPresent", "Never"]}
}
},
"api": {
"type": "object",
"properties": {
"replicas": {"type": "integer", "minimum": 1}
}
},
"postgres": {
"type": "object",
"properties": {
"storage": {
"oneOf": [
{"type": "string", "enum": ["ephemeral"]},
{"type": "string", "pattern": "^[0-9]+Gi$"}
]
}
}
}
}
}
Verify:
# Should pass
helm lint helm/ -f helm/values-prod.yaml
# Should fail with schema error
helm lint helm/ --set api.replicas=0
Expected error on the second command:
Error: values don't meet the specifications of the schema(s) in the following chart(s):
shortener:
- api.replicas: Must be greater than or equal to 1
Common mistake: additionalProperties: false at the root
Adding "additionalProperties": false at the root of the schema means any key not listed in properties causes a validation failure. This breaks the moment a user adds a perfectly valid new key (e.g., for a custom subchart or a future feature). Keep additionalProperties permissive at the root; only lock down deeply-nested known fields like image.pullPolicy where an enum makes the options explicit.
Solution to Task 3 — CI values file
Create sample-app/helm/ci/test-values.yaml:
# Picked up automatically by ct install.
postgres:
enabled: true
storage: ephemeral
secrets:
inChart: true
image:
pullPolicy: IfNotPresent
api:
probes:
enabled: true
ingress:
enabled: false
No CT configuration change is needed. By convention, every ci/*-values.yaml file under the chart directory becomes a separate install permutation for ct install. To run two install permutations (e.g., one with postgres.enabled: true and one with false), add a second ci/test-values-without-postgres.yaml.
In the CI workflow log you will see:
Installing chart 'shortener' with values file '.../ci/test-values.yaml'...
Common mistake: putting CI overrides into values.yaml
It’s tempting to set postgres.storage: ephemeral in values.yaml so CI “just works” without a separate ci/ file. Don’t. The values.yaml defaults feed into every install, including production. A CI override that accidentally reaches prod because someone ran helm install -f values.yaml without a prod override file is a real incident waiting to happen. Always keep CI-specific overrides in ci/ — completely separate from the per-env files and the defaults.
What’s next
Lesson 11 — Ingress, cert-manager, ExternalDNS. The ingress.enabled toggle the chart already has becomes a real domain and TLS certificate in 15 minutes. The values are already in place; the only missing piece is the Ingress controller and the cert-manager CRDs.