Reference — Lesson Anatomy#
A training is a MkDocs site: mkdocs.yaml says which pages exist and in what order, and each page is Markdown with interactive blocks written as HTML comments. This page covers the structure; 04 — Interactive Blocks covers every block.
mkdocs.yaml — the steps, in order#
INHERIT: mkdocs-base.yaml # theme + extensions from the framework — keep it
training_name: "Kubernetes 101" # the name Orbital shows for this training
site_name: "Dynatrace Enablement Lab: Kubernetes 101"
repo_name: "View Code on GitHub"
repo_url: "https://github.com/dynatrace-wwse/enablement-kubernetes-101"
nav:
- "Welcome": index.md
- "Prerequisites": prerequisites.md
- "1. Deploy the Operator": 1-deploy-operator.md
- "2. Deploy the DynaKube": 2-deploy-dynakube.md
- "3. Restart Application Services": 3-restart-services.md
- "Resources": resources.md
navorder is step order. Eachnaventry becomes one step in the app; a page that is not innavis not imported.- One repo, several trainings: if every top-level
naventry is a section (a group of pages), each section is imported as its own training. - The app titles the training from
site_name; Orbital's catalog preferstraining_name. Set both. (MkDocs warns about the unknowntraining_namekey — that is expected.)
Front-matter — the catalog card#
Put YAML front-matter on the first page (index.md). It feeds the catalog card and filters, and is invisible to the learner:
---
description: Instrument a live Kubernetes cluster with Dynatrace from scratch.
tags: [kubernetes, observability, operator]
difficulty: beginner # beginner | intermediate | advanced | expert
duration: 90 # minutes
---
The shape of a step#
Every step follows the same rhythm — tell, do, check, solve:
# 1. Deploy the Dynatrace Operator
<!-- STEP_SETUP
commands:
- dynatraceEvalReadSaveCredentials
-->
Why this step matters, in two or three sentences. ← TELL
Run in the terminal: ← DO
helm install dynatrace-operator ...
## Validation
<!-- LAB_QUESTION
type: shell-verification
question: "Verify the Dynatrace Operator is Running"
buttonText: "Check Operator"
command: "checkOperatorReady"
expect:
operator: exit-zero
hint: "Run the install commands above, wait 30 seconds, then check again."
explanation: "The operator is Running."
-->
<!-- LAB_QUESTION
type: multiple-choice
question: "Which two components inject the agent in AppOnly mode?"
options:
- "A CSI driver and a mutating webhook"
- "A DaemonSet per namespace"
correct: 0
explanation: "The CSI driver delivers the code modules; the webhook injects them at pod creation."
-->
<!-- LAB_SOLUTION
reveal: |
Run the Helm install above. The framework wraps it in `dynatraceDeployOperator`.
commands:
- dynatraceDeployOperator
verify:
- checkOperatorReady
-->
Top to bottom: an optional STEP_SETUP (runs silently when the step opens), the tell and do in plain Markdown, then the checks — one against the container, one against the learner's understanding — and finally the solution that lets automation pass the step without a human.
Guidelines that make a training interactive and self-maintaining:
- Small steps. One idea, one or two actions, one or more checks.
- Every step gets a validation — a shell check, a DQL check, or a quiz.
- Every step that changes the environment gets a
LAB_SOLUTION. Without it the nightly test cannot run the step, and resume cannot rebuild the environment past it. A step that changes nothing (reading, a quiz, a provisioning sanity check) says so with aLAB_NO_SOLUTIONmarker. - Put the logic in
my_functions.sh. The check callscheckX, the solution calls a function — the Markdown stays readable and the logic is reused. - Scope every DQL check to the learner with
{{DT_SESSION_ID}}— see template variables.
Folder layout#
docs/
├── index.md ← front-matter + welcome (first nav entry)
├── 1-*.md, 2-*.md, … ← one file per step
├── resources.md
├── img/ ← images (referenced relatively: img/x.png)
├── snippets/ ← reusable MkDocs snippets (--8<--)
└── requirements/ ← MkDocs requirements (do not edit)
.assessment/
└── <id>.json ← scored assessments (LAB_QUESTIONAIRE: <id>)
Images are imported with the training — reference them relatively (img/x.png) and keep them small.
Never write a block inside a code example#
The app finds interactive blocks by scanning the raw Markdown for <!-- followed by a block name — it does not skip fenced code blocks. A "just an example" block inside a ```markdown fence therefore becomes a real question, setup or solution in your step. The same goes for inline code: writing a block's full opening comment in a sentence is enough to trigger it.
To show block syntax on a page (as this template does), keep the example in a file under docs/snippets/ and include it with --8<-- "snippets/…": MkDocs renders it on GitHub Pages, and the app strips snippet includes on import. In prose, name the block (LAB_SOLUTION) without its comment opener.
MkDocs formatting#
Admonitions (!!! tip, !!! warning, …), fenced code blocks, tables and images render both on GitHub Pages and in the app. A ```dql code block renders as a query the learner can open and run. --8<-- snippet includes and grid cards navigation are stripped on import — they are for the GitHub Pages site only.