updates
This commit is contained in:
parent
f971c35b4e
commit
ffe9a59269
234 changed files with 7684 additions and 2433 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# Terraform directories
|
||||
**/.terraform/
|
||||
|
|
@ -12,6 +12,12 @@
|
|||
name: epel-release
|
||||
state: present
|
||||
|
||||
- name: Clean DNF cache
|
||||
ansible.builtin.command: dnf clean all
|
||||
args:
|
||||
warn: false
|
||||
changed_when: false
|
||||
|
||||
- name: Upgrade all packages
|
||||
ansible.builtin.dnf:
|
||||
name: "*"
|
||||
|
|
|
|||
7
ansible/roles/alpine/defaults/main.yml
Normal file
7
ansible/roles/alpine/defaults/main.yml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
# Alpine-specific service names
|
||||
ssh_service: sshd
|
||||
ntp_service: chronyd
|
||||
|
||||
# Alpine doesn't have firewalld
|
||||
manage_firewall: false
|
||||
65
ansible/roles/alpine/tasks/main.yml
Normal file
65
ansible/roles/alpine/tasks/main.yml
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
---
|
||||
# Alpine Linux specific tasks
|
||||
|
||||
- name: Update apk cache
|
||||
apk:
|
||||
update_cache: yes
|
||||
changed_when: false
|
||||
|
||||
- name: Upgrade all packages
|
||||
apk:
|
||||
upgrade: yes
|
||||
update_cache: yes
|
||||
register: apk_upgrade
|
||||
changed_when: "'OK:' not in apk_upgrade.stdout"
|
||||
|
||||
- name: Install sudo first (required for user management)
|
||||
apk:
|
||||
name: sudo
|
||||
state: present
|
||||
|
||||
- name: Install base packages
|
||||
apk:
|
||||
name:
|
||||
- bash
|
||||
- curl
|
||||
- git
|
||||
- htop
|
||||
- iotop
|
||||
- nano
|
||||
- net-tools
|
||||
- openssh-server
|
||||
- python3
|
||||
- py3-pip
|
||||
- rsync
|
||||
- shadow # For useradd/usermod commands
|
||||
- tmux
|
||||
- vim
|
||||
- wget
|
||||
state: present
|
||||
|
||||
- name: Ensure sshd is running and enabled
|
||||
service:
|
||||
name: sshd
|
||||
state: started
|
||||
enabled: yes
|
||||
|
||||
- name: Configure sudo for passwordless
|
||||
lineinfile:
|
||||
path: /etc/sudoers
|
||||
regexp: '^%sudo\s'
|
||||
line: '%sudo ALL=(ALL) NOPASSWD: ALL'
|
||||
validate: '/usr/sbin/visudo -cf %s'
|
||||
when: configure_passwordless_sudo | default(true)
|
||||
|
||||
- name: Create sudo group if it doesn't exist
|
||||
group:
|
||||
name: sudo
|
||||
state: present
|
||||
|
||||
- name: Ensure ansible user is in sudo group
|
||||
user:
|
||||
name: ansible
|
||||
groups: sudo
|
||||
append: yes
|
||||
when: ansible_user == "ansible"
|
||||
|
|
@ -35,4 +35,8 @@
|
|||
|
||||
- include_role:
|
||||
name: debian
|
||||
when: ansible_os_family == "Debian"
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
- include_role:
|
||||
name: alpine
|
||||
when: ansible_os_family == "Alpine"
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
- name: Install required packages
|
||||
# Debian/Ubuntu tasks
|
||||
- name: Install required packages (Debian/Ubuntu)
|
||||
apt:
|
||||
name:
|
||||
- debian-keyring
|
||||
|
|
@ -9,23 +10,35 @@
|
|||
- gnupg2
|
||||
state: present
|
||||
update_cache: yes
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
- name: Add Caddy GPG key
|
||||
- name: Add Caddy GPG key (Debian/Ubuntu)
|
||||
apt_key:
|
||||
url: "https://dl.cloudsmith.io/public/caddy/stable/gpg.key"
|
||||
state: present
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
- name: Add Caddy repository
|
||||
- name: Add Caddy repository (Debian/Ubuntu)
|
||||
apt_repository:
|
||||
repo: "deb https://dl.cloudsmith.io/public/caddy/stable/deb/debian any-version main"
|
||||
state: present
|
||||
filename: caddy-stable
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
- name: Install Caddy
|
||||
- name: Install Caddy (Debian/Ubuntu)
|
||||
apt:
|
||||
name: caddy
|
||||
state: present
|
||||
update_cache: yes
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
# Alpine Linux tasks
|
||||
- name: Install Caddy (Alpine)
|
||||
apk:
|
||||
name: caddy
|
||||
state: present
|
||||
update_cache: yes
|
||||
when: ansible_os_family == "Alpine"
|
||||
|
||||
- name: Ensure Caddy service is enabled and started
|
||||
service:
|
||||
|
|
|
|||
71
ansible/roles/icinga2-container/files/Dockerfile
Normal file
71
ansible/roles/icinga2-container/files/Dockerfile
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
FROM ubuntu:22.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
ENV TZ=UTC
|
||||
|
||||
# Add Icinga repository
|
||||
RUN apt-get update && apt-get install -y \
|
||||
wget \
|
||||
gnupg \
|
||||
lsb-release \
|
||||
ca-certificates \
|
||||
&& wget -O - https://packages.icinga.com/icinga.key | apt-key add - \
|
||||
&& echo "deb [signed-by=/usr/share/keyrings/icinga-archive-keyring.gpg] https://packages.icinga.com/ubuntu icinga-$(lsb_release -cs) main" > /etc/apt/sources.list.d/icinga2.list \
|
||||
&& echo "deb-src [signed-by=/usr/share/keyrings/icinga-archive-keyring.gpg] https://packages.icinga.com/ubuntu icinga-$(lsb_release -cs) main" >> /etc/apt/sources.list.d/icinga2.list \
|
||||
&& wget -O /usr/share/keyrings/icinga-archive-keyring.gpg https://packages.icinga.com/icinga.key
|
||||
|
||||
# Install packages
|
||||
RUN apt-get update && apt-get install -y \
|
||||
icinga2 \
|
||||
icinga2-ido-mysql \
|
||||
icingaweb2 \
|
||||
icingacli \
|
||||
mariadb-client \
|
||||
apache2 \
|
||||
libapache2-mod-php \
|
||||
php-bcmath \
|
||||
php-curl \
|
||||
php-gd \
|
||||
php-intl \
|
||||
php-ldap \
|
||||
php-mbstring \
|
||||
php-mysql \
|
||||
php-pgsql \
|
||||
php-soap \
|
||||
php-xml \
|
||||
php-zip \
|
||||
php-imagick \
|
||||
supervisor \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Enable Apache modules
|
||||
RUN a2enmod rewrite headers env dir mime
|
||||
|
||||
# Configure Apache for IcingaWeb2
|
||||
COPY icingaweb2.conf /etc/apache2/sites-available/
|
||||
RUN a2dissite 000-default && a2ensite icingaweb2
|
||||
|
||||
# Create directories
|
||||
RUN mkdir -p /etc/icinga2/pki \
|
||||
/var/run/icinga2/cmd \
|
||||
/var/log/icinga2 \
|
||||
/var/lib/icinga2/api/zones \
|
||||
/var/cache/icinga2 \
|
||||
&& chown -R nagios:nagios /etc/icinga2 /var/run/icinga2 /var/log/icinga2 /var/lib/icinga2 /var/cache/icinga2
|
||||
|
||||
# Setup volumes
|
||||
VOLUME ["/etc/icinga2", "/etc/icingaweb2", "/var/lib/icinga2"]
|
||||
|
||||
# Expose ports
|
||||
EXPOSE 80 5665
|
||||
|
||||
# Copy supervisord config
|
||||
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||
|
||||
# Copy startup script
|
||||
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||
RUN chmod +x /docker-entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
||||
77
ansible/roles/icinga2-container/files/docker-entrypoint.sh
Normal file
77
ansible/roles/icinga2-container/files/docker-entrypoint.sh
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Wait for database to be ready
|
||||
if [ ! -z "${MYSQL_HOST}" ]; then
|
||||
echo "Waiting for database connection..."
|
||||
for i in {1..30}; do
|
||||
if mysql -h${MYSQL_HOST} -u${MYSQL_USER} -p${MYSQL_PASSWORD} -e "SELECT 1" >/dev/null 2>&1; then
|
||||
echo "Database is ready!"
|
||||
break
|
||||
fi
|
||||
echo "Waiting for database... ($i/30)"
|
||||
sleep 2
|
||||
done
|
||||
fi
|
||||
|
||||
# Initialize Icinga2 database if needed
|
||||
if [ ! -z "${MYSQL_HOST}" ] && [ ! -z "${INIT_MYSQL}" ]; then
|
||||
echo "Checking if Icinga2 database needs initialization..."
|
||||
if ! mysql -h${MYSQL_HOST} -u${MYSQL_USER} -p${MYSQL_PASSWORD} ${MYSQL_DATABASE} -e "SELECT * FROM icinga_dbversion" >/dev/null 2>&1; then
|
||||
echo "Initializing Icinga2 database..."
|
||||
mysql -h${MYSQL_HOST} -u${MYSQL_USER} -p${MYSQL_PASSWORD} ${MYSQL_DATABASE} < /usr/share/icinga2-ido-mysql/schema/mysql.sql
|
||||
fi
|
||||
fi
|
||||
|
||||
# Configure IDO-MySQL if environment variables are set
|
||||
if [ ! -z "${MYSQL_HOST}" ]; then
|
||||
cat > /etc/icinga2/features-available/ido-mysql.conf <<EOF
|
||||
library "db_ido_mysql"
|
||||
|
||||
object IdoMysqlConnection "ido-mysql" {
|
||||
user = "${MYSQL_USER}"
|
||||
password = "${MYSQL_PASSWORD}"
|
||||
host = "${MYSQL_HOST}"
|
||||
database = "${MYSQL_DATABASE}"
|
||||
}
|
||||
EOF
|
||||
icinga2 feature enable ido-mysql
|
||||
fi
|
||||
|
||||
# Setup API if certificates don't exist
|
||||
if [ ! -f /var/lib/icinga2/certs/ca.crt ]; then
|
||||
echo "Setting up Icinga2 API..."
|
||||
icinga2 api setup
|
||||
fi
|
||||
|
||||
# Enable required features
|
||||
icinga2 feature enable api command checker mainlog notification
|
||||
|
||||
# Configure IcingaWeb2 database settings if provided
|
||||
if [ ! -z "${ICINGAWEB_MYSQL_HOST}" ]; then
|
||||
mkdir -p /etc/icingaweb2
|
||||
cat > /etc/icingaweb2/resources.ini <<EOF
|
||||
[icingaweb_db]
|
||||
type = "db"
|
||||
db = "mysql"
|
||||
host = "${ICINGAWEB_MYSQL_HOST}"
|
||||
dbname = "${ICINGAWEB_MYSQL_DATABASE}"
|
||||
username = "${ICINGAWEB_MYSQL_USER}"
|
||||
password = "${ICINGAWEB_MYSQL_PASSWORD}"
|
||||
|
||||
[icinga_ido]
|
||||
type = "db"
|
||||
db = "mysql"
|
||||
host = "${MYSQL_HOST}"
|
||||
dbname = "${MYSQL_DATABASE}"
|
||||
username = "${MYSQL_USER}"
|
||||
password = "${MYSQL_PASSWORD}"
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Fix permissions
|
||||
chown -R nagios:nagios /etc/icinga2 /var/run/icinga2 /var/log/icinga2 /var/lib/icinga2 /var/cache/icinga2
|
||||
chown -R www-data:icingaweb2 /etc/icingaweb2
|
||||
|
||||
# Execute CMD
|
||||
exec "$@"
|
||||
20
ansible/roles/icinga2-container/files/icingaweb2.conf
Normal file
20
ansible/roles/icinga2-container/files/icingaweb2.conf
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<VirtualHost *:80>
|
||||
DocumentRoot /usr/share/icingaweb2/public
|
||||
|
||||
<Directory /usr/share/icingaweb2/public>
|
||||
Options SymLinksIfOwnerMatch
|
||||
AllowOverride None
|
||||
Require all granted
|
||||
|
||||
RewriteEngine On
|
||||
RewriteBase /
|
||||
RewriteCond %{REQUEST_FILENAME} -s [OR]
|
||||
RewriteCond %{REQUEST_FILENAME} -l [OR]
|
||||
RewriteCond %{REQUEST_FILENAME} -d
|
||||
RewriteRule ^.*$ - [NC,L]
|
||||
RewriteRule ^.*$ index.php [NC,L]
|
||||
</Directory>
|
||||
|
||||
ErrorLog ${APACHE_LOG_DIR}/icingaweb2-error.log
|
||||
CustomLog ${APACHE_LOG_DIR}/icingaweb2-access.log combined
|
||||
</VirtualHost>
|
||||
18
ansible/roles/icinga2-container/files/supervisord.conf
Normal file
18
ansible/roles/icinga2-container/files/supervisord.conf
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
[supervisord]
|
||||
nodaemon=true
|
||||
logfile=/var/log/supervisor/supervisord.log
|
||||
pidfile=/var/run/supervisord.pid
|
||||
|
||||
[program:apache2]
|
||||
command=/usr/sbin/apache2ctl -D FOREGROUND
|
||||
autostart=true
|
||||
autorestart=true
|
||||
stdout_logfile=/var/log/supervisor/apache2.log
|
||||
stderr_logfile=/var/log/supervisor/apache2_err.log
|
||||
|
||||
[program:icinga2]
|
||||
command=/usr/sbin/icinga2 daemon
|
||||
autostart=true
|
||||
autorestart=true
|
||||
stdout_logfile=/var/log/supervisor/icinga2.log
|
||||
stderr_logfile=/var/log/supervisor/icinga2_err.log
|
||||
|
|
@ -1,328 +0,0 @@
|
|||
# APRS Repository Setup for Kubernetes Deployment
|
||||
|
||||
This document provides instructions for setting up your Elixir/Phoenix APRS repository to build Docker images and deploy to your K3s cluster on GitHub Actions push to main branch.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. GitHub repository for your APRS application
|
||||
2. GitHub Container Registry (ghcr.io) access
|
||||
3. Tailscale installed on your K3s nodes
|
||||
4. Tailscale OAuth client for GitHub Actions
|
||||
|
||||
## Steps to Configure Your APRS Repository
|
||||
|
||||
### 1. Create Dockerfile
|
||||
|
||||
Add this `Dockerfile` to the root of your APRS repository:
|
||||
|
||||
```dockerfile
|
||||
# Build stage
|
||||
FROM elixir:1.15-alpine AS build
|
||||
|
||||
# Install build dependencies
|
||||
RUN apk add --no-cache build-base git python3
|
||||
|
||||
# Set build env
|
||||
ENV MIX_ENV=prod
|
||||
|
||||
# Install hex + rebar
|
||||
RUN mix local.hex --force && \
|
||||
mix local.rebar --force
|
||||
|
||||
# Set build directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy mix files
|
||||
COPY mix.exs mix.lock ./
|
||||
COPY config config
|
||||
|
||||
# Install dependencies
|
||||
RUN mix deps.get --only $MIX_ENV
|
||||
RUN mix deps.compile
|
||||
|
||||
# Copy app files
|
||||
COPY priv priv
|
||||
COPY lib lib
|
||||
COPY assets assets
|
||||
|
||||
# Compile assets
|
||||
RUN mix assets.deploy
|
||||
|
||||
# Compile the release
|
||||
RUN mix compile
|
||||
|
||||
# Build release
|
||||
RUN mix release
|
||||
|
||||
# Runtime stage
|
||||
FROM alpine:3.18 AS runtime
|
||||
|
||||
# Install runtime dependencies
|
||||
RUN apk add --no-cache libstdc++ openssl ncurses-libs
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Create non-root user
|
||||
RUN addgroup -g 1000 -S aprs && \
|
||||
adduser -u 1000 -S aprs -G aprs
|
||||
|
||||
# Copy release from build stage
|
||||
COPY --from=build --chown=aprs:aprs /app/_build/prod/rel/aprs ./
|
||||
|
||||
# Add health check endpoint if not already present
|
||||
USER aprs
|
||||
|
||||
ENV HOME=/app
|
||||
ENV MIX_ENV=prod
|
||||
ENV PORT=4000
|
||||
ENV PHX_SERVER=true
|
||||
|
||||
EXPOSE 4000
|
||||
|
||||
CMD ["bin/aprs", "start"]
|
||||
```
|
||||
|
||||
### 2. Create GitHub Actions Workflow
|
||||
|
||||
Create `.github/workflows/deploy.yml` in your APRS repository:
|
||||
|
||||
```yaml
|
||||
name: Build and Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=sha,prefix={{branch}}-
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
deploy:
|
||||
needs: build-and-push
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup Tailscale
|
||||
uses: tailscale/github-action@v2
|
||||
with:
|
||||
oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
|
||||
oauth-secret: ${{ secrets.TS_OAUTH_SECRET }}
|
||||
tags: tag:ci
|
||||
|
||||
- name: Set kubeconfig
|
||||
run: |
|
||||
mkdir -p ~/.kube
|
||||
echo "${{ secrets.TAILSCALE_KUBECONFIG }}" | base64 -d > ~/.kube/config
|
||||
# Replace the K3s master IP with its Tailscale IP
|
||||
sed -i 's/server: https:\/\/[0-9.]*:6443/server: https:\/\/${{ secrets.K3S_TAILSCALE_IP }}:6443/g' ~/.kube/config
|
||||
|
||||
- name: Deploy to K3s
|
||||
run: |
|
||||
kubectl set image deployment/aprs aprs=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} -n aprs
|
||||
|
||||
- name: Wait for rollout
|
||||
run: |
|
||||
kubectl rollout status deployment/aprs -n aprs
|
||||
```
|
||||
|
||||
### 3. Configure Health Check Endpoint
|
||||
|
||||
Add a health check endpoint to your Phoenix application if not already present:
|
||||
|
||||
```elixir
|
||||
# In your router (lib/aprs_web/router.ex)
|
||||
scope "/", AprsWeb do
|
||||
pipe_through :browser
|
||||
|
||||
get "/health", HealthController, :index
|
||||
# ... other routes
|
||||
end
|
||||
```
|
||||
|
||||
Create the health controller:
|
||||
|
||||
```elixir
|
||||
# lib/aprs_web/controllers/health_controller.ex
|
||||
defmodule AprsWeb.HealthController do
|
||||
use AprsWeb, :controller
|
||||
|
||||
def index(conn, _params) do
|
||||
json(conn, %{status: "ok"})
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### 4. Update Production Configuration
|
||||
|
||||
Ensure your `config/runtime.exs` includes:
|
||||
|
||||
```elixir
|
||||
import Config
|
||||
|
||||
if config_env() == :prod do
|
||||
database_url =
|
||||
System.get_env("DATABASE_URL") ||
|
||||
raise """
|
||||
environment variable DATABASE_URL is missing.
|
||||
"""
|
||||
|
||||
config :aprs, Aprs.Repo,
|
||||
url: database_url,
|
||||
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
|
||||
socket_options: [:inet6]
|
||||
|
||||
secret_key_base =
|
||||
System.get_env("SECRET_KEY_BASE") ||
|
||||
raise """
|
||||
environment variable SECRET_KEY_BASE is missing.
|
||||
"""
|
||||
|
||||
host = System.get_env("PHX_HOST") || "example.com"
|
||||
port = String.to_integer(System.get_env("PORT") || "4000")
|
||||
|
||||
config :aprs, AprsWeb.Endpoint,
|
||||
url: [host: host, port: 443, scheme: "https"],
|
||||
http: [
|
||||
ip: {0, 0, 0, 0},
|
||||
port: port
|
||||
],
|
||||
secret_key_base: secret_key_base
|
||||
|
||||
config :aprs, AprsWeb.Endpoint, server: true
|
||||
end
|
||||
```
|
||||
|
||||
### 5. Setup Tailscale OAuth Client
|
||||
|
||||
1. Go to https://login.tailscale.com/admin/settings/oauth
|
||||
2. Create a new OAuth client with these settings:
|
||||
- Description: "GitHub Actions CI"
|
||||
- Tags: `tag:ci`
|
||||
- Capabilities: Read-only access
|
||||
3. Save the Client ID and Secret
|
||||
|
||||
### 6. Setup GitHub Secrets
|
||||
|
||||
In your APRS GitHub repository settings, add these secrets:
|
||||
|
||||
1. `TS_OAUTH_CLIENT_ID`: Your Tailscale OAuth client ID
|
||||
2. `TS_OAUTH_SECRET`: Your Tailscale OAuth client secret
|
||||
3. `K3S_TAILSCALE_IP`: The Tailscale IP of your K3s master node (get it with `tailscale ip -4` on the master)
|
||||
4. `TAILSCALE_KUBECONFIG`: Your K3s cluster kubeconfig file content (base64 encoded)
|
||||
```bash
|
||||
# On your K3s server:
|
||||
sudo cat /etc/rancher/k3s/k3s.yaml | base64 -w0
|
||||
```
|
||||
|
||||
### 7. Update Kubernetes Deployment Image
|
||||
|
||||
After your first GitHub Actions run, update the image in the Kubernetes deployment to use your actual GitHub username:
|
||||
|
||||
```bash
|
||||
# In the infra repository
|
||||
sed -i 's/YOUR_GITHUB_USERNAME/your-actual-username/g' clusters/aprs/aprs-deployment.yaml
|
||||
```
|
||||
|
||||
### 8. Generate Secrets
|
||||
|
||||
Before deploying to Kubernetes, generate proper secrets:
|
||||
|
||||
```bash
|
||||
# Generate a 64-character secret key base
|
||||
mix phx.gen.secret
|
||||
|
||||
# Generate a strong PostgreSQL password
|
||||
openssl rand -base64 32
|
||||
```
|
||||
|
||||
Update the `secrets.yaml` file with these values before applying to your cluster.
|
||||
|
||||
### 9. Install Tailscale on K3s Nodes
|
||||
|
||||
Before deploying, ensure Tailscale is installed on your K3s nodes:
|
||||
|
||||
```bash
|
||||
# From the ansible directory
|
||||
export TAILSCALE_KEY="your-tailscale-auth-key"
|
||||
ansible-playbook k3s-tailscale.yml
|
||||
```
|
||||
|
||||
### 10. Apply Initial Deployment
|
||||
|
||||
```bash
|
||||
# From the infra repository
|
||||
kubectl apply -k clusters/aprs/
|
||||
```
|
||||
|
||||
## SSL Proxy Configuration
|
||||
|
||||
Since the APRS service is only accessible from the 10.0.16.0/22 network, you'll need to set up an external SSL proxy. Here's an example nginx configuration:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name aprs.yourdomain.com;
|
||||
|
||||
ssl_certificate /path/to/cert.pem;
|
||||
ssl_certificate_key /path/to/key.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass http://10.0.16.x:4000; # Replace with actual service IP
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Monitoring and Troubleshooting
|
||||
|
||||
Check deployment status:
|
||||
```bash
|
||||
kubectl get all -n aprs
|
||||
kubectl logs -n aprs deployment/aprs
|
||||
kubectl logs -n aprs deployment/postgis
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- The PostGIS database will persist data in the PersistentVolume
|
||||
- The APRS application runs with 2 replicas for high availability
|
||||
- Resource limits are set conservatively; adjust based on your needs
|
||||
- Remember to backup your PostGIS data regularly
|
||||
|
|
@ -1,188 +0,0 @@
|
|||
# PostgreSQL Auto-Healing Configuration Guide
|
||||
|
||||
This guide explains the auto-healing mechanisms implemented to prevent and recover from PostgreSQL connection exhaustion issues.
|
||||
|
||||
## Overview
|
||||
|
||||
The auto-healing system consists of several components working together:
|
||||
|
||||
1. **Enhanced PostgreSQL Configuration**
|
||||
2. **Improved PgBouncer Connection Pooling**
|
||||
3. **Health Checks and Automatic Restarts**
|
||||
4. **Periodic Connection Cleanup**
|
||||
5. **Resource Limits and Scaling**
|
||||
|
||||
## Components
|
||||
|
||||
### 1. PostgreSQL Enhancements (`postgis-deployment-enhanced.yaml`)
|
||||
|
||||
- **Increased max_connections**: From 100 to 200
|
||||
- **Connection timeouts**:
|
||||
- `idle_in_transaction_session_timeout=30s`: Kills idle transactions after 30 seconds
|
||||
- `statement_timeout=300s`: Kills queries running longer than 5 minutes
|
||||
- **Readiness probe**: Fails if connection count exceeds 180 (90% of max)
|
||||
- **Increased resources**: 2Gi memory, 1 CPU
|
||||
|
||||
### 2. PgBouncer Enhancements (`pgbouncer-deployment-enhanced.yaml`)
|
||||
|
||||
- **Connection pooling settings**:
|
||||
- Pool mode: `transaction` (releases connections after each transaction)
|
||||
- Default pool size: 25 connections per database/user pair
|
||||
- Server idle timeout: 10 minutes
|
||||
- Query timeout: 5 minutes
|
||||
- **Retry logic**:
|
||||
- `server_login_retry=15`: Retries failed logins for 15 seconds
|
||||
- `server_round_robin=1`: Distributes connections evenly
|
||||
- **Enhanced logging**: Tracks connections, disconnections, and errors
|
||||
|
||||
### 3. APRS Application Enhancements (`aprs-statefulset-enhanced.yaml`)
|
||||
|
||||
- **Reduced connection pool**: From 10 to 5 connections per pod
|
||||
- **Connection timeouts**:
|
||||
- Pool timeout: 60 seconds
|
||||
- Connect timeout: 30 seconds
|
||||
- Idle timeout: 15 minutes
|
||||
- **Startup probe**: Allows up to 120 seconds for application startup
|
||||
- **Increased resources**: 1Gi memory, 1 CPU
|
||||
|
||||
### 4. Automatic Connection Cleanup (`postgres-cleanup-cronjob.yaml`)
|
||||
|
||||
Runs every 15 minutes to:
|
||||
- Kill idle connections older than 30 minutes
|
||||
- Kill idle-in-transaction connections older than 5 minutes
|
||||
- Log connection statistics
|
||||
- Monitor connection usage percentage
|
||||
|
||||
### 5. Pod Disruption Budgets (`postgis-pdb.yaml`)
|
||||
|
||||
Ensures at least 1 replica of PostgreSQL and PgBouncer remain available during:
|
||||
- Cluster upgrades
|
||||
- Node maintenance
|
||||
- Voluntary disruptions
|
||||
|
||||
## How Auto-Healing Works
|
||||
|
||||
### Connection Exhaustion Prevention
|
||||
|
||||
1. **Application-level**: Reduced pool size prevents each pod from using too many connections
|
||||
2. **PgBouncer-level**: Connection pooling multiplexes many client connections over fewer server connections
|
||||
3. **PostgreSQL-level**: Timeouts automatically close stale connections
|
||||
|
||||
### Automatic Recovery
|
||||
|
||||
1. **Health checks**: Pods restart automatically when:
|
||||
- PostgreSQL has too many connections (>180)
|
||||
- PgBouncer can't connect to PostgreSQL
|
||||
- Application can't connect to the database
|
||||
|
||||
2. **CronJob cleanup**: Every 15 minutes, forcefully closes:
|
||||
- Idle connections wasting resources
|
||||
- Stuck transactions blocking others
|
||||
|
||||
3. **Connection retry**: PgBouncer retries failed connections for 15 seconds before giving up
|
||||
|
||||
## Monitoring
|
||||
|
||||
Use the provided monitoring script to check system health:
|
||||
|
||||
```bash
|
||||
./monitor-postgres-health.sh
|
||||
```
|
||||
|
||||
This shows:
|
||||
- Current connection count and states
|
||||
- Connection usage percentage
|
||||
- Recent errors from logs
|
||||
- Last cleanup job execution
|
||||
|
||||
## Manual Intervention
|
||||
|
||||
If automatic healing fails, you can manually intervene:
|
||||
|
||||
```bash
|
||||
# Check connection details
|
||||
kubectl exec -it deployment/postgis -n aprs -- psql -U aprs -d aprs_prod -c "SELECT * FROM pg_stat_activity WHERE datname = 'aprs_prod';"
|
||||
|
||||
# Kill all connections (emergency)
|
||||
kubectl exec -it deployment/postgis -n aprs -- psql -U aprs -d aprs_prod -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'aprs_prod' AND pid <> pg_backend_pid();"
|
||||
|
||||
# Restart components
|
||||
kubectl rollout restart deployment/postgis -n aprs
|
||||
kubectl rollout restart deployment/pgbouncer -n aprs
|
||||
kubectl rollout restart statefulset/aprs -n aprs
|
||||
```
|
||||
|
||||
## Applying the Configuration
|
||||
|
||||
### ⚠️ WARNING: Data Persistence
|
||||
The enhanced deployments use different volume configurations. Applying them directly will create new volumes and lose existing data.
|
||||
|
||||
### Safe Application (Recommended)
|
||||
|
||||
For existing deployments with data:
|
||||
|
||||
```bash
|
||||
cd /Users/graham/dev/infra/clusters/aprs
|
||||
./apply-auto-healing-safe.sh
|
||||
```
|
||||
|
||||
This applies only non-destructive changes:
|
||||
- PodDisruptionBudgets
|
||||
- Cleanup CronJob
|
||||
- Creates patch files for gradual application
|
||||
|
||||
Then apply patches one at a time:
|
||||
```bash
|
||||
# PostgreSQL enhancements
|
||||
kubectl patch deployment postgis -n aprs --patch-file postgis-deployment-patch.yaml
|
||||
kubectl rollout status deployment/postgis -n aprs
|
||||
|
||||
# PgBouncer enhancements
|
||||
kubectl patch deployment pgbouncer -n aprs --patch-file pgbouncer-deployment-patch.yaml
|
||||
kubectl rollout status deployment/pgbouncer -n aprs
|
||||
|
||||
# APRS enhancements
|
||||
kubectl patch statefulset aprs -n aprs --patch-file aprs-statefulset-patch.yaml
|
||||
kubectl rollout status statefulset/aprs -n aprs
|
||||
```
|
||||
|
||||
### Fresh Installation
|
||||
|
||||
For new deployments without existing data:
|
||||
|
||||
```bash
|
||||
cd /Users/graham/dev/infra/clusters/aprs
|
||||
./apply-auto-healing.sh
|
||||
```
|
||||
|
||||
This script:
|
||||
1. Backs up current configurations
|
||||
2. Applies all enhanced configurations
|
||||
3. Waits for rollouts to complete
|
||||
4. Provides a summary of changes
|
||||
|
||||
## Tuning Parameters
|
||||
|
||||
You may need to adjust these based on your workload:
|
||||
|
||||
1. **PostgreSQL max_connections**: Currently 200, increase if needed
|
||||
2. **PgBouncer pool sizes**: Currently 25, adjust based on connection patterns
|
||||
3. **Application POOL_SIZE**: Currently 5, can be increased if connections are available
|
||||
4. **Cleanup intervals**: Currently 15 minutes, can be more or less frequent
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Common issues and solutions:
|
||||
|
||||
1. **"too many clients already"**: Cleanup job should fix this within 15 minutes
|
||||
2. **"server login has been failing"**: PgBouncer is in retry mode, will recover in 15 seconds
|
||||
3. **Pods in CrashLoopBackOff**: Check logs for specific errors
|
||||
4. **High memory usage**: Consider increasing resource limits
|
||||
|
||||
## Future Improvements
|
||||
|
||||
Consider implementing:
|
||||
1. Prometheus metrics for connection monitoring
|
||||
2. Alerting when connection usage exceeds 80%
|
||||
3. Horizontal pod autoscaling based on connection metrics
|
||||
4. Connection pooling at the application level using a library like `poolboy`
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "Applying auto-healing configurations safely..."
|
||||
|
||||
# Only apply configurations that don't affect data persistence
|
||||
echo "Applying PodDisruptionBudgets..."
|
||||
kubectl apply -f postgis-pdb.yaml
|
||||
|
||||
echo "Applying PostgreSQL cleanup CronJob..."
|
||||
kubectl apply -f postgres-cleanup-cronjob.yaml
|
||||
|
||||
echo "Creating enhanced configurations as patches (not applied automatically)..."
|
||||
|
||||
# Create patch files instead of replacing deployments
|
||||
cat > postgis-deployment-patch.yaml << 'EOF'
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: postgis
|
||||
namespace: aprs
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: postgis
|
||||
args:
|
||||
- postgres
|
||||
- -c
|
||||
- max_connections=200
|
||||
- -c
|
||||
- idle_in_transaction_session_timeout=30s
|
||||
- -c
|
||||
- statement_timeout=300s
|
||||
- -c
|
||||
- shared_buffers=256MB
|
||||
resources:
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "1000m"
|
||||
requests:
|
||||
memory: "1Gi"
|
||||
cpu: "500m"
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- pg_isready -U $POSTGRES_USER -d $POSTGRES_DB
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- pg_isready -U $POSTGRES_USER -d $POSTGRES_DB
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 2
|
||||
EOF
|
||||
|
||||
cat > pgbouncer-deployment-patch.yaml << 'EOF'
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: pgbouncer
|
||||
namespace: aprs
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: pgbouncer
|
||||
env:
|
||||
- name: PGBOUNCER_SERVER_IDLE_TIMEOUT
|
||||
value: "600"
|
||||
- name: PGBOUNCER_SERVER_LIFETIME
|
||||
value: "3600"
|
||||
- name: PGBOUNCER_QUERY_TIMEOUT
|
||||
value: "300"
|
||||
- name: PGBOUNCER_QUERY_WAIT_TIMEOUT
|
||||
value: "120"
|
||||
- name: PGBOUNCER_CLIENT_IDLE_TIMEOUT
|
||||
value: "300"
|
||||
- name: PGBOUNCER_CLIENT_LOGIN_TIMEOUT
|
||||
value: "60"
|
||||
- name: PGBOUNCER_SERVER_CONNECT_TIMEOUT
|
||||
value: "15"
|
||||
- name: PGBOUNCER_SERVER_LOGIN_RETRY
|
||||
value: "15"
|
||||
resources:
|
||||
requests:
|
||||
memory: "128Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "200m"
|
||||
EOF
|
||||
|
||||
cat > aprs-statefulset-patch.yaml << 'EOF'
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: aprs
|
||||
namespace: aprs
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: aprs
|
||||
env:
|
||||
- name: POOL_SIZE
|
||||
value: "5"
|
||||
- name: DATABASE_POOL_TIMEOUT
|
||||
value: "60000"
|
||||
- name: DATABASE_CONNECT_TIMEOUT
|
||||
value: "30000"
|
||||
resources:
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "1000m"
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 4000
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 12
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
echo "Safe auto-healing configurations applied!"
|
||||
echo ""
|
||||
echo "To apply the enhanced configurations gradually:"
|
||||
echo "1. Apply PostgreSQL patch: kubectl patch deployment postgis -n aprs --patch-file postgis-deployment-patch.yaml"
|
||||
echo "2. Wait for rollout: kubectl rollout status deployment/postgis -n aprs"
|
||||
echo "3. Apply PgBouncer patch: kubectl patch deployment pgbouncer -n aprs --patch-file pgbouncer-deployment-patch.yaml"
|
||||
echo "4. Wait for rollout: kubectl rollout status deployment/pgbouncer -n aprs"
|
||||
echo "5. Apply APRS patch: kubectl patch statefulset aprs -n aprs --patch-file aprs-statefulset-patch.yaml"
|
||||
echo ""
|
||||
echo "Monitor with: ./monitor-postgres-health.sh"
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "Applying PostgreSQL and PgBouncer auto-healing configurations..."
|
||||
|
||||
# Backup current deployments
|
||||
echo "Creating backups of current deployments..."
|
||||
kubectl get deployment postgis -n aprs -o yaml > postgis-deployment-backup.yaml || true
|
||||
kubectl get deployment pgbouncer -n aprs -o yaml > pgbouncer-deployment-backup.yaml || true
|
||||
kubectl get statefulset aprs -n aprs -o yaml > aprs-statefulset-backup.yaml || true
|
||||
|
||||
# Apply enhanced configurations
|
||||
echo "Applying enhanced PostgreSQL deployment..."
|
||||
kubectl apply -f postgis-deployment-enhanced.yaml
|
||||
|
||||
echo "Applying enhanced PgBouncer deployment..."
|
||||
kubectl apply -f pgbouncer-deployment-enhanced.yaml
|
||||
|
||||
echo "Applying PodDisruptionBudgets..."
|
||||
kubectl apply -f postgis-pdb.yaml
|
||||
|
||||
echo "Applying enhanced APRS StatefulSet..."
|
||||
kubectl apply -f aprs-statefulset-enhanced.yaml
|
||||
|
||||
echo "Applying PostgreSQL cleanup CronJob..."
|
||||
kubectl apply -f postgres-cleanup-cronjob.yaml
|
||||
|
||||
# Wait for rollouts
|
||||
echo "Waiting for PostgreSQL rollout..."
|
||||
kubectl rollout status deployment/postgis -n aprs --timeout=300s
|
||||
|
||||
echo "Waiting for PgBouncer rollout..."
|
||||
kubectl rollout status deployment/pgbouncer -n aprs --timeout=300s
|
||||
|
||||
echo "Waiting for APRS rollout..."
|
||||
kubectl rollout status statefulset/aprs -n aprs --timeout=300s
|
||||
|
||||
echo "Auto-healing configurations applied successfully!"
|
||||
echo ""
|
||||
echo "Summary of changes:"
|
||||
echo "1. PostgreSQL: Increased max_connections to 200, added connection timeouts"
|
||||
echo "2. PgBouncer: Enhanced retry logic and connection pooling"
|
||||
echo "3. APRS pods: Reduced pool size to 5, added startup probes"
|
||||
echo "4. Added health checks that restart pods when connections are exhausted"
|
||||
echo "5. CronJob runs every 15 minutes to clean up stale connections"
|
||||
echo ""
|
||||
echo "Monitor the deployment with:"
|
||||
echo " kubectl get pods -n aprs -w"
|
||||
echo ""
|
||||
echo "Check PostgreSQL connections with:"
|
||||
echo " kubectl exec -it deployment/postgis -n aprs -- psql -U aprs -d aprs_prod -c 'SELECT state, count(*) FROM pg_stat_activity GROUP BY state;'"
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
#!/bin/bash
|
||||
# Run these commands on your Proxmox node (e.g., lab02)
|
||||
|
||||
# Create a Ceph pool for Kubernetes with 128 placement groups
|
||||
echo "Creating Kubernetes pool..."
|
||||
ceph osd pool create kubernetes 128
|
||||
ceph osd pool application enable kubernetes rbd
|
||||
|
||||
# Create a Ceph user for Kubernetes with appropriate permissions
|
||||
echo "Creating Kubernetes user..."
|
||||
ceph auth get-or-create client.kubernetes \
|
||||
mon 'profile rbd' \
|
||||
osd 'profile rbd pool=kubernetes' \
|
||||
mgr 'profile rbd pool=kubernetes'
|
||||
|
||||
# Display the key for configuration
|
||||
echo -e "\n\nCopy the key value below for your configuration:"
|
||||
ceph auth get client.kubernetes | grep key
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
# Setting up Ceph Storage for PostgreSQL in K3s
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before applying these manifests, you need to gather information from your Proxmox Ceph cluster:
|
||||
|
||||
1. **Ceph Monitor IPs**: Get the monitor IPs from Proxmox
|
||||
```bash
|
||||
# On Proxmox node:
|
||||
ceph mon dump
|
||||
```
|
||||
|
||||
2. **Ceph Cluster ID**: Get your Ceph cluster ID
|
||||
```bash
|
||||
# On Proxmox node:
|
||||
ceph fsid
|
||||
```
|
||||
|
||||
3. **Create a Ceph pool for Kubernetes** (if not already exists):
|
||||
```bash
|
||||
# On Proxmox node:
|
||||
ceph osd pool create kubernetes 128
|
||||
ceph osd pool application enable kubernetes rbd
|
||||
```
|
||||
|
||||
4. **Create a Ceph user for Kubernetes**:
|
||||
```bash
|
||||
# On Proxmox node:
|
||||
ceph auth get-or-create client.kubernetes mon 'profile rbd' osd 'profile rbd pool=kubernetes' mgr 'profile rbd pool=kubernetes'
|
||||
```
|
||||
|
||||
## Configuration Steps
|
||||
|
||||
1. **Update the Ceph configuration** in `ceph-csi/ceph-config.yaml`:
|
||||
- Replace `mon_host` with your actual Ceph monitor IPs
|
||||
- Replace the `userKey` with the key from step 4 above
|
||||
|
||||
2. **Update the StorageClass** in `ceph-csi/ceph-storageclass.yaml`:
|
||||
- Replace `clusterID` with your Ceph cluster ID from step 2
|
||||
- Ensure the `pool` name matches what you created in step 3
|
||||
|
||||
3. **Apply the Ceph CSI driver**:
|
||||
```bash
|
||||
kubectl apply -k ceph-csi/
|
||||
```
|
||||
|
||||
4. **Verify the installation**:
|
||||
```bash
|
||||
# Check if CSI pods are running
|
||||
kubectl get pods -n ceph-csi
|
||||
|
||||
# Check if StorageClass is created
|
||||
kubectl get storageclass ceph-rbd
|
||||
```
|
||||
|
||||
## Migrating PostgreSQL to Ceph Storage
|
||||
|
||||
1. **Backup existing data** (if any):
|
||||
```bash
|
||||
kubectl exec -n aprs deployment/postgis -- pg_dump -U aprs aprs_prod > backup.sql
|
||||
```
|
||||
|
||||
2. **Scale down the current deployment**:
|
||||
```bash
|
||||
kubectl scale deployment postgis -n aprs --replicas=0
|
||||
```
|
||||
|
||||
3. **Create the new PVC with Ceph**:
|
||||
```bash
|
||||
kubectl apply -f postgis-pvc-ceph.yaml
|
||||
```
|
||||
|
||||
4. **Update the deployment to use Ceph PVC**:
|
||||
```bash
|
||||
kubectl apply -f postgis-deployment-ceph.yaml
|
||||
```
|
||||
|
||||
5. **Restore data** (if needed):
|
||||
```bash
|
||||
kubectl exec -i -n aprs deployment/postgis -- psql -U aprs aprs_prod < backup.sql
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If pods fail to mount Ceph volumes:
|
||||
|
||||
1. Check CSI driver logs:
|
||||
```bash
|
||||
kubectl logs -n ceph-csi deployment/csi-rbdplugin-provisioner
|
||||
kubectl logs -n ceph-csi daemonset/csi-rbdplugin
|
||||
```
|
||||
|
||||
2. Verify Ceph connectivity from K3s nodes:
|
||||
```bash
|
||||
# Install ceph-common on K3s nodes
|
||||
apt-get install ceph-common
|
||||
|
||||
# Test connection
|
||||
ceph -c /etc/ceph/ceph.conf -n client.kubernetes --keyring /etc/ceph/keyring status
|
||||
```
|
||||
|
||||
3. Check PVC events:
|
||||
```bash
|
||||
kubectl describe pvc postgis-pvc-ceph -n aprs
|
||||
```
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# This script creates a Kubernetes secret for pulling images from ghcr.io
|
||||
|
||||
echo "Enter your GitHub username:"
|
||||
read GITHUB_USERNAME
|
||||
|
||||
echo "Enter your GitHub Personal Access Token (with read:packages scope):"
|
||||
read -s GITHUB_TOKEN
|
||||
|
||||
# Create the secret
|
||||
kubectl create secret docker-registry ghcr-pull-secret \
|
||||
--docker-server=ghcr.io \
|
||||
--docker-username="$GITHUB_USERNAME" \
|
||||
--docker-password="$GITHUB_TOKEN" \
|
||||
--namespace=aprs \
|
||||
--dry-run=client -o yaml > ghcr-pull-secret.yaml
|
||||
|
||||
echo "Secret YAML created in ghcr-pull-secret.yaml"
|
||||
echo "Apply it with: kubectl apply -f ghcr-pull-secret.yaml"
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Generate Kubernetes secrets from 1Password
|
||||
|
||||
# Ensure we're logged in to 1Password
|
||||
if ! op whoami &>/dev/null; then
|
||||
echo "Please log in to 1Password first: eval \$(op signin)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get PostgreSQL password
|
||||
POSTGRES_PASSWORD=$(op item get "APRS PostgreSQL Password" --fields password)
|
||||
|
||||
# Get APRS application secrets
|
||||
DATABASE_URL=$(op item get "APRS Application Secrets" --fields database.url)
|
||||
DATABASE_URL_PGBOUNCER=$(op item get "APRS Application Secrets" --fields database.url.pgbouncer)
|
||||
DATABASE_PASSWORD=$(op item get "APRS Application Secrets" --fields password)
|
||||
SECRET_KEY_BASE=$(op item get "APRS Application Secrets" --fields secret.key.base)
|
||||
ERLANG_COOKIE=$(op item get "APRS Application Secrets" --fields erlang.cookie)
|
||||
|
||||
# Get GitHub Container Registry credentials
|
||||
GHCR_USERNAME=$(op item get "GitHub Container Registry" --fields username)
|
||||
GHCR_PASSWORD=$(op item get "GitHub Container Registry" --fields password)
|
||||
GHCR_AUTH=$(echo -n "${GHCR_USERNAME}:${GHCR_PASSWORD}" | base64)
|
||||
|
||||
# Create PostgreSQL secret
|
||||
kubectl create secret generic postgis-secret \
|
||||
--namespace=aprs \
|
||||
--from-literal=postgres-password="${POSTGRES_PASSWORD}" \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
# Create APRS application secret
|
||||
kubectl create secret generic aprs-secret \
|
||||
--namespace=aprs \
|
||||
--from-literal=database-url="${DATABASE_URL}" \
|
||||
--from-literal=database-url-pgbouncer="${DATABASE_URL_PGBOUNCER}" \
|
||||
--from-literal=database-password="${DATABASE_PASSWORD}" \
|
||||
--from-literal=secret-key-base="${SECRET_KEY_BASE}" \
|
||||
--from-literal=erlang-cookie="${ERLANG_COOKIE}" \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
# Create Docker registry secret
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: ghcr-pull-secret
|
||||
namespace: aprs
|
||||
type: kubernetes.io/dockerconfigjson
|
||||
stringData:
|
||||
.dockerconfigjson: |
|
||||
{
|
||||
"auths": {
|
||||
"ghcr.io": {
|
||||
"username": "${GHCR_USERNAME}",
|
||||
"password": "${GHCR_PASSWORD}",
|
||||
"auth": "${GHCR_AUTH}"
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "Secrets successfully created/updated from 1Password"
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: aprs-migrate
|
||||
namespace: aprs
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
imagePullSecrets:
|
||||
- name: ghcr-pull-secret
|
||||
containers:
|
||||
- name: migrate
|
||||
image: ghcr.io/aprsme/aprs.me:latest
|
||||
env:
|
||||
- name: DATABASE_URL
|
||||
value: "ecto://aprsme:rku23IEO9Xq1lZTl%2BDETeAGRn%2FVxdzXzuidIrHn5Gi4%3D@10.0.19.220:5432/aprsme_prod"
|
||||
- name: SECRET_KEY_BASE
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: aprs-secret
|
||||
key: secret-key-base
|
||||
- name: MIX_ENV
|
||||
value: "prod"
|
||||
- name: POOL_SIZE
|
||||
value: "2"
|
||||
- name: PHX_HOST
|
||||
value: "aprs.me"
|
||||
- name: PORT
|
||||
value: "4000"
|
||||
- name: SKIP_CLUSTERING
|
||||
value: "true"
|
||||
- name: CLUSTER_ENABLED
|
||||
value: "false"
|
||||
- name: REDIS_URL
|
||||
value: "redis://redis.aprs.svc.cluster.local:6379"
|
||||
command: ["/app/bin/migrate"]
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "Migrating APRS to external PostgreSQL database"
|
||||
echo "=============================================="
|
||||
echo ""
|
||||
|
||||
# Test connection to external database first
|
||||
echo "Testing connection to external PostgreSQL..."
|
||||
PGPASSWORD="mjBipckSZfNOoSOWLMRsfZEqJ9I5Of21" psql -h 10.0.19.220 -p 6432 -U aprsme -d aprsme_prod -c "SELECT version();" > /dev/null 2>&1
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ Successfully connected to external database"
|
||||
else
|
||||
echo "❌ Failed to connect to external database at 10.0.19.220:6432"
|
||||
echo "Please ensure the PostgreSQL server is running and accessible"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Step 1: Updating database connection secret..."
|
||||
kubectl apply -f aprs-secret-external-db.yaml
|
||||
|
||||
echo ""
|
||||
echo "Step 2: Restarting APRS pods to use new database..."
|
||||
kubectl rollout restart statefulset/aprs -n aprs
|
||||
kubectl rollout status statefulset/aprs -n aprs --timeout=300s
|
||||
|
||||
echo ""
|
||||
echo "Step 3: Verifying pods are running with new database..."
|
||||
sleep 10
|
||||
kubectl get pods -n aprs -l app=aprs
|
||||
|
||||
echo ""
|
||||
echo "Step 4: Checking application health..."
|
||||
for i in {1..5}; do
|
||||
POD=$(kubectl get pods -n aprs -l app=aprs -o jsonpath='{.items[0].metadata.name}')
|
||||
if kubectl exec -n aprs $POD -- curl -s http://localhost:4000/health > /dev/null; then
|
||||
echo "✅ Application is healthy"
|
||||
break
|
||||
else
|
||||
echo "Waiting for application to be ready... ($i/5)"
|
||||
sleep 10
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "Step 5: Removing local PostgreSQL resources..."
|
||||
read -p "Are you sure you want to remove the local PostgreSQL deployment? (y/n): " confirm
|
||||
if [ "$confirm" = "y" ]; then
|
||||
echo "Removing PostgreSQL resources..."
|
||||
kubectl delete deployment postgis -n aprs --ignore-not-found=true
|
||||
kubectl delete service postgis -n aprs --ignore-not-found=true
|
||||
kubectl delete deployment pgbouncer -n aprs --ignore-not-found=true
|
||||
kubectl delete service pgbouncer -n aprs --ignore-not-found=true
|
||||
kubectl delete cronjob postgres-cleanup -n aprs --ignore-not-found=true
|
||||
kubectl delete job -l app=postgres-cleanup -n aprs --ignore-not-found=true
|
||||
|
||||
echo ""
|
||||
echo "PostgreSQL PVC will be retained for backup purposes"
|
||||
echo "To delete it later: kubectl delete pvc postgis-pvc-ceph -n aprs"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Migration complete!"
|
||||
echo ""
|
||||
echo "Summary:"
|
||||
echo "- APRS is now using external PostgreSQL at 10.0.19.220"
|
||||
echo "- Connection is via PgBouncer on port 6432"
|
||||
echo "- Local PostgreSQL resources have been removed"
|
||||
echo ""
|
||||
echo "To verify:"
|
||||
echo "kubectl logs -n aprs -l app=aprs --tail=50"
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
echo "=== PostgreSQL Connection Health Check ==="
|
||||
echo ""
|
||||
|
||||
# Check pod status
|
||||
echo "Pod Status:"
|
||||
kubectl get pods -n aprs | grep -E "(NAME|postgis|pgbouncer|aprs-)"
|
||||
echo ""
|
||||
|
||||
# Check PostgreSQL connections
|
||||
echo "PostgreSQL Connection Stats:"
|
||||
kubectl exec deployment/postgis -n aprs -- psql -U aprs -d aprs_prod -c "
|
||||
SELECT
|
||||
state,
|
||||
COUNT(*) as connections,
|
||||
ROUND(AVG(EXTRACT(EPOCH FROM (NOW() - state_change))), 2) as avg_duration_seconds
|
||||
FROM pg_stat_activity
|
||||
WHERE datname = 'aprs_prod'
|
||||
GROUP BY state
|
||||
ORDER BY connections DESC;" 2>/dev/null || echo "Failed to query PostgreSQL"
|
||||
echo ""
|
||||
|
||||
# Check connection percentage
|
||||
echo "Connection Usage:"
|
||||
kubectl exec deployment/postgis -n aprs -- psql -U aprs -d aprs_prod -c "
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM pg_stat_activity) as current,
|
||||
(SELECT setting::int FROM pg_settings WHERE name = 'max_connections') as max,
|
||||
ROUND(100.0 * COUNT(*) / (SELECT setting::int FROM pg_settings WHERE name = 'max_connections'), 2) || '%' as usage
|
||||
FROM pg_stat_activity;" 2>/dev/null || echo "Failed to query PostgreSQL"
|
||||
echo ""
|
||||
|
||||
# Check PgBouncer logs for errors
|
||||
echo "Recent PgBouncer Errors (last 10):"
|
||||
kubectl logs -n aprs -l app=pgbouncer --tail=50 | grep -i "error\|warning" | tail -10
|
||||
echo ""
|
||||
|
||||
# Check APRS pod logs for connection errors
|
||||
echo "Recent APRS Connection Errors (last 10):"
|
||||
kubectl logs -n aprs -l app=aprs --tail=100 | grep -i "postgrex\|connection\|protocol_violation" | tail -10
|
||||
echo ""
|
||||
|
||||
# Show last cleanup job run
|
||||
echo "Last Cleanup Job Run:"
|
||||
kubectl get jobs -n aprs | grep postgres-cleanup | tail -1
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: redis
|
||||
namespace: aprs
|
||||
labels:
|
||||
app: redis
|
||||
spec:
|
||||
ports:
|
||||
- port: 6379
|
||||
targetPort: 6379
|
||||
name: redis
|
||||
- port: 9121
|
||||
targetPort: 9121
|
||||
name: metrics
|
||||
selector:
|
||||
app: redis
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: redis
|
||||
namespace: aprs
|
||||
labels:
|
||||
app: redis
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: redis
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: redis
|
||||
annotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "9121"
|
||||
spec:
|
||||
containers:
|
||||
- name: redis
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- containerPort: 6379
|
||||
name: redis
|
||||
command:
|
||||
- redis-server
|
||||
- --maxmemory
|
||||
- "256mb"
|
||||
- --maxmemory-policy
|
||||
- allkeys-lru
|
||||
- --save
|
||||
- ""
|
||||
- --appendonly
|
||||
- "no"
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: 6379
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- redis-cli
|
||||
- ping
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
|
||||
- name: redis-exporter
|
||||
image: oliver006/redis_exporter:v1.55.0
|
||||
ports:
|
||||
- containerPort: 9121
|
||||
name: metrics
|
||||
env:
|
||||
- name: REDIS_ADDR
|
||||
value: "redis://localhost:6379"
|
||||
resources:
|
||||
requests:
|
||||
memory: 32Mi
|
||||
cpu: 10m
|
||||
limits:
|
||||
memory: 64Mi
|
||||
cpu: 50m
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /metrics
|
||||
port: 9121
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
# Setting up GitHub Container Registry Authentication
|
||||
|
||||
## Step 1: Create GitHub Personal Access Token
|
||||
|
||||
1. Go to https://github.com/settings/tokens/new
|
||||
2. Name: "k3s-ghcr-pull" (or any name you prefer)
|
||||
3. Expiration: Choose based on your security requirements
|
||||
4. Scopes: Select `read:packages`
|
||||
5. Click "Generate token"
|
||||
6. Copy the token (starts with `ghp_`)
|
||||
|
||||
## Step 2: Create Kubernetes Secret
|
||||
|
||||
Replace `YOUR_GITHUB_USERNAME` and `YOUR_GITHUB_PAT` in the command below:
|
||||
|
||||
```bash
|
||||
kubectl create secret docker-registry ghcr-pull-secret \
|
||||
--docker-server=ghcr.io \
|
||||
--docker-username=YOUR_GITHUB_USERNAME \
|
||||
--docker-password=YOUR_GITHUB_PAT \
|
||||
--namespace=aprs
|
||||
```
|
||||
|
||||
## Step 3: Verify Secret
|
||||
|
||||
```bash
|
||||
kubectl get secret ghcr-pull-secret -n aprs
|
||||
```
|
||||
|
||||
## Step 4: The deployment will be automatically updated to use this secret
|
||||
|
||||
The aprs-deployment.yaml already includes the imagePullSecrets configuration.
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Create 1Password items for APRS secrets
|
||||
|
||||
# PostgreSQL password
|
||||
op item create \
|
||||
--category=password \
|
||||
--title="APRS PostgreSQL Password" \
|
||||
--vault="Private" \
|
||||
password="mjBipckSZfNOoSOWLMRsfZEqJ9I5Of21"
|
||||
|
||||
# APRS application secrets
|
||||
op item create \
|
||||
--category=login \
|
||||
--title="APRS Application Secrets" \
|
||||
--vault="Private" \
|
||||
username="aprs" \
|
||||
password="mjBipckSZfNOoSOWLMRsfZEqJ9I5Of21" \
|
||||
database.url="ecto://aprs:mjBipckSZfNOoSOWLMRsfZEqJ9I5Of21@postgis:5432/aprs" \
|
||||
database.url.pgbouncer="ecto://aprs:mjBipckSZfNOoSOWLMRsfZEqJ9I5Of21@pgbouncer:5432/aprs" \
|
||||
secret.key.base="GJxzOUW3YXBAF7xB4mH5VcXY6BN1NYLnoJYadlZyiCxcRUGBw65w4Sco7+sNiW2t" \
|
||||
erlang.cookie="aprs-cluster-cookie-ksdf8934jfklsdjf983jfklsdf"
|
||||
|
||||
# GitHub Container Registry credentials
|
||||
op item create \
|
||||
--category=login \
|
||||
--title="GitHub Container Registry" \
|
||||
--vault="Private" \
|
||||
username="gmcintire" \
|
||||
password="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" \
|
||||
url="ghcr.io"
|
||||
|
|
@ -1,296 +0,0 @@
|
|||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: connectors.tailscale.com
|
||||
spec:
|
||||
group: tailscale.com
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
storage: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
properties:
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
hostname:
|
||||
type: string
|
||||
proxyClass:
|
||||
type: string
|
||||
exitNode:
|
||||
type: boolean
|
||||
subnetRouter:
|
||||
type: object
|
||||
properties:
|
||||
routes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
status:
|
||||
type: object
|
||||
properties:
|
||||
conditions:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
reason:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
lastTransitionTime:
|
||||
type: string
|
||||
subnetRouter:
|
||||
type: object
|
||||
properties:
|
||||
routes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
isExitNode:
|
||||
type: boolean
|
||||
subresources:
|
||||
status: {}
|
||||
scope: Cluster
|
||||
names:
|
||||
plural: connectors
|
||||
singular: connector
|
||||
kind: Connector
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: proxyclasses.tailscale.com
|
||||
spec:
|
||||
group: tailscale.com
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
storage: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
properties:
|
||||
metrics:
|
||||
type: object
|
||||
properties:
|
||||
enable:
|
||||
type: boolean
|
||||
statefulSet:
|
||||
type: object
|
||||
properties:
|
||||
labels:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
annotations:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
pod:
|
||||
type: object
|
||||
properties:
|
||||
labels:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
annotations:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
tailscaleContainer:
|
||||
type: object
|
||||
properties:
|
||||
env:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
value:
|
||||
type: string
|
||||
imagePullPolicy:
|
||||
type: string
|
||||
enum: ["Always", "Never", "IfNotPresent"]
|
||||
resources:
|
||||
type: object
|
||||
properties:
|
||||
requests:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
limits:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
securityContext:
|
||||
type: object
|
||||
properties:
|
||||
privileged:
|
||||
type: boolean
|
||||
allowPrivilegeEscalation:
|
||||
type: boolean
|
||||
readOnlyRootFilesystem:
|
||||
type: boolean
|
||||
runAsNonRoot:
|
||||
type: boolean
|
||||
runAsUser:
|
||||
type: integer
|
||||
runAsGroup:
|
||||
type: integer
|
||||
capabilities:
|
||||
type: object
|
||||
properties:
|
||||
add:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
drop:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
tailscaleInitContainer:
|
||||
type: object
|
||||
properties:
|
||||
env:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
value:
|
||||
type: string
|
||||
imagePullPolicy:
|
||||
type: string
|
||||
enum: ["Always", "Never", "IfNotPresent"]
|
||||
resources:
|
||||
type: object
|
||||
properties:
|
||||
requests:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
limits:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
securityContext:
|
||||
type: object
|
||||
properties:
|
||||
privileged:
|
||||
type: boolean
|
||||
allowPrivilegeEscalation:
|
||||
type: boolean
|
||||
readOnlyRootFilesystem:
|
||||
type: boolean
|
||||
runAsNonRoot:
|
||||
type: boolean
|
||||
runAsUser:
|
||||
type: integer
|
||||
runAsGroup:
|
||||
type: integer
|
||||
capabilities:
|
||||
type: object
|
||||
properties:
|
||||
add:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
drop:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
imagePullSecrets:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
nodeName:
|
||||
type: string
|
||||
nodeSelector:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
affinity:
|
||||
type: object
|
||||
tolerations:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
operator:
|
||||
type: string
|
||||
value:
|
||||
type: string
|
||||
effect:
|
||||
type: string
|
||||
tolerationSeconds:
|
||||
type: integer
|
||||
hostNetwork:
|
||||
type: boolean
|
||||
serviceAccountName:
|
||||
type: string
|
||||
securityContext:
|
||||
type: object
|
||||
properties:
|
||||
runAsNonRoot:
|
||||
type: boolean
|
||||
runAsUser:
|
||||
type: integer
|
||||
runAsGroup:
|
||||
type: integer
|
||||
fsGroup:
|
||||
type: integer
|
||||
service:
|
||||
type: object
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
enum: ["ClusterIP", "LoadBalancer", "NodePort"]
|
||||
status:
|
||||
type: object
|
||||
properties:
|
||||
conditions:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
reason:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
observedGeneration:
|
||||
type: integer
|
||||
lastTransitionTime:
|
||||
type: string
|
||||
subresources:
|
||||
status: {}
|
||||
scope: Cluster
|
||||
names:
|
||||
plural: proxyclasses
|
||||
singular: proxyclass
|
||||
kind: ProxyClass
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
# IMPORTANT: Create your actual secret file as 03-auth-secret.yaml
|
||||
# Do not commit the actual secret to git!
|
||||
#
|
||||
# To create a Tailscale auth key:
|
||||
# 1. Go to https://login.tailscale.com/admin/settings/keys
|
||||
# 2. Generate an auth key (reusable, with appropriate expiry)
|
||||
# 3. Create the secret:
|
||||
#
|
||||
# kubectl create secret generic operator-oauth \
|
||||
# --namespace=tailscale \
|
||||
# --from-literal=client_id="<your-oauth-client-id>" \
|
||||
# --from-literal=client_secret="<your-oauth-client-secret>"
|
||||
#
|
||||
# OR for auth key:
|
||||
#
|
||||
# kubectl create secret generic operator \
|
||||
# --namespace=tailscale \
|
||||
# --from-literal=authkey="<your-auth-key>"
|
||||
#
|
||||
# Example YAML format:
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: operator
|
||||
namespace: tailscale
|
||||
type: Opaque
|
||||
stringData:
|
||||
authkey: "tskey-auth-YOUR-KEY-HERE"
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
# Example: Expose a service via Tailscale proxy
|
||||
# This creates a Tailscale device that forwards traffic to a Kubernetes service
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: grafana-tailscale
|
||||
namespace: monitoring
|
||||
annotations:
|
||||
tailscale.com/expose: "true"
|
||||
tailscale.com/hostname: "aprs-grafana"
|
||||
tailscale.com/tags: "tag:k8s,tag:monitoring"
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app.kubernetes.io/name: grafana
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 3000
|
||||
protocol: TCP
|
||||
---
|
||||
# Alternative: Using Ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: grafana-tailscale
|
||||
namespace: monitoring
|
||||
annotations:
|
||||
tailscale.com/ingress-class: "tailscale"
|
||||
spec:
|
||||
ingressClassName: tailscale
|
||||
rules:
|
||||
- host: grafana
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: grafana
|
||||
port:
|
||||
number: 80
|
||||
tls:
|
||||
- hosts:
|
||||
- grafana
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
# Tailscale Operator for K3s
|
||||
|
||||
This directory contains the Tailscale operator configuration for the k3s cluster.
|
||||
|
||||
## Setup Instructions
|
||||
|
||||
1. **Apply the base configuration:**
|
||||
```bash
|
||||
kubectl apply -f 00-namespace.yaml
|
||||
kubectl apply -f 01-rbac.yaml
|
||||
kubectl apply -f 02-crds.yaml
|
||||
```
|
||||
|
||||
2. **Create the authentication secret:**
|
||||
|
||||
Option A - Using OAuth (recommended for production):
|
||||
```bash
|
||||
# Get OAuth credentials from https://login.tailscale.com/admin/settings/oauth
|
||||
kubectl create secret generic operator-oauth \
|
||||
--namespace=tailscale \
|
||||
--from-literal=client_id="<your-oauth-client-id>" \
|
||||
--from-literal=client_secret="<your-oauth-client-secret>"
|
||||
```
|
||||
|
||||
Option B - Using auth key (simpler, good for testing):
|
||||
```bash
|
||||
# Get auth key from https://login.tailscale.com/admin/settings/keys
|
||||
kubectl create secret generic operator \
|
||||
--namespace=tailscale \
|
||||
--from-literal=authkey="<your-auth-key>"
|
||||
```
|
||||
|
||||
3. **Deploy the operator:**
|
||||
```bash
|
||||
kubectl apply -f 04-operator.yaml
|
||||
```
|
||||
|
||||
4. **Verify the operator is running:**
|
||||
```bash
|
||||
kubectl get pods -n tailscale
|
||||
kubectl logs -n tailscale deployment/operator
|
||||
```
|
||||
|
||||
## Exposing Services
|
||||
|
||||
There are several ways to expose services via Tailscale:
|
||||
|
||||
### Method 1: Service Annotations
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: my-service
|
||||
annotations:
|
||||
tailscale.com/expose: "true"
|
||||
tailscale.com/hostname: "my-app"
|
||||
tailscale.com/tags: "tag:k8s"
|
||||
```
|
||||
|
||||
### Method 2: Ingress
|
||||
```yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: my-ingress
|
||||
spec:
|
||||
ingressClassName: tailscale
|
||||
rules:
|
||||
- host: my-app
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: my-service
|
||||
port:
|
||||
number: 80
|
||||
```
|
||||
|
||||
### Method 3: Connector (Subnet Router/Exit Node)
|
||||
```yaml
|
||||
apiVersion: tailscale.com/v1alpha1
|
||||
kind: Connector
|
||||
metadata:
|
||||
name: subnet-router
|
||||
spec:
|
||||
hostname: k3s-subnet
|
||||
exitNode: false
|
||||
subnetRouter:
|
||||
routes:
|
||||
- "10.0.19.0/24" # Your cluster subnet
|
||||
tags:
|
||||
- "tag:k8s"
|
||||
- "tag:subnet-router"
|
||||
```
|
||||
|
||||
## Accessing the API Server
|
||||
|
||||
The operator can expose the Kubernetes API server on your tailnet:
|
||||
```bash
|
||||
# This is enabled by default with APISERVER_PROXY=true
|
||||
# Access via: https://tailscale-operator.tailnet-name.ts.net:443
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
1. Check operator logs:
|
||||
```bash
|
||||
kubectl logs -n tailscale deployment/operator
|
||||
```
|
||||
|
||||
2. Check proxy pods:
|
||||
```bash
|
||||
kubectl get pods -A | grep ts-
|
||||
```
|
||||
|
||||
3. Verify CRDs are installed:
|
||||
```bash
|
||||
kubectl get crds | grep tailscale
|
||||
```
|
||||
|
||||
## Security Notes
|
||||
|
||||
- Never commit auth keys or OAuth credentials to git
|
||||
- Use appropriate ACL tags in your Tailscale admin panel
|
||||
- Consider using OAuth for production deployments
|
||||
- Auth keys should be reusable and have appropriate expiry times
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: grafana-tailscale
|
||||
namespace: monitoring
|
||||
annotations:
|
||||
tailscale.com/expose: "true"
|
||||
tailscale.com/hostname: "aprs-grafana"
|
||||
tailscale.com/tags: "tag:k8s"
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app.kubernetes.io/name: grafana
|
||||
app.kubernetes.io/part-of: kube-prometheus
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 3000
|
||||
protocol: TCP
|
||||
name: http
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "Tailscale Operator Setup for K3s"
|
||||
echo "================================"
|
||||
echo ""
|
||||
|
||||
# Check if kubectl is available
|
||||
if ! command -v kubectl &> /dev/null; then
|
||||
echo "Error: kubectl is not installed or not in PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if we can connect to the cluster
|
||||
if ! kubectl cluster-info &> /dev/null; then
|
||||
echo "Error: Cannot connect to Kubernetes cluster"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Step 1: Applying base configuration..."
|
||||
kubectl apply -f 00-namespace.yaml
|
||||
kubectl apply -f 01-rbac.yaml
|
||||
kubectl apply -f 02-crds.yaml
|
||||
|
||||
echo ""
|
||||
echo "Step 2: Authentication setup"
|
||||
echo "You need to create an auth key from: https://login.tailscale.com/admin/settings/keys"
|
||||
echo ""
|
||||
echo "Choose one option:"
|
||||
echo "1) I have an auth key ready"
|
||||
echo "2) I'll set it up later"
|
||||
echo ""
|
||||
read -p "Enter your choice (1 or 2): " choice
|
||||
|
||||
if [ "$choice" = "1" ]; then
|
||||
read -p "Enter your Tailscale auth key: " authkey
|
||||
kubectl create secret generic operator \
|
||||
--namespace=tailscale \
|
||||
--from-literal=authkey="$authkey" \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
echo "Auth key secret created/updated"
|
||||
|
||||
echo ""
|
||||
echo "Step 3: Deploying operator..."
|
||||
kubectl apply -f 04-operator-authkey.yaml
|
||||
|
||||
echo ""
|
||||
echo "Waiting for operator to start..."
|
||||
sleep 5
|
||||
kubectl wait --for=condition=ready pod -l app=operator -n tailscale --timeout=60s
|
||||
|
||||
echo ""
|
||||
echo "Operator status:"
|
||||
kubectl get pods -n tailscale
|
||||
|
||||
echo ""
|
||||
echo "Step 4: Would you like to expose Grafana via Tailscale? (y/n)"
|
||||
read -p "Choice: " expose_grafana
|
||||
|
||||
if [ "$expose_grafana" = "y" ]; then
|
||||
kubectl apply -f grafana-tailscale.yaml
|
||||
echo "Grafana will be available at: https://aprs-grafana.<your-tailnet-name>.ts.net"
|
||||
fi
|
||||
else
|
||||
echo ""
|
||||
echo "Skipping auth key setup. To complete the setup later:"
|
||||
echo "1. Create an auth key at https://login.tailscale.com/admin/settings/keys"
|
||||
echo "2. Run: kubectl create secret generic operator --namespace=tailscale --from-literal=authkey='<your-key>'"
|
||||
echo "3. Apply the operator: kubectl apply -f 04-operator-authkey.yaml"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Setup complete!"
|
||||
echo ""
|
||||
echo "To check the operator status: kubectl logs -n tailscale deployment/operator"
|
||||
echo "To see Tailscale proxies: kubectl get pods -A | grep ts-"
|
||||
173
clusters/vntx/DEPLOYMENT.md
Normal file
173
clusters/vntx/DEPLOYMENT.md
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
# VNTX Cluster Deployment Guide
|
||||
|
||||
This guide walks through deploying the VNTX K3s cluster on Proxmox to consolidate all infrastructure services.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Proxmox cluster with sufficient resources
|
||||
- OpenTofu/Terraform installed locally
|
||||
- Ansible installed locally
|
||||
- SSH access to Proxmox nodes
|
||||
|
||||
## Step 1: Deploy Flatcar VMs with OpenTofu
|
||||
|
||||
```bash
|
||||
cd clusters/vntx/terraform
|
||||
|
||||
# Copy and edit the example vars
|
||||
cp terraform.tfvars.example terraform.tfvars
|
||||
vim terraform.tfvars
|
||||
|
||||
# Set Proxmox password
|
||||
export TF_VAR_proxmox_password="your-proxmox-password"
|
||||
|
||||
# Initialize and deploy
|
||||
tofu init
|
||||
tofu plan
|
||||
tofu apply
|
||||
```
|
||||
|
||||
This creates:
|
||||
- 3 control plane nodes (4 CPU, 8GB RAM, 50GB disk)
|
||||
- 2 worker nodes (8 CPU, 16GB RAM, 50GB + 200GB disks)
|
||||
|
||||
## Step 2: Bootstrap K3s Cluster
|
||||
|
||||
```bash
|
||||
cd ../ansible
|
||||
|
||||
# Prepare Flatcar nodes
|
||||
ansible-playbook -i inventory.yml prepare-flatcar.yml
|
||||
|
||||
# Install K3s cluster
|
||||
ansible-playbook -i inventory.yml k3s-install.yml
|
||||
|
||||
# Verify cluster
|
||||
export KUBECONFIG=./kubeconfig
|
||||
kubectl get nodes
|
||||
```
|
||||
|
||||
## Step 3: Deploy Core System Services
|
||||
|
||||
```bash
|
||||
cd ..
|
||||
|
||||
# Install MetalLB for load balancing
|
||||
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.12/config/manifests/metallb-native.yaml
|
||||
kubectl apply -f system/metallb/config.yaml
|
||||
|
||||
# Install Longhorn for storage
|
||||
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.5.3/deploy/longhorn.yaml
|
||||
|
||||
# Install cert-manager
|
||||
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.3/cert-manager.yaml
|
||||
|
||||
# Create namespaces
|
||||
kubectl apply -f system/base/namespaces.yaml
|
||||
```
|
||||
|
||||
## Step 4: Deploy Infrastructure Services
|
||||
|
||||
### Deploy MariaDB (shared database)
|
||||
```bash
|
||||
kubectl apply -f infrastructure/monitoring/mariadb/
|
||||
|
||||
# Create databases for services
|
||||
kubectl exec -it -n monitoring mariadb-0 -- mysql -u root -p
|
||||
CREATE DATABASE icinga2;
|
||||
CREATE DATABASE icingaweb2;
|
||||
CREATE USER 'icinga2'@'%' IDENTIFIED BY 'secure-password';
|
||||
GRANT ALL ON icinga2.* TO 'icinga2'@'%';
|
||||
CREATE USER 'icingaweb2'@'%' IDENTIFIED BY 'secure-password';
|
||||
GRANT ALL ON icingaweb2.* TO 'icingaweb2'@'%';
|
||||
```
|
||||
|
||||
### Deploy Monitoring Stack
|
||||
```bash
|
||||
# Create secrets
|
||||
kubectl create secret generic -n monitoring icinga2-mysql \
|
||||
--from-literal=password='secure-password'
|
||||
kubectl create secret generic -n monitoring icingaweb2-mysql \
|
||||
--from-literal=password='secure-password'
|
||||
|
||||
# Deploy Icinga2
|
||||
kubectl apply -f infrastructure/monitoring/icinga2/
|
||||
```
|
||||
|
||||
### Deploy Network Services
|
||||
```bash
|
||||
# Deploy Unbound DNS
|
||||
kubectl apply -f infrastructure/network-services/unbound/
|
||||
|
||||
# Deploy FreeRADIUS
|
||||
kubectl apply -f infrastructure/network-services/freeradius/
|
||||
```
|
||||
|
||||
## Step 5: Configure Ingress and Access
|
||||
|
||||
```bash
|
||||
# Get MetalLB assigned IPs
|
||||
kubectl get svc -A | grep LoadBalancer
|
||||
|
||||
# Configure DNS entries for services
|
||||
# icinga.vntx.net -> Icinga2 LoadBalancer IP
|
||||
# radius.vntx.net -> FreeRADIUS LoadBalancer IP
|
||||
```
|
||||
|
||||
## Service Migration Checklist
|
||||
|
||||
### From monitor.vntx.net (Icinga2):
|
||||
- [ ] Export Icinga2 configuration
|
||||
- [ ] Export monitoring database
|
||||
- [ ] Migrate custom check scripts
|
||||
- [ ] Update monitored hosts to point to new IP
|
||||
- [ ] Verify all checks working
|
||||
|
||||
### From DNS servers:
|
||||
- [ ] Export zone files
|
||||
- [ ] Update forwarders configuration
|
||||
- [ ] Test resolution
|
||||
- [ ] Update DHCP to use new DNS
|
||||
|
||||
### From RADIUS server:
|
||||
- [ ] Export user database
|
||||
- [ ] Export clients configuration
|
||||
- [ ] Test authentication
|
||||
- [ ] Update network devices to use new RADIUS
|
||||
|
||||
## Maintenance
|
||||
|
||||
### Cluster Updates
|
||||
```bash
|
||||
# Flatcar auto-updates by default
|
||||
# K3s updates:
|
||||
curl -sfL https://get.k3s.io | sh -s - upgrade
|
||||
```
|
||||
|
||||
### Backup
|
||||
```bash
|
||||
# Install Velero for cluster backup
|
||||
# Backup to S3-compatible storage
|
||||
```
|
||||
|
||||
### Monitoring
|
||||
- Access Icinga2 Web: http://icinga.vntx.net
|
||||
- Kubernetes dashboard: Install via Rancher or Lens
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Check pod logs
|
||||
```bash
|
||||
kubectl logs -n monitoring deployment/icinga2
|
||||
kubectl logs -n network-services deployment/freeradius
|
||||
```
|
||||
|
||||
### Node issues
|
||||
```bash
|
||||
# SSH to Flatcar node
|
||||
ssh core@10.0.0.101
|
||||
|
||||
# Check K3s service
|
||||
sudo systemctl status k3s
|
||||
sudo journalctl -u k3s -f
|
||||
```
|
||||
98
clusters/vntx/QUICKSTART.md
Normal file
98
clusters/vntx/QUICKSTART.md
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
# Quick Start: Deploy K3s on Proxmox
|
||||
|
||||
## 1. Prerequisites
|
||||
|
||||
```bash
|
||||
# Install OpenTofu (or use Terraform)
|
||||
brew install opentofu
|
||||
|
||||
# Install Ansible
|
||||
pip install ansible
|
||||
|
||||
# Get your SSH public key
|
||||
cat ~/.ssh/id_rsa.pub
|
||||
```
|
||||
|
||||
## 2. Configure for Your Environment
|
||||
|
||||
```bash
|
||||
cd clusters/vntx/terraform
|
||||
|
||||
# Edit terraform.tfvars
|
||||
vim terraform.tfvars
|
||||
```
|
||||
|
||||
**Important settings to update:**
|
||||
- `proxmox_nodes` - Set to your actual Proxmox node names (check in Proxmox UI)
|
||||
- `ssh_public_key` - Replace with your actual SSH public key
|
||||
- `control_plane_ips` and `worker_ips` - Adjust for your network
|
||||
- `proxmox_storage` - Set to your storage name (usually "local-lvm" or "local")
|
||||
|
||||
## 3. Deploy Everything
|
||||
|
||||
```bash
|
||||
cd ../
|
||||
|
||||
# Set Proxmox password
|
||||
export TF_VAR_proxmox_password="your-proxmox-root-password"
|
||||
|
||||
# Run the deployment script
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
The script will:
|
||||
1. Create 5 Flatcar Linux VMs on Proxmox
|
||||
2. Install K3s cluster
|
||||
3. Configure kubectl access
|
||||
4. Optionally install MetalLB and Longhorn
|
||||
|
||||
## 4. Manual Steps (if not using deploy.sh)
|
||||
|
||||
```bash
|
||||
# Step 1: Deploy VMs
|
||||
cd terraform
|
||||
tofu init
|
||||
tofu plan
|
||||
tofu apply
|
||||
|
||||
# Step 2: Wait for VMs to boot (60 seconds)
|
||||
|
||||
# Step 3: Install K3s
|
||||
cd ../ansible
|
||||
ansible-playbook -i inventory.yml prepare-flatcar.yml
|
||||
ansible-playbook -i inventory.yml k3s-install.yml
|
||||
|
||||
# Step 4: Get kubeconfig
|
||||
export KUBECONFIG=./kubeconfig
|
||||
kubectl get nodes
|
||||
```
|
||||
|
||||
## 5. Access Your Cluster
|
||||
|
||||
```bash
|
||||
# Set kubeconfig
|
||||
export KUBECONFIG=~/.kube/config-vntx
|
||||
|
||||
# Check nodes
|
||||
kubectl get nodes
|
||||
|
||||
# Deploy a test service
|
||||
kubectl create deployment nginx --image=nginx
|
||||
kubectl expose deployment nginx --port=80 --type=LoadBalancer
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### If VMs don't start:
|
||||
- Check Proxmox has enough resources
|
||||
- Verify network bridge name in terraform.tfvars
|
||||
- Check Proxmox storage has space
|
||||
|
||||
### If Ansible fails:
|
||||
- Ensure VMs are accessible: `ping 10.0.0.101`
|
||||
- Check SSH key is correct
|
||||
- Wait longer for VMs to fully boot
|
||||
|
||||
### If K3s installation fails:
|
||||
- SSH to node: `ssh core@10.0.0.101`
|
||||
- Check logs: `sudo journalctl -u k3s -f`
|
||||
70
clusters/vntx/README-DEPLOYMENT.md
Normal file
70
clusters/vntx/README-DEPLOYMENT.md
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
# Deploying K3s on Proxmox with Debian 13
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Create Debian 13 template on Proxmox:**
|
||||
```bash
|
||||
# SSH to your Proxmox host (10.0.0.1 or 10.0.0.2)
|
||||
ssh root@10.0.0.1
|
||||
|
||||
# Run the commands from setup-debian-template.sh
|
||||
cat setup-debian-template.sh
|
||||
```
|
||||
|
||||
2. **Update Proxmox node names:**
|
||||
Edit `terraform/terraform.tfvars` and set your actual Proxmox node names
|
||||
(you can find these in the Proxmox web UI)
|
||||
|
||||
3. **Verify your SSH key is set** (already done)
|
||||
|
||||
## Deploy the Cluster
|
||||
|
||||
```bash
|
||||
# Set Proxmox password
|
||||
export TF_VAR_proxmox_password="your-proxmox-password"
|
||||
|
||||
# Run deployment
|
||||
cd clusters/vntx
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
## What Gets Deployed
|
||||
|
||||
- **5 Debian 13 VMs:**
|
||||
- 3 control plane nodes (4 CPU, 8GB RAM each)
|
||||
- 2 worker nodes (8 CPU, 16GB RAM each)
|
||||
- Worker nodes get extra 200GB disk for container storage
|
||||
|
||||
- **K3s Kubernetes cluster** with:
|
||||
- High availability control plane
|
||||
- MetalLB for load balancing
|
||||
- Longhorn for distributed storage
|
||||
|
||||
## After Deployment
|
||||
|
||||
Your services will be accessible via MetalLB LoadBalancer IPs:
|
||||
- Icinga2: http://10.0.0.200 (example)
|
||||
- FreeRADIUS: 10.0.0.201:1812 (example)
|
||||
- Unbound DNS: Running on all nodes
|
||||
|
||||
## Manual Steps Summary
|
||||
|
||||
If the script fails, you can run manually:
|
||||
|
||||
```bash
|
||||
# 1. Create VMs
|
||||
cd terraform
|
||||
tofu init
|
||||
tofu apply
|
||||
|
||||
# 2. Configure nodes
|
||||
cd ../ansible
|
||||
ansible-playbook -i inventory.yml prepare-debian.yml
|
||||
|
||||
# 3. Install K3s
|
||||
ansible-playbook -i inventory.yml k3s-install.yml
|
||||
|
||||
# 4. Use cluster
|
||||
export KUBECONFIG=./kubeconfig
|
||||
kubectl get nodes
|
||||
```
|
||||
36
clusters/vntx/README.md
Normal file
36
clusters/vntx/README.md
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# VNTX K3s Cluster
|
||||
|
||||
This cluster runs all infrastructure services previously deployed as individual VMs on Proxmox.
|
||||
|
||||
## Architecture
|
||||
|
||||
- **Platform**: Flatcar Linux on Proxmox VE
|
||||
- **Kubernetes**: K3s (lightweight Kubernetes)
|
||||
- **Nodes**: 3 control plane + 2 worker nodes
|
||||
- **Storage**: Longhorn distributed storage
|
||||
- **Load Balancer**: MetalLB
|
||||
|
||||
## Services
|
||||
|
||||
### Infrastructure Services (`infrastructure/`)
|
||||
- **Monitoring**: Icinga2, Prometheus, Grafana
|
||||
- **DNS**: Unbound, CoreDNS
|
||||
- **Authentication**: FreeRADIUS
|
||||
- **Reverse Proxy**: Caddy
|
||||
|
||||
### System Services (`system/`)
|
||||
- **Storage**: Longhorn
|
||||
- **Ingress**: Traefik (K3s default)
|
||||
- **Certificates**: cert-manager
|
||||
- **Backups**: Velero
|
||||
|
||||
### Applications (`apps/`)
|
||||
- User-facing applications
|
||||
|
||||
## Deployment Order
|
||||
|
||||
1. Deploy Flatcar VMs via Terraform
|
||||
2. Bootstrap K3s cluster via Ansible
|
||||
3. Deploy system services
|
||||
4. Deploy infrastructure services
|
||||
5. Migrate data from existing VMs
|
||||
31
clusters/vntx/ansible/inventory.yml
Normal file
31
clusters/vntx/ansible/inventory.yml
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
all:
|
||||
children:
|
||||
k3s_cluster:
|
||||
children:
|
||||
control_plane:
|
||||
hosts:
|
||||
vntx-control-1:
|
||||
ansible_host: 10.0.0.101
|
||||
k3s_control_node: true
|
||||
k3s_primary: true
|
||||
vntx-control-2:
|
||||
ansible_host: 10.0.0.102
|
||||
k3s_control_node: true
|
||||
vntx-control-3:
|
||||
ansible_host: 10.0.0.103
|
||||
k3s_control_node: true
|
||||
workers:
|
||||
hosts:
|
||||
vntx-worker-1:
|
||||
ansible_host: 10.0.0.111
|
||||
vntx-worker-2:
|
||||
ansible_host: 10.0.0.112
|
||||
vars:
|
||||
ansible_user: debian
|
||||
ansible_ssh_common_args: '-o StrictHostKeyChecking=no'
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
k3s_version: v1.29.0+k3s1
|
||||
k3s_cluster_domain: cluster.local
|
||||
k3s_cluster_cidr: 10.42.0.0/16
|
||||
k3s_service_cidr: 10.43.0.0/16
|
||||
systemd_dir: /etc/systemd/system
|
||||
160
clusters/vntx/ansible/k3s-install.yml
Normal file
160
clusters/vntx/ansible/k3s-install.yml
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
---
|
||||
- name: Prepare Debian nodes for K3s
|
||||
hosts: k3s_cluster
|
||||
become: yes
|
||||
tasks:
|
||||
- name: Configure sysctl for K3s
|
||||
ansible.posix.sysctl:
|
||||
name: "{{ item }}"
|
||||
value: '1'
|
||||
state: present
|
||||
reload: yes
|
||||
loop:
|
||||
- net.bridge.bridge-nf-call-iptables
|
||||
- net.ipv4.ip_forward
|
||||
- net.bridge.bridge-nf-call-ip6tables
|
||||
|
||||
- name: Load required kernel modules
|
||||
community.general.modprobe:
|
||||
name: "{{ item }}"
|
||||
state: present
|
||||
loop:
|
||||
- br_netfilter
|
||||
- overlay
|
||||
|
||||
- name: Ensure kernel modules load on boot
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/modules-load.d/k3s.conf
|
||||
content: |
|
||||
br_netfilter
|
||||
overlay
|
||||
|
||||
- name: Deploy K3s on primary control node
|
||||
hosts: control_plane
|
||||
become: yes
|
||||
tasks:
|
||||
- name: Download K3s installer
|
||||
ansible.builtin.get_url:
|
||||
url: https://get.k3s.io
|
||||
dest: /tmp/k3s-install.sh
|
||||
mode: '0755'
|
||||
when: k3s_primary | default(false)
|
||||
|
||||
- name: Install K3s on primary control node
|
||||
ansible.builtin.shell: |
|
||||
INSTALL_K3S_VERSION="{{ k3s_version }}" \
|
||||
K3S_CLUSTER_INIT=true \
|
||||
INSTALL_K3S_EXEC="server \
|
||||
--cluster-cidr={{ k3s_cluster_cidr }} \
|
||||
--service-cidr={{ k3s_service_cidr }} \
|
||||
--cluster-domain={{ k3s_cluster_domain }} \
|
||||
--disable traefik \
|
||||
--disable servicelb \
|
||||
--write-kubeconfig-mode=644" \
|
||||
/tmp/k3s-install.sh
|
||||
when: k3s_primary | default(false)
|
||||
register: k3s_primary_install
|
||||
|
||||
- name: Get node token
|
||||
ansible.builtin.slurp:
|
||||
src: /var/lib/rancher/k3s/server/node-token
|
||||
register: node_token
|
||||
when: k3s_primary | default(false)
|
||||
|
||||
- name: Set token fact
|
||||
ansible.builtin.set_fact:
|
||||
k3s_token: "{{ node_token.content | b64decode | trim }}"
|
||||
when: k3s_primary | default(false)
|
||||
|
||||
- name: Get primary node URL
|
||||
ansible.builtin.set_fact:
|
||||
k3s_url: "https://{{ ansible_host }}:6443"
|
||||
when: k3s_primary | default(false)
|
||||
|
||||
- name: Deploy K3s on secondary control nodes
|
||||
hosts: control_plane:!vntx-control-1
|
||||
become: yes
|
||||
tasks:
|
||||
- name: Download K3s installer
|
||||
ansible.builtin.get_url:
|
||||
url: https://get.k3s.io
|
||||
dest: /tmp/k3s-install.sh
|
||||
mode: '0755'
|
||||
|
||||
- name: Get primary node facts
|
||||
ansible.builtin.set_fact:
|
||||
k3s_url: "{{ hostvars['vntx-control-1']['k3s_url'] }}"
|
||||
k3s_token: "{{ hostvars['vntx-control-1']['k3s_token'] }}"
|
||||
|
||||
- name: Install K3s on secondary control nodes
|
||||
ansible.builtin.shell: |
|
||||
INSTALL_K3S_VERSION="{{ k3s_version }}" \
|
||||
K3S_URL="{{ k3s_url }}" \
|
||||
K3S_TOKEN="{{ k3s_token }}" \
|
||||
INSTALL_K3S_EXEC="server \
|
||||
--cluster-cidr={{ k3s_cluster_cidr }} \
|
||||
--service-cidr={{ k3s_service_cidr }} \
|
||||
--cluster-domain={{ k3s_cluster_domain }} \
|
||||
--disable traefik \
|
||||
--disable servicelb \
|
||||
--write-kubeconfig-mode=644" \
|
||||
/tmp/k3s-install.sh
|
||||
|
||||
- name: Deploy K3s on worker nodes
|
||||
hosts: workers
|
||||
become: yes
|
||||
tasks:
|
||||
- name: Download K3s installer
|
||||
ansible.builtin.get_url:
|
||||
url: https://get.k3s.io
|
||||
dest: /tmp/k3s-install.sh
|
||||
mode: '0755'
|
||||
|
||||
- name: Get primary node facts
|
||||
ansible.builtin.set_fact:
|
||||
k3s_url: "{{ hostvars['vntx-control-1']['k3s_url'] }}"
|
||||
k3s_token: "{{ hostvars['vntx-control-1']['k3s_token'] }}"
|
||||
|
||||
- name: Install K3s agent on worker nodes
|
||||
ansible.builtin.shell: |
|
||||
INSTALL_K3S_VERSION="{{ k3s_version }}" \
|
||||
K3S_URL="{{ k3s_url }}" \
|
||||
K3S_TOKEN="{{ k3s_token }}" \
|
||||
INSTALL_K3S_EXEC="agent" \
|
||||
/tmp/k3s-install.sh
|
||||
|
||||
- name: Configure kubectl access
|
||||
hosts: vntx-control-1
|
||||
become: yes
|
||||
tasks:
|
||||
- name: Create .kube directory
|
||||
ansible.builtin.file:
|
||||
path: /home/debian/.kube
|
||||
state: directory
|
||||
owner: debian
|
||||
group: debian
|
||||
mode: '0755'
|
||||
|
||||
- name: Copy kubeconfig
|
||||
ansible.builtin.copy:
|
||||
src: /etc/rancher/k3s/k3s.yaml
|
||||
dest: /home/debian/.kube/config
|
||||
owner: debian
|
||||
group: debian
|
||||
mode: '0600'
|
||||
remote_src: yes
|
||||
|
||||
- name: Fetch kubeconfig to local machine
|
||||
ansible.builtin.fetch:
|
||||
src: /etc/rancher/k3s/k3s.yaml
|
||||
dest: ./kubeconfig
|
||||
flat: yes
|
||||
|
||||
- name: Display cluster info
|
||||
ansible.builtin.shell: kubectl get nodes
|
||||
become_user: debian
|
||||
register: nodes_output
|
||||
|
||||
- name: Show nodes
|
||||
ansible.builtin.debug:
|
||||
var: nodes_output.stdout_lines
|
||||
19
clusters/vntx/ansible/kubeconfig
Normal file
19
clusters/vntx/ansible/kubeconfig
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJkekNDQVIyZ0F3SUJBZ0lCQURBS0JnZ3Foa2pPUFFRREFqQWpNU0V3SHdZRFZRUUREQmhyTTNNdGMyVnkKZG1WeUxXTmhRREUzTlRZNU1qVTBPRFl3SGhjTk1qVXdPVEF6TVRnMU1USTJXaGNOTXpVd09UQXhNVGcxTVRJMgpXakFqTVNFd0h3WURWUVFEREJock0zTXRjMlZ5ZG1WeUxXTmhRREUzTlRZNU1qVTBPRFl3V1RBVEJnY3Foa2pPClBRSUJCZ2dxaGtqT1BRTUJCd05DQUFRWC9KVDdROEV1RktCMWhWUVRHNVVZNXFmVmljcFAybkN5eHE0Tmd1T3AKZCtUSTI4SWgycmVSQ2FsL2d2enJLamxvbTR2ekc5eDJUUnpwMW1LTGJHUlFvMEl3UURBT0JnTlZIUThCQWY4RQpCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVVAzdG5tcFphN2RIUFluaFBZM2NXCitVNmxnNGd3Q2dZSUtvWkl6ajBFQXdJRFNBQXdSUUloQVBJWS9jdzBNYXljV3RwNDFDbDZnZEZOY0xBUzNBelAKN21pZWJXcGFWbUNSQWlCaTdaME9mR0ZoeVRoL1BTVG5Qam5weXJzQnprcUlPeEVkU3U2aEdCMnphZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
|
||||
server: https://127.0.0.1:6443
|
||||
name: default
|
||||
contexts:
|
||||
- context:
|
||||
cluster: default
|
||||
user: default
|
||||
name: default
|
||||
current-context: default
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: default
|
||||
user:
|
||||
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJrVENDQVRlZ0F3SUJBZ0lJZUwwRzJmMDB3SkF3Q2dZSUtvWkl6ajBFQXdJd0l6RWhNQjhHQTFVRUF3d1kKYXpOekxXTnNhV1Z1ZEMxallVQXhOelUyT1RJMU5EZzJNQjRYRFRJMU1Ea3dNekU0TlRFeU5sb1hEVEkyTURrdwpNekU0TlRFeU5sb3dNREVYTUJVR0ExVUVDaE1PYzNsemRHVnRPbTFoYzNSbGNuTXhGVEFUQmdOVkJBTVRESE41CmMzUmxiVHBoWkcxcGJqQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJPbGhUT1V3dmVGbjlDTTcKSHZPVk94bVM1TXRsbHM2ZUNOOHN4dTg2NGQzQjBnK0grZnFBelBvbUNWdEp0UmpNdEJhSUltSU9jUWx0NFlqSQpTR1c4a05TalNEQkdNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUtCZ2dyQmdFRkJRY0RBakFmCkJnTlZIU01FR0RBV2dCVG9nRjNxOHF5eUllclNuc1Y1dDIxaTQ5MEpCREFLQmdncWhrak9QUVFEQWdOSUFEQkYKQWlFQXFvaDJIR1NYMGlObGo0RWpmVFJXcFBaWFZ1blhUcU5SSmw5UGQxdUc1RWdDSUMvMUk2eGZQZFdnN0d6ZQoyVkl0UjlqNzJFRVpFRTdjUFQwcFRUcExwdld3Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJkekNDQVIyZ0F3SUJBZ0lCQURBS0JnZ3Foa2pPUFFRREFqQWpNU0V3SHdZRFZRUUREQmhyTTNNdFkyeHAKWlc1MExXTmhRREUzTlRZNU1qVTBPRFl3SGhjTk1qVXdPVEF6TVRnMU1USTJXaGNOTXpVd09UQXhNVGcxTVRJMgpXakFqTVNFd0h3WURWUVFEREJock0zTXRZMnhwWlc1MExXTmhRREUzTlRZNU1qVTBPRFl3V1RBVEJnY3Foa2pPClBRSUJCZ2dxaGtqT1BRTUJCd05DQUFUc2lmYnZWTEpGYjRHZmpKWHhMZ2gwbndCdW9KdUhUd3NRMWM4R09lQnMKSGtVbGtrOFc0clVVb1IybkMxdVdEeEF5S3N6bE1PRHF1VXVWOXBmT1hRcXFvMEl3UURBT0JnTlZIUThCQWY4RQpCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVTZJQmQ2dktzc2lIcTBwN0ZlYmR0Cll1UGRDUVF3Q2dZSUtvWkl6ajBFQXdJRFNBQXdSUUlnU0RuNkh4UU12b2lLNkx3QkdpQW9kRFFQam1CYlUvaWkKQmVCVWFNazRyMXNDSVFEeFpYUTQxWEl3MTRJMWtsdW9wOHNRVVYzTEtWbEtqNTBiaXc5YXpia2xxUT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
|
||||
client-key-data: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSU9RdlF2RVpNZ0R3aHVSTUY1NUE4V2J4WUI0VDVQZTFIVW8xVlVnbkVvdDlvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFNldGTTVUQzk0V2YwSXpzZTg1VTdHWkxreTJXV3pwNEkzeXpHN3pyaDNjSFNENGY1K29ETQoraVlKVzBtMUdNeTBGb2dpWWc1eENXM2hpTWhJWmJ5UTFBPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=
|
||||
167
clusters/vntx/ansible/prepare-debian.yml
Normal file
167
clusters/vntx/ansible/prepare-debian.yml
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
---
|
||||
- name: Prepare Debian 13 nodes for K3s
|
||||
hosts: k3s_cluster
|
||||
become: yes
|
||||
tasks:
|
||||
- name: Wait for cloud-init to complete
|
||||
ansible.builtin.wait_for:
|
||||
path: /var/lib/cloud/instance/boot-finished
|
||||
timeout: 300
|
||||
|
||||
- name: Wait for apt locks to be released
|
||||
ansible.builtin.shell: |
|
||||
while fuser /var/lib/apt/lists/lock >/dev/null 2>&1; do
|
||||
echo "Waiting for apt lock..."
|
||||
sleep 5
|
||||
done
|
||||
changed_when: false
|
||||
|
||||
- name: Create systemd-resolved config directory
|
||||
ansible.builtin.file:
|
||||
path: /etc/systemd/resolved.conf.d
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Configure DNS for internet access
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/systemd/resolved.conf.d/dns.conf
|
||||
content: |
|
||||
[Resolve]
|
||||
DNS=9.9.9.9
|
||||
FallbackDNS=149.112.112.112
|
||||
Domains=~.
|
||||
mode: '0644'
|
||||
notify: restart systemd-resolved
|
||||
|
||||
- name: Restart systemd-resolved immediately
|
||||
ansible.builtin.systemd:
|
||||
name: systemd-resolved
|
||||
state: restarted
|
||||
daemon_reload: yes
|
||||
|
||||
- name: Update apt cache
|
||||
ansible.builtin.apt:
|
||||
update_cache: yes
|
||||
|
||||
- name: Install required packages
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- curl
|
||||
- apt-transport-https
|
||||
- ca-certificates
|
||||
- gnupg
|
||||
- lsb-release
|
||||
- iptables
|
||||
- open-iscsi
|
||||
- nfs-common
|
||||
state: present
|
||||
|
||||
- name: Load required kernel modules
|
||||
community.general.modprobe:
|
||||
name: "{{ item }}"
|
||||
state: present
|
||||
loop:
|
||||
- br_netfilter
|
||||
- overlay
|
||||
|
||||
- name: Configure sysctl for K3s
|
||||
ansible.posix.sysctl:
|
||||
name: "{{ item }}"
|
||||
value: '1'
|
||||
state: present
|
||||
reload: yes
|
||||
loop:
|
||||
- net.bridge.bridge-nf-call-iptables
|
||||
- net.ipv4.ip_forward
|
||||
- net.bridge.bridge-nf-call-ip6tables
|
||||
|
||||
- name: Ensure kernel modules load on boot
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/modules-load.d/k3s.conf
|
||||
content: |
|
||||
br_netfilter
|
||||
overlay
|
||||
mode: '0644'
|
||||
|
||||
- name: Format and mount additional storage disk (workers only)
|
||||
when: inventory_hostname in groups['workers']
|
||||
block:
|
||||
- name: Find additional storage disk
|
||||
ansible.builtin.shell: |
|
||||
# Find the 200G disk which is our storage disk
|
||||
for disk in /dev/sda /dev/sdb /dev/sdc; do
|
||||
if [ -b "$disk" ]; then
|
||||
size=$(lsblk -d -n -o SIZE "$disk" | tr -d ' ')
|
||||
if [ "$size" = "200G" ]; then
|
||||
echo "$disk"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
exit 1
|
||||
register: storage_disk_path
|
||||
failed_when: storage_disk_path.rc != 0
|
||||
|
||||
- name: Check if storage disk has a filesystem
|
||||
ansible.builtin.command: "blkid {{ storage_disk_path.stdout }}"
|
||||
register: blkid_result
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
|
||||
- name: Create filesystem on storage disk
|
||||
ansible.builtin.filesystem:
|
||||
fstype: ext4
|
||||
dev: "{{ storage_disk_path.stdout }}"
|
||||
force: no
|
||||
when: blkid_result.rc != 0
|
||||
|
||||
- name: Create mount point for container storage
|
||||
ansible.builtin.file:
|
||||
path: /var/lib/rancher
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Get UUID of storage disk
|
||||
ansible.builtin.command: "blkid -s UUID -o value {{ storage_disk_path.stdout }}"
|
||||
register: storage_uuid
|
||||
changed_when: false
|
||||
|
||||
- name: Mount storage disk by UUID
|
||||
ansible.posix.mount:
|
||||
path: /var/lib/rancher
|
||||
src: "UUID={{ storage_uuid.stdout }}"
|
||||
fstype: ext4
|
||||
opts: defaults
|
||||
state: mounted
|
||||
|
||||
- name: Configure systemd-resolved for cluster DNS
|
||||
ansible.builtin.file:
|
||||
path: /etc/systemd/resolved.conf.d
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Configure cluster DNS
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/systemd/resolved.conf.d/k3s.conf
|
||||
content: |
|
||||
[Resolve]
|
||||
DNS=10.43.0.10
|
||||
Domains=~cluster.local
|
||||
mode: '0644'
|
||||
notify: restart systemd-resolved
|
||||
|
||||
- name: Configure container runtime limits
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/security/limits.d/k3s.conf
|
||||
content: |
|
||||
* soft nofile 65536
|
||||
* hard nofile 65536
|
||||
* soft nproc 65536
|
||||
* hard nproc 65536
|
||||
mode: '0644'
|
||||
|
||||
handlers:
|
||||
- name: restart systemd-resolved
|
||||
ansible.builtin.systemd:
|
||||
name: systemd-resolved
|
||||
state: restarted
|
||||
66
clusters/vntx/create-vms-proxmox.sh
Executable file
66
clusters/vntx/create-vms-proxmox.sh
Executable file
|
|
@ -0,0 +1,66 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Script to create K3s VMs directly on Proxmox
|
||||
# Run this on your Proxmox host (10.0.0.2)
|
||||
|
||||
echo "Creating K3s cluster VMs on Proxmox..."
|
||||
|
||||
# Your SSH public key
|
||||
SSH_KEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHLyVSEEAEbGZZqwPwEup0XrlTpu3x3iF9ExvvQPA4xN graham@mcintire.me"
|
||||
|
||||
# Create a temporary file with the SSH key
|
||||
echo "$SSH_KEY" > /tmp/authorized_keys
|
||||
|
||||
# Start with higher VM IDs to avoid conflicts
|
||||
BASE_ID=130
|
||||
|
||||
echo "Using VM IDs starting from $BASE_ID"
|
||||
|
||||
# Control plane nodes
|
||||
for i in 1 2 3; do
|
||||
VMID=$((BASE_ID + i - 1))
|
||||
echo "Creating vntx-control-$i (VM $VMID)..."
|
||||
qm clone 9000 $VMID --name vntx-control-$i
|
||||
qm set $VMID \
|
||||
--cores 4 \
|
||||
--memory 8192 \
|
||||
--net0 virtio,bridge=vmbr0 \
|
||||
--ipconfig0 ip=10.0.0.$((100 + i))/24,gw=10.0.0.1 \
|
||||
--sshkey /tmp/authorized_keys
|
||||
qm resize $VMID scsi0 50G
|
||||
qm start $VMID
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Worker nodes
|
||||
for i in 1 2; do
|
||||
VMID=$((BASE_ID + 3 + i - 1))
|
||||
echo "Creating vntx-worker-$i (VM $VMID)..."
|
||||
qm clone 9000 $VMID --name vntx-worker-$i
|
||||
qm set $VMID \
|
||||
--cores 8 \
|
||||
--memory 16384 \
|
||||
--net0 virtio,bridge=vmbr0 \
|
||||
--ipconfig0 ip=10.0.0.$((110 + i))/24,gw=10.0.0.1 \
|
||||
--sshkey /tmp/authorized_keys
|
||||
qm resize $VMID scsi0 50G
|
||||
# Add additional disk for storage
|
||||
qm set $VMID --scsi1 local-lvm:200
|
||||
qm start $VMID
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Clean up
|
||||
rm -f /tmp/authorized_keys
|
||||
|
||||
echo
|
||||
echo "All VMs created and started!"
|
||||
echo
|
||||
echo "VMs created:"
|
||||
echo " vntx-control-1: 10.0.0.101 (VM ID: 130)"
|
||||
echo " vntx-control-2: 10.0.0.102 (VM ID: 131)"
|
||||
echo " vntx-control-3: 10.0.0.103 (VM ID: 132)"
|
||||
echo " vntx-worker-1: 10.0.0.111 (VM ID: 133)"
|
||||
echo " vntx-worker-2: 10.0.0.112 (VM ID: 134)"
|
||||
echo
|
||||
echo "Wait 60 seconds for cloud-init to complete, then run Ansible"
|
||||
177
clusters/vntx/deploy.sh
Executable file
177
clusters/vntx/deploy.sh
Executable file
|
|
@ -0,0 +1,177 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "=== VNTX K3s Cluster Deployment Script ==="
|
||||
echo
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Check prerequisites
|
||||
echo "Checking prerequisites..."
|
||||
|
||||
if ! command -v tofu &> /dev/null && ! command -v terraform &> /dev/null; then
|
||||
echo -e "${RED}ERROR: OpenTofu or Terraform not found${NC}"
|
||||
echo "Install OpenTofu: https://opentofu.org/docs/intro/install/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v ansible &> /dev/null; then
|
||||
echo -e "${RED}ERROR: Ansible not found${NC}"
|
||||
echo "Install Ansible: pip install ansible"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v kubectl &> /dev/null; then
|
||||
echo -e "${YELLOW}WARNING: kubectl not found${NC}"
|
||||
echo "Install kubectl to manage the cluster after deployment"
|
||||
fi
|
||||
|
||||
# Use tofu if available, otherwise terraform
|
||||
TF_CMD="tofu"
|
||||
if ! command -v tofu &> /dev/null; then
|
||||
TF_CMD="terraform"
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}Prerequisites OK${NC}"
|
||||
echo
|
||||
|
||||
# Step 1: Configure Terraform
|
||||
echo "=== Step 1: Configure Terraform ==="
|
||||
cd terraform
|
||||
|
||||
if [ ! -f terraform.tfvars ]; then
|
||||
echo -e "${RED}ERROR: terraform.tfvars not found${NC}"
|
||||
echo "Please edit terraform/terraform.tfvars with your settings"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for SSH key
|
||||
if grep -q "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC..." terraform.tfvars; then
|
||||
echo -e "${RED}ERROR: SSH public key not configured${NC}"
|
||||
echo "Please add your SSH public key to terraform.tfvars"
|
||||
echo "You can find it in ~/.ssh/id_rsa.pub or ~/.ssh/id_ed25519.pub"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify SSH key is present
|
||||
if ! grep -E "ssh-(rsa|ed25519|ecdsa)" terraform.tfvars > /dev/null; then
|
||||
echo -e "${RED}ERROR: No valid SSH key found in terraform.tfvars${NC}"
|
||||
echo "Please add your SSH public key to terraform.tfvars"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get Proxmox password
|
||||
if [ -z "$TF_VAR_proxmox_password" ]; then
|
||||
echo -n "Enter Proxmox password: "
|
||||
read -s TF_VAR_proxmox_password
|
||||
export TF_VAR_proxmox_password
|
||||
echo
|
||||
fi
|
||||
|
||||
# Initialize Terraform
|
||||
echo "Initializing Terraform..."
|
||||
$TF_CMD init
|
||||
|
||||
# Plan deployment
|
||||
echo
|
||||
echo "Planning infrastructure..."
|
||||
$TF_CMD plan -out=tfplan
|
||||
|
||||
echo
|
||||
read -p "Deploy infrastructure? (y/n) " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Deploy VMs
|
||||
echo "Deploying Flatcar VMs on Proxmox..."
|
||||
$TF_CMD apply tfplan
|
||||
|
||||
echo -e "${GREEN}Infrastructure deployed!${NC}"
|
||||
|
||||
# Step 2: Wait for VMs to be ready
|
||||
echo
|
||||
echo "=== Step 2: Waiting for VMs to be ready ==="
|
||||
cd ../ansible
|
||||
|
||||
echo "Waiting 60 seconds for VMs to boot..."
|
||||
sleep 60
|
||||
|
||||
# Test connectivity
|
||||
echo "Testing connectivity to nodes..."
|
||||
ansible -i inventory.yml all -m ping --one-line
|
||||
|
||||
# Step 3: Prepare Debian nodes
|
||||
echo
|
||||
echo "=== Step 3: Preparing Debian nodes ==="
|
||||
ansible-playbook -i inventory.yml prepare-debian.yml
|
||||
|
||||
# Step 4: Install K3s
|
||||
echo
|
||||
echo "=== Step 4: Installing K3s cluster ==="
|
||||
ansible-playbook -i inventory.yml k3s-install.yml
|
||||
|
||||
# Step 5: Setup kubeconfig
|
||||
echo
|
||||
echo "=== Step 5: Setting up kubectl access ==="
|
||||
if [ -f kubeconfig ]; then
|
||||
mkdir -p ~/.kube
|
||||
cp kubeconfig ~/.kube/config-vntx
|
||||
export KUBECONFIG=~/.kube/config-vntx
|
||||
|
||||
echo "Cluster nodes:"
|
||||
kubectl get nodes
|
||||
|
||||
echo
|
||||
echo -e "${GREEN}K3s cluster deployed successfully!${NC}"
|
||||
echo
|
||||
echo "To use this cluster:"
|
||||
echo " export KUBECONFIG=~/.kube/config-vntx"
|
||||
echo " kubectl get nodes"
|
||||
else
|
||||
echo -e "${RED}ERROR: kubeconfig not found${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 6: Deploy core services
|
||||
echo
|
||||
read -p "Deploy core services (MetalLB, Longhorn)? (y/n) " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
cd ..
|
||||
|
||||
echo "Creating namespaces..."
|
||||
kubectl apply -f system/base/namespaces.yaml
|
||||
|
||||
echo "Installing MetalLB..."
|
||||
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.12/config/manifests/metallb-native.yaml
|
||||
|
||||
echo "Waiting for MetalLB to be ready..."
|
||||
kubectl wait --namespace metallb-system \
|
||||
--for=condition=ready pod \
|
||||
--selector=app=metallb \
|
||||
--timeout=90s
|
||||
|
||||
echo "Configuring MetalLB..."
|
||||
kubectl apply -f system/metallb/config.yaml
|
||||
|
||||
echo "Installing Longhorn..."
|
||||
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.5.3/deploy/longhorn.yaml
|
||||
|
||||
echo -e "${GREEN}Core services deployed!${NC}"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "=== Deployment Complete ==="
|
||||
echo
|
||||
echo "Cluster endpoints:"
|
||||
echo " Control plane: https://10.0.0.101:6443"
|
||||
echo
|
||||
echo "Next steps:"
|
||||
echo "1. Deploy your services: kubectl apply -f infrastructure/"
|
||||
echo "2. Check the deployment guide: cat DEPLOYMENT.md"
|
||||
159
clusters/vntx/infrastructure/monitoring/icinga2/deployment.yaml
Normal file
159
clusters/vntx/infrastructure/monitoring/icinga2/deployment.yaml
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: icinga2-config
|
||||
namespace: monitoring
|
||||
data:
|
||||
icinga2.conf: |
|
||||
include "constants.conf"
|
||||
include "zones.conf"
|
||||
include <itl>
|
||||
include <plugins>
|
||||
include <plugins-contrib>
|
||||
include <manubulon>
|
||||
include <windows-plugins>
|
||||
include <nscp>
|
||||
include "features-enabled/*.conf"
|
||||
include_recursive "conf.d"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: icinga2-data
|
||||
namespace: monitoring
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: longhorn
|
||||
resources:
|
||||
requests:
|
||||
storage: 10Gi
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: icinga2
|
||||
namespace: monitoring
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: icinga2
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: icinga2
|
||||
spec:
|
||||
containers:
|
||||
- name: icinga2
|
||||
image: icinga/icinga2:2.14.0
|
||||
ports:
|
||||
- containerPort: 5665
|
||||
name: api
|
||||
env:
|
||||
- name: ICINGA2_MASTER
|
||||
value: "1"
|
||||
- name: ICINGA2_CN
|
||||
value: "icinga2.monitoring.svc.cluster.local"
|
||||
- name: MYSQL_HOST
|
||||
value: "mariadb.monitoring.svc.cluster.local"
|
||||
- name: MYSQL_DATABASE
|
||||
value: "icinga2"
|
||||
- name: MYSQL_USER
|
||||
value: "icinga2"
|
||||
- name: MYSQL_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: icinga2-mysql
|
||||
key: password
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /etc/icinga2/icinga2.conf
|
||||
subPath: icinga2.conf
|
||||
- name: data
|
||||
mountPath: /var/lib/icinga2
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "2000m"
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: icinga2-config
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: icinga2-data
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: icinga2
|
||||
namespace: monitoring
|
||||
spec:
|
||||
selector:
|
||||
app: icinga2
|
||||
ports:
|
||||
- port: 5665
|
||||
targetPort: 5665
|
||||
name: api
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: icingaweb2
|
||||
namespace: monitoring
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: icingaweb2
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: icingaweb2
|
||||
spec:
|
||||
containers:
|
||||
- name: icingaweb2
|
||||
image: icinga/icingaweb2:2.12.1
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: http
|
||||
env:
|
||||
- name: MYSQL_HOST
|
||||
value: "mariadb.monitoring.svc.cluster.local"
|
||||
- name: MYSQL_DATABASE
|
||||
value: "icingaweb2"
|
||||
- name: MYSQL_USER
|
||||
value: "icingaweb2"
|
||||
- name: MYSQL_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: icingaweb2-mysql
|
||||
key: password
|
||||
- name: ICINGA2_API_HOST
|
||||
value: "icinga2.monitoring.svc.cluster.local"
|
||||
- name: ICINGA2_API_PORT
|
||||
value: "5665"
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: icingaweb2
|
||||
namespace: monitoring
|
||||
spec:
|
||||
selector:
|
||||
app: icingaweb2
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
name: http
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: freeradius-config
|
||||
namespace: network-services
|
||||
data:
|
||||
radiusd.conf: |
|
||||
prefix = /usr
|
||||
exec_prefix = /usr
|
||||
sysconfdir = /etc
|
||||
localstatedir = /var
|
||||
sbindir = ${exec_prefix}/sbin
|
||||
logdir = /var/log/freeradius
|
||||
raddbdir = /etc/freeradius
|
||||
radacctdir = ${logdir}/radacct
|
||||
|
||||
name = radiusd
|
||||
confdir = ${raddbdir}
|
||||
modconfdir = ${confdir}/mods-config
|
||||
certdir = ${confdir}/certs
|
||||
cadir = ${confdir}/certs
|
||||
run_dir = ${localstatedir}/run/${name}
|
||||
db_dir = ${localstatedir}/lib/${name}
|
||||
libdir = ${exec_prefix}/lib/freeradius
|
||||
pidfile = ${run_dir}/${name}.pid
|
||||
|
||||
max_request_time = 30
|
||||
cleanup_delay = 5
|
||||
max_requests = 1024
|
||||
|
||||
listen {
|
||||
type = auth
|
||||
ipaddr = *
|
||||
port = 1812
|
||||
}
|
||||
|
||||
listen {
|
||||
type = acct
|
||||
ipaddr = *
|
||||
port = 1813
|
||||
}
|
||||
|
||||
log {
|
||||
destination = stdout
|
||||
auth = yes
|
||||
auth_badpass = no
|
||||
auth_goodpass = no
|
||||
}
|
||||
|
||||
$INCLUDE mods-enabled/
|
||||
$INCLUDE sites-enabled/
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: freeradius-secrets
|
||||
namespace: network-services
|
||||
type: Opaque
|
||||
data:
|
||||
# Base64 encoded secrets
|
||||
clients.conf: |
|
||||
Y2xpZW50IGxvY2FsaG9zdCB7CiAgICBpcGFkZHIgPSAxMjcuMC4wLjEKICAgIHNlY3JldCA9IHRl
|
||||
c3RpbmcxMjMKfQo=
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: freeradius
|
||||
namespace: network-services
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: freeradius
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: freeradius
|
||||
spec:
|
||||
containers:
|
||||
- name: freeradius
|
||||
image: freeradius/freeradius-server:latest
|
||||
ports:
|
||||
- containerPort: 1812
|
||||
protocol: UDP
|
||||
name: auth
|
||||
- containerPort: 1813
|
||||
protocol: UDP
|
||||
name: acct
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /etc/freeradius/radiusd.conf
|
||||
subPath: radiusd.conf
|
||||
- name: secrets
|
||||
mountPath: /etc/freeradius/clients.conf
|
||||
subPath: clients.conf
|
||||
- name: data
|
||||
mountPath: /var/lib/freeradius
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: freeradius-config
|
||||
- name: secrets
|
||||
secret:
|
||||
secretName: freeradius-secrets
|
||||
- name: data
|
||||
emptyDir: {}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: freeradius
|
||||
namespace: network-services
|
||||
spec:
|
||||
selector:
|
||||
app: freeradius
|
||||
type: LoadBalancer
|
||||
ports:
|
||||
- port: 1812
|
||||
targetPort: 1812
|
||||
protocol: UDP
|
||||
name: auth
|
||||
- port: 1813
|
||||
targetPort: 1813
|
||||
protocol: UDP
|
||||
name: acct
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: unbound-config
|
||||
namespace: network-services
|
||||
data:
|
||||
unbound.conf: |
|
||||
server:
|
||||
interface: 0.0.0.0
|
||||
access-control: 10.0.0.0/8 allow
|
||||
access-control: 172.16.0.0/12 allow
|
||||
access-control: 192.168.0.0/16 allow
|
||||
|
||||
verbosity: 1
|
||||
|
||||
# Performance tuning
|
||||
num-threads: 4
|
||||
msg-cache-slabs: 8
|
||||
rrset-cache-slabs: 8
|
||||
infra-cache-slabs: 8
|
||||
key-cache-slabs: 8
|
||||
|
||||
# Cache sizes
|
||||
rrset-cache-size: 256m
|
||||
msg-cache-size: 128m
|
||||
|
||||
# Security
|
||||
hide-identity: yes
|
||||
hide-version: yes
|
||||
|
||||
# Forward zones
|
||||
include: /etc/unbound/forward-zones.conf
|
||||
|
||||
remote-control:
|
||||
control-enable: yes
|
||||
control-interface: 127.0.0.1
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: unbound-forward-zones
|
||||
namespace: network-services
|
||||
data:
|
||||
forward-zones.conf: |
|
||||
forward-zone:
|
||||
name: "."
|
||||
forward-addr: 8.8.8.8
|
||||
forward-addr: 8.8.4.4
|
||||
forward-addr: 1.1.1.1
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: unbound
|
||||
namespace: network-services
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: unbound
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: unbound
|
||||
spec:
|
||||
hostNetwork: true
|
||||
dnsPolicy: ClusterFirstWithHostNet
|
||||
containers:
|
||||
- name: unbound
|
||||
image: mvance/unbound:latest
|
||||
ports:
|
||||
- containerPort: 53
|
||||
protocol: UDP
|
||||
hostPort: 53
|
||||
- containerPort: 53
|
||||
protocol: TCP
|
||||
hostPort: 53
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /opt/unbound/etc/unbound/unbound.conf
|
||||
subPath: unbound.conf
|
||||
- name: forward-zones
|
||||
mountPath: /etc/unbound/forward-zones.conf
|
||||
subPath: forward-zones.conf
|
||||
securityContext:
|
||||
capabilities:
|
||||
add: ["NET_BIND_SERVICE"]
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "500m"
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: unbound-config
|
||||
- name: forward-zones
|
||||
configMap:
|
||||
name: unbound-forward-zones
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: unbound
|
||||
namespace: network-services
|
||||
spec:
|
||||
selector:
|
||||
app: unbound
|
||||
clusterIP: None
|
||||
ports:
|
||||
- port: 53
|
||||
protocol: UDP
|
||||
name: dns-udp
|
||||
- port: 53
|
||||
protocol: TCP
|
||||
name: dns-tcp
|
||||
40
clusters/vntx/setup-debian-template.sh
Executable file
40
clusters/vntx/setup-debian-template.sh
Executable file
|
|
@ -0,0 +1,40 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Script to create Debian 13 cloud-init template on Proxmox
|
||||
|
||||
echo "This script will create a Debian 13 (Bookworm) cloud-init template on your Proxmox host"
|
||||
echo "Run this on your Proxmox host (10.0.0.1 or 10.0.0.2)"
|
||||
echo
|
||||
echo "Commands to run:"
|
||||
echo
|
||||
|
||||
cat << 'EOF'
|
||||
# Download Debian 13 cloud image
|
||||
wget https://cloud.debian.org/images/cloud/bookworm/latest/debian-13-genericcloud-amd64.qcow2
|
||||
|
||||
# Create a new VM
|
||||
qm create 9000 --name debian13-cloud --memory 2048 --cores 2 --net0 virtio,bridge=vmbr0
|
||||
|
||||
# Import the downloaded disk to the VM
|
||||
qm importdisk 9000 debian-13-genericcloud-amd64.qcow2 local-lvm
|
||||
|
||||
# Attach the disk to the VM
|
||||
qm set 9000 --scsihw virtio-scsi-pci --scsi0 local-lvm:vm-9000-disk-0
|
||||
|
||||
# Set boot disk
|
||||
qm set 9000 --boot c --bootdisk scsi0
|
||||
|
||||
# Add cloud-init drive
|
||||
qm set 9000 --ide2 local-lvm:cloudinit
|
||||
|
||||
# Configure serial console
|
||||
qm set 9000 --serial0 socket --vga serial0
|
||||
|
||||
# Enable QEMU agent
|
||||
qm set 9000 --agent enabled=1
|
||||
|
||||
# Convert to template
|
||||
qm template 9000
|
||||
|
||||
echo "Template created! VM ID 9000 named 'debian13-cloud'"
|
||||
EOF
|
||||
40
clusters/vntx/setup-ubuntu-template.sh
Executable file
40
clusters/vntx/setup-ubuntu-template.sh
Executable file
|
|
@ -0,0 +1,40 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Script to create Ubuntu cloud-init template on Proxmox
|
||||
|
||||
echo "This script will create an Ubuntu 22.04 cloud-init template on your Proxmox host"
|
||||
echo "Run this on your Proxmox host (10.0.0.1)"
|
||||
echo
|
||||
echo "Commands to run:"
|
||||
echo
|
||||
|
||||
cat << 'EOF'
|
||||
# Download Ubuntu 22.04 cloud image
|
||||
wget https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img
|
||||
|
||||
# Create a new VM
|
||||
qm create 9000 --name ubuntu-cloud --memory 2048 --cores 2 --net0 virtio,bridge=vmbr0
|
||||
|
||||
# Import the downloaded disk to the VM
|
||||
qm importdisk 9000 jammy-server-cloudimg-amd64.img local-lvm
|
||||
|
||||
# Attach the disk to the VM
|
||||
qm set 9000 --scsihw virtio-scsi-pci --scsi0 local-lvm:vm-9000-disk-0
|
||||
|
||||
# Set boot disk
|
||||
qm set 9000 --boot c --bootdisk scsi0
|
||||
|
||||
# Add cloud-init drive
|
||||
qm set 9000 --ide2 local-lvm:cloudinit
|
||||
|
||||
# Configure serial console
|
||||
qm set 9000 --serial0 socket --vga serial0
|
||||
|
||||
# Enable QEMU agent
|
||||
qm set 9000 --agent enabled=1
|
||||
|
||||
# Convert to template
|
||||
qm template 9000
|
||||
|
||||
echo "Template created! VM ID 9000 named 'ubuntu-cloud'"
|
||||
EOF
|
||||
29
clusters/vntx/system/base/namespaces.yaml
Normal file
29
clusters/vntx/system/base/namespaces.yaml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: metallb-system
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: longhorn-system
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: cert-manager
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: monitoring
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: network-services
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: ingress-system
|
||||
17
clusters/vntx/system/metallb/config.yaml
Normal file
17
clusters/vntx/system/metallb/config.yaml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
apiVersion: metallb.io/v1beta1
|
||||
kind: IPAddressPool
|
||||
metadata:
|
||||
name: default-pool
|
||||
namespace: metallb-system
|
||||
spec:
|
||||
addresses:
|
||||
- 10.0.0.200-10.0.0.250 # Adjust based on your network
|
||||
---
|
||||
apiVersion: metallb.io/v1beta1
|
||||
kind: L2Advertisement
|
||||
metadata:
|
||||
name: default-advertisement
|
||||
namespace: metallb-system
|
||||
spec:
|
||||
ipAddressPools:
|
||||
- default-pool
|
||||
1
clusters/vntx/terraform.tfstate
Normal file
1
clusters/vntx/terraform.tfstate
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":4,"terraform_version":"1.10.5","serial":1,"lineage":"22c48096-8818-3e9f-61ec-3007015f4917","outputs":{},"resources":[],"check_results":null}
|
||||
24
clusters/vntx/terraform/.terraform.lock.hcl
generated
Normal file
24
clusters/vntx/terraform/.terraform.lock.hcl
generated
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# This file is maintained automatically by "tofu init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.opentofu.org/telmate/proxmox" {
|
||||
version = "2.9.14"
|
||||
constraints = "~> 2.9"
|
||||
hashes = [
|
||||
"h1:asZa5VKbWeCpLNv1JAutt5CdD27HaGFjxxcr6mvn8Ps=",
|
||||
"zh:0d049d33f705e5b814d30028770c084151218439424e99684ce31d7e26a720b5",
|
||||
"zh:20b1c64ed56d81de95f3f37b82b45b4654c0de26670c0e87a474c5cce13cd015",
|
||||
"zh:2946058abd1d8e50e475b9ec39781eb02576b40dbd80f4653fade4493a4514c6",
|
||||
"zh:29e50a25c456f040ce072f23ac57b5b82ebd3b916ca5ae6688332b5ec62adc4a",
|
||||
"zh:3612932306ce5f08db94868f526cbb8c56d0d3c6ebe1c11a83f92bbf94354296",
|
||||
"zh:42d1699b0abebaac82ea5a19f4393541d8bb2741bde204a8ac1028cdc29d1b14",
|
||||
"zh:5ffd5dc567262eb8aafdf2f6eac63f7f21361da9c5d75a3c36b479638a0001b0",
|
||||
"zh:6692ef323e3b89de99934ad731f6a1850525bf8142916ae28ea4e4048d73a787",
|
||||
"zh:a5afc98e9a4038516bb58e788cb77dea67a60dce780dfcd206d7373c5a56b776",
|
||||
"zh:bf902cded709d84fa27fbf91b589c241f2238a6c4924e4e479eebd74320b93a5",
|
||||
"zh:cab0e1e72c9cebcf669fc6f35ec28cb8ab2dffb0237afc8860aa40d23bf8a49f",
|
||||
"zh:e523b99a48beec83d9bc04b2d336266044f9f53514cefb652fe6768611847196",
|
||||
"zh:f593915e8a24829d322d2eaeedcb153328cf9042f0d84f66040dde1be70ede04",
|
||||
"zh:fba1aff541133e2129dfda0160369635ab48503d5c44b8407ce5922ecc15d0bd",
|
||||
]
|
||||
}
|
||||
108
clusters/vntx/terraform/main.tf
Normal file
108
clusters/vntx/terraform/main.tf
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
terraform {
|
||||
required_providers {
|
||||
proxmox = {
|
||||
source = "Telmate/proxmox"
|
||||
version = "2.9.11"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "proxmox" {
|
||||
pm_api_url = var.proxmox_api_url
|
||||
pm_user = var.proxmox_user
|
||||
pm_password = var.proxmox_password
|
||||
pm_tls_insecure = var.proxmox_tls_insecure
|
||||
}
|
||||
|
||||
# Control plane nodes
|
||||
resource "proxmox_vm_qemu" "k3s_control" {
|
||||
count = var.control_plane_count
|
||||
|
||||
name = "vntx-control-${count.index + 1}"
|
||||
target_node = var.proxmox_nodes[count.index % length(var.proxmox_nodes)]
|
||||
|
||||
# Use Debian 13 cloud image
|
||||
clone = var.debian_template_name
|
||||
|
||||
cores = var.control_plane_specs.cores
|
||||
memory = var.control_plane_specs.memory
|
||||
|
||||
boot = "order=scsi0"
|
||||
agent = 1
|
||||
|
||||
disk {
|
||||
size = var.control_plane_specs.disk_size
|
||||
storage = var.proxmox_storage
|
||||
type = "scsi"
|
||||
format = "qcow2"
|
||||
}
|
||||
|
||||
network {
|
||||
model = "virtio"
|
||||
bridge = var.network_bridge
|
||||
}
|
||||
|
||||
# Cloud-init
|
||||
os_type = "cloud-init"
|
||||
ipconfig0 = "ip=${var.control_plane_ips[count.index]}/24,gw=${var.gateway}"
|
||||
|
||||
sshkeys = var.ssh_public_key
|
||||
|
||||
lifecycle {
|
||||
ignore_changes = [
|
||||
network,
|
||||
disk,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
# Worker nodes
|
||||
resource "proxmox_vm_qemu" "k3s_worker" {
|
||||
count = var.worker_count
|
||||
|
||||
name = "vntx-worker-${count.index + 1}"
|
||||
target_node = var.proxmox_nodes[count.index % length(var.proxmox_nodes)]
|
||||
|
||||
# Use Debian 13 cloud image
|
||||
clone = var.debian_template_name
|
||||
|
||||
cores = var.worker_specs.cores
|
||||
memory = var.worker_specs.memory
|
||||
|
||||
boot = "order=scsi0"
|
||||
agent = 1
|
||||
|
||||
disk {
|
||||
size = var.worker_specs.disk_size
|
||||
storage = var.proxmox_storage
|
||||
type = "scsi"
|
||||
format = "qcow2"
|
||||
}
|
||||
|
||||
# Additional disk for container storage
|
||||
disk {
|
||||
size = var.worker_specs.storage_disk_size
|
||||
storage = var.proxmox_storage
|
||||
type = "scsi"
|
||||
format = "qcow2"
|
||||
slot = 1
|
||||
}
|
||||
|
||||
network {
|
||||
model = "virtio"
|
||||
bridge = var.network_bridge
|
||||
}
|
||||
|
||||
# Cloud-init
|
||||
os_type = "cloud-init"
|
||||
ipconfig0 = "ip=${var.worker_ips[count.index]}/24,gw=${var.gateway}"
|
||||
|
||||
sshkeys = var.ssh_public_key
|
||||
|
||||
lifecycle {
|
||||
ignore_changes = [
|
||||
network,
|
||||
disk,
|
||||
]
|
||||
}
|
||||
}
|
||||
27
clusters/vntx/terraform/outputs.tf
Normal file
27
clusters/vntx/terraform/outputs.tf
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
output "control_plane_ips" {
|
||||
value = proxmox_vm_qemu.k3s_control[*].default_ipv4_address
|
||||
description = "IP addresses of control plane nodes"
|
||||
}
|
||||
|
||||
output "worker_ips" {
|
||||
value = proxmox_vm_qemu.k3s_worker[*].default_ipv4_address
|
||||
description = "IP addresses of worker nodes"
|
||||
}
|
||||
|
||||
output "cluster_config" {
|
||||
value = {
|
||||
control_nodes = [
|
||||
for i, vm in proxmox_vm_qemu.k3s_control : {
|
||||
name = vm.name
|
||||
ip = var.control_plane_ips[i]
|
||||
}
|
||||
]
|
||||
worker_nodes = [
|
||||
for i, vm in proxmox_vm_qemu.k3s_worker : {
|
||||
name = vm.name
|
||||
ip = var.worker_ips[i]
|
||||
}
|
||||
]
|
||||
}
|
||||
description = "Cluster configuration for Ansible"
|
||||
}
|
||||
1
clusters/vntx/terraform/terraform.tfstate
Normal file
1
clusters/vntx/terraform/terraform.tfstate
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":4,"terraform_version":"1.10.5","serial":6,"lineage":"dec2afcf-a221-0f9b-d460-39a11719c258","outputs":{"cluster_config":{"value":{"control_nodes":[{"ip":"10.0.0.101","name":"vntx-control-1"},{"ip":"10.0.0.102","name":"vntx-control-2"},{"ip":"10.0.0.103","name":"vntx-control-3"}],"worker_nodes":[{"ip":"10.0.0.111","name":"vntx-worker-1"},{"ip":"10.0.0.112","name":"vntx-worker-2"}]},"type":["object",{"control_nodes":["tuple",[["object",{"ip":"string","name":"string"}],["object",{"ip":"string","name":"string"}],["object",{"ip":"string","name":"string"}]]],"worker_nodes":["tuple",[["object",{"ip":"string","name":"string"}],["object",{"ip":"string","name":"string"}]]]}]}},"resources":[],"check_results":null}
|
||||
1
clusters/vntx/terraform/terraform.tfstate.backup
Normal file
1
clusters/vntx/terraform/terraform.tfstate.backup
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":4,"terraform_version":"1.10.5","serial":5,"lineage":"dec2afcf-a221-0f9b-d460-39a11719c258","outputs":{"cluster_config":{"value":{"control_nodes":[{"ip":"10.0.0.101","name":"vntx-control-1"},{"ip":"10.0.0.102","name":"vntx-control-2"},{"ip":"10.0.0.103","name":"vntx-control-3"}],"worker_nodes":[{"ip":"10.0.0.111","name":"vntx-worker-1"},{"ip":"10.0.0.112","name":"vntx-worker-2"}]},"type":["object",{"control_nodes":["tuple",[["object",{"ip":"string","name":"string"}],["object",{"ip":"string","name":"string"}],["object",{"ip":"string","name":"string"}]]],"worker_nodes":["tuple",[["object",{"ip":"string","name":"string"}],["object",{"ip":"string","name":"string"}]]]}]}},"resources":[{"mode":"managed","type":"proxmox_vm_qemu","name":"k3s_worker","provider":"provider[\"registry.opentofu.org/telmate/proxmox\"]","instances":[]}],"check_results":null}
|
||||
38
clusters/vntx/terraform/terraform.tfvars
Normal file
38
clusters/vntx/terraform/terraform.tfvars
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# Proxmox connection
|
||||
proxmox_api_url = "https://10.0.0.1:8006/api2/json"
|
||||
proxmox_host = "10.0.0.1"
|
||||
proxmox_user = "root@pam"
|
||||
# proxmox_password = "set via TF_VAR_proxmox_password environment variable"
|
||||
|
||||
# Proxmox cluster nodes - deploying all on vm2-380
|
||||
proxmox_nodes = ["vm2-380"]
|
||||
|
||||
# Debian template name (you'll need to create this)
|
||||
debian_template_name = "debian13-cloud"
|
||||
|
||||
# Storage
|
||||
proxmox_storage = "local-lvm" # Adjust to your storage pool name
|
||||
|
||||
# Network configuration
|
||||
network_bridge = "vmbr0"
|
||||
vlan_tag = -1 # Set to VLAN ID if needed, -1 for no VLAN
|
||||
gateway = "10.0.0.1"
|
||||
|
||||
# Reduce node count for 2-host setup
|
||||
control_plane_count = 3 # Can run on 2 hosts
|
||||
worker_count = 2
|
||||
|
||||
# Node IPs - adjust for your network
|
||||
control_plane_ips = [
|
||||
"10.0.0.101",
|
||||
"10.0.0.102",
|
||||
"10.0.0.103"
|
||||
]
|
||||
|
||||
worker_ips = [
|
||||
"10.0.0.111",
|
||||
"10.0.0.112"
|
||||
]
|
||||
|
||||
# SSH key for node access
|
||||
ssh_public_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHLyVSEEAEbGZZqwPwEup0XrlTpu3x3iF9ExvvQPA4xN graham@mcintire.me"
|
||||
31
clusters/vntx/terraform/terraform.tfvars.example
Normal file
31
clusters/vntx/terraform/terraform.tfvars.example
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# Proxmox connection
|
||||
proxmox_api_url = "https://pve1.vntx.net:8006/api2/json"
|
||||
proxmox_host = "pve1.vntx.net"
|
||||
proxmox_user = "root@pam"
|
||||
# proxmox_password = "set via TF_VAR_proxmox_password"
|
||||
|
||||
# Proxmox cluster nodes
|
||||
proxmox_nodes = ["pve1", "pve2", "pve3"]
|
||||
|
||||
# Storage
|
||||
proxmox_storage = "local-lvm"
|
||||
|
||||
# Network configuration
|
||||
network_bridge = "vmbr0"
|
||||
vlan_tag = -1 # Set to VLAN ID if needed, -1 for no VLAN
|
||||
gateway = "10.0.0.1"
|
||||
|
||||
# Node IPs
|
||||
control_plane_ips = [
|
||||
"10.0.0.101",
|
||||
"10.0.0.102",
|
||||
"10.0.0.103"
|
||||
]
|
||||
|
||||
worker_ips = [
|
||||
"10.0.0.111",
|
||||
"10.0.0.112"
|
||||
]
|
||||
|
||||
# SSH key for node access
|
||||
ssh_public_key = "ssh-rsa AAAAB3..."
|
||||
BIN
clusters/vntx/terraform/tfplan
Normal file
BIN
clusters/vntx/terraform/tfplan
Normal file
Binary file not shown.
129
clusters/vntx/terraform/variables.tf
Normal file
129
clusters/vntx/terraform/variables.tf
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
variable "proxmox_api_url" {
|
||||
description = "Proxmox API URL"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "proxmox_user" {
|
||||
description = "Proxmox user"
|
||||
type = string
|
||||
default = "root@pam"
|
||||
}
|
||||
|
||||
variable "proxmox_password" {
|
||||
description = "Proxmox password"
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "proxmox_tls_insecure" {
|
||||
description = "Allow insecure TLS for Proxmox API"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "proxmox_host" {
|
||||
description = "Proxmox host for SSH commands"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "proxmox_nodes" {
|
||||
description = "List of Proxmox nodes to distribute VMs across"
|
||||
type = list(string)
|
||||
default = ["pve1", "pve2", "pve3"]
|
||||
}
|
||||
|
||||
variable "proxmox_storage" {
|
||||
description = "Proxmox storage pool"
|
||||
type = string
|
||||
default = "local-lvm"
|
||||
}
|
||||
|
||||
variable "debian_template_name" {
|
||||
description = "Name of Debian cloud-init template VM"
|
||||
type = string
|
||||
default = "debian13-cloud"
|
||||
}
|
||||
|
||||
variable "network_bridge" {
|
||||
description = "Network bridge"
|
||||
type = string
|
||||
default = "vmbr0"
|
||||
}
|
||||
|
||||
variable "vlan_tag" {
|
||||
description = "VLAN tag for cluster network"
|
||||
type = number
|
||||
default = -1
|
||||
}
|
||||
|
||||
variable "gateway" {
|
||||
description = "Network gateway"
|
||||
type = string
|
||||
default = "10.0.0.1"
|
||||
}
|
||||
|
||||
variable "control_plane_count" {
|
||||
description = "Number of control plane nodes"
|
||||
type = number
|
||||
default = 3
|
||||
}
|
||||
|
||||
variable "worker_count" {
|
||||
description = "Number of worker nodes"
|
||||
type = number
|
||||
default = 2
|
||||
}
|
||||
|
||||
variable "control_plane_specs" {
|
||||
description = "Specifications for control plane nodes"
|
||||
type = object({
|
||||
cores = number
|
||||
memory = number
|
||||
disk_size = string
|
||||
})
|
||||
default = {
|
||||
cores = 4
|
||||
memory = 8192
|
||||
disk_size = "50G"
|
||||
}
|
||||
}
|
||||
|
||||
variable "worker_specs" {
|
||||
description = "Specifications for worker nodes"
|
||||
type = object({
|
||||
cores = number
|
||||
memory = number
|
||||
disk_size = string
|
||||
storage_disk_size = string
|
||||
})
|
||||
default = {
|
||||
cores = 8
|
||||
memory = 16384
|
||||
disk_size = "50G"
|
||||
storage_disk_size = "200G"
|
||||
}
|
||||
}
|
||||
|
||||
variable "control_plane_ips" {
|
||||
description = "Static IPs for control plane nodes"
|
||||
type = list(string)
|
||||
default = [
|
||||
"10.0.0.101",
|
||||
"10.0.0.102",
|
||||
"10.0.0.103"
|
||||
]
|
||||
}
|
||||
|
||||
variable "worker_ips" {
|
||||
description = "Static IPs for worker nodes"
|
||||
type = list(string)
|
||||
default = [
|
||||
"10.0.0.111",
|
||||
"10.0.0.112"
|
||||
]
|
||||
}
|
||||
|
||||
variable "ssh_public_key" {
|
||||
description = "SSH public key for node access"
|
||||
type = string
|
||||
}
|
||||
162
home/ansible/caddy-proxy.yml
Normal file
162
home/ansible/caddy-proxy.yml
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
---
|
||||
- name: Configure Caddy reverse proxy with Tailscale on Debian 13
|
||||
hosts: vultr
|
||||
become: yes
|
||||
vars:
|
||||
tailscale_key: "{{ lookup('env', 'TAILSCALE_KEY') }}"
|
||||
|
||||
tasks:
|
||||
- name: Wait for cloud-init to complete
|
||||
wait_for:
|
||||
path: /var/lib/cloud/instance/boot-finished
|
||||
timeout: 300
|
||||
|
||||
- name: Check cloud-init status
|
||||
command: cloud-init status --wait
|
||||
register: cloud_init_status
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Show cloud-init status
|
||||
debug:
|
||||
var: cloud_init_status.stdout
|
||||
|
||||
- name: Check if Caddy is installed
|
||||
command: which caddy
|
||||
register: caddy_installed
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Install Caddy if not present
|
||||
apt:
|
||||
name: caddy
|
||||
state: present
|
||||
update_cache: yes
|
||||
when: caddy_installed.rc != 0
|
||||
|
||||
- name: Verify Caddy is installed and running
|
||||
service:
|
||||
name: caddy
|
||||
state: started
|
||||
enabled: yes
|
||||
|
||||
- name: Check if Tailscale is installed
|
||||
command: which tailscale
|
||||
register: tailscale_check
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Install Tailscale on Debian
|
||||
block:
|
||||
- name: Install Tailscale repository key
|
||||
get_url:
|
||||
url: https://pkgs.tailscale.com/stable/debian/bookworm.noarmor.gpg
|
||||
dest: /usr/share/keyrings/tailscale-archive-keyring.gpg
|
||||
|
||||
- name: Add Tailscale repository
|
||||
apt_repository:
|
||||
repo: "deb [signed-by=/usr/share/keyrings/tailscale-archive-keyring.gpg] https://pkgs.tailscale.com/stable/debian bookworm main"
|
||||
state: present
|
||||
filename: tailscale
|
||||
|
||||
- name: Update apt cache
|
||||
apt:
|
||||
update_cache: yes
|
||||
|
||||
- name: Install Tailscale
|
||||
apt:
|
||||
name: tailscale
|
||||
state: present
|
||||
|
||||
- name: Enable and start Tailscale daemon
|
||||
service:
|
||||
name: tailscaled
|
||||
state: started
|
||||
enabled: yes
|
||||
when:
|
||||
- ansible_os_family == "Debian"
|
||||
- tailscale_check.rc != 0
|
||||
|
||||
- name: Verify Tailscale is installed
|
||||
command: tailscale version
|
||||
register: tailscale_version
|
||||
changed_when: false
|
||||
|
||||
- name: Show Tailscale version
|
||||
debug:
|
||||
var: tailscale_version.stdout
|
||||
|
||||
|
||||
- name: Check if Tailscale is already authenticated
|
||||
command: tailscale status --json
|
||||
register: tailscale_status_check
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Restart tailscaled service
|
||||
service:
|
||||
name: tailscaled
|
||||
state: restarted
|
||||
register: restart_result
|
||||
|
||||
- name: Wait for tailscaled to start
|
||||
pause:
|
||||
seconds: 5
|
||||
when: restart_result is changed
|
||||
|
||||
- name: Try to authenticate with explicit socket path
|
||||
command: tailscale --socket=/var/run/tailscale/tailscaled.sock up --authkey="{{ tailscale_key }}" --accept-routes --hostname=caddy
|
||||
when:
|
||||
- tailscale_key is defined
|
||||
- tailscale_key != ""
|
||||
environment:
|
||||
TAILSCALE_SOCKET: /var/run/tailscale/tailscaled.sock
|
||||
register: tailscale_auth
|
||||
failed_when: false
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Debug authentication result
|
||||
debug:
|
||||
msg: |
|
||||
RC: {{ tailscale_auth.rc | default('N/A') }}
|
||||
STDOUT: {{ tailscale_auth.stdout | default('') }}
|
||||
STDERR: {{ tailscale_auth.stderr | default('') }}
|
||||
when: tailscale_auth is defined and tailscale_auth is not skipped
|
||||
|
||||
- name: Get Tailscale status
|
||||
command: tailscale --socket=/var/run/tailscale/tailscaled.sock status
|
||||
register: tailscale_final_status
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Show final Tailscale status
|
||||
debug:
|
||||
var: tailscale_final_status.stdout_lines
|
||||
when: tailscale_final_status is defined and tailscale_final_status.stdout_lines is defined
|
||||
|
||||
- name: Show Tailscale setup issue
|
||||
debug:
|
||||
msg: |
|
||||
WARNING: Tailscale authentication failed.
|
||||
You may need to manually authenticate Tailscale on this host.
|
||||
|
||||
To debug further, SSH to the host and try:
|
||||
tailscale up --authkey=YOUR_KEY
|
||||
when: tailscale_auth is defined and tailscale_auth is not skipped and tailscale_auth.rc != 0
|
||||
|
||||
- name: Update Caddyfile with any custom configurations
|
||||
template:
|
||||
src: Caddyfile.j2
|
||||
dest: /etc/caddy/Caddyfile
|
||||
owner: caddy
|
||||
group: caddy
|
||||
mode: "0644"
|
||||
backup: yes
|
||||
notify: reload caddy
|
||||
when: false # Disable for now - can be enabled when you have custom configs
|
||||
|
||||
handlers:
|
||||
- name: reload caddy
|
||||
service:
|
||||
name: caddy
|
||||
state: reloaded
|
||||
40
home/ansible/check-ceph-status.yml
Normal file
40
home/ansible/check-ceph-status.yml
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
- name: Check Ceph status on Proxmox
|
||||
hosts: proxmox[0]
|
||||
become: true
|
||||
tasks:
|
||||
- name: Check Ceph status
|
||||
command: ceph status
|
||||
register: ceph_status
|
||||
changed_when: false
|
||||
|
||||
- name: Display Ceph status
|
||||
debug:
|
||||
var: ceph_status.stdout_lines
|
||||
|
||||
- name: List Ceph pools
|
||||
command: ceph osd pool ls
|
||||
register: ceph_pools
|
||||
changed_when: false
|
||||
|
||||
- name: Display Ceph pools
|
||||
debug:
|
||||
msg: "Available pools: {{ ceph_pools.stdout_lines }}"
|
||||
|
||||
- name: List Ceph auth users
|
||||
command: ceph auth ls
|
||||
register: ceph_users
|
||||
changed_when: false
|
||||
|
||||
- name: Check if kubernetes user exists
|
||||
debug:
|
||||
msg: "Kubernetes user exists: {{ 'client.kubernetes' in ceph_users.stdout }}"
|
||||
|
||||
- name: Get Ceph config
|
||||
command: ceph config generate-minimal-conf
|
||||
register: ceph_conf
|
||||
changed_when: false
|
||||
|
||||
- name: Display Ceph config
|
||||
debug:
|
||||
var: ceph_conf.stdout_lines
|
||||
29
home/ansible/check-postgres.yml
Normal file
29
home/ansible/check-postgres.yml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
- name: Check PostgreSQL configuration
|
||||
hosts: proxmox
|
||||
become: yes
|
||||
gather_facts: no
|
||||
vars:
|
||||
ct_id: 300
|
||||
|
||||
tasks:
|
||||
- name: Find PostgreSQL version and config location
|
||||
shell: |
|
||||
pct exec {{ ct_id }} -- bash -c "
|
||||
# Check if PostgreSQL is installed
|
||||
which psql || echo 'psql not found'
|
||||
|
||||
# Find PostgreSQL version
|
||||
dpkg -l | grep postgresql-[0-9] | grep -v client || echo 'PostgreSQL package not found'
|
||||
|
||||
# Find pg_hba.conf location
|
||||
find /etc -name pg_hba.conf 2>/dev/null || echo 'pg_hba.conf not found'
|
||||
|
||||
# Check if PostgreSQL is running
|
||||
systemctl status postgresql --no-pager || echo 'PostgreSQL service not found'
|
||||
"
|
||||
register: postgres_info
|
||||
|
||||
- name: Display PostgreSQL information
|
||||
debug:
|
||||
msg: "{{ postgres_info.stdout_lines }}"
|
||||
10
home/ansible/create-ceph-keyfile.yml
Normal file
10
home/ansible/create-ceph-keyfile.yml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
- name: Create Ceph keyfile for mounting
|
||||
hosts: proxmox[0]
|
||||
become: true
|
||||
tasks:
|
||||
- name: Create kubernetes key file
|
||||
copy:
|
||||
content: "AQAq14Jo/g4ZORAAwxpPOmqj1PYgmih6s6b4vA=="
|
||||
dest: /etc/ceph/client.kubernetes.key
|
||||
mode: '0600'
|
||||
56
home/ansible/create-cephfs-share.yml
Normal file
56
home/ansible/create-cephfs-share.yml
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
- name: Setup CephFS for Kubernetes shared storage
|
||||
hosts: proxmox[0]
|
||||
become: true
|
||||
vars:
|
||||
cephfs_name: kubernetes-shared
|
||||
|
||||
tasks:
|
||||
- name: Check if CephFS already exists
|
||||
command: ceph fs ls
|
||||
register: cephfs_list
|
||||
changed_when: false
|
||||
|
||||
- name: Display existing CephFS
|
||||
debug:
|
||||
var: cephfs_list.stdout_lines
|
||||
|
||||
- name: Create CephFS data pool if needed
|
||||
command: ceph osd pool create cephfs_kubernetes_data 32
|
||||
register: data_pool
|
||||
failed_when:
|
||||
- data_pool.rc != 0
|
||||
- "'already exists' not in data_pool.stderr"
|
||||
changed_when: data_pool.rc == 0
|
||||
|
||||
- name: Create CephFS metadata pool if needed
|
||||
command: ceph osd pool create cephfs_kubernetes_metadata 16
|
||||
register: metadata_pool
|
||||
failed_when:
|
||||
- metadata_pool.rc != 0
|
||||
- "'already exists' not in metadata_pool.stderr"
|
||||
changed_when: metadata_pool.rc == 0
|
||||
|
||||
- name: Create CephFS if needed
|
||||
command: ceph fs new {{ cephfs_name }} cephfs_kubernetes_metadata cephfs_kubernetes_data
|
||||
register: cephfs_create
|
||||
failed_when:
|
||||
- cephfs_create.rc != 0
|
||||
- "'already exists' not in cephfs_create.stderr"
|
||||
changed_when: cephfs_create.rc == 0
|
||||
|
||||
- name: Get CephFS status
|
||||
command: ceph fs status {{ cephfs_name }}
|
||||
register: cephfs_status
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Display CephFS status
|
||||
debug:
|
||||
var: cephfs_status.stdout_lines
|
||||
when: cephfs_status.rc == 0
|
||||
|
||||
- name: Ensure kubernetes user has CephFS permissions
|
||||
command: |
|
||||
ceph auth caps client.kubernetes mon 'allow r' mds 'allow rw' osd 'allow rw pool=cephfs_kubernetes_data, allow rw pool=cephfs_kubernetes_metadata, allow rw pool=kubernetes'
|
||||
changed_when: false
|
||||
65
home/ansible/create-debian-template.yml
Normal file
65
home/ansible/create-debian-template.yml
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
---
|
||||
- name: Create Debian 13 Cloud-Init Template on Proxmox
|
||||
hosts: lab02 # Run on first Proxmox host
|
||||
gather_facts: no
|
||||
vars:
|
||||
template_vmid: 9000
|
||||
template_name: "debian-13-cloudinit"
|
||||
storage: "ceph"
|
||||
|
||||
tasks:
|
||||
- name: Check if template already exists
|
||||
command: qm status {{ template_vmid }}
|
||||
register: vm_check
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
|
||||
- name: Template creation block
|
||||
when: vm_check.rc != 0
|
||||
block:
|
||||
- name: Download Debian 13 cloud image
|
||||
get_url:
|
||||
url: https://cloud.debian.org/images/cloud/trixie/latest/debian-13-generic-amd64.qcow2
|
||||
dest: /var/lib/vz/template/iso/debian-13-generic-amd64.qcow2
|
||||
mode: '0644'
|
||||
|
||||
- name: Create VM for template
|
||||
command: qm create {{ template_vmid }} --name {{ template_name }} --memory 2048 --cores 2 --net0 virtio,bridge=vmbr0 --scsihw virtio-scsi-single
|
||||
|
||||
- name: Import disk image
|
||||
command: qm importdisk {{ template_vmid }} /var/lib/vz/template/iso/debian-13-generic-amd64.qcow2 {{ storage }}
|
||||
register: import_result
|
||||
|
||||
- name: Get imported disk name
|
||||
set_fact:
|
||||
imported_disk: "{{ storage }}:vm-{{ template_vmid }}-disk-0"
|
||||
|
||||
- name: Attach imported disk
|
||||
command: qm set {{ template_vmid }} --scsi0 {{ imported_disk }}
|
||||
|
||||
- name: Add cloud-init drive
|
||||
command: qm set {{ template_vmid }} --ide2 {{ storage }}:cloudinit
|
||||
|
||||
- name: Configure boot and display settings
|
||||
command: qm set {{ template_vmid }} --boot c --bootdisk scsi0 --serial0 socket --vga serial0
|
||||
|
||||
- name: Enable QEMU guest agent
|
||||
command: qm set {{ template_vmid }} --agent enabled=1
|
||||
|
||||
- name: Set cloud-init defaults
|
||||
command: qm set {{ template_vmid }} --ipconfig0 ip=dhcp --ciuser debian
|
||||
|
||||
- name: Resize disk to 20G
|
||||
command: qm resize {{ template_vmid }} scsi0 20G
|
||||
|
||||
- name: Convert to template
|
||||
command: qm template {{ template_vmid }}
|
||||
|
||||
- name: Clean up downloaded image
|
||||
file:
|
||||
path: /var/lib/vz/template/iso/debian-13-generic-amd64.qcow2
|
||||
state: absent
|
||||
|
||||
- name: Template ready
|
||||
debug:
|
||||
msg: "Template '{{ template_name }}' (ID: {{ template_vmid }}) is ready on {{ inventory_hostname }}"
|
||||
10
home/ansible/create-forgejo-storage.yml
Normal file
10
home/ansible/create-forgejo-storage.yml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
- name: Create Forgejo storage directory
|
||||
hosts: proxmox[0]
|
||||
become: true
|
||||
tasks:
|
||||
- name: Create Forgejo directory on CephFS
|
||||
file:
|
||||
path: /mnt/cephfs-kubernetes/volumes/forgejo
|
||||
state: directory
|
||||
mode: '0777'
|
||||
82
home/ansible/create-k3s-vms.yml
Normal file
82
home/ansible/create-k3s-vms.yml
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
---
|
||||
- name: Create K3s VMs on Proxmox
|
||||
hosts: lab02
|
||||
gather_facts: no
|
||||
vars:
|
||||
template_vmid: 9000
|
||||
cluster_name: "home-k3s"
|
||||
network_gateway: "10.0.19.254"
|
||||
ssh_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHLyVSEEAEbGZZqwPwEup0XrlTpu3x3iF9ExvvQPA4xN graham@mcintire.me"
|
||||
|
||||
control_nodes:
|
||||
- { vmid: 1100, name: "{{ cluster_name }}-control-1", ip: "10.0.19.100/22", cores: 2, memory: 8192, disk: "32G", node: "lab02" }
|
||||
- { vmid: 1101, name: "{{ cluster_name }}-control-2", ip: "10.0.19.101/22", cores: 2, memory: 8192, disk: "32G", node: "lab03" }
|
||||
- { vmid: 1102, name: "{{ cluster_name }}-control-3", ip: "10.0.19.102/22", cores: 2, memory: 8192, disk: "32G", node: "lab04" }
|
||||
|
||||
worker_nodes:
|
||||
- { vmid: 1200, name: "{{ cluster_name }}-worker-1", ip: "10.0.19.110/22", cores: 4, memory: 8192, disk: "64G", node: "lab02" }
|
||||
- { vmid: 1201, name: "{{ cluster_name }}-worker-2", ip: "10.0.19.111/22", cores: 4, memory: 8192, disk: "64G", node: "lab03" }
|
||||
- { vmid: 1202, name: "{{ cluster_name }}-worker-3", ip: "10.0.19.112/22", cores: 4, memory: 8192, disk: "64G", node: "lab04" }
|
||||
|
||||
all_nodes: "{{ control_nodes + worker_nodes }}"
|
||||
|
||||
tasks:
|
||||
- name: Check which VMs already exist
|
||||
command: qm status {{ item.vmid }}
|
||||
loop: "{{ all_nodes }}"
|
||||
register: vm_checks
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
|
||||
- name: Create VMs that don't exist
|
||||
command: |
|
||||
qm clone {{ template_vmid }} {{ item.item.vmid }} \
|
||||
--name {{ item.item.name }} \
|
||||
--full true
|
||||
loop: "{{ vm_checks.results }}"
|
||||
when: item.rc != 0
|
||||
loop_control:
|
||||
label: "{{ item.item.name }}"
|
||||
|
||||
- name: Configure VMs
|
||||
command: |
|
||||
qm set {{ item.vmid }} \
|
||||
--cores {{ item.cores }} \
|
||||
--memory {{ item.memory }} \
|
||||
--ipconfig0 ip={{ item.ip }},gw={{ network_gateway }} \
|
||||
--sshkeys /root/.ssh/authorized_keys \
|
||||
--onboot 1
|
||||
loop: "{{ all_nodes }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
|
||||
- name: Resize disks
|
||||
command: qm resize {{ item.vmid }} scsi0 {{ item.disk }}
|
||||
loop: "{{ all_nodes }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
register: resize_result
|
||||
failed_when:
|
||||
- resize_result.rc != 0
|
||||
- "'shrinking disks is not supported' not in resize_result.stderr"
|
||||
|
||||
- name: Start VMs
|
||||
command: qm start {{ item.vmid }}
|
||||
loop: "{{ all_nodes }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
register: start_result
|
||||
failed_when:
|
||||
- start_result.rc != 0
|
||||
- "'already running' not in start_result.stderr"
|
||||
|
||||
- name: Migrate VMs to their target nodes
|
||||
command: qm migrate {{ item.vmid }} {{ item.node }} --online
|
||||
loop: "{{ all_nodes }}"
|
||||
when: item.node != 'lab02'
|
||||
loop_control:
|
||||
label: "{{ item.name }} -> {{ item.node }}"
|
||||
|
||||
- name: VM creation complete
|
||||
debug:
|
||||
msg: "All K3s VMs have been created and distributed across the cluster"
|
||||
63
home/ansible/create-postgres-lxc.yml
Normal file
63
home/ansible/create-postgres-lxc.yml
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
---
|
||||
- name: Create PostgreSQL LXC Container on Proxmox
|
||||
hosts: proxmox[0]
|
||||
become: true
|
||||
vars:
|
||||
ct_id: 300
|
||||
ct_name: postgres
|
||||
ct_template: "local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst"
|
||||
ct_memory: 8192 # 8GB RAM
|
||||
ct_disk_size: 1024 # 1TB storage
|
||||
ct_cores: 4
|
||||
ct_ip: "10.0.19.5/22"
|
||||
ct_gateway: "10.0.19.254"
|
||||
ct_storage: "ceph" # Use Ceph storage
|
||||
|
||||
tasks:
|
||||
- name: Check if container already exists
|
||||
command: pct status {{ ct_id }}
|
||||
register: ct_exists
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
|
||||
- name: Create PostgreSQL LXC container
|
||||
command: |
|
||||
pct create {{ ct_id }} {{ ct_template }} \
|
||||
--hostname {{ ct_name }} \
|
||||
--memory {{ ct_memory }} \
|
||||
--cores {{ ct_cores }} \
|
||||
--net0 name=eth0,bridge=vmbr0,ip={{ ct_ip }},gw={{ ct_gateway }} \
|
||||
--storage {{ ct_storage }} \
|
||||
--rootfs {{ ct_storage }}:{{ ct_disk_size }} \
|
||||
--unprivileged 1 \
|
||||
--features nesting=1 \
|
||||
--onboot 1 \
|
||||
--startup order=10
|
||||
when: ct_exists.rc != 0
|
||||
|
||||
- name: Start container
|
||||
command: pct start {{ ct_id }}
|
||||
when: ct_exists.rc != 0
|
||||
|
||||
- name: Wait for container to be ready
|
||||
wait_for:
|
||||
host: 10.0.19.5
|
||||
port: 22
|
||||
timeout: 60
|
||||
delay: 10
|
||||
when: ct_exists.rc != 0
|
||||
|
||||
- name: Update container packages
|
||||
shell: pct exec {{ ct_id }} -- apt update && pct exec {{ ct_id }} -- apt upgrade -y
|
||||
when: ct_exists.rc != 0
|
||||
|
||||
- name: Display container info
|
||||
debug:
|
||||
msg: |
|
||||
PostgreSQL LXC Container created:
|
||||
- ID: {{ ct_id }}
|
||||
- Name: {{ ct_name }}
|
||||
- IP: 10.0.19.5
|
||||
- RAM: {{ ct_memory }}MB
|
||||
- Storage: {{ ct_disk_size }}GB
|
||||
- Cores: {{ ct_cores }}
|
||||
12
home/ansible/create-postgres-storage.yml
Normal file
12
home/ansible/create-postgres-storage.yml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
- name: Create PostgreSQL storage directory
|
||||
hosts: proxmox[0]
|
||||
become: true
|
||||
tasks:
|
||||
- name: Create PostgreSQL directory on CephFS
|
||||
file:
|
||||
path: /mnt/cephfs-kubernetes/volumes/postgresql
|
||||
state: directory
|
||||
mode: '0700'
|
||||
owner: '999' # PostgreSQL container user
|
||||
group: '999'
|
||||
44
home/ansible/create-rbd-volumes.yml
Normal file
44
home/ansible/create-rbd-volumes.yml
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
---
|
||||
- name: Create RBD volumes on Proxmox Ceph
|
||||
hosts: proxmox[0]
|
||||
become: true
|
||||
vars:
|
||||
volumes:
|
||||
- name: adguard-config
|
||||
size: 2G
|
||||
- name: adguard-data
|
||||
size: 10G
|
||||
- name: nodered-data
|
||||
size: 5G
|
||||
|
||||
tasks:
|
||||
- name: Create RBD volumes
|
||||
shell: |
|
||||
rbd create kubernetes/{{ item.name }} --size {{ item.size }} --image-feature layering
|
||||
loop: "{{ volumes }}"
|
||||
register: rbd_create
|
||||
failed_when:
|
||||
- rbd_create.rc != 0
|
||||
- "'already exists' not in rbd_create.stderr"
|
||||
changed_when: rbd_create.rc == 0
|
||||
|
||||
- name: List created volumes
|
||||
command: rbd ls kubernetes
|
||||
register: rbd_list
|
||||
changed_when: false
|
||||
|
||||
- name: Display volumes
|
||||
debug:
|
||||
msg: "Created volumes in kubernetes pool: {{ rbd_list.stdout_lines }}"
|
||||
|
||||
- name: Get volume info
|
||||
shell: |
|
||||
rbd info kubernetes/{{ item.name }} --format json
|
||||
loop: "{{ volumes }}"
|
||||
register: volume_info
|
||||
changed_when: false
|
||||
|
||||
- name: Display volume details
|
||||
debug:
|
||||
msg: "Volume {{ item.item.name }}: {{ (item.stdout | from_json).size | int / 1024 / 1024 / 1024 }}GB"
|
||||
loop: "{{ volume_info.results }}"
|
||||
138
home/ansible/deploy-ceph-csi.yml
Normal file
138
home/ansible/deploy-ceph-csi.yml
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
---
|
||||
- name: Deploy Ceph CSI for direct RBD access
|
||||
hosts: localhost
|
||||
connection: local
|
||||
vars:
|
||||
kubeconfig: "{{ playbook_dir }}/kubeconfig"
|
||||
ceph_csi_dir: "../cluster/ceph-csi"
|
||||
|
||||
tasks:
|
||||
- name: Create temporary directory for fixed configs
|
||||
tempfile:
|
||||
state: directory
|
||||
register: temp_dir
|
||||
|
||||
- name: Copy original kustomization.yaml
|
||||
copy:
|
||||
src: "{{ ceph_csi_dir }}/kustomization.yaml"
|
||||
dest: "{{ temp_dir.path }}/kustomization.yaml"
|
||||
|
||||
- name: Update kustomization to use fixed files
|
||||
replace:
|
||||
path: "{{ temp_dir.path }}/kustomization.yaml"
|
||||
regexp: "{{ item.old }}"
|
||||
replace: "{{ item.new }}"
|
||||
loop:
|
||||
- { old: "csi-rbdplugin.yaml", new: "csi-rbdplugin-fixed.yaml" }
|
||||
- { old: "csi-rbdplugin-provisioner.yaml", new: "csi-rbdplugin-provisioner-fixed.yaml" }
|
||||
- { old: "ceph-config.yaml", new: "ceph-config-fixed.yaml" }
|
||||
|
||||
- name: Copy all required files to temp directory
|
||||
copy:
|
||||
src: "{{ ceph_csi_dir }}/{{ item }}"
|
||||
dest: "{{ temp_dir.path }}/{{ item }}"
|
||||
loop:
|
||||
- namespace.yaml
|
||||
- ceph-csi-configmap.yaml
|
||||
- ceph-csi-secret.yaml
|
||||
- ceph-csi-rbd-driver.yaml
|
||||
- csi-driver.yaml
|
||||
- ceph-storageclass.yaml
|
||||
- csi-rbdplugin-fixed.yaml
|
||||
- csi-rbdplugin-provisioner-fixed.yaml
|
||||
- ceph-config-fixed.yaml
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Check if namespace exists
|
||||
shell: |
|
||||
export KUBECONFIG={{ kubeconfig }}
|
||||
kubectl get namespace ceph-csi
|
||||
register: ns_check
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
|
||||
- name: Delete existing Ceph CSI deployment if exists
|
||||
shell: |
|
||||
export KUBECONFIG={{ kubeconfig }}
|
||||
kubectl delete daemonset,deployment,service -n ceph-csi -l app in (csi-rbdplugin,csi-rbdplugin-provisioner,csi-metrics) --ignore-not-found
|
||||
when: ns_check.rc == 0
|
||||
|
||||
- name: Wait for pods to terminate
|
||||
shell: |
|
||||
export KUBECONFIG={{ kubeconfig }}
|
||||
kubectl wait --for=delete pod -n ceph-csi -l app in (csi-rbdplugin,csi-rbdplugin-provisioner) --timeout=60s || true
|
||||
when: ns_check.rc == 0
|
||||
|
||||
- name: Apply Ceph CSI configuration
|
||||
shell: |
|
||||
export KUBECONFIG={{ kubeconfig }}
|
||||
kubectl apply -k {{ temp_dir.path }}
|
||||
register: apply_result
|
||||
|
||||
- name: Display apply result
|
||||
debug:
|
||||
var: apply_result.stdout_lines
|
||||
|
||||
- name: Wait for CSI pods to be ready
|
||||
shell: |
|
||||
export KUBECONFIG={{ kubeconfig }}
|
||||
kubectl wait --for=condition=ready pod -n ceph-csi -l app=csi-rbdplugin-provisioner --timeout=300s || true
|
||||
kubectl wait --for=condition=ready pod -n ceph-csi -l app=csi-rbdplugin --timeout=300s || true
|
||||
register: wait_result
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Check CSI pod status
|
||||
shell: |
|
||||
export KUBECONFIG={{ kubeconfig }}
|
||||
echo "=== CSI Provisioner Pods ==="
|
||||
kubectl get pods -n ceph-csi -l app=csi-rbdplugin-provisioner
|
||||
echo -e "\n=== CSI Node Plugin Pods ==="
|
||||
kubectl get pods -n ceph-csi -l app=csi-rbdplugin
|
||||
echo -e "\n=== Storage Class ==="
|
||||
kubectl get storageclass ceph-rbd
|
||||
register: status_check
|
||||
|
||||
- name: Display CSI status
|
||||
debug:
|
||||
var: status_check.stdout_lines
|
||||
|
||||
- name: Clean up temporary directory
|
||||
file:
|
||||
path: "{{ temp_dir.path }}"
|
||||
state: absent
|
||||
|
||||
- name: Test Ceph CSI with a sample PVC
|
||||
shell: |
|
||||
export KUBECONFIG={{ kubeconfig }}
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: test-ceph-pvc
|
||||
namespace: default
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: ceph-rbd
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
EOF
|
||||
register: test_pvc
|
||||
|
||||
- name: Check test PVC status
|
||||
shell: |
|
||||
export KUBECONFIG={{ kubeconfig }}
|
||||
sleep 5
|
||||
kubectl get pvc test-ceph-pvc
|
||||
register: pvc_status
|
||||
|
||||
- name: Display test PVC status
|
||||
debug:
|
||||
var: pvc_status.stdout_lines
|
||||
|
||||
- name: Clean up test PVC
|
||||
shell: |
|
||||
export KUBECONFIG={{ kubeconfig }}
|
||||
kubectl delete pvc test-ceph-pvc --ignore-not-found
|
||||
when: test_pvc is succeeded
|
||||
416
home/ansible/deploy-k3s.yml
Normal file
416
home/ansible/deploy-k3s.yml
Normal file
|
|
@ -0,0 +1,416 @@
|
|||
---
|
||||
- name: Prepare K3s nodes
|
||||
hosts: k3s_cluster
|
||||
become: true
|
||||
tasks:
|
||||
- name: Update apt cache
|
||||
apt:
|
||||
update_cache: yes
|
||||
cache_valid_time: 3600
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
- name: Install curl
|
||||
package:
|
||||
name: curl
|
||||
state: present
|
||||
|
||||
- name: Configure DNS
|
||||
copy:
|
||||
content: |
|
||||
nameserver 9.9.9.9
|
||||
nameserver 149.112.112.112
|
||||
nameserver 1.1.1.1
|
||||
dest: /etc/resolv.conf
|
||||
backup: yes
|
||||
|
||||
- name: Test DNS resolution
|
||||
command: nslookup github.com
|
||||
changed_when: false
|
||||
register: dns_test
|
||||
failed_when: false
|
||||
|
||||
- name: Show DNS test result
|
||||
debug:
|
||||
var: dns_test.stdout_lines
|
||||
when: dns_test.rc != 0
|
||||
|
||||
|
||||
- name: Load required kernel modules
|
||||
modprobe:
|
||||
name: "{{ item }}"
|
||||
state: present
|
||||
loop:
|
||||
- br_netfilter
|
||||
- overlay
|
||||
- wireguard
|
||||
ignore_errors: yes # Wireguard module may not exist in Debian 13
|
||||
|
||||
- name: Persist kernel modules
|
||||
copy:
|
||||
content: |
|
||||
br_netfilter
|
||||
overlay
|
||||
wireguard
|
||||
dest: /etc/modules-load.d/k3s.conf
|
||||
|
||||
- name: Set sysctl parameters
|
||||
sysctl:
|
||||
name: "{{ item.name }}"
|
||||
value: "{{ item.value }}"
|
||||
state: present
|
||||
sysctl_file: /etc/sysctl.d/k3s.conf
|
||||
reload: yes
|
||||
loop:
|
||||
- { name: 'net.bridge.bridge-nf-call-iptables', value: '1' }
|
||||
- { name: 'net.bridge.bridge-nf-call-ip6tables', value: '1' }
|
||||
- { name: 'net.ipv4.ip_forward', value: '1' }
|
||||
- { name: 'net.ipv6.conf.all.forwarding', value: '1' }
|
||||
|
||||
- name: Download k3s binary
|
||||
hosts: k3s_cluster
|
||||
become: true
|
||||
tasks:
|
||||
- name: Download k3s binary
|
||||
get_url:
|
||||
url: https://github.com/k3s-io/k3s/releases/download/{{ k3s_version }}/k3s
|
||||
dest: /usr/local/bin/k3s
|
||||
mode: '0755'
|
||||
|
||||
- name: Deploy first control plane node
|
||||
hosts: home-k3s-control-1
|
||||
become: true
|
||||
tasks:
|
||||
- name: Create k3s config directory
|
||||
file:
|
||||
path: /etc/rancher/k3s
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Create k3s server config
|
||||
copy:
|
||||
content: |
|
||||
cluster-init: true
|
||||
disable:
|
||||
- servicelb
|
||||
- traefik
|
||||
cluster-cidr: 10.42.0.0/16
|
||||
service-cidr: 10.43.0.0/16
|
||||
cluster-dns: 10.43.0.10
|
||||
flannel-backend: vxlan
|
||||
write-kubeconfig-mode: "0644"
|
||||
tls-san:
|
||||
- "{{ ansible_host }}"
|
||||
- "{{ ansible_hostname }}"
|
||||
dest: /etc/rancher/k3s/config.yaml
|
||||
|
||||
- name: Create k3s systemd service
|
||||
copy:
|
||||
content: |
|
||||
[Unit]
|
||||
Description=Lightweight Kubernetes
|
||||
Documentation=https://k3s.io
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
EnvironmentFile=-/etc/default/%N
|
||||
EnvironmentFile=-/etc/sysconfig/%N
|
||||
KillMode=process
|
||||
Delegate=yes
|
||||
LimitNOFILE=1048576
|
||||
LimitNPROC=infinity
|
||||
LimitCORE=infinity
|
||||
TasksMax=infinity
|
||||
TimeoutStartSec=0
|
||||
Restart=always
|
||||
RestartSec=5s
|
||||
ExecStartPre=/bin/sh -xc '! /usr/bin/systemctl is-enabled --quiet nm-cloud-setup.service'
|
||||
ExecStartPre=-/sbin/modprobe br_netfilter
|
||||
ExecStartPre=-/sbin/modprobe overlay
|
||||
ExecStart=/usr/local/bin/k3s server
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
dest: /etc/systemd/system/k3s.service
|
||||
|
||||
- name: Start k3s on first control plane
|
||||
systemd:
|
||||
name: k3s
|
||||
daemon_reload: yes
|
||||
enabled: yes
|
||||
state: started
|
||||
|
||||
- name: Wait for node token
|
||||
wait_for:
|
||||
path: /var/lib/rancher/k3s/server/node-token
|
||||
timeout: 120
|
||||
|
||||
- name: Read node token
|
||||
slurp:
|
||||
src: /var/lib/rancher/k3s/server/node-token
|
||||
register: node_token
|
||||
|
||||
- name: Set token fact
|
||||
set_fact:
|
||||
k3s_token: "{{ node_token.content | b64decode | trim }}"
|
||||
|
||||
- name: Wait for k3s to be ready
|
||||
command: /usr/local/bin/k3s kubectl get nodes
|
||||
register: nodes_result
|
||||
until: nodes_result.rc == 0
|
||||
retries: 30
|
||||
delay: 10
|
||||
changed_when: false
|
||||
environment:
|
||||
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
|
||||
|
||||
- name: Check k3s service status
|
||||
command: systemctl status k3s
|
||||
register: k3s_status
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Show k3s logs if not ready
|
||||
command: journalctl -u k3s -n 50 --no-pager
|
||||
register: k3s_logs
|
||||
when: nodes_result.rc != 0
|
||||
changed_when: false
|
||||
|
||||
- name: Display logs
|
||||
debug:
|
||||
var: k3s_logs.stdout_lines
|
||||
when: k3s_logs is defined
|
||||
|
||||
- name: Deploy additional control plane nodes
|
||||
hosts: k3s_control:!home-k3s-control-1
|
||||
become: true
|
||||
serial: 1
|
||||
tasks:
|
||||
- name: Create k3s config directory
|
||||
file:
|
||||
path: /etc/rancher/k3s
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Create k3s server config for additional control planes
|
||||
copy:
|
||||
content: |
|
||||
server: https://{{ hostvars['home-k3s-control-1']['ansible_host'] }}:6443
|
||||
token: {{ hostvars['home-k3s-control-1']['k3s_token'] }}
|
||||
disable:
|
||||
- servicelb
|
||||
- traefik
|
||||
cluster-cidr: 10.42.0.0/16
|
||||
service-cidr: 10.43.0.0/16
|
||||
cluster-dns: 10.43.0.10
|
||||
flannel-backend: vxlan
|
||||
write-kubeconfig-mode: "0644"
|
||||
tls-san:
|
||||
- "{{ ansible_host }}"
|
||||
- "{{ ansible_hostname }}"
|
||||
dest: /etc/rancher/k3s/config.yaml
|
||||
|
||||
- name: Create k3s systemd service
|
||||
copy:
|
||||
content: |
|
||||
[Unit]
|
||||
Description=Lightweight Kubernetes
|
||||
Documentation=https://k3s.io
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
EnvironmentFile=-/etc/default/%N
|
||||
EnvironmentFile=-/etc/sysconfig/%N
|
||||
KillMode=process
|
||||
Delegate=yes
|
||||
LimitNOFILE=1048576
|
||||
LimitNPROC=infinity
|
||||
LimitCORE=infinity
|
||||
TasksMax=infinity
|
||||
TimeoutStartSec=0
|
||||
Restart=always
|
||||
RestartSec=5s
|
||||
ExecStartPre=/bin/sh -xc '! /usr/bin/systemctl is-enabled --quiet nm-cloud-setup.service'
|
||||
ExecStartPre=-/sbin/modprobe br_netfilter
|
||||
ExecStartPre=-/sbin/modprobe overlay
|
||||
ExecStart=/usr/local/bin/k3s server
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
dest: /etc/systemd/system/k3s.service
|
||||
|
||||
- name: Start k3s on additional control planes
|
||||
systemd:
|
||||
name: k3s
|
||||
daemon_reload: yes
|
||||
enabled: yes
|
||||
state: started
|
||||
|
||||
- name: Deploy worker nodes
|
||||
hosts: k3s_workers
|
||||
become: true
|
||||
tasks:
|
||||
- name: Create k3s config directory
|
||||
file:
|
||||
path: /etc/rancher/k3s
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Create k3s agent config
|
||||
copy:
|
||||
content: |
|
||||
server: https://{{ hostvars['home-k3s-control-1']['ansible_host'] }}:6443
|
||||
token: {{ hostvars['home-k3s-control-1']['k3s_token'] }}
|
||||
dest: /etc/rancher/k3s/config.yaml
|
||||
|
||||
- name: Create k3s-agent systemd service
|
||||
copy:
|
||||
content: |
|
||||
[Unit]
|
||||
Description=Lightweight Kubernetes
|
||||
Documentation=https://k3s.io
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
EnvironmentFile=-/etc/default/%N
|
||||
EnvironmentFile=-/etc/sysconfig/%N
|
||||
KillMode=process
|
||||
Delegate=yes
|
||||
LimitNOFILE=1048576
|
||||
LimitNPROC=infinity
|
||||
LimitCORE=infinity
|
||||
TasksMax=infinity
|
||||
TimeoutStartSec=0
|
||||
Restart=always
|
||||
RestartSec=5s
|
||||
ExecStartPre=/bin/sh -xc '! /usr/bin/systemctl is-enabled --quiet nm-cloud-setup.service'
|
||||
ExecStartPre=-/sbin/modprobe br_netfilter
|
||||
ExecStartPre=-/sbin/modprobe overlay
|
||||
ExecStart=/usr/local/bin/k3s agent
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
dest: /etc/systemd/system/k3s-agent.service
|
||||
|
||||
- name: Start k3s-agent on workers
|
||||
systemd:
|
||||
name: k3s-agent
|
||||
daemon_reload: yes
|
||||
enabled: yes
|
||||
state: started
|
||||
|
||||
- name: Configure kubectl access
|
||||
hosts: home-k3s-control-1
|
||||
become: true
|
||||
tasks:
|
||||
- name: Create kubectl symlink
|
||||
file:
|
||||
src: /usr/local/bin/k3s
|
||||
dest: /usr/local/bin/kubectl
|
||||
state: link
|
||||
|
||||
- name: Create crictl symlink
|
||||
file:
|
||||
src: /usr/local/bin/k3s
|
||||
dest: /usr/local/bin/crictl
|
||||
state: link
|
||||
|
||||
- name: Create .kube directory for user
|
||||
file:
|
||||
path: "/home/{{ ansible_user }}/.kube"
|
||||
state: directory
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "{{ ansible_user }}"
|
||||
mode: '0755'
|
||||
|
||||
- name: Copy kubeconfig to user
|
||||
copy:
|
||||
src: /etc/rancher/k3s/k3s.yaml
|
||||
dest: "/home/{{ ansible_user }}/.kube/config"
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "{{ ansible_user }}"
|
||||
mode: '0600'
|
||||
remote_src: yes
|
||||
|
||||
- name: Fetch kubeconfig
|
||||
fetch:
|
||||
src: /etc/rancher/k3s/k3s.yaml
|
||||
dest: "{{ playbook_dir }}/kubeconfig"
|
||||
flat: yes
|
||||
|
||||
- name: Update kubeconfig server URL
|
||||
replace:
|
||||
path: "{{ playbook_dir }}/kubeconfig"
|
||||
regexp: 'server: https://127.0.0.1:6443'
|
||||
replace: 'server: https://{{ ansible_host }}:6443'
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
|
||||
- name: Apply MetalLB configuration
|
||||
hosts: home-k3s-control-1
|
||||
become: true
|
||||
tasks:
|
||||
- name: Wait for MetalLB namespace
|
||||
command: /usr/local/bin/k3s kubectl get namespace metallb-system
|
||||
register: metallb_ns
|
||||
until: metallb_ns.rc == 0
|
||||
retries: 60
|
||||
delay: 5
|
||||
changed_when: false
|
||||
environment:
|
||||
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
|
||||
|
||||
- name: Wait for MetalLB controller deployment
|
||||
command: /usr/local/bin/k3s kubectl -n metallb-system wait --for=condition=available --timeout=300s deployment/controller
|
||||
changed_when: false
|
||||
environment:
|
||||
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
|
||||
|
||||
- name: Create MetalLB IP pool manifest
|
||||
copy:
|
||||
content: |
|
||||
apiVersion: metallb.io/v1beta1
|
||||
kind: IPAddressPool
|
||||
metadata:
|
||||
name: home-pool
|
||||
namespace: metallb-system
|
||||
spec:
|
||||
addresses:
|
||||
- 10.0.19.1-10.0.19.99
|
||||
dest: /tmp/metallb-pool.yaml
|
||||
|
||||
- name: Apply MetalLB IP pool
|
||||
command: /usr/local/bin/k3s kubectl apply -f /tmp/metallb-pool.yaml
|
||||
environment:
|
||||
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
|
||||
|
||||
- name: Create MetalLB L2 advertisement manifest
|
||||
copy:
|
||||
content: |
|
||||
apiVersion: metallb.io/v1beta1
|
||||
kind: L2Advertisement
|
||||
metadata:
|
||||
name: home-l2
|
||||
namespace: metallb-system
|
||||
spec:
|
||||
ipAddressPools:
|
||||
- home-pool
|
||||
dest: /tmp/metallb-l2.yaml
|
||||
|
||||
- name: Apply MetalLB L2 advertisement
|
||||
command: /usr/local/bin/k3s kubectl apply -f /tmp/metallb-l2.yaml
|
||||
environment:
|
||||
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
|
||||
|
||||
- name: Clean up manifests
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: absent
|
||||
loop:
|
||||
- /tmp/metallb-pool.yaml
|
||||
- /tmp/metallb-l2.yaml
|
||||
25
home/ansible/download-debian13-template.yml
Normal file
25
home/ansible/download-debian13-template.yml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
- name: Download Debian 13 LXC template
|
||||
hosts: proxmox[0]
|
||||
become: true
|
||||
tasks:
|
||||
- name: Update template list
|
||||
command: pveam update
|
||||
changed_when: false
|
||||
|
||||
- name: Search for Debian 13 template
|
||||
command: pveam available | grep -i "debian-13"
|
||||
register: debian13_search
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Show available Debian 13 templates
|
||||
debug:
|
||||
var: debian13_search.stdout_lines
|
||||
when: debian13_search.rc == 0
|
||||
|
||||
- name: Download Debian 12 template (if 13 not available)
|
||||
command: pveam download local debian-12-standard_12.7-1_amd64.tar.zst
|
||||
register: download_result
|
||||
failed_when: false
|
||||
changed_when: "'already exists' not in download_result.stderr"
|
||||
77
home/ansible/fix-nfs-permissions.yml
Normal file
77
home/ansible/fix-nfs-permissions.yml
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
---
|
||||
- name: Fix NFS permissions for all users write access
|
||||
hosts: proxmox[0]
|
||||
become: true
|
||||
vars:
|
||||
nfs_export_path: /mnt/cephfs-kubernetes
|
||||
k8s_network: "10.0.19.0/24"
|
||||
|
||||
tasks:
|
||||
- name: Update NFS export configuration for better access
|
||||
lineinfile:
|
||||
path: /etc/exports
|
||||
line: "{{ nfs_export_path }} {{ k8s_network }}(rw,sync,no_subtree_check,no_root_squash,no_all_squash)"
|
||||
regexp: "^{{ nfs_export_path | regex_escape() }}\\s+"
|
||||
create: yes
|
||||
backup: yes
|
||||
notify:
|
||||
- restart nfs
|
||||
- export nfs shares
|
||||
|
||||
- name: Ensure base directories have proper permissions
|
||||
file:
|
||||
path: "{{ nfs_export_path }}/{{ item }}"
|
||||
state: directory
|
||||
mode: '0777'
|
||||
loop:
|
||||
- volumes
|
||||
- shared
|
||||
|
||||
- name: Set world-writable permissions on general storage directories
|
||||
file:
|
||||
path: "{{ nfs_export_path }}/{{ item }}"
|
||||
state: directory
|
||||
mode: '0777'
|
||||
loop:
|
||||
- volumes/adguard
|
||||
- volumes/nodered
|
||||
- volumes/forgejo
|
||||
- shared
|
||||
ignore_errors: yes # In case some don't exist yet
|
||||
|
||||
- name: Preserve PostgreSQL specific permissions
|
||||
file:
|
||||
path: "{{ nfs_export_path }}/volumes/postgresql"
|
||||
state: directory
|
||||
owner: '999'
|
||||
group: '999'
|
||||
mode: '0700'
|
||||
recurse: yes
|
||||
when: ansible_check_mode or (nfs_export_path + "/volumes/postgresql") is directory
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Set sticky bit on shared directories
|
||||
command: chmod +t {{ nfs_export_path }}/{{ item }}
|
||||
loop:
|
||||
- volumes
|
||||
- shared
|
||||
changed_when: false
|
||||
|
||||
- name: Display current exports
|
||||
command: exportfs -v
|
||||
register: exports_output
|
||||
changed_when: false
|
||||
|
||||
- name: Show current exports
|
||||
debug:
|
||||
var: exports_output.stdout_lines
|
||||
|
||||
handlers:
|
||||
- name: restart nfs
|
||||
service:
|
||||
name: nfs-kernel-server
|
||||
state: restarted
|
||||
|
||||
- name: export nfs shares
|
||||
command: exportfs -ra
|
||||
changed_when: false
|
||||
33
home/ansible/fix-postgres-hba.yml
Normal file
33
home/ansible/fix-postgres-hba.yml
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
- name: Fix PostgreSQL pg_hba.conf for K3s access
|
||||
hosts: proxmox
|
||||
become: yes
|
||||
gather_facts: no
|
||||
vars:
|
||||
ct_id: 300
|
||||
|
||||
tasks:
|
||||
- name: Update pg_hba.conf to allow K3s nodes
|
||||
shell: |
|
||||
pct exec {{ ct_id }} -- bash -c "
|
||||
# Backup current pg_hba.conf
|
||||
cp /etc/postgresql/16/main/pg_hba.conf /etc/postgresql/16/main/pg_hba.conf.backup
|
||||
|
||||
# Check if K3s network entry exists
|
||||
if ! grep -q '10.0.19.0/24' /etc/postgresql/16/main/pg_hba.conf; then
|
||||
# Add K3s network access
|
||||
echo 'host all all 10.0.19.0/24 md5' >> /etc/postgresql/16/main/pg_hba.conf
|
||||
fi
|
||||
|
||||
# Reload PostgreSQL
|
||||
systemctl reload postgresql
|
||||
"
|
||||
|
||||
- name: Verify pg_hba.conf entries
|
||||
shell: |
|
||||
pct exec {{ ct_id }} -- grep '10.0.19' /etc/postgresql/16/main/pg_hba.conf
|
||||
register: hba_entries
|
||||
|
||||
- name: Display pg_hba.conf entries
|
||||
debug:
|
||||
msg: "{{ hba_entries.stdout_lines }}"
|
||||
17
home/ansible/fix-postgres-permissions.yml
Normal file
17
home/ansible/fix-postgres-permissions.yml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
- name: Fix PostgreSQL directory permissions
|
||||
hosts: proxmox[0]
|
||||
become: true
|
||||
tasks:
|
||||
- name: Fix PostgreSQL directory ownership
|
||||
file:
|
||||
path: /mnt/cephfs-kubernetes/volumes/postgresql
|
||||
state: directory
|
||||
owner: '999'
|
||||
group: '999'
|
||||
mode: '0700'
|
||||
recurse: yes
|
||||
|
||||
- name: Ensure write permissions
|
||||
command: chmod -R 700 /mnt/cephfs-kubernetes/volumes/postgresql
|
||||
changed_when: false
|
||||
22
home/ansible/get-ceph-kubernetes-key.yml
Normal file
22
home/ansible/get-ceph-kubernetes-key.yml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
- name: Get Ceph kubernetes user key
|
||||
hosts: proxmox[0]
|
||||
become: true
|
||||
tasks:
|
||||
- name: Get kubernetes user auth
|
||||
command: ceph auth get client.kubernetes
|
||||
register: k8s_auth
|
||||
changed_when: false
|
||||
|
||||
- name: Display kubernetes auth
|
||||
debug:
|
||||
var: k8s_auth.stdout_lines
|
||||
|
||||
- name: Extract just the key
|
||||
command: ceph auth print-key client.kubernetes
|
||||
register: k8s_key
|
||||
changed_when: false
|
||||
|
||||
- name: Display key
|
||||
debug:
|
||||
msg: "Kubernetes key: {{ k8s_key.stdout }}"
|
||||
44
home/ansible/install-ceph-client.yml
Normal file
44
home/ansible/install-ceph-client.yml
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
---
|
||||
- name: Install Ceph client packages on K3s nodes
|
||||
hosts: k3s_cluster
|
||||
become: true
|
||||
tasks:
|
||||
- name: Update apt cache
|
||||
apt:
|
||||
update_cache: yes
|
||||
cache_valid_time: 3600
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
- name: Install ceph-common package
|
||||
package:
|
||||
name: ceph-common
|
||||
state: present
|
||||
register: ceph_install
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Check if ceph-common is installed
|
||||
command: which ceph
|
||||
register: ceph_check
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
|
||||
- name: Display ceph installation status
|
||||
debug:
|
||||
msg: "Ceph client installed: {{ ceph_check.rc == 0 }}"
|
||||
|
||||
- name: Create ceph configuration directory
|
||||
file:
|
||||
path: /etc/ceph
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Load RBD kernel module
|
||||
modprobe:
|
||||
name: rbd
|
||||
state: present
|
||||
|
||||
- name: Persist RBD module
|
||||
lineinfile:
|
||||
path: /etc/modules-load.d/ceph.conf
|
||||
line: rbd
|
||||
create: yes
|
||||
9
home/ansible/install-nfs-client.yml
Normal file
9
home/ansible/install-nfs-client.yml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
- name: Install NFS client on K3s nodes
|
||||
hosts: k3s_cluster
|
||||
become: true
|
||||
tasks:
|
||||
- name: Install NFS client packages
|
||||
package:
|
||||
name: nfs-common
|
||||
state: present
|
||||
54
home/ansible/inventory.yml
Normal file
54
home/ansible/inventory.yml
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
all:
|
||||
children:
|
||||
proxmox:
|
||||
hosts:
|
||||
lab02:
|
||||
ansible_host: 10.0.16.231
|
||||
lab03:
|
||||
ansible_host: 10.0.16.232
|
||||
lab04:
|
||||
ansible_host: 10.0.16.233
|
||||
vars:
|
||||
ansible_user: root
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
k3s_cluster:
|
||||
children:
|
||||
k3s_control:
|
||||
hosts:
|
||||
home-k3s-control-1:
|
||||
ansible_host: 10.0.19.100
|
||||
k3s_control_node: true
|
||||
home-k3s-control-2:
|
||||
ansible_host: 10.0.19.101
|
||||
k3s_control_node: true
|
||||
home-k3s-control-3:
|
||||
ansible_host: 10.0.19.102
|
||||
k3s_control_node: true
|
||||
k3s_workers:
|
||||
hosts:
|
||||
home-k3s-worker-1:
|
||||
ansible_host: 10.0.19.110
|
||||
home-k3s-worker-2:
|
||||
ansible_host: 10.0.19.111
|
||||
home-k3s-worker-3:
|
||||
ansible_host: 10.0.19.112
|
||||
vars:
|
||||
ansible_user: debian
|
||||
ansible_ssh_private_key_file: "~/.ssh/id_rsa"
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
ansible_ssh_common_args: "-o StrictHostKeyChecking=no"
|
||||
k3s_version: v1.28.5+k3s1
|
||||
vultr:
|
||||
hosts:
|
||||
caddy:
|
||||
ansible_host: "caddy.w5isp.com"
|
||||
vars:
|
||||
ansible_user: root # Initial connection as root to create users
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
ansible_ssh_common_args: "-o StrictHostKeyChecking=no"
|
||||
caddy_servers:
|
||||
hosts:
|
||||
caddy:
|
||||
vars:
|
||||
ansible_user: root # Will switch to ansible user after creation
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
19
home/ansible/kubeconfig
Normal file
19
home/ansible/kubeconfig
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJkakNDQVIyZ0F3SUJBZ0lCQURBS0JnZ3Foa2pPUFFRREFqQWpNU0V3SHdZRFZRUUREQmhyTTNNdGMyVnkKZG1WeUxXTmhRREUzTlRjd09EVTRORGd3SGhjTk1qVXdPVEExTVRVeU5EQTRXaGNOTXpVd09UQXpNVFV5TkRBNApXakFqTVNFd0h3WURWUVFEREJock0zTXRjMlZ5ZG1WeUxXTmhRREUzTlRjd09EVTRORGd3V1RBVEJnY3Foa2pPClBRSUJCZ2dxaGtqT1BRTUJCd05DQUFUQm5YbGtOQlhZN0hrRTRyczlycEtCL0NhTnh1ekZUSUpTUjdpdWQyMEUKb2ZSNWZYVlFrTWp5R0tBZTBQaWFWT1ZFZXo0OEdVeUl1T21pMTZIZnp3czdvMEl3UURBT0JnTlZIUThCQWY4RQpCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVVF0d2RJaDlkWVlpTkZzcDBvRmZBCk5lYXNMem93Q2dZSUtvWkl6ajBFQXdJRFJ3QXdSQUlnVk5acVBQMDBOR09xYnF3SFkxdVM4YjRLeG9MWkpXekUKbmNmNUJHazNqQVlDSUNNMlZwd3E5VmZtTTRFMTZ0VEFqeStHOGprWW1PMEFCdHdST1NnM293cDgKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
|
||||
server: https://10.0.19.100:6443
|
||||
name: default
|
||||
contexts:
|
||||
- context:
|
||||
cluster: default
|
||||
user: default
|
||||
name: default
|
||||
current-context: default
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: default
|
||||
user:
|
||||
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJrRENDQVRlZ0F3SUJBZ0lJWHliZTEvMDh1N2N3Q2dZSUtvWkl6ajBFQXdJd0l6RWhNQjhHQTFVRUF3d1kKYXpOekxXTnNhV1Z1ZEMxallVQXhOelUzTURnMU9EUTRNQjRYRFRJMU1Ea3dOVEUxTWpRd09Gb1hEVEkyTURrdwpOVEUxTWpRd09Gb3dNREVYTUJVR0ExVUVDaE1PYzNsemRHVnRPbTFoYzNSbGNuTXhGVEFUQmdOVkJBTVRESE41CmMzUmxiVHBoWkcxcGJqQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJOWEs5VndoYWNaT1BjNVQKVGp2bVdCdDJaZmxiWmkzM1pjZk5VWVRTSGUxbzZaMEhvUVhHdDN5V2lrOVdmUGF6OTRYSHZVZXdLSmpOTnJBWgpsYzk2U21HalNEQkdNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUtCZ2dyQmdFRkJRY0RBakFmCkJnTlZIU01FR0RBV2dCVE5CbDhSUGJnRTBFVXRUeGJyUXhyekE0Lzg2VEFLQmdncWhrak9QUVFEQWdOSEFEQkUKQWlBcmUySmNPa0lDbmhZWXptODZCVThEampNdFJZUTE1SWRoamJuenZPZ09WUUlnREtsaHIxL1FKWkMwellaLwoveUt5OEEzTWN4aXRQMEJkdXBvQzhnRDdrQkU9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJkekNDQVIyZ0F3SUJBZ0lCQURBS0JnZ3Foa2pPUFFRREFqQWpNU0V3SHdZRFZRUUREQmhyTTNNdFkyeHAKWlc1MExXTmhRREUzTlRjd09EVTRORGd3SGhjTk1qVXdPVEExTVRVeU5EQTRXaGNOTXpVd09UQXpNVFV5TkRBNApXakFqTVNFd0h3WURWUVFEREJock0zTXRZMnhwWlc1MExXTmhRREUzTlRjd09EVTRORGd3V1RBVEJnY3Foa2pPClBRSUJCZ2dxaGtqT1BRTUJCd05DQUFUNlZCdUI4UStFblNXOUFZT0N3b0ZsUFcwdzdqUjZlZC9qdTArbVIySDgKVkdnTk1qdXFUalU0a3o0RWhrYmJsaHloVVc5ZE9FWVRJSDM2bVlGUHltaVBvMEl3UURBT0JnTlZIUThCQWY4RQpCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVXpRWmZFVDI0Qk5CRkxVOFc2ME1hCjh3T1AvT2t3Q2dZSUtvWkl6ajBFQXdJRFNBQXdSUUloQVBja200cHA2anI1aStWcFRLbDNFTU5EWmNoQTZ5eXgKVDA4UCt5eHlieGxyQWlCZWJ6cDFqSDBYbUZKVjcxWjd2dDZFMzlsVTcyTTJJdlM0YW1zUncvd1lqdz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
|
||||
client-key-data: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUFPNjA5Q2tBNEl1NXQyRFdMVkhBMHRDTFBZSHlBZ21nQlBrU3V2OElXOUVvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFMWNyMVhDRnB4azQ5emxOT08rWllHM1psK1Z0bUxmZGx4ODFSaE5JZDdXanBuUWVoQmNhMwpmSmFLVDFaODlyUDNoY2U5UjdBb21NMDJzQm1WejNwS1lRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=
|
||||
13
home/ansible/list-lxc-templates.yml
Normal file
13
home/ansible/list-lxc-templates.yml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
- name: List available LXC templates
|
||||
hosts: proxmox[0]
|
||||
become: true
|
||||
tasks:
|
||||
- name: List templates in local storage
|
||||
command: pveam list local
|
||||
register: templates
|
||||
changed_when: false
|
||||
|
||||
- name: Show Debian templates
|
||||
debug:
|
||||
msg: "{{ templates.stdout_lines | select('search', 'debian') | list }}"
|
||||
28
home/ansible/migrate-template.yml
Normal file
28
home/ansible/migrate-template.yml
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
- name: Migrate template to all Proxmox nodes
|
||||
hosts: lab02
|
||||
gather_facts: no
|
||||
vars:
|
||||
template_vmid: 9000
|
||||
|
||||
tasks:
|
||||
- name: Migrate template to lab03
|
||||
command: qm migrate {{ template_vmid }} lab03
|
||||
register: migrate_lab03
|
||||
failed_when:
|
||||
- migrate_lab03.rc != 0
|
||||
- "'already on node' not in migrate_lab03.stderr"
|
||||
|
||||
- name: Migrate template to lab04
|
||||
command: qm migrate {{ template_vmid }} lab04
|
||||
register: migrate_lab04
|
||||
failed_when:
|
||||
- migrate_lab04.rc != 0
|
||||
- "'already on node' not in migrate_lab04.stderr"
|
||||
|
||||
- name: Migrate template back to lab02
|
||||
command: qm migrate {{ template_vmid }} lab02
|
||||
register: migrate_back
|
||||
failed_when:
|
||||
- migrate_back.rc != 0
|
||||
- "'already on node' not in migrate_back.stderr"
|
||||
40
home/ansible/mount-ceph-rbd.yml
Normal file
40
home/ansible/mount-ceph-rbd.yml
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
- name: Configure Ceph RBD access on K3s nodes
|
||||
hosts: k3s_cluster
|
||||
become: true
|
||||
vars:
|
||||
ceph_monitors: "10.0.16.231:6789,10.0.16.232:6789,10.0.16.233:6789"
|
||||
ceph_user: "kubernetes"
|
||||
ceph_key: "AQAq14Jo/g4ZORAAwxpPOmqj1PYgmih6s6b4vA=="
|
||||
|
||||
tasks:
|
||||
- name: Create Ceph configuration file
|
||||
copy:
|
||||
content: |
|
||||
[global]
|
||||
fsid = 6e5e8932-3613-471d-afa0-410fade4d4ff
|
||||
mon_host = 10.0.16.231,10.0.16.232,10.0.16.233
|
||||
auth_cluster_required = cephx
|
||||
auth_service_required = cephx
|
||||
auth_client_required = cephx
|
||||
dest: /etc/ceph/ceph.conf
|
||||
mode: '0644'
|
||||
|
||||
- name: Create Ceph keyring for kubernetes user
|
||||
copy:
|
||||
content: |
|
||||
[client.kubernetes]
|
||||
key = {{ ceph_key }}
|
||||
dest: /etc/ceph/ceph.client.kubernetes.keyring
|
||||
mode: '0600'
|
||||
|
||||
- name: Test Ceph connectivity
|
||||
shell: |
|
||||
rbd ls kubernetes --id kubernetes
|
||||
register: rbd_test
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
|
||||
- name: Show RBD test result
|
||||
debug:
|
||||
var: rbd_test
|
||||
35
home/ansible/mount-cephfs-k8s.yml
Normal file
35
home/ansible/mount-cephfs-k8s.yml
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
- name: Configure CephFS access on K3s nodes
|
||||
hosts: k3s_cluster
|
||||
become: true
|
||||
tasks:
|
||||
- name: Install ceph-fuse package for CephFS
|
||||
package:
|
||||
name:
|
||||
- ceph-fuse
|
||||
- ceph-common
|
||||
state: present
|
||||
ignore_errors: yes
|
||||
register: ceph_install
|
||||
|
||||
- name: Create mount point for testing
|
||||
file:
|
||||
path: /mnt/cephfs-test
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Test CephFS mount
|
||||
shell: |
|
||||
timeout 5 ceph-fuse -n client.kubernetes --keyring=/etc/ceph/ceph.client.kubernetes.keyring -m 10.0.16.231:6789,10.0.16.232:6789,10.0.16.233:6789 /mnt/cephfs-test
|
||||
register: mount_test
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
|
||||
- name: Unmount test
|
||||
shell: fusermount -u /mnt/cephfs-test
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
|
||||
- name: Show mount test result
|
||||
debug:
|
||||
msg: "CephFS mount test: {{ 'SUCCESS' if mount_test.rc == 124 else 'FAILED' }}"
|
||||
27
home/ansible/replicate-template.yml
Normal file
27
home/ansible/replicate-template.yml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
- name: Replicate Debian 13 template to all Proxmox nodes
|
||||
hosts: proxmox
|
||||
gather_facts: no
|
||||
vars:
|
||||
template_vmid: 9000
|
||||
storage: "ceph"
|
||||
source_node: "lab02"
|
||||
|
||||
tasks:
|
||||
- name: Check if template exists on this node
|
||||
command: qm status {{ template_vmid }}
|
||||
register: vm_check
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
|
||||
- name: Clone template from source node
|
||||
when:
|
||||
- vm_check.rc != 0
|
||||
- inventory_hostname != source_node
|
||||
command: |
|
||||
qm clone {{ template_vmid }} {{ template_vmid }} --name debian-13-cloudinit --target {{ inventory_hostname }}
|
||||
delegate_to: "{{ source_node }}"
|
||||
|
||||
- name: Template status
|
||||
debug:
|
||||
msg: "Template {{ template_vmid }} exists on {{ inventory_hostname }}"
|
||||
82
home/ansible/setup-ceph-clients-k3s.yml
Normal file
82
home/ansible/setup-ceph-clients-k3s.yml
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
---
|
||||
- name: Setup Ceph client tools on K3s nodes
|
||||
hosts: k3s_cluster
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
- name: Install Ceph repository key
|
||||
apt_key:
|
||||
url: https://download.ceph.com/keys/release.asc
|
||||
state: present
|
||||
|
||||
- name: Get Debian codename
|
||||
command: lsb_release -cs
|
||||
register: debian_codename
|
||||
changed_when: false
|
||||
|
||||
- name: Add Ceph repository
|
||||
apt_repository:
|
||||
repo: "deb https://download.ceph.com/debian-reef/ {{ debian_codename.stdout }} main"
|
||||
state: present
|
||||
filename: ceph
|
||||
|
||||
- name: Update apt cache
|
||||
apt:
|
||||
update_cache: yes
|
||||
|
||||
- name: Install Ceph client packages
|
||||
apt:
|
||||
name:
|
||||
- ceph-common
|
||||
- python3-rbd
|
||||
- python3-rados
|
||||
state: present
|
||||
|
||||
- name: Create Ceph configuration directory
|
||||
file:
|
||||
path: /etc/ceph
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Copy Ceph configuration
|
||||
copy:
|
||||
content: |
|
||||
[global]
|
||||
fsid = 6e5e8932-3613-471d-afa0-410fade4d4ff
|
||||
mon_initial_members = lab02,lab03,lab04
|
||||
mon_host = 10.0.16.231,10.0.16.232,10.0.16.233
|
||||
auth_cluster_required = cephx
|
||||
auth_service_required = cephx
|
||||
auth_client_required = cephx
|
||||
dest: /etc/ceph/ceph.conf
|
||||
mode: '0644'
|
||||
|
||||
- name: Copy Ceph kubernetes keyring
|
||||
copy:
|
||||
content: |
|
||||
[client.kubernetes]
|
||||
key = AQAq14Jo/g4ZORAAwxpPOmqj1PYgmih6s6b4vA==
|
||||
dest: /etc/ceph/ceph.client.kubernetes.keyring
|
||||
mode: '0600'
|
||||
|
||||
- name: Test Ceph connectivity
|
||||
command: ceph -s --id kubernetes
|
||||
register: ceph_status
|
||||
changed_when: false
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Display Ceph status
|
||||
debug:
|
||||
var: ceph_status.stdout_lines
|
||||
when: ceph_status.rc == 0
|
||||
|
||||
- name: Check RBD module
|
||||
modprobe:
|
||||
name: rbd
|
||||
state: present
|
||||
|
||||
- name: Ensure RBD module loads on boot
|
||||
lineinfile:
|
||||
path: /etc/modules
|
||||
line: rbd
|
||||
create: yes
|
||||
155
home/ansible/setup-ceph-rbd-direct.yml
Normal file
155
home/ansible/setup-ceph-rbd-direct.yml
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
---
|
||||
# This playbook sets up direct Ceph RBD access for Kubernetes
|
||||
# It replaces NFS-based access with native Ceph RBD CSI
|
||||
|
||||
- name: Ensure Ceph kubernetes pool exists
|
||||
hosts: proxmox[0]
|
||||
become: true
|
||||
tasks:
|
||||
- name: Check if kubernetes pool exists
|
||||
command: ceph osd pool ls
|
||||
register: ceph_pools
|
||||
changed_when: false
|
||||
|
||||
- name: Create kubernetes pool if not exists
|
||||
command: ceph osd pool create kubernetes 128 128
|
||||
when: "'kubernetes' not in ceph_pools.stdout"
|
||||
|
||||
- name: Initialize pool for RBD use
|
||||
command: rbd pool init kubernetes
|
||||
when: "'kubernetes' not in ceph_pools.stdout"
|
||||
|
||||
- name: Check if kubernetes user exists
|
||||
command: ceph auth list
|
||||
register: ceph_users
|
||||
changed_when: false
|
||||
|
||||
- name: Create kubernetes user if not exists
|
||||
shell: |
|
||||
ceph auth get-or-create client.kubernetes \
|
||||
mon 'profile rbd' \
|
||||
osd 'profile rbd pool=kubernetes' \
|
||||
mgr 'profile rbd pool=kubernetes'
|
||||
when: "'client.kubernetes' not in ceph_users.stdout"
|
||||
|
||||
- name: Get kubernetes user key
|
||||
command: ceph auth print-key client.kubernetes
|
||||
register: k8s_key
|
||||
changed_when: false
|
||||
|
||||
- name: Display kubernetes key
|
||||
debug:
|
||||
msg: "Kubernetes key: {{ k8s_key.stdout }}"
|
||||
|
||||
- name: Set fact for kubernetes key
|
||||
set_fact:
|
||||
ceph_kubernetes_key: "{{ k8s_key.stdout }}"
|
||||
|
||||
- name: Setup Ceph client on K3s nodes
|
||||
import_playbook: setup-ceph-clients-k3s.yml
|
||||
|
||||
- name: Deploy Ceph CSI
|
||||
import_playbook: deploy-ceph-csi.yml
|
||||
|
||||
- name: Verify Ceph RBD setup
|
||||
hosts: localhost
|
||||
connection: local
|
||||
vars:
|
||||
kubeconfig: "{{ playbook_dir }}/kubeconfig"
|
||||
|
||||
tasks:
|
||||
- name: Check CSI driver status
|
||||
shell: |
|
||||
export KUBECONFIG={{ kubeconfig }}
|
||||
echo "=== CSI Drivers ==="
|
||||
kubectl get csidrivers
|
||||
echo -e "\n=== CSI Nodes ==="
|
||||
kubectl get csinodes
|
||||
echo -e "\n=== Storage Classes ==="
|
||||
kubectl get storageclass
|
||||
echo -e "\n=== CSI Pods Status ==="
|
||||
kubectl get pods -n ceph-csi
|
||||
register: csi_status
|
||||
|
||||
- name: Display CSI status
|
||||
debug:
|
||||
var: csi_status.stdout_lines
|
||||
|
||||
- name: Create test application with Ceph RBD
|
||||
shell: |
|
||||
export KUBECONFIG={{ kubeconfig }}
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: ceph-test
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: test-rbd-pvc
|
||||
namespace: ceph-test
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: ceph-rbd
|
||||
resources:
|
||||
requests:
|
||||
storage: 5Gi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-rbd-pod
|
||||
namespace: ceph-test
|
||||
spec:
|
||||
containers:
|
||||
- name: test
|
||||
image: nginx:alpine
|
||||
volumeMounts:
|
||||
- name: test-volume
|
||||
mountPath: /data
|
||||
volumes:
|
||||
- name: test-volume
|
||||
persistentVolumeClaim:
|
||||
claimName: test-rbd-pvc
|
||||
EOF
|
||||
register: test_app
|
||||
|
||||
- name: Wait for test pod to be ready
|
||||
shell: |
|
||||
export KUBECONFIG={{ kubeconfig }}
|
||||
kubectl wait --for=condition=ready pod -n ceph-test test-rbd-pod --timeout=120s
|
||||
register: wait_result
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Check test application status
|
||||
shell: |
|
||||
export KUBECONFIG={{ kubeconfig }}
|
||||
echo "=== Test PVC Status ==="
|
||||
kubectl get pvc -n ceph-test
|
||||
echo -e "\n=== Test Pod Status ==="
|
||||
kubectl get pod -n ceph-test
|
||||
echo -e "\n=== RBD Image List ==="
|
||||
kubectl exec -n ceph-test test-rbd-pod -- df -h /data
|
||||
register: test_status
|
||||
when: wait_result is succeeded
|
||||
|
||||
- name: Display test results
|
||||
debug:
|
||||
var: test_status.stdout_lines
|
||||
when: wait_result is succeeded
|
||||
|
||||
- name: Cleanup test resources
|
||||
shell: |
|
||||
export KUBECONFIG={{ kubeconfig }}
|
||||
kubectl delete namespace ceph-test --ignore-not-found
|
||||
when: test_app is succeeded
|
||||
|
||||
- name: Summary
|
||||
debug:
|
||||
msg:
|
||||
- "Ceph RBD CSI setup complete!"
|
||||
- "Storage class 'ceph-rbd' is now available for direct Ceph RBD access"
|
||||
- "You can now create PVCs with storageClassName: ceph-rbd"
|
||||
- "This provides better performance than NFS-based access"
|
||||
66
home/ansible/setup-nfs-cephfs.yml
Normal file
66
home/ansible/setup-nfs-cephfs.yml
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
---
|
||||
- name: Setup NFS export for CephFS
|
||||
hosts: proxmox[0]
|
||||
become: true
|
||||
vars:
|
||||
nfs_export_path: /mnt/cephfs-kubernetes
|
||||
k8s_network: "10.0.19.0/24"
|
||||
|
||||
tasks:
|
||||
- name: Install NFS server
|
||||
apt:
|
||||
name:
|
||||
- nfs-kernel-server
|
||||
- ceph-fuse
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
- name: Create CephFS mount point
|
||||
file:
|
||||
path: "{{ nfs_export_path }}"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Mount CephFS
|
||||
mount:
|
||||
path: "{{ nfs_export_path }}"
|
||||
src: "10.0.16.231,10.0.16.232,10.0.16.233:/"
|
||||
fstype: ceph
|
||||
opts: "name=kubernetes,secretfile=/etc/ceph/client.kubernetes.key"
|
||||
state: mounted
|
||||
|
||||
- name: Create Kubernetes storage directories
|
||||
file:
|
||||
path: "{{ nfs_export_path }}/{{ item }}"
|
||||
state: directory
|
||||
mode: '0777'
|
||||
owner: nobody
|
||||
group: nogroup
|
||||
loop:
|
||||
- volumes
|
||||
- volumes/adguard
|
||||
- volumes/nodered
|
||||
- shared
|
||||
|
||||
- name: Ensure world-writable permissions on CephFS mount
|
||||
file:
|
||||
path: "{{ nfs_export_path }}"
|
||||
mode: '0777'
|
||||
recurse: yes
|
||||
|
||||
- name: Configure NFS exports
|
||||
lineinfile:
|
||||
path: /etc/exports
|
||||
line: "{{ nfs_export_path }} {{ k8s_network }}(rw,sync,no_subtree_check,no_root_squash,no_all_squash)"
|
||||
create: yes
|
||||
notify: restart nfs
|
||||
|
||||
- name: Export NFS shares
|
||||
command: exportfs -ra
|
||||
changed_when: false
|
||||
|
||||
handlers:
|
||||
- name: restart nfs
|
||||
service:
|
||||
name: nfs-kernel-server
|
||||
state: restarted
|
||||
175
home/ansible/setup-postgres-lxc.yml
Normal file
175
home/ansible/setup-postgres-lxc.yml
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
---
|
||||
- name: Setup PostgreSQL with PostGIS in LXC
|
||||
hosts: proxmox[0]
|
||||
become: true
|
||||
vars:
|
||||
ct_id: 300
|
||||
postgres_version: 16
|
||||
|
||||
tasks:
|
||||
- name: Check if container is running
|
||||
command: pct status {{ ct_id }}
|
||||
register: ct_status
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
|
||||
- name: Start container if stopped
|
||||
command: pct start {{ ct_id }}
|
||||
when: ct_status.rc == 0 and 'stopped' in ct_status.stdout
|
||||
|
||||
- name: Wait for container to be ready
|
||||
wait_for:
|
||||
host: 10.0.19.5
|
||||
port: 22
|
||||
timeout: 60
|
||||
delay: 5
|
||||
when: ct_status.rc == 0
|
||||
|
||||
- name: Check if PostgreSQL is already installed
|
||||
shell: pct exec {{ ct_id }} -- which psql || echo "not installed"
|
||||
register: psql_check
|
||||
changed_when: false
|
||||
when: ct_status.rc == 0
|
||||
|
||||
- name: Update package list
|
||||
shell: pct exec {{ ct_id }} -- apt-get update -y
|
||||
when: ct_status.rc == 0 and "'not installed' in psql_check.stdout"
|
||||
|
||||
- name: Install PostgreSQL
|
||||
shell: pct exec {{ ct_id }} -- bash -c "export DEBIAN_FRONTEND=noninteractive && apt-get install -y postgresql postgresql-contrib"
|
||||
when: ct_status.rc == 0 and "'not installed' in psql_check.stdout"
|
||||
|
||||
- name: Start PostgreSQL service
|
||||
shell: |
|
||||
pct exec {{ ct_id }} -- bash -c "
|
||||
systemctl enable postgresql
|
||||
systemctl start postgresql
|
||||
systemctl status postgresql
|
||||
"
|
||||
register: pg_status
|
||||
when: ct_status.rc == 0
|
||||
|
||||
- name: Show PostgreSQL status
|
||||
debug:
|
||||
var: pg_status.stdout_lines
|
||||
when: pg_status is defined
|
||||
|
||||
- name: Get PostgreSQL version
|
||||
shell: pct exec {{ ct_id }} -- ls /etc/postgresql/
|
||||
register: pg_dir
|
||||
when: ct_status.rc == 0
|
||||
|
||||
- name: Configure PostgreSQL for network access
|
||||
shell: |
|
||||
pct exec {{ ct_id }} -- bash -c "
|
||||
# Get actual PostgreSQL version
|
||||
PG_VERSION=$(ls /etc/postgresql/ 2>/dev/null | head -1)
|
||||
|
||||
# If no PostgreSQL directory found, exit
|
||||
if [ -z \"\$PG_VERSION\" ]; then
|
||||
echo 'PostgreSQL not found'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Configure postgresql.conf
|
||||
sed -i \"s/#listen_addresses = 'localhost'/listen_addresses = '*'/g\" /etc/postgresql/\$PG_VERSION/main/postgresql.conf
|
||||
|
||||
# Configure pg_hba.conf properly
|
||||
echo '# TYPE DATABASE USER ADDRESS METHOD' > /etc/postgresql/\$PG_VERSION/main/pg_hba.conf
|
||||
echo 'local all postgres peer' >> /etc/postgresql/\$PG_VERSION/main/pg_hba.conf
|
||||
echo 'local all all peer' >> /etc/postgresql/\$PG_VERSION/main/pg_hba.conf
|
||||
echo 'host all all 127.0.0.1/32 scram-sha-256' >> /etc/postgresql/\$PG_VERSION/main/pg_hba.conf
|
||||
echo 'host all all ::1/128 scram-sha-256' >> /etc/postgresql/\$PG_VERSION/main/pg_hba.conf
|
||||
echo 'host all all 10.0.19.0/24 md5' >> /etc/postgresql/\$PG_VERSION/main/pg_hba.conf
|
||||
echo 'host all all 10.0.16.0/22 md5' >> /etc/postgresql/\$PG_VERSION/main/pg_hba.conf
|
||||
echo 'host all all 10.42.0.0/16 md5' >> /etc/postgresql/\$PG_VERSION/main/pg_hba.conf
|
||||
|
||||
# Restart PostgreSQL
|
||||
systemctl restart postgresql
|
||||
"
|
||||
register: pg_config_result
|
||||
|
||||
- name: Verify pg_hba.conf was updated
|
||||
shell: |
|
||||
pct exec {{ ct_id }} -- bash -c "
|
||||
PG_VERSION=$(ls /etc/postgresql/ 2>/dev/null | head -1)
|
||||
if [ -n \"\$PG_VERSION\" ]; then
|
||||
echo '=== PostgreSQL pg_hba.conf ==='
|
||||
cat /etc/postgresql/\$PG_VERSION/main/pg_hba.conf | grep -E '10.0.19|10.42|10.0.16'
|
||||
else
|
||||
echo 'PostgreSQL not found'
|
||||
fi
|
||||
"
|
||||
register: pg_hba_verify
|
||||
|
||||
- name: Display pg_hba.conf entries
|
||||
debug:
|
||||
var: pg_hba_verify.stdout_lines
|
||||
when: pg_hba_verify is defined
|
||||
|
||||
- name: Install sudo if missing
|
||||
shell: pct exec {{ ct_id }} -- apt-get install -y sudo
|
||||
when: ct_status.rc == 0
|
||||
|
||||
- name: Create databases and users
|
||||
shell: |
|
||||
pct exec {{ ct_id }} -- su - postgres -c "psql << 'EOF'
|
||||
-- Drop existing if needed and recreate
|
||||
DROP DATABASE IF EXISTS forgejo;
|
||||
DROP USER IF EXISTS forgejo;
|
||||
CREATE USER forgejo WITH PASSWORD 'fj8K3n2Qp9Lx5mW7';
|
||||
CREATE DATABASE forgejo OWNER forgejo;
|
||||
\c forgejo
|
||||
CREATE EXTENSION IF NOT EXISTS postgis;
|
||||
GRANT ALL PRIVILEGES ON DATABASE forgejo TO forgejo;
|
||||
|
||||
-- Create other databases as needed
|
||||
DROP DATABASE IF EXISTS apps;
|
||||
DROP USER IF EXISTS appuser;
|
||||
CREATE USER appuser WITH PASSWORD 'AppPass2024!';
|
||||
CREATE DATABASE apps OWNER appuser;
|
||||
\c apps
|
||||
CREATE EXTENSION IF NOT EXISTS postgis;
|
||||
GRANT ALL PRIVILEGES ON DATABASE apps TO appuser;
|
||||
EOF"
|
||||
when: ct_status.rc == 0
|
||||
|
||||
- name: Configure firewall
|
||||
shell: |
|
||||
pct exec {{ ct_id }} -- bash -c "
|
||||
apt-get install -y ufw
|
||||
ufw allow 22/tcp
|
||||
ufw allow 5432/tcp
|
||||
ufw --force enable
|
||||
"
|
||||
when: ct_status.rc == 0
|
||||
|
||||
- name: Wait for PostgreSQL to be ready
|
||||
wait_for:
|
||||
host: 10.0.19.5
|
||||
port: 5432
|
||||
timeout: 30
|
||||
delay: 2
|
||||
|
||||
- name: Test PostgreSQL connectivity
|
||||
shell: |
|
||||
pct exec {{ ct_id }} -- sudo -u postgres psql -c "SELECT version();" || echo "PostgreSQL query failed"
|
||||
register: pg_test
|
||||
when: ct_status.rc == 0
|
||||
|
||||
- name: Display connection info
|
||||
debug:
|
||||
msg: |
|
||||
PostgreSQL with PostGIS installed successfully!
|
||||
|
||||
Connection details:
|
||||
- Host: 10.0.19.5
|
||||
- Port: 5432
|
||||
- Version: PostgreSQL {{ postgres_version }} with PostGIS 3
|
||||
|
||||
Databases:
|
||||
- forgejo (user: forgejo, pass: fj8K3n2Qp9Lx5mW7)
|
||||
- apps (user: appuser, pass: AppPass2024!)
|
||||
|
||||
From Kubernetes pods, connect using:
|
||||
postgresql://forgejo:fj8K3n2Qp9Lx5mW7@10.0.19.5:5432/forgejo
|
||||
22
home/ansible/update-cephfs-size.yml
Normal file
22
home/ansible/update-cephfs-size.yml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
- name: Check CephFS capacity
|
||||
hosts: proxmox[0]
|
||||
become: true
|
||||
tasks:
|
||||
- name: Check Ceph cluster capacity
|
||||
command: ceph df
|
||||
register: ceph_df
|
||||
changed_when: false
|
||||
|
||||
- name: Display Ceph capacity
|
||||
debug:
|
||||
var: ceph_df.stdout_lines
|
||||
|
||||
- name: Get CephFS status
|
||||
command: ceph fs status kubernetes-shared
|
||||
register: cephfs_status
|
||||
changed_when: false
|
||||
|
||||
- name: Display CephFS status
|
||||
debug:
|
||||
var: cephfs_status.stdout_lines
|
||||
233
home/cloud-init/debian-13-base.yaml
Normal file
233
home/cloud-init/debian-13-base.yaml
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
#cloud-config
|
||||
# Minimal cloud-init configuration for Debian 13 base template
|
||||
|
||||
# Update and upgrade packages on first boot
|
||||
package_update: true
|
||||
package_upgrade: true
|
||||
|
||||
# Configure timezone
|
||||
timezone: America/Chicago
|
||||
|
||||
# Install essential packages
|
||||
packages:
|
||||
# System utilities
|
||||
- sudo
|
||||
- curl
|
||||
- wget
|
||||
- git
|
||||
- vim
|
||||
- nano
|
||||
- tmux
|
||||
- htop
|
||||
- iotop
|
||||
- rsync
|
||||
- net-tools
|
||||
- dnsutils
|
||||
- traceroute
|
||||
- mtr-tiny
|
||||
- tcpdump
|
||||
- nmap
|
||||
- netcat-openbsd
|
||||
|
||||
# Development tools
|
||||
- build-essential
|
||||
- python3
|
||||
- python3-pip
|
||||
- python3-venv
|
||||
- jq
|
||||
- fzf
|
||||
- zsh
|
||||
|
||||
# System monitoring
|
||||
- sysstat
|
||||
- smartmontools
|
||||
- lm-sensors
|
||||
|
||||
# Time synchronization
|
||||
- chrony
|
||||
|
||||
# Security
|
||||
- fail2ban
|
||||
- ufw
|
||||
|
||||
# Container runtime (optional)
|
||||
- docker.io
|
||||
- docker-compose
|
||||
|
||||
# Other useful tools
|
||||
- apt-transport-https
|
||||
- ca-certificates
|
||||
- gnupg
|
||||
- lsb-release
|
||||
- software-properties-common
|
||||
|
||||
# Create users with sudo access
|
||||
users:
|
||||
- name: graham
|
||||
groups: sudo,docker
|
||||
shell: /bin/bash
|
||||
sudo: ALL=(ALL) NOPASSWD:ALL
|
||||
ssh_import_id:
|
||||
- gh:gmcintire
|
||||
|
||||
- name: andy
|
||||
groups: sudo,docker
|
||||
shell: /bin/bash
|
||||
sudo: ALL=(ALL) NOPASSWD:ALL
|
||||
ssh_import_id:
|
||||
- gh:nsnw
|
||||
|
||||
- name: ansible
|
||||
uid: 10001
|
||||
groups: sudo,docker
|
||||
shell: /bin/bash
|
||||
sudo: ALL=(ALL) NOPASSWD:ALL
|
||||
ssh_authorized_keys:
|
||||
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC5aTtW3lgF9PfV8sTIxZfbvo6dUBMq5C5zJrXy6mwdb5UK9xvqty5Vlh6F1FqYlZgvIyR7NqLbXnKqsvLVGdkTm9VEXGlNcbPJ3ilbhc3OJPX0k63CwokU6nSztXguE1v3KB7JjXiM0dj7ujuDzAGfPZNClR6u06NjIamzeWcQnsJV3P8rLxa7ppvFgBp0K1N1pMb3AUqXOutsOUjSvs5C4H+GMmKYCYNpPGbfKNDLfB9vYLPYv/ynF1lDDzBDL6gy0PnDX6lXC9jvfPWSKNu5lgIUc6p9J++I/ERGktbGPvEaLPeJjgIJPbVLwvnmCLDj2VBmvu2cVE+oXWjLJLF7y8T1cZA0d7aH1TWHcaFPNd3nsPbWGOA3PtMoNRLN/6b7mA7QAKCEBVcR1cZ8c5wQ7ilcLETrNBmJLPpBQa7tLvCx8Q8KPNmtZX6dMKR1o7yj5mXq7Slzo6RTdKLpDqODPVPmGboChMQWFEvGCKxVYLDuvS8iXHdkRE+C9xwqNis= ansible
|
||||
|
||||
# Configure SSH
|
||||
ssh_pwauth: false
|
||||
disable_root: true
|
||||
|
||||
# SSH configuration
|
||||
write_files:
|
||||
- path: /etc/ssh/sshd_config.d/99-custom.conf
|
||||
content: |
|
||||
PermitRootLogin no
|
||||
PasswordAuthentication no
|
||||
ChallengeResponseAuthentication no
|
||||
UsePAM yes
|
||||
X11Forwarding yes
|
||||
PrintMotd no
|
||||
AcceptEnv LANG LC_*
|
||||
Banner /etc/issue.net
|
||||
|
||||
- path: /etc/issue.net
|
||||
content: |
|
||||
******************************************************************
|
||||
* *
|
||||
* This system is for authorized users only. All activity is *
|
||||
* monitored and reported. Unauthorized access is prohibited. *
|
||||
* *
|
||||
******************************************************************
|
||||
|
||||
- path: /etc/sudoers.d/90-cloud-init-users
|
||||
content: |
|
||||
# Created by cloud-init
|
||||
graham ALL=(ALL) NOPASSWD:ALL
|
||||
andy ALL=(ALL) NOPASSWD:ALL
|
||||
ansible ALL=(ALL) NOPASSWD:ALL
|
||||
permissions: '0440'
|
||||
|
||||
- path: /etc/motd
|
||||
content: |
|
||||
Welcome to Debian 13 (Trixie)
|
||||
|
||||
System Information:
|
||||
* Documentation: https://www.debian.org/doc/
|
||||
* Support: https://www.debian.org/support
|
||||
|
||||
- path: /etc/chrony/sources.d/custom.sources
|
||||
content: |
|
||||
pool 0.us.pool.ntp.org iburst
|
||||
pool 1.us.pool.ntp.org iburst
|
||||
pool 2.us.pool.ntp.org iburst
|
||||
pool 3.us.pool.ntp.org iburst
|
||||
|
||||
- path: /etc/sysctl.d/99-custom.conf
|
||||
content: |
|
||||
# IP forwarding for containers/k8s
|
||||
net.ipv4.ip_forward = 1
|
||||
net.ipv6.conf.all.forwarding = 1
|
||||
|
||||
# Increase inotify limits for containers
|
||||
fs.inotify.max_user_watches = 524288
|
||||
fs.inotify.max_user_instances = 512
|
||||
|
||||
# Network performance tuning
|
||||
net.core.rmem_max = 134217728
|
||||
net.core.wmem_max = 134217728
|
||||
net.ipv4.tcp_rmem = 4096 87380 134217728
|
||||
net.ipv4.tcp_wmem = 4096 65536 134217728
|
||||
|
||||
# Security hardening
|
||||
net.ipv4.conf.all.rp_filter = 1
|
||||
net.ipv4.conf.default.rp_filter = 1
|
||||
net.ipv4.tcp_syncookies = 1
|
||||
net.ipv4.conf.all.accept_source_route = 0
|
||||
net.ipv6.conf.all.accept_source_route = 0
|
||||
|
||||
- path: /etc/security/limits.d/99-custom.conf
|
||||
content: |
|
||||
# Increase limits for containers
|
||||
* soft nofile 65536
|
||||
* hard nofile 65536
|
||||
* soft nproc 32768
|
||||
* hard nproc 32768
|
||||
|
||||
# Configure firewall (ufw)
|
||||
ufw:
|
||||
enabled: true
|
||||
rules:
|
||||
- port: 22
|
||||
protocol: tcp
|
||||
rule: allow
|
||||
comment: SSH
|
||||
- port: 80
|
||||
protocol: tcp
|
||||
rule: allow
|
||||
comment: HTTP
|
||||
- port: 443
|
||||
protocol: tcp
|
||||
rule: allow
|
||||
comment: HTTPS
|
||||
|
||||
# Run commands on first boot
|
||||
runcmd:
|
||||
# Configure fail2ban
|
||||
- systemctl enable fail2ban
|
||||
- systemctl start fail2ban
|
||||
|
||||
# Enable and configure Docker
|
||||
- systemctl enable docker
|
||||
- systemctl start docker
|
||||
- usermod -aG docker graham
|
||||
- usermod -aG docker andy
|
||||
- usermod -aG docker ansible
|
||||
|
||||
# Apply sysctl settings
|
||||
- sysctl -p /etc/sysctl.d/99-custom.conf
|
||||
|
||||
# Configure chrony
|
||||
- systemctl restart chrony
|
||||
|
||||
# Secure shared memory
|
||||
- echo "tmpfs /run/shm tmpfs defaults,noexec,nosuid 0 0" >> /etc/fstab
|
||||
- mount -o remount /run/shm
|
||||
|
||||
# Create common directories
|
||||
- mkdir -p /opt/scripts /opt/configs
|
||||
|
||||
# Set proper permissions
|
||||
- chmod 644 /etc/issue.net
|
||||
- chmod 440 /etc/sudoers.d/90-cloud-init-users
|
||||
|
||||
# Update locate database
|
||||
- updatedb
|
||||
|
||||
# Configure automatic updates
|
||||
apt:
|
||||
preserve_sources_list: true
|
||||
conf: |
|
||||
APT::Periodic::Update-Package-Lists "1";
|
||||
APT::Periodic::Download-Upgradeable-Packages "1";
|
||||
APT::Periodic::AutocleanInterval "7";
|
||||
APT::Periodic::Unattended-Upgrade "1";
|
||||
|
||||
# Power state change timeout
|
||||
power_state:
|
||||
delay: now
|
||||
mode: reboot
|
||||
message: Initial configuration complete, rebooting...
|
||||
timeout: 30
|
||||
condition: true
|
||||
225
home/cloud-init/vultr-debian-13-caddy.yaml
Normal file
225
home/cloud-init/vultr-debian-13-caddy.yaml
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
#cloud-config
|
||||
# Cloud-init configuration for Debian 13 Vultr server with Caddy proxy and Tailscale
|
||||
|
||||
# Update and upgrade packages on first boot
|
||||
package_update: true
|
||||
package_upgrade: true
|
||||
|
||||
# Configure hostname
|
||||
hostname: caddy
|
||||
|
||||
# Configure timezone
|
||||
timezone: America/Chicago
|
||||
|
||||
# Install packages
|
||||
packages:
|
||||
# Include all base packages
|
||||
- sudo
|
||||
- curl
|
||||
- wget
|
||||
- git
|
||||
- vim
|
||||
- tmux
|
||||
- htop
|
||||
- rsync
|
||||
- net-tools
|
||||
- dnsutils
|
||||
- build-essential
|
||||
- python3
|
||||
- python3-pip
|
||||
- apt-transport-https
|
||||
- ca-certificates
|
||||
- gnupg
|
||||
- lsb-release
|
||||
- software-properties-common
|
||||
- fail2ban
|
||||
- ufw
|
||||
|
||||
# Caddy and web server tools
|
||||
- debian-keyring
|
||||
- debian-archive-keyring
|
||||
|
||||
# Create users with sudo access
|
||||
users:
|
||||
- name: graham
|
||||
groups: sudo
|
||||
shell: /bin/bash
|
||||
sudo: ALL=(ALL) NOPASSWD:ALL
|
||||
ssh_import_id:
|
||||
- gh:gmcintire
|
||||
|
||||
- name: andy
|
||||
groups: sudo
|
||||
shell: /bin/bash
|
||||
sudo: ALL=(ALL) NOPASSWD:ALL
|
||||
ssh_import_id:
|
||||
- gh:nsnw
|
||||
|
||||
- name: ansible
|
||||
uid: 10001
|
||||
groups: sudo
|
||||
shell: /bin/bash
|
||||
sudo: ALL=(ALL) NOPASSWD:ALL
|
||||
ssh_authorized_keys:
|
||||
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC5aTtW3lgF9PfV8sTIxZfbvo6dUBMq5C5zJrXy6mwdb5UK9xvqty5Vlh6F1FqYlZgvIyR7NqLbXnKqsvLVGdkTm9VEXGlNcbPJ3ilbhc3OJPX0k63CwokU6nSztXguE1v3KB7JjXiM0dj7ujuDzAGfPZNClR6u06NjIamzeWcQnsJV3P8rLxa7ppvFgBp0K1N1pMb3AUqXOutsOUjSvs5C4H+GMmKYCYNpPGbfKNDLfB9vYLPYv/ynF1lDDzBDL6gy0PnDX6lXC9jvfPWSKNu5lgIUc6p9J++I/ERGktbGPvEaLPeJjgIJPbVLwvnmCLDj2VBmvu2cVE+oXWjLJLF7y8T1cZA0d7aH1TWHcaFPNd3nsPbWGOA3PtMoNRLN/6b7mA7QAKCEBVcR1cZ8c5wQ7ilcLETrNBmJLPpBQa7tLvCx8Q8KPNmtZX6dMKR1o7yj5mXq7Slzo6RTdKLpDqODPVPmGboChMQWFEvGCKxVYLDuvS8iXHdkRE+C9xwqNis= ansible
|
||||
|
||||
# Configure SSH
|
||||
ssh_pwauth: false
|
||||
disable_root: true
|
||||
|
||||
# Write configuration files
|
||||
write_files:
|
||||
- path: /etc/ssh/sshd_config.d/99-custom.conf
|
||||
content: |
|
||||
PermitRootLogin no
|
||||
PasswordAuthentication no
|
||||
Banner /etc/issue.net
|
||||
|
||||
- path: /etc/issue.net
|
||||
content: |
|
||||
******************************************************************
|
||||
* Authorized access only. All activity is monitored. *
|
||||
******************************************************************
|
||||
|
||||
- path: /etc/apt/sources.list.d/caddy-stable.list
|
||||
content: |
|
||||
deb [signed-by=/usr/share/keyrings/caddy-stable-archive-keyring.gpg] https://dl.cloudsmith.io/public/caddy/stable/deb/debian any-version main
|
||||
|
||||
- path: /etc/caddy/Caddyfile
|
||||
content: |
|
||||
# Global options
|
||||
{
|
||||
# Email for Let's Encrypt
|
||||
email admin@example.com
|
||||
|
||||
# Enable the admin endpoint
|
||||
admin off
|
||||
}
|
||||
|
||||
# Default site - return 404 for undefined hosts
|
||||
:80 {
|
||||
respond 404
|
||||
}
|
||||
|
||||
# Example reverse proxy configuration
|
||||
# example.com {
|
||||
# reverse_proxy localhost:8080
|
||||
# }
|
||||
permissions: '0644'
|
||||
owner: root:root
|
||||
|
||||
- path: /etc/sysctl.d/99-tailscale.conf
|
||||
content: |
|
||||
# Tailscale UDP GRO fix
|
||||
net.ipv4.udp_rmem_min = 131072
|
||||
net.ipv4.udp_wmem_min = 131072
|
||||
net.core.rmem_default = 131072
|
||||
net.core.wmem_default = 131072
|
||||
net.core.netdev_max_backlog = 5000
|
||||
|
||||
- path: /opt/scripts/install-tailscale.sh
|
||||
content: |
|
||||
#!/bin/bash
|
||||
# Install Tailscale
|
||||
curl -fsSL https://pkgs.tailscale.com/stable/debian/bookworm.noarmor.gpg | tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null
|
||||
curl -fsSL https://pkgs.tailscale.com/stable/debian/bookworm.tailscale-keyring.list | tee /etc/apt/sources.list.d/tailscale.list
|
||||
apt-get update
|
||||
apt-get install -y tailscale
|
||||
|
||||
# Enable and start tailscaled
|
||||
systemctl enable tailscaled
|
||||
systemctl start tailscaled
|
||||
|
||||
# If TAILSCALE_KEY is provided, authenticate
|
||||
if [ -n "$TAILSCALE_KEY" ]; then
|
||||
tailscale up --authkey="$TAILSCALE_KEY" --accept-routes --hostname=$(hostname)
|
||||
fi
|
||||
permissions: '0755'
|
||||
|
||||
- path: /opt/scripts/setup-caddy.sh
|
||||
content: |
|
||||
#!/bin/bash
|
||||
# Ensure Caddy starts after network is ready
|
||||
mkdir -p /etc/systemd/system/caddy.service.d
|
||||
cat > /etc/systemd/system/caddy.service.d/override.conf <<EOF
|
||||
[Unit]
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
# Restart on failure
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable caddy
|
||||
systemctl start caddy
|
||||
permissions: '0755'
|
||||
|
||||
# Configure firewall
|
||||
ufw:
|
||||
enabled: true
|
||||
rules:
|
||||
- port: 22
|
||||
protocol: tcp
|
||||
rule: allow
|
||||
comment: SSH
|
||||
- port: 80
|
||||
protocol: tcp
|
||||
rule: allow
|
||||
comment: HTTP
|
||||
- port: 443
|
||||
protocol: tcp
|
||||
rule: allow
|
||||
comment: HTTPS
|
||||
- port: 41641
|
||||
protocol: udp
|
||||
rule: allow
|
||||
from_any: true
|
||||
comment: Tailscale
|
||||
|
||||
# Run commands on first boot
|
||||
runcmd:
|
||||
# Add Caddy GPG key and install
|
||||
- curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
|
||||
- apt-get update
|
||||
- apt-get install -y caddy
|
||||
|
||||
# Apply sysctl settings
|
||||
- sysctl -p /etc/sysctl.d/99-tailscale.conf
|
||||
|
||||
# Create Caddy directories
|
||||
- mkdir -p /etc/caddy/sites-enabled
|
||||
- chown -R caddy:caddy /etc/caddy
|
||||
|
||||
# Install Tailscale
|
||||
- /opt/scripts/install-tailscale.sh
|
||||
|
||||
# Setup Caddy service
|
||||
- /opt/scripts/setup-caddy.sh
|
||||
|
||||
# Configure fail2ban for Caddy
|
||||
- |
|
||||
cat > /etc/fail2ban/jail.d/caddy.conf <<EOF
|
||||
[caddy-status]
|
||||
enabled = true
|
||||
filter = caddy-status
|
||||
logpath = /var/log/caddy/access.log
|
||||
maxretry = 5
|
||||
bantime = 600
|
||||
EOF
|
||||
|
||||
# Enable services
|
||||
- systemctl enable fail2ban
|
||||
- systemctl start fail2ban
|
||||
|
||||
# Final message
|
||||
final_message: "Debian 13 Caddy proxy setup complete! System ready after $UPTIME seconds"
|
||||
|
||||
# Power state - don't reboot automatically for proxy servers
|
||||
power_state:
|
||||
delay: now
|
||||
mode: poweroff
|
||||
message: Initial configuration complete
|
||||
condition: false
|
||||
38
home/cluster/adguard/deployment-simple.yaml
Normal file
38
home/cluster/adguard/deployment-simple.yaml
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: adguard
|
||||
namespace: adguard
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: adguard
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: adguard
|
||||
spec:
|
||||
containers:
|
||||
- name: adguard
|
||||
image: adguard/adguardhome:latest
|
||||
ports:
|
||||
- containerPort: 3000 # Web UI
|
||||
name: web
|
||||
- containerPort: 53 # DNS
|
||||
name: dns
|
||||
protocol: UDP
|
||||
- containerPort: 53 # DNS TCP
|
||||
name: dns-tcp
|
||||
protocol: TCP
|
||||
- containerPort: 80 # HTTP
|
||||
name: http
|
||||
- containerPort: 443 # HTTPS
|
||||
name: https
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
50
home/cluster/adguard/deployment.yaml
Normal file
50
home/cluster/adguard/deployment.yaml
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: adguard
|
||||
namespace: adguard
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: adguard
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: adguard
|
||||
spec:
|
||||
containers:
|
||||
- name: adguard
|
||||
image: adguard/adguardhome:latest
|
||||
ports:
|
||||
- containerPort: 3000 # Web UI
|
||||
name: web
|
||||
- containerPort: 53 # DNS
|
||||
name: dns
|
||||
protocol: UDP
|
||||
- containerPort: 53 # DNS TCP
|
||||
name: dns-tcp
|
||||
protocol: TCP
|
||||
- containerPort: 80 # HTTP
|
||||
name: http
|
||||
- containerPort: 443 # HTTPS
|
||||
name: https
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /opt/adguardhome/conf
|
||||
- name: data
|
||||
mountPath: /opt/adguardhome/work
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
volumes:
|
||||
- name: config
|
||||
persistentVolumeClaim:
|
||||
claimName: adguard-config-rbd
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: adguard-data-rbd
|
||||
36
home/cluster/adguard/migrate-data.yaml
Normal file
36
home/cluster/adguard/migrate-data.yaml
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: adguard-data-migration
|
||||
namespace: adguard
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: migrate
|
||||
image: busybox:1.36
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
echo "Starting AdGuard data migration..."
|
||||
if [ -d /old-data/AdGuardHome ]; then
|
||||
echo "Copying AdGuard data..."
|
||||
cp -av /old-data/* /new-data/
|
||||
else
|
||||
echo "No data to migrate"
|
||||
fi
|
||||
echo "Migration complete!"
|
||||
volumeMounts:
|
||||
- name: old-data
|
||||
mountPath: /old-data
|
||||
- name: new-data
|
||||
mountPath: /new-data
|
||||
volumes:
|
||||
- name: old-data
|
||||
persistentVolumeClaim:
|
||||
claimName: adguard-data
|
||||
- name: new-data
|
||||
persistentVolumeClaim:
|
||||
claimName: adguard-data-rbd
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: tailscale
|
||||
name: adguard
|
||||
|
|
@ -1,28 +1,25 @@
|
|||
apiVersion: v1
|
||||
kind: PersistentVolume
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: portal-pv
|
||||
namespace: portal
|
||||
labels:
|
||||
type: local
|
||||
name: adguard-config
|
||||
namespace: adguard
|
||||
spec:
|
||||
storageClassName: local-path
|
||||
capacity:
|
||||
storage: 1Gi
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
hostPath:
|
||||
path: "/var/www/data/storage"
|
||||
storageClassName: local-path
|
||||
resources:
|
||||
requests:
|
||||
storage: 2Gi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: portal-pv-claim
|
||||
namespace: portal
|
||||
name: adguard-data
|
||||
namespace: adguard
|
||||
spec:
|
||||
storageClassName: local-path
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: local-path
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
storage: 10Gi
|
||||
25
home/cluster/adguard/pvc-rbd.yaml
Normal file
25
home/cluster/adguard/pvc-rbd.yaml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: adguard-config-rbd
|
||||
namespace: adguard
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: ceph-rbd
|
||||
resources:
|
||||
requests:
|
||||
storage: 2Gi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: adguard-data-rbd
|
||||
namespace: adguard
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: ceph-rbd
|
||||
resources:
|
||||
requests:
|
||||
storage: 10Gi
|
||||
25
home/cluster/adguard/pvc.yaml
Normal file
25
home/cluster/adguard/pvc.yaml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: adguard-config
|
||||
namespace: adguard
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: ceph-rbd
|
||||
resources:
|
||||
requests:
|
||||
storage: 2Gi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: adguard-data
|
||||
namespace: adguard
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: ceph-rbd
|
||||
resources:
|
||||
requests:
|
||||
storage: 10Gi
|
||||
22
home/cluster/adguard/service.yaml
Normal file
22
home/cluster/adguard/service.yaml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: adguard
|
||||
namespace: adguard
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
selector:
|
||||
app: adguard
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 3000
|
||||
protocol: TCP
|
||||
name: web
|
||||
- port: 53
|
||||
targetPort: 53
|
||||
protocol: UDP
|
||||
name: dns-udp
|
||||
- port: 53
|
||||
targetPort: 53
|
||||
protocol: TCP
|
||||
name: dns-tcp
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue