継続は力なり

タイトル通り定期的な更新を心掛けるブログです。

Mackerel のカスタムプラグインを自作して FastAPI 関連プロセスを監視してみる

タダです.

Mackerel のプラグインには様々なものが既に用意されていますが,そのプラグインにないものがあれば作ってみようということで,FastAPI を動かすために必要な Uvicorn のプロセスを監視するプラグインを作ってみたのでこの記事でその模様をまとめます.

プラグインの種類

プラグインには2つの種類があり,メトリックプラグインとチェックプラグインがあります.前者はサーバーリソースの値をメトリック値を取得,送信するためのもので CPU やメモリなどが標準のサーバーのメトリックとしてありますが,標準のメトリック以外のものはカスタムメトリックとしてグラフに描画されます.後者はホスト上の特定プロセスの死活状況をチェックしたりするときに使い,メトリックプラグインのようにグラフの描画はないです.

メトリックプラグインとチェックプラグインを作る

それではメトリックプラグインとチェックプラグインを作っていきます.

メトリックプラグインを作る

Uvicorn のプロセスは uvicorn main:app --reload のように実行しても1プロセスしかあがらないようで,ドキュメント にも本番環境では gunicorn と一緒に使うことが推奨されているため下記のようなコマンドを実行したとします.

gunicorn example:app -w [ワーカー数] -k uvicorn.workers.UvicornWorker

www.uvicorn.org

メトリックプラグインのコードとして次のものを用意しました.メトリックプラグインのインターフェース仕様で {metric name}\t{metric value}\t{epoch seconds}というフォーマットで標準出力されることが求められているためスクリプトの最終行で echo -e "${metric_name}\t${metric}\t${date}" を行っている以外は大したことしてないかと思います.スクリプトの実行権限を chmod +x uvicorn-metrics.sh を与えることも忘れずにやりましょう.

uvicorn-metrics.s

#!/bin/sh

metric_name="uvicorn.test_metric.number"
metric=`ps aux |grep uvicorn | grep -v grep | wc -l`
date=`date +%s`

echo -e "${metric_name}\t${metric}\t${date}"

そして,/etc/mackerel-agent/mackerel-agent.conf にも下記の定義を追加して sudo systemctl restart mackerel-agent で再起動します.

[plugin.metrics.uvicorn]
command = "/xxx/xxx/uvicorn-metrics.sh"

数分待つと,メトリックのデータが送られてきてるのを確認できました.Uvicorn の8つのプロセスが記録されていることが確認できました.

f:id:sadayoshi_tada:20210630073845p:plain

チェックプラグインを作る

次にチェックプラグインをつくっていきますが,メトリックプラグイン同様にスクリプトとして次のようなものを用意しました.正常なプロセスの数はメトリックプラグインの記録された値として8を入れてその時は正常ですが,それ以外の値になったら Critical になるように設定しています(チェックプラグインの仕様はドキュメント に記載があります).スクリプトの実行権限を chmod +xuvicorn-ps-check.shで与えます.

uvicorn-ps-check.sh

#!/bin/sh

count=`ps aux |grep uvicorn | grep -v grep | wc -l`
if [ $count -eq 8(正常なプロセスの数) ]; then
  exit 0
else
  exit 2
fi

そして,/etc/mackerel-agent/mackerel-agent.conf にも下記の定義を追加して sudo systemctl restart mackerel-agent で再起動します.

[plugin.checks.uvicorn]
command = "/xxx/xxx/uvicorn-ps-check.sh"

数分待つと,Monitors のセクションに uvicorn が監視されていることを確認できました.

f:id:sadayoshi_tada:20210630074619p:plain

試しにプロセスを落としてアラートが出るかを確認してみます.

$ gunicorn main:app -w 8 -k uvicorn.workers.UvicornWorker
~中略~
[2021-06-29 22:20:44 +0000] [22251] [INFO] Waiting for application startup.
[2021-06-29 22:20:44 +0000] [22251] [INFO] Application startup complete.
^C[2021-06-29 22:47:40 +0000] [22246] [INFO] Handling signal: int
~中略~
[2021-06-29 22:47:40 +0000] [22246] [INFO] Shutting down: Master

Critical 扱いでアラートが出ることが確認できました.プロセスが落ちてもその状況をモニタリングできるようになりました.

f:id:sadayoshi_tada:20210630074922p:plain

まとめ

今回の開発で a-know (id:a-know)さんの記事を参考に Uvicorn のプロセス監視のプラグインを作っていきました.a-know さんありがとうございます! Go でも作っていきたいけどまずはさくっとシェルスクリプトプラグインを作れる体験を持てたのはとても大きかったです.こっからプラグイン開発に入り込んで行けたらなと思います.

blog.a-know.me

AWS Chatbot から Lambda の発火させて手動作業を自動化する

タダです.

業務で不定期ではあるけれど定型作業の依頼が非エンジニアから飛んでくる時があり,最初はすぐに済ませるため手作業で行っていたタスクを仕組み化しようと思い,AWS Chatbot を入れているので Chatbot と Lambda を使って作業を省力化したのでこの記事にまとめます.

作業の省力化のモチベーション

この取り組みのモチベーションとしては大きく2点あります.1つ目は定型作業が仕組み化できてないことです.もう1つは非エンジニアから依頼があったときにエンジニアに依頼するより非エンジニア側の作業で完結できるようにした方が効率的と考えたことです.

仕組みの検討

仕組みをどのようにしようかと考えたときにSlack でコミュニケーションをとっているため Slack で完結するようなものにしたいと思って Chatbot を使っていこうと考えました.Chatbot から Lambda を呼び出すことができるのでこの仕組みをつかっていきます.

docs.aws.amazon.com

Chatbot から Lambda を呼び出すための設定

Chatbot から Lambda を呼び出すための設定として①Chatbot の IAM ロール設定②Lambda のコードを準備します.

①Chatbot の IAM ロール設定

Chatbot の IAM ロールで Lambda を呼び出すための権限を足します.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "lambda:invokeAsync",
                "lambda:invokeFunction"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

docs.aws.amazon.com

②Lambda のコード

Lambda は CloudFront のキャッシュを無効化する処理をさせるための関数です.Slack から CloudFront の DistributionId が渡ってくるので event のパラメーターとしてとってます.

import json
import boto3
import logging
import time

logger = logging.getLogger()
logger.setLevel(logging.INFO)


def lambda_handler(event, context):
    cf = boto3.client('cloudfront')
    distribution_id = event['distribution_id']
    logger.info('Creating invalidation')
    res = cf.create_invalidation(
        DistributionId=distribution_id,
        InvalidationBatch={
            'Paths': {
                'Quantity': 1,
                'Items': ['/*'],
            },
            'CallerReference': str(time.time())
            
        }
    )

    invalidation_id = res['Invalidation']['Id']
    logger.info('InvalidationId is %s', invalidation_id)

    logger.info('Execution complete')
    return "Complete."

以上で準備が完了です.

Slack から Lambda を発火させる

aws のボットを対象のチャンネルに招待して@aws invoke Lambda名 --payload {“distribution_id”: “DistributionId”}を実行します.すると,Would you like me to do so?とでるのでYesを押せば Lambda が発火します.StatusCodePayloadの部分も処理が完了していることを確認できました.CloudFront の画面でもログに出ている InvalidationId でキャッシュ無効化処理が完了していることも確認できたので完了です.

Slack の実行画面

f:id:sadayoshi_tada:20210626173703p:plain

Lambda の実行ログ

START RequestId: aef1bac9-a44b-4a8c-b07c-14554732243a Version: $LATEST
[INFO]  2021-06-14T03:23:52.584Z    aef1bac9-a44b-4a8c-b07c-14554732243a    Found credentials in environment variables.
[INFO]  2021-06-14T03:23:53.323Z    aef1bac9-a44b-4a8c-b07c-14554732243a    Creating invalidation
[INFO]  2021-06-14T03:23:54.304Z    aef1bac9-a44b-4a8c-b07c-14554732243a    InvalidationId is I1CA2NN7EAW1TR(CloudFront の InvalidationId)
[INFO]  2021-06-14T03:23:54.322Z    aef1bac9-a44b-4a8c-b07c-14554732243a    Function complete
END RequestId: aef1bac9-a44b-4a8c-b07c-14554732243a

CloudFront の Invalidations 画面

f:id:sadayoshi_tada:20210627105250p:plain

まとめ

Chatbot から Lambda を呼び出す処理を実装するためにやったことを整理しました.Slack でコミュニケーションをしている場合にオペレーション省力化の1つとしてこう言った手法が取れることをしれてよかったです.同じような課題を持っている方の参考になれば嬉しいです!

Nginx + Puma + Sinatra のコンテナを ECS Fargate で起動する時にハマったことをまとめる

タダです.

業務で Nginx + Puma + Sinatra の構成を ECS Fargate で動かそうとしてハマったのでこの記事でその辺をまとめていきます.

ハマったこと

Nginx と Sinatra のコンテナを別々で立ち上げた時に Nginx -> Puma へのソケット通信が通らず,connect() to unix:/xxx/xxx/sockets/puma.sock failed (2: No such file or directory) while connecting to upstream というメッセージが出ていました.

ステータスは画像のように Running になるけど...っていう状況でした

f:id:sadayoshi_tada:20210618141654p:plain

Nginx と Puma の関連設定は以下の指定をし,指定した場所でソケット通信が通るよう意図したはずなのに何故だとハマっていました.

Nginx 設定

http {
 ~中略~

    upstream hoge{
            server unix:/xxx/xxx/sockets/puma.sock;
    }
    server {
        listen  80;
        listen 443;
     
        access_log /var/log/nginx/access.log;
        error_log  /var/log/nginx/error.log;
    
        server_name hoge.com;

        location / {
            try_files $uri @fuga;
        }

        location @fuga {
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_redirect off;
            proxy_pass http://hoge;
            proxy_set_header Host $proxy_host;
        }
    }
}

Puma の設定

root = File.expand_path("../..", __FILE__)

bind "unix:#{root}/xxx/sockets/puma.sock"
pidfile "#{root}/xxx/pids/puma.pid"
state_path "#{root}/xxx/pids/puma.state"
rackup "#{root}/config.ru"
threads 10, 20
activate_control_app

設定漏れがあった箇所

設定が漏れていたのが Sinatra のコンテナイメージビルド時と ECS のタスク定義の設定でした.

Sinatra コンテナイメージビルド時のミス

ビルド時のミスはソケット通信時アクセスするディレクトリをマウントできていなかったため,Nginx のコンテナから参照できないでいました.そのため,ソケット通信を行うディレクトリを VOLUME で指定しました.

Dockerfile 例

FROM ruby:2.7.2

WORKDIR /xxx

RUN mkdir -p /xxx/xxx/sockets/
RUN mkdir -p /xxx/xxx/pids/
RUN mkdir -p /xxx/xxx/log/

COPY . /xxx/

RUN bundle install -j4

VOLUME /xxx/xxx <= これが不足していた
CMD bash -c "cd /xxx/app && bundle exec pumactl start" 

ECS のタスク定義の設定

上述の誤りと関連し,ECS タスク定義の Nginx のコンテナでボリュームマウントする Sinatra コンテナを指定します.

docs.aws.amazon.com

タスク定義のコンテナ設定抜粋

  {
    "name": "nginx",
    "image": "nginx-repo",
    "portMappings": [
        {
            "containerPort": 80,
            "hostPort": 80,
            "protocol": "tcp"
        }
    ],
    "essential": true,
    "volumesFrom": [
        {
            "sourceContainer": "sinatra"
        }
    ],
    "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
            "awslogs-group": "/ecs/hoge-task",
            "awslogs-region": "ap-northeast-1",
            "awslogs-stream-prefix": "ecs"
        }
    }
  }

設定の誤りを修正後のデプロイ確認

2点の修正を行った後に ECS へコンテナをデプロイして確認したところconnect() to unix:/xxx/xxx/sockets/puma.sock failed (2: No such file or directory) while connecting to upstream のメッセージは消えて,Nginx -> Puma -> Sinatra への通信が通りました.

f:id:sadayoshi_tada:20210618145728p:plain

まとめ

初歩的なミスですが,苦戦してしまったので備忘録として記事にまとめました.同じ事象に当たった方の参考になれば嬉しいです!

コンテナ・サーバーレスアプリケーションデプロイのマネージドサービス『AWS Proton』をサンプルを動かして理解する

タダです.

AWS Proton」が GA したので,使ってみたいと思いサンプルプロジェクトを使ってみてこの記事に所感などまとめていきます.

AWS Proton とは

AWS Proton」はコンテナやサーバーレスアプリケーションのためのフルマネージド型アプリケーションデプロイサービスです.特徴としてインフラを管理するチーム(プラットフォーム)と開発チームとの協調のために効きそうなサービスなのかと想像します.プラットフォームを管理するチームは Environment Template で共有リソースを作りと Service Template でインフラ,監視,CI/CD を定義するテンプレートを作ります.開発チームは Service Templateを使ってデプロイ をするような形です.

プラットフォームチームは AWS Proton を使用して、コンピューティング、ネットワーキング、コードパイプライン、セキュリティ、監視などのサービスをプロビジョン、デプロイ、監視に必要となるすべてを定義する「スタック」を作成します。デベロッパーは AWS Proton コンソールにログインし、発行済みの AWS Proton スタックを使用してインフラストラクチャのプロビジョニングを自動化し、アプリケーションコードをすばやくデプロイします。

aws.amazon.com

AWS Proton のサービスページより引用 f:id:sadayoshi_tada:20210613211228p:plain

料金

AWS Proton」を使う時は料金かかりません.

利用リージョン

記事執筆時点(2021/06/13)の利用可能なリージョンとしては,バージニア/オハイオ/オレゴン/東京/アイルランドで利用できる状況です.

AWS Proton のサンプルを動かしてみる

ドキュメントに沿ってマネジメントコンソールからサンプルを試してみます.

docs.aws.amazon.com

1. Environment Template を作る

まず,Environment Template を作ります.ECS Fargate のテンプレートが用意されているので選択し,テンプレート名とテンプレートの表示名を設定し,他はデフォルト設定で進みます.

f:id:sadayoshi_tada:20210614010414p:plain f:id:sadayoshi_tada:20210614010423p:plain

テンプレートができたらテンプレートを公開する必要があるので,公開します.公開できると Publishedになります.

f:id:sadayoshi_tada:20210614010438p:plain f:id:sadayoshi_tada:20210614010434p:plain

2. Service Template を作る

次に,Service Templateを作ります.Environment Template同様に ECS Fargate のテンプレートがあるので選んで,テンプレート名とテンプレートの表示名を設定し, 互換性のあるテンプレートして項番1で作ったEnvironment Templateの表示名を選び,他はデフォルト設定で進みます.

f:id:sadayoshi_tada:20210614010841p:plain f:id:sadayoshi_tada:20210614010846p:plain f:id:sadayoshi_tada:20210614010941p:plain

テンプレートができたらテンプレートを公開する必要があるので,公開します.公開できると Publishedになります.

f:id:sadayoshi_tada:20210614011102p:plain f:id:sadayoshi_tada:20210614011056p:plain

3. Environment を作る

Environment Templateができたので,Environmentを作っていきます.ドキュメントの記載に沿って設定していきます.

f:id:sadayoshi_tada:20210614011228p:plain f:id:sadayoshi_tada:20210614011220p:plain f:id:sadayoshi_tada:20210614011215p:plain

Environment の設定が完了すると CloudFormation が走り,VPC/Subnet/Internet Gateway/Security Group/ECS Cluster/ECS Task Execution Role が出来上がります.

f:id:sadayoshi_tada:20210614011701p:plain

4. Service を作る

次はService Templateを使ってServiceを作っていきます.ドキュメントの記載に沿って設定していきます.リポジトリへの接続部分はサンプルリポジトリを fork して設定しています.

f:id:sadayoshi_tada:20210614011829p:plain f:id:sadayoshi_tada:20210614011836p:plain f:id:sadayoshi_tada:20210614011859p:plain f:id:sadayoshi_tada:20210614011909p:plain

Serviceの設定が完了すると,ECS のサービスや ALB リソースの他に CodePipeline/CodeBuild といった CI/CD リソースが作られていきました.

f:id:sadayoshi_tada:20210614012255p:plain f:id:sadayoshi_tada:20210614012312p:plain

ALB の DNS 名にアクセスすると Nginx のページが出るところまで確認できました.

f:id:sadayoshi_tada:20210614012510p:plain

一通りの確認が終わったら,全てのリソースを削除しておきましょう.

まとめ

AWS Proton」のサンプルを触ってみることでどんなサービスなのかを理解してみることをやってみました.正直まだまだわからないところが多いのでサンプルプロジェクトを読み解いて少しずつ理解していければと思うのとマルチアカウントのサポートも入っているため理解して業務でも組み込める場所があったら検討していきたいです.

今回のリリースの一環として、このサービスに追加される 2 つの新機能をお知らせします。1 つ目として、AWS Proton は、マルチアカウントインフラストラクチャをサポートするようになりました。この新機能により、プラットフォームオペレーターは AWS Proton を使用して、複数の AWS アカウント間でアーキテクチャを安全に設定および管理できます。2 つ目の機能リリースは、AWS Proton API の IAM 条件コンテキストキーのサポートです。この追加の管理レイヤーにより、オペレーターは、テンプレートの特性に基づいてサービスを作成できるデベロッパーを指定できます。

aws.amazon.com

Mackerel の『式による監視』を試した

タダです.

Mackerel の機能で自分が指定した式を使った監視ルールや独自のメトリクスをダッシュボード に表示できる 「式による監視」があります.これは実験的機能と呼ばれ,正式機能ではないものの先行公開されています.

mackerel.io mackerel.io

下記の記事を見た時から気になってて,通常のメトリクスでは見えないデータを表示できるのでどんな感じで使えるかを試してみました.この記事ではその内容をまとめます.

mackerel.io

式による監視の特徴

式による監視」は Mackerel の Monitors -> 監視ルールを追加 -> 式による監視 から追加できます. 監視対象以下の式の部分に自分で監視したいルールを定義し,閾値を設定します.監視ルールとして使える関数はこちらで参照可能です.

f:id:sadayoshi_tada:20210611060214p:plain

監視の追加画面

f:id:sadayoshi_tada:20210611061755p:plain f:id:sadayoshi_tada:20210611061807p:plain

仕様として次のものがあります.

監視間隔は5分ごとになります

監視項目として設定可能な式は、グラフの系列が1本になるものだけになります

式よってメトリック値が取得できない・式が複雑すぎてタイムアウトする、などの場合は、ステータスがUnknownとしてアラートが発生します

監視項目の上限数は20です

Trialプラン及び有料プランでのみご利用いただけます

ALB のターゲットグループのリクエスト成功率を監視してみる

式による監視」機能を試すものとして冒頭のはてなさんの記事を参考にして, ALB の(2xxと返したアクセス数) / ((2xxと返したアクセス数 + (5xxと返したアクセス数))という監視ルールを設定し,閾値を超えた場合 Slack に投稿することをしてみます.5分間隔でチェックし,ALB のターゲットグループで 5XX系エラーが出てシステムの可用性に影響が出た時にわかります.

監視式

scale(
    divide(
      host(
        <hostid>,
        custom.alb.httpcode_count.target_2xx
      ),
      sum(
        group(
          host(
            <hostid>,
            custom.alb.httpcode_count.target_2xx
          ),
          host(
            <hostid>,
            custom.alb.httpcode_count.target_5xx
          )
        )
      )
    ),
    100
)

稼働率が100%から99.99%以下になったら Warning で 99.97 %以下になったら Critical のようなを作ってみました.

f:id:sadayoshi_tada:20210611105857p:plain

実際に 99.97%以下の計算になった場合がポストされたのが下記の画像になります.計算上 99.78% になったため Critical になっています.

f:id:sadayoshi_tada:20210611103851p:plain

まとめ

Mackerel の「式による監視」機能を試してみたので簡単にやり方をまとめてみました.式として他にも様々な関数が用意されているので収集したメトリックから他にも適宜必要に応じたデータを見えるようにしていきたいです!