После внедрения Loki на рабочем проекте – решил добавить его и себе.
А заодно – добавить node_exporter
и alertmanager
, что бы получать уведомления, когда на разделах будет заканчиваться место.
Обычно “Ссылки по теме” размещаю в конце поста, но тут стоит их добавить в начале.
Для общего знакомства с Prometheus:
- Prometheus: мониторинг — введение, установка, запуск, примеры
- Prometehus: обзор — federation, мониторинг Docker Swarm и настройки Alertmanager
- Prometheus: запуск сервера с Alertmanager, cAdvisor и Grafana
Loki:
- Grafana Labs: Loki — сбор и просмотр логов
- Grafana Labs: Loki – распределённая система, теги и фильтры
Содержание
Текущий мониторинг
Общий мониторинг сейчас осуществляется двумя сервисами – NGINX Amplify и uptrends.com.
NGINX Amplify
Умеет сразу всё из коробки, установка в несколько кликов, но есть одна проблема: алерты по метрике system.disk.in_use можно создавать только для корневого раздела.
У сервера, на котором работает RTFM, имеется дополнительный диск, смонтированный в /backups
:
[simterm]
root@rtfm-do-production:/home/setevoy# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 20G 0 disk └─sda1 8:1 0 20G 0 part /backups vda 254:0 0 50G 0 disk └─vda1 254:1 0 50G 0 part / vdb 254:16 0 440K 1 disk
[/simterm]
Выглядит Amplify dashboard вот так:
Бекапы
В /backups
сохраняются локальные копии бекапов, создаваемые скриптом simple-backup
, см. описание в посте Python: скрипт бекапа файлов и баз MySQL в AWS S3.
Скрипт не идеальный, и многое в нём хочется поправить (или вообще переписать с нуля), но он работает, и свои функции выполняет.
Собственно проблема в том, что он сначала складывает файлы в /backups
, а потом выполняет загрузку в AWS S3.
Если в /backups
сохранить не удалось – то и в S3 данные не отправляются.
Пока для “решения” этой проблемы в cron-задаче просто добавил уведомление на почту при фейлах:
[simterm]
root@rtfm-do-production:/home/setevoy# crontab -l | grep back #Ansible: simple-backup 0 1 * * * /opt/simple-backup/sitebackup.py -c /usr/local/etc/production-simple-backup.ini >> /var/log/simple-backup.log || cat /var/log/simple-backup.log | mailx -s "RTFM production backup - Failed" [email protected]
[/simterm]
uptrends.com
Просто ping-сервис с уведомлением на почту, если веб-сервер отдаёт не 200 код.
В бесплатном варианте ограничение на один сайт, и лимиты на уведомления – но мне хватает почты:
Prometheus, Grafana и Loki
Сегодня будем добавлять дополнительный мониторинг.
Вообще, изначально – просто хотелось прикрутить Loki, что бы следить за логами, но раз уж буду добавлять её – почему бы не прикрутить Prometheus, node_exporter
и Alertmanager, что бы слать себе уведомления на почту и в свой Slack?
Тем более, что все конфиги уже есть – остаётся их скопировать с рабочего проекта, и изменить под себя, т.к. такого количества алертов и метрик, как там – сейчас не нужно.
Пока этот стек запущу на самом сервере с RTFM, потом, может быть, вынесу на какой-то отдельный минимальный сервер: когда будут готовы роли и шаблоны это не составит труда.
Автоматизировать будем, как обычно, через Ansible.
План таков:
- добавить роль monitoring в Ansible
- добавить шаблон Docker Compose для запуска стека сервисов, в котором будут:
- prometheus-server
- node_exporter
- loki
- grafana 6.0
- promtail для сбора логов
- по ходу дела – надо будет обновить роли:
- nginx – добавить новый виртуалхост для проксирования к Grafana и Prometheus
- letsencrypt – для получения сертификата для виртуалхоста с мониторингом
Когда/если мониторинг будет выносится на отдельный сервер – будет смысл добавить blackbox_exporter
и проверять все свои сайты.
В целом сейчас автоматизация RTFM выглядит примерно так же, как описано в посте AWS: миграция RTFM 3.0 (final) — CloudFormation и Ansible роли, только сейчас сервер хостится в DigitalOcean, а все файлы для Ansible собраны в едином приватном репозитоии Github (Miscrosoft сделала хороший подарок всем, разрешив использование приватных репозиториев в бесплатном аккаунте – видимо, испугавшись массового оттока пользователей после покупки Github).
Попозже вынесу все роли, которые используются в этом посте и нынешней автоматизации в публичный репозиторий с фейковыми данными.
Ansible – создание роли
Создаём каталоги:
[simterm]
$ mkdir -p roles/monitoring/{tasks,templates}
[/simterm]
Пока этого хватит.
Добавляем роль в плейбук:
... - role: amplify tags: amplify, monitoring, app - role: monitoring tags: prometheus, monitoring, app ...
Тег app используется как замена all, для запуска всех ролей кроме некоторых, monitoring – для запуска всего, связанного с мониторингом, а с помощью тега prometheus будем провиженить только то, что насетапим сейчас.
Запускается Ansible простым bash-скриптом, см. Скрипт запуска Ansible.
Создаём файл roles/monitoring/tasks/main.yml
, в котором начинаем описывать задачи.
Пользователь и каталоги
Сразу задаём переменные в group_vars/all.yml
:
... # MONITORING prometheus_home: "/opt/prometheus" prometheus_data: "/data/prometheus" prometheus_user: "prometheus"
В roles/monitoring/tasks/main.yml
добавляем создание пользователя:
- name: "Add Prometheus user" user: name: "{{ prometheus_user }}" shell: "/usr/sbin/nologin"
Создание каталога, в котором будем хранить конфиги и Docker Compose файл:
- name: "Create monitoring stack dir {{ prometheus_home }}" file: path: "{{ prometheus_home }}" state: directory owner: "{{ prometheus_user }}" group: "{{ prometheus_user }}" recurse: yes
И каталог для TSDB Prometheus – метрики будут хранится неделю, больше не надо:
- name: "Create Prometehus TSDB data dir {{ prometheus_data }}" file: path: "{{ prometheus_data }}" state: directory owner: "{{ prometheus_user }}" group: "{{ prometheus_user }}"
В рабочем проекте каталогов намного больше:
/etc/prometheus
– с конфигами самого Prometheus, Alertmanager-а, blackbox-exporter-а/etc/grafana
– с конфигами Grafana/opt/prometheus
– с Compose файлом/data/prometheus
– TSDB Prometheus-а/data/grafana
– с данными Grafana (-rwxr-xr-x 1 grafana grafana 8.9G Mar 9 09:12 grafana.db
– OMG!)
Можно запустить, и проверить.
На Dev-окружении пока, конечно:
[simterm]
$ ./ansible_exec.sh -t prometheus Tags: prometheus Env: rtfm-dev ... Dry-run check passed. Are you sure to proceed? [y/n] y Applying roles... ... TASK [monitoring : Add Prometheus user] **** changed: [ssh.dev.rtfm.co.ua] TASK [monitoring : Create monitoring stack dir /opt/prometheus] **** changed: [ssh.dev.rtfm.co.ua] TASK [monitoring : Create Prometehus TSDB data dir /data/prometheus] **** changed: [ssh.dev.rtfm.co.ua] PLAY RECAP **** ssh.dev.rtfm.co.ua : ok=4 changed=3 unreachable=0 failed=0 Provisioning done.
[/simterm]
Проверям каталоги:
[simterm]
root@rtfm-do-dev:~# ll /data/prometheus/ /opt/prometheus/ /data/prometheus/: total 0 /opt/prometheus/: total 0
[/simterm]
Пользователя:
[simterm]
root@rtfm-do-dev:~# id prometheus uid=1003(prometheus) gid=1003(prometheus) groups=1003(prometheus)
[/simterm]
systemd
и Docker Compose
Далее создаём шаблон systemd
-юнит файла и шаблон для запуска стека, пока тут будет только два контейнера с prometehus-server
и node_exporter
.
Пример создания systemd
-файла для запуска Docker Compose есть тут – Linux: systemd сервис для Docker Compose.
Создаём файл шаблона roles/monitoring/templates/prometheus.service.j2
:
[Unit] Description=Prometheus monitoring stack Requires=docker.service After=docker.service [Service] Restart=always WorkingDirectory={{ prometheus_home }} # Compose up ExecStart=/usr/local/bin/docker-compose -f prometheus-compose.yml up # Compose down, remove containers and volumes ExecStop=/usr/local/bin/docker-compose -f prometheus-compose.yml down -v [Install] WantedBy=multi-user.target
И шаблон Compose-файла – roles/monitoring/templates/prometheus-compose.yml.j2
:
version: '2.4' networks: prometheus: services: prometheus-server: image: prom/prometheus networks: - prometheus ports: - 9091:9090 restart: unless-stopped mem_limit: 500m mem_reservation: 100m node-exporter: image: prom/node-exporter networks: - prometheus ports: - 9100:9100 volumes: - /proc:/host/proc:ro - /sys:/host/sys:ro - /:/rootfs:ro command: - '--path.procfs=/host/proc' - '--path.sysfs=/host/sys' - --collector.filesystem.ignored-mount-points - "^/(sys|proc|dev|host|etc|rootfs/var/lib/docker/containers|rootfs/var/lib/docker/overlay2|rootfs/run/docker/netns|rootfs/var/lib/docker/aufs)($$|/)" restart: unless-stopped mem_limit: 500m mem_reservation: 100m
В roles/monitoring/tasks/main.yml
добавляем копирование шаблонов на сервер и запуск сервиса:
... - name: "Copy Compose file {{ prometheus_home }}/prometheus-compose.yml" template: src: templates/prometheus-compose.yml.j2 dest: "{{ prometheus_home }}/prometheus-compose.yml" owner: "{{ prometheus_user }}" group: "{{ prometheus_user }}" mode: 0644 - name: "Copy systemd service file /etc/systemd/system/prometheus.service" template: src: "templates/prometheus.service.j2" dest: "/etc/systemd/system/prometheus.service" owner: "root" group: "root" mode: 0644 - name: "Start monitoring service" service: name: "prometheus" state: restarted enabled: yes
Запускаем скрипт, проверяем сервис:
[simterm]
root@rtfm-do-dev:~# systemctl status prometheus.service ● prometheus.service - Prometheus monitoring stack Loaded: loaded (/etc/systemd/system/prometheus.service; enabled; vendor preset: enabled) Active: active (running) since Sat 2019-03-09 09:52:20 EET; 5s ago Main PID: 1347 (docker-compose) Tasks: 5 (limit: 4915) Memory: 54.1M CPU: 552ms CGroup: /system.slice/prometheus.service ├─1347 /usr/local/bin/docker-compose -f prometheus-compose.yml up └─1409 /usr/local/bin/docker-compose -f prometheus-compose.yml up
[/simterm]
И контейнеры:
[simterm]
root@rtfm-do-dev:~# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 8decc7775ae9 jc5x/firefly-iii ".deploy/docker/entr…" 7 seconds ago Up 5 seconds 0.0.0.0:9090->80/tcp firefly_firefly_1 3647286526c2 prom/node-exporter "/bin/node_exporter …" 7 seconds ago Up 5 seconds 0.0.0.0:9100->9100/tcp prometheus_node-exporter_1 dbe85724c7cf prom/prometheus "/bin/prometheus --c…" 7 seconds ago Up 5 seconds 0.0.0.0:9091->9090/tcp prometheus_prometheus-server_1
[/simterm]
(форматирование сбивается 🙁 )
(firefly-iii – это домашняя бухгалтерия, см. Firefly III: домашняя бухгалтерия)
Let’s Encrypt
Для мониторинга будет использоваться домен monitor.example.com (и dev.monitor.example.com для Dev-окружения), для которого надо получить сертификат.
Полностью его роль выглядит так:
- name: "Install Let's Encrypt client" apt: name: letsencrypt state: latest - name: "Check if NGINX is installed" package_facts: manager: "auto" - name: "NGINX test result - True" debug: msg: "NGINX found" when: "'nginx' in ansible_facts.packages" - name: "NGINX test result - False" debug: msg: "NGINX NOT found" when: "'nginx' not in ansible_facts.packages" - name: "Stop NGINX" systemd: name: nginx state: stopped when: "'nginx' in ansible_facts.packages" # on first install - no /etc/letsencrypt/live/ will be present - name: "Check if /etc/letsencrypt/live/ already present" stat: path: "/etc/letsencrypt/live/" register: le_live_dir - name: "/etc/letsencrypt/live/ check result" debug: msg: "{{ le_live_dir.stat.path }}" - name: "Initialize live_certs with garbage if no /etc/letsencrypt/live/ found" command: "ls -1 /etc/letsencrypt/" register: live_certs when: le_live_dir.stat.exists == false - name: "Check existing certificates" command: "ls -1 /etc/letsencrypt/live/" register: live_certs when: le_live_dir.stat.exists == true - name: "Certs found" debug: msg: "{{ live_certs.stdout_lines }}" - name: "Obtain certificates" command: "letsencrypt certonly --standalone --agree-tos -m {{ notify_email }} -d {{ item.1 }}" with_subelements: - "{{ web_projects }}" - domains when: "item.1 not in live_certs.stdout_lines" - name: "Start NGINX" systemd: name: nginx state: started when: "'nginx' in ansible_facts.packages" - name: "Update renewal settings to web-root" lineinfile: dest: "/etc/letsencrypt/renewal/{{ item.1 }}.conf" regexp: '^authenticator ' line: "authenticator = webroot" state: present with_subelements: - "{{ web_projects }}" - domains - name: "Add Let's Encrypt cronjob for cert renewal" cron: name: letsencrypt_renewal special_time: weekly job: letsencrypt renew --webroot -w /var/www/html/ &> /var/log/letsencrypt/letsencrypt.log && service nginx reload
Список доменов, для которых необходимо получить сертификаты берётся из вложенного списка domains
:
... - name: "Obtain certificates" command: "letsencrypt certonly --standalone --agree-tos -m {{ notify_email }} -d {{ item.1 }}" with_subelements: - "{{ web_projects }}" - domains when: "item.1 not in live_certs.stdout_lines" ...
При этом сначала выполняется проверка уже имеющихся сертификатов, что бы не делать запрос каждый раз:
... - name: "Check existing certificates" command: "ls -1 /etc/letsencrypt/live/" register: live_certs when: le_live_dir.stat.exists == true ...
web_projects
и domains
задаются в файлах переменных:
[simterm]
$ ll group_vars/rtfm-* -rw-r--r-- 1 setevoy setevoy 4731 Mar 8 20:26 group_vars/rtfm-dev.yml -rw-r--r-- 1 setevoy setevoy 5218 Mar 8 20:26 group_vars/rtfm-production.yml
[/simterm]
И выглядят так:
... ####################### ### Roles variables ### ####################### # used in letsencrypt, nginx, php-fpm web_projects: - name: rtfm domains: - dev.rtfm.co.ua - name: setevoy domains: - dev.money.example.com - dev.use.example.com ...
У регистратора добавляем субдомены monitor.example.com и dev.monitor.example.com, ждём обновления DNS:
[simterm]
root@rtfm-do-dev:~# dig dev.monitor.example.com +short 174.***.***.179
[/simterm]
Обновляем списки domains
, и получаем сертификаты:
[simterm]
$ ./ansible_exec.sh -t letsencrypt Tags: letsencrypt Env: rtfm-dev ... TASK [letsencrypt : Check if NGINX is installed] **** ok: [ssh.dev.rtfm.co.ua] TASK [letsencrypt : NGINX test result - True] **** ok: [ssh.dev.rtfm.co.ua] => { "msg": "NGINX found" } TASK [letsencrypt : NGINX test result - False] **** skipping: [ssh.dev.rtfm.co.ua] TASK [letsencrypt : Stop NGINX] **** changed: [ssh.dev.rtfm.co.ua] TASK [letsencrypt : Check if /etc/letsencrypt/live/ already present] **** ok: [ssh.dev.rtfm.co.ua] TASK [letsencrypt : /etc/letsencrypt/live/ check result] **** ok: [ssh.dev.rtfm.co.ua] => { "msg": "/etc/letsencrypt/live/" } TASK [letsencrypt : Initialize live_certs with garbage if no /etc/letsencrypt/live/ found] **** skipping: [ssh.dev.rtfm.co.ua] TASK [letsencrypt : Check existing certificates] **** changed: [ssh.dev.rtfm.co.ua] TASK [letsencrypt : Certs found] **** ok: [ssh.dev.rtfm.co.ua] => { "msg": [ "dev.use.example.com", "dev.money.example.com", "dev.rtfm.co.ua", "README" ] } TASK [letsencrypt : Obtain certificates] **** skipping: [ssh.dev.rtfm.co.ua] => (item=[{'name': 'rtfm'}, 'dev.rtfm.co.ua']) skipping: [ssh.dev.rtfm.co.ua] => (item=[{'name': 'setevoy'}, 'dev.money.example.com']) skipping: [ssh.dev.rtfm.co.ua] => (item=[{'name': 'setevoy'}, 'dev.use.example.com']) changed: [ssh.dev.rtfm.co.ua] => (item=[{'name': 'setevoy'}, 'dev.monitor.example.com']) TASK [letsencrypt : Start NGINX] **** changed: [ssh.dev.rtfm.co.ua] TASK [letsencrypt : Update renewal settings to web-root] **** ok: [ssh.dev.rtfm.co.ua] => (item=[{'name': 'rtfm'}, 'dev.rtfm.co.ua']) ok: [ssh.dev.rtfm.co.ua] => (item=[{'name': 'setevoy'}, 'dev.money.example.com']) ok: [ssh.dev.rtfm.co.ua] => (item=[{'name': 'setevoy'}, 'dev.use.example.com']) changed: [ssh.dev.rtfm.co.ua] => (item=[{'name': 'setevoy'}, 'dev.monitor.example.com']) PLAY RECAP **** ssh.dev.rtfm.co.ua : ok=13 changed=5 unreachable=0 failed=0 Provisioning done.
[/simterm]
NGINX
Далее – добавляем шаблоны виртуахостов для monitor.example.com и dev.monitor.example.com – roles/nginx/templates/dev/dev.monitor.example.com.conf.j2
и roles/nginx/templates/production/monitor.example.com.conf.j2
:
upstream prometheus_server { server 127.0.0.1:9091; } upstream grafana { server 127.0.0.1:3000; } server { listen 80; server_name {{ item.1 }}; # Lets Encrypt Webroot location ~ /.well-known { root /var/www/html; allow all; } location / { allow {{ office_allow_location }}; allow {{ home_allow_location }}; deny all; return 301 https://{{ item.1 }}$request_uri; } } server { listen 443 ssl; server_name {{ item.1 }}; access_log /var/log/nginx/{{ item.1 }}-access.log; error_log /var/log/nginx/{{ item.1 }}-error.log warn; auth_basic_user_file {{ web_data_root_prefix }}/{{ item.0.name }}/.htpasswd_{{ item.0.name }}; auth_basic "Password-protected Area"; allow {{ office_allow_location }}; allow {{ home_allow_location }}; deny all; ssl_certificate /etc/letsencrypt/live/{{ item.1 }}/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/{{ item.1 }}/privkey.pem; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on; ssl_dhparam /etc/nginx/dhparams.pem; ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:ECDHE-RSA-AES128-GCM-SHA256:AES256+EECDH:DHE-RSA-AES128-GCM-SHA256:AES256+EDH:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4"; ssl_session_timeout 1d; ssl_stapling on; ssl_stapling_verify on; location / { proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://grafana$request_uri; } location /prometheus { proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://prometheus_server$request_uri; } }
(см. пост OpenBSD: установка NGINX и настройки безопасности)
Шаблоны копируются из roles/nginx/tasks/main.yml
, используя те же списки web_projects
и domains
:
... - name: "Add NGINX virtualhosts configs" template: src: "templates/{{ env }}/{{ item.1 }}.conf.j2" dest: "/etc/nginx/conf.d/{{ item.1 }}.conf" owner: "root" group: "root" mode: 0644 with_subelements: - "{{ web_projects }}" - domains ...
Запускаем:
[simterm]
$ ./ansible_exec.sh -t nginx Tags: nginx Env: rtfm-dev ... TASK [nginx : NGINX test return code] **** ok: [ssh.dev.rtfm.co.ua] => { "msg": "0" } TASK [nginx : Service NGINX restart and enable on boot] **** changed: [ssh.dev.rtfm.co.ua] PLAY RECAP **** ssh.dev.rtfm.co.ua : ok=13 changed=3 unreachable=0 failed=0
[/simterm]
По идее – уже должен открываться Prometheus:
“404 page not found” – это сообщение уже от самого Prometheus, надо ещё ему путь настроить.
Так как с NGINX и SSL закончили – можно приступать к настройке самих сервисов.
Конфигурация prometheus-server
Создаём новый файл шаблона roles/monitoring/templates/prometheus-server-conf.yml.j2
:
global: scrape_interval: 15s external_labels: monitor: 'rtfm-monitoring-{{ env }}' #alerting: # alertmanagers: # - static_configs: # - targets: # - alertmanager:9093 #rule_files: # - "alert.rules" scrape_configs: - job_name: 'node-exporter' static_configs: - targets: - 'localhost:9100'
alerting
пока комментируем – добавим позже.
Добавляем копирование шаблона на сервер:
... - name: "Copy Prometheus server config {{ prometheus_home }}/prometheus-server-conf.yml" template: src: "templates/prometheus-server-conf.yml" dest: "{{ prometheus_home }}/prometheus-server-conf.yml" owner: "{{ prometheus_user }}" group: "{{ prometheus_user }}" mode: 0644 ...
Обновляем roles/monitoring/templates/prometheus-compose.yml.j2
– добавляем маппинг файла в контейнер:
... prometheus-server: image: prom/prometheus networks: - prometheus ports: - 9091:9090 volumes: - {{ prometheus_home }}/prometheus-server-conf.yml:/etc/prometheus.yml restart: unless-stopped ...
Деплоим:
[simterm]
$ ./ansible_exec.sh -t prometheus Tags: prometheus Env: rtfm-dev ... TASK [monitoring : Start monitoring service] **** changed: [ssh.dev.rtfm.co.ua] PLAY RECAP **** ssh.dev.rtfm.co.ua : ok=7 changed=2 unreachable=0 failed=0 Provisioning done.
[/simterm]
Проверяем – нет, всё-равно 404…
А, вспомнил – --web.external-url
нужен. Правда – тут придётся получать домен из web_projects
и domains
, как в ролях nginx
и letsencrypt
.
И надо добавить указание на файл настроек – --config.file
.
Обновляем Compose, заодно добавляем маппинг /data/prometheus
:
... prometheus-server: image: prom/prometheus networks: - prometheus ports: - 9091:9090 volumes: - {{ prometheus_home }}/prometheus-server-conf.yml:/etc/prometheus.yml - {{ prometheus_data }}:/prometheus/data/ command: - '--config.file=/etc/prometheus.yml' - '--web.external-url=https://{{ item.1 }}/prometheus' restart: always ...
В задачу копирования шаблона – добавляем выборку домена:
... - name: "Copy Compose file {{ prometheus_home }}/prometheus-compose.yml" template: src: "templates/prometheus-compose.yml.j2" dest: "{{ prometheus_home }}/prometheus-compose.yml" owner: "{{ prometheus_user }}" group: "{{ prometheus_user }}" mode: 0644 with_subelements: - "{{ web_projects }}" - domains when: "'monitor' in item.1.name" ...
Запускаем ещё раз, и:
prometheus-server_1 | level=error ts=2019-03-09T09:53:28.427567744Z caller=main.go:688 err=”opening storage failed: lock DB directory: open /prometheus/data/lock: permission denied”
Угу…
Проверяем владельца каталога на хосте:
[simterm]
root@rtfm-do-dev:/opt/prometheus# ls -l /data/ total 8 drwxr-xr-x 2 prometheus prometheus 4096 Mar 9 09:19 prometheus
[/simterm]
Пользователя, под которым работает процесс в контейнере:
[simterm]
root@rtfm-do-dev:/opt/prometheus# docker exec -ti prometheus_prometheus-server_1 ps aux PID USER TIME COMMAND 1 nobody 0:00 /bin/prometheus --config.file=/etc/prometheus.yml --web.ex
[/simterm]
Сравниваем ID пользователя в контейнере:
[simterm]
root@rtfm-do-dev:/opt/prometheus# docker exec -ti prometheus_prometheus-server_1 id nobody uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
[/simterm]
И на хосте:
[simterm]
root@rtfm-do-dev:/opt/prometheus# id nobody uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
[/simterm]
Меняем владельца каталога /data/prometheus
в roles/monitoring/templates/prometheus-compose.yml.j2
:
... - name: "Create Prometehus TSDB data dir {{ prometheus_data }}" file: path: "{{ prometheus_data }}" state: directory owner: "nobody" group: "nogroup" recurse: yes ...
Передеплоиваем всё, и – вуаля!
Осталось поправить таргеты – сейчас prometheus-server
не может подключиться к node_exporter
:
Потому что конфиги копипастил)
Обновляем roles/monitoring/templates/prometheus-server-conf.yml.j2
, меняем localhost:
... scrape_configs: - job_name: 'node-exporter' static_configs: - targets: - 'localhost:9100' ...
На имя контейнера, как оно задано в Compose-файле – node-exporter:
С этим, вроде, всё…
Нет – надо проверить – собирает ли node_exporter
данные о дисках:
Нет… В node_filesystem_avail_bytes
только корневой раздел.
Надо вспомнить из-за чего это.
Настройки node_exporter
Читаем тут – https://github.com/prometheus/node_exporter#using-docker.
Обновляем Compose – добавляем указание bind-mount
== rslave
и path.rootfs
на /rootfs:
... node-exporter: image: prom/node-exporter networks: - prometheus ports: - 9100:9100 volumes: - /proc:/host/proc:ro - /sys:/host/sys:ro - /:/rootfs:ro,rslave command: - '--path.rootfs=/rootfs' - '--path.procfs=/host/proc' - '--path.sysfs=/host/sys' - --collector.filesystem.ignored-mount-points - "^/(sys|proc|dev|host|etc|rootfs/var/lib/docker/containers|rootfs/var/lib/docker/overlay2|rootfs/run/docker/netns|rootfs/var/lib/docker/aufs)($$|/)" restart: unless-stopped mem_limit: 500m mem_reservation: 100m
Перезапускам сервис, проверяем точки монтирования:
[simterm]
root@rtfm-do-dev:/opt/prometheus# curl -s localhost:9100/metrics | grep sda node_disk_io_now{device="sda"} 0 node_disk_io_time_seconds_total{device="sda"} 0.044 node_disk_io_time_weighted_seconds_total{device="sda"} 0.06 node_disk_read_bytes_total{device="sda"} 7.448576e+06 node_disk_read_time_seconds_total{device="sda"} 0.056 node_disk_reads_completed_total{device="sda"} 232 node_disk_reads_merged_total{device="sda"} 0 node_disk_write_time_seconds_total{device="sda"} 0.004 node_disk_writes_completed_total{device="sda"} 1 node_disk_writes_merged_total{device="sda"} 0 node_disk_written_bytes_total{device="sda"} 4096 node_filesystem_avail_bytes{device="/dev/sda1",fstype="ext4",mountpoint="/backups"} 4.910125056e+09 node_filesystem_device_error{device="/dev/sda1",fstype="ext4",mountpoint="/backups"} 0 node_filesystem_files{device="/dev/sda1",fstype="ext4",mountpoint="/backups"} 327680 node_filesystem_files_free{device="/dev/sda1",fstype="ext4",mountpoint="/backups"} 327663 node_filesystem_free_bytes{device="/dev/sda1",fstype="ext4",mountpoint="/backups"} 5.19528448e+09 node_filesystem_readonly{device="/dev/sda1",fstype="ext4",mountpoint="/backups"} 0 node_filesystem_size_bytes{device="/dev/sda1",fstype="ext4",mountpoint="/backups"} 5.216272384e+09
[/simterm]
Так, тут всё ОК, вроде.
Я за*бался…
Сейчас, когда форматирую черновик – то всё так быстро и просто выглядит… А на деле, даже уже имея готовые конфиги, примеры, и зная, что и как делать – повозиться пришлось.
Что осталось?
А…
Grafana, Loki, promtail и alertmanager.
OMG…
Пьём чай.
Так, дальше уже по-быстрому.
Надо в /data
сделать отдельный каталог /data/monitoring
, и в нём уже – для Prometheus, Grafana и Loki.
Обновляем prometheus_data
:
... prometheus_data: "/data/monitoring/prometheus" ...
Добавляем:
... loki_data: "/data/monitoring/loki" grafana_data: "/data/monitoring/grafana" ...
Добавляем их создание в roles/monitoring/tasks/main.yml
:
... - name: "Create Loki's data dir {{ loki_data }}" file: path: "{{ loki_data }}" state: directory owner: "{{ prometheus_user }}" group: "{{ prometheus_user }}" recurse: yes - name: "Create Grafana DB dir {{ grafana_data }}" file: path: "{{ grafana_data }}" state: directory owner: "{{ prometheus_user }}" group: "{{ prometheus_user }}" recurse: yes ...
Loki
В Compose шаблон добавляем Loki:
... loki: image: grafana/loki:master networks: - prometheus ports: - "3100:3100" volumes: - {{ prometheus_home }}/loki-conf.yml:/etc/loki/local-config.yaml - {{ loki_data }}:/tmp/loki/ command: -config.file=/etc/loki/local-config.yaml restart: unless-stopped ...
Создаём шаблон roles/monitoring/templates/loki-conf.yml.j2
– дефолтный, без DynamoDB и S3 – всё храним в /data/monitoring/loki
:
auth_enabled: false server: http_listen_port: 3100 ingester: lifecycler: address: 0.0.0.0 ring: store: inmemory replication_factor: 1 chunk_idle_period: 15m schema_config: configs: - from: 0 store: boltdb object_store: filesystem schema: v9 index: prefix: index_ period: 168h storage_config: boltdb: directory: /tmp/loki/index filesystem: directory: /tmp/loki/chunks limits_config: enforce_metric_name: false
В roles/monitoring/tasks/main.yml
добавляем копирование файла:
... - name: "Copy Loki config {{ prometheus_home }}/loki-conf.yml" template: src: "templates/loki-conf.yml.j2" dest: "{{ prometheus_home }}/loki-conf.yml" owner: "{{ prometheus_user }}" group: "{{ prometheus_user }}" mode: 0644 ...
Grafana
И Grafana:
... grafana: image: grafana/grafana:6.0.0 ports: - "3000:3000" networks: - prometheus depends_on: - loki restart: unless-stopped ...
Каталоги и конфиги добавим позже.
Деплоим, проверяем:
Окей – Grafana уже работает, надо только поправить её настройки.
Создаём шаблон её конфига – по сути тут надо только указать:
... [auth.basic] enabled = false ... [security] # default admin user, created on startup admin_user = {{ grafana_ui_username }} # default admin password, can be changed before first start of grafana, or in profile settings admin_password = {{ grafana_ui_dashboard_admin_pass }} ...
Если мне память не изменяет – больше в нём ничего не менял.
Проверим на рабочем Production:
[simterm]
admin@monitoring-production:~$ cat /etc/grafana/grafana.ini | grep -v \# | grep -v ";" | grep -ve '^$' [paths] [server] [database] [session] [dataproxy] [analytics] [security] admin_user = user admin_password = pass [snapshots] [users] [auth] [auth.anonymous] [auth.github] [auth.google] [auth.generic_oauth] [auth.grafana_com] [auth.proxy] [auth.basic] enabled = false [auth.ldap] [smtp] [emails] [log] [log.console] [log.file] [log.syslog] [event_publisher] [dashboards.json] [alerting] [metrics] [metrics.graphite] [tracing.jaeger] [grafana_com] [external_image_storage] [external_image_storage.s3] [external_image_storage.webdav] [external_image_storage.gcs]
[/simterm]
Угу.
Генерируем пароль:
[simterm]
$ pwgen 12 1 Foh***ae1
[/simterm]
Шифруем его:
[simterm]
$ ansible-vault encrypt_string New Vault password: Confirm New Vault password: Reading plaintext input from stdin. (ctrl-d to end input) Foh***ae1!vault | $ANSIBLE_VAULT;1.1;AES256 38306462643964633766373435613135386532373133333137653836663038653538393165353931 ... 6636633634353131350a343461633265353461386561623233636266376266326337383765336430 3038 Encryption successful
[/simterm]
Создаём переменные grafana_ui_username
и grafana_ui_dashboard_admin_pass
:
... # MONITORING prometheus_home: "/opt/prometheus" prometheus_user: "prometheus" # data dirs prometheus_data: "/data/monitoring/prometheus" loki_data: "/data/monitoring/loki" grafana_data: "/data/monitoring/grafana" grafana_ui_username: "setevoy" grafana_ui_dashboard_admin_pass: !vault | $ANSIBLE_VAULT;1.1;AES256 38306462643964633766373435613135386532373133333137653836663038653538393165353931 ... 6636633634353131350a343461633265353461386561623233636266376266326337383765336430 3038
Создаём шаблон конфига Grafana roles/monitoring/templates/grafana-conf.yml.j2
:
[paths] [server] [database] [session] [dataproxy] [analytics] [security] admin_user = {{ grafana_ui_username }} admin_password = {{ grafana_ui_dashboard_admin_pass }} [snapshots] [users] [auth] [auth.anonymous] [auth.github] [auth.google] [auth.generic_oauth] [auth.grafana_com] [auth.proxy] [auth.basic] enabled = false [auth.ldap] [smtp] [emails] [log] [log.console] [log.file] [log.syslog] [event_publisher] [dashboards.json] [alerting] [metrics] [metrics.graphite] [tracing.jaeger] [grafana_com] [external_image_storage] [external_image_storage.s3] [external_image_storage.webdav] [external_image_storage.gcs]
Добавляем его копирование:
... - name: "Copy systemd service file /etc/systemd/system/prometheus.service" template: src: "templates/prometheus.service.j2" dest: "/etc/systemd/system/prometheus.service" owner: "root" group: "root" mode: 0644 ...
Добавляем его маппинг в Compose:
... grafana: image: grafana/grafana:6.0.0 ports: - "3000:3000" volumes: - {{ prometheus_home }}/grafana-conf.yml:/etc/grafana/grafana.ini - {{ grafana_data }}:/var/lib/grafana ...
Ещё надо будет замапить {{ prometheus_home }}/provisioning
– тут Grafana будет свои настройки держать, но это потом.
Может вообще отдельный таки каталог ей придётся делать.
Деплоим, проверяем:
GF_PATHS_DATA=’/var/lib/grafana’ is not writable.
You may have issues with file permissions, more information here: http://docs.grafana.org/installation/docker/#migration-from-a-previous-version-of-the-docker-container-to-5-1-or-later
mkdir: cannot create directory ‘/var/lib/grafana/plugins’: Permission denied
Hu%^%*@d(&!!!
Читаем документацию:
default user id 472 instead of 104
Да, теперь вспомнил.
Добавляем создание юзера grafana
со своим UID.
В переменные вносим:
... grafana_user: "grafana" grafana_uid: 472
Добавляем создание пользователя и группы:
- name: "Add Prometheus user" user: name: "{{ prometheus_user }}" shell: "/usr/sbin/nologin" - name: "Create Grafana group {{ grafana_user }}" group: name: "{{ grafana_user }}" gid: "{{ grafana_uid }}" - name: "Create Grafana's user {{ grafana_user }} with UID {{ grafana_uid }}" user: name: "{{ grafana_user }}" uid: "{{ grafana_uid }}" group: "{{ grafana_user }}" shell: "/usr/sbin/nologin" ...
И меняем владельца каталога {{ grafana_data }}
:
... - name: "Create Grafana DB dir {{ grafana_data }}" file: path: "{{ grafana_data }}" state: directory owner: "{{ grafana_user }}" group: "{{ grafana_user }}" recurse: yes ...
Передеплоиваем, проверяем ещё раз:
Ура)
Но логов ещё нет, т.к. не добавляли promtail
.
Кроме того – надо добавить настройку datasource
для Grafana из Ansible.
Фух…
Погнали дальше.
Добавляем создание каталога {{ prometheus_home }}/grafana-provisioning/datasources
:
... - name: "Create {{ prometheus_home }}/grafana-provisioning/datasources directory" file: path: "{{ prometheus_home }}/grafana-provisioning/datasources" owner: "{{ grafana_user }}" group: "{{ grafana_user }}" mode: 0755 state: directory ...
Добавляем его маппинг в Compose:
... grafana: image: grafana/grafana:6.0.0 ports: - "3000:3000" volumes: - {{ prometheus_home }}/grafana-conf.yml:/etc/grafana/grafana.ini - {{ prometheus_home }}/grafana-provisioning:/etc/grafana/ - {{ grafana_data }}:/var/lib/grafana ...
Деплоим, проверяем данные в контейнере:
[simterm]
root@rtfm-do-dev:/opt/prometheus# docker exec -ti prometheus_grafana_1 sh $ ls -l /etc/grafana total 8 drwxr-xr-x 2 grafana grafana 4096 Mar 9 11:46 datasources -rw-r--r-- 1 1003 1003 571 Mar 9 11:26 grafana.ini
[/simterm]
Окей.
Теперь надо добавить датасорс Loki – roles/monitoring/templates/grafana-datasources.yml.j2
(см. Grafana: добавление datasource из Ansible):
# config file version apiVersion: 1 deleteDatasources: - name: Loki datasources: - name: Loki type: loki access: proxy url: http://loki:3100 isDefault: true version: 1
И его копирование на сервер:
... - name: "Copy Grafana datasources config {{ prometheus_home }}/grafana-provisioning/datasources/datasources.yml" template: src: "templates/grafana-datasources.yml.j2" dest: "{{ prometheus_home }}/grafana-provisioning/datasources/datasources.yml" owner: "{{ grafana_user }}" group: "{{ grafana_user }}" ...
Деплоим, проверяем:
t=2019-03-09T11:52:35+0000 lvl=eror msg=”can’t read datasource provisioning files from directory” logger=provisioning.datasources path=/etc/grafana/provisioning/datasources error=”open /etc/grafana/provisioning/datasources: no such file o
r directory”
А, да.
Фиксим путь в Compose – {{ prometheus_home }}/grafana-provisioning
должен маппится не в корень /etc/grafana
, а как /etc/grafana/provisioning
:
... volumes: - {{ prometheus_home }}/grafana-conf.yml:/etc/grafana/grafana.ini - {{ prometheus_home }}/grafana-provisioning:/etc/grafana/provisioning - {{ grafana_data }}:/var/lib/grafana ...
Передеплоиваем, и с этим всё готово:
promtail
Так.
Добавляем контейнер с promtail
.
Потом ещё alertmanager
и его алерты… Но сегодня, наверно уже не успею.
Добавляем контейнер в Compose.
Не помню на счёт файла positions.yaml
– надо ли его хранить на хосте…
Но раз на наших Production не делал – то, наверно, не критично – точно помню, что в Slack Grafan-ы спрашивал об этом, но в поиске тред уже не находится.
Пока пропустим, делаем без него:
... promtail: image: grafana/promtail:master volumes: - {{ prometheus_home }}/promtail-conf.yml:/etc/promtail/docker-config.yaml # - {{ prometheus_home }}/promtail-positions.yml:/tmp/positions.yaml - /var/log:/var/log command: -config.file=/etc/promtail/docker-config.yaml
Создаём шаблон roles/monitoring/templates/promtail-conf.yml.j2
:
server: http_listen_port: 9080 grpc_listen_port: 0 positions: filename: /tmp/positions.yaml client: url: http://loki:3100/api/prom/push scrape_configs: - job_name: system entry_parser: raw static_configs: - targets: - localhost labels: job: varlogs env: {{ env }} host: {{ set_hostname }} __path__: /var/log/*log - job_name: nginx entry_parser: raw static_configs: - targets: - localhost labels: job: nginx env: {{ env }} host: {{ set_hostname }} __path__: /var/log/nginx/*log
Тут:
url: http://loki:3100/api/prom/push
– URL aka имя контейнера с Loki, в которыйpromtail
будетPUSH
-ить данныеenv: {{ env }}
иhost: {{ set_hostname }}
– дополнительные теги, задаются вgroup_vars/rtfm-dev.yml
иgroup_vars/rtfm-production.yml
:
env: dev
set_hostname: rtfm-do-dev
Добавляем копирование файла:
... - name: "Copy Promtail config {{ prometheus_home }}/promtail-conf.yml" template: src: "templates/promtail-conf.yml.j2" dest: "{{ prometheus_home }}/promtail-conf.yml" owner: "{{ prometheus_user }}" group: "{{ prometheus_user }}" ...
Деплоим:
level=info ts=2019-03-09T12:09:07.709299788Z caller=tailer.go:78 msg=”start tailing file” path=/var/log/user.log
2019/03/09 12:09:07 Seeked /var/log/bootstrap.log – &{Offset:0 Whence:0}
level=info ts=2019-03-09T12:09:07.709435374Z caller=tailer.go:78 msg=”start tailing file” path=/var/log/bootstrap.log
2019/03/09 12:09:07 Seeked /var/log/dpkg.log – &{Offset:0 Whence:0}
level=info ts=2019-03-09T12:09:07.709746566Z caller=tailer.go:78 msg=”start tailing file” path=/var/log/dpkg.log
level=warn ts=2019-03-09T12:09:07.710448913Z caller=client.go:172 msg=”error sending batch, will retry” status=-1 error=”Post http://loki:3100/api/prom/push: dial tcp: lookup loki on 127.0.0.11:53: no such host”
level=warn ts=2019-03-09T12:09:07.726751418Z caller=client.go:172 msg=”error sending batch, will retry” status=-1 error=”Post http://loki:3100/api/prom/push: dial tcp: lookup loki on 127.0.0.11:53: no such host”
Так…
Логи он собирает, но не может увидеть Loki.
А пачиму?
А патамушта надо depends
добавлять.
Обновляем Compose, для promtail
добавляем:
... depends_on: - loki
Нет… Не помогло…
Что?
А! Сеть жеж!
Снова-таки – конфиги копировал, там немного другой сетап.
Добавляем networks
в Compose:
... promtail: image: grafana/promtail:master networks: - prometheus volumes: - /opt/prometheus/promtail-conf.yml:/etc/promtail/docker-config.yaml # - /opt/prometheus/promtail-positions.yml:/tmp/positions.yaml - /var/log:/var/log command: -config.file=/etc/promtail/docker-config.yaml depends_on: - loki
И:
Готово.
Всё.
На этом хватит.
Alertmanager и интеграция со Slack описаны в посте Prometehus: обзор — federation, мониторинг Docker Swarm и настройки Alertmanager.
А теперь я, наконец-то, пойду завтракать 🙂
Потому что начал сетапить всё это часов в 9 утра, а сейчас – 14.30.
А привожу черновик в нормальный вид вообще в 8 вечера…