Repository navigation
docs: migrate 12-factor tutorials to Canonical Kubernetes - #2882
florentianayuwono wants to merge 31 commits into
Conversation
Replace MicroK8s with Canonical Kubernetes throughout the 12-factor app
tutorials so they use CK8s exclusively, following up on the how-to guide
migration.
- Rework the shared tutorial setup (setup_stable.rst, setup_edge.rst) to
install and bootstrap Canonical Kubernetes (k8s snap), enable
local-storage and ingress, and register the cluster as a Juju cloud with
'juju add-k8s' before bootstrapping.
- Replace the MicroK8s registry push (rockcraft.skopeo copy to
localhost:32000) with a direct containerd image import ('ctr images
import') and reference the image without the registry prefix in the six
framework tutorials and their task.yaml tests.
- Update prose and 'juju status' sample output accordingly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove the MicroK8s options from the manage-web-app-charms how-to guides so they use Canonical Kubernetes exclusively, matching the tutorial migration. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
The six executable task suites still provision MicroK8s, so their CK8s commands cannot run until the test environment is updated.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Migrates the 12-factor tutorials from MicroK8s to Canonical Kubernetes.
Changes:
- Updates shared CK8s setup and Juju registration.
- Replaces registry pushes with containerd image imports.
- Refreshes tutorial instructions and sample output.
File summaries
| File | Summary |
|---|---|
docs/tutorial/kubernetes-charm-spring-boot.rst |
Updates Spring Boot tutorial for CK8s. |
docs/tutorial/kubernetes-charm-go.rst |
Updates Go tutorial for CK8s. |
docs/tutorial/kubernetes-charm-flask.rst |
Updates Flask tutorial for CK8s. |
docs/tutorial/kubernetes-charm-fastapi.rst |
Updates FastAPI tutorial for CK8s. |
docs/tutorial/kubernetes-charm-express.rst |
Updates Express tutorial for CK8s. |
docs/tutorial/kubernetes-charm-django.rst |
Updates Django tutorial for CK8s. |
docs/tutorial/code/spring-boot/task.yaml |
Uses CK8s containerd image import; requires CK8s test-suite provisioning. |
docs/tutorial/code/go/task.yaml |
Uses CK8s containerd image import; requires CK8s test-suite provisioning. |
docs/tutorial/code/flask/task.yaml |
Uses CK8s containerd image import; requires CK8s test-suite provisioning. |
docs/tutorial/code/fastapi/task.yaml |
Uses CK8s containerd image import; requires CK8s test-suite provisioning. |
docs/tutorial/code/expressjs/task.yaml |
Uses CK8s containerd image import; requires CK8s test-suite provisioning. |
docs/tutorial/code/django/task.yaml |
Uses CK8s containerd image import; requires CK8s test-suite provisioning. |
docs/reuse/tutorial/setup_stable.rst |
Defines stable CK8s setup and Juju registration. |
docs/reuse/tutorial/setup_edge.rst |
Defines edge CK8s setup and Juju registration. |
Review details
- Files reviewed: 16/16 changed files
- Comments generated: 6
- Review effort level: Lite
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The tutorial task.yaml files now import images with the k8s snap's containerd (`ctr`) and deploy to a `k8s-cloud` Juju cloud, but the shared docsuite only provisioned MicroK8s. Give docs/tutorial/code/ a dedicated suite that installs the k8s snap, configures the docker.io containerd mirror, bootstraps Canonical Kubernetes, enables local-storage, ingress and the load balancer, and registers k8s-cloud with Juju. The how-to code suite stays on MicroK8s. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Addressed in 49c61e3: the six tutorial task suites no longer inherit the MicroK8s
The how-to code suite ( Note: these suites are |
alithethird
left a comment
There was a problem hiding this comment.
Left some nitpicks, overall looking good.
Address PR review feedback: avoid driving containerd directly (`ctr images import`) in the 12-factor tutorials. Instead deploy a small local OCI registry at localhost:5000 and push rocks to it with `rockcraft.skopeo copy`, mirroring the previous MicroK8s registry flow. - setup_stable.rst / setup_edge.rst: add a "Set up a local registry" section (distribution registry binary as a systemd service + containerd hosts.d config for localhost:5000). - tutorial task.yaml (6 frameworks): push rocks via skopeo and reference images as localhost:5000/<app>:<ver>. - tutorial .rst (6 frameworks): update prose/bullets to the registry and skopeo workflow. - spread.yaml tutorial suite: deploy the registry + hosts.d config in place of the docker.io pull-through mirror block. - Trim heading underlines in the setup files (review nitpick). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…s-ck8s-isd-4330 # Conflicts: # docs/howto/manage-web-app-charms/use-web-app-charm.rst
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Resolve the registry/containerd mismatch, CK8s ingress access setup, and removed MicroK8s guidance.
Review effort: Lite
Findings: 7
Open (8)
Import rocks into containerd instead of pushing to localhost · New Import rocks into containerd instead of pushing to localhost · New Import rocks into containerd instead of pushing to localhost · New Import rocks into containerd instead of pushing to localhost · New Import rocks into containerd instead of pushing to localhost · New Import rocks into containerd instead of pushing to localhost · New Align spread tests with direct containerd image imports · New Restore MicroK8s and Canonical K8s tabs · New
Resolved since last review (6)
This executable tutorial task inherits thedocsuitesetup fromspread.yaml:235-271, which… This executable tutorial task inherits thedocsuitesetup fromspread.yaml:235-271, which… This executable tutorial task inherits thedocsuitesetup fromspread.yaml:235-271, which… This executable tutorial task inherits thedocsuitesetup fromspread.yaml:235-271, which… This executable tutorial task inherits thedocsuitesetup fromspread.yaml:235-271, which… This executable tutorial task inherits thedocsuitesetup fromspread.yaml:235-271, which…
d1f2138 to
9817acc
Compare
There was a problem hiding this comment.
🟡 Changes recommended
The stable and edge setup instructions omit the load-balancer provisioning and loopback DNAT commands required by the documented ingress checks.
2 open findings
8 resolved since last review
Align spread tests with direct containerd image imports Import rocks into containerd instead of pushing to localhost Import rocks into containerd instead of pushing to localhost Import rocks into containerd instead of pushing to localhost Import rocks into containerd instead of pushing to localhost Import rocks into containerd instead of pushing to localhost Import rocks into containerd instead of pushing to localhost Restore MicroK8s and Canonical K8s tabs
🧠 Review effort: Lite
The Canonical Kubernetes ingress is fronted by a LoadBalancer service rather than binding the node's host ports like the MicroK8s add-on, so the documented `curl ... 127.0.0.1` requests could not reach the app. Enable the built-in load balancer with an L2 address pool set to the VM's own IP in the tutorial setup, and resolve the ingress host to that address (`$INGRESS_IP`) instead of the loopback address. Align the spread tutorial suite with the documented commands and drop the test-only loopback DNAT workaround. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: florentianayuwono <76247368+florentianayuwono@users.noreply.github.com> Signed-off-by: florentianayuwono <76247368+florentianayuwono@users.noreply.github.com>
Remove a stray trailing double-backtick from a `curl` command in the Spring Boot tutorial so the rendered code block can be copied verbatim. Add a release note for the 12-factor tutorial migration to Canonical Kubernetes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… tutorials Replace the static local-binary OCI registry and containerd hosts.d configuration with an in-cluster NodePort registry exposed on the node at localhost:32000, mirroring the MicroK8s registry addon. Wait for the load-balancer (MetalLB) webhook to become ready before configuring its address pool to avoid a validation-webhook race, and reuse a single INGRESS_IP variable defined during setup across the tutorial curls. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Waiting on `k8s status --wait-ready` does not guarantee that MetalLB's validating webhook is serving, so configuring the load-balancer address pool could still race the webhook and leave the feature in a Failed state. Wait for the metallb-controller deployment (which hosts the webhook) to roll out before setting the CIDRs, matching behaviour verified on a live Canonical Kubernetes cluster. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Canonical Kubernetes exposes ingress through Cilium, whose ``cilium`` IngressClass is not the cluster default. Unlike the MicroK8s ingress addon, Cilium does not claim classless Ingress resources, so the nginx-ingress-integrator's Ingress was never assigned an address and the integrator stalled on "Waiting for ingress IP availability". Set ``ingress-class=cilium`` on the integrator so its Ingress targets the Cilium controller. Validated end to end on a clean Canonical Kubernetes VM. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The framework profiles generate charmcraft.yaml with ``base`` and ``platforms``, so ``charmcraft pack`` names the artifact ``<name>_<architecture>.charm``. The tutorials still showed the old ``bases``-style ``<name>_ubuntu-<series>-<architecture>.charm`` output (django even showed 22.04). Correct the example output to match. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use the single ``self-hosted-linux-amd64-noble-large`` runner label for the docs-tests job instead of the multi-label set, which was mapping to a starved runner pool and preventing the docs spread tests from running. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Switch the snap-build and spread-select jobs to the combined ``self-hosted-linux-amd64-noble-medium`` and ``spread-installed`` runner labels, matching the docs-tests job. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
erinecon
left a comment
There was a problem hiding this comment.
Thanks for updating the tutorials! My initial issues have been resolved, but now I'm running into issues when integrating with a database. I also spotted a doc linting issue that we can resolve
I tested Express, FastAPI, and Flask
| jobs: | ||
| snap-build: | ||
| runs-on: [self-hosted, linux, amd64, noble, medium] | ||
| runs-on: self-hosted-linux-amd64-noble-medium |
There was a problem hiding this comment.
Linter is complaining about this change (and the other similar change later in this file):
.github/workflows/spread-docs.yaml:25:14: label "self-hosted-linux-amd64-noble-medium" is
unknown. available labels are "windows-latest", "windows-latest-8-cores", "windows-2025",
"windows-2022", "windows-11-arm", "ubuntu-slim", "ubuntu-latest", "ubuntu-latest-4-cores",
"ubuntu-latest-8-cores", "ubuntu-latest-16-cores", "ubuntu-24.04", "ubuntu-24.04-arm",
"ubuntu-22.04", "ubuntu-22.04-arm", "macos-latest", "macos-latest-xl", "macos-latest-xlarge",
"macos-latest-large", "macos-26-xlarge", "macos-26", "macos-15-intel", "macos-15-xlarge",
"macos-15-large", "macos-15", "macos-14-xl", "macos-14-xlarge", "macos-14-large", "macos-14",
"macos-13-xl", "macos-13-xlarge", "macos-13-large", "macos-13", "self-hosted", "x64", "arm",
"arm64", "linux", "macos", "windows", "linux", "spread-enabled", "spread-installed", "amd64",
"arm64", "ppc64el", "riscv64", "s390x", "focal", "jammy", "noble", "resolute", "medium",
"large", "xlarge", "2xlarge", "2xlarge-extra", "edge", "tiobe", "large-tiobe",
"2xlarge-tiobe". if it is a custom label for self-hosted runner, set list of labels in
actionlint.yaml config file [runner-label]
| Check the total number of visitors: | ||
|
|
||
| .. code-block:: bash | ||
|
|
||
| curl http://flask-hello-world/visitors --resolve flask-hello-world:80:127.0.0.1 | ||
| curl http://flask-hello-world/visitors --resolve flask-hello-world:80:$INGRESS_IP | ||
|
|
||
| This request should return ``1`` after the previous request to the root endpoint. |
There was a problem hiding this comment.
I did not get a response from the visitors endpoint:
(.venv) ubuntu@charm-dev:~/flask-hello-world/charm$ curl http://flask-hello-world --resolve flask-hello-world:80:$INGRESS_IP
Hi!
(.venv) ubuntu@charm-dev:~/flask-hello-world/charm$ curl http://flask-hello-world/visitors --resolve flask-hello-world:80:$INGRESS_IP
(.venv) ubuntu@charm-dev:~/flask-hello-world/charm$ curl http://flask-hello-world/visitors --resolve flask-hello-world:80:$INGRESS_IP
(.venv) ubuntu@charm-dev:~/flask-hello-world/charm$ curl http://flask-hello-world --resolve flask-hello-world:80:$INGRESS_IP
Hi!
(.venv) ubuntu@charm-dev:~/flask-hello-world/charm$ curl http://flask-hello-world/visitors --resolve flask-hello-world:80:$INGRESS_IP
There was a problem hiding this comment.
I tried again today with Flask and Go -- in both attempts, I did not get a response from the visitors endpoint.
I also tried the Go tutorial with both PostgreSQL 14 and 16 -- same lack of response.
Signed-off-by: Alex Lowe <alex.lowe@canonical.com>
Co-authored-by: Erin Conley <erin.conley@canonical.com> Signed-off-by: florentianayuwono <76247368+florentianayuwono@users.noreply.github.com>
Co-authored-by: Erin Conley <erin.conley@canonical.com> Signed-off-by: florentianayuwono <76247368+florentianayuwono@users.noreply.github.com>
Co-authored-by: Erin Conley <erin.conley@canonical.com> Signed-off-by: florentianayuwono <76247368+florentianayuwono@users.noreply.github.com>
Co-authored-by: Erin Conley <erin.conley@canonical.com> Signed-off-by: florentianayuwono <76247368+florentianayuwono@users.noreply.github.com>
Increase the Multipass VM memory from 4 GB to 8 GB in the shared setup
pages and the per-framework prerequisites to avoid pack errors and I/O
timeouts during the build steps. Add "balancer('s)?" to the custom
wordlist so the load-balancer prose passes the documentation linter.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Django 6.1 requires PostgreSQL 15 or later, but the tutorial deployed postgresql-k8s from the 14/edge channel (PostgreSQL 14), so the charm's startup migration failed with "PostgreSQL 15 or later is required". Deploy postgresql-k8s from 16/stable to match the Django requirement, consistent with the upstream 12-factor Django tutorial. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The registry setup inlined the full Kubernetes manifest as a heredoc, which made the shared setup pages long and hard to read. Move the manifest into docs/reuse/tutorial/registry.yaml and download it with curl in the tutorial, mirroring the upstream 26.04 tutorials. Add a readiness probe so the registry rollout wait reflects actual readiness. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
erinecon
left a comment
There was a problem hiding this comment.
I was able to successfully complete the Django tutorial 🎉
For the other tutorials with a dedicated section to integrate with a database -- I'm consistently seeing a lack of response from the visitors endpoint.
| Check the total number of visitors: | ||
|
|
||
| .. code-block:: bash | ||
|
|
||
| curl http://flask-hello-world/visitors --resolve flask-hello-world:80:127.0.0.1 | ||
| curl http://flask-hello-world/visitors --resolve flask-hello-world:80:$INGRESS_IP | ||
|
|
||
| This request should return ``1`` after the previous request to the root endpoint. |
There was a problem hiding this comment.
I tried again today with Flask and Go -- in both attempts, I did not get a response from the visitors endpoint.
I also tried the Go tutorial with both PostgreSQL 14 and 16 -- same lack of response.
Avoids the ca-certificates.crt staging conflict (canonical/rockcraft#1261) that occurs with the ubuntu@24.04 base. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tstrap Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The cilium ingress class treats the ImplementationSpecific path / as an exact match, so endpoints other than / returned an empty 404. Use the /.* pattern and give the ingress more time to come up. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>



Follow-up to the how-to guide migration (#2743), addressing review feedback that the 12-factor tutorials still required MicroK8s. This migrates the tutorials to use Canonical Kubernetes (CK8s) exclusively — no MicroK8s alternative.
Changes
Shared tutorial setup (
docs/reuse/tutorial/setup_stable.rst,setup_edge.rst)sudo snap install k8s --classic,sudo k8s bootstrap).local-storage(replaces MicroK8s'hostpath-storage) andingress.load-balancerwith an L2 address pool set to the VM's own IP. Unlike the MicroK8s ingress add-on, the CK8s ingress is fronted by aLoadBalancerservice, so a load balancer is required for the ingress to be assigned a reachable address. The tutorialcurlcommands resolve the ingress host to that address ($INGRESS_IP) instead of the loopback address.sudo k8s config | juju add-k8s ...), since CK8s isn't autodetected like MicroK8s.localhost:5000. CK8s has no bundledregistryaddon, so the tutorials run the staticdistributionregistry as a systemd service and point containerd at it through/etc/containerd/hosts.d/localhost:5000/hosts.toml.Six framework tutorials and their
task.yamltests (Django, Express, FastAPI, Flask, Go, Spring Boot)rockcraft.skopeo copytolocalhost:5000, per reviewer request to use a registry rather than driving containerd directly in a tutorial.localhost:5000/prefix.juju statussample output for CK8s.How-to (
docs/howto/manage-web-app-charms/integrate-web-app-charm.rst)Spread (
spread.yaml)docs/tutorial/code/suite deploys the same local registry and containerdhosts.dconfig so the executable tutorial tests exercise the documented registry workflow on CK8s.Validation
sphinx-build --fail-on-warning.task.yamlfiles are executable spread integration tests. They require a full Multipass + CK8s setup and a self-hosted docs runner, so their end-to-end execution on CK8s — particularly ingress vianginx-ingress-integrator— should be validated in CI.make lint && make test.