add plattform services

This commit is contained in:
Marcel Arndt
2026-01-05 16:01:40 +01:00
parent e25d8dd5d9
commit 193319fa52
65 changed files with 4240 additions and 39 deletions
@@ -0,0 +1,33 @@
---
# - name: AUTHENTIK | Verzeichnisse erstellen und Berechtigungen setzen
# ansible.builtin.file:
# path: "/mnt/cephfs/authentik/data/{{ item }}"
# state: directory
# owner: 1000
# group: 1000
# mode: '0755'
# loop:
# - cache
# - certs
# - db
# - media
# - templates
# run_once: true
# delegate_to: "{{ groups['managers'][0] }}"
- name: AUTHENTIK | Generate Compose file
ansible.builtin.template:
src: docker-compose.yml.j2
dest: /mnt/cephfs/authentik/authentik.yml
mode: 0644
run_once: true
delegate_to: "{{ groups['managers'][0] }}"
- name: AUTHENTIK | Deploy app stack
community.docker.docker_stack:
state: present
name: authentik
compose:
- /mnt/cephfs/authentik/authentik.yml
delegate_to: "{{ groups['managers'][0] }}"
run_once: true
@@ -0,0 +1,100 @@
---
networks:
traefik_public:
external: true
internal:
services:
postgresql:
image: docker.io/library/postgres:16-alpine
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $POSTGRES_DB -U $POSTGRES_USER"]
start_period: 20s
interval: 30s
retries: 5
timeout: 5s
volumes:
- /mnt/cephfs/authentik/data/db:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: "{{ pg_pass }}"
POSTGRES_USER: "{{ pg_user | default('authentik') }}"
POSTGRES_DB: "{{ pg_db | default('authentik') }}"
networks:
- internal
redis:
image: docker.io/library/redis:alpine
command: --save 60 1 --loglevel warning
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
start_period: 20s
interval: 30s
retries: 5
timeout: 3s
volumes:
- /mnt/cephfs/authentik/data/cache:/data
networks:
- internal
server:
image: "{{ authentik_image | default('ghcr.io/goauthentik/server') }}:{{ authentik_tag | default('2025.6.3') }}"
restart: unless-stopped
command: server
environment:
AUTHENTIK_SECRET_KEY: "{{ authentik_secret_key }}"
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__USER: "{{ pg_user | default('authentik') }}"
AUTHENTIK_POSTGRESQL__NAME: "{{ pg_db | default('authentik') }}"
AUTHENTIK_POSTGRESQL__PASSWORD: "{{ pg_pass }}"
AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
volumes:
- /mnt/cephfs/authentik/data/media:/media
- /mnt/cephfs/authentik/data/templates:/templates
networks:
- traefik_public
- internal
deploy:
labels:
traefik.enable: "true"
traefik.swarm.network: {{ traefik_net }}
traefik.http.routers.authentik.rule: Host(`{{ traefik_route }}`) || HostRegexp(`{subdomain:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?}.genius.ceo`) && PathPrefix(`/outpost.goauthentik.io/`)
traefik.http.routers.authentik.entrypoints: https
traefik.http.routers.authentik.tls: "true"
traefik.http.routers.authentik.tls.certresolver: main
traefik.http.services.authentik.loadbalancer.server.port: 9000
# - "traefik.enable=true"
# - "traefik.swarm.network={{ traefik_net }}"
# - "traefik.http.routers.authentik.rule=Host(`{{ traefik_route }}`) || HostRegexp(`{subdomain:[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?}.genius.ceo`) && PathPrefix(`/outpost.goauthentik.io/`)"
# - "traefik.http.routers.authentik.entrypoints=https"
# - "traefik.http.routers.authentik.tls=true"
# - "traefik.http.routers.authentik.tls.certresolver=main"
# - "traefik.http.services.authentik.loadbalancer.server.port=9000"
worker:
image: "{{ authentik_image | default('ghcr.io/goauthentik/server') }}:{{ authentik_tag | default('2025.6.3') }}"
restart: unless-stopped
command: worker
environment:
AUTHENTIK_SECRET_KEY: "{{ authentik_secret_key }}"
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__USER: "{{ pg_user | default('authentik') }}"
AUTHENTIK_POSTGRESQL__NAME: "{{ pg_db | default('authentik') }}"
AUTHENTIK_POSTGRESQL__PASSWORD: "{{ pg_pass }}"
# `user: root` and the docker socket volume are optional.
# See more for the docker socket integration here:
# https://goauthentik.io/docs/outposts/integrations/docker
# Removing `user: root` also prevents the worker from fixing the permissions
# on the mounted folders, so when removing this make sure the folders have the correct UID/GID
# (1000:1000 by default)
user: root
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /mnt/cephfs/authentik/data/media:/media
- /mnt/cephfs/authentik/data/certs:/certs
- /mnt/cephfs/authentik/data/templates:/templates
networks:
- internal
+11
View File
@@ -0,0 +1,11 @@
---
authentik_image: "ghcr.io/goauthentik/server"
authentik_tag: "2025.6.3"
authentik_secret_key: ""
pg_user: "authentik"
pg_pass: ""
pg_db: "authentik"
traefik_net: "traefik_public"
traefik_route: "auth.genius.ceo"
@@ -0,0 +1,93 @@
---
- name: CEPH | Private IP des ersten Managers ermitteln
ansible.builtin.set_fact:
ceph_bootstrap_ip: "{{ hostvars[inventory_hostname]['ansible_' + private_interface]['ipv4']['address'] }}"
when: inventory_hostname == groups['managers'][0]
- name: CEPH | Cluster auf dem ersten Manager initialisieren (Bootstrap)
ansible.builtin.command:
cmd: "cephadm bootstrap --mon-ip {{ ceph_bootstrap_ip }}"
creates: /etc/ceph/ceph.conf
when: inventory_hostname == groups['managers'][0]
- name: CEPH | Öffentlichen SSH-Schlüssel von cephadm abrufen
ansible.builtin.command: "cephadm shell -- ceph cephadm get-pub-key"
register: cephadm_pub_key
changed_when: false
delegate_to: "{{ groups['managers'][0] }}"
run_once: true
- name: CEPH | Öffentlichen Schlüssel von cephadm auf allen Knoten für root verteilen
ansible.posix.authorized_key:
user: root
key: "{{ hostvars[groups['managers'][0]]['cephadm_pub_key'].stdout }}"
state: present
key_options: 'no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty'
- name: CEPH | Andere Knoten zum Ceph-Cluster hinzufügen
ansible.builtin.command:
cmd: "ceph orch host add {{ item }} {{ hostvars[item]['ansible_' + private_interface]['ipv4']['address'] }}"
loop: "{{ groups['all'] }}"
delegate_to: "{{ groups['managers'][0] }}"
run_once: true
- name: CEPH | Prüfen, ob bereits OSDs (Speichergeräte) vorhanden sind
ansible.builtin.command: "ceph osd ls"
register: existing_osds
changed_when: false
failed_when: false
delegate_to: "{{ groups['managers'][0] }}"
run_once: true
- name: CEPH | Spezifische Festplatte ({{ ceph_osd_device }}) auf jedem Knoten als OSD hinzufügen
ansible.builtin.command: "ceph orch daemon add osd {{ item }}:{{ ceph_osd_device }}"
loop: "{{ groups['all'] }}"
delegate_to: "{{ groups['managers'][0] }}"
run_once: true
when: existing_osds.stdout | length == 0
- name: CEPH | Prüfen, ob CephFS bereits existiert
ansible.builtin.command: "ceph fs ls -f json"
register: cephfs_list
changed_when: false
delegate_to: "{{ groups['managers'][0] }}"
run_once: true
- name: CEPH | CephFS Pools und Dateisystem erstellen, falls nicht vorhanden
block:
- name: Metadaten-Pool für CephFS erstellen
ansible.builtin.command: "ceph osd pool create {{ cephfs_name }}_metadata"
- name: Daten-Pool für CephFS erstellen
ansible.builtin.command: "ceph osd pool create {{ cephfs_name }}_data"
- name: CephFS-Dateisystem erstellen
ansible.builtin.command: "ceph fs new {{ cephfs_name }} {{ cephfs_name }}_metadata {{ cephfs_name }}_data"
when: cephfs_list.stdout | from_json | length == 0
delegate_to: "{{ groups['managers'][0] }}"
run_once: true
- name: CEPH | Metadaten-Server (MDS) für CephFS starten
ansible.builtin.command: "ceph orch apply mds {{ cephfs_name }} --placement=2"
delegate_to: "{{ groups['managers'][0] }}"
run_once: true
when: cephfs_list.stdout | from_json | length == 0
- name: CEPH | Ceph Admin-Schlüssel für das Mounten abrufen
ansible.builtin.command: "ceph auth get-key client.admin"
register: ceph_admin_key
changed_when: false
delegate_to: "{{ groups['managers'][0] }}"
run_once: true
- name: CEPH | Mount-Punkt für CephFS erstellen
ansible.builtin.file:
path: /mnt/cephfs
state: directory
mode: '0755'
- name: CEPH | CephFS auf allen Knoten mounten (und in /etc/fstab eintragen)
ansible.posix.mount:
path: /mnt/cephfs
src: "{{ hostvars[groups['managers'][0]]['ceph_bootstrap_ip'] }}:/"
fstype: ceph
opts: "name=admin,secret={{ ceph_admin_key.stdout }}"
state: mounted
+108
View File
@@ -0,0 +1,108 @@
---
- name: COMMON | Systempakete aktualisieren und upgraden
ansible.builtin.apt:
update_cache: true
upgrade: dist
autoremove: true
autoclean: true
- name: COMMON | Notwendige Pakete installieren
ansible.builtin.apt:
name:
- ufw
- fail2ban
- unattended-upgrades
- apt-listchanges
- docker-ce
- python3-pip
- chrony
- lvm2
- cephadm
- ceph-common
state: present
- name: COMMON | Chrony Dienst starten und aktivieren
ansible.builtin.service:
name: chronyd
state: started
enabled: true
- name: COMMON | Docker Dienst starten und aktivieren
ansible.builtin.service:
name: docker
state: started
enabled: true
- name: COMMON | Einen dedizierten Admin-Benutzer erstellen
ansible.builtin.user:
name: "{{ admin_user }}"
password: "{{ admin_password}}"
shell: /bin/bash
groups: sudo,docker
append: true
state: present
- name: COMMON | SSH-Schlüssel für den Admin-Benutzer einrichten
ansible.posix.authorized_key:
user: "{{ admin_user }}"
key: "{{ item }}"
state: present
with_items: "{{ authorized_keys }}"
- name: COMMON | cephadm-Benutzer erstellen
ansible.builtin.user:
name: "cephadm"
password: "{{ cephadm_password }}"
shell: /bin/bash
groups: sudo,docker
append: yes
state: present
- name: COMMON | .ssh Verzeichnis für cephadm-Benutzer erstellen
ansible.builtin.file:
path: /home/cephadm/.ssh
state: directory
- name: COMMON | Passwortloses Sudo für cephadm-Benutzer erlauben
ansible.builtin.copy:
dest: "/etc/sudoers.d/91-cephadm-nopasswd"
content: "cephadm ALL=(ALL) NOPASSWD: ALL"
mode: '0440'
validate: 'visudo -cf %s'
- name: COMMON | ed25519 SSH-Schlüssel für cephadm-Benutzer generieren (nur auf dem ersten Manager)
community.crypto.openssh_keypair:
path: /home/cephadm/.ssh/id_ed25519
type: ed25519
owner: cephadm
group: cephadm
mode: '0600'
when: inventory_hostname == groups['managers'][0]
- name: COMMON | Öffentlichen SSH-Schlüssel von cephadm abrufen
ansible.builtin.slurp:
src: /home/cephadm/.ssh/id_ed25519.pub
register: cephadm_ssh_pub_key
when: inventory_hostname == groups['managers'][0]
- name: COMMON | Öffentlichen SSH-Schlüssel von cephadm auf allen Knoten verteilen
ansible.posix.authorized_key:
user: cephadm
key: "{{ hostvars[groups['managers'][0]]['cephadm_ssh_pub_key']['content'] | b64decode }}"
state: present
- name: COMMON | Automatische Sicherheitsupdates konfigurieren
ansible.builtin.copy:
src: assets/50unattended-upgrades
dest: /etc/apt/apt.conf.d/50unattended-upgrades
owner: root
group: root
mode: '0644'
- name: COMMON | Periodische Auto-Updates aktivieren
ansible.builtin.copy:
src: assets/20auto-upgrades
dest: /etc/apt/apt.conf.d/20auto-upgrades
owner: root
group: root
mode: '0644'
@@ -0,0 +1,58 @@
---
- name: SWARM | Ensure Docker SDK for Python is installed
ansible.builtin.apt:
name: python3-docker
state: present
- name: SWARM | Get interface IP address for the manager
ansible.builtin.set_fact:
manager_ip: "{{ hostvars[inventory_hostname]['ansible_' + private_interface]['ipv4']['address'] }}"
when: inventory_hostname == groups['managers'][0]
- name: SWARM | Initialize the Docker Swarm
community.docker.docker_swarm:
state: present
advertise_addr: "{{ manager_ip }}"
when: inventory_hostname == groups['managers'][0]
register: swarm_init_result
- name: SWARM | Get the join tokens
community.docker.docker_swarm_info:
register: swarm_info
when: inventory_hostname == groups['managers'][0]
- name: SWARM | Verify that join tokens were fetched
ansible.builtin.assert:
that:
- swarm_info is defined
- swarm_info.swarm_facts is defined
- swarm_info.swarm_facts.JoinTokens.Manager is defined
- swarm_info.swarm_facts.JoinTokens.Worker is defined
fail_msg: "Konnte die Join-Tokens vom Swarm Manager nicht abrufen. Ist der Swarm korrekt initialisiert?"
success_msg: "Join-Tokens erfolgreich abgerufen."
when: inventory_hostname == groups['managers'][0]
- name: SWARM | Join manager nodes to the Swarm
community.docker.docker_swarm:
state: join
remote_addrs: [ "{{ hostvars[groups['managers'][0]]['manager_ip'] }}:2377" ]
join_token: "{{ hostvars[groups['managers'][0]]['swarm_info']['swarm_facts']['JoinTokens']['Manager'] }}"
when: inventory_hostname in groups['managers']
- name: SWARM | Join worker nodes to the Swarm
community.docker.docker_swarm:
state: join
remote_addrs: [ "{{ hostvars[groups['managers'][0]]['manager_ip'] }}:2377" ]
join_token: "{{ hostvars[groups['managers'][0]]['swarm_info']['swarm_facts']['JoinTokens']['Worker'] }}"
when: inventory_hostname in groups['workers']
- name: SWARM | Verify Swarm Cluster State (run on manager)
ansible.builtin.command: docker node ls
register: swarm_nodes
changed_when: false
when: inventory_hostname == groups['managers'][0]
- name: SWARM | Display cluster state
ansible.builtin.debug:
msg: "{{ swarm_nodes.stdout_lines }}"
when: inventory_hostname == groups['managers'][0]
@@ -0,0 +1,3 @@
---
dockge_stacks_dir: /mnt/cephfs/dockge/stacks
dockge_data_dir: /mnt/cephfs/dockge/data
+42
View File
@@ -0,0 +1,42 @@
---
- name: DOCKGE | Copy Stack Files
copy:
directory_mode: true
src: /Users/d3r0/dev/repositories/active/gc/iac/ansible/resources/dockge
dest: /mnt/cephfs
run_once: true
delegate_to: "{{ groups['managers'][0] }}"
# - name: DOCKGE | Sicherstellen, dass das Verzeichnis für die Anwendungs Daten existiert
# ansible.builtin.file:
# path: "{{ dockge_data_dir }}"
# state: directory
# owner: root
# group: root
# mode: '0755'
# become: true
# - name: DOCKGE | Sicherstellen, dass das Verzeichnis für die Stacks existiert
# ansible.builtin.file:
# path: "{{ dockge_stacks_dir }}"
# state: directory
# owner: root
# group: root
# mode: '0755'
# become: true
# - name: DOCKGE | Stack aus der Template-Datei bereitstellen
# community.docker.docker_stack:
# state: present
# name: dockge
# compose:
# - "{{ lookup('template', '../../../resources/dockge/dockge.yml') }}"
# delegate_to: "{{ groups['managers'][0] }}"
# run_once: true
- name: DOCKGE | Deploy app stack
community.docker.docker_stack:
state: present
name: dockge
compose:
- /mnt/cephfs/dockge/dockge.yml
delegate_to: "{{ groups['managers'][0] }}"
run_once: true
@@ -0,0 +1,9 @@
---
- name: FAIL2BAN | Eine lokale Jail-Konfiguration erstellen
ansible.builtin.template:
src: jail.local.j2
dest: /etc/fail2ban/jail.local
owner: root
group: root
mode: '0644'
notify: restart fail2ban
@@ -0,0 +1,8 @@
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
[sshd]
enabled = true
port = {{ ssh_port }}
+14
View File
@@ -0,0 +1,14 @@
postgres_version: 16-alpine
gitea_version: "1.21"
gitea_domain: "{{ subdomain }}.{{ main_domain }}"
gitea_http_port: 3000
gitea_ssh_port: 2222
data_dir: "{{ ceph_volume }}/gitea"
subdomain: git
gitea_db_type: "postgres"
gitea_db_host: db:5432
gitea_db_name: "gitea"
gitea_db_user: "gitea"
gitea_db_password: ""
+38
View File
@@ -0,0 +1,38 @@
- name: GITEA | Ensure data directories
ansible.builtin.file:
path: '{{ data_dir }}/data'
state: directory
owner: 1000
group: 1000
mode: '0750'
recurse: yes
delegate_to: "{{ groups['managers'][0] }}"
- name: GITEA | Ensure DB data directories
ansible.builtin.file:
path: "{{ data_dir }}/data/db"
state: directory
# Postgres Alpine nutzt UID 70 (postgres).
# Bei Debian-Images wäre es 999.
owner: 70
group: 70
mode: '0700'
recurse: yes
delegate_to: "{{ groups['managers'][0] }}"
- name: GITEA | Generate Compose file
ansible.builtin.template:
src: docker-compose.yml.j2
dest: '{{ data_dir }}/gitea.yml'
mode: 0644
run_once: true
delegate_to: "{{ groups['managers'][0] }}"
- name: GITEA | Deploy stack
community.docker.docker_stack:
state: present
name: gitea
compose:
- '{{ data_dir }}/gitea.yml'
delegate_to: "{{ groups['managers'][0] }}"
run_once: true
@@ -0,0 +1,62 @@
networks:
{{ traefik_public_net }}:
external: true
internal:
services:
server:
image: gitea/gitea:{{ gitea_version }}
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE={{ gitea_db_type }}
- GITEA__database__HOST={{ gitea_db_host }}
- GITEA__database__NAME={{ gitea_db_name }}
- GITEA__database__USER={{ gitea_db_user }}
- GITEA__database__PASSWD={{ gitea_db_password }}
- GITEA__server__DOMAIN={{ gitea_domain }}
- GITEA__server__SSH_DOMAIN={{ gitea_domain }}
- GITEA__server__SSH_PORT={{ gitea_ssh_port }}
- GITEA__server__ROOT_URL=https://{{ gitea_domain }}/
volumes:
- {{ data_dir }}/data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
networks:
- internal
- {{ traefik_public_net }}
ports:
- "{{ gitea_ssh_port }}:22"
deploy:
mode: replicated
replicas: 1
labels:
- "traefik.enable=true"
- "traefik.docker.network={{ traefik_public_net }}"
- "traefik.http.routers.gitea.rule=Host(`{{ gitea_domain }}`)"
- "traefik.http.routers.gitea.entrypoints=https"
- "traefik.http.routers.gitea.tls.certresolver=main"
- "traefik.http.services.gitea.loadbalancer.server.port=3000"
db:
image: postgres:{{ postgres_version }}
restart: always
environment:
- POSTGRES_USER={{ gitea_db_user }}
- POSTGRES_PASSWORD={{ gitea_db_password }}
- POSTGRES_DB={{ gitea_db_name }}
networks:
- internal
volumes:
- {{ data_dir }}/data/db:/var/lib/postgresql/data
command:
- "postgres"
- "-c"
- "fsync=on"
- "-c"
- "full_page_writes=on"
- "-c"
- "synchronous_commit=on"
deploy:
mode: replicated
replicas: 1
+47
View File
@@ -0,0 +1,47 @@
---
- name: KESTRA | Ensure data directory
ansible.builtin.file:
path: '{{ data_dir }}/data/data'
state: directory
mode: '0755'
run_once: true
delegate_to: "{{ groups['managers'][0] }}"
- name: KESTRA | Ensure db directory
ansible.builtin.file:
path: '{{ data_dir }}/data/db'
state: directory
mode: '0755'
run_once: true
delegate_to: "{{ groups['managers'][0] }}"
- name: KESTRA | Konfigurationsdatei für tmpfiles.d erstellen
ansible.builtin.copy:
content: "d /tmp/kestra-wd 0755 root root -"
dest: /etc/tmpfiles.d/kestra-wd.conf
owner: root
group: root
mode: '0644'
- name: KESTRA | Create Kestra working directory
ansible.builtin.file:
path: /tmp/kestra-wd
state: directory
mode: '0755'
- name: KESTRA | Generate Compose file
ansible.builtin.template:
src: docker-compose.yml.j2
dest: '{{ data_dir }}/kestra.yml'
mode: 0644
run_once: true
delegate_to: "{{ groups['managers'][0] }}"
- name: KESTRA | Deploy stack
community.docker.docker_stack:
state: present
name: kestra
compose:
- /mnt/cephfs/kestra/kestra.yml
delegate_to: "{{ groups['managers'][0] }}"
run_once: true
@@ -0,0 +1,92 @@
networks:
internal:
{{ traefik_public_net }}:
external: true
services:
postgres:
image: postgres:17
volumes:
- {{ data_dir }}/data/db:/var/lib/postgresql/data
environment:
POSTGRES_DB: {{ kestra.db.name }}
POSTGRES_USER: {{ kestra.db.user }}
POSTGRES_PASSWORD: "{{ kestra.db.pass }}"
healthcheck:
test: ["CMD-SHELL", "pg_isready -d '$${POSTGRES_DB}' -U $${POSTGRES_USER}"]
interval: 30s
timeout: 10s
retries: 10
networks:
- internal
deploy:
mode: replicated
replicas: 1
kestra:
image: kestra/kestra:v0.24.2
entrypoint: /bin/bash
# Note that this is meant for development only. Refer to the documentation for production deployments of Kestra which runs without a root user.
user: "root"
command:
- -c
- /app/kestra server standalone --worker-thread=128
volumes:
- {{ data_dir }}/data/data:/app/storage
- /var/run/docker.sock:/var/run/docker.sock
- /tmp/kestra-wd:/tmp/kestra-wd
environment:
KESTRA_CONFIGURATION: |
datasources:
postgres:
url: jdbc:postgresql://postgres:5432/kestra
driverClassName: org.postgresql.Driver
username: {{ kestra.db.user }}
password: {{ kestra.db.pass }}
kestra:
tutorialFlows:
enabled: false
traces:
root: DEFAULT
micronaut:
metrics:
export:
otlp:
enabled: true
url: http://signoz_otel-collector:4318/v1/metrics
otel:
traces:
exporter: otlp
exporter:
otlp:
endpoint: http://signoz_otel-collector:4318
server:
basic-auth:
username: {{ kestra.basic_auth.user }}
password: {{ kestra.basic_auth.pass }}
repository:
type: postgres
storage:
type: local
local:
base-path: "/app/storage"
queue:
type: postgres
tasks:
tmp-dir:
path: /tmp/kestra-wd/tmp
url: http://localhost:8080/
networks:
- {{ traefik_public_net }}
- internal
deploy:
mode: replicated
replicas: 1
labels:
- "traefik.enable=true"
- "traefik.swarm.network={{ traefik_public_net }}"
- "traefik.http.routers.kestra.rule=Host(`{{ subdomain }}.{{ main_domain }}`)"
- "traefik.http.routers.kestra.entrypoints=https"
- "traefik.http.routers.kestra.tls=true"
- "traefik.http.routers.kestra.tls.certresolver=main"
- "traefik.http.services.kestra.loadbalancer.server.port=8080"
+11
View File
@@ -0,0 +1,11 @@
subdomain: kestra
data_dir: "{{ ceph_volume }}/kestra"
kestra:
basic_auth:
user: "ma@coachhamburg.com"
pass: "igyozi9B87yTeiQ6z2sbe8Y4aQLJV58jdaCNu"
db:
name: kestra
user: kestra
pass: ""
@@ -0,0 +1,13 @@
---
- name: Copy Stack Files
copy:
directory_mode: true
src: /Users/d3r0/dev/repositories/active/gc/iac/ansible/resources/monitoring
dest: /srv
- block:
- name: Deploy Monitoring stack
community.docker.docker_stack:
state: present
name: monitoring
compose:
- /srv/monitoring/observability.yml
@@ -0,0 +1,25 @@
---
- name: PORTAINER | Ensure data directories
ansible.builtin.file:
path: '{{ data_dir }}/data'
state: directory
mode: '0755'
run_once: true
delegate_to: "{{ groups['managers'][0] }}"
- name: PORTAINER | Generate Compose file
ansible.builtin.template:
src: docker-compose.yml.j2
dest: '{{ data_dir }}/portainer.yml'
mode: 0644
run_once: true
delegate_to: "{{ groups['managers'][0] }}"
- name: PORTAINER | Deploy stack
community.docker.docker_stack:
state: present
name: portainer
compose:
- '{{ data_dir }}/portainer.yml'
delegate_to: "{{ groups['managers'][0] }}"
run_once: true
@@ -0,0 +1,37 @@
version: '3.2'
services:
agent:
image: portainer/agent:{{ portainer_version }}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /var/lib/docker/volumes:/var/lib/docker/volumes
networks:
- {{ traefik_public_net }}
deploy:
mode: global
placement:
constraints: [node.platform.os == linux]
portainer:
image: portainer/portainer-ce:{{ portainer_version }}
command: -H tcp://portainer_agent:9001 --tlsskipverify
volumes:
- {{ data_dir }}/data:/data
networks:
- {{ traefik_public_net }}
deploy:
mode: replicated
replicas: 1
labels:
- "traefik.enable=true"
- "traefik.swarm.network={{ traefik_public_net }}"
- "traefik.http.routers.portainer.rule=Host(`{{ subdomain }}.{{ main_domain }}`)"
- "traefik.http.routers.portainer.entrypoints=https"
- "traefik.http.routers.portainer.tls=true"
- "traefik.http.routers.portainer.tls.certresolver=main"
- "traefik.http.services.portainer.loadbalancer.server.port=9000"
networks:
{{ traefik_public_net }}:
external: true
@@ -0,0 +1,4 @@
subdomain: port
data_dir: "{{ ceph_volume }}/portainer"
portainer_version: 2.33.5
@@ -0,0 +1,18 @@
---
- name: Copy Stack Files
copy:
directory_mode: true
src: /Users/d3r0/dev/repositories/active/gc/iac/ansible/resources/signoz-infra
dest: /mnt/cephfs
delegate_to: "{{ groups['managers'][0] }}"
run_once: true
- name: Deploy Signoz Infra stack
community.docker.docker_stack:
state: present
name: signoz-infra
prune: true
compose:
- /mnt/cephfs/signoz-infra/signoz-infra.yml
delegate_to: "{{ groups['managers'][0] }}"
run_once: true
+18
View File
@@ -0,0 +1,18 @@
---
- name: Copy Stack Files
copy:
directory_mode: true
src: /Users/d3r0/dev/repositories/active/gc/iac/ansible/resources/signoz
dest: /mnt/cephfs
delegate_to: "{{ groups['managers'][0] }}"
run_once: true
- name: Deploy Signoz stack
community.docker.docker_stack:
state: present
name: signoz
prune: true
compose:
- /mnt/cephfs/signoz/signoz.yml
delegate_to: "{{ groups['managers'][0] }}"
run_once: true
@@ -0,0 +1,30 @@
---
- name: SSH | Ensure privilege separation directory exists
ansible.builtin.file:
path: /run/sshd
state: directory
mode: '0755'
- name: SSH | Root-Login nur mit Schlüssel erlauben
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PermitRootLogin'
line: 'PermitRootLogin prohibit-password'
validate: 'sshd -t -f %s'
notify: restart sshd
- name: SSH | Passwort-Authentifizierung deaktivieren
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PasswordAuthentication'
line: 'PasswordAuthentication no'
validate: 'sshd -t -f %s'
notify: restart sshd
- name: SSH | Leere Passwörter verbieten
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PermitEmptyPasswords'
line: 'PermitEmptyPasswords no'
validate: 'sshd -t -f %s'
notify: restart sshd
@@ -0,0 +1,18 @@
[http]
[http.middlewares]
[http.middlewares.authentik.forwardAuth]
address = "http://authentik_server:9000/outpost.goauthentik.io/auth/traefik"
trustForwardHeader = true
authResponseHeaders = [
"X-authentik-username",
"X-authentik-groups",
"X-authentik-email",
"X-authentik-name",
"X-authentik-uid",
"X-authentik-jwt",
"X-authentik-meta-jwks",
"X-authentik-meta-outpost",
"X-authentik-meta-provider",
"X-authentik-meta-app",
"X-authentik-meta-version"
]
@@ -0,0 +1,80 @@
[global]
checkNewVersion = true
sendAnonymousUsage = false
[experimental]
otlpLogs = true
[core]
defaultRuleSyntax = "v2"
[accessLog]
filePath = "/logs/access.log"
format = "json"
# Enable the Dashboard
[api]
dashboard = true
# Write out Traefik logs
[log]
level = "INFO"
format = "json"
filePath = "/logs/traefik.log"
# [log.otlp.http]
# endpoint = "http://signoz_otel-collector:4318/v1/logs"
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.http.redirections.entryPoint]
to = "https"
scheme = "https"
[entryPoints.https]
address = ":443"
# [entryPoints.https.http.tls]
# certResolver = "main"
# OTel
# [tracing]
# serviceName = "traefik"
# [tracing.otlp.http]
# endpoint = "http://signoz_otel-collector:4318/v1/traces"
# [tracing.otlp.http.tls]
# insecureSkipVerify = true
# # Metrics
# [metrics]
# addInternals = false
# [metrics.otlp]
# serviceName = "traefik"
# addEntryPointsLabels = true
# addRoutersLabels = true
# addServicesLabels = true
# [metrics.otlp.http]
# endpoint = "http://signoz_otel-collector:4318/v1/metrics"
# [metrics.otlp.grpc]
# endpoint = "monitoring_alloy:4317"
# insecure = true
# Let's Encrypt
[certificatesResolvers.main.acme]
email = "ma@coachhamburg.com"
storage = "acme.json"
# uncomment to use staging CA for testing
# caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
# [certificatesResolvers.main.acme.tlsChallenge]
[certificatesResolvers.main.acme.dnsChallenge]
provider = "digitalocean"
# Uncomment to use HTTP validation, like a caveman!
# [certificatesResolvers.main.acme.httpChallenge]
# entryPoint = "http"
[providers]
[providers.swarm]
endpoint = "unix:///var/run/docker.sock"
exposedByDefault = false
[providers.file]
directory = "/etc/traefik/dynamic"
watch = true
+44
View File
@@ -0,0 +1,44 @@
---
- name: TRAEFIK | Copy Stack Files
copy:
directory_mode: true
src: traefik
dest: "{{ ceph_volume }}"
run_once: true
delegate_to: "{{ groups['managers'][0] }}"
- name: TRAEFIK | Generate Compose file
ansible.builtin.template:
src: docker-compose.yml.j2
dest: "{{ data_dir }}/traefik.yml"
mode: 0644
run_once: true
delegate_to: "{{ groups['managers'][0] }}"
- name: TRAEFIK | Ensure permissions on acme.json
ansible.builtin.file:
path: "{{ data_dir }}/data/acme.json"
mode: '0600'
run_once: true
delegate_to: "{{ groups['managers'][0] }}"
- name: TRAEFIK | traefik_public Netzwerk erstellen
community.docker.docker_network:
name: traefik_public
driver: overlay
state: present
attachable: yes
ipam_config:
- subnet: '172.16.200.0/24'
gateway: '172.16.200.1'
run_once: true
delegate_to: "{{ groups['managers'][0] }}"
- name: TRAEFIK | Deploy app stack
community.docker.docker_stack:
state: present
name: traefik
compose:
- "{{ data_dir }}/traefik.yml"
delegate_to: "{{ groups['managers'][0] }}"
run_once: true
@@ -0,0 +1,55 @@
services:
app:
image: traefik:{{ traefik_version }}
ports:
- target: 80
published: 80
protocol: tcp
mode: host
- target: 443
published: 443
protocol: tcp
mode: host
- target: 8080
published: 8080
protocol: tcp
environment:
# - HETZNER_API_TOKEN={{ hetzner_api_key }}
- DO_AUTH_TOKEN={{ do_api_key }}
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- {{ data_dir }}/config:/etc/traefik
- {{ data_dir }}/data/logs:/logs
- {{ data_dir }}/data/acme.json:/acme.json
# healthcheck:
# test: ["CMD", "traefik", "healthcheck", "--ping"]
# timeout: 1s
# interval: 10s
# retries: 3
# start_period: 10s
networks:
- {{ traefik_public_net }}
# Global mode makes an instance of traefik listen on _every_ node, so that regardless of which
# node the request arrives on, it'll be forwarded to the correct backend service.
deploy:
mode: global
labels:
- "traefik.enable=true"
- "traefik.swarm.network={{ traefik_public_net }}"
- "traefik.http.routers.api.rule=Host(`{{ subdomain }}.{{ main_domain }}`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"
- "traefik.http.routers.api.entrypoints=https"
{% if use_authentik %}
- "traefik.http.routers.api.middlewares=authentik@file"
{% endif %}
- "traefik.http.routers.api.tls.domains[0].main={{ main_domain }}"
- "traefik.http.routers.api.tls.domains[0].sans=*.{{ main_domain }}"
- "traefik.http.routers.api.tls=true"
- "traefik.http.routers.api.tls.certresolver=main"
- "traefik.http.routers.api.service=api@internal"
- "traefik.http.services.dummy.loadbalancer.server.port=9999"
placement:
constraints: [node.role == manager]
networks:
{{ traefik_public_net }}:
external: true
+5
View File
@@ -0,0 +1,5 @@
subdomain: router
use_authentik: true
data_dir: "{{ ceph_volume }}/traefik"
traefik_version: v3.6.2
@@ -0,0 +1,84 @@
---
- name: FIREWALL | UFW auf Standardeinstellungen zurücksetzen
community.general.ufw:
state: reset
- name: FIREWALL | Standardmäßig allen ausgehenden Traffic erlauben
community.general.ufw:
direction: outgoing
policy: allow
- name: FIREWALL | Standardmäßig allen eingehenden Traffic blockieren
community.general.ufw:
direction: incoming
policy: deny
- name: FIREWALL | Eingehenden SSH-Traffic auf öffentlichem Interface erlauben
community.general.ufw:
rule: allow
port: "{{ ssh_port }}"
proto: tcp
interface: "{{ public_interface }}"
direction: in
- name: FIREWALL | Eingehenden SSH-Traffic auf privatem Interface erlauben
community.general.ufw:
rule: allow
port: "{{ ssh_port }}"
proto: tcp
interface: "{{ private_interface }}"
direction: in
- name: FIREWALL | Eingehenden HTTP/HTTPS-Traffic auf öffentlichem Interface erlauben
community.general.ufw:
rule: allow
port: "{{ item.port }}"
proto: "{{ item.proto }}"
interface: "{{ public_interface }}"
direction: in
with_items:
- { port: '80', proto: 'tcp' }
- { port: '443', proto: 'tcp' }
- name: FIREWALL | Ceph Monitor Ports auf privatem Interface erlauben
community.general.ufw:
rule: allow
port: "{{ item }}"
proto: tcp
interface: "{{ private_interface }}"
direction: in
with_items:
- '3300'
- '6789'
- name: FIREWALL | Ceph OSD/MGR Port-Range auf öffentlichem Interface erlauben
community.general.ufw:
rule: allow
port: "6800:7568"
proto: tcp
interface: "{{ private_interface }}"
direction: in
- name: FIREWALL | Docker Swarm Management Ports auf privatem Interface erlauben
community.general.ufw:
rule: allow
port: "2377"
proto: tcp
interface: "{{ private_interface }}"
direction: in
- name: FIREWALL | Docker Swarm Discovery/Network Ports auf privatem Interface erlauben
community.general.ufw:
rule: allow
port: "{{ item.port }}"
proto: "{{ item.proto }}"
interface: "{{ private_interface }}"
direction: in
with_items:
- { port: '7946', proto: 'tcp' }
- { port: '7946', proto: 'udp' }
- { port: '4789', proto: 'udp' }
- name: FIREWALL | UFW aktivieren
community.general.ufw:
state: enabled