Рубрики
Без рубрики

Мониторинг времени работы приложения с python, nuxt.js и questdb

Высокодействующие услуги, которые обслуживают миллионы запросов на видимость состояния системы … Теги с QuestDB, Python, NUXT, Fastapi.

Высокодействующие услуги, которые обслуживают миллионы запросов на видимость состояния системы для клиентов и внутренних команд. В этом руководстве показано, как легкая база данных серии Time-Series в сочетании с проверками состояния в очереди, и простые пользовательские ингредиенты являются ключевыми ингредиентами для надежного мониторинга приложений.

Зачем строить страницу состояния для приложения?

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

Хотя ответственность страницы заключается в предоставлении информации, она может снизить нагрузку на службу поддержки и устранить дублирующие билеты в поддержку. Страницы статуса являются важной частью управления инцидентами, и, как правило, другие команды пользуются преимуществами, такими как владельцы клиентов и услуг, когда им нужно обратиться к SLA. В этом уроке я покажу вам, как построить простую еще мощную страницу состояния, которая хорошо забивает производительность и дизайн.

Что мы будем строить

Обзор

Как уже упоминалось выше, мы создадим простую страницу состояния из двух частей: Backend контролирует наш сервис, а интерфейс показывает статус наших услуг в почасовой масштабе.

Вам понадобится опыт работы в Python, JavaScript и Basic SQL знания. Чтобы построить наш сервис, мы будем использовать Fastapi, Ultra-Fast Python Web Framework, Celerry для задач мониторинга планирования, QuestDB, самые быстрые базы данных Time-Series с открытым исходным кодом, чтобы хранить результаты мониторинга и nuxtjs для их отображения.

Есть много, чтобы учиться, так что давайте прыгнем прямо в!

Предварительные условия

Вам нужно будет установить следующее на вашем компьютере:

  • Python 3.8.
  • Nodejs 14+
  • Докер
  • Docker Compose

Настройка среды

Создать новый проект

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

status-page (project root)
└── app (backend service directory)

Установка Questdb & redis

Теперь мы установим QuestDB и Redis. QuestDB используется для хранения состояния HTTP и состояния службы нашего приложения со временем, а Redis используется в качестве брокера сообщений между Backend Application и работниками, которые будут делать запланированные мониторинг.

Чтобы установить эти услуги, мы будем использовать Docker и Docker Compose. Мы собираемся создать Docker-Compose.yml Файл в пределах корня проекта со следующим контентом:

version: '3'

volumes:
  questdb_data: {}

services:
  redis:
    image: 'redis:latest'
    ports:
      - '6379:6379'

  questdb:
    image: 'questdb/questdb:latest'
    volumes:
      # Map QuestDB's data directory to the host
      - 'questdb_data:/root/.questdb/db'
    ports:
      - '9000:9000'
      - '8812:8812'

Вуаля! Когда мы бежим Docker-Compose up , QuestDB и Redis начнут, и мы можем получить доступ к интерактивной консоли Questdb на http://127.0.0.1:9000. .

Установите зависимости от бэкэнд

Теперь у нас есть структура проекта, и мы можем запускать необходимые услуги, поэтому нам нужно создать нашу бэкэндовую службу для сбора данных о веб-сайте или услуге, которую мы хотели бы отслеживать. Мы будем использовать поэзию для управления зависимостями Python во время этого руководства, поэтому давайте начнем с установки этого.

$ pip install poetry

Чтобы определить требования проекта, создать pyproject.toml Файл со следующим контентом:

[tool.poetry]
name = "status-page"
version = "0.1.0"
description = "QuestDB tutorial for creating a simple status page."
authors = ["Your name "]
license = "MIT"

[tool.poetry.dependencies]
python = "3.8"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

Установите зависимости проекта, выполняя следующее:

poetry add python fastapi pydantic uvicorn requests \
 psycopg2-binary "databases[postgresql]" "celery[redis]"

Как вы можете предположить, проверяя требования, мы будем использовать интерфейс Postgres QuestDB для подключения. Когда Поэзия Заканчивает свою работу, он добавит зависимости в pyproject.toml И теперь мы можем начать реализовать сервис Backend.

Создать простую API

Пришло время, давайте создадим бэкэндскую службу, но пошаговый. В пределах приложение каталог, создать __init__.py и main.py Отказ Первый несет ответственность за создание приложение Справочник к пакету, в то время как последний определит API нашего сервиса обнародован. Открыть main.py Для редактирования и добавления следующего:

# main.py

from fastapi import FastAPI

app = FastAPI(
    title="Status Page",
    description="This service gives back the status of the configured URL.",
    version="0.1.0",
)

Поздравляю! Вы только что создали сервис Backend. Вы можете пойти и попробовать, выполнив

$ poetry run uvicorn app.main:app --host 127.0.0.1 --port 8000 --reload
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
...
INFO:     Application startup complete.

Хотя услуга пока ничего не делает, он работает и слушает любые изменения кода. Добавьте новую конечную точку и просмотреть его перезагрузить:

# main.py

# ...

@app.get(path="/signals", tags=["Monitoring"])
async def get_signals():
  return {}

Теперь мы создали конечную точку API, которая будет служить данным состояния системы контролируемого URL. Если вы откроете http://127.0.0.1:8000/redoc. , вы можете увидеть созданную документацию для конечной точки, или вы можете проверить его работать в http://127.0.0.1:8000/signals. Хотя это еще не вернет никаких данных.

Настало время повеселиться, мы собираемся интегрировать QuestDB с нашей блестящей новой Backend Service.

Интегрировать QuestDB с Fastapi

Интеграция QuestDB с Fastapi проще, чем вы думаете. Благодаря Questdb’s Совместимость Postgres , вы можете использовать любые стандартные или популярные сторонние библиотеки любого языка программирования, который реализует протокол проволоки Postgres.

Настроить таблицу

Самый первый шаг – создать таблицу в QuestDB. Как уже говорилось, наш подход простой, так что стол тоже проста. QuestDB работает из нашего Socker Compose Script, поэтому мы открываем интерактивную консоль в http://127.0.0.1:9000 И создайте новую таблицу, запустив следующий запрос:

CREATE TABLE
    signals(url STRING, http_status INT, received TIMESTAMP, available BOOLEAN)
    timestamp(received);

Запрос выполняется, и после обновления списка таблиц слева, вы можете увидеть созданную таблицу.

Подключите Questdb и fastapi

Поскольку у нас есть таблица в базе данных, пришло время подключиться к QuestDB и запрашивать некоторые данные для возврата через API. Для подключения мы будем использовать интерфейс Postgres of QuestDB и SQLalchemy для подключения к нему.

Чтобы иметь возможность повторно использовать двигатель позже, создайте новый файл в приложение Пакет, который отвечает за определение того, как подключиться и назвать его db.py :

# db.py

from sqlalchemy import create_engine

engine = create_engine(
    "postgresql://admin:quest@127.0.0.1:8812/qdb", # Use a the default credentials
    pool_size=5, # Set pool size greater than 1 to not block async requests
    pool_pre_ping=True # Set pre-ping to ensure a connection is opened when sending a query
)

Чтобы настроить схему, которая представляет таблицу в базе данных, создать Models.py Содержащие определение схемы:

# models.py

from datetime import datetime
from pydantic import BaseModel, Schema


class Signal(BaseModel):
    url: str = Schema(..., description="The monitored URL")
    http_status: int = Schema(..., description="HTTP status code returned by upstream")
    available: bool = Schema(..., description="Represents the service availability")
    received: datetime = Schema(..., description="Timestamp when the signal received")

Давайте остановимся здесь на мгновение и поговорим через то, что мы сделали на последних шагах:

  • Настройте API, которая будет служить запросам, исходящим из интерфейса
  • Создан таблица в QuestDB для наших записей статуса и предоставленные учетные данные подключения для провода Postgres
  • Реализована схема, которая используется для сериализации результатов, возвращаемых базой данных

Следующим шагом является инициировать соединение и вернуть результаты из базы данных. Во-первых, импортировать двигатель и Сигнал Схема, а затем продлить функцию, которая служит /сигналы Конечная точка:

# main.py

# Other imports ...
from app.db import engine
from app.models import Signal

from typing import List
from pydantic import BaseModel

class SignalResponse(BaseModel):
    url: str
    records: List[Signal]

После добавления defaultdict Импорт, реализация /сигналы Конечная точка должна выглядеть так:

# main.py

# Other imports ...
from collections import defaultdict

# ...

@app.get(path="/signals", response_model=List[SignalResponse], tags=["Monitoring"])
async def get_signals(limit: int = 60):

    # A simple query to return every record belongs to the website we will monitor
    query = f"""
    SELECT * FROM signals
    WHERE url = 'https://questdb.io' ORDER BY received DESC LIMIT {limit};
    """

    signals = defaultdict(list)

    with engine.connect() as conn: # connect to the database
        for result in conn.execute(query): # execute the SELECT query
            signal = Signal(**dict(result)) # parse the results results returned by QuestDB
            signals[signal.url].append(signal) # add every result per URL

    # Return the response which is validated against the `response_model` schema
    return [
        SignalResponse(url=url, records=list(reversed(records)))
        for url, records in signals.items()
    ]

Давайте повторимся на нашем коде выше, начиная с вершины:

  • Мы добавили defaultdict Импортировать (Мы объясним это позже)
  • расширенный декоратор функции для использования Ответ_model = список [SignalResponse] модель ответа мы уже определили
  • изменил функцию подписи, чтобы включить Ограничить Параметр и установите значение по умолчанию на 60 Так как мы будем контролировать состояние HTTP каждую минуту
  • Выберите записи из базы данных и приготовьте словарь для Parsed Сигнал s.

Вы можете спросить, почему группировать возвращенные записи на URL. Хотя мы будем следить за простотой только одним URL-адресом, я бросаю вызов вам изменить реализацию позже и исследовать QuestDB для обработки мониторинга нескольких URL.

В следующих строках мы подключаемся к базе данных, выполняя запрос и заполняет словарь, который мы будем использовать в последних четырех строках, чтобы построить SignalResponse Отказ Наша версия main.py На данный момент выглядит следующее:

# main.py

from collections import defaultdict
from typing import List

from fastapi import FastAPI
from pydantic import BaseModel

from app.db import engine
from app.models import Signal


# Add a response model to indicate the structure of the signals API response.
class SignalResponse(BaseModel):
    url: str
    records: List[Signal]


app = FastAPI(
    title="Status Page",
    description="This service gives back the status of the configured URL.",
    version="0.1.0",
)

@app.get(path="/signals", response_model=List[SignalResponse], tags=["Monitoring"])
async def get_signals(limit: int = 60):

    # A simple query to return every record belongs to the website we will monitor
    query = f"""
    SELECT * FROM signals
    WHERE url = 'https://questdb.io' ORDER BY received DESC LIMIT {limit};
    """

    signals = defaultdict(list)

    with engine.connect() as conn: # connect to the database
        for result in conn.execute(query): # execute the SELECT query
            signal = Signal(**dict(result)) # parse the results results returned by QuestDB
            signals[signal.url].append(signal) # add every result per URL

    # Return the response which is validated against the `response_model` schema
    return [
        SignalResponse(url=url, records=list(reversed(records)))
        for url, records in signals.items()
    ]

Задачи мониторинга расписания

Для планирования задачи мониторинга мы будем использовать избиение сельдерея, встроенный периодический планировщик задач. Реализация сельдерей.

Планирование с сельдереем

Прежде чем напланировать любую задачу, нам нужно настроить сельдерей. В приложение Пакет, создайте новый Celery.py который будет содержать конфигурацию расписания сельдерея и удара. Импорт Сельдерей Для создания задач и Crontab Для построения Unix-подобных Crontabs для наших задач. Задача является пунктирным путем представления функции, которая выполняется сельдереем ( app.tasks.monitor ) и отправляется в очереди, обрабатываемые Redis.

Единственное, что осталось – настроить график удара, который является простым словарем. Мы даем имя для расписания, определить пунктирный путь, указывающий на задачу (функцию), и укажите сам расписание:

# celery.py

from celery import Celery
from celery.schedules import crontab

MONITORING_TASK = "app.tasks.monitor"

celery_app = Celery("tasks", broker="redis://localhost:6379/0")

# Set a queue for task routes
celery_app.conf.task_routes = {
  MONITORING_TASK: "main-queue"
}

# Schedule the monitoring task
celery_app.conf.beat_schedule = {
    "monitor": { # Name of the schedule
        "task": MONITORING_TASK, # Register the monitoring task
        "schedule": crontab(
            minute=f"*/1" # Run the task every minute
        ),
    }
}

Создать задачу мониторинга

И последняя часть: создание задачи мониторинга. В предыдущем разделе мы говорили о «задании мониторинга» несколько раз, но мы не видели конкретную реализацию.

В этом разделе «Окончательный раздел», связанный с Backenc, вы реализуете задачу, которая проверит доступность желаемого веб-сайта или услуги и сохраняет результаты в качестве записей в QuestDB. Задача мониторинга простая Http head. Запрос и сохранение ответа на базу данных. Мы видим реализацию на кусках Tasks.py упоминается в сельдерее как пунктирной путь раньше.

Во-первых, начнем с импорта:

# tasks.py

from datetime import datetime

import requests

from app.celery import celery_app
from app.db import engine
from app.models import Signal

# ...

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

# tasks.py

# ...

@celery_app.task # register the function as a Celery task
def monitor():
    try:
        response = requests.head("https://questdb.io")
    except Exception as exc: # handle any exception which may occur due to connection errors
        query = f"""
            INSERT INTO signals(received,url,http_status,available)
            VALUES(systimestamp(), 'https://questdb.io', -1, False);
            """

        # Open a connection and execute the query
        with engine.connect() as conn:
            conn.execute(query)

        # Re-raise the exception to not hide issues
        raise exc

# ...

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

# ...

@celery_app.task
def monitor():
    # ...

        signal = Signal(
        url="https://questdb.io",
        http_status=response.status_code,
        received=datetime.now(),
        available=response.status_code >= 200 and response.status_code < 400,
    )

    # ...

Здесь мы не делаем ничего особенного, хотя на следующий шаг интереснее: вставка результата в базу данных. Наконец, мы готовимся и выполняем запрос на основе сигнал Отказ

# ...

@celery_app.task
def monitor():
    # ...

    query = f"""
    INSERT INTO signals(received,url,http_status,available)
    VALUES(systimestamp(), '{signal.url}', {signal.http_status}, {signal.available});
    """

    with engine.connect() as conn:
        conn.execute(query)

Поздравляю! Вы только что прибыли в последнюю часть внедрения Backend Service. Мы сделали много вещей и построили услугу, которая может периодически проверять состояние веб-сайта, сохранить его в базе данных и подвергать результаты через API.

Самое последнее, что нам нужно обратиться, состоит в том, чтобы разрешить соединения, инициированные позже позже. Поскольку он будет работать на localhost: 3000, и мы не используем доменные имена, порт другой, следовательно, все запросы будут отклонены с ошибками, связанными с Ресурс с перекрестным происхождением обмена Отказ

Чтобы решить эту проблему, добавьте следующее промежуточное программное обеспечение на приложение, которое позволит нам подключиться к http://localhost: 3000 :

# main.py

# Other imports ...
from fastapi.middleware.cors import CORSMiddleware

# app = FastAPI ...

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# ...

Внедрить интерфейс

Настройка Frontend

Чтобы построить интерфейс, мы будем использовать nuxt.js. Мы будем использовать пряжа Чтобы настроить стартовый проект, запустив пряжа и выбирая ответы, подробно описанные ниже.

$ yarn create nuxt-app frontend

[...]

? Project name: frontend
? Programming language: JavaScript
? Package manager: Yarn
? UI framework: Tailwind CSS
? Nuxt.js modules: Axios
? Linting tools: (Press  to select,  to toggle all,  to invert selection)
? Testing framework: None
? Rendering mode: Single Page App
? Deployment target: Static (Static/JAMStack hosting)
? Development tools: (Press  to select,  to toggle all,  to invert selection)
? What is your GitHub username? gabor-boros
? Version control system: Git

Корень проекта теперь выглядит так:

status-page
├── app/
├── frontend/
├── docker-compose.yml
├── poetry.lock
└── pyproject.toml

Уборка сгенерированного проекта

Поскольку нам не нужен какой-либо стиль, доставляемый генерацией проекта, нам нужно избавиться от них. Открыть Интернет/макеты/default.vue и заменить его содержание с




Теперь мы изменим Frontend/Страницы/index.vue и позвоните в Backend Service. Давайте начнем с <скрипты> Отказ







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

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

Последняя часть – определить периодический звонок на бэкэнду, когда компонент является монтируется Отказ Правильно, у нас есть логика, которая позвонит Backend и сохранить данные на сегодняшний день. Теперь мы должны отображать результаты.






Запустить проект

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

# Shell 1 - Start the application
$ docker-compose up -d
$ poetry run uvicorn app.main:app --host 127.0.0.1 --port 8000 --reload

# Shell 2 - Start the worker process
$ poetry run celery --app=app.tasks worker --beat -l info -Q main-queue -c 1

# Shell 3 - Start the frontend
$ cd frontend
$ yarn dev

Перейдите к http://localhost: 3000 Чтобы увидеть бэкэнд сообщить о состоянии мониторинга URL. Первая задача для проверки состояния системы выполняется при запуске планировщика и работника и Состояние сайта со временем можно увидеть через несколько минут на странице или при проверке на более позднем этапе:

Резюме

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

Спасибо за Ваше внимание!

Контейнерный исходный код доступен в https://github.com/gabor-boros/questdb-statuspage .

Оригинал: “https://dev.to/gaborboros/monitoring-the-uptime-of-an-application-with-python-nuxt-js-and-questdb-16p3”