Lesson 10 Helm Chart for the Two Services
Theme:

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.