Skip to content

AKS SSH Quickstart

AKS SSH Quickstart #8

# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
name: AKS SSH Quickstart
"on":
push:
branches:
- ismille/**
workflow_dispatch:
permissions:
contents: read
id-token: write
concurrency:
group: aks-ssh-quickstart-${{ github.ref }}
cancel-in-progress: false
jobs:
azure-setup:
runs-on: ubuntu-latest
environment: azure-ci
timeout-minutes: 120
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
cache-dependency-path: go.sum
- name: Azure login
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Show Azure context
run: |
set -euo pipefail
az account show --output jsonc
- name: Build kubectl-unbounded
run: |
set -euo pipefail
mkdir -p bin
go build -o bin/kubectl-unbounded ./cmd/kubectl-unbounded
echo "$PWD/bin" >> "$GITHUB_PATH"
- name: Generate ephemeral SSH keypair
run: |
set -euo pipefail
mkdir -p .tmp/aks-ssh
ssh-keygen -q -t ed25519 -N '' -f .tmp/aks-ssh/id_ed25519
chmod 600 .tmp/aks-ssh/id_ed25519
- name: Derive run-scoped names
run: |
set -euo pipefail
mkdir -p workflow-artifacts/metadata
suffix="${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
cat > workflow-artifacts/metadata/aks-metadata.env <<EOF
AKS_NAME=ssh-quickstart-${suffix}
AKS_RG=ssh-quickstart-${suffix}
SITE_NAME=remote
REMOTE_NODE_CIDR=10.10.100.0/24
REMOTE_POD_CIDR=10.245.0.0/16
EOF
- name: Load metadata into environment
run: |
set -euo pipefail
cat workflow-artifacts/metadata/aks-metadata.env >> "$GITHUB_ENV"
- name: Upload metadata artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: aks-quickstart-metadata
path: workflow-artifacts/metadata/aks-metadata.env
retention-days: 1
- name: Validate remote CIDRs against AKS defaults
run: |
set -euo pipefail
aks_service_cidr="10.0.0.0/16"
aks_pod_cidr="10.244.0.0/16"
if [[ "$REMOTE_NODE_CIDR" == "$aks_service_cidr" || "$REMOTE_NODE_CIDR" == "$aks_pod_cidr" ]]; then
printf 'REMOTE_NODE_CIDR (%s) collides with AKS default CIDR (%s or %s)\n' "$REMOTE_NODE_CIDR" "$aks_service_cidr" "$aks_pod_cidr" >&2
exit 1
fi
if [[ "$REMOTE_POD_CIDR" == "$aks_service_cidr" || "$REMOTE_POD_CIDR" == "$aks_pod_cidr" ]]; then
printf 'REMOTE_POD_CIDR (%s) collides with AKS default CIDR (%s or %s)\n' "$REMOTE_POD_CIDR" "$aks_service_cidr" "$aks_pod_cidr" >&2
exit 1
fi
- name: Create Azure resource group
run: |
set -euo pipefail
az group create \
--name "$AKS_RG" \
--location eastus2 \
--output jsonc
- name: Create AKS cluster via quickstart
env:
WORKFLOW_KUBECONFIG: ${{ github.workspace }}/.tmp/aks-quickstart.kubeconfig
KUBECONFIG: ${{ github.workspace }}/.tmp/aks-quickstart.kubeconfig
run: |
set -euo pipefail
mkdir -p .tmp
rm -f "$WORKFLOW_KUBECONFIG"
bash hack/scripts/aks-quickstart.sh create \
--name "$AKS_NAME" \
--resource-group "$AKS_RG" \
--location eastus2 \
--site-name "$SITE_NAME" \
--remote-node-cidr "$REMOTE_NODE_CIDR" \
--remote-pod-cidr "$REMOTE_POD_CIDR" \
--ssh-key .tmp/aks-ssh/id_ed25519.pub
- name: Refresh workflow kubeconfig
env:
WORKFLOW_KUBECONFIG: ${{ github.workspace }}/.tmp/aks-quickstart.kubeconfig
run: |
set -euo pipefail
az aks get-credentials \
--resource-group "$AKS_RG" \
--name "$AKS_NAME" \
--file "$WORKFLOW_KUBECONFIG" \
--overwrite-existing \
--output none
- name: Stage workflow artifacts
run: |
set -euo pipefail
mkdir -p workflow-artifacts/kubeconfig workflow-artifacts/ssh
cp .tmp/aks-quickstart.kubeconfig workflow-artifacts/kubeconfig/aks-quickstart.kubeconfig
cp .tmp/aks-ssh/id_ed25519 workflow-artifacts/ssh/id_ed25519
cp .tmp/aks-ssh/id_ed25519.pub workflow-artifacts/ssh/id_ed25519.pub
- name: Upload kubeconfig artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: aks-kubeconfig
path: workflow-artifacts/kubeconfig/aks-quickstart.kubeconfig
retention-days: 1
- name: Upload SSH material artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: aks-ssh-material
path: workflow-artifacts/ssh/
retention-days: 1
- name: Collect Azure setup diagnostics
if: failure()
run: |
set -euo pipefail
printf 'Azure account context:\n'
az account show --output jsonc || true
if [[ -z "${AKS_RG:-}" ]]; then
printf 'AKS_RG is not set; skipping resource-group diagnostics\n'
exit 0
fi
printf 'Azure resource group state for %s:\n' "$AKS_RG"
az group show --name "$AKS_RG" --output jsonc || true
if [[ "$(az group exists --name "$AKS_RG")" != "true" ]]; then
printf 'resource group %s does not exist; skipping activity-log lookup\n' "$AKS_RG"
exit 0
fi
printf 'Recent Azure activity log entries for %s:\n' "$AKS_RG"
az monitor activity-log list \
--resource-group "$AKS_RG" \
--offset 2h \
--max-events 20 \
--output table || true
proxmox-bootstrap:
needs:
- azure-setup
runs-on:
- self-hosted
- pve
timeout-minutes: 120
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
cache-dependency-path: go.sum
- name: Verify runner dependencies
run: |
set -euo pipefail
for tool in kubectl jq pvesh pvesm qm ip ssh ssh-keygen curl cksum grep; do
if ! command -v "$tool" >/dev/null 2>&1; then
printf 'missing required runner dependency: %s\n' "$tool" >&2
exit 1
fi
done
- name: Download kubeconfig artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: aks-kubeconfig
path: workflow-artifacts/kubeconfig
- name: Download SSH material artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: aks-ssh-material
path: workflow-artifacts/ssh
- name: Download metadata artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: aks-quickstart-metadata
path: workflow-artifacts/metadata
- name: Restore metadata and kubeconfig environment
run: |
set -euo pipefail
mkdir -p .tmp .tmp/aks-ssh
cp workflow-artifacts/kubeconfig/aks-quickstart.kubeconfig .tmp/aks-quickstart.kubeconfig
cp workflow-artifacts/ssh/id_ed25519 .tmp/aks-ssh/id_ed25519
cp workflow-artifacts/ssh/id_ed25519.pub .tmp/aks-ssh/id_ed25519.pub
chmod 600 .tmp/aks-ssh/id_ed25519
cat workflow-artifacts/metadata/aks-metadata.env >> "$GITHUB_ENV"
echo "WORKFLOW_KUBECONFIG=$PWD/.tmp/aks-quickstart.kubeconfig" >> "$GITHUB_ENV"
echo "KUBECONFIG=$PWD/.tmp/aks-quickstart.kubeconfig" >> "$GITHUB_ENV"
echo "SSH_PRIVATE_KEY=$PWD/.tmp/aks-ssh/id_ed25519" >> "$GITHUB_ENV"
- name: Build kubectl-unbounded
run: |
set -euo pipefail
mkdir -p bin
go build -o bin/kubectl-unbounded ./cmd/kubectl-unbounded
echo "$PWD/bin" >> "$GITHUB_PATH"
- name: Create Proxmox VMs
run: |
set -euo pipefail
hack/scripts/proxmox-ssh-quickstart-vms.sh create \
--state-file .tmp/proxmox-vms.env \
--ssh-public-key .tmp/aks-ssh/id_ed25519.pub \
--vm-prefix "$AKS_NAME"
- name: Load Proxmox VM state
run: |
set -euo pipefail
hack/scripts/proxmox-ssh-quickstart-vms.sh print-env \
--state-file .tmp/proxmox-vms.env >> "$GITHUB_ENV"
- name: Bootstrap first Proxmox VM
run: |
set -euo pipefail
kubectl unbounded machine manual-bootstrap "${SITE_NAME}-worker-1" \
--kubeconfig "$WORKFLOW_KUBECONFIG" \
--site "$SITE_NAME" \
| ssh -o StrictHostKeyChecking=no \
-o BatchMode=yes \
-o UserKnownHostsFile=/dev/null \
-o ConnectTimeout=10 \
-i "$SSH_PRIVATE_KEY" \
"$SSH_USERNAME@$VM1_IP" sudo -n bash
- name: Bootstrap second Proxmox VM
run: |
set -euo pipefail
kubectl unbounded machine manual-bootstrap "${SITE_NAME}-worker-2" \
--kubeconfig "$WORKFLOW_KUBECONFIG" \
--site "$SITE_NAME" \
| ssh -o StrictHostKeyChecking=no \
-o BatchMode=yes \
-o UserKnownHostsFile=/dev/null \
-o ConnectTimeout=10 \
-i "$SSH_PRIVATE_KEY" \
"$SSH_USERNAME@$VM2_IP" sudo -n bash
- name: Wait for both nodes to become Ready
run: |
set -euo pipefail
for machine in "${SITE_NAME}-worker-1" "${SITE_NAME}-worker-2"; do
node=""
for attempt in $(seq 1 60); do
node=$(KUBECONFIG="$WORKFLOW_KUBECONFIG" kubectl get nodes -l "unbounded-kube.io/machine=$machine" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)
if [[ -n "$node" ]]; then
break
fi
if [[ "$attempt" -eq 60 ]]; then
printf 'node for machine %s did not appear after %s attempts\n' "$machine" "$attempt" >&2
exit 1
fi
sleep 10
done
KUBECONFIG="$WORKFLOW_KUBECONFIG" kubectl wait --for=condition=Ready "node/$node" --timeout=20m
done
- name: Cleanup Proxmox resources
if: always()
run: |
set -euo pipefail
proxmox_state_file=.tmp/proxmox-vms.env
cleanup_failed=0
mkdir -p logs
cp "$proxmox_state_file" logs/proxmox-vms.env 2>/dev/null || true
destroy_proxmox_vms() {
if [[ ! -f "$proxmox_state_file" ]]; then
return 0
fi
if ! hack/scripts/proxmox-ssh-quickstart-vms.sh destroy --state-file "$proxmox_state_file"; then
printf 'failed to destroy Proxmox VMs recorded in %s\n' "$proxmox_state_file" >&2
cleanup_failed=1
return 0
fi
rm -f "$proxmox_state_file"
}
destroy_proxmox_vms
if [[ "$cleanup_failed" -ne 0 ]]; then
exit 1
fi
- name: Collect cluster diagnostics
if: failure()
run: |
set -euo pipefail
mkdir -p logs
cp .tmp/proxmox-vms.env logs/proxmox-vms.env 2>/dev/null || true
KUBECONFIG="$WORKFLOW_KUBECONFIG" kubectl get nodes -o wide > logs/nodes.txt 2>&1 || true
KUBECONFIG="$WORKFLOW_KUBECONFIG" kubectl get machines -o wide > logs/machines.txt 2>&1 || true
KUBECONFIG="$WORKFLOW_KUBECONFIG" kubectl get machines -o yaml > logs/machines.yaml 2>&1 || true
KUBECONFIG="$WORKFLOW_KUBECONFIG" kubectl describe machines > logs/machines-describe.txt 2>&1 || true
KUBECONFIG="$WORKFLOW_KUBECONFIG" kubectl -n unbounded-kube logs deploy/machina-controller > logs/machina.log 2>&1 || true
- name: Upload diagnostics
if: failure()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: aks-ssh-quickstart-logs
path: logs/
if-no-files-found: ignore
retention-days: 14
azure-cleanup:
needs:
- azure-setup
- proxmox-bootstrap
if: always()
runs-on: ubuntu-latest
environment: azure-ci
timeout-minutes: 30
steps:
- name: Download metadata artifact
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: aks-quickstart-metadata
path: workflow-artifacts/metadata
- name: Restore cleanup metadata
run: |
set -euo pipefail
if [[ -f workflow-artifacts/metadata/aks-metadata.env ]]; then
cat workflow-artifacts/metadata/aks-metadata.env >> "$GITHUB_ENV"
fi
- name: Azure login
if: ${{ env.AKS_RG != '' }}
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Delete Azure resource group
if: ${{ env.AKS_RG != '' }}
run: |
set -euo pipefail
if [[ "$(az group exists --name "$AKS_RG")" != "true" ]]; then
printf 'resource group %s does not exist; skipping delete\n' "$AKS_RG"
exit 0
fi
az group delete --yes --no-wait --name "$AKS_RG"