Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8bc9f65
docs(plan): mark S8 done and expand the S12 deck review scope
patterncatalyst Oct 11, 2026
187d93e
docs(plan): S9 minikube plan
patterncatalyst Oct 11, 2026
b63d6e9
docs(plan): host Helm is 4.3.0 for S9
patterncatalyst Oct 11, 2026
48f2e97
docs(plan): S9 in progress, pins for minikube step
patterncatalyst Oct 11, 2026
6f13fc1
feat(demo-05): kustomize manifests and scripts for minikube
patterncatalyst Oct 11, 2026
3ccf368
ci: k8s selector kind and manifest validation job
patterncatalyst Oct 11, 2026
9879196
docs(§R): minikube topology and image-flow diagrams
patterncatalyst Oct 11, 2026
931b56b
docs(demo-05): minikube README, prerequisites pins, selector docs
patterncatalyst Oct 11, 2026
13bd819
docs(§R): Appendix R draft, glossary order 99, A-R counts, CLAUDE.md
patterncatalyst Oct 11, 2026
eed7011
docs(§R): Appendix R with recorded run, S9 evidence in the plan
patterncatalyst Oct 11, 2026
1479428
docs(plan): reword S9 deviation note
patterncatalyst Oct 11, 2026
aa3ee98
fix(demo-05): export Quarkus traces over OTLP/HTTP
patterncatalyst Oct 11, 2026
9489af0
fix(demo-05): .NET OTLP over HTTP, register consumer sources, SERVICE…
patterncatalyst Oct 11, 2026
819b862
ci: select the k8s directory for deleted manifests
patterncatalyst Oct 11, 2026
0a09f38
fix(demo-05): restore kubectl context after start, idempotent teardow…
patterncatalyst Oct 11, 2026
3975405
feat(demo-05): OpenTelemetry traces for Go and C++, align service names
patterncatalyst Oct 11, 2026
c36c778
fix(dotnet): restore and publish in one RUN with a locked NuGet cache…
patterncatalyst Oct 11, 2026
cb76442
docs(demo-05): Appendix R traces for all six languages, S9 repair rou…
patterncatalyst Oct 11, 2026
cbeab56
fix(demo-05): restore kubectl context on any exit from setup-profile
patterncatalyst Oct 11, 2026
271704d
feat(demo-05): propagate trace context through Kafka headers in Python
patterncatalyst Oct 11, 2026
d257c92
docs(plan): S9 final recheck and follow-ups
patterncatalyst Oct 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 61 additions & 1 deletion .github/workflows/examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ jobs:
cpp: ${{ steps.select.outputs.cpp }}
compose: ${{ steps.select.outputs.compose }}
scripts: ${{ steps.select.outputs.scripts }}
k8s: ${{ steps.select.outputs.k8s }}
steps:
- uses: actions/checkout@v7
with:
Expand Down Expand Up @@ -497,12 +498,71 @@ jobs:
done < <(jq -r '.[]' <<<"$FILES")
exit "$failed"

# Kubernetes manifests: render the infrastructure and every language overlay, validate
# against the 1.36.5 schemas (and the Strimzi CRDs), and assert the conventions the
# minikube run relies on. No cluster is needed.
k8s:
name: Kubernetes manifests
needs: changes
if: needs.changes.outputs.k8s != '[]'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
# The runner's kubectl is not pinned; install the version the cluster runs.
- name: Install kubectl v1.36.5 and kubeconform v0.8.0
env:
KUBECTL_SHA256: 33bc88a24c3b09cf55bfd59e6ca977881b61fc3f25ab65bc80c65b430248648e
run: |
mkdir -p "$HOME/.local/bin" && cd "$RUNNER_TEMP"
curl -fsSLO https://dl.k8s.io/release/v1.36.5/bin/linux/amd64/kubectl
echo "$KUBECTL_SHA256 kubectl" | sha256sum -c -
install -m 0755 kubectl "$HOME/.local/bin/kubectl"
base=https://github.com/yannh/kubeconform/releases/download/v0.8.0
curl -fsSLO "$base/kubeconform-linux-amd64.tar.gz"
curl -fsSL "$base/CHECKSUMS" | grep ' kubeconform-linux-amd64.tar.gz$' | sha256sum -c -
tar -xzf kubeconform-linux-amd64.tar.gz kubeconform
install -m 0755 kubeconform "$HOME/.local/bin/kubeconform"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Render, validate and assert
env:
DIRS: ${{ needs.changes.outputs.k8s }}
# datreeio/CRDs-catalog pinned to a commit; it carries the Strimzi schemas.
CRDS: https://raw.githubusercontent.com/datreeio/CRDs-catalog/63669a570e231d4f1f8396d229a1de512bcf0a34/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json
run: |
failed=0
while IFS= read -r dir; do
[ -d "$dir" ] || { echo "::error::$dir not found"; failed=1; continue; }
kubeconform -v
kubeconform -strict -summary -kubernetes-version 1.36.5 -schema-location default -schema-location "$CRDS" "$dir/namespace.yaml" || failed=1
targets=("$dir/infra")
for o in "$dir"/overlays/*/; do targets+=("${o%/}"); done
for t in "${targets[@]}"; do
echo "::group::$t"
# infra reads the shared schema from ../../db/init, outside its directory.
kubectl kustomize --load-restrictor LoadRestrictionsNone "$t" > "$RUNNER_TEMP/render.yaml" || { echo "::error::render failed: $t"; failed=1; echo "::endgroup::"; continue; }
kubeconform -strict -summary -kubernetes-version 1.36.5 -schema-location default -schema-location "$CRDS" "$RUNNER_TEMP/render.yaml" || { echo "::error::kubeconform failed: $t"; failed=1; }
if grep -nE 'image: .*:latest|:latest$' "$RUNNER_TEMP/render.yaml"; then echo "::error::$t uses :latest"; failed=1; fi
case "$t" in
*/overlays/*)
lang="$(basename "$t")"
[ "$(grep -cE "image: localhost/cndp-05/${lang}-[a-z-]+:v1$" "$RUNNER_TEMP/render.yaml")" -eq 3 ] || { echo "::error::$t must use 3 images localhost/cndp-05/${lang}-<svc>:v1"; failed=1; }
[ "$(grep -c 'imagePullPolicy: Never' "$RUNNER_TEMP/render.yaml")" -eq 3 ] || { echo "::error::$t must set imagePullPolicy: Never on 3 containers"; failed=1; }
grep -q 'nodePort: 30080' "$RUNNER_TEMP/render.yaml" || { echo "::error::$t lacks nodePort 30080"; failed=1; } ;;
*/infra)
grep -q 'nodePort: 30300' "$RUNNER_TEMP/render.yaml" || { echo "::error::$t lacks nodePort 30300"; failed=1; }
grep -vE 'image: (docker.io|quay.io)/' <(grep -E '^\s+image: ' "$RUNNER_TEMP/render.yaml") && { echo "::error::$t has an image that is not fully qualified"; failed=1; } ;;
esac
echo "::endgroup::"
done
done < <(jq -r '.[]' <<<"$DIRS")
exit "$failed"

# The one check branch protection requires: passes when every job above
# succeeded or was skipped by `changes`, fails otherwise.
examples-ok:
name: examples-ok
if: always()
needs: [changes, jvm, dotnet, go, python, cpp, compose, scripts]
needs: [changes, jvm, dotnet, go, python, cpp, compose, scripts, k8s]
runs-on: ubuntu-latest
steps:
- env:
Expand Down
8 changes: 6 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ examples/
Default infra: **Docker Engine with `docker compose`** on Fedora/RHEL (`lgtm-docker-stack`
patterns). Run an example with `docker compose up -d --build --wait` and tear it down with
`docker compose down -v`. All examples, including 09, 12, 22 and 24, run on compose.
minikube (`lgtm-minikube-stack` patterns) and OpenShift Local are optional paths added later in
Example 05 also runs on minikube (`examples/05-event-driven/k8s/`, Appendix R): profile `cndp-k8s`
(docker driver, containerd, Kubernetes v1.36.5), NodePorts 30080 and 30300 published on 127.0.0.1 at
creation, Strimzi 1.2.0 for Kafka, kustomize manifests, `RUNTIME=kubernetes ./verify.sh`. Always pass
`--keep-context` to `minikube start` (setup-profile.sh also restores the prior current-context, because minikube's restart path overrides the flag) and `--context cndp-k8s` to kubectl; never use port-forward,
`minikube tunnel` or `minikube service`. OpenShift Local (Appendix S) is added later in
the modernization plan. Prometheus/Mimir is published on host port 19090 (Cockpit holds 9090
on Fedora/RHEL). Each example must run standalone.

Expand Down Expand Up @@ -135,7 +139,7 @@ Do not use `gh cache delete --all`: it also removes the entries the current poli
the examples that use it.
- Locks are regenerated with `conan lock create --update` (no lockfile in) so transitive packages take
the newest ConanCenter version; stale transitive pins are lifted with `override=True` in the
conanfile (fmt, lz4, util-linux-libuuid, jsoncpp). Held on purpose: abseil 20260107.1 (protobuf 6.33.5),
conanfile (fmt, lz4, util-linux-libuuid, jsoncpp). Held back: abseil 20260107.1 (protobuf 6.33.5),
protobuf 6.33.5 (grpc is below 7), opentelemetry-proto 1.7.0 (opentelemetry-cpp), taocpp-pegtl 3.2.8
(cppgraphqlgen needs 3.x), lua 5.4.8 (sol2), librdkafka 2.14.2 and opentelemetry-cpp 1.26.0 (ConanCenter
behind upstream). flex and bison get `MAKEINFO=true` through a scoped Conan conf, not a fake binary.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ the *Designing Cloud-Native APIs* deck series; the decks themselves live in

## What's inside

- **A setup guide, 13 chapters, 17 deep-dive appendices (A–Q), and a glossary** —
- **A setup guide, 13 chapters, 18 deep-dive appendices (A–R), and a glossary** —
covering REST, gRPC, GraphQL, WebSockets, event-driven architecture, schemas and
versioning, observability, resilience, and the patterns that tie them together.
- **Six languages, one set of tabs** — every code sample appears in Spring Boot,
Expand Down Expand Up @@ -55,7 +55,7 @@ cd python && docker compose down -v
| Path | What it holds |
|---|---|
| `_parts/` | the five Parts that form the homepage cards |
| `_docs/` | the setup guide, chapters, appendices A–Q, and the glossary |
| `_docs/` | the setup guide, chapters, appendices A–R, and the glossary |
| `_includes/codetabs.html` + `assets/js/codetabs.js` | the multi-language code tabs (no plugin) |
| `assets/diagrams/` | paired `name.svg` + `name.excalidraw` figures |
| `lgtm-presentation/` | the six source decks + `BUILD.md` manifest |
Expand Down
5 changes: 3 additions & 2 deletions _docs/05-event-driven.md
Original file line number Diff line number Diff line change
Expand Up @@ -1290,8 +1290,9 @@ redelivery is the at-least-once guarantee, and the reason your handler dedupes.
`kcat` or the Kafka console tools make the offsets and redelivery visible.

The code is in [`examples/05-event-driven/`](https://github.com/patterncatalyst/cloud-native-design-patterns/tree/main/examples/05-event-driven/). The run script there builds and runs
it; its `README.md` covers what it does and how to drive it.
it; its `README.md` covers what it does and how to drive it. The same example, with Kafka managed by
the Strimzi operator, also runs on a Kubernetes cluster; Appendix R covers that deployment.

---
*Verification status: verified — [`examples/05-event-driven/`](https://github.com/patterncatalyst/cloud-native-design-patterns/tree/main/examples/05-event-driven/) passes 10/10 checks in each of the six languages on Docker Engine (2026-10-10)
*Verification status: verified — [`examples/05-event-driven/`](https://github.com/patterncatalyst/cloud-native-design-patterns/tree/main/examples/05-event-driven/) passes 10/10 checks in each of the six languages on Docker Engine (2026-10-10) and on minikube (2026-10-11, Appendix R)
(event publication to order.placed, fan-out to two independent consumer groups (shipping and notification), and idempotent consumers under a redelivered duplicate event).*
17 changes: 9 additions & 8 deletions _docs/29-appendix-p-dotnet-on-linux.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,9 @@ Every example in this book uses a two-stage Containerfile:
FROM registry.access.redhat.com/ubi9/dotnet-100:9.8-1791421718 AS build
ENV NUGET_PACKAGES=/nuget-cache
WORKDIR /opt/app-root/src
COPY --chown=1001:0 OrderService/OrderService.csproj OrderService/
RUN --mount=type=cache,target=/nuget-cache,uid=1001 dotnet restore OrderService/OrderService.csproj
COPY --chown=1001:0 OrderService/ OrderService/
RUN --mount=type=cache,target=/nuget-cache,uid=1001 \
dotnet publish OrderService/OrderService.csproj -c Release --no-restore -o /opt/app-root/app
RUN --mount=type=cache,target=/nuget-cache,uid=1001,sharing=locked \
dotnet publish OrderService/OrderService.csproj -c Release -o /opt/app-root/app

FROM registry.access.redhat.com/ubi9/dotnet-100-aspnet:9.8-1791363520
COPY --from=build --chown=1001:0 --chmod=g=u /opt/app-root/app /opt/app-root/app
Expand All @@ -184,10 +182,13 @@ ENTRYPOINT ["dotnet", "OrderService.dll"]
```

**Stage 1 (build)** uses the SDK image (`ubi9/dotnet-100`, .NET 10.0.12, ~780 MB). It
restores NuGet packages first (this layer is cached if the `.csproj` has not changed),
then copies source and publishes. Both steps run as the image's non-root user (UID 1001,
group 0); a BuildKit cache mount keeps the NuGet package cache across builds, and
`--no-restore` makes the publish reuse what the restore step fetched. Every package in the
copies the source and publishes. The publish restores the NuGet packages in the same
`RUN` that mounts the package cache, as the image's non-root user (UID 1001, group 0); the
BuildKit cache mount keeps the package cache across builds, so a rebuild restores from disk
without the network (`sharing=locked` serializes images built in parallel, which would otherwise
extract into the same package directory at once). Restore and publish stay in one step because a separate restore layer
can be reused after BuildKit has dropped the cache mount, and a later publish with
`--no-restore` then fails with NETSDK1064 (packages not found). Every package in the
`.csproj` files is pinned to an exact version. Red Hat does not publish a `ubi10` .NET
image, so the examples stay on `ubi9` and pin the exact tags.

Expand Down
2 changes: 1 addition & 1 deletion _docs/29-glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "Acronyms"
marker: "★"
label: "Glossary"
order: 31
order: 99
part: "Deep-dive appendices"
description: "Every acronym used across the book, expanded and placed in context — grouped by where it shows up: the wire, the data layer, events, the platform, security, observability, and the domain."
---
Expand Down
Loading
Loading