---
# monitoring-stack.yml
# Full monitoring stack: Prometheus + Grafana + Alertmanager
- name: "Deploy Monitoring Exporters (All Hosts)"
hosts: all_managed
become: true
gather_facts: false
vars:
node_exporter_version: "v1.7.0"
cadvisor_version: "v0.49.0"
tasks:
- name: "📡 Node Exporter"
community.docker.docker_container:
name: node_exporter
image: "prom/node-exporter:{{ node_exporter_version }}"
state: started
restart_policy: unless-stopped
network_mode: host
pid_mode: host
user: "65534:65534"
read_only: true
security_opts: ["no-new-privileges:true"]
cap_drop: [ALL]
volumes:
- "/:/host:ro,rslave"
command:
- "--path.rootfs=/host"
- "--web.listen-address=:9100"
- name: "📊 cAdvisor"
community.docker.docker_container:
name: cadvisor
image: "gcr.io/cadvisor/cadvisor:{{ cadvisor_version }}"
state: started
restart_policy: unless-stopped
privileged: true
ports:
- "127.0.0.1:8080:8080"
volumes:
- "/:/rootfs:ro"
- "/var/run:/var/run:ro"
- "/sys:/sys:ro"
- "/var/lib/docker/:/var/lib/docker:ro"
# ─────────────────────────────────────────────────────
- name: "Deploy Monitoring Server"
hosts: monitoring # ← dedicated monitoring host
become: true
gather_facts: true
vars:
prometheus_version: "v2.49.0"
grafana_version: "10.3.0"
alertmanager_version: "v0.26.0"
monitoring_domain: "monitoring.{{ app_domain }}"
tasks:
# ════════════════════════════════════════
# PHASE 1: PREREQUISITES
# ════════════════════════════════════════
- name: "════ PHASE 1: Setup ════"
ansible.builtin.debug:
msg: "Monitoring prerequisites..."
- name: "📁 Directories"
ansible.builtin.file:
path: "{{ item.path }}"
state: directory
owner: "{{ item.owner | default('root') }}"
group: "{{ item.owner | default('root') }}"
mode: "{{ item.mode | default('0755') }}"
loop:
- { path: /etc/prometheus, owner: "65534" }
- { path: /etc/prometheus/rules, owner: "65534" }
- { path: /etc/alertmanager, owner: "65534", mode: "0700" }
- { path: /etc/grafana/provisioning/datasources, owner: "472" }
- { path: /etc/grafana/provisioning/dashboards, owner: "472" }
- { path: /var/lib/grafana/dashboards, owner: "472" }
- name: "🌐 Monitoring network"
community.docker.docker_network:
name: monitoring_net
state: present
labels:
managed-by: ansible
purpose: monitoring
# ════════════════════════════════════════
# PHASE 2: PROMETHEUS
# ════════════════════════════════════════
- name: "════ PHASE 2: Prometheus ════"
ansible.builtin.debug:
msg: "Deploying Prometheus..."
- name: "⚙️ Prometheus config"
ansible.builtin.template:
src: templates/prometheus.yml.j2
dest: /etc/prometheus/prometheus.yml
owner: "65534"
mode: '0644'
notify: Reload Prometheus
- name: "⚙️ Alert rules"
ansible.builtin.copy:
src: files/prometheus/rules/
dest: /etc/prometheus/rules/
owner: "65534"
mode: '0644'
notify: Reload Prometheus
- name: "💾 Prometheus volume"
community.docker.docker_volume:
name: prometheus_data
state: present
- name: "🔥 Prometheus container"
community.docker.docker_container:
name: prometheus
image: "prom/prometheus:{{ prometheus_version }}"
state: started
restart_policy: unless-stopped
user: "65534:65534"
read_only: true
memory: "1g"
cap_drop: [ALL]
security_opts: ["no-new-privileges:true"]
tmpfs:
/tmp: "size=64m"
ports:
- "127.0.0.1:9090:9090"
volumes:
- "/etc/prometheus:/etc/prometheus:ro"
- "prometheus_data:/prometheus:rw"
command:
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.retention.time=30d"
- "--web.enable-lifecycle"
networks:
- name: monitoring_net
# ════════════════════════════════════════
# PHASE 3: ALERTMANAGER
# ════════════════════════════════════════
- name: "════ PHASE 3: Alertmanager ════"
ansible.builtin.debug:
msg: "Deploying Alertmanager..."
- name: "⚙️ Alertmanager config"
ansible.builtin.template:
src: templates/alertmanager.yml.j2
dest: /etc/alertmanager/alertmanager.yml
owner: "65534"
mode: '0600'
notify: Reload Alertmanager
no_log: true
- name: "💾 Alertmanager volume"
community.docker.docker_volume:
name: alertmanager_data
state: present
- name: "🔔 Alertmanager container"
community.docker.docker_container:
name: alertmanager
image: "prom/alertmanager:{{ alertmanager_version }}"
state: started
restart_policy: unless-stopped
user: "65534:65534"
read_only: true
memory: "128m"
cap_drop: [ALL]
security_opts: ["no-new-privileges:true"]
tmpfs:
/tmp: "size=32m"
ports:
- "127.0.0.1:9093:9093"
volumes:
- "/etc/alertmanager:/etc/alertmanager:ro"
- "alertmanager_data:/alertmanager:rw"
command:
- "--config.file=/etc/alertmanager/alertmanager.yml"
- "--storage.path=/alertmanager"
networks:
- name: monitoring_net
# ════════════════════════════════════════
# PHASE 4: GRAFANA
# ════════════════════════════════════════
- name: "════ PHASE 4: Grafana ════"
ansible.builtin.debug:
msg: "Deploying Grafana..."
- name: "⚙️ Grafana datasource"
ansible.builtin.copy:
dest: /etc/grafana/provisioning/datasources/prometheus.yml
owner: "472"
mode: '0644'
content: |
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
url: http://prometheus:9090
isDefault: true
editable: false
notify: Restart Grafana
- name: "⚙️ Grafana dashboard provisioning"
ansible.builtin.copy:
dest: /etc/grafana/provisioning/dashboards/default.yml
owner: "472"
mode: '0644'
content: |
apiVersion: 1
providers:
- name: Default
type: file
options:
path: /var/lib/grafana/dashboards
notify: Restart Grafana
- name: "📊 Deploy dashboards"
ansible.builtin.copy:
src: "files/grafana/dashboards/{{ item }}"
dest: /var/lib/grafana/dashboards/
owner: "472"
mode: '0644'
loop:
- node-exporter.json
- docker-cadvisor.json
notify: Restart Grafana
- name: "💾 Grafana volume"
community.docker.docker_volume:
name: grafana_data
state: present
- name: "📈 Grafana container"
community.docker.docker_container:
name: grafana
image: "grafana/grafana:{{ grafana_version }}"
state: started
restart_policy: unless-stopped
user: "472:472"
read_only: true
memory: "512m"
cap_drop: [ALL]
security_opts: ["no-new-privileges:true"]
tmpfs:
/tmp: "size=64m"
ports:
- "127.0.0.1:3000:3000"
env:
GF_SECURITY_ADMIN_PASSWORD: "{{ vault_grafana_pass }}"
GF_USERS_ALLOW_SIGN_UP: "false"
GF_AUTH_ANONYMOUS_ENABLED: "false"
GF_SERVER_ROOT_URL: "https://{{ monitoring_domain }}"
GF_ANALYTICS_REPORTING_ENABLED: "false"
volumes:
- "grafana_data:/var/lib/grafana:rw"
- "/etc/grafana/provisioning:/etc/grafana/provisioning:ro"
- "/var/lib/grafana/dashboards:/var/lib/grafana/dashboards:ro"
networks:
- name: monitoring_net
no_log: true
# ════════════════════════════════════════
# PHASE 5: VERIFICATION
# ════════════════════════════════════════
- name: "════ PHASE 5: Verification ════"
ansible.builtin.debug:
msg: "Verifying monitoring stack..."
- name: "🔍 Prometheus health"
ansible.builtin.uri:
url: "http://localhost:9090/-/ready"
status_code: 200
retries: 6
delay: 5
until: not prom_health.failed
register: prom_health
- name: "🔍 Grafana health"
ansible.builtin.uri:
url: "http://localhost:3000/api/health"
status_code: 200
retries: 12
delay: 5
until: not grafana_health.failed
register: grafana_health
- name: "📊 Monitoring Report"
ansible.builtin.debug:
msg:
- "╔══════════════════════════════════════════════╗"
- "║ 📊 MONITORING STACK DEPLOYED! ║"
- "╠══════════════════════════════════════════════╣"
- "║ Prometheus : http://localhost:9090 ║"
- "║ Alertmanager: http://localhost:9093 ║"
- "║ Grafana: http://localhost:3000 ║"
- "╠══════════════════════════════════════════════╣"
- "║ Scraping hosts: {{ groups['all_managed'] | join(', ') }}"
- "╚══════════════════════════════════════════════╝"
handlers:
- name: Reload Prometheus
ansible.builtin.uri:
url: "http://localhost:9090/-/reload"
method: POST
ignore_errors: true
- name: Reload Alertmanager
ansible.builtin.uri:
url: "http://localhost:9093/-/reload"
method: POST
ignore_errors: true
- name: Restart Grafana
community.docker.docker_container:
name: grafana
state: started
restart: true
Full Monitoring Stack:
│
├── All managed hosts:
│ ├── node_exporter :9100 → host metrics
│ └── cAdvisor :8080 → container metrics
│
├── Monitoring server:
│ ├── Prometheus :9090 → scrape + store + alert
│ ├── Alertmanager :9093 → routing + notifications
│ └── Grafana :3000 → dashboards
│
├── Security (όλα):
│ ├── non-root, read_only, cap_drop: ALL
│ └── localhost binding μόνο
│
└── Config as Code:
├── Prometheus: Jinja2 template (dynamic hosts)
├── Alertmanager: Vault secrets
└── Grafana: JSON dashboards provisioning