Skip to content

Prerequisites

Everything that must be gathered or reserved before starting an installation. Nothing on this page replaces scripts/preflight.sh, which mechanically checks part of these prerequisites: this page says what has to be gathered, the script says whether it actually is.

Hardware and hypervisor

The reference cluster runs on six virtual machines, on a hypervisor (VMware ESXi on the reference instance): three control-plane nodes, three worker nodes, Talos v1.13.

That number is a reference sizing, not a constraint of the guide: a customer may deploy more or fewer. It is declared in the sheet by TV_NODE_COUNT, which scripts/validate-install.sh compares against the number of nodes actually Ready (Parameters).

  • Talos extensions: the workers carry the iscsi-tools extension (kernel module iscsi_tcp), required by Longhorn for replicated block storage. It is not declared in the configuration: it is baked into the installer image by the Talos Image Factory (https://factory.talos.dev, tick siderolabs/iscsi-tools), and the worker profile's install.image points at that schematic's installer. The same image serves the upgrades; a generic installer in its place would lose iSCSI at the first talosctl upgrade.
  • Disks: two disks per worker. The worker profile's install.disk designates the system disk of the customer VM. The second one is dedicated to Longhorn: talos/patches/longhorn-disk.yaml declares a user volume on it, formatted and mounted by Talos at /var/mnt/longhorn without a reboot, which the worker profile exposes to the kubelet. Without a second disk, Longhorn lands on the ephemeral partition, shared with images and logs, and the free space of both becomes one.
  • StorageClass: the cluster must expose a default StorageClass well before the application layer. It is Vault, laid down as early as the base layer, that claims one first: its three pods stay Pending for as long as it is missing, and the whole rest of the procedure waits with them. scripts/preflight.sh checks this in its control 7, but only when run without --skip-cluster, hence at step 4 of the Step by step and not before. The expected value is carried by TV_STORAGE_CLASS in the customer sheet.
  • Resources per node (CPU, RAM): this repository sets no validated minimum value. Until a reference sizing exists, ask the vendor for a recommendation before ordering the hardware, and record the actual consumption after the first deployment to refine subsequent orders.

Single point of failure on a single physical host

The redundancy of three control-plane plus three workers protects against a software failure or the reboot of a VM, not against the loss of the host, its power supply or its datastore: on a single server, that loss takes the cluster and the data layer away together. A separate data VM on that same host does not improve availability, it improves operability, which is a real but different gain. The only measure that removes this point of failure is a second physical host for the data VMs. Even before that placement choice, a backup that leaves this host comes first: this guide provides none.

The server, the hypervisor, the Talos VMs and the data VMs

Everything this figure shows sits on a single physical machine. That is what to see in it: the six Talos VMs and the data VMs share the same server, the same power supply and the same datastore.

Addressing plan to reserve

Six addresses or ranges to reserve before starting, independent of the domain. The values below are an illustrative example, in the same format as those of scripts/install.env.example, never a value to copy as is.

Variable Role Illustrative example
${TV_CP_ENDPOINT} Talos control-plane endpoint, consumed by talosctl 10.0.0.100
${TV_VIP} virtual address published by kube-vip, single entry point of the cluster 10.0.0.200
${TV_VIP_RANGE} range managed by kube-vip, VIP included, at least ten contiguous addresses 10.0.0.200-10.0.0.209
${TV_TRAEFIK_CLUSTERIP} fixed ClusterIP of the Traefik service, referenced by CoreDNS 10.111.0.21
${TV_POD_SUBNET} pod subnet, consumed by talosctl 10.244.0.0/16
${TV_SVC_SUBNET} service subnet, consumed by talosctl 10.96.0.0/12

${TV_CP_ENDPOINT} lives in the Talos configuration and is out of reach of scripts/render-config.sh: it is set when the machine configuration is generated, not by text substitution. ${TV_POD_SUBNET} and ${TV_SVC_SUBNET} also live in that configuration, but not only there: render-config.sh substitutes them as well in core/vpn/peer-officer.template.conf, where the tunnel advertises those two ranges to the agents. Changing them in Talos without carrying them over into the sheet lets the tunnel come up, the agents connect, and nothing be reached behind it, with no error at all.

DNS zone and Cloudflare account

  • A domain dedicated to the customer, ${TV_DOMAIN}, already delegated to Cloudflare before starting.
  • A Cloudflare account with access to that domain's zone, and its zone identifier, ${TV_CF_ZONE_ID}.
  • An API token scoped to that single zone, with two permissions and nothing more: Zone:DNS:Edit, which writes the _acme-challenge records of the DNS-01 challenge, and Zone:Zone:Read, which control 5 of scripts/preflight.sh requires because it queries GET /zones/${TV_CF_ZONE_ID}. That is the token scripts/preflight.sh checks through the environment variable CF_API_TOKEN. A token limited to Zone:DNS:Edit makes that control return a KO zone <id> : HTTP 403: the message names the incomplete scope as the first cause, then the zone and revocation, and gives the /user/tokens/verify request separating them.

Certificate issuance uses the DNS-01 challenge rather than HTTP-01: HTTP-01 forbids wildcards and would require publicly exposing every host, the opposite of this platform's exposure model.

Three prerequisite-level constraints follow from this, to be validated before starting:

  • The cluster's internal DNS resolution rewrites every *.${TV_DOMAIN}, _acme-challenge included. The propagation check that cert-manager runs before requesting validation can therefore never succeed if it goes through the cluster resolver.
  • cert-manager must consequently run with --dns01-recursive-nameservers-only and --dns01-recursive-nameservers=1.1.1.1:53,8.8.8.8:53, which assumes the nodes can reach those two resolvers outbound on port 53. That is a network opening to have validated with the customer, on the same footing as the addressing plan.
  • This setting does not live in cert-manager's upstream manifests: any reinstallation or update that reapplies its Deployment silently erases it, and the failure only shows up at the next renewal, up to sixty days later.

.app domain and HSTS preload: no workaround possible

If ${TV_DOMAIN} ends in .app, the domain is on the browsers' HSTS preload list. Until the wildcard certificate is issued and Ready, no host of the platform is reachable, and the browser offers no way to get past it: the "proceed anyway" button does not exist on this kind of domain. Check before starting that the Cloudflare zone really is delegated for this domain.

External data layer

The customer sheet declares six external data endpoints, and scripts/preflight.sh refuses to pass for as long as they do not answer: this is a constraint of this guide's scripts, to be provisioned and made reachable from the cluster before the installation.

Actual state of the repository, which the operator provisioning these six services must know. Kafka, Redis and Elasticsearch are deployed inside the cluster by the repository's base layer (data/kafka.yaml, data/redis.yaml, data/elasticsearch.yaml), not outside the cluster, and no service connects to the last two: those two endpoints today provide no function. They must nonetheless be provisioned, because preflight.sh tests them like the four others and refuses to pass if one of them does not answer. Providing them is therefore mandatory in order to install, without being useful at runtime.

A seventh endpoint, absent from this list as it is from the customer sheet, is by contrast indispensable: Keycloak's database. No manifest in this repository deploys it. apps/keycloak/keycloak.yaml hard-codes jdbc:postgresql://keycloak-db-rw:5432/app, and no manifest creates the Service keycloak-db-rw. A Postgres database reachable under that exact name and that port is therefore to be provisioned like the six others, without any variable of the sheet or any control of preflight.sh claiming it or checking it. The detail is at step 11 of the Step by step.

Variable Used for Symptom if wrong
${TV_PG_HOST} application Postgres .NET pods restarting in a loop, with no explicit message on the Kubernetes side
${TV_KAFKA_BOOTSTRAP} not consumed: Kafka is deployed inside the cluster by the base layer no effect, variable required by preflight.sh
${TV_REDIS_HOST} not consumed: no service connects to it, and a Redis is already deployed inside the cluster by the base layer no effect, variable required by preflight.sh
${TV_ES_HOST} not consumed: no service connects to it, and an Elasticsearch is already deployed inside the cluster by the base layer no effect, variable required by preflight.sh
${TV_MINIO_ENDPOINT} object storage for the content-service media broken images with a 403
${TV_SMTP_HOST} the customer's real SMTP no mail leaves the platform, with no error on the application side

scripts/preflight.sh checks a TCP open (nc -z) on each of these six endpoints: that proves the port answers, not that the credentials are right nor that the expected database or index exists.

Two operational constraints to settle at provisioning time, not afterwards: never put the MinIO data and the PGDATA on the same virtual disk, on separate datastores if the host allows it; backups, monitoring and restore of the data VMs are the operator's responsibility, this guide provides none.

These values are written into Vault during the step-by-step installation, not into the manifests.

Operator workstation tooling

Checked mechanically by scripts/preflight.sh, section "outils du poste".

Tool Used for
kubectl driving the cluster once bootstrapped
kustomize building or validating the base layer locally (kustomize build .), in addition to kubectl's own kustomize integration
talosctl generating the machine configuration, bootstrapping the cluster; to be matched to the installed Talos version
curl checking the Cloudflare token, one-off API calls
sed, grep used by render-config.sh and by the guide's scripts
nc checking the reachability of the data layer (section "8. couche de donnees" of preflight.sh)
dig checking that internal hosts are not resolved publicly, the only automatic safeguard of the public/internal separation (control 8 of scripts/validate-install.sh)
jq listing the keys of a Vault path without displaying the values (page Configuration)

This repository sets no minimum version for these tools beyond their compatibility with Talos v1.13 and the Kubernetes version of the target cluster: match talosctl and kubectl to those two versions before starting, for lack of another validated reference point.

Accounts and accesses to obtain before starting

  • Hypervisor (or physical) access to the target hardware, sufficient to create the six virtual machines.
  • talosctl access: the ability to generate a set of Talos secrets specific to this instance (never reusing secrets from another instance).
  • A Cloudflare account on ${TV_DOMAIN}, with the token and the zone identifier from the previous paragraph.
  • Network access and credentials to each of the six services of the external data layer.
  • Access to the trade.infra repository to clone the guide and the scripts.
  • Outbound HTTPS on port 443, from the operator workstation and from the six nodes, to github.com, charts.longhorn.io, charts.external-secrets.io, the ${TV_REGISTRY} registry and the public registries of the upstream images (Talos, Calico, Traefik, Vault, kube-vip, cert-manager, metrics-server, Longhorn). No control of preflight.sh tests that egress: on a filtered network, the gap only shows up at step 3 of the Step by step, "Lay down the base layer", as pods stuck in ImagePullBackOff, that is to say during the intervention window. That opening is validated with the customer on the same footing as outbound port 53 and as the addressing plan.

Verify

Every command of this guide, those of this page as well as those of the following ones, runs from the root of a clone of the trade.infra repository. That clone is not in the archive: it is obtained from the vendor, who provides the repository URL, read access and the reference this guide describes (Overview).

git clone <repository-url-provided-by-the-vendor> trade.infra
cd trade.infra
git checkout <reference-provided-by-the-vendor>
git rev-parse --short HEAD

The last line prints the reference actually obtained: it must be the one the vendor handed over with this archive. If it differs, stop here and ask for the version of the guide that matches: the counts the following pages assert about the repository only hold for the reference described. The current directory stays that root until the end of the installation.

cp scripts/install.env.example scripts/install.env

The sheet is filled in on page 03: this copy only provides a file for the next command to read. As long as the sheet is not filled in, ${TV_DOMAIN} stays exemple.tld and control 3 rejects it, which is expected at this stage.

The cluster does not exist yet: run preflight.sh with --skip-cluster. That flag skips the two sections that require a cluster, and those only: cluster reachability (6) and the default StorageClass (7). Control 8, the reachability of the six endpoints of the data layer, runs in both cases: its nc -z calls go from the workstation to endpoints external to the cluster, and it is precisely before the installation that this answer matters most.

export CF_API_TOKEN=...
scripts/preflight.sh --env scripts/install.env --skip-cluster

Expected, once the sheet is filled in (page 03): PREFLIGHT : tous les controles passent. That line does cover the six data endpoints: the six ok lines of control 8 are in the output, and six KO lines would be there just as much.

If a line shows KO, the corresponding control has failed. Among the controls run at this stage, every KO line blocks the installation: it is not worked around, it is fixed before going any further. Once the cluster is bootstrapped (page 06), run the same command again without --skip-cluster: it adds the two sections that require the cluster, reachability and the default StorageClass.