Files
terraform-libvirt-talos/do
2025-08-06 07:40:13 +00:00

391 lines
13 KiB
Bash
Executable File

#!/bin/bash
set -euo pipefail
# the talos image builder.
# NB this can be one of:
# imager: build locally using the ghcr.io/siderolabs/imager container image.
# image_factory: build remotely using the image factory service at https://factory.talos.dev.
# NB this is automatically set to imager when running on linux 6.1+; otherwise,
# it is set to image_factory.
talos_image_builder="$(perl -e 'print ((`uname -r` =~ /^(\d+\.\d+)/ && $1 >= 6.1) ? "imager" : "image_factory")')"
# see https://github.com/siderolabs/talos/releases
# renovate: datasource=github-releases depName=siderolabs/talos
talos_version="1.10.6"
# see https://github.com/siderolabs/extensions/pkgs/container/qemu-guest-agent
# see https://github.com/siderolabs/extensions/tree/main/guest-agents/qemu-guest-agent
talos_qemu_guest_agent_extension_tag="10.0.2@sha256:0d16cd4f8cefab33f091e336bc666943a71355ee010d0dfa0e46498693af1c52"
# see https://github.com/siderolabs/extensions/pkgs/container/drbd
# see https://github.com/siderolabs/extensions/tree/main/storage/drbd
# see https://github.com/LINBIT/drbd
talos_drbd_extension_tag="9.2.14-v1.10.6@sha256:ca7fba878c5acb8fdfe130a39472a6c0a5c9dd74d65ba7507c09780a873b29c7"
# see https://github.com/siderolabs/extensions/pkgs/container/spin
# see https://github.com/siderolabs/extensions/tree/main/container-runtime/spin
talos_spin_extension_tag="v0.19.0@sha256:c88e8b1a6de4acd8d98f6aacc716c8e9aef3f7962d04893b49afc77d013b8ba2"
# see https://github.com/piraeusdatastore/piraeus-operator/releases
# renovate: datasource=github-releases depName=piraeusdatastore/piraeus-operator
piraeus_operator_version="2.9.0"
export CHECKPOINT_DISABLE='1'
export TF_LOG='DEBUG' # TRACE, DEBUG, INFO, WARN or ERROR.
export TF_LOG_PATH='terraform.log'
export TALOSCONFIG=$PWD/talosconfig.yml
export KUBECONFIG=$PWD/kubeconfig.yml
function step {
echo "### $* ###"
}
function update-talos-extension {
# see https://github.com/siderolabs/extensions?tab=readme-ov-file#installing-extensions
local variable_name="$1"
local image_name="$2"
local images="$3"
local image="$(grep -F "$image_name:" <<<"$images")"
local tag="${image#*:}"
echo "updating the talos extension to $image..."
variable_name="$variable_name" tag="$tag" perl -i -pe '
BEGIN {
$var = $ENV{variable_name};
$val = $ENV{tag};
}
s/^(\Q$var\E=).*/$1"$val"/;
' do
}
function update-talos-extensions {
step "updating the talos extensions"
local images="$(crane export "ghcr.io/siderolabs/extensions:v$talos_version" | tar x -O image-digests)"
update-talos-extension talos_qemu_guest_agent_extension_tag ghcr.io/siderolabs/qemu-guest-agent "$images"
update-talos-extension talos_drbd_extension_tag ghcr.io/siderolabs/drbd "$images"
update-talos-extension talos_spin_extension_tag ghcr.io/siderolabs/spin "$images"
}
function build_talos_image__imager {
# see https://www.talos.dev/v1.10/talos-guides/install/boot-assets/
# see https://www.talos.dev/v1.10/advanced/metal-network-configuration/
# see Profile type at https://github.com/siderolabs/talos/blob/v1.10.6/pkg/imager/profile/profile.go#L23-L46
local talos_version_tag="v$talos_version"
rm -rf tmp/talos
mkdir -p tmp/talos
cat >"tmp/talos/talos-$talos_version.yml" <<EOF
arch: amd64
platform: nocloud
secureboot: false
version: $talos_version_tag
customization:
extraKernelArgs:
- net.ifnames=0
input:
kernel:
path: /usr/install/amd64/vmlinuz
initramfs:
path: /usr/install/amd64/initramfs.xz
baseInstaller:
imageRef: ghcr.io/siderolabs/installer:$talos_version_tag
systemExtensions:
- imageRef: ghcr.io/siderolabs/qemu-guest-agent:$talos_qemu_guest_agent_extension_tag
- imageRef: ghcr.io/siderolabs/drbd:$talos_drbd_extension_tag
- imageRef: ghcr.io/siderolabs/spin:$talos_spin_extension_tag
output:
kind: image
imageOptions:
diskSize: $((2*1024*1024*1024))
diskFormat: raw
outFormat: raw
EOF
echo "creating image..."
docker run --rm -i \
-v $PWD/tmp/talos:/secureboot:ro \
-v $PWD/tmp/talos:/out \
-v /dev:/dev \
--privileged \
"ghcr.io/siderolabs/imager:$talos_version_tag" \
- < "tmp/talos/talos-$talos_version.yml"
}
function build_talos_image__image_factory {
# see https://www.talos.dev/v1.10/learn-more/image-factory/
# see https://github.com/siderolabs/image-factory?tab=readme-ov-file#http-frontend-api
local talos_version_tag="v$talos_version"
rm -rf tmp/talos
mkdir -p tmp/talos
echo "creating image factory schematic..."
cat >"tmp/talos/talos-$talos_version.yml" <<EOF
customization:
extraKernelArgs:
- net.ifnames=0
systemExtensions:
officialExtensions:
- siderolabs/qemu-guest-agent
- siderolabs/drbd
- siderolabs/spin
EOF
local schematic_response="$(curl \
-X POST \
--silent \
--data-binary @"tmp/talos/talos-$talos_version.yml" \
https://factory.talos.dev/schematics)"
local schematic_id="$(jq -r .id <<<"$schematic_response")"
if [ -z "$schematic_id" ]; then
echo "ERROR: Failed to create the image schematic."
exit 1
fi
local image_url="https://factory.talos.dev/image/$schematic_id/$talos_version_tag/nocloud-amd64.raw.zst"
echo "downloading image from $image_url..."
rm -f tmp/talos/nocloud-amd64.raw.zst
curl \
--silent \
--output tmp/talos/nocloud-amd64.raw.zst \
"$image_url"
echo "extracting image..."
unzstd tmp/talos/nocloud-amd64.raw.zst
}
function build_talos_image {
case "$talos_image_builder" in
imager)
build_talos_image__imager
;;
image_factory)
build_talos_image__image_factory
;;
*)
echo $"unknown talos_image_builder $talos_image_builder"
exit 1
;;
esac
echo "converting image to the qcow2 format..."
local talos_libvirt_base_volume_name="talos-$talos_version.qcow2"
qemu-img convert -O qcow2 tmp/talos/nocloud-amd64.raw tmp/talos/$talos_libvirt_base_volume_name
qemu-img info tmp/talos/$talos_libvirt_base_volume_name
if [ -n "$(virsh vol-list default | grep $talos_libvirt_base_volume_name)" ]; then
virsh vol-delete --pool default $talos_libvirt_base_volume_name
fi
echo "uploading image to libvirt..."
virsh vol-create-as default $talos_libvirt_base_volume_name 10M
virsh vol-upload --pool default $talos_libvirt_base_volume_name tmp/talos/$talos_libvirt_base_volume_name
cat >terraform.tfvars <<EOF
talos_version = "$talos_version"
talos_libvirt_base_volume_name = "$talos_libvirt_base_volume_name"
EOF
}
function init {
step 'build talos image'
build_talos_image
step 'terraform init'
terraform init -lockfile=readonly
}
function plan {
step 'terraform plan'
terraform plan -out=tfplan
}
function apply {
step 'terraform apply'
terraform apply tfplan
terraform output -raw talosconfig >talosconfig.yml
terraform output -raw kubeconfig >kubeconfig.yml
health
piraeus-install
export-kubernetes-ingress-ca-crt
info
}
function health {
step 'talosctl health'
local controllers="$(terraform output -raw controllers)"
local workers="$(terraform output -raw workers)"
local c0="$(echo $controllers | cut -d , -f 1)"
talosctl -e $c0 -n $c0 \
health \
--control-plane-nodes $controllers \
--worker-nodes $workers
}
function piraeus-install {
# see https://github.com/piraeusdatastore/piraeus-operator
# see https://github.com/piraeusdatastore/piraeus-operator/blob/v2.9.0/docs/how-to/talos.md
# see https://github.com/piraeusdatastore/piraeus-operator/blob/v2.9.0/docs/tutorial/get-started.md
# see https://github.com/piraeusdatastore/piraeus-operator/blob/v2.9.0/docs/tutorial/replicated-volumes.md
# see https://github.com/piraeusdatastore/piraeus-operator/blob/v2.9.0/docs/explanation/components.md
# see https://github.com/piraeusdatastore/piraeus-operator/blob/v2.9.0/docs/reference/linstorsatelliteconfiguration.md
# see https://github.com/piraeusdatastore/piraeus-operator/blob/v2.9.0/docs/reference/linstorcluster.md
# see https://linbit.com/drbd-user-guide/linstor-guide-1_0-en/
# see https://linbit.com/drbd-user-guide/linstor-guide-1_0-en/#ch-kubernetes
# see 5.7.1. Available Parameters in a Storage Class at https://linbit.com/drbd-user-guide/linstor-guide-1_0-en/#s-kubernetes-sc-parameters
# see https://linbit.com/drbd-user-guide/drbd-guide-9_0-en/
# see https://www.talos.dev/v1.10/kubernetes-guides/configuration/storage/#piraeus--linstor
step 'piraeus install'
kubectl apply --server-side -k "https://github.com/piraeusdatastore/piraeus-operator//config/default?ref=v$piraeus_operator_version"
step 'piraeus wait'
kubectl wait pod --timeout=15m --for=condition=Ready -n piraeus-datastore -l app.kubernetes.io/component=piraeus-operator
step 'piraeus configure'
kubectl apply -n piraeus-datastore -f - <<'EOF'
apiVersion: piraeus.io/v1
kind: LinstorSatelliteConfiguration
metadata:
name: talos-loader-override
spec:
podTemplate:
spec:
initContainers:
- name: drbd-shutdown-guard
$patch: delete
- name: drbd-module-loader
$patch: delete
volumes:
- name: run-systemd-system
$patch: delete
- name: run-drbd-shutdown-guard
$patch: delete
- name: systemd-bus-socket
$patch: delete
- name: lib-modules
$patch: delete
- name: usr-src
$patch: delete
- name: etc-lvm-backup
hostPath:
path: /var/etc/lvm/backup
type: DirectoryOrCreate
- name: etc-lvm-archive
hostPath:
path: /var/etc/lvm/archive
type: DirectoryOrCreate
EOF
kubectl apply -f - <<EOF
apiVersion: piraeus.io/v1
kind: LinstorCluster
metadata:
name: linstor
EOF
kubectl apply -f - <<EOF
apiVersion: storage.k8s.io/v1
kind: StorageClass
provisioner: linstor.csi.linbit.com
metadata:
name: linstor-lvm-r1
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete
parameters:
csi.storage.k8s.io/fstype: xfs
linstor.csi.linbit.com/autoPlace: "1"
linstor.csi.linbit.com/storagePool: lvm
EOF
step 'piraeus configure wait'
kubectl wait pod --timeout=15m --for=condition=Ready -n piraeus-datastore -l app.kubernetes.io/name=piraeus-datastore
kubectl wait LinstorCluster/linstor --timeout=15m --for=condition=Available
step 'piraeus create-device-pool'
local workers="$(terraform output -raw workers)"
local nodes=($(echo "$workers" | tr ',' ' '))
for ((n=0; n<${#nodes[@]}; ++n)); do
local node="w$((n))"
local wwn="$(printf "000000000000ab%02x" $n)"
step "piraeus wait node $node"
while ! kubectl linstor storage-pool list --node "$node" >/dev/null 2>&1; do sleep 3; done
step "piraeus create-device-pool $node"
if ! kubectl linstor storage-pool list --node "$node" --storage-pool lvm | grep -q lvm; then
kubectl linstor physical-storage create-device-pool \
--pool-name lvm \
--storage-pool lvm \
lvm \
"$node" \
"/dev/disk/by-id/wwn-0x$wwn"
fi
done
}
function piraeus-info {
step 'piraeus node list'
kubectl linstor node list
step 'piraeus storage-pool list'
kubectl linstor storage-pool list
step 'piraeus volume list'
kubectl linstor volume list
}
function info {
local controllers="$(terraform output -raw controllers)"
local workers="$(terraform output -raw workers)"
local nodes=($(echo "$controllers,$workers" | tr ',' ' '))
step 'talos node installer image'
for n in "${nodes[@]}"; do
# NB there can be multiple machineconfigs in a machine. we only want to see
# the ones with an id that looks like a version tag.
talosctl -n $n get machineconfigs -o json \
| jq -r 'select(.metadata.id | test("v\\d+")) | .spec' \
| yq -r '.machine.install.image' \
| sed -E "s,(.+),$n: \1,g"
done
step 'talos node os-release'
for n in "${nodes[@]}"; do
talosctl -n $n read /etc/os-release \
| sed -E "s,(.+),$n: \1,g"
done
step 'kubernetes nodes'
kubectl get nodes -o wide
piraeus-info
}
function export-kubernetes-ingress-ca-crt {
step 'export kubernetes-ingress-ca-crt.pem'
kubectl get -n cert-manager secret/ingress-tls -o jsonpath='{.data.tls\.crt}' \
| base64 -d \
> kubernetes-ingress-ca-crt.pem
}
function upgrade {
step 'talosctl upgrade'
local controllers=($(terraform output -raw controllers | tr ',' ' '))
local workers=($(terraform output -raw workers | tr ',' ' '))
for n in "${controllers[@]}" "${workers[@]}"; do
talosctl -e $n -n $n upgrade --preserve --wait
done
health
}
function destroy {
terraform destroy -auto-approve
}
case $1 in
update-talos-extensions)
update-talos-extensions
;;
init)
init
;;
plan)
plan
;;
apply)
apply
;;
plan-apply)
plan
apply
;;
health)
health
;;
info)
info
;;
destroy)
destroy
;;
*)
echo $"Usage: $0 {init|plan|apply|plan-apply|health|info}"
exit 1
;;
esac