Retire direct MetalLB deployment

This commit is contained in:
2026-10-18 14:32:06 +02:00
parent 092de68915
commit 7400a9a66a
7 changed files with 8 additions and 369 deletions
-103
View File
@@ -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
+8 -46
View File
@@ -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.
-74
View File
@@ -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
-66
View File
@@ -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'
-34
View File
@@ -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"
-14
View File
@@ -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
-32
View File
@@ -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."