Initial commit: Traefik deployment
This commit is contained in:
commit
e85158fc04
13 changed files with 578 additions and 0 deletions
81
.forgejo/workflows/build.yml
Normal file
81
.forgejo/workflows/build.yml
Normal 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
11
.gitignore
vendored
Normal 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
53
Makefile
Normal 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
48
README.md
Normal 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
|
||||
23
ansible/deploy-traefik.yml
Normal file
23
ansible/deploy-traefik.yml
Normal 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
|
||||
4
ansible/roles/traefik/handlers/main.yml
Normal file
4
ansible/roles/traefik/handlers/main.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
- name: reload systemd
|
||||
systemd:
|
||||
daemon_reload: yes
|
||||
93
ansible/roles/traefik/tasks/main.yml
Normal file
93
ansible/roles/traefik/tasks/main.yml
Normal 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 }}"
|
||||
26
ansible/roles/traefik/templates/dynamic.yml.j2
Normal file
26
ansible/roles/traefik/templates/dynamic.yml.j2
Normal 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"
|
||||
22
ansible/roles/traefik/templates/traefik.service.j2
Normal file
22
ansible/roles/traefik/templates/traefik.service.j2
Normal 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
|
||||
44
ansible/roles/traefik/templates/traefik.yml.j2
Normal file
44
ansible/roles/traefik/templates/traefik.yml.j2
Normal 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"
|
||||
43
ansible/rollback-traefik.yml
Normal file
43
ansible/rollback-traefik.yml
Normal 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
50
scripts/setup.py
Executable 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
80
scripts/trigger_build.py
Executable 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()
|
||||
Loading…
Reference in a new issue