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

Задний план

MailSlurp — сервис для отправки и получения писем через REST. Он используется компаниями в своих наборах тестов и конвейерах развертывания, поэтому время безотказной работы имеет решающее значение.

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

Это был увлекательный процесс, поэтому мы решили поделиться тем, что узнали. В этом посте объясняется, как мы создали страницу статуса, используя Terraform, Codebuild, S3 и немного HTML и Javascript (VueJS и Bootstrap CDN). Это заняло у нас пару часов, но в конечном итоге сэкономило нам гораздо больше времени (и денег)!

Что такое страница статуса?

Страницы состояния обычно представляют собой простые веб-сайты, отображающие текущее или недавнее историческое состояние онлайн-сервиса или API. Многие технологические компании используют их для информирования клиентов о состоянии своих систем. Эти страницы состояния избавляют пользователей от любых сомнений относительно того, что может быть сломано (их код, их интернет или служба, на которую они полагаются?), и уменьшают количество входящих запросов в службу поддержки.

Платные услуги

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

Примеры страниц состояния (что мы создаем?)

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

Определение работоспособности службы

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

  • Можно ли получить доступ к целевой странице и API через HTTP-запрос?
  • Можно ли установить клиент SDK через NPM и может ли он выполнять запросы
  • Можно ли отправлять и получать электронные письма с помощью API (основная функция MailSlurp)

Этот дымовой тест написан на Javascript с использованием Jest и периодически запускается AWS CodeBuild. Если мы сможем зафиксировать результаты этих тестов и сохранить статус в S3, мы сможем получить этот статус в Javascript со статической страницы статуса HTML всякий раз, когда пользователь загружает ее!

Настройка CodeBuild

Я уже писал подробный пост о том, как планировать задания в CodeBuild, но при построении страницы состояния вы можете использовать любой инструмент CI для запуска своих тестов: CircleCI, Go.cd, Jenkins и т. д. Все, что вам нужно сделать, это обернуть тесты в сценарии, который может интерпретировать результаты и публиковать их в удаленном общедоступном хранилище. В MailSlurp мы широко используем AWS, поэтому CodeBuild и корзина S3 имеют смысл. Вот наш CodeBuild buildspec.yml для набора дымовых тестов MailSlurp.

version: 0.2
phases:
  build:
    commands:
      - yarn install
      - node run.js

Как видите, он просто устанавливает зависимости узла и выполняет скрипт run.js. Подробнее об этом ниже.

Обработка результатов теста

Наш набор тестов завершается с 0, если все тесты успешны, и >0, если какой-либо тест не пройден. Итак, давайте напишем скрипт с именем run.js, который выполняет тесты в дочернем процессе и обрабатывает результат. Позже мы захотим опубликовать результаты на S3, поэтому давайте сначала установим некоторые зависимости.

Вот зависимости для нашего средства запуска тестов Jest и для AWS SDK:

{
  "dependencies": {
    "aws-sdk": "^2.485.0",
    "jest": "^24.1.0"
  }
}

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

Теперь напишем run.js так, чтобы это:

  • порождает процесс
  • запускает тесты
  • перехватывает код выхода
  • отправляет код выхода как JSON на S3
const { spawnSync } = require('child_process')
const S3 = require('aws-sdk/clients/s3')
const s3Client = new S3()
// run jest tests in a process and handle failures
console.log("Running tests in subprocess")
const {
  status,
  stdout,
  stderr
} = spawnSync('npx', ['jest'], { ...process.env, CI: true })
// log the outputs from process if they exist
[stdout, stderr].forEach(output => {
  console.log((output || "").toString('utf8'))
})
// create json representation of test results plus epoch timestamp
const json = JSON.stringify({
  status,
  timestamp: new Date().getTime() / 1000
})
// specify s3 access and destination
const params = {
  ACL: "public-read",
  Body: json,
  Bucket: "mailslurp-public-status",
  Key: "status.json",
  ContentType: "application/json"
}
// upload json to s3
console.log("Uploading status to S3")
s3Client.putObject(params, (err, _) => {
  // if s3 client error then exit with error
  if (err) {
    console.error(err)
    process.exit(1)
  } else {
    // if successful 
    // exit with same exit code as the test process
    console.log("Upload successful")
    process.exit(status);
  }
})

Настройка S3 с помощью Terraform

Итак, теперь у нас есть репозиторий Smoketest CodeBuild, который обрабатывает результаты теста и отправляет их в виде JSON в корзину. Помните, что этот тест выполняется по расписанию, поэтому файл JSON будет обновляться каждые десять минут, чтобы отразить состояние системы.

Одна маленькая проблема — ведра еще не существует! Нам нужно создать ведро, чтобы скрипт успешно работал. Вы можете сделать это вручную, но Terraform — это действительно хороший способ управления инфраструктурой. Вот как вы это делаете (обратите внимание, что вам нужно будет настроить поставщика Terraform):

resource "aws_s3_bucket" "status" {
  bucket = "mailslurp-public-status"
  acl    = "public-read"
cors_rule {
    allowed_headers = ["*"]
    allowed_methods = ["GET"]
    allowed_origins = ["*"]
    expose_headers  = ["ETag"]
    max_age_seconds = 3000
  }
}

Теперь запустите terraform apply, чтобы применить ресурсы к вашей учетной записи AWS. Если вы откроете консоль AWS в веб-браузере, вы должны увидеть созданную вами корзину.

После создания ведра вы можете запустить скрипт узла и просмотреть результаты через общедоступный URL-адрес. В данном случае https://mailslurp-public-status.s3-us-west-2.amazonaws.com/status.json.

{"status":0,"timestamp":1562022690.971}

Ухой! Почти готово. Теперь HTML для отображения этих данных в более удобном для пользователя виде.

HTML-код страницы состояния

Чтобы отобразить JSON для пользователей, нам нужно создать простой статический веб-сайт и немного Javascript для получения результатов. Давайте начнем с базовой разметки HTML5 и включим Bootstrap, jQuery и VueJS через CDN. Вы можете сделать все это без каких-либо библиотек или зависимостей, но ради простоты и скорости мы выбрали эти вспомогательные библиотеки, размещенные на CDN.

Помните, что мы хотим, чтобы эта страница состояния была простой — мы не хотим перебарщивать и создавать полноценную PWA с тестами и шагами сборки. Нам нужен один файл. jQuery поможет с кросс-браузерными AJAX-запросами, а VueJS упростит шаблонирование результата. Bootstrap заставит его выглядеть красиво.

Вот выдержка из HTML. Обратите внимание на использование CDN, чтобы избежать необходимости управлять этими библиотеками и развертывать их.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>MailSlurp Status</title>
  <!-- jQuery -->
  <script src="https://code.jquery.com/jquery-3.4.1.slim.js"></script>
  <!-- VueJS -->
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
  <!-- bootstrap -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
</head>
<body>
    <!-- we will put markup and javascript here -->
</body>
</html>

Получение данных о состоянии во внешнем интерфейсе

Чтобы получить результаты статуса теста, нам просто нужно получить JSON из созданной нами корзины. Для поддержки старых браузеров мы будем использовать jQuery. Давайте посмотрим, как это выглядит.

$.getJSON("https://mailslurp-public-status.s3-us-west-2.amazonaws.com/status.json", function (data) { /* todo */ });

Итак, чтобы использовать этот вызов AJAX, давайте создадим элемент Vue и сопоставим результаты с нашим HTML.

<script>
  new Vue({
    el: '#root',
    data: function () {
      return {
        loading: true,
        status: null,
        timestamp: null
      }
    },
    created: function () {
      var _this = this;
      $.getJSON("https://mailslurp-public-status.s3-us-west-2.amazonaws.com/status.json", function (data) {
        _this.status = data.status;
        _this.timestamp = data.timestamp;
        _this.loading = false;
      })
    }
  })
</script>

И вот немного HTML для отображения результатов. Он использует классы Bootstrap для макета и стиля. Идентификатор #root сопоставляется со свойством el Vue и позволяет Vue отслеживать и обновлять элементы внутри него.

Мы добавили заголовок H1 и сообщение о загрузке с v-if="loading". Мы будем отображать это сообщение до тех пор, пока не получим результат и не решим, что с ним делать.

Обратите внимание, что другие элементы v-else и v-else-if имеют атрибут v-cloak. Это специальный атрибут, который Vue удалит из управляемых элементов после загрузки. Нам решать, когда Vue не удалит этот тег. Таким образом, правило CSS, такое как [v-cloak]{ display: none }, гарантирует, что элементы с этим атрибутом не будут видны до запуска Vue, и сможет их скрыть (из-за их логики v-else).

<div id="root" class="container py-5">
  <h1 class="h1 mb-5">MailSlurp Status</h1>
  <!-- loading -->
  <p class="lead" v-if="loading">Loading...</p>
<!-- 0 exit code means tests passed -->
  <div v-else-if="status === 0" v-cloak>
    <div class="alert alert-success">
      <h4 class="alert-heading">All clear!</h4>
      <p class="mb-0">All systems are functioning. If you need help in any way please contact support</p>
    </div>
  </div>
<!-- anything else indicates an error -->
  <div v-else v-cloak>
    <div class="alert alert-danger">
<h4 class="alert-heading">Service disruptions!</h4>
      <p class="mb-0">Some MailSlurp systems are failing, please hold tight. Developers are looking into the
        issue. If it persists please contact support.</p>
    </div>
  </div>
<div class="pt-3">
    <a href="https://www.mailslurp.com">Home</a>
    <span>|</span>
    <a href="https://docs.mailslurp.com">Documentation</a>
    <span>|</span>
    <a href="https://twitter.com/@mailslurp">Twitter</a>
    <span>|</span>
    <a href="[email protected]">Support</a>
  </div>
</div>
<style>
  [v-cloak] {
    display: none;
  }
</style>

Наконец, мы включаем некоторые ссылки и контактную информацию.

Довольно просто! Если мы откроем HTML-код в браузере, он должен выглядеть примерно так:

Развертывание HTML в CDN и домене

Теперь, когда мы можем отображать статус в HTML, мы хотим развернуть его в CDN, чтобы любой мог легко получить доступ и просмотреть состояние нашей системы. Также нам нужна поддержка https и удобное доменное имя для нашего сайта. status.mailslurp.com подойдет.

Мы снова можем использовать Terraform для создания веб-сайта S3 и дистрибутива CloudFront CDN. Затем мы можем создать запись псевдонима Route53 для дистрибутива CloudFront и прикрепить существующий сертификат SSL для домена MailSlurp.

Там немного кода, но шаги просты.

  • Создайте ресурс веб-сайта корзины S3 для HTML
  • Создайте дистрибутив CloudFront для указанной корзины и прикрепите наш сертификат верхнего уровня.
  • Создайте запись Route53 A для этого дистрибутива с именем status.mailslurp.com, чтобы CDN был легко доступен.

Примечание. Развертывание ресурсов CDN Terraform иногда занимает до часа. Просто позвольте ему делать свое дело, оно в конце концов закончится.

# create a bucket for the status html
resource "aws_s3_bucket" "status_html" {
  bucket = "status.mailslurp.com"
  acl = "public-read"
  website {
    index_document = "index.html"
    error_document = "404.html"
  }
  policy = <<EOF
{
  "Version": "2008-10-17",
  "Statement": [
    {
      "Sid": "PublicReadForGetBucketObjects",
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::status.mailslurp.com/*"
    }
  ]
}
EOF
}
# enter a route 53 record for our cdn
resource "aws_route53_record" "status_cdn_alias" {
  zone_id = "${var.zone_id}"
  name    = "status.mailslurp.com"
  type    = "A"
alias {
    name = "${aws_cloudfront_distribution.status_cdn.domain_name}"
    zone_id = "${aws_cloudfront_distribution.status_cdn.hosted_zone_id}"
    evaluate_target_health = false
  }
}
# create the cdn for caching and ssl
resource "aws_cloudfront_distribution" "status_cdn" {
  origin {
    custom_origin_config {
      http_port              = "80"
      https_port             = "443"
      origin_protocol_policy = "http-only"
      origin_ssl_protocols   = ["TLSv1", "TLSv1.1", "TLSv1.2"]
    }
domain_name = "${aws_s3_bucket.status_html.website_endpoint}"
    origin_id   = "status.mailslurp.com"
  }
enabled             = true
  is_ipv6_enabled     = true
  comment             = "Managed by Terraform"
  default_root_object = "index.html"
aliases = ["status.mailslurp.com"]
default_cache_behavior {
    allowed_methods = ["HEAD", "DELETE", "POST", "GET", "OPTIONS", "PUT", "PATCH"]
cached_methods = [
      "GET",
      "HEAD",
    ]
target_origin_id = "status.mailslurp.com"
forwarded_values {
      query_string = true
cookies {
        forward = "none"
      }
    }
viewer_protocol_policy = "redirect-to-https"
    min_ttl                = 0
    default_ttl            = 3600
    max_ttl                = 86400
    compress               = true
  }
price_class = "PriceClass_100"
restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }
viewer_certificate {
    # mailslurp certificate arn
    acm_certificate_arn = "${var.certificate_arn}"
    ssl_support_method  = "sni-only"
  }
}

Публикация HTML в нашей корзине

Последний шаг — отправить HTML-код нашей страницы состояния в корзину status_html, которую мы создали с помощью Terraform. Есть много способов сделать это, но интерфейс командной строки AWS упрощает задачу.

aws s3 cp index.html s3://status.mailslurp.com

Завершение и тестирование

Вот и все. У нас есть все необходимое для статусной страницы. Давайте вспомним, как это работает.

  • Запланированный набор тестов выполняется CodeBuild каждые десять минут.
  • Набор тестов заключен в скрипт Node, который собирает результаты и публикует их в формате JSON в корзину S3.
  • Мы создали корзину S3 для хранения статусов в Terraform
  • Мы создали HTML-страницу, которая использует Bootstrap, Vue и jQuery для извлечения и рендеринга сохраненного JSON из корзины.
  • Мы создали запись псевдонима Route53, CDN и веб-сайт S3 для страницы состояния HTML и развернули ее с помощью интерфейса командной строки AWS.

Теперь давайте намеренно запустим тесты, чтобы увидеть, обновляется ли наша страница состояния…

И вуаля. После провала тестов и загрузки https://status.mailslurp.com мы видим оповещение о сбое в обслуживании на MailSlurp.

Вывод

Страница состояния сообщает клиентам о состоянии службы, чтобы разработчики могли сосредоточиться на устранении проблем в трудные времена. Хороший способ проверить работоспособность службы/API — сквозные тесты. MailSlurp — это API для создания реальных адресов электронной почты через REST и отправки и получения от них. Вы можете использовать эти электронные письма в своих тестах, чтобы регистрироваться с уникальными учетными записями, проверять коды подтверждения oauth, анализировать транзакционную почту и многое другое. Проверь!