Initial commit: Traefik deployment

This commit is contained in:
Chezlepro 2025-10-08 16:03:46 -04:00
commit e85158fc04
13 changed files with 578 additions and 0 deletions

View file

@ -0,0 +1,81 @@
name: Build Traefik from Source
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
traefik_version:
description: 'Version de Traefik à compiler (ex: v3.2.0)'
required: true
default: 'v3.2.0'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Determine Traefik version
id: version
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
VERSION="${{ github.event.inputs.traefik_version }}"
else
VERSION="${{ github.ref_name }}"
fi
echo "traefik_version=$VERSION" >> $GITHUB_OUTPUT
echo "🏷️ Version Traefik: $VERSION"
- name: Clone Traefik repository
run: |
git clone --depth 1 --branch ${{ steps.version.outputs.traefik_version }} https://github.com/traefik/traefik.git traefik-src
cd traefik-src
echo "📦 Traefik ${{ steps.version.outputs.traefik_version }} cloné"
- name: Build Traefik
run: |
cd traefik-src
export CGO_ENABLED=0
make generate
make binary
echo "✅ Compilation terminée"
- name: Verify binary
run: |
cd traefik-src
./dist/traefik version
ls -lh ./dist/traefik
- name: Create release archive
run: |
mkdir -p release
cp traefik-src/dist/traefik release/
cd release
tar -czf traefik-${{ steps.version.outputs.traefik_version }}-linux-amd64.tar.gz traefik
sha256sum traefik-${{ steps.version.outputs.traefik_version }}-linux-amd64.tar.gz > traefik-${{ steps.version.outputs.traefik_version }}-linux-amd64.tar.gz.sha256
echo "📦 Archive créée"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: traefik-${{ steps.version.outputs.traefik_version }}
path: release/*
retention-days: 90
- name: Create Release (si tag)
if: startsWith(github.ref, 'refs/tags/')
uses: actions/forgejo-release@v2
with:
direction: upload
token: ${{ secrets.GITHUB_TOKEN }}
release-dir: release
tag: ${{ github.ref_name }}

11
.gitignore vendored Normal file
View file

@ -0,0 +1,11 @@
*.retry
inventory.ini
__pycache__/
*.py[cod]
venv/
*.key
*.pem
acme.json
*.log
.DS_Store
*.tar.gz

53
Makefile Normal file
View file

@ -0,0 +1,53 @@
.PHONY: help install-collections setup test-connection deploy prod rollback clean build-forgejo
PYTHON := python3
ANSIBLE := ansible-playbook
INVENTORY := inventory.ini
help:
@echo "Commandes disponibles:"
@echo " make install-collections - Installe Ansible"
@echo " make setup - Configure le déploiement"
@echo " make test-connection - Teste la connexion SSH"
@echo " make build-forgejo - Déclenche la compilation sur Forgejo"
@echo " make deploy - Déploie Traefik (blue/green)"
@echo " make rollback - Revient à la version précédente"
@echo " make status - Affiche le statut"
@echo " make clean - Nettoie"
install-collections:
@echo "📦 Installation d'Ansible..."
@$(PYTHON) -m pip install ansible jinja2 pyyaml cryptography requests
@echo "✓ Ansible installé"
setup:
@echo "🔧 Configuration..."
@$(PYTHON) scripts/setup.py
test-connection:
@echo "🔍 Test de connexion..."
@ansible all -i $(INVENTORY) -m ping
build-forgejo:
@echo "🏗️ Déclenchement de la compilation sur Forgejo..."
@$(PYTHON) scripts/trigger_build.py
deploy:
@echo "🚀 Déploiement Traefik..."
@$(ANSIBLE) -i $(INVENTORY) ansible/deploy-traefik.yml
prod: install-collections setup test-connection deploy
@echo "✅ Déploiement complet terminé!"
rollback:
@echo "⏮️ Rollback..."
@$(ANSIBLE) -i $(INVENTORY) ansible/rollback-traefik.yml
status:
@echo "📊 Statut..."
@ansible all -i $(INVENTORY) -m shell -a "systemctl status traefik-*"
clean:
@echo "🧹 Nettoyage..."
@rm -rf ansible/*.retry
@rm -f /tmp/traefik_current_color

48
README.md Normal file
View file

@ -0,0 +1,48 @@
# Traefik Deployment avec Blue/Green
Projet de déploiement Traefik avec compilation sur Forgejo et déploiement sécurisé.
## Architecture
- **Forgejo** (http://eregion.chezlepro.ca:3000) : Compilation depuis sources
- **Serveur prod** : Binaire uniquement (sécurité maximale)
- **Stratégie** : Blue/Green deployment
## Quick Start
```bash
# 1. Installer Ansible
make install-collections
# 2. Configurer
make setup
# 3. Tester la connexion
make test-connection
# 4. Compiler sur Forgejo (~10-15 min)
make build-forgejo
# 5. Déployer
make deploy
```
## Commandes
- `make help` - Affiche l'aide
- `make build-forgejo` - Compile sur Forgejo
- `make deploy` - Déploie en Blue/Green
- `make rollback` - Rollback instantané
- `make status` - Statut des services
## Sécurité
Le serveur de production ne contient :
- ❌ Aucun outil de compilation
- ❌ Aucun code source
- ✅ Binaire Traefik uniquement
## Support
Instance Forgejo : http://eregion.chezlepro.ca:3000
Dépôt : http://eregion.chezlepro.ca:3000/Chezlepro/traefik-deploy

View file

@ -0,0 +1,23 @@
---
- name: Deploy Traefik (Blue/Green)
hosts: traefik_servers
become: yes
vars:
traefik_version: "{{ traefik_version | default('v3.2.0') }}"
traefik_base_dir: "/opt/traefik"
current_color_file: "/tmp/traefik_current_color"
pre_tasks:
- name: Read current color
slurp:
src: "{{ current_color_file }}"
register: current_color_raw
ignore_errors: yes
- name: Set colors
set_fact:
current_color: "{{ current_color_raw.content | b64decode | trim if current_color_raw.content is defined else 'blue' }}"
new_color: "{{ 'green' if (current_color_raw.content | b64decode | trim if current_color_raw.content is defined else 'blue') == 'blue' else 'blue' }}"
roles:
- role: traefik

View file

@ -0,0 +1,4 @@
---
- name: reload systemd
systemd:
daemon_reload: yes

View file

@ -0,0 +1,93 @@
---
- name: Install minimal dependencies
apt:
name:
- curl
- ca-certificates
state: present
update_cache: yes
when: ansible_os_family == "Debian"
- name: Create directories
file:
path: "{{ item }}"
state: directory
mode: '0755'
loop:
- "{{ traefik_base_dir }}/{{ new_color }}"
- "{{ traefik_base_dir }}/{{ new_color }}/config"
- "{{ traefik_base_dir }}/{{ new_color }}/logs"
- name: Create acme.json
file:
path: "{{ traefik_base_dir }}/{{ new_color }}/acme.json"
state: touch
mode: '0600'
- name: Download binary from Forgejo
get_url:
url: "{{ forgejo_url }}/{{ forgejo_owner }}/{{ forgejo_repo }}/releases/download/{{ traefik_version }}/traefik-{{ traefik_version }}-linux-amd64.tar.gz"
dest: "/tmp/traefik_{{ new_color }}.tar.gz"
mode: '0644'
force: yes
- name: Extract binary
unarchive:
src: "/tmp/traefik_{{ new_color }}.tar.gz"
dest: "{{ traefik_base_dir }}/{{ new_color }}"
remote_src: yes
- name: Make executable
file:
path: "{{ traefik_base_dir }}/{{ new_color }}/traefik"
mode: '0755'
- name: Copy static config
template:
src: traefik.yml.j2
dest: "{{ traefik_base_dir }}/{{ new_color }}/config/traefik.yml"
mode: '0644'
- name: Copy dynamic config
template:
src: dynamic.yml.j2
dest: "{{ traefik_base_dir }}/{{ new_color }}/config/dynamic.yml"
mode: '0644'
- name: Create systemd service
template:
src: traefik.service.j2
dest: "/etc/systemd/system/traefik-{{ new_color }}.service"
mode: '0644'
- name: Reload systemd
systemd:
daemon_reload: yes
- name: Start new instance
systemd:
name: "traefik-{{ new_color }}"
state: started
enabled: yes
- name: Wait for health check
uri:
url: "http://localhost:8080/ping"
status_code: 200
register: result
until: result.status == 200
retries: 30
delay: 2
- name: Stop old instance
systemd:
name: "traefik-{{ current_color }}"
state: stopped
enabled: no
when: current_color != new_color
ignore_errors: yes
- name: Update color marker
copy:
content: "{{ new_color }}"
dest: "{{ current_color_file }}"

View file

@ -0,0 +1,26 @@
http:
routers:
forgejo:
rule: "Host(`git.example.com`)"
entryPoints:
- websecure
service: forgejo-service
tls:
certResolver: letsencrypt
services:
forgejo-service:
loadBalancer:
servers:
- url: "http://localhost:3000"
middlewares:
security-headers:
headers:
browserXssFilter: true
contentTypeNosniff: true
forceSTSHeader: true
stsIncludeSubdomains: true
stsPreload: true
stsSeconds: 31536000
customFrameOptionsValue: "SAMEORIGIN"

View file

@ -0,0 +1,22 @@
[Unit]
Description=Traefik {{ new_color }}
Documentation=https://doc.traefik.io
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=root
Group=root
ExecStart={{ traefik_base_dir }}/{{ new_color }}/traefik --configFile={{ traefik_base_dir }}/{{ new_color }}/config/traefik.yml
Restart=always
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths={{ traefik_base_dir }}/{{ new_color }}
AmbientCapabilities=CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,44 @@
global:
checkNewVersion: true
sendAnonymousUsage: false
api:
dashboard: true
insecure: true
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
http:
tls:
certResolver: letsencrypt
certificatesResolvers:
letsencrypt:
acme:
email: {{ letsencrypt_email }}
storage: {{ traefik_base_dir }}/{{ new_color }}/acme.json
httpChallenge:
entryPoint: web
providers:
file:
filename: {{ traefik_base_dir }}/{{ new_color }}/config/dynamic.yml
watch: true
log:
level: INFO
filePath: {{ traefik_base_dir }}/{{ new_color }}/logs/traefik.log
accessLog:
filePath: {{ traefik_base_dir }}/{{ new_color }}/logs/access.log
ping:
entryPoint: "web"

View file

@ -0,0 +1,43 @@
---
- name: Rollback Traefik
hosts: traefik_servers
become: yes
vars:
current_color_file: "/tmp/traefik_current_color"
tasks:
- name: Read current color
slurp:
src: "{{ current_color_file }}"
register: current_color_raw
ignore_errors: yes
- name: Set rollback color
set_fact:
current_color: "{{ current_color_raw.content | b64decode | trim if current_color_raw.content is defined else 'blue' }}"
rollback_color: "{{ 'green' if (current_color_raw.content | b64decode | trim if current_color_raw.content is defined else 'blue') == 'blue' else 'blue' }}"
- name: Stop current
systemd:
name: "traefik-{{ current_color }}"
state: stopped
- name: Start previous
systemd:
name: "traefik-{{ rollback_color }}"
state: started
enabled: yes
- name: Update marker
copy:
content: "{{ rollback_color }}"
dest: "{{ current_color_file }}"
- name: Wait for health
uri:
url: "http://localhost:8080/ping"
status_code: 200
register: result
until: result.status == 200
retries: 10
delay: 2

50
scripts/setup.py Executable file
View file

@ -0,0 +1,50 @@
#!/usr/bin/env python3
import sys
from pathlib import Path
def setup():
print("=== Configuration du déploiement Traefik ===\n")
server_ip = input("IP du serveur de production: ").strip()
if not server_ip:
print("❌ IP requise")
sys.exit(1)
ssh_key = input("Clé SSH [~/.ssh/id_rsa]: ").strip()
if not ssh_key:
ssh_key = str(Path.home() / ".ssh" / "id_rsa")
ssh_key_path = Path(ssh_key).expanduser()
if not ssh_key_path.exists():
print(f"❌ Clé SSH introuvable: {ssh_key_path}")
sys.exit(1)
ssh_user = input("Utilisateur SSH [root]: ").strip() or "root"
letsencrypt_email = input("Email Let's Encrypt: ").strip()
if not letsencrypt_email:
print("❌ Email requis")
sys.exit(1)
traefik_version = input("Version Traefik [v3.2.0]: ").strip() or "v3.2.0"
inventory_content = f"""[traefik_servers]
traefik_prod ansible_host={server_ip} ansible_user={ssh_user} ansible_ssh_private_key_file={ssh_key_path}
[traefik_servers:vars]
ansible_python_interpreter=/usr/bin/python3
letsencrypt_email={letsencrypt_email}
traefik_version={traefik_version}
forgejo_url=http://eregion.chezlepro.ca:3000
forgejo_owner=Chezlepro
forgejo_repo=traefik-deploy
"""
Path("inventory.ini").write_text(inventory_content)
print(f"\n✓ Configuration créée: inventory.ini")
print("\nProchaines étapes:")
print(" make test-connection")
print(" make build-forgejo")
print(" make deploy")
if __name__ == "__main__":
setup()

80
scripts/trigger_build.py Executable file
View file

@ -0,0 +1,80 @@
#!/usr/bin/env python3
import sys
import requests
import configparser
from pathlib import Path
import getpass
def read_inventory():
config = configparser.ConfigParser()
config.read('inventory.ini')
if 'traefik_servers:vars' in config:
return {
'forgejo_url': config['traefik_servers:vars'].get('forgejo_url', 'http://eregion.chezlepro.ca:3000'),
'forgejo_owner': config['traefik_servers:vars'].get('forgejo_owner', 'Chezlepro'),
'forgejo_repo': config['traefik_servers:vars'].get('forgejo_repo', 'traefik-deploy'),
'traefik_version': config['traefik_servers:vars'].get('traefik_version', 'v3.2.0')
}
return None
def trigger_workflow(forgejo_url, owner, repo, version, token):
api_url = f"{forgejo_url}/api/v1/repos/{owner}/{repo}/actions/workflows/build.yml/dispatches"
headers = {
"Authorization": f"token {token}",
"Content-Type": "application/json"
}
data = {
"ref": "main",
"inputs": {
"traefik_version": version
}
}
print(f"🏗️ Déclenchement de la compilation Traefik {version}...")
print(f" URL: {forgejo_url}/{owner}/{repo}")
response = requests.post(api_url, headers=headers, json=data)
if response.status_code in [200, 201, 204]:
print(f"✅ Workflow déclenché!")
print(f"\n📊 Suivez sur: {forgejo_url}/{owner}/{repo}/actions")
return True
else:
print(f"❌ Erreur {response.status_code}: {response.text}")
return False
def main():
config = read_inventory()
if not config:
print("❌ inventory.ini introuvable. Exécutez: make setup")
sys.exit(1)
print("=" * 70)
print("🏗️ COMPILATION SUR FORGEJO")
print("=" * 70)
print(f"\nInstance: {config['forgejo_url']}")
print(f"Dépôt: {config['forgejo_owner']}/{config['forgejo_repo']}")
print(f"Version: {config['traefik_version']}\n")
token = getpass.getpass("Token Forgejo: ")
if not token:
print("❌ Token requis")
sys.exit(1)
if trigger_workflow(
config['forgejo_url'],
config['forgejo_owner'],
config['forgejo_repo'],
config['traefik_version'],
token
):
print("\n⏳ Compilation: ~10-15 minutes")
print(" Puis: make deploy")
else:
sys.exit(1)
if __name__ == "__main__":
main()