D9.4 Grafana με Ansible 📈

Grafana Provisioning — Dashboards as code

Το Grafana υποστηρίζει provisioning — datasources και dashboards ορίζονται ως αρχεία (YAML/JSON) αντί για manual UI configuration.


Ρύθμιση provisioning

tasks:

  # ── Provisioning directories ───────────────
  - name: Grafana provisioning directories
    ansible.builtin.file:
      path:  "{{ item }}"
      state: directory
      owner: "472"    # ← grafana UID
      group: "472"
      mode:  '0755'
    loop:
      - /etc/grafana/provisioning/datasources
      - /etc/grafana/provisioning/dashboards
      - /var/lib/grafana/dashboards

  # ── Datasource: Prometheus ─────────────────
  - name: Prometheus 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
            access:  proxy
            isDefault: true
            editable: false    # ← ΔΕΝ επιτρέπεται αλλαγή από UI
    notify: Restart Grafana

  # ── Dashboard provisioning ─────────────────
  - name: Dashboard provisioning config
    ansible.builtin.copy:
      dest:  /etc/grafana/provisioning/dashboards/default.yml
      owner: "472"
      mode:  '0644'
      content: |
        apiVersion: 1
        providers:
          - name:    Default
            folder:  ''
            type:    file
            options:
              path: /var/lib/grafana/dashboards
    notify: Restart Grafana

  # ── Dashboards JSON files ──────────────────
  - name: Deploy dashboards
    ansible.builtin.copy:
      src:   "files/grafana/dashboards/{{ item }}"
      dest:  /var/lib/grafana/dashboards/
      owner: "472"
      mode:  '0644'
    loop:
      - node-exporter.json      # ← Node Exporter Full dashboard
      - docker-cadvisor.json    # ← Docker container dashboard
      - alerts-overview.json    # ← Alert status dashboard
    notify: Restart Grafana

Grafana container deployment

tasks:

  - name: Grafana data volume
    community.docker.docker_volume:
      name:  grafana_data
      state: present
      labels:
        managed-by: ansible

  - name: "📈 Grafana container"
    community.docker.docker_container:
      name:            grafana
      image:           "grafana/grafana:10.3.0"
      state:           started
      restart_policy:  unless-stopped
      user:            "472:472"      # ← grafana user
      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"    # ← localhost only
      env:
        GF_SECURITY_ADMIN_USER:     admin
        GF_SECURITY_ADMIN_PASSWORD: "{{ vault_grafana_pass }}"
        GF_SECURITY_DISABLE_GRAVATAR: "true"
        GF_ANALYTICS_REPORTING_ENABLED: "false"
        GF_ANALYTICS_CHECK_FOR_UPDATES: "false"
        GF_USERS_ALLOW_SIGN_UP: "false"
        GF_AUTH_ANONYMOUS_ENABLED: "false"
        GF_SERVER_ROOT_URL:   "https://monitoring.{{ app_domain }}"
        GF_SMTP_ENABLED:      "true"
        GF_SMTP_HOST:         "{{ smtp_host }}:587"
        GF_SMTP_USER:         "{{ smtp_user }}"
        GF_SMTP_PASSWORD:     "{{ vault_smtp_pass }}"
        GF_SMTP_FROM_ADDRESS: "grafana@{{ app_domain }}"
      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
      labels:
        monitoring: grafana
        managed-by: ansible
      no_log: true

handlers:

  - name: Restart Grafana
    community.docker.docker_container:
      name:  grafana
      state: started
      restart: true

Grafana API — Programmatic management

tasks:

  # ── Αναμονή για Grafana ────────────────────
  - name: Wait for Grafana
    ansible.builtin.uri:
      url:         "http://localhost:3000/api/health"
      status_code: 200
    retries: 12
    delay:   5
    until:   not grafana_health.failed
    register: grafana_health

  # ── Δημιουργία API key ─────────────────────
  - name: Create API key
    ansible.builtin.uri:
      url:     "http://localhost:3000/api/auth/keys"
      method:  POST
      user:    admin
      password: "{{ vault_grafana_pass }}"
      force_basic_auth: true
      body_format: json
      body:
        name: ansible-provisioning
        role: Admin
      status_code: [200, 409]    # 409 = already exists
      return_content: true
    register: api_key
    no_log:   true

  # ── Δημιουργία organization ───────────────
  - name: Set org name
    ansible.builtin.uri:
      url:    "http://localhost:3000/api/org"
      method: PUT
      headers:
        Authorization: "Bearer {{ api_key.json.key | default('') }}"
      body_format: json
      body:
        name: "{{ company_name | default('My Company') }}"
    when: api_key.json.key is defined
    no_log: true

Σύνοψη D9.4

Grafana
│
├── Provisioning (as code):
│   ├── datasources/ → Prometheus (auto-configured)
│   └── dashboards/  → JSON files (auto-loaded)
│
├── Container:
│   ├── user: 472 (grafana non-root)
│   ├── read_only: true
│   └── port: 127.0.0.1:3000
│
├── Security settings (env vars):
│   ├── allow_sign_up: false
│   ├── anonymous: disabled
│   └── SMTP για notifications
│
└── API: REST API για programmatic management