> For the complete documentation index, see [llms.txt](https://experience-digital-transform.gitbook.io/experience-digital-transformation/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://experience-digital-transform.gitbook.io/experience-digital-transformation/cloud-for-dpi/chapter-5/1-prepare-the-installation.md).

# 1. Prepare the installation

Before the first machine is created, we decide where the platform runs and record those decisions in its configuration. Chapter 3 introduced OpenStack discovery, and Chapter 4 explained the image and network requirements for Cluster API. Here we apply them in seven steps. Each step shows the command we use, the result we look for and the configuration value it produces. The [preparation walkthrough](https://git.vanillacore.net/giga/content/src/branch/main/chapter-5/lab/walkthroughs/01-preparation.md) contains the same steps with the complete checks.

## Machines in the environment

The installation begins with a **gateway VM** and a **Management Kubernetes VM**, also called the seed. The gateway provides the protected administration route and forwards connections into the private network. Kubernetes on the seed hosts the cluster controllers and Management services. This reference installation starts with one Kubernetes VM; an availability design using several machines needs a corresponding Management deployment. [\[9\]](https://git.vanillacore.net/govstack/govstack-bootstrap/src/commit/1195f539afe5a1018be07d2e1865824c225ee407/docs/spoke-lifecycle.md)[\[10\]](https://git.vanillacore.net/govstack/govstack-bootstrap/src/commit/1195f539afe5a1018be07d2e1865824c225ee407/tofu/bootstrap/main.tf)

A managed Spoke has control-plane and worker VMs sized for its workload. Its configured VPN gateway is an additional CAPI-managed Kubernetes Node with a taint that keeps ordinary application workloads away from it. The public gateway forwards the Spoke's VPN connection to that Node. This gives the operator a protected path to the Spoke's private endpoints. [\[13\]](https://git.vanillacore.net/govstack/govstack-bootstrap/src/commit/1195f539afe5a1018be07d2e1865824c225ee407/templates/loadbalancer-foundation-apps.yaml.j2)

<figure><img src="https://1406653965-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkZFMZ3jCMUwHtTN0sL4J%2Fuploads%2Fgit-blob-baa667f40f72b245bc951f03b9d24a552e18247d%2Ffinal.png?alt=media" alt="The initial environment contains a gateway VM and a Management Kubernetes VM hosting controllers. A managed Spoke contains control-plane VMs, workers and a CAPI-managed VPN gateway Node. Machine counts depend on the chosen capacity and availability requirements."><figcaption><p>Figure 6. The gateway provides access; Kubernetes machines host Management services or the Spoke's platform and application workloads.</p></figcaption></figure>

## Step 1. Select the controller and cloud profile

The installation runs its cloud commands on an Ansible inventory host, the controller. The cloud profile selected on that host determines the project in which the machines are created, so we run all discovery there. The reference configuration names inventory host `oslab-ciab`, checks hostname `cumulus` and uses profile `test`: [\[15\]](https://git.vanillacore.net/govstack/govstack-bootstrap/src/commit/1195f539afe5a1018be07d2e1865824c225ee407/config/environments/oslab-ciab.example.yml)

```yaml
controller:
  inventory_host: oslab-ciab  # Inventory entry running cloud preparation.
  expected_hostname: cumulus # Hostname checked on that machine.
  tenant_cloud: test         # OpenStack profile on that machine.
openstack:
  region_name: RegionOne     # Region for the selected installation.
```

On the controller, we select the same configuration file, profile and region, then ask OpenStack which project the profile authenticates to:

```bash
# Use the protected cloud configuration and the profile named above.
export OS_CLIENT_CONFIG_FILE=/opt/configuration/environments/openstack/clouds.yml
export OS_CLOUD=test
export OS_REGION_NAME=RegionOne
# Authenticate and print only the project ID, not the token.
openstack token issue -f value -c project_id
```

The command prints one project ID. If it is not the project intended for the platform, we correct the profile before continuing. On the reference controller, the cloud configuration is readable only with elevated rights, which the playbooks also use. Steps 2 to 5 run in the same shell. [\[1\]](https://docs.openstack.org/python-openstackclient/latest/configuration/index.html)[\[15\]](https://git.vanillacore.net/govstack/govstack-bootstrap/src/commit/1195f539afe5a1018be07d2e1865824c225ee407/config/environments/oslab-ciab.example.yml)

## Step 2. Choose the machine sizes

Every machine needs a flavor: the gateway, the Management seed, and the control-plane, worker and VPN gateway machines of each Spoke. We list the sizes available to the project and inspect a candidate in detail:

```bash
# List the available sizes with vCPUs, RAM (MiB) and disk (GB).
openstack flavor list -c Name -c VCPUs -c RAM -c Disk
# Inspect the reference Management size.
openstack flavor show SCS-4V-8 -c vcpus -c ram -c disk
```

The reference configuration uses `SCS-1V-2-5` for the gateway, `SCS-4V-8` for Management and `SCS-2V-4-20s` for the LoadBalancer Spoke's control plane and workers. If a name is missing in our cloud, we choose an available flavor with comparable resources. The Management seed needs at least 8192 MiB of RAM; the installation refuses a smaller flavor. [\[10\]](https://git.vanillacore.net/govstack/govstack-bootstrap/src/commit/1195f539afe5a1018be07d2e1865824c225ee407/tofu/bootstrap/main.tf)[\[15\]](https://git.vanillacore.net/govstack/govstack-bootstrap/src/commit/1195f539afe5a1018be07d2e1865824c225ee407/config/environments/oslab-ciab.example.yml)

## Step 3. Check the base image and external network

The gateway and seed boot from a general Linux image, and the gateway receives its public address from an external network. The reference configuration names them `Ubuntu 24.04` and `public`:

```bash
# The base image must exist and be active.
openstack image show "Ubuntu 24.04" -c id -c status
# The network must be external so that it can supply floating IPs.
openstack network show public -c id -c router:external
```

We continue when the image status is `active` and `router:external` is `True`. Spoke machines use a separate image built for their Kubernetes version during the Management installation. That stage records the image ID and version. We compare that version with each cluster file before rendering; the lab helper rejects a mismatch. [\[9\]](https://git.vanillacore.net/govstack/govstack-bootstrap/src/commit/1195f539afe5a1018be07d2e1865824c225ee407/docs/spoke-lifecycle.md)[\[10\]](https://git.vanillacore.net/govstack/govstack-bootstrap/src/commit/1195f539afe5a1018be07d2e1865824c225ee407/tofu/bootstrap/main.tf)

## Step 4. Plan the addresses

The installation creates one private network, `10.42.0.0/24`. Its DHCP service assigns machine addresses from `.20` to `.95` and from `.128` to `.254`. Fixed addresses therefore come from the two remaining blocks: the gateway, the seed and every API address from `.2` to `.19`, and the exposed-Service pools from `.96` to `.127`. The reference configuration reserves `.100` to `.120` for Management services. We end that range at `.107`, so that the pools of all three Spokes fit beside it. [\[10\]](https://git.vanillacore.net/govstack/govstack-bootstrap/src/commit/1195f539afe5a1018be07d2e1865824c225ee407/tofu/bootstrap/main.tf)

| Allocation             | Management                          | `loadbalancer`               | `information-mediator`       | `example-workloads`          |
| ---------------------- | ----------------------------------- | ---------------------------- | ---------------------------- | ---------------------------- |
| Fixed addresses        | gateway `.3`, seed `.10`, API `.11` | API `.12`, VPN gateway `.15` | API `.13`, VPN gateway `.16` | API `.14`, VPN gateway `.17` |
| Exposed-Service pool   | `.100` to `.107`                    | `.121` to `.126`             | `.108` to `.111`             | `.112` to `.115`             |
| Pod CIDR               | `192.168.0.0/16`                    | `172.21.0.0/16`              | `172.22.0.0/16`              | `172.23.0.0/16`              |
| Service CIDR           | `10.96.0.0/12`                      | `10.80.0.0/16`               | `10.81.0.0/16`               | `10.82.0.0/16`               |
| VPN range and UDP port | `10.255.0.0/24`, 51820              | `10.255.10.0/24`, 51830      | `10.255.11.0/24`, 51831      | `10.255.12.0/24`, 51832      |

Pod and Service ranges are internal to each cluster. We still give every Spoke its own ranges, outside the private network and the VPN ranges, so that an address in a log or route identifies one environment. We then compare the plan with the networks that already exist in the project:

```bash
# List existing subnets; the planned ranges must not overlap with them.
openstack subnet list -c Name -c Subnet
```

An overlap with an existing subnet would send traffic for one of our ranges to the wrong network, so we change the plan before writing it into the configuration.

## Step 5. Check the capacity

The starting installation has 11 machines: the gateway, the seed, and for each Spoke one control-plane machine, one worker and one VPN gateway Node. A rolling replacement briefly adds one control-plane machine and one worker per Spoke, so the quota must allow 17 instances at peak, with matching cores and RAM:

```bash
# Show quota limits and current use for cores, RAM and instances.
openstack limits show --absolute
```

We add the planned demand to `total_cores_used`, `total_ram_used` and `total_instances_used` and compare the sums with `max_total_cores`, `max_total_ram_size` and `max_total_instances`. Older clients print the same values as `totalCoresUsed`, `maxTotalCores` and so on. The preflight in Step 7 performs the same calculation from the cluster files. It does not count the Spoke VPN gateway Nodes, so we reserve their three instances ourselves. [\[10\]](https://git.vanillacore.net/govstack/govstack-bootstrap/src/commit/1195f539afe5a1018be07d2e1865824c225ee407/tofu/bootstrap/main.tf)

## Step 6. Write the configuration files

Steps 6 and 7 run on the execution host that holds the `govstack-bootstrap` checkout and starts Ansible. We keep the site configuration outside the checkout in a protected directory and copy the tracked templates there:

```bash
# Work in the govstack-bootstrap checkout; site files stay outside it.
cd "$HOME/src/govstack-bootstrap"
SITE="$HOME/.local/state/chapter5-lab/live"
umask 077
mkdir -p "$SITE/clusters"
cp inventories/oslab-ciab/hosts.yml "$SITE/inventory.yml"
cp config/environments/oslab-ciab.example.yml "$SITE/environment.yml"
cp config/clusters/loadbalancer.example.yml "$SITE/clusters/loadbalancer.yml"
cp config/clusters/spoke.example.yml "$SITE/clusters/information-mediator.yml"
cp config/clusters/spoke.example.yml "$SITE/clusters/example-workloads.yml"
# Later playbooks read site.yml from the checkout; Git ignores this file.
cp -n config/site.example.yml config/site.yml
```

The inventory reads the controller's address, user and jump host from `OSLAB_CIAB_ANSIBLE_HOST`, `OSLAB_CIAB_ANSIBLE_USER` and `OSLAB_CIAB_PROXY_JUMP`. `config/site.yml` holds the tool versions and the operator's WireGuard peers for the Management gateway. The environment file receives the decisions from Steps 1 to 4; these are its central fields:

```yaml
openstack:
  region_name: RegionOne
  external_network: public
  baseline_image: Ubuntu 24.04
  gateway_flavor: SCS-1V-2-5
  management_flavor: SCS-4V-8
infrastructure:
  network_cidr: 10.42.0.0/24
  gateway_ip: 10.42.0.3
  seed_ip: 10.42.0.10
  api_vip: 10.42.0.11
  service_vip_cidr: 10.42.0.96/27
  service_vip_start: 10.42.0.100
  service_vip_end: 10.42.0.107
```

Each cluster file receives its Spoke's name, role, flavors, addresses and VPN settings from the same plan; Section 3 shows the LoadBalancer file. OpenTofu state, infrastructure outputs, image metadata and credentials are produced later. They stay out of Git, in protected directories on the controller and the execution host. Configuration and state together let us trace the installation and rebuild it. [\[2\]](https://opentofu.org/docs/language/state/)[\[9\]](https://git.vanillacore.net/govstack/govstack-bootstrap/src/commit/1195f539afe5a1018be07d2e1865824c225ee407/docs/spoke-lifecycle.md)[\[10\]](https://git.vanillacore.net/govstack/govstack-bootstrap/src/commit/1195f539afe5a1018be07d2e1865824c225ee407/tofu/bootstrap/main.tf)

## Step 7. Run the read-only preflight

The preflight playbook checks the prepared files against the cloud without changing anything:

```bash
# Read-only: check controller, services, flavors, image, network and quota.
ansible-playbook -i "$SITE/inventory.yml" playbooks/01-preflight-readonly.yml \
  -e @"$SITE/environment.yml" \
  -e environment_config_file="$SITE/environment.yml" \
  -e preflight_clusters_dir="$SITE/clusters"
```

It confirms the controller's hostname, authenticates with the selected profile and checks the required services, the flavors, the active base image, the external network, the seed's RAM and the quota for the declared machines. A successful run ends with `Preflight passed for oslab-ciab; nothing was changed.` A failed check reports the value that blocked it, for example an inactive image or too little quota. With a passing preflight, Section 2 starts the installation. [\[9\]](https://git.vanillacore.net/govstack/govstack-bootstrap/src/commit/1195f539afe5a1018be07d2e1865824c225ee407/docs/spoke-lifecycle.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://experience-digital-transform.gitbook.io/experience-digital-transformation/cloud-for-dpi/chapter-5/1-prepare-the-installation.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
