From 7400a9a66a98cf603d8de0967573560434e25d6a Mon Sep 17 00:00:00 2001 From: 0xFEEDC0DE64 Date: Sun, 18 Oct 2026 14:32:06 +0200 Subject: [PATCH] Retire direct MetalLB deployment --- .gitea/workflows/deploy.yml | 103 ------------------------------------ README.md | 54 +++---------------- ci-deployer.yaml | 74 -------------------------- create-ci-kubeconfig.sh | 66 ----------------------- install.sh | 34 ------------ metallb.yaml | 14 ----- test.sh | 32 ----------- 7 files changed, 8 insertions(+), 369 deletions(-) delete mode 100644 .gitea/workflows/deploy.yml delete mode 100644 ci-deployer.yaml delete mode 100755 create-ci-kubeconfig.sh delete mode 100755 install.sh delete mode 100644 metallb.yaml delete mode 100755 test.sh diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml deleted file mode 100644 index 659ef31..0000000 --- a/.gitea/workflows/deploy.yml +++ /dev/null @@ -1,103 +0,0 @@ -name: Validate and deploy MetalLB configuration - -on: - push: - pull_request: - -env: - KUBECONFORM_VERSION: v0.7.0 - KUBECTL_VERSION: v1.36.3 - METALLB_VERSION: v0.16.0 - KUBERNETES_API: https://host.containers.internal:6443 - KUBERNETES_TLS_SERVER_NAME: 192.168.0.2 - NAMESPACE: metallb-system - -jobs: - validate: - name: Validate manifests - runs-on: ubuntu-latest - steps: - - name: Check out source - uses: actions/checkout@v4 - - - name: Validate scripts and local resources - run: bash test.sh - - - name: Verify the pinned upstream manifest - run: | - curl --fail --silent --show-error --location \ - --output "${RUNNER_TEMP}/metallb-native.yaml" \ - "https://raw.githubusercontent.com/metallb/metallb/${METALLB_VERSION}/config/manifests/metallb-native.yaml" - docker run --rm --interactive \ - "ghcr.io/yannh/kubeconform:${KUBECONFORM_VERSION}" \ - -strict -ignore-missing-schemas -summary < "${RUNNER_TEMP}/metallb-native.yaml" - - deploy: - name: Deploy site configuration - if: gitea.ref == 'refs/heads/main' - needs: validate - runs-on: ubuntu-latest - steps: - - name: Check out source - uses: actions/checkout@v4 - - - name: Install kubectl - run: | - curl --fail --silent --show-error --location \ - --output "${RUNNER_TEMP}/kubectl" \ - "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" - chmod 0700 "${RUNNER_TEMP}/kubectl" - - - name: Configure Kubernetes access - env: - KUBE_CONFIG_BASE64: ${{ secrets.KUBE_CONFIG_BASE64 }} - run: | - test -n "${KUBE_CONFIG_BASE64}" - printf '%s' "${KUBE_CONFIG_BASE64}" | base64 --decode > "${RUNNER_TEMP}/kubeconfig" - chmod 0600 "${RUNNER_TEMP}/kubeconfig" - export KUBECONFIG="${RUNNER_TEMP}/kubeconfig" - "${RUNNER_TEMP}/kubectl" config set-cluster cluster \ - --server="${KUBERNETES_API}" \ - --tls-server-name="${KUBERNETES_TLS_SERVER_NAME}" - - - name: Apply and verify - run: | - export KUBECONFIG="${RUNNER_TEMP}/kubeconfig" - "${RUNNER_TEMP}/kubectl" apply \ - --dry-run=server \ - --validate=false \ - --filename metallb.yaml - "${RUNNER_TEMP}/kubectl" apply \ - --validate=false \ - --filename metallb.yaml - - for attempt in {1..60}; do - IFS='|' read -r c_generation c_observed c_desired c_updated c_ready c_available c_unavailable <<< "$( - "${RUNNER_TEMP}/kubectl" --namespace "${NAMESPACE}" get deployment/controller \ - --output=jsonpath='{.metadata.generation}|{.status.observedGeneration}|{.spec.replicas}|{.status.updatedReplicas}|{.status.readyReplicas}|{.status.availableReplicas}|{.status.unavailableReplicas}' - )" - IFS='|' read -r s_generation s_observed s_desired s_updated s_ready s_available s_unavailable <<< "$( - "${RUNNER_TEMP}/kubectl" --namespace "${NAMESPACE}" get daemonset/speaker \ - --output=jsonpath='{.metadata.generation}|{.status.observedGeneration}|{.status.desiredNumberScheduled}|{.status.updatedNumberScheduled}|{.status.numberReady}|{.status.numberAvailable}|{.status.numberUnavailable}' - )" - - echo "Check ${attempt}/60: controller ${c_ready}/${c_desired} ready; speaker ${s_ready}/${s_desired} ready" - if [[ "${c_observed}" == "${c_generation}" \ - && "${c_updated}" == "${c_desired}" \ - && "${c_ready}" == "${c_desired}" \ - && "${c_available}" == "${c_desired}" \ - && ( -z "${c_unavailable}" || "${c_unavailable}" == "0" ) \ - && "${s_observed}" == "${s_generation}" \ - && "${s_updated}" == "${s_desired}" \ - && "${s_ready}" == "${s_desired}" \ - && "${s_available}" == "${s_desired}" \ - && ( -z "${s_unavailable}" || "${s_unavailable}" == "0" ) ]]; then - echo "MetalLB configuration is active and all components are ready" - exit 0 - fi - - sleep 5 - done - - echo "MetalLB did not become ready within five minutes" >&2 - exit 1 diff --git a/README.md b/README.md index ffa4ce9..fce6b38 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,10 @@ -# MetalLB configuration +# MetalLB configuration (retired) -This folder installs MetalLB `v0.16.0` from its pinned upstream native manifest -and configures the L2 address pool. MetalLB, not Cilium, announces -load-balancer addresses from `192.168.0.10-192.168.0.49`. +MetalLB is now managed by Argo CD from the public +[homelab-gitops application](https://code.brunner.ninja/feedc0de/homelab-gitops/src/branch/main/apps/metallb). +That repository owns the pinned upstream installation, L2 address pool, and +Argo application definition. -```sh -./install.sh -``` - -The files contain no credentials. The private LAN address range is -infrastructure information, so decide whether that disclosure is acceptable -before publishing the repository. - -The installer downloads the manifest for its pinned upstream version, applies -it, waits for the controller and all speakers, and -then applies `metallb.yaml`. - -## Gitea Actions CI/CD - -Every push and pull request validates the scripts, the site-specific resources, -and the downloaded upstream manifest. A successful -push to `main` applies `metallb.yaml` and verifies that both the controller and -all speaker pods are ready. - -The upstream MetalLB installation remains an administrator bootstrap. CI owns -only the existing `IPAddressPool/default` and `L2Advertisement/l2`; it cannot -change MetalLB's workloads, CRDs, RBAC, webhook certificate, or Secrets. After -changing `METALLB_VERSION`, run `./install.sh` once as an administrator before -merging the matching CI constants. - -Create the restricted deployment identity once: - -```sh -./create-ci-kubeconfig.sh -``` - -For remote cluster access: - -```sh -KUBECTL_SSH_HOST=arschrock ./create-ci-kubeconfig.sh -``` - -Store only the final output line as the Gitea Actions repository secret -`KUBE_CONFIG_BASE64`. Revoke it by deleting -`metallb-system/metallb-config-deployer-token` and the repository secret. - -Run `./test.sh` for local shell and Kubernetes schema validation. +This repository no longer contains deployment automation or cluster +credentials and may be archived. Its Git history remains available as the +record of the former direct-deployment approach. diff --git a/ci-deployer.yaml b/ci-deployer.yaml deleted file mode 100644 index dcb11f5..0000000 --- a/ci-deployer.yaml +++ /dev/null @@ -1,74 +0,0 @@ -# Bootstrap once with an administrator identity. CI owns only the existing -# site-specific MetalLB configuration, not the upstream installation or RBAC. -apiVersion: v1 -kind: ServiceAccount -metadata: - name: metallb-config-deployer - namespace: metallb-system -automountServiceAccountToken: false ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: metallb-config-deployer - namespace: metallb-system -rules: - - apiGroups: - - metallb.io - resources: - - ipaddresspools - resourceNames: - - default - verbs: - - get - - patch - - update - - apiGroups: - - metallb.io - resources: - - l2advertisements - resourceNames: - - l2 - verbs: - - get - - patch - - update - - apiGroups: - - apps - resources: - - deployments - resourceNames: - - controller - verbs: - - get - - apiGroups: - - apps - resources: - - daemonsets - resourceNames: - - speaker - verbs: - - get ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: metallb-config-deployer - namespace: metallb-system -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: metallb-config-deployer -subjects: - - kind: ServiceAccount - name: metallb-config-deployer - namespace: metallb-system ---- -apiVersion: v1 -kind: Secret -metadata: - name: metallb-config-deployer-token - namespace: metallb-system - annotations: - kubernetes.io/service-account.name: metallb-config-deployer -type: kubernetes.io/service-account-token diff --git a/create-ci-kubeconfig.sh b/create-ci-kubeconfig.sh deleted file mode 100755 index fdd9144..0000000 --- a/create-ci-kubeconfig.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -project_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) -namespace=${NAMESPACE:-metallb-system} -service_account=${SERVICE_ACCOUNT:-metallb-config-deployer} -secret=${TOKEN_SECRET:-metallb-config-deployer-token} -ssh_host=${KUBECTL_SSH_HOST:-} -ssh_config=${SSH_CONFIG:-} - -cluster_kubectl() { - if [[ -n "${ssh_host}" ]]; then - if [[ -n "${ssh_config}" ]]; then - ssh -F "${ssh_config}" "${ssh_host}" kubectl "$@" - else - ssh "${ssh_host}" kubectl "$@" - fi - else - kubectl "$@" - fi -} - -if [[ -n "${ssh_host}" ]]; then - cluster_kubectl apply --filename=- < "${project_dir}/ci-deployer.yaml" >&2 -else - cluster_kubectl apply --filename "${project_dir}/ci-deployer.yaml" >&2 -fi - -for attempt in {1..30}; do - if cluster_kubectl --namespace "${namespace}" get secret "${secret}" \ - --output=jsonpath='{.data.token}' 2>/dev/null | grep -q .; then - break - fi - if [[ "${attempt}" == 30 ]]; then - echo "Timed out waiting for the service-account token" >&2 - exit 1 - fi - sleep 1 -done - -workdir=$(mktemp --directory) -trap 'rm -rf "${workdir}"' EXIT - -server=$(cluster_kubectl config view --minify --output=jsonpath='{.clusters[0].cluster.server}') -cluster_kubectl --namespace "${namespace}" get secret "${secret}" \ - --output=jsonpath='{.data.ca\.crt}' | base64 --decode > "${workdir}/ca.crt" -token=$(cluster_kubectl --namespace "${namespace}" get secret "${secret}" \ - --output=jsonpath='{.data.token}' | base64 --decode) - -export KUBECONFIG="${workdir}/config" -kubectl config set-cluster cluster \ - --server="${server}" \ - --certificate-authority="${workdir}/ca.crt" \ - --embed-certs=true >/dev/null -kubectl config set-credentials "${service_account}" --token="${token}" >/dev/null -kubectl config set-context metallb \ - --cluster=cluster \ - --user="${service_account}" \ - --namespace="${namespace}" >/dev/null -kubectl config use-context metallb >/dev/null - -echo "Store the following single line as the Gitea Actions secret KUBE_CONFIG_BASE64." >&2 -echo "Treat it as a password; do not commit it." >&2 -base64 --wrap=0 "${KUBECONFIG}" -printf '\n' diff --git a/install.sh b/install.sh deleted file mode 100755 index e59e238..0000000 --- a/install.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/sh - -set -eu - -METALLB_VERSION=v0.16.0 -METALLB_MANIFEST_URL="https://raw.githubusercontent.com/metallb/metallb/$METALLB_VERSION/config/manifests/metallb-native.yaml" -NAMESPACE=metallb-system -SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) - -for command in curl kubectl; do - if ! command -v "$command" >/dev/null 2>&1; then - echo "Required command not found: $command" >&2 - exit 1 - fi -done - -UPSTREAM_MANIFEST=$(mktemp) -trap 'rm -f "$UPSTREAM_MANIFEST"' EXIT HUP INT TERM - -curl --fail --silent --show-error --location \ - --output "$UPSTREAM_MANIFEST" "$METALLB_MANIFEST_URL" - -kubectl apply --dry-run=server --validate=false \ - --filename "$UPSTREAM_MANIFEST" -kubectl apply --validate=false --filename "$UPSTREAM_MANIFEST" - -kubectl wait --for=condition=Available deployment/controller \ - --namespace "$NAMESPACE" --timeout=5m -kubectl rollout status daemonset/speaker \ - --namespace "$NAMESPACE" --timeout=5m - -kubectl apply --dry-run=server --validate=false \ - --filename "$SCRIPT_DIR/metallb.yaml" -kubectl apply --validate=false --filename "$SCRIPT_DIR/metallb.yaml" diff --git a/metallb.yaml b/metallb.yaml deleted file mode 100644 index de5bdbe..0000000 --- a/metallb.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: metallb.io/v1beta1 -kind: IPAddressPool -metadata: - name: default - namespace: metallb-system -spec: - addresses: - - 192.168.0.10-192.168.0.49 ---- -apiVersion: metallb.io/v1beta1 -kind: L2Advertisement -metadata: - name: l2 - namespace: metallb-system diff --git a/test.sh b/test.sh deleted file mode 100755 index 0cd3077..0000000 --- a/test.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -project_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) -kubeconform_image=ghcr.io/yannh/kubeconform:v0.7.0 - -sh -n "${project_dir}/install.sh" -bash -n "${project_dir}/create-ci-kubeconfig.sh" -bash -n "${project_dir}/test.sh" - -grep -Fq 'METALLB_VERSION=v0.16.0' "${project_dir}/install.sh" - -validate_file() { - local manifest=$1 - shift - - if command -v kubeconform >/dev/null 2>&1; then - kubeconform -strict -summary "$@" "${manifest}" - elif command -v docker >/dev/null 2>&1; then - docker run --rm --interactive "${kubeconform_image}" \ - -strict -summary "$@" < "${manifest}" - else - echo "Required command not found: install kubeconform or Docker" >&2 - exit 1 - fi -} - -validate_file "${project_dir}/metallb.yaml" -ignore-missing-schemas -validate_file "${project_dir}/ci-deployer.yaml" - -echo "MetalLB scripts and site-specific resources are valid."