<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Deployment on lo0 — Tech Blog</title><link>https://blog.lo0.es/en/tags/deployment/</link><description>Recent content in Deployment on lo0 — Tech Blog</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Sun, 31 May 2026 08:00:00 +0200</lastBuildDate><atom:link href="https://blog.lo0.es/en/tags/deployment/index.xml" rel="self" type="application/rss+xml"/><item><title>Seven greenfield deployment phases for an on-premise LLM platform: from hardware in the room to the first production token</title><link>https://blog.lo0.es/en/posts/greenfield-deployment-phases-on-premise-llm-platform/</link><pubDate>Sun, 31 May 2026 08:00:00 +0200</pubDate><guid>https://blog.lo0.es/en/posts/greenfield-deployment-phases-on-premise-llm-platform/</guid><description>&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>The two previous posts in this architectural trilogy settled the pieces: &lt;a href="https://blog.lo0.es/en/posts/anatomy-on-premise-llm-inference-stack-seven-layers/">the seven layers of the LLM inference stack&lt;/a> describe the components above the cluster, and &lt;a href="https://blog.lo0.es/en/posts/five-maturity-levels-platform-under-llm/">the five maturity levels of the platform&lt;/a> describe the strata below. This post settles the &lt;strong>when&lt;/strong>: in what order each thing gets deployed when you start from zero, hardware bought, racks installed, physical cabling done, and you want to reach a cluster serving the first production token to a customer. Seven nominal phases &lt;strong>F0 to F6&lt;/strong> with no calendar commitments, organised by &lt;strong>technical dependencies&lt;/strong> (you do not enter F3 without the F2 gate) and with an identifiable &lt;strong>critical path&lt;/strong>. F0 hardware inventory and electrical/network connectivity. F1 bare metal OS + drivers + container runtime. F2 Kubernetes cluster with CNI and Ceph storage operational. F3 GitOps and infrastructure observability. F4 identity, TLS, secrets and policies. F5 GPU platform with LLM-aware observability. F6 the LLM stack operational and open to production traffic. For each phase: what gets built, what has to be ready first (dependencies between phases), the &lt;strong>gate&lt;/strong> that validates its closure, and the typical trap that delays the critical path. The thesis: an on-premise LLM platform sinks far more often from &lt;strong>sequencing badly&lt;/strong> than from &lt;strong>choosing badly&lt;/strong>. The tools have all been invented; the order is the only thing every team rediscovers.&lt;/p>
&lt;h2 id="you-are-here-the-seven-phases-and-their-dependencies">You are here: the seven phases and their dependencies&lt;/h2>
&lt;p>The phases do not run in pure series. F2 and F3 can be started at the same time to speed things up (installing Kubernetes and preparing the GitOps repo in parallel). F4 can overlap with the tail end of F3. F5 waits for F4 to close because GPU pods demand NetworkPolicy and RBAC from day one. F6 is &lt;strong>a single atomic step&lt;/strong>: the cluster goes into production or it does not.&lt;/p>
&lt;div class="diagram" style="max-width:820px;margin:1rem auto;">
&lt;svg viewBox="0 0 820 340" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="dag of phases F0 to F6 with dependencies and critical path">
&lt;style>.b{stroke:#333;stroke-width:1.4;rx:6}.f0{fill:#f6e2e2;stroke:#a33}.f1{fill:#f4e3cf;stroke:#a63}.f2{fill:#eef0d0;stroke:#7a3}.f3{fill:#dfe9f5;stroke:#356}.f4{fill:#d8eecf;stroke:#373}.f5{fill:#f5e3d8;stroke:#763}.f6{fill:#ead8f5;stroke:#634}.lbl{font:600 12px sans-serif;fill:#222}.sm{font:11px sans-serif;fill:#222}.tiny{font:600 10px sans-serif;fill:#222}.note{font:italic 10px sans-serif;fill:#555}.arr{stroke:#666;stroke-width:1.4;fill:none;marker-end:url(#a)}.crit{stroke:#c33;stroke-width:2.4;fill:none;marker-end:url(#ac)}&lt;/style>
&lt;defs>&lt;marker id="a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#666"/>&lt;/marker>&lt;marker id="ac" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#c33"/>&lt;/marker>&lt;/defs>
&lt;text x="410" y="22" text-anchor="middle" class="lbl">Phase DAG · critical path marked in red&lt;/text>
&lt;rect x="40" y="50" width="110" height="48" class="b f0"/>&lt;text x="95" y="68" text-anchor="middle" class="tiny">F0&lt;/text>&lt;text x="95" y="84" text-anchor="middle" class="sm">Hardware&lt;/text>&lt;text x="95" y="98" text-anchor="middle" class="note">Inventory · network&lt;/text>
&lt;rect x="180" y="50" width="110" height="48" class="b f1"/>&lt;text x="235" y="68" text-anchor="middle" class="tiny">F1&lt;/text>&lt;text x="235" y="84" text-anchor="middle" class="sm">Bare metal&lt;/text>&lt;text x="235" y="98" text-anchor="middle" class="note">OS · drivers&lt;/text>
&lt;rect x="320" y="50" width="110" height="48" class="b f2"/>&lt;text x="375" y="68" text-anchor="middle" class="tiny">F2&lt;/text>&lt;text x="375" y="84" text-anchor="middle" class="sm">k8s cluster&lt;/text>&lt;text x="375" y="98" text-anchor="middle" class="note">Cilium · Ceph&lt;/text>
&lt;rect x="460" y="50" width="110" height="48" class="b f3"/>&lt;text x="515" y="68" text-anchor="middle" class="tiny">F3&lt;/text>&lt;text x="515" y="84" text-anchor="middle" class="sm">GitOps + obs&lt;/text>&lt;text x="515" y="98" text-anchor="middle" class="note">Flux · VM/Loki&lt;/text>
&lt;rect x="600" y="50" width="110" height="48" class="b f4"/>&lt;text x="655" y="68" text-anchor="middle" class="tiny">F4&lt;/text>&lt;text x="655" y="84" text-anchor="middle" class="sm">Identity&lt;/text>&lt;text x="655" y="98" text-anchor="middle" class="note">OIDC · Kyverno&lt;/text>
&lt;rect x="320" y="170" width="110" height="48" class="b f5"/>&lt;text x="375" y="188" text-anchor="middle" class="tiny">F5&lt;/text>&lt;text x="375" y="204" text-anchor="middle" class="sm">GPU plane&lt;/text>&lt;text x="375" y="218" text-anchor="middle" class="note">NVIDIA op · DCGM&lt;/text>
&lt;rect x="600" y="170" width="110" height="48" class="b f6"/>&lt;text x="655" y="188" text-anchor="middle" class="tiny">F6&lt;/text>&lt;text x="655" y="204" text-anchor="middle" class="sm">LLM stack live&lt;/text>&lt;text x="655" y="218" text-anchor="middle" class="note">7 layers active&lt;/text>
&lt;path class="crit" d="M150,74 L180,74"/>
&lt;path class="crit" d="M290,74 L320,74"/>
&lt;path class="crit" d="M430,74 L460,74"/>
&lt;path class="crit" d="M570,74 L600,74"/>
&lt;path class="arr" d="M655,98 L655,170"/>
&lt;path class="arr" d="M375,98 L375,170"/>
&lt;path class="crit" d="M430,194 L600,194"/>
&lt;text x="410" y="262" text-anchor="middle" class="sm" fill="#c33">Critical path: F0 → F1 → F2 → F3 → F4 → F5 → F6&lt;/text>
&lt;text x="410" y="282" text-anchor="middle" class="note">Possible overlaps: F2 ↔ F3 (prepare repo while the cluster is built) · F3 ↔ F4 (policies in audit before enforce)&lt;/text>
&lt;text x="410" y="304" text-anchor="middle" class="note">Not overlappable: F4 before F5 (GPU without RBAC = a bomb) · F5 before F6 (the LLM stack does not start without a GPU plane)&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>The red arrows are the &lt;strong>critical path&lt;/strong>: the sequential bottleneck that no parallelism can shorten. The grey arrows are dependencies that admit partial overlap. Recognising where to overlap and where not is the difference between a three-month deployment and a six-month one for the same perimeter.&lt;/p>
&lt;h2 id="the-analogy-the-expedition-to-an-eight-thousand-metre-summit">The analogy: the expedition to an eight-thousand-metre summit&lt;/h2>
&lt;p>An expedition to a high alpine summit is not a long trek. It is a series of &lt;strong>camps&lt;/strong> that are set up in order, each with its altitude, its function and its validation gate: if you do not acclimatise properly at base camp, you cannot go up to C1 without risk; if C2 does not have its kitchen and its radio running, you cannot send people higher; if the summit push is attempted without porters in the high camps, there is no safe descent.&lt;/p>
&lt;p>Greenfield deployment of an LLM platform works identically. &lt;strong>F0&lt;/strong> is the arrival of the material at base camp: boxes, sponsors, permits, first review. &lt;strong>F1&lt;/strong> is setting up an operational base camp: kitchen, tents, generator. &lt;strong>F2&lt;/strong> is the climb to C1: there is real altitude now (a k8s cluster running) and you breathe differently. &lt;strong>F3&lt;/strong> is C2: it adds communications, planning and operational acclimatisation. &lt;strong>F4&lt;/strong> is C3, the last night before the push: team roped up, oxygen ready, every protocol verified. &lt;strong>F5&lt;/strong> is the day of the summit push, intense effort, thin margins. &lt;strong>F6&lt;/strong> is the summit and the start of a safe descent: from here on the expedition is in day-to-day operation, no longer in construction.&lt;/p>
&lt;p>The analogy holds two useful lessons: &lt;strong>you do not skip a camp&lt;/strong> (going straight from base camp to the summit kills the team), and &lt;strong>the gates are technical, not emotional&lt;/strong> (if the barometer says storm, you do not set out, however much enthusiasm there is). The platform team that follows those two rules reaches the summit. The one that negotiates them does not.&lt;/p>
&lt;h2 id="f0--hardware-in-the-room-base-camp">F0 — Hardware in the room: base camp&lt;/h2>
&lt;p>&lt;strong>What gets built in this phase.&lt;/strong> An inventory of the hardware received (servers, switches, PDUs, BMC), racks assembled, electrical and data cabling terminated, physical labelling of each machine (rack/U/function), connectivity to the corporate network, management IPs assigned, BMC reachable over VPN with MFA, first ping to each node from the bastion.&lt;/p>
&lt;p>&lt;strong>Dependencies.&lt;/strong> None technical, this is the phase &lt;strong>before the software&lt;/strong>. There are procurement dependencies (servers bought, switches bought), civil works dependencies (a room with enough cooling, a raised floor) and administrative ones (datacenter access for the technicians).&lt;/p>
&lt;p>&lt;strong>Validation gate that closes F0.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Every node appears in the inventory with &lt;code>(hostname, MAC, management IP, data IP, rack, U, function, owner)&lt;/code>.&lt;/li>
&lt;li>Each node&amp;rsquo;s BMC responds to &lt;code>ipmitool power status&lt;/code> and to the HTTPS UI from the management VPN.&lt;/li>
&lt;li>The top-of-rack switch has its configuration versioned in git (even if there is no cluster GitOps yet, the switch configs are).&lt;/li>
&lt;li>A command &lt;code>for h in $(cat hosts); do ping -c1 -W1 $h.mgmt; done&lt;/code> returns 100 % success.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Typical trap.&lt;/strong> Cabling &amp;ldquo;however it comes out&amp;rdquo; with no physical labelling and no diagram. When F4 arrives and a NetworkPolicy has to be troubleshot, not knowing which physical interface carries which VLAN doubles the diagnosis time of every incident for ever.&lt;/p>
&lt;p>&lt;strong>Why F0 does not overlap with F1.&lt;/strong> Until every server has a management IP and a live BMC, OS bootstrap cannot be automated. Every hour invested in F0 saves hours in every later phase. It is the phase with the best ROI in the project and the only one that admits no shortcuts.&lt;/p>
&lt;h2 id="f1--bare-metal-the-operational-base-camp">F1 — Bare metal: the operational base camp&lt;/h2>
&lt;p>&lt;strong>What gets built.&lt;/strong> The operating system image (stable Debian or Ubuntu LTS) provisioned via PXE or cloud-init with the &lt;code>cloud-config&lt;/code> versioned in git. Each node has: a coherent hostname, LVM partitions, kernel ≥ 6.6, the &lt;code>containerd&lt;/code> container runtime, NVIDIA drivers on the GPU nodes, &lt;code>chrony&lt;/code> synchronising against your own servers, the operator&amp;rsquo;s SSH key as the only way in, and &lt;code>nvidia-smi&lt;/code> passing a smoke test on the GPU nodes.&lt;/p>
&lt;p>&lt;strong>Dependencies.&lt;/strong> F0 closed. It needs the management network working so PXE responds and so the bastion can reach every node.&lt;/p>
&lt;p>&lt;strong>Validation gate that closes F1.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>ansible -i inventory all -m ping&lt;/code> returns 100 % success (or the equivalent with Salt / Pulumi / etc).&lt;/li>
&lt;li>Every GPU node passes &lt;code>nvidia-smi&lt;/code> showing the expected GPUs with a consistent driver across nodes.&lt;/li>
&lt;li>Each node&amp;rsquo;s clock drifts by less than 50 ms from the reference NTP.&lt;/li>
&lt;li>A physical reboot of a node leaves it in exactly the same state after boot (idempotency).&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Typical trap.&lt;/strong> NVIDIA drivers installed by hand with &lt;code>apt install&lt;/code> or with NVIDIA&amp;rsquo;s &lt;code>.run&lt;/code> script. It works on day one and breaks the day of the first kernel update. The operational rule already established in &lt;a href="https://blog.lo0.es/en/posts/five-maturity-levels-platform-under-llm/">the post on the five levels&lt;/a>: the drivers end up managed by the GPU Operator in F5; whatever is done now is only so that &lt;code>nvidia-smi&lt;/code> passes the smoke test, not for production.&lt;/p>
&lt;p>&lt;strong>Possible overlap.&lt;/strong> F1 can start for some nodes while F0 is still being finished on others (real greenfield rarely delivers every server on the same day). The F1 gate is per cluster, not per individual node.&lt;/p>
&lt;h2 id="f2--kubernetes-cluster-operational">F2 — Kubernetes cluster operational&lt;/h2>
&lt;p>&lt;strong>What gets built.&lt;/strong> RKE2 installed with three HA control plane nodes, all workers joined (CPU and GPU), Cilium as CNI with &lt;code>kubeProxyReplacement&lt;/code> enabled and a BGP control plane pointing at the ToR switches from F0, Rook-Ceph deployed on the storage nodes to cover block (RBD), filesystem (CephFS) and object (S3-compatible RGW), &lt;code>kubectl get nodes&lt;/code> returning every node &lt;code>Ready&lt;/code>, a first test pod with a PVC mounting and data persisting after the pod restarts.&lt;/p>
&lt;p>&lt;strong>Dependencies.&lt;/strong> F1 closed (drivers + container runtime). Switches with BGP configured (closed in F0). NVMe disks partitioned or available raw for Ceph OSDs.&lt;/p>
&lt;p>&lt;strong>Validation gate that closes F2.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>kubectl get nodes -o wide&lt;/code> shows every node &lt;code>Ready&lt;/code> with the expected Kubernetes version.&lt;/li>
&lt;li>A Deployment with replicas=3 and per-node antiAffinity starts and the pods land on different nodes.&lt;/li>
&lt;li>An RWO PVC (RBD) creates a volume, the pod writes data, the pod is deleted, another pod mounts it and reads the data.&lt;/li>
&lt;li>An RWX PVC (CephFS) does the same with two pods writing simultaneously.&lt;/li>
&lt;li>An RGW bucket via &lt;code>s3cmd&lt;/code> or &lt;code>mc&lt;/code> accepts &lt;code>put&lt;/code> and &lt;code>get&lt;/code> over TLS.&lt;/li>
&lt;li>Hubble (the read side of the CNI) shows flow logs between two pods in different namespaces.&lt;/li>
&lt;li>Chaos test: draining a non-GPU worker node; the workloads are rescheduled automatically.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Typical trap.&lt;/strong> Starting to &lt;code>kubectl apply&lt;/code> real workloads in F2 without GitOps. The backlog of things-applied-by-hand grows faster than the capacity to migrate it to git afterwards. The rule: in F2 only the cluster &lt;strong>prerequisites&lt;/strong> are applied (CNI, CSI, default storage class). Any application workload waits for F3.&lt;/p>
&lt;p>&lt;strong>Possible overlap.&lt;/strong> F2 ↔ F3. While the cluster is being built, the GitOps repo is prepared in parallel (directory structure, first Helm releases). When F2 closes, Flux is plugged into the repo and everything that was going to be &lt;code>kubectl apply&lt;/code> is already a reconciled manifest.&lt;/p>
&lt;h2 id="f3--gitops-and-infrastructure-observability">F3 — GitOps and infrastructure observability&lt;/h2>
&lt;p>&lt;strong>What gets built.&lt;/strong> Forgejo deployed first (it is a prerequisite for everything that follows). A &lt;code>gitops-infra&lt;/code> repo with the initial structure (&lt;code>apps/&lt;/code>, &lt;code>infrastructure/&lt;/code>, &lt;code>tenants/&lt;/code>, &lt;code>clusters/&lt;/code>). Flux installed and reconciling that repo. The prerequisite workloads applied by hand in F2 are moved to the repo and reconciled (no operational &lt;code>kubectl apply&lt;/code> remains). VictoriaMetrics + vmagent scraping metrics. Grafana with initial dashboards (USE/RED + cluster + Ceph + Cilium). Loki receiving logs via vector/fluent-bit. Alertmanager + Keep routing alerts to a chat channel. Barman Cloud backups for Postgres (future CNPG) and scheduled Ceph snapshots.&lt;/p>
&lt;p>&lt;strong>Dependencies.&lt;/strong> F2 closed. An RGW bucket to store backups (covered by the Ceph from F2).&lt;/p>
&lt;p>&lt;strong>Validation gate that closes F3.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>A change applied to the repo is reflected in the cluster in under 5 minutes with no manual intervention.&lt;/li>
&lt;li>A change applied with &lt;code>kubectl edit&lt;/code> directly to the cluster is detected by Flux and reverted (binding drift detection, not merely observational).&lt;/li>
&lt;li>Grafana shows dashboards for the cluster, Ceph, Cilium and GPU nodes (DCGM does not arrive until F5, but basic node metrics do).&lt;/li>
&lt;li>A test alert sent to Alertmanager reaches the chat channel in under 1 minute.&lt;/li>
&lt;li>Restoring a Postgres backup on a temporary cluster returns coherent data (the test defines the real RPO).&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Typical trap.&lt;/strong> Having Helm charts in git but still applying them with &lt;code>helm install&lt;/code> from the terminal. That is level 1 dressed up as level 2. F3 only closes when Flux is &lt;strong>the only authority&lt;/strong> that applies changes and humans edit the repo, not the cluster.&lt;/p>
&lt;p>&lt;strong>Possible overlap.&lt;/strong> F3 ↔ F4. While F3 is being closed, the Defguard and cert-manager manifests can be prepared in the repo. When they reconcile they have somewhere to land.&lt;/p>
&lt;h2 id="f4--identity-certificates-secrets-policies">F4 — Identity, certificates, secrets, policies&lt;/h2>
&lt;p>&lt;strong>What gets built.&lt;/strong> Defguard deployed with its dedicated Postgres (CNPG). An initial realm with the platform operators enrolled with MFA and WireGuard. OIDC integrated into kube-apiserver (&lt;code>--oidc-issuer-url&lt;/code>, &lt;code>--oidc-client-id&lt;/code>, &lt;code>--oidc-username-claim&lt;/code>), into Forgejo, into Grafana, into Alertmanager, one single SSO. cert-manager installed with an internal CA issuing internal certs for mTLS and with Let&amp;rsquo;s Encrypt ACME for edge certs. SOPS configured with a KMS (it can be a physical HSM, an age key in a vault, or an external Vault) and External Secrets Operator syncing secrets into the cluster. Kyverno deployed with initial policies in &lt;code>audit&lt;/code> mode for a week, then promoted to &lt;code>enforce&lt;/code>. A default-deny NetworkPolicy applied to every existing namespace. Tetragon enabled for runtime security. The kube-apiserver audit log sent to Loki with long retention.&lt;/p>
&lt;p>&lt;strong>Dependencies.&lt;/strong> F3 closed (Flux applies the manifests, VM/Loki ingest metrics and logs).&lt;/p>
&lt;p>&lt;strong>Validation gate that closes F4.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>kubectl&lt;/code> with a shared admin &lt;code>kubeconfig&lt;/code> &lt;strong>stops working&lt;/strong>; every operator uses their own OIDC token with MFA.&lt;/li>
&lt;li>A secret in plain &lt;code>data:&lt;/code> in a commit is rejected by the pre-commit hook (or by Kyverno admission).&lt;/li>
&lt;li>A pod without &lt;code>securityContext.runAsNonRoot=true&lt;/code> is rejected by Kyverno at admission.&lt;/li>
&lt;li>A deliberately wrong NetworkPolicy (allow-all) in a tenant namespace is rejected.&lt;/li>
&lt;li>An audit of the last day returns the complete list of actors and changes (minimum regulatory footprint).&lt;/li>
&lt;li>Basic internal pen-test: an attacker with a forged &lt;code>kubeconfig&lt;/code> fails MFA and is recorded.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Typical trap.&lt;/strong> Kyverno in permanent &lt;code>audit&lt;/code> mode because &amp;ldquo;we do not want to break production workloads&amp;rdquo;. F4 closes when the policies are in &lt;code>enforce&lt;/code>. Until then, you are still in F3 wearing an F4 face.&lt;/p>
&lt;p>&lt;strong>Why F4 does not overlap with F5.&lt;/strong> F5 introduces GPU pods that move a lot of VRAM and a lot of compute. Without default-deny NetworkPolicy, without OIDC RBAC, without Kyverno blocking insecure configurations, the GPU pods are the juiciest attack surface in the cluster. Any compromise in F5 with F4 not closed is near-total access to the expensive hardware.&lt;/p>
&lt;h2 id="f5--gpu-platform-with-llm-aware-observability">F5 — GPU platform with LLM-aware observability&lt;/h2>
&lt;p>&lt;strong>What gets built.&lt;/strong> NVIDIA GPU Operator via Flux with the driver version decided in F1 (no longer touched by hand). DCGM Exporter exposing GPU metrics to VictoriaMetrics. MIG manager configured for the nodes where it makes sense (for example, on a 4×H100 SXM cluster: two GPUs with full passthrough for the general LLM at TP=4, two GPUs partitioned into 2×3g.40gb each for small LLMs and embeddings). Topology Manager with the &lt;code>single-numa-node&lt;/code> policy. KEDA with the Prometheus scaler installed and an example ScaledObject pointing at a vLLM metric (&lt;code>vllm:num_requests_running&lt;/code>). OpenTelemetry Collector with OTLP receivers, &lt;code>attributes&lt;/code> processors (enriching spans with &lt;code>tenant_id&lt;/code>, &lt;code>priority_tier&lt;/code>), exporters to Langfuse and to Tempo. LeaderWorkerSet API enabled for tensor parallel topologies. OME (Operator Model Engine) or vLLM Production Stack deployed as a controller, still with no models loaded.&lt;/p>
&lt;p>&lt;strong>Dependencies.&lt;/strong> F4 closed (the GPU pods inherit default-deny NetworkPolicy, OIDC RBAC and Kyverno policies).&lt;/p>
&lt;p>&lt;strong>Validation gate that closes F5.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>A test pod requesting &lt;code>nvidia.com/gpu: 1&lt;/code> is scheduled on the right node and &lt;code>nvidia-smi&lt;/code> from inside the container sees the right GPU (whole or a MIG slice).&lt;/li>
&lt;li>DCGM Exporter exposes metrics in Grafana (utilisation, VRAM, temperature, NVLink bandwidth) for every GPU.&lt;/li>
&lt;li>A test vLLM Deployment starts with a small model (for example, a 7B FP16) loaded from Ceph RGW.&lt;/li>
&lt;li>An OpenTelemetry span generated by that vLLM reaches Langfuse with correct &lt;code>gen_ai.*&lt;/code> attributes.&lt;/li>
&lt;li>KEDA scales the test Deployment from 1 to N replicas under synthetic load and back to 1 when it stops.&lt;/li>
&lt;li>A GPU Operator upgrade to a new version drains and reschedules the GPU pods with no loss of service.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Typical trap.&lt;/strong> Loading the large model &amp;ldquo;just to try&amp;rdquo; before DCGM and OTel are green. When something fails, there will be no metrics to distinguish OOM, thermal throttling, a driver mismatch or a network problem, and you diagnose blind. The rule: &lt;strong>small model first&lt;/strong>, golden path green, &lt;strong>then&lt;/strong> the large model.&lt;/p>
&lt;p>&lt;strong>Possible overlap.&lt;/strong> None with F6. F6 is atomic.&lt;/p>
&lt;h2 id="f6--llm-stack-in-production">F6 — LLM stack in production&lt;/h2>
&lt;p>&lt;strong>What gets built.&lt;/strong> The &lt;strong>seven layers of the inference stack&lt;/strong> described in the &lt;a href="https://blog.lo0.es/en/posts/anatomy-on-premise-llm-inference-stack-seven-layers/">corresponding post&lt;/a>, deployed in this order:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Vector store + relational data&lt;/strong> (Qdrant, PostgreSQL CNPG, Ceph RGW for weights and adapters, CephFS for datasets). Some components already existed from F3 as data; here they specialise for RAG with their initial collections and schemas.&lt;/li>
&lt;li>&lt;strong>Embeddings + reranker&lt;/strong> (Infinity with &lt;code>multilingual-e5-large&lt;/code>, TEI with &lt;code>bge-reranker-v2-m3&lt;/code>). This is the layer that must be green before any large model, because RAG depends on it.&lt;/li>
&lt;li>&lt;strong>LLM inference&lt;/strong> (vLLM Production Stack with the general LLM and the code LLM). It loads models from Ceph RGW. The initial multi-LoRA pool is empty.&lt;/li>
&lt;li>&lt;strong>Gateway&lt;/strong> (Envoy AI Gateway) with Defguard OAuth, routing by &lt;code>body.model&lt;/code>, rate-limit per tenant. This is the point that &lt;strong>opens traffic to the outside&lt;/strong>.&lt;/li>
&lt;li>&lt;strong>LLM-aware observability&lt;/strong> (Langfuse plugged into the OTel from F5).&lt;/li>
&lt;li>&lt;strong>GitOps control plane&lt;/strong> and &lt;strong>dependency tracking&lt;/strong> were already active since F3 and F4 respectively; here you simply add the catalogue of the new LLM services to them.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Dependencies.&lt;/strong> All the previous phases closed.&lt;/p>
&lt;p>&lt;strong>Validation gate that closes F6.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>A curl to the public endpoint with a Defguard bearer token receives a correct chat completion response in technical Spanish, with &lt;code>trace_id&lt;/code> propagated.&lt;/li>
&lt;li>The trace appears in Langfuse with complete &lt;code>gen_ai.*&lt;/code> attributes, a latency breakdown and its own &lt;code>tenant_id&lt;/code>.&lt;/li>
&lt;li>A 5 % traffic canary to the new model over 24 h degrades neither quality nor latency metrics.&lt;/li>
&lt;li>A controlled traffic burst triggers KEDA, the replicas scale, and P95 latency stays within budget.&lt;/li>
&lt;li>A deliberate failure of one vLLM pod does not affect endpoint availability (replicas + reschedule).&lt;/li>
&lt;li>The internal operator demonstrates the complete path for revoking a tenant&amp;rsquo;s access in under 5 minutes (Defguard → Kyverno → closing the NetworkPolicy).&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Typical trap.&lt;/strong> Opening real customer traffic before having the incident runbook signed off, the SLO negotiated and the continuity plan tested. F6 is technically closed; operationally, the platform is still an experiment until the first real postmortem proves the team knows how to respond.&lt;/p>
&lt;h2 id="the-maths-that-matter-relative-weight-of-effort-per-phase">The maths that matter: relative weight of effort per phase&lt;/h2>
&lt;p>Without committing to calendar weeks, we can quantify the &lt;strong>relative weight&lt;/strong> of the engineering effort per phase in a typical greenfield. The curve is not uniform:&lt;/p>
$$
\text{effort}_{F_i} \approx \text{base}_i \cdot (1 + \epsilon_i)
$$
&lt;p>where $\text{base}_i$ is the nominal effort and $\epsilon_i$ is the &lt;strong>surprises&lt;/strong> factor (wrong cabling, incompatible drivers, badly issued certificates, version conflicts). The table below gives the nominal relative weight and the typical surprise factor observed:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Phase&lt;/th>
&lt;th>Nominal weight&lt;/th>
&lt;th>Typical surprise factor ε&lt;/th>
&lt;th>Mean effective weight&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>F0 — Hardware&lt;/td>
&lt;td>8 %&lt;/td>
&lt;td>0.5 (1× to 2×)&lt;/td>
&lt;td>&lt;strong>12 %&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>F1 — Bare metal&lt;/td>
&lt;td>6 %&lt;/td>
&lt;td>0.3&lt;/td>
&lt;td>8 %&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>F2 — k8s cluster&lt;/td>
&lt;td>12 %&lt;/td>
&lt;td>0.4&lt;/td>
&lt;td>17 %&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>F3 — GitOps + obs&lt;/td>
&lt;td>14 %&lt;/td>
&lt;td>0.5&lt;/td>
&lt;td>21 %&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>F4 — Identity + policies&lt;/td>
&lt;td>18 %&lt;/td>
&lt;td>0.7&lt;/td>
&lt;td>&lt;strong>31 %&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>F5 — GPU plane&lt;/td>
&lt;td>10 %&lt;/td>
&lt;td>0.4&lt;/td>
&lt;td>14 %&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>F6 — LLM stack live&lt;/td>
&lt;td>8 %&lt;/td>
&lt;td>0.3&lt;/td>
&lt;td>10 %&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Buffer / integration&lt;/td>
&lt;td>24 %&lt;/td>
&lt;td>—&lt;/td>
&lt;td>—&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Two operational observations. &lt;strong>F4 concentrates more surprises than any other phase&lt;/strong> (OIDC federation across four or five apps with different configurations, Kyverno policies that knock over legitimate workloads, secrets broken by poorly tested encryption). &lt;strong>F0 has a high surprise coefficient relative to its size&lt;/strong> because any cabling or labelling error is discovered late and paid for dearly. The two practical consequences: plan &lt;strong>F4 with generous margin&lt;/strong> and do not skimp on time in &lt;strong>F0&lt;/strong>, because every hour saved there costs five later.&lt;/p>
&lt;p>&lt;strong>Critical path and slack.&lt;/strong> The critical path is linear F0 → F1 → F2 → F3 → F4 → F5 → F6. The only real slack is the overlaps already identified:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>F2 ↔ F3 (slack ~30 %)&lt;/strong>: prepare the repo and initial dashboards while the cluster is being built.&lt;/li>
&lt;li>&lt;strong>F3 ↔ F4 (slack ~20 %)&lt;/strong>: identity manifests ready as F3 closes, applied immediately.&lt;/li>
&lt;li>&lt;strong>Within F4&lt;/strong>: policies in &lt;code>audit&lt;/code> mode running in parallel with the Defguard setup.&lt;/li>
&lt;/ul>
&lt;p>Nothing shortens the critical path by more than about 15 % of the total. Anyone promising a productive greenfield in half a reasonable timescale is selling something else: probably skipping F4, or loading F6 with an F5 that is green-but-not-validated.&lt;/p>
&lt;h2 id="final-diagram-the-complete-deployment-schedule">Final diagram: the complete deployment schedule&lt;/h2>
&lt;div class="diagram" style="max-width:820px;margin:1rem auto;">
&lt;svg viewBox="0 0 820 540" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="complete phased deployment schedule with pieces and gates">
&lt;style>.b{stroke:#333;stroke-width:1.4;rx:6}.bg{fill:#fafafa;stroke:#bbb;rx:8}.f0{fill:#f6e2e2;stroke:#a33}.f1{fill:#f4e3cf;stroke:#a63}.f2{fill:#eef0d0;stroke:#7a3}.f3{fill:#dfe9f5;stroke:#356}.f4{fill:#d8eecf;stroke:#373}.f5{fill:#f5e3d8;stroke:#763}.f6{fill:#ead8f5;stroke:#634}.gate{fill:#fffbe0;stroke:#a90}.lbl{font:600 12px sans-serif;fill:#222}.sm{font:11px sans-serif;fill:#222}.tiny{font:600 10px sans-serif;fill:#222}.note{font:italic 10px sans-serif;fill:#555}.arr{stroke:#666;stroke-width:1.4;fill:none;marker-end:url(#a)}&lt;/style>
&lt;defs>&lt;marker id="a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#666"/>&lt;/marker>&lt;/defs>
&lt;text x="410" y="22" text-anchor="middle" class="lbl">Complete schedule: pieces per phase and validation gates&lt;/text>
&lt;rect x="40" y="40" width="740" height="68" class="b f0"/>&lt;text x="60" y="58" class="tiny">F0 · HARDWARE IN THE ROOM&lt;/text>
&lt;text x="60" y="76" class="sm">Inventory · cabling · BMC TLS+MFA · management IPs · BGP switches versioned&lt;/text>
&lt;text x="60" y="92" class="note">Gate: `for h in hosts; ping HOST.mgmt` 100 % success · complete inventory&lt;/text>
&lt;rect x="40" y="116" width="740" height="68" class="b f1"/>&lt;text x="60" y="134" class="tiny">F1 · BARE METAL&lt;/text>
&lt;text x="60" y="152" class="sm">PXE/cloud-init · LTS OS · kernel ≥6.6 · containerd · NVIDIA drivers · chrony · LVM&lt;/text>
&lt;text x="60" y="168" class="note">Gate: `ansible all -m ping` 100 % · `nvidia-smi` smoke OK · idempotent reboot&lt;/text>
&lt;rect x="40" y="192" width="740" height="68" class="b f2"/>&lt;text x="60" y="210" class="tiny">F2 · KUBERNETES CLUSTER&lt;/text>
&lt;text x="60" y="228" class="sm">RKE2 HA · Cilium (kube-proxy replacement + BGP) · Rook-Ceph (RBD + CephFS + RGW)&lt;/text>
&lt;text x="60" y="244" class="note">Gate: RWO/RWX PVCs OK · RGW bucket OK · node drain with no downtime&lt;/text>
&lt;rect x="40" y="268" width="740" height="68" class="b f3"/>&lt;text x="60" y="286" class="tiny">F3 · GITOPS + INFRA OBSERVABILITY&lt;/text>
&lt;text x="60" y="304" class="sm">Forgejo · Flux · VictoriaMetrics + Grafana + Loki · Alertmanager + Keep · backups&lt;/text>
&lt;text x="60" y="320" class="note">Gate: repo change → cluster in &amp;lt;5min · drift reverted · backup restore OK&lt;/text>
&lt;rect x="40" y="344" width="740" height="68" class="b f4"/>&lt;text x="60" y="362" class="tiny">F4 · IDENTITY + POLICIES&lt;/text>
&lt;text x="60" y="380" class="sm">Defguard OIDC+MFA+WG · cert-manager · SOPS+ESO · Kyverno enforce · NP default deny · Tetragon&lt;/text>
&lt;text x="60" y="396" class="note">Gate: shared admin kubeconfig no longer works · policies in enforce · complete audit log&lt;/text>
&lt;rect x="40" y="420" width="740" height="68" class="b f5"/>&lt;text x="60" y="438" class="tiny">F5 · GPU PLATFORM + LLM-AWARE OBSERVABILITY&lt;/text>
&lt;text x="60" y="456" class="sm">NVIDIA GPU Operator · DCGM · MIG manager · KEDA with vLLM metrics · OTel gen_ai.* · OME&lt;/text>
&lt;text x="60" y="472" class="note">Gate: GPU pod scheduled · DCGM green · vLLM smoke with a small model · KEDA scales&lt;/text>
&lt;rect x="40" y="496" width="740" height="38" class="b f6"/>&lt;text x="60" y="514" class="tiny">F6 · LLM STACK LIVE&lt;/text>
&lt;text x="60" y="528" class="sm">7 layers active · first model green · canary OK · runbook signed · first customer with an SLA&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>The schedule is not decorative: each row defines what gets built in its phase &lt;strong>and the gate that closes it&lt;/strong>. A phase is not considered finished until its gate is green. A phase with a yellow gate drags every later one; trying to jump to the next with a partially met gate is what produces, several weeks later, the incident that forces you to &amp;ldquo;go back to F4 with production running&amp;rdquo;, the most expensive situation in the whole cost matrix of the &lt;a href="https://blog.lo0.es/en/posts/five-maturity-levels-platform-under-llm/">post on the five levels&lt;/a>.&lt;/p>
&lt;h2 id="typical-planning-mistakes">Typical planning mistakes&lt;/h2>
&lt;p>Patterns that delay or sink the greenfield deployment, regardless of the tools chosen:&lt;/p>
&lt;p>&lt;strong>1. Buying the LLM before the cluster.&lt;/strong> Starting the project with &amp;ldquo;which model are we going to serve&amp;rdquo; instead of &amp;ldquo;what platform can sustain any reasonable model&amp;rdquo;. The model is an interchangeable parameter; the platform is not.&lt;/p>
&lt;p>&lt;strong>2. Underestimating F0.&lt;/strong> &amp;ldquo;The network team does that.&amp;rdquo; Yes, but the output of F0 is consumed by every later phase. If the network team delivers late, the whole project is late, and nobody had marked it as the critical path.&lt;/p>
&lt;p>&lt;strong>3. Overlapping F4 with F5 &amp;ldquo;to save time&amp;rdquo;.&lt;/strong> It is the one dependency with no slack. If you try to overlap, F5 ends up operating with policies in permanent &lt;code>audit&lt;/code> (you are not in F4) or without OIDC integrated (operators with a shared kubeconfig touching GPUs). Both antipatterns stay in production.&lt;/p>
&lt;p>&lt;strong>4. Skipping the small model smoke test in F5.&lt;/strong> &amp;ldquo;Let&amp;rsquo;s go straight for the 70B.&amp;rdquo; When something fails (and something will), there will be no baseline to diagnose against.&lt;/p>
&lt;p>&lt;strong>5. Treating F6 as &amp;ldquo;turning on vLLM&amp;rdquo;.&lt;/strong> F6 includes gateway, LLM-aware observability, runbook, SLO, continuity plan. Turning on vLLM takes five minutes; closing F6 is weeks of validation and sign-off.&lt;/p>
&lt;p>&lt;strong>6. Not defining gates in writing.&lt;/strong> If the gates are not written down, they are negotiable after the fact. &amp;ldquo;This already counts as F4&amp;rdquo; is the sentence that precedes the next six months of retrofit.&lt;/p>
&lt;p>&lt;strong>7. Assigning a phase to a single owner.&lt;/strong> Every phase needs at least two people who understand it. Staff turnover on long projects destroys knowledge; written gates + cross review preserve it.&lt;/p>
&lt;p>&lt;strong>8. Forgetting the descent route.&lt;/strong> The post focuses on going up. Day-to-day operation (the descent, in the analogy) is another story that also deserves planning: runbooks, on-call, upgrade capacity, end-of-life plan. Teams that only plan the ascent reach the summit and stay there with no oxygen.&lt;/p>
&lt;h2 id="applied-to-typical-on-premise-hardware-4h100-sxm">Applied to typical on-premise hardware: 4×H100 SXM&lt;/h2>
&lt;p>On the generic reference cluster (4×H100 SXM 80 GB, NVLink, 640 GB RAM per GPU node, 3 control plane nodes, 3-5 CPU worker nodes, 2 GPU worker nodes), the &lt;strong>temporal&lt;/strong> split of the work is distributed like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">F0 (hardware)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└─ 8 physical servers racked + switches + BMC + management IPs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├─ 3 nodes cp-01..03 — control plane (no GPU)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├─ 3 nodes worker-cpu-01..03 — CPU plane (Forgejo, Ceph, observability)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └─ 2 nodes worker-gpu-01..02 — GPU plane (4×H100 SXM each)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">F1 (bare metal)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└─ OS + drivers + containerd on all 8 nodes alike
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> (NVIDIA drivers only on the 2 GPU nodes, `nvidia-smi` smoke)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">F2 (k8s cluster)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└─ RKE2 control plane on cp-01..03 (HA with embedded etcd)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> workers joining: 3 CPU + 2 GPU
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Ceph OSDs on the 3 CPU worker nodes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> default pools: RBD-replicated-3, CephFS-replicated-3, RGW
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">F3 (GitOps + obs)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└─ Forgejo + Flux + VM/Grafana/Loki + Keep on the CPU plane
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> first `gitops-infra` repo reconciles what came from F2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">F4 (identity)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└─ Defguard on the CPU plane (StatefulSet with Postgres CNPG)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> OIDC in kube-apiserver, Forgejo, Grafana, Alertmanager
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Kyverno as a Deployment on the control plane
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">F5 (GPU plane)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└─ NVIDIA GPU Operator targets the GPU workers
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> MIG manager: 1st GPU MIG 7g.80gb (= passthrough), 2nd 2×3g.40gb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> OTel Collector as a DaemonSet on the GPU plane + CPU plane
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> first vLLM with a 7B FP16 model green
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">F6 (LLM stack)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└─ The 7 layers reconcile via Flux from a second repo `gitops-llm`
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> first public endpoint with Defguard OAuth
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> first production customer enrolled under an SLA
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The physical distribution of the cluster exploits the isolation between planes defined in F0: the control plane never touches a GPU, the CPU plane concentrates relevant state (Forgejo, Ceph, Postgres CNPG, Langfuse, Defguard) and the GPU plane specialises as much as possible. That separation, decided in F0 before the first server is installed, conditions the success of the remaining phases. It is another reminder of why F0 matters more than it looks.&lt;/p>
&lt;h2 id="what-we-have-not-covered-upcoming-posts">What we have not covered (upcoming posts)&lt;/h2>
&lt;p>This post walks the &lt;strong>ascent route&lt;/strong> to the summit. Some pieces still deserve an article of their own:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>The safe descent&lt;/strong>: day-to-day operation, runbooks per component, on-call, continuous capacity planning, the cluster upgrade cycle with no downtime.&lt;/li>
&lt;li>&lt;strong>Multi-site (the second summit)&lt;/strong>: how two clusters are federated with Cilium Cluster Mesh and what extra phases that introduces. F3.5 (Cluster Mesh) and F4.5 (cross-site replication) are the missing phases.&lt;/li>
&lt;li>&lt;strong>The brownfield route&lt;/strong>: what changes when there is already a cluster with workloads. The phases stay the same, but the gates are applied retroactively and every step requires migration planning.&lt;/li>
&lt;li>&lt;strong>The real calendar cost&lt;/strong>: typical ranges in weeks for a platform team of 2-3 people, split by phase, with uncertainty bands.&lt;/li>
&lt;li>&lt;strong>The handoff to operations&lt;/strong>: how the platform is handed from the deployment team to the operations team, which documents are signed, what is inherited and what is renegotiated.&lt;/li>
&lt;/ul>
&lt;h2 id="see-also">See also&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/gitops-inference-stack-flux/">GitOps for the inference stack with Flux: operating the assistant as code&lt;/a> — how to turn these phases into a reproducible, reconciled GitOps deployment.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/anatomy-on-premise-llm-inference-stack-seven-layers/">Anatomy of an on-premise LLM inference stack&lt;/a> — the seven layers built in F6. The components, not the schedule.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/five-maturity-levels-platform-under-llm/">Five maturity levels of the platform under the LLM&lt;/a> — the levels corresponding to phases F1→F5. The strata, not the sequence.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/oss-catalogue-llmops-six-stages-tool-by-tool/">The OSS catalogue for LLMOps in six stages&lt;/a> — individual cards for the pieces cited here.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/llm-tracing-opentelemetry-genai/">LLM tracing with OpenTelemetry GenAI&lt;/a> — the OTel of F5 with detail on the &lt;code>gen_ai.*&lt;/code> semantic conventions.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/capacity-planning-on-premise-llm-inference/">Capacity planning for on-premise LLM inference&lt;/a> — when F6 closes, the immediate operational question is how many replicas fit and how many customers they support; the sizing that ends the conversation with procurement.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/gpu-observability-llm-inference-twelve-dcgm-vllm-metrics/">GPU observability for LLM inference&lt;/a> — the concrete cockpit of DCGM and vLLM that F5 enables: the twelve metrics that decide whether the cluster is operated or operated on by intuition.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/llm-inference-autoscaling-kubernetes-hpa-keda/">LLM autoscaling on Kubernetes&lt;/a> — KEDA and HPA with custom metrics, the first real deliverable of F6 when traffic starts to move.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/canary-blue-green-shadow-llm-models/">Canary, blue-green and shadow for LLM models&lt;/a> — the mechanics of promoting models with no downtime that F6 must support from day one.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/llm-inference-router-l7-switchboard/">The LLM inference router&lt;/a> — the last piece F6 closes: catalogue, L7 traffic splitting, cross-cutting policy, failover and prefix-aware routing. Without the router, F6 does not finish even with all seven layers running.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/isolating-ai-agents-workstation-to-cluster/">Isolating AI agents: from workstation to cluster&lt;/a> — the runtime hardening that fits into F4 (identity, policies) and F5 (platform): Kata &lt;code>RuntimeClass&lt;/code> and Tetragon &lt;code>TracingPolicy&lt;/code> for agent workloads; its &lt;a href="https://blog.lo0.es/en/posts/runbook-caging-ai-agent-bubblewrap-tetragon/">runbook&lt;/a> brings the files.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="references">References&lt;/h2>
&lt;ul>
&lt;li>RKE2 Documentation — &lt;a href="https://docs.rke2.io/">docs.rke2.io&lt;/a>&lt;/li>
&lt;li>Cilium documentation — &lt;a href="https://docs.cilium.io/">docs.cilium.io&lt;/a>&lt;/li>
&lt;li>Rook documentation — &lt;a href="https://rook.io/">rook.io/docs&lt;/a>&lt;/li>
&lt;li>Flux GitOps toolkit — &lt;a href="https://fluxcd.io/">fluxcd.io&lt;/a>&lt;/li>
&lt;li>Forgejo — &lt;a href="https://forgejo.org/">forgejo.org&lt;/a>&lt;/li>
&lt;li>cert-manager — &lt;a href="https://cert-manager.io/">cert-manager.io&lt;/a>&lt;/li>
&lt;li>External Secrets Operator — &lt;a href="https://external-secrets.io/">external-secrets.io&lt;/a>&lt;/li>
&lt;li>Kyverno — &lt;a href="https://kyverno.io/">kyverno.io&lt;/a>&lt;/li>
&lt;li>NVIDIA GPU Operator — &lt;a href="https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/">docs.nvidia.com/datacenter/cloud-native/gpu-operator&lt;/a>&lt;/li>
&lt;li>DCGM Exporter — &lt;a href="https://github.com/NVIDIA/dcgm-exporter">github.com/NVIDIA/dcgm-exporter&lt;/a>&lt;/li>
&lt;li>KEDA — &lt;a href="https://keda.sh/">keda.sh&lt;/a>&lt;/li>
&lt;li>LeaderWorkerSet API — &lt;a href="https://github.com/kubernetes-sigs/lws">github.com/kubernetes-sigs/lws&lt;/a>&lt;/li>
&lt;li>OpenTelemetry Semantic Conventions for GenAI — &lt;a href="https://opentelemetry.io/docs/specs/semconv/gen-ai/">opentelemetry.io/docs/specs/semconv/gen-ai&lt;/a>&lt;/li>
&lt;/ul></description></item></channel></rss>