provisioning-catalog/components/cilium/taskserv/install-cilium.sh

323 lines
11 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
set -euo pipefail
[ -r "./env-cilium" ] && . ./env-cilium
# kubeconfig — env-cilium override → kubeadm → k0s → generate from k0s
K0S_DATA_DIR="${K0S_DATA_DIR:-/var/lib/k0s}"
if [ -z "${KUBECONFIG:-}" ] || [ ! -f "${KUBECONFIG}" ]; then
if [ -f "/etc/kubernetes/admin.conf" ]; then
KUBECONFIG="/etc/kubernetes/admin.conf"
elif [ -f "${K0S_DATA_DIR}/pki/admin.conf" ]; then
KUBECONFIG="${K0S_DATA_DIR}/pki/admin.conf"
else
KUBECONFIG="/tmp/k0s-admin.conf"
k0s kubeconfig admin > "${KUBECONFIG}"
fi
fi
export KUBECONFIG
# Detect kubectl: native binary or k0s wrapper
if command -v kubectl &>/dev/null; then
KUBECTL="kubectl"
elif command -v k0s &>/dev/null; then
KUBECTL="k0s kubectl"
else
echo "ERROR: neither kubectl nor k0s found in PATH" >&2
exit 1
fi
OS="$(uname | tr '[:upper:]' '[:lower:]')"
ARCH="$(uname -m | sed -e 's/x86_64/amd64/' -e 's/aarch64/arm64/' -e 's/armv7l/arm/')"
CILIUM_VERSION="${CILIUM_VERSION:-1.19.3}"
CILIUM_CLUSTER="${CILIUM_CLUSTER_NAME:-}"
CILIUM_CLUSTER_ID="${CILIUM_CLUSTER_ID:-0}"
CILIUM_INGRESS="${CILIUM_ENABLE_INGRESS_CONTROLLER:-false}"
CILIUM_INGRESS_LB="${CILIUM_INGRESS_LB_MODE:-shared}"
CILIUM_GATEWAY_API="${CILIUM_ENABLE_GATEWAY_API:-false}"
CILIUM_GATEWAY_API_VERSION="${CILIUM_GATEWAY_API_VERSION:-v1.2.1}"
CILIUM_KPR="${CILIUM_KUBE_PROXY_REPLACEMENT:-false}"
CILIUM_SOCKET_LB="${CILIUM_SOCKET_LB:-true}"
CILIUM_ENVOY_ENABLED="${CILIUM_ENVOY_ENABLED:-true}"
CILIUM_L2_ANNOUNCEMENT="${CILIUM_ENABLE_L2_ANNOUNCEMENT:-false}"
CILIUM_L2_NEIGH="${CILIUM_ENABLE_L2_NEIGH_DISCOVERY:-false}"
# Install cilium CLI if missing or outdated
CILIUM_CLI_VERSION="${CILIUM_CLI_VERSION:-v0.19.0}"
if ! cilium version 2>/dev/null | grep -q "${CILIUM_CLI_VERSION}"; then
echo "=== cilium-cli: installing ${CILIUM_CLI_VERSION} ==="
curl -sL --remote-name-all \
"https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-${OS}-${ARCH}.tar.gz"{,.sha256sum}
sha256sum --check "cilium-${OS}-${ARCH}.tar.gz.sha256sum"
sudo tar xzfC "cilium-${OS}-${ARCH}.tar.gz" /usr/local/bin
rm "cilium-${OS}-${ARCH}.tar.gz" "cilium-${OS}-${ARCH}.tar.gz.sha256sum"
fi
CILIUM_ALREADY_INSTALLED=false
if cilium status --wait=false 2>/dev/null | grep -q "OK"; then
CILIUM_ALREADY_INSTALLED=true
fi
# Gateway API CRDs — must exist before cilium install/upgrade
if [ "${CILIUM_GATEWAY_API}" = "true" ]; then
echo "=== cilium: installing Gateway API CRDs ${CILIUM_GATEWAY_API_VERSION} ==="
GW_STD="https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/${CILIUM_GATEWAY_API_VERSION}/config/crd/standard"
for crd in \
gateway.networking.k8s.io_gatewayclasses.yaml \
gateway.networking.k8s.io_gateways.yaml \
gateway.networking.k8s.io_httproutes.yaml \
gateway.networking.k8s.io_referencegrants.yaml \
gateway.networking.k8s.io_grpcroutes.yaml; do
${KUBECTL} apply -f "${GW_STD}/${crd}"
done
# TLSRoute and TCPRoute are experimental-channel only; Cilium 1.19+ operator
# requires TLSRoute or the reconciler loop errors before creating the GatewayClass.
# TCPRoute is needed for raw-TCP Gateway listeners (e.g. Radicle P2P).
GW_EXP="https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/${CILIUM_GATEWAY_API_VERSION}/config/crd/experimental"
${KUBECTL} apply -f "${GW_EXP}/gateway.networking.k8s.io_tlsroutes.yaml"
${KUBECTL} apply -f "${GW_EXP}/gateway.networking.k8s.io_tcproutes.yaml"
fi
if [ "${CILIUM_ALREADY_INSTALLED}" = "true" ]; then
echo "=== cilium: already installed — upgrading to v${CILIUM_VERSION} ==="
else
echo "=== cilium: installing v${CILIUM_VERSION} ==="
fi
INSTALL_ARGS=(
"--version" "${CILIUM_VERSION}"
"--set" "ipam.mode=kubernetes"
)
if [ -n "${CILIUM_CLUSTER}" ]; then
INSTALL_ARGS+=(
"--set" "cluster.name=${CILIUM_CLUSTER}"
"--set" "cluster.id=${CILIUM_CLUSTER_ID}"
)
fi
if [ "${CILIUM_KPR}" = "true" ]; then
K8S_HOST="${CILIUM_K8S_SERVICE_HOST:-}"
K8S_PORT="${CILIUM_K8S_SERVICE_PORT:-6443}"
if [ -z "${K8S_HOST}" ]; then
_api_url="$(${KUBECTL} config view --raw -o jsonpath='{.clusters[0].cluster.server}' 2>/dev/null || true)"
if [ -z "${_api_url}" ]; then
echo "ERROR: kube-proxy-replacement=true requires CILIUM_K8S_SERVICE_HOST or a reachable KUBECONFIG" >&2
exit 1
fi
_stripped="${_api_url#https://}"
K8S_HOST="${_stripped%%:*}"
_maybe_port="${_stripped##*:}"
[ "${_maybe_port}" != "${K8S_HOST}" ] && K8S_PORT="${_maybe_port}"
fi
INSTALL_ARGS+=(
"--set" "kubeProxyReplacement=true"
"--set" "k8sServiceHost=${K8S_HOST}"
"--set" "k8sServicePort=${K8S_PORT}"
)
echo " kube-proxy replacement: enabled (apiserver=${K8S_HOST}:${K8S_PORT})"
fi
if [ "${CILIUM_SOCKET_LB}" = "false" ]; then
INSTALL_ARGS+=(
"--set" "socketLB.enabled=false"
)
echo " socketLB: disabled (prevents eBPF socket hooks on host network)"
else
INSTALL_ARGS+=(
"--set" "socketLB.enabled=true"
)
echo " socketLB: enabled (required for source-IP preservation via FIPs)"
fi
if [ "${CILIUM_ENVOY_ENABLED}" = "false" ]; then
INSTALL_ARGS+=(
"--set" "envoy.enabled=false"
)
echo " envoy: disabled (L7 policies unavailable — saves ~200MB RAM on constrained nodes)"
else
INSTALL_ARGS+=(
"--set" "envoy.enabled=true"
)
echo " envoy: enabled (required for Gateway API and L7 policies)"
fi
if [ "${CILIUM_INGRESS}" = "true" ]; then
INSTALL_ARGS+=(
"--set" "ingressController.enabled=true"
"--set" "ingressController.loadbalancerMode=${CILIUM_INGRESS_LB}"
"--set" "ingressController.default=true"
)
echo " ingress controller: enabled (lb-mode=${CILIUM_INGRESS_LB})"
fi
if [ "${CILIUM_GATEWAY_API}" = "true" ]; then
INSTALL_ARGS+=(
"--set" "gatewayAPI.enabled=true"
)
echo " gateway API: enabled (CRDs ${CILIUM_GATEWAY_API_VERSION})"
fi
if [ "${CILIUM_L2_NEIGH}" = "true" ]; then
INSTALL_ARGS+=(
"--set" "l2NeighDiscovery.enabled=true"
)
echo " L2 neighbor discovery: enabled (prerequisite for L2 announcements)"
fi
if [ "${CILIUM_L2_ANNOUNCEMENT}" = "true" ]; then
INSTALL_ARGS+=(
"--set" "l2announcements.enabled=true"
)
echo " L2 announcements: enabled (ARP-based VIP advertisement on L2 segments)"
fi
if [ "${CILIUM_ALREADY_INSTALLED}" = "true" ]; then
cilium upgrade "${INSTALL_ARGS[@]}"
else
cilium install "${INSTALL_ARGS[@]}"
fi
echo "=== cilium: waiting for status OK (timeout 5m) ==="
cilium status --wait --wait-duration 5m0s || cilium status
echo "=== cilium: ready ==="
# Provision cluster-level public Gateway when public_fip is declared in component settings.
# This is idempotent: kubectl apply is safe to re-run on upgrade.
# When gateway_sharing_key is set (HCCM clusters with shared LB IPs), the Gateway
# is annotated with lbipam.cilium.io/sharing-key so multiple Gateways can share the IP.
#
# The Gateway manifests below apply ONLY the base http:80 listener — but
# spec.listeners is x-kubernetes-list-type=map (merge key "name"), and every
# app component appends its own https listener afterward via a JSON-patch
# add (catalog/lib/gateway-tls.sh:_lib_gateway_patch_https). Plain `kubectl
# apply` (client-side, 3-way merge from this script's own last-applied-config)
# does not know about that OpenAPI merge-key semantics and replaces the whole
# array — wiping every app-added listener on the next cilium upgrade. Server-
# Side Apply with a stable field-manager respects the merge key and only ever
# asserts this script's own "http" entry, leaving other managers' entries
# alone. --force-conflicts takes ownership of fields previously set by plain
# client-side apply (the original bootstrap), which would otherwise conflict.
CILIUM_GATEWAY_PUBLIC_IP="${CILIUM_GATEWAY_PUBLIC_IP:-}"
CILIUM_GATEWAY_NAME="${CILIUM_GATEWAY_NAME:-${CILIUM_CLUSTER}}"
CILIUM_GATEWAY_SHARING_KEY="${CILIUM_GATEWAY_SHARING_KEY:-}"
if [ "${CILIUM_GATEWAY_API}" = "true" ] && [ -n "${CILIUM_GATEWAY_PUBLIC_IP}" ] && [ -n "${CILIUM_GATEWAY_NAME}" ]; then
echo "=== cilium: provisioning cluster Gateway '${CILIUM_GATEWAY_NAME}' @ ${CILIUM_GATEWAY_PUBLIC_IP} ==="
${KUBECTL} apply -f - <<CLUSTER_POOL
---
apiVersion: cilium.io/v2
kind: CiliumLoadBalancerIPPool
metadata:
name: ${CILIUM_GATEWAY_NAME}-public-pool
labels:
app.kubernetes.io/name: ${CILIUM_GATEWAY_NAME}
app.kubernetes.io/managed-by: provisioning
spec:
blocks:
- cidr: "${CILIUM_GATEWAY_PUBLIC_IP}/32"
serviceSelector:
matchLabels:
io.cilium.gateway/owning-gateway: ${CILIUM_GATEWAY_NAME}
CLUSTER_POOL
if [ -n "${CILIUM_GATEWAY_SHARING_KEY}" ]; then
${KUBECTL} apply --server-side --field-manager=cilium-taskserv --force-conflicts -f - <<CLUSTER_GW
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: ${CILIUM_GATEWAY_NAME}
namespace: kube-system
labels:
app.kubernetes.io/name: ${CILIUM_GATEWAY_NAME}
app.kubernetes.io/managed-by: provisioning
annotations:
lbipam.cilium.io/ips: "${CILIUM_GATEWAY_PUBLIC_IP}"
lbipam.cilium.io/sharing-key: "${CILIUM_GATEWAY_SHARING_KEY}"
spec:
gatewayClassName: cilium
listeners:
- name: http
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: All
CLUSTER_GW
else
${KUBECTL} apply --server-side --field-manager=cilium-taskserv --force-conflicts -f - <<CLUSTER_GW
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: ${CILIUM_GATEWAY_NAME}
namespace: kube-system
labels:
app.kubernetes.io/name: ${CILIUM_GATEWAY_NAME}
app.kubernetes.io/managed-by: provisioning
annotations:
lbipam.cilium.io/ips: "${CILIUM_GATEWAY_PUBLIC_IP}"
spec:
gatewayClassName: cilium
listeners:
- name: http
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: All
CLUSTER_GW
fi
echo " cluster Gateway '${CILIUM_GATEWAY_NAME}' → ${CILIUM_GATEWAY_PUBLIC_IP}:80${CILIUM_GATEWAY_SHARING_KEY:+ (sharing-key: ${CILIUM_GATEWAY_SHARING_KEY})}"
fi
# Optional private/VPN Gateway for internal services sharing an internal LB IP.
CILIUM_GATEWAY_PRIVATE_IP="${CILIUM_GATEWAY_PRIVATE_IP:-}"
CILIUM_GATEWAY_PRIVATE_NAME="${CILIUM_GATEWAY_PRIVATE_NAME:-private-gateway}"
if [ "${CILIUM_GATEWAY_API}" = "true" ] && [ -n "${CILIUM_GATEWAY_PRIVATE_IP}" ]; then
echo "=== cilium: provisioning private Gateway '${CILIUM_GATEWAY_PRIVATE_NAME}' @ ${CILIUM_GATEWAY_PRIVATE_IP} ==="
${KUBECTL} apply -f - <<PRIVATE_POOL
---
apiVersion: cilium.io/v2
kind: CiliumLoadBalancerIPPool
metadata:
name: ${CILIUM_GATEWAY_PRIVATE_NAME}-pool
labels:
app.kubernetes.io/name: ${CILIUM_GATEWAY_PRIVATE_NAME}
app.kubernetes.io/managed-by: provisioning
spec:
blocks:
- cidr: "${CILIUM_GATEWAY_PRIVATE_IP}/32"
serviceSelector:
matchLabels:
io.cilium.gateway/owning-gateway: ${CILIUM_GATEWAY_PRIVATE_NAME}
PRIVATE_POOL
${KUBECTL} apply --server-side --field-manager=cilium-taskserv --force-conflicts -f - <<PRIVATE_GW
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: ${CILIUM_GATEWAY_PRIVATE_NAME}
namespace: kube-system
labels:
app.kubernetes.io/name: ${CILIUM_GATEWAY_PRIVATE_NAME}
app.kubernetes.io/managed-by: provisioning
annotations:
lbipam.cilium.io/ips: "${CILIUM_GATEWAY_PRIVATE_IP}"
spec:
gatewayClassName: cilium
listeners:
- name: http
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: All
PRIVATE_GW
echo " private Gateway '${CILIUM_GATEWAY_PRIVATE_NAME}' → ${CILIUM_GATEWAY_PRIVATE_IP}:80 (VPN/internal only)"
fi