diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..546454e --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +**/.claude/settings.local.json + +# Claude's instructions file +CLAUDE.md + +# Certificate files +*.cert + +# Environment files +cluster-settings.env +clouds.yaml diff --git a/00-bootstrap-vm-cs.sh b/00-bootstrap-vm-cs.sh index f0495ee..dd2b1cf 100755 --- a/00-bootstrap-vm-cs.sh +++ b/00-bootstrap-vm-cs.sh @@ -5,78 +5,209 @@ # (c) Kurt Garloff , 2/2025 # SPDX-License-Identifier: CC-BY-SA-4.0 -# ToDo: Magic to switch b/w apt, zypper, dnf, pacman, ... +# Detect architecture and OS ARCH=$(uname -m) ARCH="${ARCH/x86_64/amd64}" OS=$(uname -s | tr A-Z a-z) +# Detect distribution details from os-release +if [ -f /etc/os-release ]; then + # Load variables from os-release file + . /etc/os-release + OS_ID=$ID + OS_VERSION_ID=$VERSION_ID + OS_ID_LIKE=$ID_LIKE +else + # Fallback to basic detection + OS_ID="unknown" + OS_VERSION_ID="unknown" + OS_ID_LIKE="" +fi + +echo "Detected OS: $OS_ID $OS_VERSION_ID" + # Usage: install_via_pkgmgr pkgnm [pkgnm [...]] install_via_pkgmgr() { - sudo $INSTCMD "$@" + echo "Installing packages: $@" + sudo $INSTCMD "$@" || { echo "Failed to install packages: $@"; return 1; } + echo "Successfully installed packages: $@" } # Verify sha256sum test_sha256() { - OUT=$(sha256sum "$1") - OUT=${OUT%% *} - if test "$OUT" != "$2"; then return 1; else return 0; fi + OUT=$(sha256sum "$1") + OUT=${OUT%% *} + if test "$OUT" != "$2"; then + echo "ERROR: Checksum mismatch for ${1}" 1>&2 + echo "Expected: $2" 1>&2 + echo "Got: $OUT" 1>&2 + return 1 + else + echo "Checksum verified for ${1}" + return 0 + fi } # Usage install_via_download_bin URL sha256 [newname] install_via_download_bin() { - cd ~/Download - curl -LO "$1" || return - FNM="${1##*/}" - if ! test_sha256 "$FNM" "$2"; then echo "Checksum mismatch for ${FNM}" 1>&2; return 1; fi - chmod +x "$FNM" - sudo mv "$FNM" /usr/local/bin/"$3" + cd ~/Download || { echo "ERROR: Failed to cd into ~/Download"; return 1; } + echo "Downloading $1..." + curl -LO "$1" || { echo "ERROR: Failed to download $1"; return 1; } + FNM="${1##*/}" + if ! test_sha256 "$FNM" "$2"; then return 1; fi + chmod +x "$FNM" || { echo "ERROR: Failed to set executable permissions on $FNM"; return 1; } + + # Determine target filename + local TARGET="/usr/local/bin/${FNM}" + if [ -n "$3" ] && [ "$3" != "." ]; then + TARGET="/usr/local/bin/$3" + fi + + echo "Moving $FNM to $TARGET" + sudo mv "$FNM" "$TARGET" || { echo "ERROR: Failed to move $FNM to $TARGET"; return 1; } + echo "Successfully installed $TARGET" } -# Usage install_via_download_bin URL sha256 extrpath [newname] +# Usage install_via_download_tgz URL sha256 extrpath [newname] install_via_download_tgz() { - cd ~/Download - curl -LO "$1" || return - FNM="${1##*/}" - if ! test_sha256 "$FNM" "$2"; then echo "Checksum mismatch for ${FNM}" 1>&2; return 1; fi - tar xvzf "$FNM" - sudo mv "$3" /usr/local/bin/"$4" + cd ~/Download || { echo "ERROR: Failed to cd into ~/Download"; return 1; } + echo "Downloading $1..." + curl -LO "$1" || { echo "ERROR: Failed to download $1"; return 1; } + FNM="${1##*/}" + if ! test_sha256 "$FNM" "$2"; then return 1; fi + + echo "Extracting $FNM..." + tar xzf "$FNM" || { echo "ERROR: Failed to extract $FNM"; return 1; } + + # Determine target filename + local TARGET="/usr/local/bin/${3##*/}" + if [ -n "$4" ] && [ "$4" != "." ]; then + TARGET="/usr/local/bin/$4" + fi + + echo "Moving $3 to $TARGET" + sudo mv "$3" "$TARGET" || { + echo "ERROR: Failed to move $3 to $TARGET"; + echo "Looking for file to move..."; + find . -name "${3##*/}" -type f; + return 1; + } + echo "Successfully installed $TARGET" } -# Debian 12 (Bookworm) +# Ensure the Download directory exists and is writable +echo "Creating Download directory if needed..." mkdir -p ~/Download +if [ ! -d ~/Download ]; then + echo "ERROR: Could not create ~/Download directory" + exit 1 +fi +# Make sure we have write permissions +touch ~/Download/.write_test && rm ~/Download/.write_test || { + echo "ERROR: Cannot write to ~/Download directory" + exit 1 +} + +# Set up package manager command INSTCMD="apt-get install -y --no-install-recommends --no-install-suggests" -DEB12_PKGS=(docker.io golang jq yq git gh python3-openstackclient) -DEB12_TGZS=("https://get.helm.sh/helm-v3.17.1-${OS}-${ARCH}.tar.gz") -DEB12_TCHK=("3b66f3cd28409f29832b1b35b43d9922959a32d795003149707fea84cbcd4469") -DEB12_TOLD=("${OS}-${ARCH}/helm") -DEB12_TNEW=(".") -DEB12_BINS=("https://github.com/kubernetes-sigs/kind/releases/download/v0.26.0/kind-${OS}-${ARCH}" - "https://dl.k8s.io/release/v1.31.6/bin/${OS}/${ARCH}/kubectl" - "https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.9.4/clusterctl-${OS}-${ARCH}" - ) -DEB12_BCHK=("d445b44c28297bc23fd67e51cc24bb294ae7b977712be2d4d312883d0835829b" - "c46b2f5b0027e919299d1eca073ebf13a4c5c0528dd854fc71a5b93396c9fa9d" - "0c80a58f6158cd76075fcc9a5d860978720fa88860c2608bb00944f6af1e5752" - ) -DEB12_BNEW=("kind" "." "clusterctl") +# Define package list with conditional logic for Docker based on distribution +if [ "$OS_ID" = "ubuntu" ]; then + echo "Setting up Docker repository for Ubuntu..." + sudo apt-get update + sudo apt-get install -y ca-certificates curl gnupg + sudo install -m 0755 -d /etc/apt/keyrings + curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg + sudo chmod a+r /etc/apt/keyrings/docker.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/$OS_ID $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + sudo apt-get update + PACKAGES=(docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin golang jq git gh python3-openstackclient) +elif [ "$OS_ID" = "debian" ] || [[ "$OS_ID_LIKE" == *"debian"* ]]; then + echo "Using Debian package sources..." + PACKAGES=(docker.io golang jq git gh python3-openstackclient) +else + echo "Using default package list for unknown distribution..." + PACKAGES=(docker.io golang jq git gh python3-openstackclient) +fi + +# Define binary downloads with their checksums +TARBALLS=("https://get.helm.sh/helm-v3.17.1-${OS}-${ARCH}.tar.gz") +TARBALL_CHECKSUMS=("3b66f3cd28409f29832b1b35b43d9922959a32d795003149707fea84cbcd4469") +TARBALL_EXTRACT_PATHS=("${OS}-${ARCH}/helm") +TARBALL_NEW_NAMES=(".") + +BINARIES=("https://github.com/kubernetes-sigs/kind/releases/download/v0.26.0/kind-${OS}-${ARCH}" + "https://dl.k8s.io/release/v1.31.6/bin/${OS}/${ARCH}/kubectl" + "https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.9.4/clusterctl-${OS}-${ARCH}") +BINARY_CHECKSUMS=("d445b44c28297bc23fd67e51cc24bb294ae7b977712be2d4d312883d0835829b" + "c46b2f5b0027e919299d1eca073ebf13a4c5c0528dd854fc71a5b93396c9fa9d" + "0c80a58f6158cd76075fcc9a5d860978720fa88860c2608bb00944f6af1e5752") +BINARY_NEW_NAMES=("kind" "." "clusterctl") + +# Update package lists +echo "Updating package lists..." sudo apt-get update -install_via_pkgmgr "${DEB12_PKGS[@]}" || exit 1 -for i in $(seq 0 $((${#DEB12_TGZS[*]}-1))); do - install_via_download_tgz "${DEB12_TGZS[$i]}" "${DEB12_TCHK[$i]}" "${DEB12_TOLD[$i]}" "${DEB12_TNEW[$i]}" || exit 2 + +# Install packages +echo "Installing required packages..." +install_via_pkgmgr "${PACKAGES[@]}" || exit 1 + +# Install tools from tarballs +echo "Installing tools from tarballs..." +for i in $(seq 0 $((${#TARBALLS[*]}-1))); do + echo "Processing tarball ${TARBALLS[$i]}..." + install_via_download_tgz "${TARBALLS[$i]}" "${TARBALL_CHECKSUMS[$i]}" "${TARBALL_EXTRACT_PATHS[$i]}" "${TARBALL_NEW_NAMES[$i]}" || exit 2 done -for i in $(seq 0 $((${#DEB12_BINS[*]}-1))); do - install_via_download_bin "${DEB12_BINS[$i]}" "${DEB12_BCHK[$i]}" "${DEB12_BNEW[$i]}" || exit 3 + +# Install binary tools +echo "Installing binary tools..." +for i in $(seq 0 $((${#BINARIES[*]}-1))); do + echo "Processing binary ${BINARIES[$i]}..." + install_via_download_bin "${BINARIES[$i]}" "${BINARY_CHECKSUMS[$i]}" "${BINARY_NEW_NAMES[$i]}" || exit 3 done +# Install envsubst +echo "Installing envsubst..." GOBIN=/tmp go install github.com/drone/envsubst/v2/cmd/envsubst@latest sudo mv /tmp/envsubst /usr/local/bin/ -test -e "~/.bash_aliases" || echo -e "alias ll='ls -lF'\nalias k=kubectl" > ~/.bash_aliases -sudo groupmod -a -U `whoami` docker +# Set up bash aliases if they don't exist +echo "Setting up bash aliases..." +if [ ! -e ~/.bash_aliases ]; then + echo -e "alias ll='ls -lF'\nalias k=kubectl" > ~/.bash_aliases + echo "Created ~/.bash_aliases file" +fi + +# Set up Docker group +echo "Adding current user to Docker group..." +# First check if docker group exists +if getent group docker > /dev/null; then + # Add user to docker group - different syntax for Ubuntu vs Debian + if [ "$OS_ID" = "ubuntu" ]; then + echo "Using Ubuntu-specific command to add user to docker group..." + sudo usermod -aG docker $(whoami) + else + echo "Using Debian-specific command to add user to docker group..." + sudo groupmod -a -U $(whoami) docker + fi + echo "User $(whoami) added to docker group" +else + echo "Docker group does not exist. Creating docker group first..." + sudo groupadd docker + echo "Adding user $(whoami) to docker group..." + sudo usermod -aG docker $(whoami) + echo "User $(whoami) added to docker group" +fi + +echo "Note: You may need to log out and back in for group changes to take effect" + +# Start Docker +echo "Enabling and starting Docker service..." sudo systemctl enable --now docker +echo "Installation complete! You may need to log out and back in for group changes to take effect." + diff --git a/01-kind-cluster.sh b/01-kind-cluster.sh index 5264a8e..5eda618 100755 --- a/01-kind-cluster.sh +++ b/01-kind-cluster.sh @@ -1,5 +1,7 @@ #!/bin/bash +# Create a local kind (Kubernetes in Docker) cluster set -e + # Detect MTU of interface with default route DEV=$(ip route show default | head -n1 | sed 's/^.*dev \([^ ]*\).*$/\1/') CLOUDMTU=$(ip link show $DEV | head -n1 | sed 's/^.*mtu \([0-9]*\) .*$/\1/') @@ -11,10 +13,15 @@ if test $DOCKERMTU -gt $CLOUDMTU; then # Just in case ... sudo sysctl net.ipv4.tcp_mtu_probing=1 fi + # Create kind cluster if test "$(kind get clusters)" != "kind"; then + echo "Creating a new kind cluster..." kind create cluster else echo "kind cluster already running" fi + +echo "Verifying cluster status..." kubectl cluster-info +echo "Kind cluster ready." \ No newline at end of file diff --git a/02-deploy-capi.sh b/02-deploy-capi.sh index 6974d13..2897d8f 100755 --- a/02-deploy-capi.sh +++ b/02-deploy-capi.sh @@ -1,15 +1,28 @@ #!/bin/bash -# Rollout ORC, CAPI, CAPO +# Rollout ORC, CAPI, CAPO (Cluster API with OpenStack provider) set -e -# Set feature flags + +echo "Setting required feature flags for Cluster API..." export CLUSTER_TOPOLOGY=true export EXP_CLUSTER_RESOURCE_SET=true export EXP_RUNTIME_SDK=true +echo "CLUSTER_TOPOLOGY=$CLUSTER_TOPOLOGY" +echo "EXP_CLUSTER_RESOURCE_SET=$EXP_CLUSTER_RESOURCE_SET" +echo "EXP_RUNTIME_SDK=$EXP_RUNTIME_SDK" + +echo "Deploying OpenStack Resource Controller (ORC)..." # We need ORC these days and clusterctl has chosen to ignore that kubectl apply -f https://github.com/k-orc/openstack-resource-controller/releases/latest/download/install.yaml + +echo "Initializing Cluster API with OpenStack infrastructure provider..." # Rollout capi and capo (assuming that orc gets deployed independently) clusterctl init --infrastructure openstack -# Wait for completion + +echo "Waiting for CAPI deployments to be ready..." kubectl -n capi-system rollout status deployment + +echo "Waiting for CAPO deployments to be ready..." kubectl -n capo-system rollout status deployment +echo "Cluster API components deployed successfully." + diff --git a/03-deploy-cso.sh b/03-deploy-cso.sh index 35605f8..5ab95d5 100755 --- a/03-deploy-cso.sh +++ b/03-deploy-cso.sh @@ -1,8 +1,11 @@ #!/bin/bash -# Deploy CSO +# Deploy Cluster Stack Operator (CSO) set -e + +echo "Creating temporary directory..." mkdir ~/tmp || true +echo "Generating CSO RBAC configuration..." cat > ~/tmp/cso-rbac.yaml < ~/tmp/clouds-$OS_CLOUD.yaml umask $OLD_UMASK # FIXME: We will provide more settings in cluster-settings.env later, hardcode it for now -#if test "$CS_CCMLB=octavia-ovn"; then OCTOVN="--set octavia_ovn=true"; else unset OCTOVN; fi -OCTOVN="--set octavia_ovn=true" +if test "$CS_CCMLB" = "octavia-ovn"; then OCTOVN="--set octavia_ovn=true"; else unset OCTOVN; fi if test -n "$OS_CACERT"; then echo "# Found CA cert file configured to be \"$OS_CACERT\"" OS_CACERT="$(ls ${OS_CACERT/\~/$HOME})" @@ -83,3 +87,5 @@ if test -n "$OS_CACERT"; then else helm upgrade -i openstack-secrets -n "$CS_NAMESPACE" --create-namespace https://github.com/SovereignCloudStack/openstack-csp-helper/releases/latest/download/openstack-csp-helper.tgz -f ~/tmp/clouds-$OS_CLOUD.yaml $OCTOVN fi + +echo "Cloud secret created successfully." diff --git a/07-create-cluster.sh b/07-create-cluster.sh index 86c20f8..c5903b0 100755 --- a/07-create-cluster.sh +++ b/07-create-cluster.sh @@ -1,5 +1,7 @@ #!/bin/bash +# Create a Kubernetes cluster using Cluster API set -e + # We need settings if test -n "$1"; then SET="$1" @@ -8,9 +10,13 @@ else else echo "You need to pass a cluster-settings.env file as parameter"; exit 1 fi fi + # Read settings -- make sure you can trust it source "$SET" +echo "Using settings from $SET" + # Sanity checks +echo "Performing sanity checks on required variables..." if test -z "$CS_MAINVER"; then echo "Configure CS_MAINVER"; exit 2; fi if test -z "$CS_VERSION"; then echo "Configure CS_VERSION"; exit 3; fi if test -z "$CL_PATCHVER"; then echo "Configure CL_PATCHVER"; exit 4; fi @@ -19,6 +25,14 @@ if test -z "$CL_PODCIDR"; then echo "Configure CL_PODCIDR"; exit 6; fi if test -z "$CL_SVCCIDR"; then echo "Configure CL_SVCCIDR"; exit 7; fi if test -z "$CL_CTRLNODES"; then echo "Configure CL_CTRLNODES"; exit 8; fi if test -z "$CL_WRKRNODES"; then echo "Configure CL_WRKRNODES"; exit 9; fi + +echo "Creating cluster manifest for $CL_NAME..." +echo " Kubernetes Version: v$CL_PATCHVER" +echo " Control Plane Nodes: $CL_CTRLNODES" +echo " Worker Nodes: $CL_WRKRNODES" +echo " Pod CIDR: $CL_PODCIDR" +echo " Service CIDR: $CL_SVCCIDR" + # Create Cluster yaml # TODO: There are a number of variables that allow us to set things like # flavors, disk sizes, loadbalancer types, etc. @@ -43,15 +57,19 @@ spec: topology: class: openstack-scs-${CS_MAINVER/./-}-$CS_VERSION controlPlane: - replicas: $CL_CTRLNODES + replicas: $CL_CONTROLNODES version: v$CL_PATCHVER workers: machineDeployments: - - class: default-worker + - class: $CL_WORKERCLASS name: md-0 - replicas: $CL_WRKRNODES + replicas: $CL_WORKERNODES variables: - name: apiserver_loadbalancer - value: "octavia-ovn" + value: "$CL_LBTYPE" EOF + +echo "Applying cluster manifest..." kubectl apply -f ~/tmp/cluster-$CL_NAME.yaml + +echo "Cluster $CL_NAME creation initiated. Run 08-wait-cluster.sh to monitor progress." diff --git a/08-wait-cluster.sh b/08-wait-cluster.sh index 6301435..15ed2a7 100755 --- a/08-wait-cluster.sh +++ b/08-wait-cluster.sh @@ -1,26 +1,68 @@ #!/bin/bash -# Wait for cluster +# Check cluster status and save kubeconfig set -e + # We need settings if test -n "$1"; then SET="$1" + echo "Using settings from $1" else - if test -e cluster-settings.env; then SET=cluster-settings.env; - else echo "You need to pass a cluster-settings.env file as parameter"; exit 1 + if test -e cluster-settings.env; then + SET=cluster-settings.env + echo "Using settings from cluster-settings.env" + else + echo "You need to pass a cluster-settings.env file as parameter" + exit 1 fi fi + # Read settings -- make sure you can trust it source "$SET" + +# Display cluster state +echo "Checking state of cluster $CL_NAME in namespace $CS_NAMESPACE..." kubectl get cluster -A -#set -x -kubectl wait --timeout=14m --for=condition=certificatesavailable -n "$CS_NAMESPACE" kubeadmcontrolplanes -l cluster.x-k8s.io/cluster-name=$CL_NAME -kubectl get -n "$CS_NAMESPACE" cluster $CL_NAME + +# Show cluster description clusterctl describe cluster -n "$CS_NAMESPACE" $CL_NAME --grouping=false -KCFG=~/.kube/$CS_NAMESPACE.$CL_NAME -OLDUMASK=$(umask) -umask 0077 -clusterctl get kubeconfig -n "$CS_NAMESPACE" $CL_NAME > $KCFG -umask $OLDUMASK -KUBECONFIG=$KCFG kubectl get nodes -o wide -KUBECONFIG=$KCFG kubectl get pods -A -echo "# Hint: Use KUBECONFIG=$KCFG kubectl ... to access you workload cluster $CS_NAMESPACE/$CL_NAME" +echo + +# Get cluster status if available +#kubectl wait --timeout=14m --for=condition=certificatesavailable -n "$CS_NAMESPACE" kubeadmcontrolplanes -l cluster.x-k8s.io/cluster-name=$CL_NAME +if kubectl get cluster -n "$CS_NAMESPACE" $CL_NAME &>/dev/null; then + CLUSTER_STATUS=$(kubectl get cluster -n "$CS_NAMESPACE" $CL_NAME -o jsonpath='{.status.phase}') + echo "Cluster status: $CLUSTER_STATUS" + + # If cluster is ready, save kubeconfig + if [ "$CLUSTER_STATUS" = "Provisioned" ]; then + echo "Cluster is ready!" + + # Get kubeconfig + echo "Creating ~/.kube directory if needed..." + mkdir -p ~/.kube + KCFG=~/.kube/$CS_NAMESPACE.$CL_NAME + + echo "Saving kubeconfig to $KCFG + OLDUMASK=$(umask) + umask 0077 + clusterctl get kubeconfig -n "$CS_NAMESPACE" $CL_NAME > $KCFG + umask $OLDUMASK + + echo "Kubeconfig has been saved" + echo "You can access the cluster with: export KUBECONFIG=~/.kube/$CS_NAMESPACE.$CL_NAME" + echo + + # Display cluster info + echo "Displaying cluster info:" + KUBECONFIG=$KCFG kubectl cluster-info + #KUBECONFIG=$KCFG kubectl get nodes -o wide + #KUBECONFIG=$KCFG kubectl get pods -A + echo "# Hint: Use KUBECONFIG=$KCFG kubectl ... to access you workload cluster $CS_NAMESPACE/$CL_NAME" + else + echo "Cluster is not yet ready (status: $CLUSTER_STATUS)" + echo "Run this script again after some time to check status and save kubeconfig when ready" + fi +else + echo "Cluster $CL_NAME not found or not accessible in namespace $CS_NAMESPACE" + echo "Please check if the cluster has been created and you have the necessary permissions" +fi \ No newline at end of file diff --git a/99-prepare-files.sh b/99-prepare-files.sh new file mode 100755 index 0000000..8538af2 --- /dev/null +++ b/99-prepare-files.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +set -e + +# Create the openstack config directory if it doesn't exist +mkdir -p ~/.config/openstack + +# Check for cert files in the current directory +CERT_FILE=$(find . -maxdepth 1 -name "*.cert" ! -name "*.sample" | head -n 1) + +if [ -n "$CERT_FILE" ]; then + echo "Found certificate file: $CERT_FILE" + # Copy cert file to openstack config directory + cp "$CERT_FILE" ~/.config/openstack/ + CERT_FILENAME=$(basename "$CERT_FILE") + CERT_ABSOLUTE_PATH="$HOME/.config/openstack/$CERT_FILENAME" + echo "Copied $CERT_FILE to $CERT_ABSOLUTE_PATH" +fi + +# Check for clouds.yaml file +if [ -f "clouds.yaml" ]; then + echo "Found clouds.yaml file" + + # If both cert and clouds.yaml exist, update the cacert path + if [ -n "$CERT_FILE" ]; then + echo "Updating cacert path in clouds.yaml to: $CERT_ABSOLUTE_PATH" + + # Check if cacert line exists in the file + if grep -q "cacert:" clouds.yaml; then + # Update existing cacert line + sed "s|cacert:.*|cacert: \"$CERT_ABSOLUTE_PATH\"|g" clouds.yaml > clouds.yaml.tmp + else + # Add cacert line under the cloud config (assuming the cloud name is at the correct indentation) + awk -v cert="$CERT_ABSOLUTE_PATH" ' + /^ [a-zA-Z0-9_-]+:/ { cloud_found = 1 } + cloud_found && /^ auth:/ { + print + print " cacert: \"" cert "\"" + cloud_found = 0 + next + } + { print } + ' clouds.yaml > clouds.yaml.tmp + fi + mv clouds.yaml.tmp clouds.yaml + else + echo "No certificate file found, removing cacert entry if present" + # Remove cacert line if no cert file exists + sed '/cacert:/d' clouds.yaml > clouds.yaml.tmp + mv clouds.yaml.tmp clouds.yaml + fi + + # Copy clouds.yaml to openstack config directory + cp clouds.yaml ~/.config/openstack/ + echo "Copied clouds.yaml to ~/.config/openstack/" +fi + +echo "OpenStack configuration preparation complete" \ No newline at end of file diff --git a/README.md b/README.md index 4dbf029..c9da2a0 100644 --- a/README.md +++ b/README.md @@ -3,47 +3,122 @@ Some helpful snippets of code to automate the creation of Cluster-Stacks ## Goals Creating cluster stacks and cluster based on these takes a significant -number of stacks that are hard to remember correctly for people that are -not cluster-API and Cluster Class expert. +number of steps that are hard to remember correctly for people that are +not cluster-API and Cluster Class experts. -We this hide them in a number of distinct steps that are in numbered scripts. -The reason for not doing everything in one script is that you do register -cloud secrets or install capi much less often than install cluster classses -which happens much less often than creating clusters. +We hide them in a number of distinct scripts. The reason for not doing +everything in one script is that you register cloud secrets or install CAPI +much less often than install cluster classes, which happens much less often +than creating clusters. + +## Prerequisites and Setup + +### Obtaining the Cloud-in-a-Box Certificate + +If you're using SCS Cloud-in-a-Box (CiaB), you'll need to obtain the certificate. The certificates are located on the CiaB manager host at `/etc/ssl/certs/ca-certificates.crt`. + +To obtain it: + +1. SSH into your CiaB manager host (usually as user `dragon`) +2. Copy the certificate file: + ```bash + scp dragon@:/etc/ssl/certs/ca-certificates.crt ./ca-certificates.crt + ``` +3. Extract the last certificate from the file (the CiaB-specific certificate): + ```bash + # Extract the last certificate from the bundle + tac ca-certificates.crt | awk '/-----BEGIN CERTIFICATE-----/{flag=1} flag{print} /-----END CERTIFICATE-----/{exit}' | tac > ciab.cert + ``` +4. Place this `ciab.cert` file in the same directory as your scripts + +### Preparing Configuration Files + +Before running the main scripts, you may want to use the utility script to prepare your configuration: + +1. Place your `ciab.cert` file (or any other OpenStack certificate with `.cert` extension) in the script directory +2. Create your `clouds.yaml` file from the template +3. Run the preparation script: + ```bash + ./99-prepare-files.sh + ``` + +This script will: +- Create the `~/.config/openstack` directory if it doesn't exist +- Copy any `.cert` files to the config directory +- Update `clouds.yaml` to reference the certificate with its absolute path +- Copy the updated `clouds.yaml` to the config directory ## Settings There is a [cluster-settings-template.env](cluster-settings-template.env) file that contains the parameters typically adjusted by users. Please create a -copy, fill it in, and pass it to the scripts. +copy, fill it in, and pass it to the scripts. You can also use the +[cluster-settings.env.sample](cluster-settings.env.sample) file as a reference, +which includes example values for all parameters. + +### Configuration Parameters + +#### Registry and Repository Settings +- `CS_REGISTRY=registry.scs.community/kaas/cluster-stacks`: Registry for cluster stacks +- `CSO_HELM_REPO=oci://registry.scs.community/cluster-stacks/cso`: Helm chart repository for CSO + +#### Namespace and Project Settings +- `CS_NAMESPACE=clusterns`: Namespace for cluster resources +- `CLOUDS_YAML=~/.config/openstack/clouds.yaml`: Path to OpenStack credentials file +- `OS_CLOUD=${OS_CLOUD:-openstack}`: Name of the cloud in the clouds.yaml file +- `CS_CCMLB=octavia-ovn`: Cloud controller manager load balancer type (octavia-ovn or octavia-amphora) + +#### Cluster Stack Settings +- `CS_MAINVER=`: Kubernetes major.minor version (e.g., 1.32) +- `CS_VERSION=`: Cluster stack template version (e.g., v1 or v0-sha.XXXXXXX) +- `CS_CHANNEL=custom`: Update channel for ClusterStack +- `CS_AUTO_SUBSCRIBE=false`: Whether to automatically subscribe to updates + +#### Workload Cluster Settings +- `CL_PATCHVER=`: Full Kubernetes version (e.g., 1.32.3) +- `CL_NAME=`: Cluster name +- `CL_PODCIDR=172.16.0.0/18`: Pod CIDR range +- `CL_SVCCIDR=10.96.0.0/14`: Service CIDR range +- `CL_CTRLNODES=1`: Number of control plane nodes +- `CL_WRKRNODES=1`: Number of worker nodes +- `CL_WORKER_CLASS=default-worker`: Worker class used for machine deployments +- `CL_LBTYPE=octavia-ovn`: Load balancer type (depends on OpenStack environment) ## Scripts ### Once per management host -* 00-bootstrap-vm-cs.sh: Install the needed software to be able to do - cluster management on this host. (Developed for Debian 12.) +* `00-bootstrap-vm-cs.sh`: Install the needed software to be able to do + cluster management on this host. (Developed for Debian and Ubuntu.) This is only needed if you do not have the needed tools preinstalled. -* 01-kind-cluster.sh: Create kind cluster -* 02-deploy-capi.sh: Install ORC and CAPI. -* 03-deploy-cso.sh: Install the Cluster Stack Operator. +* `01-kind-cluster.sh`: Create kind cluster +* `02-deploy-capi.sh`: Install ORC and CAPI. +* `03-deploy-cso.sh`: Install the Cluster Stack Operator. * 18-delete-kind.sh: Remove kind cluster management again. ### Once per OpenStack Project in which we want to install clusters (NS) -* 04-cloud-secret.sh: Create namespace and secrets to work with the - wanted OpenStack project. +* `04-cloud-secret.sh`: Create namespace and secrets to work with the + wanted OpenStack project. Uses the `CS_CCMLB` setting to configure + the cloud controller manager load balancer type. ### Once per Kubernetes aka CS version (maj.min) -* 05-deploy-cstack.sh: Create the Cluster Stack which is a template +* `05-deploy-cstack.sh`: Create the Cluster Stack which is a template for various clusters with the same major minor version of k8s. Should trigger cluster class creation and image registration. -* 06-wait-clusterclass.sh: Wait for the cluster class +* `06-wait-clusterclass.sh`: Wait for the cluster class to be ready ### Once per cluster -* 07-create-cluster.sh: Create a workload cluster as per all the settings +* `07-create-cluster.sh`: Create a workload cluster as per all the settings that are passed. -* 08-wait-cluster.sh: Wait for the workload cluster +* `08-wait-cluster.sh`: Check cluster status and save kubeconfig if ready. + This script no longer waits in a loop but provides immediate status + feedback. If the cluster is ready, it saves the kubeconfig to + `~/.kube/.` (without the .yaml extension). + +* `16-cleanup-cluster.sh`: Remove loadbalancers and persistent volumes from cluster. (Not yet implemented.) +* `17-delete-cluster.sh`: Remove cluster when no longer needed. -* 16-cleanup-cluster.sh: Remove loadbalancers and persistent volumes from cluster. -* 17-delete-cluster.sh: Remove cluster again. +### Utility scripts +* `99-prepare-files.sh`: Prepare configuration files by copying certificates + and updating paths in clouds.yaml ### CSI Cinder fixup The `cloud.conf` generated by the helm openstack-csp-helper in step 04 diff --git a/ciab.cert.sample b/ciab.cert.sample new file mode 100644 index 0000000..e69de29 diff --git a/clouds.yaml.sample b/clouds.yaml.sample new file mode 100644 index 0000000..b169bd6 --- /dev/null +++ b/clouds.yaml.sample @@ -0,0 +1,15 @@ +clouds: + ciab-test: + region_name: "RegionOne" + interface: "public" + identity_api_version: 3 + #verify: false # Skip TLS verification, insecure, use only if you can't get the ca-bundle (next line) for self-signed certs + cacert: "/path/to/ca-bundle.crt" + auth: + auth_url: "https://api.in-a-box.cloud:5000/v3" + project_name: "test" + project_domain_name: "test" + user_domain_name: "test" + # These are often split off into a secure.yaml + username: "test" + password: "test" diff --git a/cluster-settings-template.env b/cluster-settings-template.env index 1ae5442..560bc6c 100644 --- a/cluster-settings-template.env +++ b/cluster-settings-template.env @@ -11,11 +11,25 @@ CS_NAMESPACE=clusterns CLOUDS_YAML=~/.config/openstack/clouds.yaml # Name of the cloud in there (default: openstack, any name works now) OS_CLOUD=${OS_CLOUD:-openstack} +# Cloud controller manager load balancer type (octavia-ovn or octavia-amphora) +CS_CCMLB=octavia-ovn + +### Registry and repository settings +# Registry for cluster stacks +CS_REGISTRY=registry.scs.community/kaas/cluster-stacks +# Helm chart repository for CSO +CSO_HELM_REPO=oci://registry.scs.community/cluster-stacks/cso + ### Per cluster stack settings # Kubernetes Maj.Min, e.g. 1.32 (without leading v), can be left empty (see last line) CS_MAINVER= # CS Template version that matches, e.g. v1 or v0-sha.XXXXXXX, see table CS_VERSION= +# Update channel for ClusterStack +CS_CHANNEL=custom +# Whether to automatically subscribe to updates +CS_AUTO_SUBSCRIBE=false + ### Now the per workload cluster settings # Full K8s Version Maj.Min.Patch, without leading 'v', e.g. 1.32.3 (this is per cluster) CL_PATCHVER= @@ -26,10 +40,14 @@ CL_PODCIDR=172.16.0.0/18 # Service CIDR (e.g. 10.96.0.0/12) CL_SVCCIDR=10.96.0.0/14 # Number of (initial) control plane nodes -CL_CTRLNODES=1 +CL_CONTROLNODES=1 # Number of (initial) worker nodes -CL_WRKRNODES=1 +CL_WORKERNODES=1 +# Worker class used for machine deployments +CL_WORKERCLASS=default-worker +# Load balancer type (depends on OpenStack environment) +CL_LBTYPE=octavia-ovn + ### Autofill magic, don't touch CS_NAMESPACE=${CS_NAMESPACE:-$OS_CLOUD} CS_MAINVER=${CS_MAINVER:-${CL_PATCHVER%.*}} - diff --git a/cluster-settings.env.sample b/cluster-settings.env.sample new file mode 100644 index 0000000..c8cb6f1 --- /dev/null +++ b/cluster-settings.env.sample @@ -0,0 +1,33 @@ +# Cluster settings +# This is a minimal sample file, please see cluster-settings-template.env +# for a commented version with all settings. + +### Per namespace: secrets +#CS_NAMESPACE=ciab +CLOUDS_YAML=~/.config/openstack/clouds.yaml +OS_CLOUD=ciab-test +CS_CCMLB=octavia-ovn + +### Registry and repository settings +CS_REGISTRY=registry.scs.community/kaas/cluster-stacks +CSO_HELM_REPO=oci://registry.scs.community/cluster-stacks/cso + +### Per cluster stack settings +#CS_MAINVER=1.30 # autofilled +CS_VERSION=v3 +CS_CHANNEL=custom +CS_AUTO_SUBSCRIBE=false + +### Per workload cluster settings +CL_PATCHVER=1.30.11 +CL_NAME=my-cluster +CL_PODCIDR=172.16.0.0/18 +CL_SVCCIDR=10.96.0.0/14 +CL_CONTROLNODES=1 +CL_WORKERNODES=1 +CL_WORKERCLASS=default-worker +CL_LBTYPE=octavia-ovn + +### Autofill magic, don't touch +CS_NAMESPACE=${CS_NAMESPACE:-$OS_CLOUD} +CS_MAINVER=${CS_MAINVER:-${CL_PATCHVER%.*}}