Когда серверы и приложения работают круглые сутки без выходных, рано или поздно встаёт вопрос наблюдения за их состоянием. Просто зайти по SSH и посмотреть top уже не выход, особенно если машин больше десятка, а сервисов на каждой по пять. Метрики нужны исторические, доступные в любой момент, наглядные на графиках и собранные с десятков источников одновременно. Именно для этого создавались системы хранения временных рядов, и Graphite остаётся одним из старожилов и образцов жанра в этой области.

Появившийся ещё в 2008 году в недрах компании Orbitz, Graphite задал стандарт для всей индустрии. Многие популярные сегодня системы, включая Prometheus и InfluxDB, в той или иной мере оглядывались на его архитектуру и протокол передачи метрик. Этот протокол, в народе называемый просто Graphite line protocol, до сих пор поддерживается как один из основных способов отправки данных в десятках разных инструментов. Простота приёма данных по обычному TCP-сокету сделала Graphite универсальным приёмником для самой пёстрой инфраструктуры.

Сам Graphite состоит из трёх ключевых компонентов. Веб-приложение на Django отвечает за интерфейс пользователя и построение графиков. Бекенд хранения под названием Carbon принимает метрики и записывает их на диск. А библиотека Whisper занимается непосредственно форматом файлов с временными рядами, оптимизированным под быстрое чтение и компактное хранение исторических данных. Каждая часть может работать независимо, что позволяет масштабировать систему гибко и под разные нагрузки.

Настройка системного брандмауэра UFW и подготовка базовых пакетов для предстоящей установки

Любая работа с серверным программным обеспечением начинается с базовой подготовки системы. Прежде всего стоит обновить пакеты до актуальных версий, потому что свежеустановленная Ubuntu обычно содержит образ на момент релиза, а с тех пор накопились десятки обновлений безопасности и исправлений.

$ sudo apt update && sudo apt upgrade

Двойной амперсанд тут не случаен. Он гарантирует, что вторая команда выполнится только если первая отработала успешно. Если apt update провалится из-за проблем с сетью или повреждённого репозитория, то apt upgrade даже не запустится, что избавит от риска получить неконсистентное состояние пакетной системы. Это классический приём для последовательного выполнения зависимых команд в Unix-оболочках.

Следом подтягиваются базовые утилиты, без которых трудно представить работу администратора. Часть из них наверняка уже установлена в системе, но команда не сделает хуже, потому что apt просто пропустит уже установленные пакеты.

$ sudo apt install wget curl nano unzip -y

Утилита wget пригодится для скачивания файлов и архивов. Инструмент curl делает то же самое, но с большей гибкостью и часто используется для работы с HTTP-API. Редактор nano отлично подходит новичкам своей простотой, а опытные пользователи всё равно его открывают для быстрой правки конфигов. Архиватор unzip понадобится для распаковки ZIP-файлов, которые периодически встречаются в работе.

Когда базовая часть готова, наступает черёд настройки брандмауэра. Безопасность сервера это не та область, где стоит экономить время на старте. Открытый наружу порт без необходимости это потенциальная дыра, которую обязательно найдут автоматические сканеры в течение нескольких часов после публикации сервера в интернет. UFW, или Uncomplicated Firewall, это удобная надстройка над iptables, которая делает базовую настройку максимально простой.

$ sudo ufw status

В нормально настроенной системе вывод покажет что-то подобное.

Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)

Видно, что разрешён только SSH-доступ. Для работы веб-сервиса нужно открыть стандартные порты HTTP и HTTPS.

$ sudo ufw allow http
$ sudo ufw allow https

UFW сам распознаёт имена сервисов из файла /etc/services и переводит их в номера портов 80 и 443. Это удобнее, чем писать номера руками, потому что снижает шанс опечатки. После добавления правил повторная проверка статуса покажет обновлённую картину.

$ sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
80/tcp                     ALLOW       Anywhere
443                        ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)
80/tcp (v6)                ALLOW       Anywhere (v6)
443 (v6)                   ALLOW       Anywhere (v6)

Все нужные порты открыты, причём как для IPv4, так и для IPv6. Современные серверы часто имеют адреса обоих типов, и поддержка обеих версий протокола стала обязательной.

Установка зависимостей для сборки Python-пакетов и компиляции нативных расширений

Graphite ставится через менеджер Python-пакетов pip, и большинство его компонентов написаны на этом языке. Но не всё работает на чистом Python. Часть критичных для производительности модулей, особенно отвечающих за графику и работу с временными рядами, написаны на C и требуют компиляции из исходников при установке. Это значит, что на сервере должен быть полноценный набор инструментов разработчика.

$ sudo apt install vim python3-dev python3-pip libcairo2-dev libffi-dev build-essential

Каждый пакет здесь играет роль. Редактор vim это альтернатива nano для тех, кто предпочитает классический подход. Заголовки python3-dev содержат файлы для компиляции расширений Python из исходников. Без них pip не сможет собрать модули с нативным кодом и выдаст загадочные ошибки про отсутствующий Python.h. Менеджер пакетов pip скачивает и устанавливает библиотеки из репозитория PyPI. Библиотека libcairo2-dev отвечает за двумерную графику и нужна для рендеринга графиков Graphite. Заголовки libffi-dev позволяют Python вызывать функции из библиотек на C. Метапакет build-essential тянет за собой компилятор gcc, make и набор стандартных библиотек, без которых сборка любого C-кода невозможна.

Получение и установка пакетов Graphite через pip с сохранением структуры файловой системы

Graphite не входит в стандартные репозитории Ubuntu, и устанавливать его проще всего напрямую из официальных репозиториев на GitHub. Это даёт самую свежую версию со всеми последними исправлениями, в отличие от устаревших пакетов из дистрибутива, которые могли отстать на несколько лет.

$ export PYTHONPATH="/opt/graphite/lib/:/opt/graphite/webapp/"
$ sudo pip install --no-binary=:all: https://github.com/graphite-project/whisper/tarball/master
$ sudo pip install --no-binary=:all: https://github.com/graphite-project/carbon/tarball/master
$ sudo pip install --no-binary=:all: https://github.com/graphite-project/graphite-web/tarball/master

Переменная окружения PYTHONPATH говорит Python, где искать модули Graphite. Без её установки pip развернёт компоненты в стандартные системные пути, что в случае Graphite крайне неудобно. Авторы проекта выбрали путь /opt/graphite как корневую директорию для всех файлов, и pip уважает это решение, если правильно настроить переменную.

Флаг --no-binary=:all: заставляет pip собирать пакеты из исходников, а не использовать готовые скомпилированные wheel-файлы. Для Graphite это принципиально, потому что готовые бинарные сборки могут не учитывать специфическую структуру установки в /opt/graphite. Сборка из исходников занимает больше времени, но даёт правильный результат.

Три отдельных команды соответствуют трём компонентам системы. Сначала ставится Whisper, потому что от него зависят остальные части. Затем Carbon, который использует Whisper для записи метрик на диск. И последним устанавливается веб-интерфейс graphite-web, который читает данные через Carbon и Whisper для построения графиков.

Установка СУБД PostgreSQL из официального репозитория и создание базы данных для Graphite

Веб-интерфейс Graphite использует базу данных для хранения настроек пользователей, дашбордов, сохранённых графиков и других метаданных. Сами метрики туда не пишутся, для них есть Whisper. Но без работающей базы веб-приложение просто не запустится. PostgreSQL это надёжный выбор, проверенный годами и поддерживаемый разработчиками Django.

В репозиториях Ubuntu обычно лежат не самые свежие версии PostgreSQL. Поэтому разумнее подключить официальный репозиторий проекта, который поддерживается командой PostgreSQL Global Development Group.

$ curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | sudo tee /usr/share/keyrings/postgresql-key.gpg >/dev/null

Эта конвейерная команда выполняет сразу несколько действий. Сначала curl скачивает ASCII-формат GPG-ключа PostgreSQL. Затем gpg --dearmor преобразует его в бинарный формат, который требуется современными версиями apt. И наконец tee записывает результат в системную папку, где apt ищет ключи для проверки подписей пакетов. Перенаправление в /dev/null убирает дублирующий вывод в консоль, который генерирует tee.

Следом нужно добавить запись о репозитории в систему.

$ sudo sh -c 'echo "deb [signed-by=/usr/share/keyrings/postgresql-key.gpg arch=amd64] http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'

Конструкция выглядит громоздкой из-за того, что выполняется внутри sh -c с одинарными кавычками. Это нужно для того, чтобы перенаправление в файл произошло с правами sudo, а не от обычного пользователя. Вызов $(lsb_release -cs) подставляет кодовое имя текущей версии Ubuntu, например jammy для Ubuntu 22.04. Так репозиторий автоматически подбирает пакеты под нужный дистрибутив без необходимости править URL вручную.

После добавления репозитория обычным образом обновляется кеш и ставится PostgreSQL.

$ sudo apt update
$ sudo apt install postgresql postgresql-contrib libpq-dev

Пакет postgresql это сам сервер базы данных, postgresql-contrib добавляет дополнительные модули и расширения, а libpq-dev содержит заголовочные файлы для компиляции Python-обёрток над PostgreSQL.

Проверка статуса сервиса покажет, что PostgreSQL стартовал и работает.

$ sudo systemctl status postgresql
? postgresql.service - PostgreSQL RDBMS
     Loaded: loaded (/lib/systemd/system/postgresql.service; enabled; vendor preset: enabled)
     Active: active (exited) since Tue 2022-09-27 10:09:35 UTC; 4s ago
    Process: 4456 ExecStart=/bin/true (code=exited, status=0/SUCCESS)
   Main PID: 4456 (code=exited, status=0/SUCCESS)
        CPU: 1ms

Sep 27 10:09:35 matrix systemd[1]: Starting PostgreSQL RDBMS...
Sep 27 10:09:35 matrix systemd[1]: Finished PostgreSQL RDBMS.

Статус active exited может смутить новичка, но это нормально для PostgreSQL в Ubuntu. Дело в том, что главный systemd-юнит PostgreSQL это всего лишь обёртка, которая управляет реальными процессами кластеров через скрипт pg_ctlcluster. Сами процессы базы запускаются как отдельные юниты с именами вроде postgresql@14-main.

Дальше нужно создать пользователя и базу для Graphite. Это делается через интерактивную оболочку psql от имени системного пользователя postgres.

$ sudo -su postgres psql

Внутри оболочки выполняются SQL-команды для создания пользователя и базы.

postgres=# CREATE USER graphite WITH PASSWORD 'your_password';
postgres=# CREATE DATABASE graphitedb WITH OWNER graphite;
postgres=# \q

Команда CREATE USER создаёт нового пользователя базы данных, не путать с системным пользователем Linux. Пароль your_password нужно заменить на реально надёжный, потому что он будет использоваться приложением для подключения к базе. Команда CREATE DATABASE с опцией OWNER сразу делает свежесозданного пользователя владельцем новой базы, что даёт ему полные права на манипуляции с её содержимым. Команда \q или просто Ctrl+D выходит из оболочки psql обратно в shell.

Конфигурирование подсистемы Carbon и хранилища метрик с правилами ретенции данных

Carbon это сердце системы Graphite, через которое проходят все поступающие метрики. У него есть три компонента, каждый со своей задачей. Сервис carbon-cache принимает метрики и записывает их на диск, это обязательный минимум для работы. Сервис carbon-relay реплицирует данные между несколькими серверами, полезен в распределённых установках. Сервис carbon-aggregator буферизует метрики и агрегирует их перед записью, что снижает нагрузку на диск при больших потоках данных.

Для базовой установки достаточно только carbon-cache. Начинается всё с копирования примеров конфигов в рабочие файлы.

$ sudo cp /opt/graphite/conf/carbon.conf.example /opt/graphite/conf/carbon.conf

Файл carbon.conf содержит основные настройки сервиса. Пример конфига обычно настроен под типовую установку и работает из коробки. Для тонкой настройки производительности придётся в него заглянуть, но на старте можно оставить как есть.

Гораздо важнее настроить схему хранения данных, которая определяет, с какой детальностью и как долго хранятся метрики.

$ sudo cp /opt/graphite/conf/storage-schemas.conf.example /opt/graphite/conf/storage-schemas.conf

Этот файл содержит правила, по которым Carbon определяет, как записывать каждую конкретную метрику.

$ sudo nano /opt/graphite/conf/storage-schemas.conf

Внутри обнаружится стандартная заготовка, по которой видно структуру записей.

[carbon]
pattern = ^carbon\.
retentions = 60:90d

Каждая секция в квадратных скобках это имя правила. Параметр pattern содержит регулярное выражение, по которому Carbon ищет соответствие имени метрики. Запись ^carbon. означает, что под это правило попадают все метрики, начинающиеся со слова carbon с точкой после него. Параметр retentions описывает стратегию хранения. Запись 60:90d читается так. Каждые 60 секунд записывается одно значение, и эти значения хранятся в течение 90 дней.

Для теста и понимания работы системы можно добавить собственное правило. Все метрики с префиксом test будут попадать под него.

[test]
pattern = ^test\.
retentions = 10s:10m,1m:1h

Здесь интереснее. Запись содержит две части через запятую, и это две степени детализации одних и тех же данных. Первое определение 10s:10m создаёт точку данных каждые десять секунд и хранит их в течение десяти минут. Второе определение 1m:1h берёт уже накопленные данные и аггрегирует их в одну точку в минуту, храня уже целый час. Такая многоуровневая ретенция это сильная сторона Whisper. Свежие данные доступны с высокой детализацией, а старые сжимаются до более низкого разрешения, экономя место на диске.

После сохранения файла можно запускать сервис carbon-cache.

$ sudo /opt/graphite/bin/carbon-cache.py start

Запуск идёт напрямую через Python-скрипт, потому что в стандартной установке Graphite не идёт с systemd-юнитом. Для продакшна разумно создать такой юнит вручную, чтобы сервис автоматически стартовал при перезагрузке системы.

Настройка веб-приложения Graphite с генерацией секретного ключа и подключением к базе

Веб-часть Graphite это Django-приложение, которое требует своей конфигурации. Прежде всего нужен секретный ключ, который Django использует для криптографических нужд вроде подписи сессионных кук и защиты от CSRF-атак.

$ python3 -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'
sp%71)6b$%^bc(7xpz1d!)x3(azog01&k^8l02*!y0#)72p07y

Команда импортирует функцию из самого Django и сразу вызывает её, выводя случайный ключ в консоль. Этот ключ нужно сохранить, он пригодится через минуту. Длина и состав символов в ключе подобраны так, чтобы дать высокую энтропию.

Дальше создаётся файл локальных настроек приложения из шаблона.

$ sudo cp /opt/graphite/webapp/graphite/local_settings.py.example /opt/graphite/webapp/graphite/local_settings.py

Этот файл переопределяет стандартные настройки Graphite под конкретную установку. Открываем его на редактирование.

$ sudo nano /opt/graphite/webapp/graphite/local_settings.py

Несколько параметров требуют раскомментирования и заполнения реальными значениями. Секретный ключ ставится в SECRET_KEY.

SECRET_KEY = 'your-secret-key'

Разрешённые хосты лучше пока поставить как звёздочку, что разрешит обращения с любого имени. Для продакшна это плохая практика, и стоит указать конкретные домены.

ALLOWED_HOSTS = [ '*' ]

Часовой пояс важен для правильного отображения временных меток на графиках. Здесь нужно указать свой регион в формате IANA.

TIME_ZONE = 'Asia/Kolkata'

Параметр USE_REMOTE_USER_AUTHENTICATION включает проверку пользователя через внешнюю аутентификацию.

USE_REMOTE_USER_AUTHENTICATION = True

Самая важная часть это настройки подключения к базе данных. Здесь указываются те же имя пользователя и пароль, которые задавались при создании БД в PostgreSQL.

DATABASES = {
    'default': {
        'NAME': 'graphitedb',
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'USER': 'graphite',
        'PASSWORD': 'your_password',
        'HOST': '127.0.0.1',
        'PORT': ''
    }
}

Драйвер postgresql_psycopg2 это самая популярная Python-обёртка над PostgreSQL, и для её работы нужен ещё один пакет.

$ sudo pip install psycopg2-binary

Версия с суффиксом binary это уже скомпилированный пакет, который не требует системных зависимостей PostgreSQL для установки. Для большинства случаев этого хватает.

Теперь нужно создать таблицы в базе данных, выполнив миграции Django.

$ sudo PYTHONPATH=/opt/graphite/webapp/ django-admin.py migrate --settings=graphite.settings

Переменная PYTHONPATH в начале команды указывает интерпретатору Python, где искать модули. Параметр --settings явно говорит Django, какой файл настроек использовать. Без этого Django не найдёт настроек и упадёт с ошибкой.

Operations to perform:
  Apply all migrations: account, admin, auth, contenttypes, dashboard, events, sessions, tagging, tags, url_shortener
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying account.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying dashboard.0001_initial... OK
  Applying events.0001_initial... OK
  Applying sessions.0001_initial... OK
  Applying tagging.0001_initial... OK
  Applying tagging.0002_on_delete... OK
  Applying tags.0001_initial... OK
  Applying url_shortener.0001_initial... OK

Каждая строка с OK означает успешно применённую миграцию. Если на каком-то шаге появится ошибка, дальнейшие шаги делать смысла нет, нужно сначала разбираться с тем, что пошло не так.

Затем собираются статические файлы веб-приложения.

$ sudo PYTHONPATH=/opt/graphite/webapp/ django-admin.py collectstatic --settings=graphite.settings

Эта команда копирует CSS, JavaScript и картинки всех Django-приложений в одну папку, откуда их будет отдавать веб-сервер. Без этого шага веб-интерфейс отобразится без стилей и картинок, превратившись в кучу нечитаемого HTML.

Права на нужные директории передаются пользователю www-data, под которым работает Apache.

$ sudo chown -R www-data:www-data /opt/graphite/storage/
$ sudo chown -R www-data:www-data /opt/graphite/static/
$ sudo chown -R www-data:www-data /opt/graphite/webapp/

Без правильных прав Apache не сможет читать и писать файлы Graphite, и веб-интерфейс просто не запустится. Флаг -R делает изменение прав рекурсивным, охватывая все вложенные файлы и папки.

Финальный шаг это создание суперпользователя для входа в админку.

$ sudo PYTHONPATH=/opt/graphite/webapp/ django-admin.py createsuperuser --settings=graphite.settings
Username (leave blank to use 'root'): navjot
Email address: Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript.
Password: 
Password (again): 
Superuser created successfully.

Эти учётные данные будут использоваться позже для входа в Graphite через веб-интерфейс. Имя пользователя можно выбрать любое осмысленное, email тоже произвольный. Пароль вводится дважды для исключения опечаток.

Настройка Apache с модулем WSGI для запуска Django-приложения Graphite

Graphite поставляется с готовой конфигурацией для Apache, что упрощает развёртывание. Apache через модуль mod_wsgi умеет запускать Python-приложения как часть своего процесса, что даёт приличную производительность и удобное управление.

$ sudo apt install apache2 libapache2-mod-wsgi-py3

Пакет libapache2-mod-wsgi-py3 содержит модуль WSGI именно для Python 3, что важно. В системе может встретиться и версия для Python 2, но она уже устарела.

Файл-обвязка для WSGI создаётся из примера.

$ sudo cp /opt/graphite/conf/graphite.wsgi.example /opt/graphite/conf/graphite.wsgi

Конфиг виртуального хоста копируется из примеров Graphite в стандартную папку Apache.

$ sudo cp /opt/graphite/examples/example-graphite-vhost.conf /etc/apache2/sites-available/graphite.conf

Файл нужно отредактировать под конкретную установку.

$ sudo nano /etc/apache2/sites-available/graphite.conf

Несколько правок принципиально важны. В первой строке порт меняется с 80 на 127.0.0.1:8080.

<VirtualHost 127.0.0.1:8080>

Привязка к 127.0.0.1 означает, что Apache будет слушать только локальные соединения. Извне попасть на него напрямую будет невозможно, и это правильно, потому что наружу мы выставим Nginx как прокси. Порт 8080 выбран как обычный для внутренних HTTP-сервисов, не конфликтующий с системными.

Имя домена указывается через ServerName.

ServerName graphite.example.com #Replace with your domain

Это значение нужно заменить на реальный домен сервера, иначе Apache не будет правильно идентифицировать запросы.

Ниже строки с Alias /static/ добавляется блок, разрешающий доступ к статическим файлам.

#Add below lines
<Directory /opt/graphite/static/>
     Require all granted
</Directory>

Без этого блока Apache закроет доступ к статике, и веб-интерфейс снова окажется без оформления.

Дефолтный виртуальный хост Apache отключается, новый включается.

$ sudo a2dissite 000-default
$ sudo a2ensite graphite

Утилиты a2dissite и a2ensite это Debian-специфичные хелперы, которые просто работают с символическими ссылками в папках sites-available и sites-enabled. Удобно тем, что не нужно помнить точные пути.

Apache нужно перенастроить на прослушивание только порта 8080 вместо стандартного 80.

$ sudo nano /etc/apache2/ports.conf

В файле строка Listen 80 заменяется на следующее.

Listen 127.0.0.1:8080

После всех правок Apache перезапускается.

$ sudo systemctl restart apache2

Быстрая проверка работоспособности делается через curl.

$ curl 127.0.0.1:8080

Корректно работающий Graphite вернёт HTML-структуру своего фреймсетового интерфейса.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">
<!-- Copyright 2008 Orbitz WorldWide

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->

<html>
  <head>
    <title>Graphite Browser</title>
  </head>


<frameset rows="80,*" frameborder="1" border="1">
  <frame src="/browser/header" name="Header" id='header' scrolling="no" noresize="true" />

    <frame src="/composer?" name="content" id="composerFrame"/>

</frameset>
</html>

Использование фреймсетов в 2022 году выглядит как привет из девяностых, и это часть очарования Graphite, который сохранил свой классический интерфейс почти без изменений за полтора десятилетия существования. Функциональность от этого не страдает, но многим современные альтернативы вроде Grafana кажутся приятнее визуально. Это не помеха, потому что Graphite часто и используется как бекенд для Grafana, которая берёт данные и рисует свои красивые графики.

Установка свежей версии Nginx из официального репозитория и настройка SSL-сертификатов через Certbot

Apache в этой схеме работает на внутреннем порту и недоступен снаружи. Перед ним встаёт Nginx, который принимает соединения из интернета, обеспечивает HTTPS и проксирует запросы дальше на Apache. Зачем такая двухуровневая схема. Apache хорошо работает с WSGI и Django-приложениями, а Nginx эффективнее раздаёт статику и шифрует трафик. Связка из обоих даёт лучшее от двух миров.

В стандартных репозиториях Ubuntu лежит относительно старая версия Nginx, а в официальном репозитории проекта всегда свежая стабильная сборка.

$ curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \
| sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null

Знакомая по PostgreSQL конструкция импорта ключа. Сначала скачивается ASCII-формат, потом конвертируется в бинарный и сохраняется в системную папку для ключей apt.

Запись о репозитории добавляется в список источников.

$ echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg arch=amd64] \
http://nginx.org/packages/ubuntu `lsb_release -cs` nginx" \
| sudo tee /etc/apt/sources.list.d/nginx.list

Обратные кавычки вокруг lsb_release -cs это устаревший синтаксис подстановки команды, эквивалент $(lsb_release -cs). Оба работают одинаково, и здесь использован классический вариант.

Дальше стандартная процедура обновления и установки.

$ sudo apt update
$ sudo apt install nginx

Проверка версии покажет, что установлен свежий релиз.

$ nginx -v
nginx version: nginx/1.22.0

Запуск сервиса делается обычным systemctl.

$ sudo systemctl start nginx

Для работы по HTTPS нужен SSL-сертификат, и в наше время бесплатные сертификаты от Let's Encrypt стали стандартом для большинства случаев. Получаются они через утилиту Certbot, которую разумнее ставить через snap для всегда актуальной версии.

$ sudo snap install core
$ sudo snap refresh core

Сначала обновляется ядро snap-системы. Snap в Ubuntu 22.04 идёт из коробки, и эти команды просто гарантируют, что версия snapd актуальна.

$ sudo snap install --classic certbot

Флаг --classic нужен, потому что Certbot требует доступа к системным файлам за пределами своей песочницы, что не позволяет обычный режим snap.

$ sudo ln -s /snap/bin/certbot /usr/bin/certbot

Символическая ссылка делает Certbot доступным в стандартном PATH, чтобы можно было вызывать команду без указания полного пути.

Генерация сертификата идёт одной длинной командой.

$ sudo certbot certonly --nginx --agree-tos --no-eff-email --staple-ocsp --preferred-challenges http -m Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript. -d graphite.example.com

Параметр certonly означает только получение сертификата без автоматической настройки веб-сервера, потому что конфиг Nginx будет писаться руками. Флаг --nginx говорит Certbot использовать Nginx для проверки владения доменом. Параметр --agree-tos автоматически принимает условия использования Let's Encrypt, а --no-eff-email отказывается от рассылки от Electronic Frontier Foundation. Опция --staple-ocsp включает поддержку OCSP-степлинга для ускорения проверки отзыва сертификатов. Email через -m нужен для уведомлений о скором истечении срока действия. Домен через -d это собственно тот, для которого выпускается сертификат.

Дополнительно генерируется параметр Диффи-Хеллмана для усиления безопасности TLS.

$ sudo openssl dhparam -dsaparam -out /etc/ssl/certs/dhparam.pem 4096

Число 4096 это длина ключа в битах. Чем длиннее, тем безопаснее, но и генерация дольше. На слабых серверах этот шаг может занять минуты, на мощных секунды. Параметр -dsaparam ускоряет генерацию без существенной потери безопасности.

Сертификаты Let's Encrypt живут всего 90 дней, поэтому обновление должно быть автоматическим. Snap-версия Certbot уже настраивает таймер для этого.

$ sudo systemctl list-timers

В списке таймеров найдётся запись про обновление сертификата.

NEXT                        LEFT          LAST                        PASSED   UNIT                           ACTIVATES             

.................................................................................................................................
Wed 2022-09-28 00:00:00 UTC 7h left       Tue 2022-09-27 00:00:01 UTC 16h ago  logrotate.timer                logrotate.service
Wed 2022-09-28 02:39:09 UTC 10h left      Tue 2022-09-27 09:42:42 UTC 6h ago   apt-daily.timer                apt-daily.service
Wed 2022-09-28 06:02:00 UTC 13h left      n/a                         n/a      snap.certbot.renew.timer       snap.certbot.renew.service

Тестовый запуск обновления проверяет работу всей цепочки без реального переоформления.

$ sudo certbot renew --dry-run

Отсутствие ошибок в выводе означает, что обновление будет работать корректно в реальный момент.

Финальная конфигурация Nginx как обратного прокси с переадресацией HTTP на HTTPS

Когда сертификаты на руках, наступает время написать боевой конфиг Nginx для проксирования запросов к Apache. Сначала правится главный nginx.conf.

$ sudo nano /etc/nginx/nginx.conf

Перед строкой include /etc/nginx/conf.d/*.conf; добавляется параметр для увеличения размера хеш-таблицы имён серверов.

server_names_hash_bucket_size  64;

Это нужно для длинных доменных имён, иначе Nginx может ругаться на нехватку места в хеш-таблице при запуске.

Основной конфиг виртуального хоста кладётся отдельным файлом.

$ sudo nano /etc/nginx/conf.d/graphite.conf

Содержимое описывает полноценный HTTPS-сервер с прокси на Apache и редиректом с HTTP.

server {
    listen       443 ssl http2;
    listen       [::]:443 ssl http2;
    server_name  graphite.example.com;

    access_log  /var/log/nginx/graphite.access.log;
    error_log   /var/log/nginx/graphite.error.log;

	# SSL
    ssl_certificate      /etc/letsencrypt/live/graphite.example.com/fullchain.pem;
    ssl_certificate_key  /etc/letsencrypt/live/graphite.example.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/graphite.example.com/chain.pem;
    ssl_session_timeout  5m;
    ssl_session_cache shared:MozSSL:10m;
    ssl_session_tickets off;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    ssl_ecdh_curve X25519:prime256v1:secp384r1:secp521r1;
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_dhparam /etc/ssl/certs/dhparam.pem;
    resolver 8.8.8.8;

    location / {
       proxy_set_header Connection "upgrade";
       proxy_set_header Upgrade $http_upgrade;
       proxy_http_version 1.1;

       proxy_set_header Host $http_host;
       proxy_set_header X-Real-IP $remote_addr;
       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       proxy_set_header X-Forwarded-Proto $scheme;
       proxy_set_header X-NginX-Proxy true;

       proxy_pass http://127.0.0.1:8080;
       proxy_redirect off;
    }
}

# enforce HTTPS
server {
    listen       80;
    listen       [::]:80;
    server_name  graphite.example.com;
    return 301   https://$host$request_uri;
}

В этом конфиге много всего, и каждая директива играет роль. Прослушивание идёт одновременно на IPv4 и IPv6, что важно для современных серверов. Параметр http2 включает HTTP/2 для ускорения загрузки. Список ssl_ciphers это рекомендованный Mozilla набор шифров для современного уровня безопасности, исключающий устаревшие и небезопасные алгоритмы. Поддержка TLS только версий 1.2 и 1.3 убирает старые уязвимые версии. OCSP-степлинг через ssl_stapling ускоряет проверку валидности сертификата клиентом.

Блок location проксирует все запросы на Apache по адресу 127.0.0.1:8080. Заголовки proxy_set_header передают Apache реальную информацию о клиенте. Без X-Real-IP и X-Forwarded-For Apache видел бы все запросы как идущие с localhost, что ломало бы логирование и анализ доступа. Заголовки Connection upgrade и Upgrade нужны для поддержки WebSocket-соединений, если Graphite или плагины их используют.

Второй блок server отвечает за редирект с HTTP на HTTPS. Код 301 означает постоянное перенаправление, что говорит браузерам и поисковикам кешировать решение и сразу обращаться по HTTPS в будущем.

Проверка синтаксиса перед перезапуском обязательна.

$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Если в конфиге опечатка, nginx -t её найдёт и подскажет строку. Без этой проверки можно перезапустить Nginx с битым конфигом и получить неработающий сервис, что на боевой машине превратится в крупный инцидент.

$ sudo systemctl restart nginx

Перезапуск применяет новую конфигурацию, и Nginx начинает работать как HTTPS-прокси перед Apache.

Первый вход в Graphite и отправка тестовых метрик через сетевой протокол

После всех настроек можно открывать браузер и обращаться по адресу https://graphite.example.com. Открывается главная страница Graphite в фреймсетовом интерфейсе с заголовком сверху и областью композера снизу. Кнопка Login в правом верхнем углу ведёт на страницу входа, куда вводятся учётные данные созданного ранее суперпользователя.

Чтобы убедиться, что данные действительно поступают и отображаются на графиках, нужно отправить хотя бы одну тестовую метрику. Graphite принимает данные по простому текстовому протоколу через TCP-порт 2003, и это можно сделать обычной утилитой netcat.

$ echo "test.count 9 `date +%s`" | nc -q0 127.0.0.1 2003;

Формат сообщения предельно прост. Сначала имя метрики через точки иерархическое, потом значение, потом временная метка в формате Unix-времени. Команда date +%s выдаёт текущее время в нужном формате. Утилита nc отправляет это всё на локальный порт 2003, где слушает carbon-cache. Флаг -q0 завершает соединение сразу после отправки данных.

Для более интересной картинки можно отправить несколько значений в цикле с задержкой.

$ for i in 4 6 8 16 2; do echo "test.count $i `date +%s`" | nc -q0 127.0.0.1 2003; sleep 6; done

Цикл for проходит по списку чисел 4 6 8 16 2 и для каждого отправляет метрику с этим значением. Пауза sleep 6 между отправками даёт Carbon время на обработку и создание точек данных с правильным интервалом.

После отправки в веб-интерфейсе Graphite нужно открыть боковую панель Metrics, развернуть test и кликнуть на count. На графике появятся отправленные значения. С этого момента можно подключать к Graphite реальные источники метрик: collectd для системных показателей, statsd для счётчиков из приложений, диагностические скрипты собственного сочинения. Возможностей масса, и протокол приёма данных настолько прост, что его можно реализовать буквально в одну строчку на любом языке программирования.

Многие команды связывают Graphite с Grafana для получения красивого интерфейса дашбордов. Сам Graphite остаётся хранилищем и приёмником метрик, а Grafana берёт на себя визуализацию. Эта связка считается классической в мире мониторинга и встречается в инфраструктурах самых разных размеров, от маленьких стартапов до крупных корпораций с тысячами серверов.

Зачем компании вкладываться в развёртывание собственной системы хранения метрик и где Graphite раскрывается лучше всего

Освоение и внедрение системы мониторинга вроде Graphite это инвестиция, которая окупается не сразу, но окупается основательно. Без централизованного сбора метрик любая нетривиальная инфраструктура превращается в чёрный ящик, где о проблемах узнают от пользователей, а не от собственных систем наблюдения. Это плохой подход, который дорого обходится бизнесу в виде простоев и упущенной выручки.

Graphite не самый молодой инструмент в своём классе, и более новые системы вроде Prometheus или Victoria Metrics предлагают современные подходы вроде pull-модели сбора и продвинутого языка запросов. Но Graphite остаётся достойным выбором по нескольким причинам. Простота протокола приёма данных делает его универсальным приёмником, в который льют метрики десятки разных систем. Стабильность и проверенность временем дают уверенность, что инструмент не подкинет сюрпризов через год эксплуатации. И зрелая экосистема плагинов и интеграций позволяет решать самые разные задачи без необходимости писать всё с нуля.

Для команд, которые только начинают строить систему мониторинга, Graphite даёт пологий вход в тему. Базовая установка работает без сложных концепций, метрики льются в простом текстовом формате, графики строятся в браузере без программирования. Постепенно можно наращивать сложность, добавляя Grafana для дашбордов, alerting через сторонние инструменты, репликацию данных между несколькими серверами для отказоустойчивости.

Альтернативный путь это использование готовых облачных сервисов мониторинга вроде Datadog или New Relic. Они избавляют от необходимости поднимать собственную инфраструктуру, но при росте количества метрик счета быстро становятся неподъёмными. Самохостящаяся система требует начальных вложений времени, но при больших объёмах данных оказывается существенно дешевле в долгосрочной перспективе. Плюс полный контроль над данными остаётся в руках компании, что критично для регулируемых отраслей и работы с чувствительной информацией.

Знание принципов работы Graphite и подобных систем выходит за рамки одного инструмента. Понимание концепций временных рядов, ретенции данных, агрегации, протоколов передачи метрик это универсальные знания, которые пригодятся при работе с любой системой мониторинга. Инженер, разобравшийся с одной такой платформой основательно, без особого труда переключается на другие, потому что фундаментальные принципы везде одни и те же.