refactor: move Ansible roles to standard ansible/roles/ location

This commit is contained in:
2026-02-25 20:46:33 -06:00
parent 74172c4e5a
commit 86708542c9
40 changed files with 1 additions and 1 deletions

View File

@@ -1,4 +0,0 @@
[Resolve]
DNS=127.0.0.1
DNSSEC=yes
DNSStubListener=no

View File

@@ -1,18 +0,0 @@
---
# Default variables for dns-manager role
# DNS Configuration
dns_management_enabled: true
use_hosts_file_fallback: false
dns_ttl: 360
dns_propagation_wait: 2
create_ptr_record: true
# Cluster Configuration (to be provided by calling role)
# cluster_endpoint: "fastpass.local.mk-labs.cloud"
# cluster_vip: "10.1.71.53"
# DNS Server Configuration (from group_vars)
# dns_server: "monorail"
# base_domain: "local.mk-labs.cloud"
# vault_technitium_api_key: "{{ vault_technitium_api_key }}"

View File

@@ -1,17 +0,0 @@
---
# role: dns-manager
# description: Reusable role for managing DNS entries across clusters using Technitium DNS
# author: mk-labs
# version: 1.0.0
- name: Create DNS entry using Technitium DNS task
ansible.builtin.include_tasks: "{{ playbook_dir }}/tasks/add_technitium_dns_entry.yml"
- name: Wait for DNS propagation
ansible.builtin.pause:
seconds: "{{ dns_propagation_wait | default(10) }}"
when: dns_management_enabled | default(true)
- name: Look up A (IPv4) records for example.org
ansible.builtin.debug:
msg: "{{ query('community.dns.lookup', 'fastpass') }}"

View File

@@ -1,147 +0,0 @@
---
# ansible/roles/haproxy/defaults/main.yml
# HAProxy version and installation
haproxy_version: "3.2"
haproxy_install_method: "package" # package or source
# System configuration
haproxy_user: haproxy
haproxy_group: haproxy
haproxy_chroot_dir: /var/lib/haproxy
haproxy_config_dir: /etc/haproxy
haproxy_log_dir: /var/log/haproxy
# Global settings
haproxy_global_maxconn: 4096
haproxy_global_log_facility: local0
haproxy_global_log_level: info
haproxy_global_nbthread: "{{ ansible_processor_vcpus }}"
# Default timeouts (milliseconds)
haproxy_timeout_connect: 5000
haproxy_timeout_client: 50000
haproxy_timeout_server: 50000
# Stats interface
haproxy_stats_enable: true
haproxy_stats_port: 8404
haproxy_stats_uri: /stats
haproxy_stats_refresh: 30s
haproxy_stats_username: admin
haproxy_stats_password: "{{ vault_haproxy_stats_password | default('changeme') }}"
# SSL/TLS configuration
haproxy_ssl_cert_dir: "{{ haproxy_config_dir }}/certs"
haproxy_ssl_default_crt: "{{ haproxy_ssl_cert_dir }}/default.pem"
haproxy_ssl_ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384"
haproxy_ssl_options: "ssl-min-ver TLSv1.2 no-tls-tickets"
# Health check defaults
haproxy_health_check_interval: 2000
haproxy_health_check_timeout: 1000
haproxy_health_check_rise: 2
haproxy_health_check_fall: 3
# Service discovery integration
haproxy_netbox_integration: false # true
haproxy_netbox_url: "http://fire-station.local.mk-labs.cloud:8000"
haproxy_netbox_token: "{{ vault_netbox_token | default('') }}"
# Frontends configuration
haproxy_frontends: []
# - name: http_front
# bind: "*:80"
# mode: http
# redirect: "scheme https code 301"
#
# - name: https_front
# bind: "*:443 ssl crt {{ haproxy_ssl_cert_dir }}"
# mode: http
# options:
# - "http-server-close"
# - "forwardfor"
# acls:
# - "netbox hdr(host) -i fire-station.local.mk-labs.cloud"
# - "wordpress hdr(host) -i be-our-guest.local.mk-labs.cloud"
# use_backends:
# - "netbox_back if netbox"
# - "wordpress_back if wordpress"
# Backends configuration
haproxy_backends: []
# - name: netbox_back
# mode: http
# balance: roundrobin
# options:
# - "httpchk GET /"
# servers:
# - name: fire-station
# address: 10.1.71.102:8000
# check: true
# TCP services configuration
haproxy_tcp_services: []
# - name: postgres
# frontend_port: 5432
# backend_port: 5432
# balance: leastconn
# servers:
# - name: netbox-db
# address: 10.1.71.102
# Firewall configuration
haproxy_configure_firewall: true
haproxy_firewall_allowed_ports:
- 80/tcp
- 443/tcp
- 8404/tcp
# Monitoring and observability
haproxy_prometheus_exporter: false
haproxy_prometheus_port: 9101
# Logging
haproxy_rsyslog_config: true
haproxy_log_rotation: true
# Certificate management - Certbot with Cloudflare DNS
haproxy_certbot_enable: true
haproxy_certbot_challenge_method: "dns-cloudflare" # or "webroot" for HTTP-01
haproxy_certbot_email: "ryan.blundon@protonmail.com"
haproxy_certbot_staging: false # Set to true for testing with Let's Encrypt staging
# Cloudflare DNS configuration
haproxy_certbot_cloudflare_api_token: "{{ vault_cloudflare_api_token }}"
haproxy_certbot_cloudflare_credentials_file: "/etc/letsencrypt/cloudflare.ini"
# Certificate domains - DNS-01 supports wildcards
haproxy_certbot_domains:
- domain: "*.mk-labs.cloud"
include_base: true
- domain: "*.local.mk-labs.cloud"
include_base: true # Also include local.mk-labs.cloud
- domain: "lightning-lane.local.mk-labs.cloud"
include_base: false
# Alternative: specific domains without wildcard
# haproxy_certbot_domains:
# - domain: "lightning-lane.local.mk-labs.cloud"
# - domain: "fire-station.local.mk-labs.cloud"
# - domain: "be-our-guest.local.mk-labs.cloud"
# Certbot renewal settings
haproxy_certbot_renewal_cron_minute: "0"
haproxy_certbot_renewal_cron_hour: "3" # 3 AM daily
haproxy_certbot_renewal_cron_day: "*"
haproxy_certbot_post_hook: "{{ haproxy_config_dir }}/certbot-post-hook.sh"
# Certificate storage
haproxy_certbot_cert_dir: "/etc/letsencrypt/live"
haproxy_certbot_archive_dir: "/etc/letsencrypt/archive"
# DNS propagation wait time (seconds)
haproxy_certbot_dns_propagation_seconds: 60
# Fallback to self-signed if Certbot fails
haproxy_certbot_fallback_selfsigned: true

View File

@@ -1,26 +0,0 @@
---
# ansible/roles/haproxy/handlers/main.yml
- name: Reload haproxy
ansible.builtin.systemd:
name: haproxy
state: reloaded
listen: reload haproxy
- name: Restart haproxy
ansible.builtin.systemd:
name: haproxy
state: restarted
daemon_reload: true
listen: restart haproxy
- name: Reload systemd
ansible.builtin.systemd:
daemon_reload: true
listen: reload systemd
- name: Restart rsyslog
ansible.builtin.systemd:
name: rsyslog
state: restarted
listen: restart rsyslog

View File

@@ -1,202 +0,0 @@
---
# ansible/roles/haproxy/tasks/certbot-cloudflare.yml
- name: Install Certbot and Cloudflare DNS plugin
ansible.builtin.package:
name:
- certbot
- python3-certbot-dns-cloudflare
state: present
- name: Create Let's Encrypt configuration directory
ansible.builtin.file:
path: /etc/letsencrypt
state: directory
owner: root
group: root
mode: '0755'
- name: Create Cloudflare credentials file
ansible.builtin.template:
src: cloudflare-credentials.ini.j2
dest: "{{ haproxy_certbot_cloudflare_credentials_file }}"
owner: root
group: root
mode: '0600'
no_log: true
- name: Create Certbot post-renewal hook script
ansible.builtin.template:
src: certbot-post-hook.sh.j2
dest: "{{ haproxy_certbot_post_hook }}"
owner: root
group: root
mode: '0755'
- name: Create Certbot renewal hooks directory
ansible.builtin.file:
path: /etc/letsencrypt/renewal-hooks/deploy
state: directory
mode: '0755'
- name: Link post-hook to renewal hooks directory
ansible.builtin.file:
src: "{{ haproxy_certbot_post_hook }}"
dest: /etc/letsencrypt/renewal-hooks/deploy/haproxy-reload.sh
state: link
force: true
- name: Check for existing certificates
ansible.builtin.stat:
path: "{{ haproxy_certbot_cert_dir }}/{{ item.domain | regex_replace('\\*\\.', '') }}"
register: existing_certs
loop: "{{ haproxy_certbot_domains }}"
loop_control:
label: "{{ item.domain }}"
- name: Build certificate request commands
ansible.builtin.set_fact:
certbot_commands: "{{ certbot_commands | default([]) + [command_item] }}"
vars:
cert_name: "{{ item.item.domain | regex_replace('\\*\\.', '') }}"
domain_list: >-
{{ [item.item.domain] +
(item.item.include_base | default(false) | ternary(
[item.item.domain | regex_replace('\\*\\.', '')],
[]
))
}}
command_item:
domain: "{{ item.item.domain }}"
cert_name: "{{ cert_name }}"
domains: "{{ domain_list }}"
exists: "{{ item.stat.exists }}"
loop: "{{ existing_certs.results }}"
loop_control:
label: "{{ item.item.domain }}"
- name: Display certificate request plan
ansible.builtin.debug:
msg: |
Will request certificates for:
{% for cmd in certbot_commands %}
- {{ cmd.cert_name }} ({{ cmd.domains | join(', ') }}) - {{ 'EXISTS' if cmd.exists else 'NEW' }}
{% endfor %}
- name: Request certificates from Let's Encrypt via Cloudflare DNS
ansible.builtin.command: >
certbot certonly
--non-interactive
--agree-tos
--email {{ haproxy_certbot_email }}
--dns-cloudflare
--dns-cloudflare-credentials {{ haproxy_certbot_cloudflare_credentials_file }}
--dns-cloudflare-propagation-seconds {{ haproxy_certbot_dns_propagation_seconds }}
--cert-name {{ item.cert_name }}
{% for domain in item.domains %}
--domain {{ domain }}
{% endfor %}
{% if haproxy_certbot_staging %}
--staging
{% endif %}
--deploy-hook {{ haproxy_certbot_post_hook }}
loop: "{{ certbot_commands }}"
loop_control:
label: "{{ item.cert_name }}"
when: not item.exists
register: certbot_result
failed_when:
- certbot_result.rc != 0
- "'Certificate not yet due for renewal' not in certbot_result.stdout"
- "'too many certificates already issued' not in certbot_result.stderr"
- name: Display certificate request results
ansible.builtin.debug:
msg: "Certificate for {{ item.item.cert_name }}: {{ 'SUCCESS' if item.rc == 0 else 'SKIPPED/FAILED' }}"
loop: "{{ certbot_result.results }}"
loop_control:
label: "{{ item.item.cert_name }}"
when: certbot_result.results is defined
- name: Run post-hook to combine certificates for HAProxy
ansible.builtin.command: "{{ haproxy_certbot_post_hook }}"
changed_when: true
when: certbot_result.changed
- name: Setup automatic certificate renewal via cron
ansible.builtin.cron:
name: "Certbot certificate renewal"
minute: "{{ haproxy_certbot_renewal_cron_minute }}"
hour: "{{ haproxy_certbot_renewal_cron_hour }}"
day: "{{ haproxy_certbot_renewal_cron_day }}"
job: "/usr/bin/certbot renew --quiet --deploy-hook {{ haproxy_certbot_post_hook }} 2>&1 | logger -t certbot"
user: root
state: present
- name: Create systemd timer for certificate renewal (alternative to cron)
when: ansible_service_mgr == "systemd"
block:
- name: Create certbot renewal service
ansible.builtin.copy:
content: |
[Unit]
Description=Certbot Renewal
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet --deploy-hook {{ haproxy_certbot_post_hook }}
StandardOutput=journal
StandardError=journal
dest: /etc/systemd/system/certbot-renewal.service
mode: '0644'
- name: Create certbot renewal timer
ansible.builtin.copy:
content: |
[Unit]
Description=Certbot Renewal Timer
[Timer]
OnCalendar=daily
RandomizedDelaySec=1h
Persistent=true
[Install]
WantedBy=timers.target
dest: /etc/systemd/system/certbot-renewal.timer
mode: '0644'
- name: Enable and start certbot renewal timer
ansible.builtin.systemd:
name: certbot-renewal.timer
enabled: true
state: started
daemon_reload: true
- name: Test certificate renewal (dry-run)
ansible.builtin.command: certbot renew --dry-run --dns-cloudflare --dns-cloudflare-credentials {{ haproxy_certbot_cloudflare_credentials_file }}
register: renewal_test
changed_when: false
failed_when: false
- name: Display renewal test results
ansible.builtin.debug:
msg: |
Certificate renewal dry-run: {{ 'SUCCESS ✓' if renewal_test.rc == 0 else 'FAILED ✗' }}
{% if renewal_test.rc == 0 %}
Your certificates will renew automatically.
{% else %}
Please check the output above for errors.
{% endif %}
- name: List all certificates
ansible.builtin.command: certbot certificates
register: cert_list
changed_when: false
- name: Display certificate information
ansible.builtin.debug:
msg: "{{ cert_list.stdout_lines }}"

View File

@@ -1,78 +0,0 @@
---
# ansible/roles/haproxy/tasks/certificates.yml
- name: Check if default certificate exists
ansible.builtin.stat:
path: "{{ haproxy_ssl_default_crt }}"
register: default_cert
- name: Generate self-signed certificate for development
when: not default_cert.stat.exists
block:
- name: Generate private key
openssl_privatekey:
path: "{{ haproxy_ssl_cert_dir }}/default.key"
size: 2048
mode: '0600'
owner: "{{ haproxy_user }}"
group: "{{ haproxy_group }}"
- name: Generate certificate signing request
openssl_csr:
path: "{{ haproxy_ssl_cert_dir }}/default.csr"
privatekey_path: "{{ haproxy_ssl_cert_dir }}/default.key"
common_name: "lightning-lane.local.mk-labs.cloud"
subject_alt_name:
- "DNS:lightning-lane.local.mk-labs.cloud"
- "DNS:*.local.mk-labs.cloud"
owner: "{{ haproxy_user }}"
group: "{{ haproxy_group }}"
- name: Generate self-signed certificate
openssl_certificate:
path: "{{ haproxy_ssl_cert_dir }}/default.crt"
privatekey_path: "{{ haproxy_ssl_cert_dir }}/default.key"
csr_path: "{{ haproxy_ssl_cert_dir }}/default.csr"
provider: selfsigned
selfsigned_not_after: "+3650d"
owner: "{{ haproxy_user }}"
group: "{{ haproxy_group }}"
- name: Combine certificate and key for HAProxy
ansible.builtin.shell: |
cat {{ haproxy_ssl_cert_dir }}/default.crt \
{{ haproxy_ssl_cert_dir }}/default.key \
> {{ haproxy_ssl_default_crt }}
args:
creates: "{{ haproxy_ssl_default_crt }}"
- name: Set permissions on combined certificate
ansible.builtin.file:
path: "{{ haproxy_ssl_default_crt }}"
owner: "{{ haproxy_user }}"
group: "{{ haproxy_group }}"
mode: '0600'
- name: Create certificate directory structure
ansible.builtin.file:
path: "{{ haproxy_ssl_cert_dir }}/{{ item }}"
state: directory
owner: "{{ haproxy_user }}"
group: "{{ haproxy_group }}"
mode: '0755'
loop:
- live
- archive
- name: Deploy additional certificates
when: haproxy_ssl_certificates is defined
block:
- name: Copy certificate files
ansible.builtin.copy:
src: "{{ item.src }}"
dest: "{{ haproxy_ssl_cert_dir }}/live/{{ item.name }}.pem"
owner: "{{ haproxy_user }}"
group: "{{ haproxy_group }}"
mode: '0600'
loop: "{{ haproxy_ssl_certificates }}"
notify: Reload haproxy

View File

@@ -1,94 +0,0 @@
---
# ansible/roles/haproxy/tasks/configure.yml
- name: Fetch services from NetBox
when: haproxy_netbox_integration
block:
- name: Query NetBox for service configurations
ansible.builtin.uri:
url: "{{ haproxy_netbox_url }}/api/ipam/services/"
method: GET
headers:
Authorization: "Token {{ haproxy_netbox_token }}"
Accept: "application/json"
return_content: true
register: netbox_services
changed_when: false
- name: Parse NetBox services
ansible.builtin.set_fact:
haproxy_frontends: "{{ haproxy_frontends | default([]) + netbox_parsed_frontends }}"
haproxy_backends: "{{ haproxy_backends | default([]) + netbox_parsed_backends }}"
vars:
netbox_parsed_frontends: "{{ netbox_services.json.results | map(attribute='custom_fields') | list }}"
netbox_parsed_backends: "{{ netbox_services.json.results | map(attribute='custom_fields') | list }}"
when: netbox_services.json.results is defined
- name: Generate HAProxy main configuration
ansible.builtin.template:
src: haproxy.cfg.j2
dest: "{{ haproxy_config_dir }}/haproxy.cfg"
owner: root
group: root
mode: '0644'
backup: true
validate: 'haproxy -c -f %s'
notify: reload haproxy
- name: Configure rsyslog for HAProxy
when: haproxy_rsyslog_config
block:
- name: Create rsyslog configuration for HAProxy
ansible.builtin.copy:
content: |
# HAProxy logging configuration
$ModLoad imudp
$UDPServerRun 514
# Create separate log files for HAProxy
local0.* {{ haproxy_log_dir }}/haproxy.log
local0.info {{ haproxy_log_dir }}/haproxy-info.log
local0.notice {{ haproxy_log_dir }}/haproxy-notice.log
local0.err {{ haproxy_log_dir }}/haproxy-error.log
# Stop processing HAProxy logs
& stop
dest: /etc/rsyslog.d/49-haproxy.conf
mode: '0644'
notify: restart rsyslog
- name: Configure logrotate for HAProxy
when: haproxy_log_rotation
ansible.builtin.template:
src: logrotate.j2
dest: /etc/logrotate.d/haproxy
mode: '0644'
- name: Create HAProxy systemd override directory
ansible.builtin.file:
path: /etc/systemd/system/haproxy.service.d
state: directory
mode: '0755'
- name: Configure HAProxy systemd service overrides
ansible.builtin.copy:
content: |
[Service]
# Increase file descriptor limits
LimitNOFILE=65536
# Runtime directory
RuntimeDirectory=haproxy
RuntimeDirectoryMode=0755
# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths={{ haproxy_log_dir }}
dest: /etc/systemd/system/haproxy.service.d/override.conf
mode: '0644'
notify:
- Reload systemd
- Restart haproxy

View File

@@ -1,128 +0,0 @@
---
# ansible/roles/haproxy/tasks/install.yml
- name: Install HAProxy repository (Ubuntu)
when: ansible_os_family == "Debian"
block:
- name: Add HAProxy PPA
ansible.builtin.apt_repository:
repo: "ppa:vbernat/haproxy-{{ haproxy_version }}"
state: present
update_cache: true
- name: Install HAProxy
ansible.builtin.apt:
name: haproxy
state: present
update_cache: true
- name: Install HAProxy (Fedora/RHEL)
when: ansible_os_family == "RedHat"
block:
- name: Install HAProxy
ansible.builtin.dnf:
name: haproxy
state: present
- name: Install additional packages
ansible.builtin.package:
name:
- rsyslog
- logrotate
- socat # For HAProxy socket communication
state: present
- name: Create HAProxy system user
ansible.builtin.user:
name: "{{ haproxy_user }}"
group: "{{ haproxy_group }}"
system: true
shell: /usr/sbin/nologin
home: "{{ haproxy_chroot_dir }}"
create_home: true
- name: Create required directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: "{{ haproxy_user }}"
group: "{{ haproxy_group }}"
mode: '0755'
loop:
- "{{ haproxy_config_dir }}"
- "{{ haproxy_ssl_cert_dir }}"
- "{{ haproxy_log_dir }}"
- "{{ haproxy_chroot_dir }}"
- /var/lib/haproxy/dev
- name: Create HAProxy socket directory
ansible.builtin.file:
path: /run/haproxy
state: directory
owner: "{{ haproxy_user }}"
group: "{{ haproxy_group }}"
mode: '0755'
- name: Configure firewall rules
when: haproxy_configure_firewall
block:
- name: Open HAProxy ports (UFW)
when: ansible_os_family == "Debian"
community.general.ufw:
rule: allow
port: "{{ item.split('/')[0] }}"
proto: "{{ item.split('/')[1] }}"
loop: "{{ haproxy_firewall_allowed_ports }}"
- name: Open HAProxy ports (firewalld)
when: ansible_os_family == "RedHat"
ansible.posix.firewalld:
port: "{{ item }}"
permanent: true
state: enabled
immediate: true
loop: "{{ haproxy_firewall_allowed_ports }}"
- name: Install Prometheus HAProxy exporter
when: haproxy_prometheus_exporter
block:
- name: Download HAProxy exporter
ansible.builtin.get_url:
url: "https://github.com/prometheus/haproxy_exporter/releases/download/v0.15.0/haproxy_exporter-0.15.0.linux-amd64.tar.gz"
dest: /tmp/haproxy_exporter.tar.gz
mode: '0644'
- name: Extract HAProxy exporter
ansible.builtin.unarchive:
src: /tmp/haproxy_exporter.tar.gz
dest: /usr/local/bin/
remote_src: true
extra_opts:
- --strip-components=1
- --wildcards
- '*/haproxy_exporter'
- name: Create HAProxy exporter systemd service
ansible.builtin.copy:
content: |
[Unit]
Description=HAProxy Exporter
After=network.target
[Service]
Type=simple
User={{ haproxy_user }}
ExecStart=/usr/local/bin/haproxy_exporter --haproxy.scrape-uri=unix:/run/haproxy/admin.sock
Restart=on-failure
[Install]
WantedBy=multi-user.target
dest: /etc/systemd/system/haproxy-exporter.service
mode: '0644'
- name: Enable and start HAProxy exporter
ansible.builtin.systemd:
name: haproxy-exporter
state: started
enabled: true
daemon_reload: true

View File

@@ -1,40 +0,0 @@
---
# ansible/roles/haproxy/tasks/main.yml
# - name: Include OS-specific variables
# include_vars: "{{ ansible_os_family }}.yml"
# tags: [haproxy, always]
- name: Include installation tasks
ansible.builtin.include_tasks: install.yml
tags: [haproxy, install]
- name: Include Certbot with Cloudflare DNS tasks
ansible.builtin.include_tasks: certbot-cloudflare.yml
when:
- haproxy_certbot_enable
- haproxy_certbot_challenge_method == "dns-cloudflare"
tags: [haproxy, certificates, certbot, cloudflare]
- name: Include fallback self-signed certificate tasks
ansible.builtin.include_tasks: certificates.yml
when:
- not haproxy_certbot_enable
- haproxy_certbot_fallback_selfsigned | default(false)
tags: [haproxy, certificates, selfsigned]
- name: Include configuration tasks
ansible.builtin.include_tasks: configure.yml
tags: [haproxy, configure]
- name: Include validation tasks
ansible.builtin.include_tasks: validate.yml
tags: [haproxy, validate]
- name: Ensure HAProxy is started and enabled
ansible.builtin.systemd:
name: haproxy
state: started
enabled: true
daemon_reload: true
tags: [haproxy, service]

View File

@@ -1,47 +0,0 @@
---
# ansible/roles/haproxy/tasks/validate.yml
- name: Validate HAProxy configuration syntax
command: haproxy -c -f {{ haproxy_config_dir }}/haproxy.cfg
register: haproxy_validation
changed_when: false
failed_when: haproxy_validation.rc != 0
- name: Display validation results
debug:
msg: "{{ haproxy_validation.stdout_lines }}"
when: haproxy_validation.stdout_lines is defined
- name: Check HAProxy service status
systemd:
name: haproxy
state: started
register: haproxy_status
changed_when: false
- name: Verify HAProxy is listening on configured ports
wait_for:
host: "{{ ansible_default_ipv4.address }}"
port: "{{ item }}"
timeout: 10
loop:
- 80
- 443
- "{{ haproxy_stats_port }}"
when: haproxy_status.status.ActiveState == "active"
- name: Test stats interface accessibility
uri:
url: "http://localhost:{{ haproxy_stats_port }}{{ haproxy_stats_uri }}"
user: "{{ haproxy_stats_username }}"
password: "{{ haproxy_stats_password }}"
force_basic_auth: true
status_code: 200
register: stats_test
changed_when: false
failed_when: false
- name: Display stats interface status
debug:
msg: "Stats interface is {{ 'accessible' if stats_test.status == 200 else 'not accessible' }}"

View File

@@ -1,107 +0,0 @@
#!/bin/bash
# {{ ansible_managed }}
# Certbot post-renewal hook for HAProxy
# Combines certificate and private key for HAProxy consumption
set -euo pipefail
# Configuration
LETSENCRYPT_LIVE="/etc/letsencrypt/live"
HAPROXY_CERT_DIR="{{ haproxy_ssl_cert_dir }}/live"
HAPROXY_USER="{{ haproxy_user }}"
HAPROXY_GROUP="{{ haproxy_group }}"
LOG_FILE="{{ haproxy_log_dir }}/certbot-hook.log"
# Ensure HAProxy cert directory exists
mkdir -p "$HAPROXY_CERT_DIR"
# Logging function
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}
log "Starting certificate deployment for HAProxy"
# Track if any certificates were updated
CERTS_UPDATED=false
# Process each certificate in Let's Encrypt live directory
for cert_dir in "$LETSENCRYPT_LIVE"/*; do
if [ ! -d "$cert_dir" ] || [ "$(basename "$cert_dir")" = "README" ]; then
continue
fi
CERT_NAME=$(basename "$cert_dir")
# Check if certificate files exist
if [ ! -f "$cert_dir/fullchain.pem" ] || [ ! -f "$cert_dir/privkey.pem" ]; then
log "WARNING: Missing certificate files for $CERT_NAME, skipping"
continue
fi
log "Processing certificate: $CERT_NAME"
# Create combined PEM file (fullchain + privkey)
# HAProxy requires the certificate chain and private key in a single file
COMBINED_PEM="$HAPROXY_CERT_DIR/${CERT_NAME}.pem"
# Combine certificates
cat "$cert_dir/fullchain.pem" "$cert_dir/privkey.pem" > "$COMBINED_PEM.tmp"
# Check if certificate actually changed
if [ -f "$COMBINED_PEM" ] && cmp -s "$COMBINED_PEM" "$COMBINED_PEM.tmp"; then
log "Certificate $CERT_NAME unchanged, skipping"
rm "$COMBINED_PEM.tmp"
continue
fi
# Move new certificate into place
mv "$COMBINED_PEM.tmp" "$COMBINED_PEM"
# Set proper permissions
chown "$HAPROXY_USER:$HAPROXY_GROUP" "$COMBINED_PEM"
chmod 600 "$COMBINED_PEM"
log "Created/Updated: $COMBINED_PEM"
CERTS_UPDATED=true
done
# Create/update default certificate symlink
# Use the first wildcard cert or the first available cert as default
DEFAULT_CERT="{{ haproxy_ssl_default_crt }}"
WILDCARD_CERT="$HAPROXY_CERT_DIR/local.mk-labs.cloud.pem"
if [ -f "$WILDCARD_CERT" ]; then
ln -sf "$WILDCARD_CERT" "$DEFAULT_CERT"
log "Updated default certificate symlink to wildcard cert"
elif [ -n "$(ls -A "$HAPROXY_CERT_DIR" 2>/dev/null)" ]; then
FIRST_CERT=$(ls "$HAPROXY_CERT_DIR"/*.pem 2>/dev/null | head -n1)
ln -sf "$FIRST_CERT" "$DEFAULT_CERT"
log "Updated default certificate symlink to $FIRST_CERT"
fi
# Only reload HAProxy if certificates were actually updated
if [ "$CERTS_UPDATED" = true ]; then
log "Certificates updated, validating HAProxy configuration"
# Validate HAProxy configuration
if haproxy -c -f {{ haproxy_config_dir }}/haproxy.cfg >/dev/null 2>&1; then
log "HAProxy configuration valid, reloading service"
if systemctl reload haproxy; then
log "HAProxy reloaded successfully"
else
log "ERROR: Failed to reload HAProxy"
exit 1
fi
else
log "ERROR: HAProxy configuration validation failed"
haproxy -c -f {{ haproxy_config_dir }}/haproxy.cfg 2>&1 | tee -a "$LOG_FILE"
exit 1
fi
else
log "No certificates were updated, skipping HAProxy reload"
fi
log "Certificate deployment complete"
exit 0

View File

@@ -1,12 +0,0 @@
# {{ ansible_managed }}
# Cloudflare API credentials for Certbot DNS-01 challenge
# This file contains sensitive information and should be protected
# Cloudflare API Token (RECOMMENDED)
# Create at: https://dash.cloudflare.com/profile/api-tokens
# Required permissions: Zone:DNS:Edit for specific zone
dns_cloudflare_api_token = {{ haproxy_certbot_cloudflare_api_token }}
# Alternative: Global API Key (NOT RECOMMENDED - too permissive)
# dns_cloudflare_email = your-email@example.com
# dns_cloudflare_api_key = your-global-api-key

View File

@@ -1,188 +0,0 @@
{# ansible/roles/haproxy/templates/haproxy.cfg.j2 #}
#---------------------------------------------------------------------
# HAProxy Configuration for {{ inventory_hostname }}
# Generated by Ansible - DO NOT EDIT MANUALLY
# Magic Kingdom Labs - lightning-lane load balancer
#---------------------------------------------------------------------
#---------------------------------------------------------------------
# Global settings
#---------------------------------------------------------------------
global
log 127.0.0.1 {{ haproxy_global_log_facility }} {{ haproxy_global_log_level }}
chroot {{ haproxy_chroot_dir }}
pidfile /var/run/haproxy.pid
maxconn {{ haproxy_global_maxconn }}
user {{ haproxy_user }}
group {{ haproxy_group }}
daemon
nbthread {{ haproxy_global_nbthread }}
# Runtime socket for management
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
stats timeout 30s
# SSL/TLS configuration
ssl-default-bind-ciphers {{ haproxy_ssl_ciphers }}
ssl-default-bind-options {{ haproxy_ssl_options }}
ssl-default-server-ciphers {{ haproxy_ssl_ciphers }}
ssl-default-server-options {{ haproxy_ssl_options }}
# Performance tuning
tune.ssl.default-dh-param 2048
tune.ssl.cachesize 100000
tune.ssl.lifetime 600
#---------------------------------------------------------------------
# Common defaults
#---------------------------------------------------------------------
defaults
mode http
log global
option httplog
option dontlognull
option http-server-close
option redispatch
retries 3
timeout connect {{ haproxy_timeout_connect }}
timeout client {{ haproxy_timeout_client }}
timeout server {{ haproxy_timeout_server }}
timeout http-request 10s
timeout http-keep-alive 10s
timeout check 10s
maxconn 3000
# Error pages
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http
#---------------------------------------------------------------------
# Stats interface
#---------------------------------------------------------------------
{% if haproxy_stats_enable %}
listen stats
bind *:{{ haproxy_stats_port }}
mode http
stats enable
stats uri {{ haproxy_stats_uri }}
stats refresh {{ haproxy_stats_refresh }}
stats realm Magic\ Kingdom\ Labs\ -\ HAProxy\ Statistics
stats auth {{ haproxy_stats_username }}:{{ haproxy_stats_password }}
stats admin if TRUE
{% endif %}
#---------------------------------------------------------------------
# HTTP Redirect Frontend (HTTP -> HTTPS)
#---------------------------------------------------------------------
frontend http_front
bind *:80
mode http
# Redirect all HTTP traffic to HTTPS
redirect scheme https code 301
#---------------------------------------------------------------------
# HTTPS Frontend with SNI
#---------------------------------------------------------------------
frontend https_front
bind *:443 ssl crt {{ haproxy_ssl_cert_dir }}/live crt {{ haproxy_ssl_default_crt }} alpn h2,http/1.1
mode http
# Security headers
http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
http-response set-header X-Frame-Options "SAMEORIGIN"
http-response set-header X-Content-Type-Options "nosniff"
http-response set-header X-XSS-Protection "1; mode=block"
# Logging
option httplog
option forwardfor
{% if haproxy_frontends is defined and haproxy_frontends | length > 0 %}
{% for frontend in haproxy_frontends %}
{% if frontend.acls is defined %}
# ACLs for {{ frontend.name }}
{% for acl in frontend.acls %}
acl {{ acl }}
{% endfor %}
{% endif %}
{% endfor %}
{% for frontend in haproxy_frontends %}
{% if frontend.use_backends is defined %}
# Backend routing for {{ frontend.name }}
{% for use_backend in frontend.use_backends %}
use_backend {{ use_backend }}
{% endfor %}
{% endif %}
{% endfor %}
{% endif %}
# Default backend
default_backend no_match_back
#---------------------------------------------------------------------
# Backends
#---------------------------------------------------------------------
{% for backend in haproxy_backends %}
backend {{ backend.name }}
mode {{ backend.mode | default('http') }}
balance {{ backend.balance | default('roundrobin') }}
{% if backend.options is defined %}
{% for option in backend.options %}
option {{ option }}
{% endfor %}
{% endif %}
{% if backend.http_check is defined %}
option httpchk {{ backend.http_check }}
{% endif %}
{% if backend.servers is defined %}
{% for server in backend.servers %}
server {{ server.name }} {{ server.address }}{% if server.check | default(false) %} check{% endif %}{% if server.backup | default(false) %} backup{% endif %}{% if server.weight is defined %} weight {{ server.weight }}{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
# Default backend for unmatched requests
backend no_match_back
mode http
http-request deny deny_status 404
#---------------------------------------------------------------------
# TCP Services
#---------------------------------------------------------------------
{% for tcp_service in haproxy_tcp_services %}
# {{ tcp_service.name | upper }} Service (TCP)
frontend {{ tcp_service.name }}_front
bind *:{{ tcp_service.frontend_port }}
mode tcp
option tcplog
{% if tcp_service.timeout_client is defined %}
timeout client {{ tcp_service.timeout_client }}
{% endif %}
default_backend {{ tcp_service.name }}_back
backend {{ tcp_service.name }}_back
mode tcp
balance {{ tcp_service.balance | default('roundrobin') }}
{% if tcp_service.timeout_server is defined %}
timeout server {{ tcp_service.timeout_server }}
{% endif %}
{% if tcp_service.timeout_connect is defined %}
timeout connect {{ tcp_service.timeout_connect }}
{% endif %}
{% if tcp_service.servers is defined %}
{% for server in tcp_service.servers %}
server {{ server.name }} {{ server.address }}:{{ tcp_service.backend_port }}{% if server.check | default(true) %} check{% endif %}
{% endfor %}
{% endif %}
{% endfor %}

View File

@@ -1,14 +0,0 @@
{# ansible/roles/haproxy/templates/logrotate.j2 #}
{{ haproxy_log_dir }}/*.log {
daily
rotate 14
missingok
notifempty
compress
delaycompress
sharedscripts
postrotate
/bin/kill -HUP `cat /var/run/rsyslog.pid 2> /dev/null` 2> /dev/null || true
/bin/kill -HUP `cat /var/run/haproxy.pid 2> /dev/null` 2> /dev/null || true
endscript
}

View File

@@ -1,95 +0,0 @@
---
# Role to install and configure a n8n server
- name: 1. Pre-install system updates
become: true
ansible.builtin.apt:
name: '*'
state: present
update_cache: true
- name: 2. Install required packages
become: true
ansible.builtin.package:
name: "{{ item }}"
state: present
loop:
- curl
- wget
- gnupg
- lsb-release
- ca-certificates
- software-properties-common
- name: 3. Enable UFW
become: true
community.general.ufw:
state: enabled
- name: 4. Open firewall ports for n8n
become: true
community.general.ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop:
- 22
- 5678
- name: 5. Download node installer
# become: true
ansible.builtin.get_url:
url: 'https://deb.nodesource.com/setup_lts.x'
dest: '/tmp/setup_lts.x'
mode: '0755'
- name: 6. Run node installer
become: true
ansible.builtin.shell:
cmd: '/tmp/setup_lts.x'
- name: 7. Install Node.js and npm
become: true
ansible.builtin.package:
name: nodejs
state: present
- name: 8. Install npm@latest
become: true
ansible.builtin.shell:
cmd: 'sudo npm install -g npm@latest'
- name: 9. Install "n8n" node.js package globally.
become: true
community.general.npm:
name: n8n
global: true
- name: 10. Copy n8n.service to /etc/systemd/system
become: true
ansible.builtin.template:
dest: /etc/systemd/system/n8n.service
owner: root
group: root
mode: '0644'
src: n8n.service.j2
- name: 11. Create n8n user
become: true
ansible.builtin.user:
name: n8n
state: present
shell: /bin/bash
home: /home/n8n
createhome: true
- name: 12. Reload systemd to re-read configs
ansible.builtin.systemd_service:
daemon_reload: true
- name: 13. Start n8n service
become: true
ansible.builtin.systemd:
name: n8n.service
state: restarted
enabled: true

View File

@@ -1,22 +0,0 @@
[Unit]
Description=n8n - Workflow Automation Tool
After=network.target
[Service]
Type=simple
User=n8n
Group=n8n
ExecStart=/usr/local/bin/n8n
WorkingDirectory=/home/n8n
Environment=NODE_ENV=production
Environment=N8N_BASIC_AUTH_ACTIVE=true
Environment=N8N_BASIC_AUTH_USER=admin
#Environment=N8N_BASIC_AUTH_PASSWORD=your_secure_password
Environment=N8N_HOST=0.0.0.0
Environment=N8N_PORT=5678
Environment=N8N_PROTOCOL=http
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target

View File

@@ -1,34 +0,0 @@
---
# Role to disable swap
- name: 1. Check if swap is enabled
ansible.builtin.shell: swapon --show=NAME --noheadings
register: swap_status
changed_when: false
- name: 2. Disable swap
become: true
when: swap_status.stdout != ''
block:
- name: 2.1 Disable swap
ansible.builtin.shell: swapoff -a
- name: 2.2 Persist swap off by commenting out swap in fstab
ansible.builtin.replace:
path: /etc/fstab
regexp: '^([^#].*?\sswap\s+sw\s+.*)$'
replace: '# \1'
backup: true
- name: 2.3 Remove swap file
become: true
ansible.builtin.file:
path: "{{ swap_status.stdout }}"
state: absent
- name: 2.4 Remove zram-generator-defaults
become: true
when: ansible_os_family == "RedHat"
ansible.builtin.file:
path: /etc/systemd/zram-generator.conf
state: absent

View File

@@ -1,46 +0,0 @@
---
# Role to install and configure an Chrony NTP server
- name: 1. Install Chrony NTP for time synchronization
become: true
ansible.builtin.dnf:
name: chrony
state: present
- name: 2.1 Remove DHCP NTP servers from config
become: true
ansible.builtin.lineinfile:
path: /etc/chrony.conf
line: "server unifi.int.mk-labs.cloud iburst"
state: absent
- name: 2.2 Ensure specific NTP servers are present
become: true
ansible.builtin.lineinfile:
path: /etc/chrony.conf
line: "server {{ item }} iburst"
state: present
loop: "{{ ntp_servers }}"
- name: 3. Ensure network allowed are present
become: true
ansible.builtin.lineinfile:
path: /etc/chrony.conf
line: "allow {{ item }}"
state: present
loop: "{{ allowed_networks }}"
- name: 4.Restart ntpd service
become: true
ansible.builtin.systemd:
name: chronyd
state: restarted
enabled: true
- name: 5. Allow NTP traffic
become: true
ansible.posix.firewalld:
service: ntp
permanent: true
immediate: true
state: enabled

View File

@@ -1,11 +0,0 @@
---
prometheus_version: v2.40.1
grafana_version: "9.2.5"
alertmanager_version: v0.24.0
#alertmanager_smtp_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
64306663363562356132323065396635636630373031303739323666373262663961393132316333
6135653763363566303331313639633030623530646239310a353236343035643132646230333466
36336439376131333630346563323833313164353265313264643232373465633561663331396133
3163303166373166390a396131303239356139653063616437363933333130393563646338663933
3966

View File

@@ -1,8 +0,0 @@
apiVersion: 1
providers:
- name: Pre-loaded local dashboards
type: file
options:
foldersFromFilesStructure: true
path: /var/lib/grafana/dashboards

View File

@@ -1,814 +0,0 @@
{
"__inputs": [
{
"name": "DS_PROMETHEUS",
"label": "prometheus",
"description": "Prometheus as the datasource is obligatory",
"type": "datasource",
"pluginId": "prometheus",
"pluginName": "Prometheus"
}
],
"__requires": [
{
"type": "grafana",
"id": "grafana",
"name": "Grafana",
"version": "7.4.5"
},
{
"type": "panel",
"id": "graph",
"name": "Graph",
"version": ""
},
{
"type": "datasource",
"id": "prometheus",
"name": "Prometheus",
"version": "1.0.0"
},
{
"type": "panel",
"id": "table",
"name": "Table",
"version": ""
}
],
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"gnetId": 14282,
"graphTooltip": 0,
"id": null,
"iteration": 1617715580880,
"links": [],
"panels": [
{
"collapsed": false,
"datasource": "Prometheus",
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 0
},
"id": 8,
"panels": [],
"title": "CPU",
"type": "row"
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "Prometheus",
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 7,
"w": 24,
"x": 0,
"y": 1
},
"hiddenSeries": false,
"id": 15,
"legend": {
"alignAsTable": true,
"avg": true,
"current": false,
"max": true,
"min": false,
"rightSide": true,
"show": true,
"total": false,
"values": true
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null as zero",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "7.4.5",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": true,
"steppedLine": false,
"targets": [
{
"expr": "sum(rate(container_cpu_usage_seconds_total{instance=~\"$host\",name=~\"$container\",name=~\".+\"}[5m])) by (name) *100",
"hide": false,
"interval": "",
"legendFormat": "{{name}}",
"refId": "A"
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "CPU Usage",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"$$hashKey": "object:606",
"format": "percent",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"$$hashKey": "object:607",
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"collapsed": false,
"datasource": "Prometheus",
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 8
},
"id": 11,
"panels": [],
"title": "Memory",
"type": "row"
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "Prometheus",
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 9
},
"hiddenSeries": false,
"id": 9,
"legend": {
"alignAsTable": true,
"avg": true,
"current": false,
"max": true,
"min": false,
"rightSide": true,
"show": true,
"total": false,
"values": true
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null as zero",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "7.4.5",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": true,
"steppedLine": false,
"targets": [
{
"expr": "sum(container_memory_rss{instance=~\"$host\",name=~\"$container\",name=~\".+\"}) by (name)",
"hide": false,
"interval": "",
"legendFormat": "{{name}}",
"refId": "A"
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Memory Usage",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"$$hashKey": "object:606",
"format": "bytes",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"$$hashKey": "object:607",
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "Prometheus",
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 9
},
"hiddenSeries": false,
"id": 14,
"legend": {
"alignAsTable": true,
"avg": true,
"current": false,
"max": true,
"min": false,
"rightSide": true,
"show": true,
"total": false,
"values": true
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null as zero",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "7.4.5",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": true,
"steppedLine": false,
"targets": [
{
"expr": "sum(container_memory_cache{instance=~\"$host\",name=~\"$container\",name=~\".+\"}) by (name)",
"hide": false,
"interval": "",
"legendFormat": "{{name}}",
"refId": "A"
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Memory Cached",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"$$hashKey": "object:606",
"format": "bytes",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"$$hashKey": "object:607",
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"collapsed": false,
"datasource": "Prometheus",
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 17
},
"id": 2,
"panels": [],
"title": "Network",
"type": "row"
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "Prometheus",
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 18
},
"hiddenSeries": false,
"id": 4,
"legend": {
"alignAsTable": true,
"avg": true,
"current": false,
"hideEmpty": false,
"hideZero": false,
"max": true,
"min": false,
"rightSide": true,
"show": true,
"sideWidth": null,
"total": false,
"values": true
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "7.4.5",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "sum(rate(container_network_receive_bytes_total{instance=~\"$host\",name=~\"$container\",name=~\".+\"}[5m])) by (name)",
"hide": false,
"interval": "",
"legendFormat": "{{name}}",
"refId": "A"
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Received Network Traffic",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"$$hashKey": "object:674",
"format": "Bps",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"$$hashKey": "object:675",
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "Prometheus",
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 18
},
"hiddenSeries": false,
"id": 6,
"legend": {
"alignAsTable": true,
"avg": true,
"current": false,
"max": true,
"min": false,
"rightSide": true,
"show": true,
"total": false,
"values": true
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "7.4.5",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "sum(rate(container_network_transmit_bytes_total{instance=~\"$host\",name=~\"$container\",name=~\".+\"}[5m])) by (name)",
"interval": "",
"legendFormat": "{{name}}",
"refId": "A"
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Sent Network Traffic",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"$$hashKey": "object:832",
"format": "Bps",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"$$hashKey": "object:833",
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"collapsed": false,
"datasource": "Prometheus",
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 26
},
"id": 19,
"panels": [],
"title": "Misc",
"type": "row"
},
{
"datasource": "Prometheus",
"fieldConfig": {
"defaults": {
"custom": {
"align": null,
"filterable": false
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "id"
},
"properties": [
{
"id": "custom.width",
"value": 260
}
]
},
{
"matcher": {
"id": "byName",
"options": "Running"
},
"properties": [
{
"id": "unit",
"value": "d"
},
{
"id": "decimals",
"value": 1
},
{
"id": "custom.displayMode",
"value": "color-text"
},
{
"id": "color",
"value": {
"fixedColor": "dark-green",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 27
},
"id": 17,
"options": {
"showHeader": true,
"sortBy": []
},
"pluginVersion": "7.4.5",
"targets": [
{
"expr": "(time() - container_start_time_seconds{instance=~\"$host\",name=~\"$container\",name=~\".+\"})/86400",
"format": "table",
"instant": true,
"interval": "",
"legendFormat": "{{name}}",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Containers Info",
"transformations": [
{
"id": "filterFieldsByName",
"options": {
"include": {
"names": [
"container_label_com_docker_compose_project",
"container_label_com_docker_compose_project_working_dir",
"image",
"instance",
"name",
"Value",
"container_label_com_docker_compose_service"
]
}
}
},
{
"id": "organize",
"options": {
"excludeByName": {},
"indexByName": {},
"renameByName": {
"Value": "Running",
"container_label_com_docker_compose_project": "Label",
"container_label_com_docker_compose_project_working_dir": "Working dir",
"container_label_com_docker_compose_service": "Service",
"image": "Registry Image",
"instance": "Instance",
"name": "Name"
}
}
}
],
"type": "table"
}
],
"schemaVersion": 27,
"style": "dark",
"tags": ["cadvisor", "docker"],
"templating": {
"list": [
{
"allValue": ".*",
"current": {},
"datasource": "Prometheus",
"definition": "label_values({__name__=~\"container.*\"},instance)",
"description": null,
"error": null,
"hide": 0,
"includeAll": true,
"label": "Host",
"multi": false,
"name": "host",
"options": [],
"query": {
"query": "label_values({__name__=~\"container.*\"},instance)",
"refId": "Prometheus-host-Variable-Query"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 5,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"allValue": ".*",
"current": {},
"datasource": "Prometheus",
"definition": "label_values({__name__=~\"container.*\", instance=~\"$host\"},name)",
"description": null,
"error": null,
"hide": 0,
"includeAll": true,
"label": "Container",
"multi": false,
"name": "container",
"options": [],
"query": {
"query": "label_values({__name__=~\"container.*\", instance=~\"$host\"},name)",
"refId": "Prometheus-container-Variable-Query"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
}
]
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "Cadvisor exporter",
"uid": "pMEd7m0Mz",
"version": 1,
"description": "Simple exporter for cadvisor only"
}

View File

@@ -1,7 +0,0 @@
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://192.168.0.1:9090

View File

@@ -1,11 +0,0 @@
groups:
- name: AllInstances
rules:
- alert: InstanceDown
expr: up == 0
for: 1m
annotations:
title: 'Instance {{ $labels.instance }} down'
description: '{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 1 minute.'
labels:
severity: 'critical'

View File

@@ -1,28 +0,0 @@
global:
scrape_interval: 15s
scrape_configs:
- job_name: prometheus
scrape_interval: 30s
static_configs:
- targets: ["localhost:9090"]
- job_name: node-exporter
scrape_interval: 30s
static_configs:
- targets:
["192.168.0.1:9100", "192.168.0.10:9100", "192.168.0.11:9100"]
- job_name: cadvisor
scrape_interval: 30s
static_configs:
- targets: ["192.168.0.1:9101", "192.168.0.11:9101"]
rule_files:
- prometheus_alerts_rules.yml
alerting:
alertmanagers:
- static_configs:
- targets:
- 192.168.0.1:9093

View File

@@ -1,85 +0,0 @@
- name: Create Folder /srv/prometheus if not exist
file:
path: /srv/prometheus
mode: 0755
state: directory
- name: Create Folder /srv/grafana if not exist
file:
path: /srv/grafana
mode: 0755
state: directory
- name: Create Folder /srv/alertmanager if not exist
file:
path: /srv/alertmanager
mode: 0755
state: directory
- name: Create prometheus configuration file
copy:
dest: /srv/prometheus/prometheus.yml
src: prometheus_main.yml
mode: 0644
- name: Create prometheus alert configuration file
copy:
dest: /srv/prometheus/prometheus_alerts_rules.yml
src: prometheus_alerts_rules.yml
mode: 0644
- name: Create grafana configuration files
copy:
dest: /srv/
src: grafana
mode: 0644
- name: Create alertmanager configuration file
template:
dest: /srv/alertmanager/alertmanager.yml
src: alertmanager/alertmanager.j2
mode: 0644
- name: Create Prometheus container
docker_container:
name: prometheus
restart_policy: always
image: prom/prometheus:
volumes:
- /srv/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
- /srv/prometheus/prometheus_alerts_rules.yml:/etc/prometheus/prometheus_alerts_rules.yml
- prometheus_main_data:/prometheus
command: >
--config.file=/etc/prometheus/prometheus.yml
--storage.tsdb.path=/prometheus
--web.console.libraries=/etc/prometheus/console_libraries
--web.console.templates=/etc/prometheus/consoles
--web.enable-lifecycle
published_ports: "9090:9090"
- name: Create Grafana container
docker_container:
name: grafana
restart_policy: always
image: grafana/grafana:
volumes:
- grafana-data:/var/lib/grafana
- /srv/grafana/provisioning:/etc/grafana/provisioning
- /srv/grafana/dashboards:/var/lib/grafana/dashboards
env:
GF_AUTH_ANONYMOUS_ENABLED: "true"
GF_AUTH_ANONYMOUS_ORG_ROLE: "Admin"
published_ports: "3000:3000"
- name: Create Alertmanager container
docker_container:
name: alertmanager
restart_policy: always
image: prom/alertmanager:
volumes:
- alertmanager-data:/data
- /srv/alertmanager:/config
command: >
--config.file=/config/alertmanager.yml
--log.level=debug
published_ports: "9093:9093"

View File

@@ -1,13 +0,0 @@
route:
receiver: "mail"
repeat_interval: 4h
group_by: [ alertname ]
receivers:
- name: "mail"
email_configs:
- smarthost: "outlook.office365.com:587"
auth_username: "test@padok.fr"
auth_password: "{{ alertmanager_smtp_password }}"
from: "test@padok.fr"
to: "test@padok.fr"

View File

@@ -1,3 +0,0 @@
---
node_exporter_version: v1.4.0
cadvisor_version: v0.46.0

View File

@@ -1,29 +0,0 @@
---
- name: Create NodeExporter
docker_container:
name: node-exporter
restart_policy: always
image: prom/node-exporter:{{ node_exporter_version }}
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command: >
--path.procfs=/host/proc
--path.rootfs=/rootfs
--path.sysfs=/host/sys
--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)
published_ports: "9100:9100"
- name: Create cAdvisor
docker_container:
name: cadvisor
restart_policy: always
image: gcr.io/cadvisor/cadvisor:{{ cadvisor_version }}
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
published_ports: "9101:8080"

View File

@@ -1,38 +0,0 @@
Role Name
=========
A brief description of the role goes here.
Requirements
------------
Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required.
Role Variables
--------------
A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well.
Dependencies
------------
A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles.
Example Playbook
----------------
Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too:
- hosts: servers
roles:
- { role: username.rolename, x: 42 }
License
-------
BSD
Author Information
------------------
An optional section for the role authors to include contact information, or a website (HTML is not allowed).

View File

@@ -1,3 +0,0 @@
#SPDX-License-Identifier: MIT-0
---
# defaults file for time-sync

View File

@@ -1,3 +0,0 @@
#SPDX-License-Identifier: MIT-0
---
# handlers file for time-sync

View File

@@ -1,35 +0,0 @@
#SPDX-License-Identifier: MIT-0
galaxy_info:
author: your name
description: your role description
company: your company (optional)
# If the issue tracker for your role is not on github, uncomment the
# next line and provide a value
# issue_tracker_url: http://example.com/issue/tracker
# Choose a valid license ID from https://spdx.org - some suggested licenses:
# - BSD-3-Clause (default)
# - MIT
# - GPL-2.0-or-later
# - GPL-3.0-only
# - Apache-2.0
# - CC-BY-4.0
license: license (GPL-2.0-or-later, MIT, etc)
min_ansible_version: 2.1
# If this a Container Enabled role, provide the minimum Ansible Container version.
# min_ansible_container_version:
galaxy_tags: []
# List tags for your role here, one per line. A tag is a keyword that describes
# and categorizes the role. Users find roles by searching for tags. Be sure to
# remove the '[]' above, if you add tags to this list.
#
# NOTE: A tag is limited to a single word comprised of alphanumeric characters.
# Maximum 20 tags per role.
dependencies: []
# List your role dependencies here, one per line. Be sure to remove the '[]' above,
# if you add dependencies to this list.

View File

@@ -1,41 +0,0 @@
---
# tasks file for time-sync
- name: Manage Time Synchronization
become: true
block:
- name: Remove the ntp package
ansible.builtin.package:
name: ntp
state: absent
- name: Install the chrony package
ansible.builtin.package:
name: chrony
state: present
- name: Remove pool entries from Chrony config file
ansible.builtin.lineinfile:
path: /etc/chrony/chrony.conf
regexp: '^pool\s*'
state: absent
- name: Add local NTP servers to Chrony config file
ansible.builtin.lineinfile:
path: /etc/chrony/chrony.conf
line: "server 10.1.71.21 iburst"
state: present
- name: Start and enable chronyd
ansible.builtin.systemd:
name: chronyd
state: started
enabled: true
- name: Force time synchronization
become: true
ansible.builtin.command: chronyc makestep
register: chronyc_status
- name: Display chronyc status
ansible.builtin.debug:
var: chronyc_status.stdout

View File

@@ -1,3 +0,0 @@
#SPDX-License-Identifier: MIT-0
localhost

View File

@@ -1,6 +0,0 @@
#SPDX-License-Identifier: MIT-0
---
- hosts: localhost
remote_user: root
roles:
- time-sync

View File

@@ -1,3 +0,0 @@
#SPDX-License-Identifier: MIT-0
---
# vars file for time-sync