D9.6 Πρακτικό παράδειγμα — Full Monitoring Stack 🚀

Αρχιτεκτονική

---
# 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
# Deploy exporters σε ΟΛΟΥΣ τους managed hosts
ansible-playbook playbooks/monitoring-stack.yml \
    --tags exporters \
    --ask-vault-pass

# Deploy full monitoring server
ansible-playbook playbooks/monitoring-stack.yml \
    --limit monitoring \
    --ask-vault-pass

# Update config μόνο (reload χωρίς restart)
ansible-playbook playbooks/monitoring-stack.yml \
    --limit monitoring \
    --tags config \
    --ask-vault-pass

Σύνοψη D9.6

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