xpeditis2.0/infra/prod/scripts/02-setup-k3s-server.sh
2026-09-07 21:40:50 +02:00

246 lines
9.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# =============================================================================
# 02 - Installation du cluster k3s (app-01)
# =============================================================================
# Prerequis : 00-bootstrap-common.sh app deja execute sur ce serveur.
#
# sudo K3S_VERSION=v1.31.5+k3s1 PUBLIC_IP=<ip> bash 02-setup-k3s-server.sh
#
# Choix structurants et pourquoi :
# --secrets-encryption les Secrets sont chiffres au repos dans la base
# d'etat de k3s. Sans ce drapeau, un acces disque
# (snapshot, vol de volume) livre tous les secrets.
# --protect-kernel-defaults refuse de demarrer si les sysctl attendus par
# kubelet ne sont pas poses : echec bruyant plutot
# que derive silencieuse.
# audit-log journal d'audit de l'API : indispensable pour
# repondre a "qui a supprime ce deploiement".
# Traefik est CONSERVE (celui livre par k3s) et reconfigure via
# HelmChartConfig : entrypoints, en-tetes de
# securite, IPs de confiance Cloudflare.
set -euo pipefail
[[ "$EUID" -eq 0 ]] || { echo "Executer en root (sudo)." >&2; exit 1; }
K3S_VERSION="${K3S_VERSION:-v1.31.5+k3s1}"
PUBLIC_IP="${PUBLIC_IP:-$(curl -s --max-time 5 https://ifconfig.me || true)}"
PRIVATE_IP="${PRIVATE_IP:-10.10.1.10}"
MANIFEST_DIR=/var/lib/rancher/k3s/server/manifests
log() { printf '\n>>> %s\n' "$*"; }
[[ -n "$PUBLIC_IP" ]] || { echo "PUBLIC_IP introuvable, passez-la en variable." >&2; exit 1; }
# --- 1. Prerequis noyau ------------------------------------------------------
log "Parametres noyau exiges par kubelet (--protect-kernel-defaults)"
cat > /etc/sysctl.d/90-kubelet.conf <<'CONF'
vm.panic_on_oom=0
vm.overcommit_memory=1
kernel.panic=10
kernel.panic_on_oops=1
CONF
sysctl --system >/dev/null
log "Desactivation du swap (exige par kubelet)"
swapoff -a || true
sed -i '/\sswap\s/s/^/#/' /etc/fstab || true
# --- 2. Politique d'audit de l'API Kubernetes -------------------------------
log "Politique d'audit"
mkdir -p /var/lib/rancher/k3s/server /var/log/k3s
cat > /var/lib/rancher/k3s/server/audit-policy.yaml <<'YAML'
apiVersion: audit.k8s.io/v1
kind: Policy
# Ne jamais journaliser le contenu des Secrets : le journal d'audit deviendrait
# lui-meme un coffre-fort en clair.
omitStages:
- RequestReceived
rules:
- level: None
resources:
- group: ""
resources: ["secrets", "configmaps"]
verbs: ["get", "list", "watch"]
# Bruit de fond : sondes et lectures d'etat.
- level: None
users: ["system:kube-proxy", "system:apiserver"]
verbs: ["watch", "list"]
- level: None
nonResourceURLs: ["/healthz*", "/readyz*", "/livez*", "/version", "/metrics"]
# Toute ecriture est tracee avec ses metadonnees (qui, quoi, quand).
- level: Metadata
verbs: ["create", "update", "patch", "delete", "deletecollection"]
# Acces aux Secrets : trace, sans le contenu.
- level: Metadata
resources:
- group: ""
resources: ["secrets"]
# Escalades de privileges : trace complet cote requete.
- level: Request
resources:
- group: "rbac.authorization.k8s.io"
resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"]
- level: Metadata
YAML
chmod 600 /var/lib/rancher/k3s/server/audit-policy.yaml
# --- 3. Configuration Traefik (avant l'installation : k3s l'applique au boot) -
log "Configuration Traefik (HelmChartConfig)"
mkdir -p "$MANIFEST_DIR"
cat > "${MANIFEST_DIR}/traefik-config.yaml" <<'YAML'
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: traefik
namespace: kube-system
spec:
valuesContent: |-
# Les vraies IP des visiteurs arrivent dans X-Forwarded-For, pose par
# Cloudflare. Sans cette liste de confiance, la limitation de debit et les
# audit_logs verraient tous l'IP de Cloudflare : inexploitable.
# Rafraichir avec scripts/refresh-cloudflare-ips.sh.
additionalArguments:
- "--entrypoints.web.forwardedHeaders.trustedIPs=173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/13,104.24.0.0/14,172.64.0.0/13,131.0.72.0/22,2400:cb00::/32,2606:4700::/32,2803:f800::/32,2405:b500::/32,2405:8100::/32,2a06:98c0::/29,2c0f:f248::/32"
- "--entrypoints.websecure.forwardedHeaders.trustedIPs=173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/13,104.24.0.0/14,172.64.0.0/13,131.0.72.0/22,2400:cb00::/32,2606:4700::/32,2803:f800::/32,2405:b500::/32,2405:8100::/32,2a06:98c0::/29,2c0f:f248::/32"
# Tout le trafic HTTP est redirige en HTTPS au niveau de l'entrypoint :
# aucun Ingress ne peut oublier de le faire.
- "--entrypoints.web.http.redirections.entryPoint.to=websecure"
- "--entrypoints.web.http.redirections.entryPoint.scheme=https"
- "--entrypoints.web.http.redirections.entryPoint.permanent=true"
# Delais de garde : borne les connexions lentes (slowloris).
- "--entrypoints.websecure.transport.respondingTimeouts.readTimeout=60s"
- "--entrypoints.websecure.transport.respondingTimeouts.writeTimeout=0s"
- "--entrypoints.websecure.transport.respondingTimeouts.idleTimeout=180s"
- "--serversTransport.maxIdleConnsPerHost=100"
# WebSocket (Socket.IO) : pas de timeout d'ecriture, sinon les
# notifications temps reel sont coupees toutes les 60 s.
- "--metrics.prometheus=true"
- "--metrics.prometheus.addEntryPointsLabels=true"
- "--metrics.prometheus.addServicesLabels=true"
- "--accesslog=true"
- "--accesslog.format=json"
- "--accesslog.fields.headers.defaultMode=drop"
- "--accesslog.fields.headers.names.User-Agent=keep"
- "--accesslog.fields.headers.names.Cf-Connecting-Ip=keep"
- "--ping=true"
# Le tableau de bord Traefik n'est jamais expose.
ingressRoute:
dashboard:
enabled: false
logs:
general:
level: WARN
access:
enabled: true
# Le service est en LoadBalancer (klipper-lb de k3s) : il ecoute
# directement sur les ports 80/443 de l'hote, filtres par le firewall
# Hetzner qui n'accepte que les IP Cloudflare.
service:
spec:
externalTrafficPolicy: Local
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 512Mi
YAML
# --- 4. Installation de k3s --------------------------------------------------
if ! command -v k3s >/dev/null; then
log "Installation de k3s ${K3S_VERSION}"
curl -sfL https://get.k3s.io | \
INSTALL_K3S_VERSION="${K3S_VERSION}" \
INSTALL_K3S_EXEC="server \
--node-name=xpeditis-prod-app-01 \
--node-ip=${PRIVATE_IP} \
--advertise-address=${PRIVATE_IP} \
--tls-san=${PUBLIC_IP} \
--tls-san=${PRIVATE_IP} \
--write-kubeconfig-mode=0600 \
--secrets-encryption \
--protect-kernel-defaults \
--kube-apiserver-arg=audit-log-path=/var/log/k3s/audit.log \
--kube-apiserver-arg=audit-policy-file=/var/lib/rancher/k3s/server/audit-policy.yaml \
--kube-apiserver-arg=audit-log-maxage=30 \
--kube-apiserver-arg=audit-log-maxbackup=10 \
--kube-apiserver-arg=audit-log-maxsize=100 \
--kube-apiserver-arg=request-timeout=300s \
--kubelet-arg=streaming-connection-idle-timeout=5m \
--kubelet-arg=event-qps=0 \
--etcd-expose-metrics=false" \
sh -
else
log "k3s deja installe : $(k3s --version | head -1)"
fi
systemctl enable --now k3s
log "Attente de la disponibilite du noeud"
for _ in $(seq 1 60); do
k3s kubectl get node >/dev/null 2>&1 && break
sleep 5
done
k3s kubectl get node -o wide
# --- 5. kubectl pour l'utilisateur deploy -----------------------------------
log "Configuration de kubectl pour l'utilisateur deploy"
DEPLOY_USER="${DEPLOY_USER:-deploy}"
install -d -m 700 -o "$DEPLOY_USER" -g "$DEPLOY_USER" "/home/${DEPLOY_USER}/.kube"
install -m 600 -o "$DEPLOY_USER" -g "$DEPLOY_USER" \
/etc/rancher/k3s/k3s.yaml "/home/${DEPLOY_USER}/.kube/config"
grep -q 'KUBECONFIG' "/home/${DEPLOY_USER}/.bashrc" || \
echo 'export KUBECONFIG=$HOME/.kube/config' >> "/home/${DEPLOY_USER}/.bashrc"
# `k3s kubectl` reste reserve a root ; deploy passe par son kubeconfig.
ln -sf /usr/local/bin/k3s /usr/local/bin/kubectl
# --- 6. Rotation des journaux d'audit ---------------------------------------
cat > /etc/logrotate.d/k3s-audit <<'CONF'
/var/log/k3s/audit.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
copytruncate
su root root
}
CONF
# --- 7. Verifications --------------------------------------------------------
log "Etat du cluster"
k3s kubectl get nodes
k3s kubectl -n kube-system get pods
cat <<NEXT
=============================================================================
Cluster k3s pret.
=============================================================================
Le chiffrement des Secrets au repos est actif :
sudo k3s secrets-encrypt status
Recuperer le kubeconfig sur votre poste (l'API n'est ouverte qu'a vos IPs
d'administration, cf. firewall Terraform) :
ssh deploy@${PUBLIC_IP} 'cat ~/.kube/config' \\
| sed "s/127.0.0.1/${PUBLIC_IP}/" > ~/.kube/xpeditis-prod.yaml
chmod 600 ~/.kube/xpeditis-prod.yaml
export KUBECONFIG=~/.kube/xpeditis-prod.yaml
kubectl get nodes
Etape suivante :
sudo bash 03-install-cluster-addons.sh
=============================================================================
NEXT