mirror of
https://github.com/kubernetes-sigs/kubespray.git
synced 2026-10-30 22:16:23 -02:30
188 lines
9.9 KiB
Markdown
188 lines
9.9 KiB
Markdown
# Kubernetes on OpenNebula with Terraform
|
|
|
|
Provision a Kubespray-ready cluster on OpenNebula 7.2+: Terraform creates the
|
|
VMs from an existing contextualized template and generates the Ansible
|
|
inventory, then the standard Kubespray playbook deploys Kubernetes.
|
|
|
|
## Requirements
|
|
|
|
- OpenNebula >= 7.2 with the XML-RPC endpoint reachable from this machine
|
|
- An existing VM template with the [one-context](https://github.com/OpenNebula/one-apps)
|
|
package installed (all OpenNebula marketplace OS appliances qualify) (for
|
|
the contextualization requirement — see the disk-options note below for a
|
|
separate template constraint). The template's network context must be
|
|
enabled (`NETWORK = "YES"` is set by this module).
|
|
- An existing Virtual Network whose addresses are reachable from the machine
|
|
running Terraform and Ansible
|
|
- Terraform >= 1.3 or OpenTofu (provider `OpenNebula/opennebula ~> 1.5` — the
|
|
release line adapted to the OpenNebula 7.x API)
|
|
- The VM template should NOT define its own NIC — the module attaches the
|
|
network explicitly, and a template NIC would leave nodes dual-homed with
|
|
the default route on the wrong interface.
|
|
- To use any disk option (`master_disk_size`/`worker_disk_size`/`additional_disk_size`)
|
|
the template's first disk must reference its image by numeric `IMAGE_ID` —
|
|
templates created via Sunstone wizards or the CLI often reference the image
|
|
by name (`IMAGE=`), which the provider cannot read; the module fails at
|
|
plan time with a clear error in that case.
|
|
|
|
## Quickstart
|
|
|
|
```ShellSession
|
|
$ cd kubespray
|
|
$ cp -LRp contrib/terraform/opennebula/sample-inventory inventory/mycluster
|
|
$ cd inventory/mycluster
|
|
$ $EDITOR cluster.tfvars # template_name, network_name, master/worker_count, ssh_public_keys
|
|
$ export OPENNEBULA_ENDPOINT="https://one.example.com:2634/RPC2"
|
|
$ export OPENNEBULA_USERNAME="oneadmin"
|
|
$ export OPENNEBULA_PASSWORD="opennebula"
|
|
$ terraform -chdir=../../contrib/terraform/opennebula init
|
|
$ terraform -chdir=../../contrib/terraform/opennebula apply \
|
|
-var-file=$PWD/cluster.tfvars \
|
|
-var inventory_file=$PWD/inventory.ini
|
|
```
|
|
|
|
**Pass `-var inventory_file=$PWD/inventory.ini` on every subsequent `apply`
|
|
as well** — without it the variable falls back to a path relative to the
|
|
module directory and the previously rendered inventory file is removed.
|
|
|
|
After `apply`, the VMs are RUNNING in OpenNebula and `inventory.ini` contains
|
|
all Kubespray groups. Kubespray pins its Ansible version — install it in a
|
|
virtualenv from the repo root before deploying:
|
|
|
|
```ShellSession
|
|
$ python3 -m venv ~/.venvs/kubespray && source ~/.venvs/kubespray/bin/activate && \
|
|
pip install -r requirements.txt
|
|
```
|
|
|
|
Deploy Kubernetes:
|
|
|
|
```ShellSession
|
|
$ cd ../.. && \
|
|
ansible-playbook -i inventory/mycluster/inventory.ini cluster.yml -b
|
|
```
|
|
|
|
Smoke-test checklist: VMs visible and RUNNING in Sunstone; `inventory.ini`
|
|
written next to your `cluster.tfvars`; `ansible -i inventory/mycluster/inventory.ini -m ping all`
|
|
succeeds (one-context may need a few seconds after RUNNING before SSH is up).
|
|
|
|
To tear the cluster down: `terraform -chdir=contrib/terraform/opennebula destroy
|
|
-var-file=$PWD/inventory/mycluster/cluster.tfvars` (run from the repo root;
|
|
this also deletes the rendered `inventory.ini`).
|
|
|
|
The Terraform state lives in `contrib/terraform/opennebula/` — manage one
|
|
cluster per checkout, or use `terraform workspace` for more.
|
|
|
|
## Variables
|
|
|
|
### Required
|
|
|
|
- `template_name`: name of the existing contextualized OpenNebula VM template
|
|
- `network_name`: name of the existing Virtual Network for the nodes
|
|
- `master_count` / `worker_count`: how many control-plane and worker nodes to
|
|
create (named `master-0..`, `worker-0..`; at least one master required) —
|
|
the simple way to define the cluster
|
|
- `machines`: advanced alternative to the counts (takes precedence when set);
|
|
map of machines, key = machine name (VM/hostname becomes `<prefix>-<key>`)
|
|
- `node_type`: `master` or `worker` (at least one master required)
|
|
- `ip`: optional static IP inside an address range of the network; empty
|
|
string lets OpenNebula assign a lease. When `network_reservation_size > 0`
|
|
the static IP must fall inside the **reservation's** address range, not
|
|
just the parent network's
|
|
- `ssh_public_keys`: list of SSH public keys injected into the VMs via
|
|
contextualization
|
|
|
|
### Connection
|
|
|
|
Set `OPENNEBULA_ENDPOINT`, `OPENNEBULA_USERNAME`, `OPENNEBULA_PASSWORD`
|
|
(and optionally `OPENNEBULA_INSECURE=true`) in the environment, or the
|
|
`one_endpoint` / `one_username` / `one_password` / `one_insecure` variables
|
|
(e.g. `one_endpoint = "https://one.example.com:2634/RPC2"`).
|
|
OpenNebula XML-RPC sends the username:password with every request — use a
|
|
TLS endpoint in production (`http://…:2633` is the plaintext out-of-the-box
|
|
default).
|
|
|
|
### Optional
|
|
|
|
- `prefix` (default `k8s`): VM name prefix — must be unique per OpenNebula
|
|
user when the extras are enabled (the extra-disk image, VM group and
|
|
reservation are named `<prefix>-...` and OpenNebula rejects duplicate
|
|
names)
|
|
- `ansible_user` (default `root`): SSH user written into the inventory
|
|
- `inventory_file` (default `inventory.ini`): where the inventory is
|
|
rendered — relative paths resolve against `contrib/terraform/opennebula/`
|
|
because of `-chdir`; prefer an absolute path (e.g. set it in your
|
|
`cluster.tfvars`)
|
|
- `master_cpu`/`master_vcpu`/`master_memory` (1 / 2 / 4096 MB)
|
|
- `worker_cpu`/`worker_vcpu`/`worker_memory` (1 / 4 / 8192 MB)
|
|
- `master_disk_size`/`worker_disk_size` (MB, default 0 = keep the template's
|
|
disk; grow-only and create-time — raising it later does NOT grow an
|
|
existing cluster's root disks). Any disk option drops the template's
|
|
secondary disks (only the first disk is re-declared).
|
|
- `vm_create_timeout` (default `20m`)
|
|
- `additional_disk_size` (MB, default 0): extra non-persistent DATABLOCK disk
|
|
cloned per node (requires `image_datastore_name`) — strictly create-time in
|
|
BOTH directions: enabling it on an existing cluster silently hot-attaches
|
|
the re-declared OS disk as a duplicate OS-image clone on every node (apply
|
|
succeeds, wrong result), resizing it later fails against the in-use image,
|
|
and disabling it can attempt to detach the boot disk. Decide disk options
|
|
at cluster creation; changing them means recreating the VMs. The module
|
|
automatically re-declares the template's first disk alongside the extra
|
|
disk (preserving the template's `SIZE=` override; `TARGET`/`DRIVER` fall
|
|
back to image defaults); templates with more than one disk lose their
|
|
secondary disks whenever any disk option is used.
|
|
- `masters_anti_affinity` (default `false`): spread masters across hosts with
|
|
an `ANTI_AFFINED` VM group; requires at least as many hosts as masters — on
|
|
fewer hosts the VM stays pending until `vm_create_timeout` — changing this
|
|
on an existing cluster replaces every master VM.
|
|
- `network_reservation_size` (default 0): carve an address reservation of
|
|
this size out of `network_name` and attach the nodes to it — create-time
|
|
only (the provider cannot grow a reservation in place); use
|
|
`network_reservation_first_ip`/`network_reservation_ar_id` to pin the
|
|
reserved range so static `machines` IPs can target it. Toggling the
|
|
reservation on/off (0↔N) on an existing cluster re-attaches every node's
|
|
NIC on the other network and re-leases all IPs — it will break a deployed
|
|
cluster; treat it as a create-time choice. The reservation must be at
|
|
least as large as the number of nodes (enforced at plan time). Leaving
|
|
`network_reservation_first_ip`/`network_reservation_ar_id` unpinned causes
|
|
a harmless perpetual in-place diff on `plan` (the provider reads back the
|
|
allocated values) — pin both to avoid it.
|
|
- `network_reservation_first_ip` (default `""`): first IP of the reservation
|
|
carved from the parent network (empty = let OpenNebula choose)
|
|
- `network_reservation_ar_id` (default `null`): address-range ID of the
|
|
parent network to reserve from (null = provider default)
|
|
|
|
## Troubleshooting
|
|
|
|
- **CoreDNS / nodelocaldns in CrashLoopBackOff after deploy** (validated on
|
|
Ubuntu 24.04 marketplace images): the nodes' `/etc/resolv.conf` points at
|
|
the systemd-resolved stub (`127.0.0.53`), which CoreDNS detects as a
|
|
forwarding loop — or the OpenNebula VNet defines no `DNS` attribute at all.
|
|
Fix: set `upstream_dns_servers` (e.g. `[8.8.8.8, 1.1.1.1]` or your internal
|
|
resolver) in `inventory/$CLUSTER/group_vars/all/all.yml` and re-run
|
|
`cluster.yml`. Infrastructure-level alternative: set the `DNS` attribute on
|
|
the OpenNebula Virtual Network so contextualized guests get a real resolver.
|
|
- **A failed first run can leave the cluster half-installed**: if
|
|
`cluster.yml` dies before the network plugin is deployed, a re-run may wait
|
|
forever on "control plane nodes to be Ready" (the nodes stay NotReady
|
|
without a CNI). Recreate the VMs (`terraform destroy` + `apply`) and deploy
|
|
again on clean machines.
|
|
- **Transient `Service is in unknown state` handler failures** during heavy
|
|
image-pull phases are usually a one-off systemd/dbus hiccup — re-running
|
|
`cluster.yml` is safe and idempotent.
|
|
|
|
## Known issues
|
|
|
|
- Deleting VMs/images/networks manually in OpenNebula breaks `terraform
|
|
refresh`/`plan` with `NO_EXISTS` ([provider #623](https://github.com/OpenNebula/terraform-provider-opennebula/issues/623));
|
|
workaround: `terraform state rm <resource>`.
|
|
- Overriding the template disk (any disk option: `*_disk_size > 0` or
|
|
`additional_disk_size > 0`) can produce perpetual
|
|
in-place updates in `plan` with some template layouts
|
|
([provider #598](https://github.com/OpenNebula/terraform-provider-opennebula/issues/598)).
|
|
- A network reservation supports a single address range
|
|
([provider #625](https://github.com/OpenNebula/terraform-provider-opennebula/issues/625)).
|
|
- IPv4 only: IPv6 read-back from templates is broken in provider 1.5.0
|
|
([provider #615](https://github.com/OpenNebula/terraform-provider-opennebula/issues/615)).
|
|
- Provider CI upstream covers OpenNebula 6.10/7.0; 7.2 keeps XML-RPC
|
|
compatibility and has no known provider issues.
|