Building a Graphite Server on Debian 11

Trying to build a Graphite server with custom location for install. The steps in the Graphite documentation are not working for me.
It installs most of the things in /usr/local and some other paths. So I came up with some other steps of installing it and it seems to be working well until now.

The installtion will be in /data/graphite on a dedicated drive. It will use Python virtual environment at that location.

Using

  • Debian 11
  • Python 3.9 (from debian)
  • Postgres 13 for Django database
  • Graphite installed with pip from the git repo master

Installing and configuring postgres

apt install postgresql postgresql-client postgresql-contrib

Changing the password for the postgres database user:

su - postgres
psql -d template1 -c "ALTER USER postgres WITH PASSWORD 'PostgresPassword';"

/etc/postgresql/13/main/pg_hba.conf

# To allow remote connection
host    all         all         192.168.122.0/24    trust
host    all         all         192.168.123.0/24    trust

/etc/postgresql/13/main/postgresql.conf

Adding

listen_addresses = '*'

Then

systemctl enable postgresql
systemctl start postgresql && systemctl status postgresql
pg_isready

Creating the database for graphite-web

su - postgres 
psql
CREATE DATABASE graphite;
CREATE USER graphite WITH PASSWORD 'GraphiteDBPassword';
ALTER ROLE graphite SET client_encoding TO 'utf8';
ALTER ROLE graphite SET default_transaction_isolation TO 'read committed';
ALTER ROLE graphite SET timezone TO 'America/Toronto';
GRANT ALL PRIVILEGES ON DATABASE graphite TO graphite;

Installing dependencies for graphite

apt install python3-dev python3-pip libcairo2-dev libffi-dev build-essential

Installing graphite

apt install git -y
apt install python3-venv
cd /data
python3 -m venv graphite
cd graphite
source bin/activate
pip install wheel

Installing psycopg2

apt install libpq-dev

pip install psycopg2
mkdir downloads
cd downloads
git clone https://github.com/graphite-project/whisper.git
git clone https://github.com/graphite-project/carbon.git
git clone https://github.com/graphite-project/graphite-web.git

Modifying the setup.py files for carbon and graphite-web.

For carbon setup.py commenting out

if os.environ.get('GRAPHITE_NO_PREFIX'):
    cf.remove_section('install')
else:
    print('#' * 80)
    print('')
    print('Carbon\'s default installation prefix is "/opt/graphite".')
    print('')
    print('To install Carbon in the Python\'s default location run:')
    print('$ GRAPHITE_NO_PREFIX=True python setup.py install')
    print('')
    print('#' * 80)
    try:
        cf.add_section('install')
    except DuplicateSectionError:
        pass
    if not cf.has_option('install', 'prefix'):
        cf.set('install', 'prefix', '/opt/graphite')
    if not cf.has_option('install', 'install-lib'):
        cf.set('install', 'install-lib', '%(prefix)s/lib')

with open('setup.cfg', 'w') as f:
    cf.write(f)

For graphite-web setup.py file commenting out

if os.environ.get('GRAPHITE_NO_PREFIX') or os.environ.get('READTHEDOCS'):
    cf.remove_section('install')
else:
    try:
        cf.add_section('install')
    except DuplicateSectionError:
        pass
    if not cf.has_option('install', 'prefix'):
        cf.set('install', 'prefix', '/opt/graphite')
    if not cf.has_option('install', 'install-lib'):
        cf.set('install', 'install_lib', '%(prefix)s/webapp')

with open('setup.cfg', 'w') as f:
    cf.write(f)

Then from within the downloads directory, installing all three components

pip install ./whisper
pip install ./carbon
pip install ./graphite-web

In webapp directory, creating a symlink to graphite

cd ../webapp
ln -s /data/graphite/lib/python3.9/site-packages/graphite graphite

Configuring carbon

Working in /data/graphite/conf directory

cd ../conf
cp carbon.conf.example carbon.conf
cp storage-schemas.conf.example storage-schemas.conf
cp storage-aggregation.conf.example storage-aggregation.conf
cp relay-rules.conf.example relay-rules.conf

Creating user and group carbon

groupadd -g 497 carbon
useradd -c "carbon user" -g 497 -u 497 -s /bin/false carbon

carbon.conf

LOCAL_DATA_DIR = /data/graphite/storage/whisper/

Creating a startup script for carbon

For systemd

/etc/systemd/system/carbon-cache.service

[Unit]
Description=Graphite Carbon Cache
After=network.target

[Service]
Type=forking
ExecStart=/data/graphite/bin/carbon-cache.py --config=/data/graphite/conf/carbon.conf --pidfile=/var/run/carbon-cache.pid start
ExecReload=/bin/kill -USR1 $MAINPID
PIDFile=/var/run/carbon-cache.pid

[Install]
WantedBy=multi-user.target

Configuring graphite-web

pip install gunicorn

systemd files

# This is /etc/systemd/system/graphite-web.socket
[Unit]
Description=graphite-web socket

[Socket]
ListenStream=/run/graphite-api.sock
ListenStream=127.0.0.1:8080

[Install]
WantedBy=sockets.target
# This is /etc/systemd/system/graphite-web.service
[Unit]
Description=graphite-web service
Requires=graphite-web.socket

[Service]
ExecStart=/data/graphite/bin/gunicorn wsgi --pythonpath=/data/graphite/webapp/graphite --bind 127.0.0.1:8080
Restart=on-failure
#User=graphite
#Group=graphite
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
PrivateTmp=true

[Install]
WantedBy=multi-user.target

local_settings.py

(graphite) root@graphite-01:/data/graphite# cd ../webapp/graphite
(graphite) root@graphite-01:/data/graphite/webapp/graphite# cp local_settings.py.example local_settings.py
SECRET_KEY = 'secretkey'
ALLOWED_HOSTS = [ '*' ]
TIME_ZONE = 'America/Toronto'

LOG_ROTATION = True
LOG_ROTATION_COUNT = 1
LOG_RENDERING_PERFORMANCE = True
LOG_CACHE_PERFORMANCE = True

GRAPHITE_ROOT = '/data/graphite'

CONF_DIR = '/data/graphite/conf'
STORAGE_DIR = '/data/graphite/storage'
STATIC_ROOT = '/data/graphite/static'
LOG_DIR = '/data/graphite/storage/log/webapp'
INDEX_FILE = '/data/graphite/storage/index'

DASHBOARD_CONF = '/data/graphite/conf/dashboard.conf'
GRAPHTEMPLATES_CONF = '/data/graphite/conf/graphTemplates.conf'

CERES_DIR = '/data/graphite/storage/ceres'
WHISPER_DIR = '/data/graphite/storage/whisper'
RRD_DIR = '/data/graphite/storage/rrd'
STANDARD_DIRS = [WHISPER_DIR, RRD_DIR]

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'graphite',
        'USER': 'graphite',
        'PASSWORD': 'GraphiteDBPassword',
        'HOST': 'localhost',
        'PORT': '',
    }
}
mkdir /data/graphite/storage/whisper
mkdir /data/graphite/storage/rrd
mkdir /data/graphite/storage/ceres
mkdir /data/graphite/storage/index
mkdir /data/graphite/static

Doing migrations

(graphite) root@graphite-01:/data/graphite/webapp/graphite# cd ../../
(graphite) root@graphite-01:/data/graphite# pwd
/data/graphite
(graphite) root@graphite-01:/data/graphite#

(graphite) root@graphite-01:/data/graphite# mkdir -p storage/log/webapp
(graphite) root@graphite-01:/data/graphite# touch storage/log/webapp/info.log
(graphite) root@graphite-01:/data/graphite# django-admin migrate --settings=graphite.settings

Installing and configuring nginx

apt install nginx -y
touch /var/log/nginx/graphite.access.log
touch /var/log/nginx/graphite.error.log
chmod 640 /var/log/nginx/graphite.*
chown www-data:www-data /var/log/nginx/graphite.*

/etc/nginx/sites-available/graphite

upstream graphite {
    server 127.0.0.1:8080 fail_timeout=0;
}

server {
    listen 80 default_server;

    server_name graphite-01.marthemed.org 192.168.122.120;

    root /data/graphite/webapp;

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

    location = /favicon.ico {
        return 204;
    }

    # serve static content from the "content" directory
    location /static {
        alias /data/graphite/webapp/content;
        expires max;
    }

    location / {
        try_files $uri @graphite;
    }

    location @graphite {
        proxy_pass_header Server;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Scheme $scheme;
        proxy_connect_timeout 10;
        proxy_read_timeout 10;
        proxy_pass http://graphite;
    }
}
ln -s /etc/nginx/sites-available/graphite /etc/nginx/sites-enabled
rm -f /etc/nginx/sites-enabled/default
systemctl restart nginx

Other steps

systemctl daemon-reload
systemctl enable carbon-cache
systemctl enable graphite-web
systemctl enable nginx

Testing

Let's do some test using graphitesend

mkdir testing_graphite
cd testing_graphite/
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install wheel
pip install graphitesend

storage-schemas.conf already has a catch-all definition

[default]
pattern = .*
retentions = 60s:1d,5m:30d,1h:3y

Using some scripts (inspired from the examples from graphitesend)

import time
import graphitesend
import socket

server_hostname = socket.gethostname()

g = graphitesend.init(prefix="testing", \
    system_name=server_hostname, \
    group='loadavg_', \
    suffix='min', \
    graphite_server='192.168.122.120', \
    graphite_port=2003)

while True:
    (la1, la5, la15) = open('/proc/loadavg').read().strip().split()[:3]
    print(g.send_dict({'1': la1, '5': la5, '15': la15}))
    time.sleep(1)
import time
import graphitesend
import socket

server_hostname = socket.gethostname()

g = graphitesend.init(prefix="testing", \
    system_name=server_hostname, \
    group='netstat.', \
    lowercase_metric_names=True, \
    graphite_server='192.168.122.120', \
    graphite_port=2003)

while True:
    lines = open('/proc/net/netstat').readlines()
    tcp_metrics = lines[0].split()[1:]
    tcp_values = lines[1].split()[1:]
    ip_metrics = lines[2].split()[1:]
    ip_values = lines[3].split()[1:]

    data_list = zip(tcp_metrics + ip_metrics, tcp_values + ip_values)
    print(g.send_list(data_list))
    time.sleep(1)