### Summary of Changes - **Legacy Cleanup**: - Deleted `ci/deploy/` (`docker-compose.yml`, `.env.template`, `nginx/`) - Deleted `ci/scripts/deploy-certbot.sh` - Deleted `ci/scripts/deploy-new-tags.sh` - **Cloud-Init Template Updates**: - Replaced username `debian` with `adam` - Removed deprecated `disable: - servicelb` - Cleaned up heredoc YAML formatting
221 lines
7.7 KiB
YAML
221 lines
7.7 KiB
YAML
#cloud-config
|
|
# ==============================================================================
|
|
# Cloud-Init User-Data Template for K3s Single-Node Cluster (com-ermnvldmr)
|
|
#
|
|
# Usage:
|
|
# 1. Copy this template to user-data.yaml:
|
|
# cp ci/cloud-init/user-data.template.yaml ci/cloud-init/user-data.yaml
|
|
# 2. Replace the stubs below (SSH authorized key & Age private key).
|
|
# 3. Pass user-data.yaml during VM creation in Yandex Cloud / Cloud Provider.
|
|
# Note: user-data.yaml is ignored by git to protect private keys.
|
|
# ==============================================================================
|
|
|
|
preserve_hostname: false
|
|
hostname: com-ermnvldmr
|
|
ssh_pwauth: false
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# 1. User & SSH Key Configuration (Replace stub with your public SSH key)
|
|
# ------------------------------------------------------------------------------
|
|
users:
|
|
- name: adam
|
|
sudo: ALL=(ALL) NOPASSWD:ALL
|
|
shell: /bin/bash
|
|
ssh_authorized_keys:
|
|
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPLACEHOLDERFORYOURPUBLICSSHKEY user@hostname"
|
|
|
|
write_files:
|
|
# ----------------------------------------------------------------------------
|
|
# 2. SOPS / Age Decryption Key (Replace stub with your private AGE key)
|
|
# ----------------------------------------------------------------------------
|
|
- path: /root/.config/sops/age/keys.txt
|
|
owner: root:root
|
|
permissions: '0600'
|
|
content: |
|
|
# public key: age1jpa7hqyzzdfq7lvnw3l8qy6648mtjgpcqnljl5jz92dcqjrnsqfsn7m0fl
|
|
AGE-SECRET-KEY-1PLACEHOLDERFORYOURPRIVATEAGEKEY
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# 3. Security Hardening Configuration
|
|
# ----------------------------------------------------------------------------
|
|
- path: /etc/ssh/sshd_config.d/99-security-hardening.conf
|
|
owner: root:root
|
|
permissions: '0600'
|
|
content: |
|
|
PasswordAuthentication no
|
|
PermitRootLogin prohibit-password
|
|
KbdInteractiveAuthentication no
|
|
X11Forwarding no
|
|
MaxAuthTries 3
|
|
ClientAliveInterval 300
|
|
ClientAliveCountMax 2
|
|
|
|
- path: /etc/sysctl.d/99-k3s-hardening.conf
|
|
owner: root:root
|
|
permissions: '0644'
|
|
content: |
|
|
# Network SYN Flood Protection
|
|
net.ipv4.tcp_syncookies = 1
|
|
# Disable ICMP redirects
|
|
net.ipv4.conf.all.accept_redirects = 0
|
|
net.ipv4.conf.default.accept_redirects = 0
|
|
net.ipv4.conf.all.send_redirects = 0
|
|
net.ipv4.conf.default.send_redirects = 0
|
|
# Disable Source Routing
|
|
net.ipv4.conf.all.accept_source_route = 0
|
|
net.ipv4.conf.default.accept_source_route = 0
|
|
# Anti-spoofing (Reverse Path Filtering)
|
|
net.ipv4.conf.all.rp_filter = 1
|
|
net.ipv4.conf.default.rp_filter = 1
|
|
# Required for K8s pod networking
|
|
net.ipv4.ip_forward = 1
|
|
# System Hardening
|
|
kernel.kptr_restrict = 2
|
|
fs.protected_hardlinks = 1
|
|
fs.protected_symlinks = 1
|
|
fs.inotify.max_user_watches = 524288
|
|
fs.inotify.max_user_instances = 512
|
|
|
|
- path: /etc/rancher/k3s/config.yaml
|
|
owner: root:root
|
|
permissions: '0600'
|
|
content: |
|
|
write-kubeconfig-mode: "0644"
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# 4. Bootstrap Cluster Script
|
|
# ----------------------------------------------------------------------------
|
|
- path: /usr/local/bin/bootstrap-cluster.sh
|
|
owner: root:root
|
|
permissions: '0755'
|
|
content: |
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
echo "=== [Bootstrap] Ensuring Reliable DNS Resolution ==="
|
|
if ! grep -q "nameserver" /etc/resolv.conf 2>/dev/null; then
|
|
cat << 'EOF_DNS' > /etc/resolv.conf
|
|
nameserver 1.1.1.1
|
|
nameserver 8.8.8.8
|
|
nameserver 77.88.8.8
|
|
EOF_DNS
|
|
fi
|
|
|
|
echo "=== [Bootstrap] Applying Sysctl Hardening ==="
|
|
sysctl --system
|
|
|
|
echo "=== [Bootstrap] Ensuring SSH Host Keys & Starting SSH ==="
|
|
ssh-keygen -A
|
|
systemctl restart ssh || systemctl restart sshd || true
|
|
|
|
echo "=== [Bootstrap] Configuring UFW Firewall ==="
|
|
ufw default deny incoming
|
|
ufw default allow outgoing
|
|
ufw default allow routed
|
|
ufw allow 22/tcp comment 'SSH'
|
|
ufw allow 80/tcp comment 'HTTP Traefik'
|
|
ufw allow 443/tcp comment 'HTTPS Traefik'
|
|
# Allow Kubernetes Pod & Service traffic
|
|
ufw allow in on cni0 || true
|
|
ufw allow in on flannel.1 || true
|
|
ufw allow from 10.42.0.0/16 comment 'K3s Pod CIDR'
|
|
ufw allow from 10.43.0.0/16 comment 'K3s Service CIDR'
|
|
ufw --force enable
|
|
|
|
echo "=== [Bootstrap] Enabling Fail2ban ==="
|
|
systemctl enable --now fail2ban || true
|
|
|
|
echo "=== [Bootstrap] Checking for Secondary Persistent Data Disk ==="
|
|
if [ -b /dev/vdb ]; then
|
|
echo "Secondary disk /dev/vdb detected."
|
|
if ! blkid /dev/vdb >/dev/null 2>&1; then
|
|
echo "Formatting /dev/vdb with ext4 filesystem..."
|
|
mkfs.ext4 -F -L k3s-storage /dev/vdb
|
|
fi
|
|
mkdir -p /var/lib/rancher/k3s/storage
|
|
if ! grep -qs '/var/lib/rancher/k3s/storage' /proc/mounts; then
|
|
mount /dev/vdb /var/lib/rancher/k3s/storage
|
|
echo "LABEL=k3s-storage /var/lib/rancher/k3s/storage ext4 defaults,noatime 0 2" >> /etc/fstab
|
|
echo "Mounted /dev/vdb to /var/lib/rancher/k3s/storage."
|
|
fi
|
|
fi
|
|
|
|
echo "=== [Bootstrap] Starting K3s Service ==="
|
|
systemctl enable --now k3s.service
|
|
|
|
echo "=== [Bootstrap] Waiting for K3s API server ready ==="
|
|
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
|
|
until kubectl get nodes >/dev/null 2>&1; do
|
|
echo "Waiting for Kubernetes API..."
|
|
sleep 3
|
|
done
|
|
|
|
echo "=== [Bootstrap] Cluster node is online ==="
|
|
kubectl get nodes
|
|
|
|
echo "=== [Bootstrap] Creating flux-system namespace ==="
|
|
kubectl create namespace flux-system --dry-run=client -o yaml | kubectl apply -f -
|
|
|
|
echo "=== [Bootstrap] Injecting SOPS age decryption key ==="
|
|
if [ -f /root/.config/sops/age/keys.txt ]; then
|
|
kubectl create secret generic sops-age \
|
|
--namespace=flux-system \
|
|
--from-file=age.agekey=/root/.config/sops/age/keys.txt \
|
|
--dry-run=client -o yaml | kubectl apply -f -
|
|
echo "sops-age secret created."
|
|
else
|
|
echo "WARNING: /root/.config/sops/age/keys.txt not found. Inject key manually via kubectl."
|
|
fi
|
|
|
|
echo "=== [Bootstrap] Installing Flux Operator / Custom Resources ==="
|
|
kubectl apply -f https://github.com/fluxcd/flux2/releases/download/v2.4.0/install.yaml
|
|
|
|
echo "=== [Bootstrap] Setting up Kubectl access for non-root users ==="
|
|
for home_dir in /home/*; do
|
|
if [ -d "$home_dir" ]; then
|
|
user_name=$(basename "$home_dir")
|
|
mkdir -p "$home_dir/.kube"
|
|
cp /etc/rancher/k3s/k3s.yaml "$home_dir/.kube/config"
|
|
chown -R "$user_name:$user_name" "$home_dir/.kube"
|
|
chmod 600 "$home_dir/.kube/config"
|
|
fi
|
|
done
|
|
|
|
echo "=== [Bootstrap] Applying initial GitOps root sync ==="
|
|
kubectl apply -f - << 'EOF_GITOPS'
|
|
apiVersion: source.toolkit.fluxcd.io/v1
|
|
kind: GitRepository
|
|
metadata:
|
|
name: ermnvldmr-repo
|
|
namespace: flux-system
|
|
spec:
|
|
interval: 1m
|
|
url: https://github.com/deytenit/ermnvldmr.com.git
|
|
ref:
|
|
branch: next
|
|
---
|
|
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
|
kind: Kustomization
|
|
metadata:
|
|
name: cluster-sync
|
|
namespace: flux-system
|
|
spec:
|
|
interval: 5m
|
|
sourceRef:
|
|
kind: GitRepository
|
|
name: ermnvldmr-repo
|
|
path: ./ci/k8s/clusters/production
|
|
prune: true
|
|
wait: true
|
|
timeout: 2m
|
|
decryption:
|
|
provider: sops
|
|
secretRef:
|
|
name: sops-age
|
|
EOF_GITOPS
|
|
|
|
echo "=== [Bootstrap] First-Boot Bootstrap Sequence Complete ==="
|
|
|
|
runcmd:
|
|
- /usr/local/bin/bootstrap-cluster.sh
|