Practical lab guide
Lab: run the MicroBank baseline in kind
Package the existing local application as Kubernetes workloads and verify a real deposit.
Kubernetes
Predict before you try
Does a ready Pod prove that a Service request and the saved MicroBank operation will succeed?
Try it: 4. Start and verify the real applications →On this page
Prerequisites and outcome
Complete the seven DevOps Foundations implementation labs. Keep their Accounts startup refactor, Dockerfiles, Terraform configuration, Ansible template, and Python probe. Work in that MicroBank checkout, not in the LearnWithSK repository.
Use the dedicated cluster from Architecture and kind. Follow the implementation steps below and record what happens in your environment. The deployment remains unverified until you run and check it.
Your goal is to reproduce the deposit-to-Ledger flow from Foundations. The optional Auth0 UI is outside this baseline. Backend authorization and Accounts settlement consumption remain missing from this baseline. Use only local synthetic data.
1. Select images and free the previous profile
Use the already-built images from Foundations. Check that STUDY_TAG names the actual build, then stop only that Compose project. This releases loopback ports 24666, 28080, and 28081 without deleting its data.
: "${STUDY_TAG:?Select the previously built source revision}"
: "${FLOCI_IMAGE:?Select your reviewed Floci image tag or digest}"
./scripts/study/compose.sh stop
kind load docker-image --name microbank-advanced \
"microbank-study/accounts:$STUDY_TAG" "microbank-study/ledger:$STUDY_TAG"
mkdir -p infra/study/k8s .local/k8s-runtime .local/k8s-terraform evidence
kubectl --context kind-microbank-advanced create namespace microbank
kubectl --context kind-microbank-advanced annotate namespace microbank \
learning.learnwithsk.dev/local-only=true
If the namespace already exists, inspect its owner and contents before continuing; do not overwrite an unrelated namespace. Keep .local/, Terraform state, runtime files, and evidence out of Git. Reuse the pinned Floci image from Foundations and record the architecture. This Kubernetes adaptation is documentation-reviewed; the downloadable starter execution evidence covers Compose. Do not introduce real AWS credentials.
2. Generate explicit local manifests
Save scripts/study/k8s_baseline.py. JSON is accepted as a Kubernetes manifest and avoids requiring an extra YAML package for this generator. Review the generated objects before applying them.
import json
import os
from pathlib import Path
ROOT = Path('infra/study/k8s')
ROOT.mkdir(parents=True, exist_ok=True)
TAG = os.environ['STUDY_TAG']
FLOCI = os.environ['FLOCI_IMAGE']
if not TAG or not FLOCI:
raise SystemExit('Select actual application and emulator images')
def service(name, port):
return {'apiVersion': 'v1', 'kind': 'Service', 'metadata': {'name': name},
'spec': {'selector': {'app': name}, 'ports': [{'port': port, 'targetPort': port}]}}
def pvc(name):
return {'apiVersion': 'v1', 'kind': 'PersistentVolumeClaim',
'metadata': {'name': name},
'spec': {'accessModes': ['ReadWriteOnce'],
'resources': {'requests': {'storage': '1Gi'}}}}
def workload(name, image, port, request_memory, limit_memory, env=None,
secret=None, volume=None, probe=None):
container = {'name': name, 'image': image, 'imagePullPolicy': 'IfNotPresent',
'ports': [{'containerPort': port}],
'resources': {'requests': {'cpu': '100m', 'memory': request_memory},
'limits': {'memory': limit_memory}}}
if env:
container['env'] = [{'name': key, 'value': value} for key, value in env.items()]
if secret:
container['envFrom'] = [{'secretRef': {'name': secret}}]
if probe:
container['startupProbe'] = {**probe, 'periodSeconds': 5, 'timeoutSeconds': 3,
'failureThreshold': 36}
container['readinessProbe'] = {**probe, 'periodSeconds': 5, 'timeoutSeconds': 3}
pod = {'automountServiceAccountToken': False, 'containers': [container]}
if volume:
claim, path = volume
container['volumeMounts'] = [{'name': 'data', 'mountPath': path}]
pod['volumes'] = [{'name': 'data', 'persistentVolumeClaim': {'claimName': claim}}]
return {'apiVersion': 'apps/v1', 'kind': 'Deployment', 'metadata': {'name': name},
'spec': {'replicas': 1, 'strategy': {'type': 'Recreate'},
'selector': {'matchLabels': {'app': name}},
'template': {'metadata': {'labels': {'app': name}}, 'spec': pod}}}
infra = []
for name, database in [('accounts-db', 'accounts_dev'), ('ledger-db', 'ledger_dev')]:
infra.extend([
pvc(name), service(name, 5432),
workload(name, 'postgres:15-bookworm', 5432, '128Mi', '512Mi',
env={'POSTGRES_DB': database, 'POSTGRES_USER': 'postgres'},
secret='database-fixture', volume=(name, '/var/lib/postgresql/data'),
probe={'exec': {'command': ['pg_isready', '-U', 'postgres', '-d', database]}})
])
infra.extend([
pvc('floci'), service('floci', 4566),
workload('floci', FLOCI, 4566, '512Mi', '1536Mi',
env={'FLOCI_HOSTNAME': 'floci', 'FLOCI_BASE_URL': 'http://floci:4566',
'FLOCI_STORAGE_MODE': 'persistent', 'FLOCI_STORAGE_PERSISTENT_PATH': '/app/data',
'FLOCI_DEFAULT_REGION': 'ca-central-1'}, volume=('floci', '/app/data'),
probe={'httpGet': {'path': '/_floci/health', 'port': 4566}})
])
apps = [
service('accounts', 8000),
workload('accounts', f'microbank-study/accounts:{TAG}', 8000, '256Mi', '512Mi',
secret='microbank-runtime', probe={'httpGet': {'path': '/health', 'port': 8000}}),
service('ledger', 8001),
workload('ledger', f'microbank-study/ledger:{TAG}', 8001, '512Mi', '1Gi',
env={'SPRING_JPA_HIBERNATE_DDL_AUTO': 'update',
'JAVA_TOOL_OPTIONS': '-XX:MaxRAMPercentage=60'},
secret='microbank-runtime', probe={'httpGet': {'path': '/v1/health', 'port': 8001}})
]
for filename, items in [('infra.json', infra), ('apps.json', apps)]:
ROOT.joinpath(filename).write_text(json.dumps(
{'apiVersion': 'v1', 'kind': 'List', 'items': items}, indent=2) + '\n')
These are single-replica, Recreate lab workloads. The database fixture is not HA, and kind's default storage does not survive cluster deletion. The memory values are starting limits; measure actual usage before adjusting them. A PVC requires a working default StorageClass; inspect it before proceeding. A replacement Accounts Pod does not overlap the old publisher in this baseline.
python3 scripts/study/k8s_baseline.py
kubectl --context kind-microbank-advanced get storageclass
kubectl --context kind-microbank-advanced -n microbank create secret generic database-fixture \
--from-literal=POSTGRES_PASSWORD=study-only
Floci needs no LocalStack token Secret. The manifest supplies local endpoint and persistence settings. Keep dummy database/application credentials in the separate local Secrets.
kubectl --context kind-microbank-advanced -n microbank apply -f infra/study/k8s/infra.json
kubectl --context kind-microbank-advanced -n microbank get pvc,pods
kubectl --context kind-microbank-advanced -n microbank rollout status deploy/accounts-db --timeout=180s
kubectl --context kind-microbank-advanced -n microbank rollout status deploy/ledger-db --timeout=180s
kubectl --context kind-microbank-advanced -n microbank rollout status deploy/floci --timeout=180s
If a PVC remains Pending, solve the storage issue before starting the application. Do not remove persistence just to hide the failure.
3. Bootstrap the separate emulator instance
Confirm port 24666 is free after stopping the Compose profile. In a separate terminal, keep this loopback-only forwarding process running, then check http://127.0.0.1:24666/_floci/health identifies Floci 2.1.0 before applying:
kubectl --context kind-microbank-advanced -n microbank port-forward \
--address 127.0.0.1 service/floci 24666:4566
Reuse the reviewed Foundations Terraform configuration, but not its state: the Kubernetes emulator is a different instance. These commands assume the Foundations provider lock file already exists.
cp infra/study/terraform/main.tf .local/k8s-terraform/main.tf
cp infra/study/terraform/.terraform.lock.hcl .local/k8s-terraform/.terraform.lock.hcl
terraform -chdir=.local/k8s-terraform init
terraform -chdir=.local/k8s-terraform plan -out=local.tfplan
terraform -chdir=.local/k8s-terraform apply local.tfplan
terraform -chdir=.local/k8s-terraform output -json runtime > .local/k8s-runtime/runtime.json
ansible-playbook -i localhost, infra/study/ansible/configure.yml \
-e "study_dir=$PWD/.local/k8s-runtime"
kubectl --context kind-microbank-advanced -n microbank create secret generic microbank-runtime \
--from-env-file=.local/k8s-runtime/runtime.env
The existing template works because the Kubernetes Services deliberately retain accounts-db, ledger-db, and floci names. SNS/SQS names and the path-style queue URL contract also remain the same. Only the disposable runtime instance and its Terraform state differ. Keep the generated files private.
4. Start and verify the real applications
kubectl --context kind-microbank-advanced -n microbank apply -f infra/study/k8s/apps.json
kubectl --context kind-microbank-advanced -n microbank rollout status deploy/accounts --timeout=180s
kubectl --context kind-microbank-advanced -n microbank rollout status deploy/ledger --timeout=180s
Keep each forwarding command in its own terminal, then run the unchanged Foundations probe from another terminal:
kubectl --context kind-microbank-advanced -n microbank port-forward \
--address 127.0.0.1 service/accounts 28080:8000
kubectl --context kind-microbank-advanced -n microbank port-forward \
--address 127.0.0.1 service/ledger 28081:8001
python3 scripts/study/probe.py --create --case-file evidence/kind-case.json
python3 scripts/study/probe.py --verify evidence/kind-case.json
kubectl --context kind-microbank-advanced -n microbank get pods \
-o custom-columns=NAME:.metadata.name,IMAGE:.status.containerStatuses[*].imageID
Acceptance is a real ledger_verified: true result with one matching entry and a 1,000-cent balance for the fresh account. Accounts may still report pending. Port-forwards select a Pod and can terminate when it is replaced; restart them after rollouts. Do not treat a stale forwarding process as proof the Service is broken.
Checkpoint, recovery, and cleanup
Keep versions, rendered manifests, image IDs, resource observations, case file, and the actual result. If any step fails, stop and use the troubleshooting bite. Do not mark the lab verified from YAML parsing alone.
Continue using this baseline for later modules. When finished with the entire track, stop scheduled checks and optional tools, destroy these emulator resources through this separate Terraform directory while forwarding still works, then remove only the named kind cluster. Cluster deletion destroys its local volumes. Preserve any needed evidence first; do not copy the lost emulator's state into a new instance.
Read the generator as a design you now understand
Before running it, find the same links you practised in classroom: each Service selector matches Pod labels, target ports match listeners, and the Pod template receives configuration. The extra pieces are persistent claims, private runtime inputs, and dependencies that must exist first. infra.json supplies databases/emulator; apps.json supplies the APIs. Keeping them separate is what makes the later GitOps ownership exercise understandable.
Treat each wait as a local checkpoint. PVCs Bound and processes Ready permit the next inspection; they do not prove the final deposit. If rollout passes but the probe fails, follow the transaction across Accounts, outbox, queue, and Ledger using the earlier design rather than rerunning the entire setup. Save the same-case result for GitOps and observability.
Sources
MicroBank baseline source↗, kind image loading↗, PVC behavior↗, port forwarding↗, and Floci SQS URL strategy↗.
Prove what you learned
Inspect the result
Retain context/namespace, desired/observed revision, relevant events and behaviour evidence.
Explain it in your own words
Explain why your observation supports the stated outcome, and name one thing it does not prove.
Try a small variation
Change one supported fixture constraint and predict the failing layer; restore it and rerun the same check.
Your evidence should show
- Record the context/revision or label the work as a paper or prepared fixture.
- Compare the prediction with the actual result from “4. Start and verify the real applications”.
- Retain context/namespace, desired/observed revision, relevant events and behaviour evidence.
- Explain the transfer challenge and retain the same verification question.
Hint
Trace desired state, scheduling, process, readiness, Service selection and application result.
Answer guide: compare after predicting
No. Inspect the specific failing layer and repeat the same caller-side and application checks.
Compare this with your actual observation. A paper answer is useful reasoning practice; record execution separately.
Recall container ports, process health and persistent storage. Revisit the previous lesson.
Save the prediction, result and explanation ↓Your notes and evidence
Record observations, questions, or links to your work. Keep credentials out of your notes.
Back up or restore this path
Progress and notes stay in this browser. A backup contains only this learning path.