Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BAUeCFpDkRD6tU5wGsc1C
246 lines
9.6 KiB
Bash
Executable File
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
|