Postfix のサンドボックス環境をローカルに作る

この記事は「はてなエンジニア Advent Calendar 2022」の 23 日目の記事です。 昨日は id:onishi さんで はてなのエンジニア×人事の取り組み - 大西ブログ でした。

訳あって Postfix を勉強しています。

Postfix 実用ガイド などを参考に Postfix を動作させてみようと思ったのですが、インターネットを経由した場合のメールの送受信は準備や設定が大変そうでした。このため、メールの送受信などをローカルで試せる Postfix サンドボックス環境を作ってみることにしました。

サンドボックス環境の概要

  • リポジトリhiroygo/postfix-local です
  • docker compose でメール送信用 Postfix コンテナとメール受信用 Postfix コンテナを起動します
  • メール送信用コンテナのホスト名を send.localhost、メール受信用コンテナのホスト名を recv.localhost としてます。2 つのコンテナは共通の docker network で接続されているため、互いのコンテナを名前解決できます
  • メールの送信では、メール送信用コンテナ内で Postfix sendmail コマンドを実行します。これによりメールがメール送信用コンテナからメール受信用コンテナに送信されます。送信者は root@send.localhost で、受信者は root@recv.localhost です

メールを送受信してみる

make send-mail でメールを送信して、make show-recv-mail で受信したメールを確認できます。もし root ユーザ(root@recv.localhost)以外に送信したい場合はユーザの追加が必要になります。ログは make log-sendmake log-recv で確認できます。

% make up 
...
  
% make send-mail 
...
  
% make show-recv-mail
docker compose exec -it recv.localhost cat /var/spool/mail/root
From root@send.localhost  Sat Dec 10 08:10:53 2022
Return-Path: <root@send.localhost>
X-Original-To: root@recv.localhost
Delivered-To: root@recv.localhost
Received: from send.localhost (unknown [172.22.0.3])
    by recv.localhost (Postfix) with ESMTPS id A34C812EF3F
    for <root@recv.localhost>; Sat, 10 Dec 2022 08:10:53 +0000 (UTC)
Received: by send.localhost (Postfix, from userid 0)
    id 95DFA12EF02; Sat, 10 Dec 2022 08:10:53 +0000 (UTC)
From: root@send.localhost
To: root@recv.localhost
Subject: this is subject
Message-Id: <20221210081053.95DFA12EF02@send.localhost>
Date: Sat, 10 Dec 2022 08:10:53 +0000 (UTC)

this is body.

% make log-send 
docker compose logs send.localhost
postfix-local-send.localhost-1  | Dec 10 08:10:29 send postfix/postfix-script[102]: starting the Postfix mail system
postfix-local-send.localhost-1  | Dec 10 08:10:29 send postfix/master[103]: daemon started -- version 3.5.13, configuration /etc/postfix
postfix-local-send.localhost-1  | Dec 10 08:10:53 send postfix/pickup[104]: 95DFA12EF02: uid=0 from=<root>
postfix-local-send.localhost-1  | Dec 10 08:10:53 send postfix/cleanup[114]: 95DFA12EF02: message-id=<20221210081053.95DFA12EF02@send.localhost>
postfix-local-send.localhost-1  | Dec 10 08:10:53 send postfix/qmgr[105]: 95DFA12EF02: from=<root@send.localhost>, size=307, nrcpt=1 (queue active)
postfix-local-send.localhost-1  | Dec 10 08:10:53 send postfix/smtp[116]: 95DFA12EF02: to=<root@recv.localhost>, relay=recv.localhost[172.22.0.2]:25, delay=0.06, delays=0.01/0.02/0.02/0.01, dsn=2.0.0, status=sent (250 2.0.0 Ok: queued as A34C812EF3F)
postfix-local-send.localhost-1  | Dec 10 08:10:53 send postfix/qmgr[105]: 95DFA12EF02: removed

サンドボックス環境は役に立ったか

master デーモン だけでなく、smtpsmtpd の動作やログも確認できて、勉強になりました。ただサンドボックス環境の構築が思ったより大変で、構築のために試行錯誤してたら、だいぶ Postfix の勉強ができてしまった感があります...

細かいこと

  • Postfix 公式の Docker イメージはなさそうだったので、普通に apt-get install してます
  • メール送信用コンテナ、メール受信用コンテナとも main.cf はほぼデフォルトです。変更点は以下を見てもらえるとわかります
  • recv.localhost 用の MX レコードの設定は不要です。Postfix はメール送信時、 MX レコードが無いと A レコードから、送信先を決定するっぽいです
  • send/run.sh
    • メール送信用コンテナでは、このスクリプトから Postfixmaster デーモン を起動します
    • コンテナ終了時は shutdown_handler でシグナルを受け取り postfix stop で master デーモンと、その子プロセスを終了させます。master デーモンのソースコードをみた感じ、子プロセスの終了を待機してなさそうなので、スクリプト側で待機処理を入れています
    • Postfix は名前解決に /var/spool/postfix/etc/resolv.conf を使うので、Postfix 起動前に /etc/resolv.con をコピーしています。/etc/resolv.conf はコンテナ起動時に書き換えられるため、コンテナ起動後にコピーします。コピーではなくシンボリックリンクでもいいと思います

はてなエンジニア Advent Calendar 2022、明日は id:yutailang0119 さんです!

「プロフェッショナルSSL/TLS」を読んだ

SSL/TLS が全然分からなかったので、会社の人からおすすめされた プロフェッショナルSSL/TLS を読みました。

体系的に解説されていて、説明も丁寧です。とても面白かったです。
基礎知識としては 第1章 SSL/TLS と暗号技術, 第2章 プロトコル, 第3章 公開鍵基盤 を読めばよさそうです。
あとは 第11章 OpenSSL, 第12章 OpenSSLによるテスト で OpenSSL の使い方を知れてよかったです。 (第11章、第12章は無料で読めるみたいです!『OpenSSLクックブック』提供開始のお知らせ – 技術書出版と販売のラムダノート )

学習の補助として参考にしたページがいくつかあるので紹介しておきます。

AWS Lambda with Go のハンドラ

AWS Lambda を Go で実装するとき、ハンドラを lambda.Start() に渡して実行することが多いと思います。1
lambda.Start() が受け取るハンドラの型は interface{} です。これは empty interface なのでどんな型でも入れることができますが、実際には守らないといけない条件があります。

package main
 
import (
        "fmt"
        "github.com/aws/aws-lambda-go/lambda"
)

func myHandler() {
        fmt.Println("hello")
}
 
func main() {
        // ハンドラを lambda.Start() に渡す
        lambda.Start(myHandler)
}

ハンドラのシグネチャ

AWS Lambda function handler in Go - AWS Lambda で説明されているように lambda.Start() に渡すハンドラは関数であり、以下のシグネチャである必要があります。 シグネチャを満たさない場合、Lambda 実行時にエラーが発生します

func ()
func () error
func (TIn) error
func () (TOut, error)
func (TIn) (TOut, error)
func (context.Context) error
func (context.Context, TIn) error
func (context.Context) (TOut, error)
func (context.Context, TIn) (TOut, error)

ハンドラの引数

シグネチャにも書いてあるように、ハンドラは引数 TIn を受け取れます。 これにより CloudWatch event などから受け取ったデータをハンドラに渡すことが出来ます。 公式サンプルのハンドラ では events.CloudWatchEvent を受け取っています。

// CloudWatchEvent is the outer structure of an event sent via CloudWatch Events.
// For examples of events that come via CloudWatch Events, see https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/EventTypes.html
type CloudWatchEvent struct {
    Version    string          `json:"version"`
    ID         string          `json:"id"`
    DetailType string          `json:"detail-type"`
    Source     string          `json:"source"`
    AccountID  string          `json:"account"`
    Time       time.Time       `json:"time"`
    Region     string          `json:"region"`
    Resources  []string        `json:"resources"`
    Detail     json.RawMessage `json:"detail"`
}

func handler(ctx context.Context, event events.CloudWatchEvent) {
    fmt.Printf("Detail = %s\n", event.Detail)
}

TInjson.Unmarshal() に対応している必要があります。 これは外部からの JSON データが []byte から TInjson.Unmarshal() され、ハンドラに渡されるためです。 2
先ほどの events.CloudWatchEventJSON と構造体のフィールドを対応させる tag がついていて、 json.Unmarshal() に対応しています。

外部からの JSON データがハンドラに渡されるまでの流れは以下のページが詳しいです。
https://www.mo4tech.com/aws-lambda-for-go.html

jsonエンコード・デコードついては以下のページが詳しいです。
https://zenn.dev/hsaki/articles/go-convert-json-struct

ハンドラの戻り値

ハンドラは (error) または (TOut, error) の形で結果を返せます。 TOutTIn とは逆に json.Marshal() に対応している必要があります。 3

公式サンプル では (events.APIGatewayProxyResponse, error) で返しています。

// Handler is your Lambda function handler
// It uses Amazon API Gateway request/responses provided by the aws-lambda-go/events package,
// However you could use other event sources (S3, Kinesis etc), or JSON-decoded primitive types such as 'string'.
func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {

    // stdout and stderr are sent to AWS CloudWatch Logs
    log.Printf("Processing Lambda request %s\n", request.RequestContext.RequestID)

    // If no name is provided in the HTTP request body, throw an error
    if len(request.Body) < 1 {
        return events.APIGatewayProxyResponse{}, ErrNameNotProvided
    }

    return events.APIGatewayProxyResponse{
        Body:       "Hello " + request.Body,
        StatusCode: 200,
    }, nil

}

AWS-CDK 本体がどうやってユーザが書いたスタックを読み込んでいるか調べた

最近、仕事で AWS-CDK を触っています。 AWS-CDK ではユーザは以下のようにスタックを書いて、AWS リソースを作成します。

import * as cdk from '@aws-cdk/core';
import * as s3 from '@aws-cdk/aws-s3';

export class CdkTestStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    new s3.Bucket(this, 'Bucket', {});
  }
}

npm run cdk diffnpm run cdk deploy すると、AWS-CDK 本体(cdk コマンド) が実行されますが、その際どうやってユーザが書いたスタックを読み込んでいるのか 気になったので調べてみました。

AWS-CDK は v1.146.0 を使いました。 調査には Visual Studio CodeJavaScript Debug Terminal を使いました。

npm run cdk diff 実行時

diff() の動作

この関数で cdk diff の結果を表示しています。重要そうなコードだけ抜き出します。実装は diff() を参照してください。

  public async diff(options: DiffOptions): Promise<number> {
     const stacks = await this.selectStacksForDiff(options.stackNames, options.exclusively);
     //...
           // Compare N stacks against deployed templates
           for (const stack of stacks.stackArtifacts) {
             stream.write(format('Stack %s\n', chalk.bold(stack.displayName)));
             const currentTemplate = await this.props.cloudFormation.readCurrentTemplateWithNestedStacks(stack);
             diffs += options.securityOnly
               ? numberFromBool(printSecurityDiff(currentTemplate, stack, RequireApproval.Broadening))
               : printStackDiff(currentTemplate, stack, strict, contextLines, stream);
           }
      // ...
  }

ユーザが書いたスタックの読み込み

  • selectStacksForDiff() を追っていくと execProgram() にたどり着きます
  • readCurrentTemplateWithNestedStacks() で deploy 済みのスタックを取得します
  • printStackDiff() で deploy 済みのスタックと cdk.out の差分を表示します

npm run cdk deploy 実行時

deploy() を簡単に見てみましたが、処理の流れは diff() と似たような感じだったです

スカイダイビングを体験してきました

2021/12/04 に栃木県の 藤岡スカイダイビングクラブ でスカイダイビングを体験してきました。

藤岡駅からタクシーでスカイダイビング場に行きました。 f:id:arrowislandnai:20220106005633j:plain

スカイダイビング場はこんな感じでした。 f:id:arrowislandnai:20211209225235j:plain

スタッフさんが飼っている「りょーま」くんです。 f:id:arrowislandnai:20211209225311j:plain

かわいい。 f:id:arrowislandnai:20211209225414j:plain

ジャンプスーツを着たら、このセスナで高度約 3800m まで行き、インストラクターさんと一緒にジャンプします。 f:id:arrowislandnai:20211209225727j:plain f:id:arrowislandnai:20220106011029p:plain

セスナからの景色です。体験中はインストラクターさんが GoPro で撮影してくれます。 f:id:arrowislandnai:20211209225752p:plain

3800m まで来たらジャンプします。セスナから飛び出す瞬間が一番怖かったです。 f:id:arrowislandnai:20211209225823p:plain

そのまま約 60 秒間落下します。 風圧が凄くて、あまり息ができなかったです。浮遊感はなかったです。 f:id:arrowislandnai:20211209225838p:plain

その後パラシュートを開いて、約 5 分間降下します。無事に着地できました。 f:id:arrowislandnai:20211209225853p:plain

恐怖でジャンプできるか、かなり不安だったんですが、思ったほど怖くなかったです。 とても良い思い出になりました。皆さんもぜひスカイダイビングしてみてください!

LVM で論理ボリュームを作成する

Vagrantfile

検証に Vagrant を使います。
1GB のディスクを 2 つ追加しています。
この 2 つのディスクを使い、論理ボリュームを作成します。

# -*- mode: ruby -*-
# vi: set ft=ruby :

ENV["VAGRANT_EXPERIMENTAL"] = "disks"

Vagrant.configure("2") do |config|
  config.vm.box = "bento/ubuntu-18.04"
  config.vm.disk :disk, size: "1GB",  name: "disk-0"
  config.vm.disk :disk, size: "1GB",  name: "disk-1"
end

ディスクの確認

vagrant@vagrant:~$ lsblk 
NAME                   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda                      8:0    0   64G  0 disk 
└─sda1                   8:1    0   64G  0 part 
  ├─vagrant--vg-root   253:0    0   63G  0 lvm  /
  └─vagrant--vg-swap_1 253:1    0  980M  0 lvm  [SWAP]
sdb                      8:16   0    1G  0 disk 
sdc                      8:32   0    1G  0 disk 

sdb にパーティションを作成

vagrant@vagrant:~$ sudo fdisk /dev/sdb

Welcome to fdisk (util-linux 2.31.1).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

Device does not contain a recognized partition table.
Created a new DOS disklabel with disk identifier 0x27a74be3.

Command (m for help): n
Partition type
   p   primary (0 primary, 0 extended, 4 free)
   e   extended (container for logical partitions)
Select (default p): 

Using default response p.
Partition number (1-4, default 1): 
First sector (2048-2097151, default 2048): 
Last sector, +sectors or +size{K,M,G,T,P} (2048-2097151, default 2097151): 

Created a new partition 1 of type 'Linux' and of size 1023 MiB.

Command (m for help): t
Selected partition 1
Hex code (type L to list all codes): 8e
Changed type of partition 'Linux' to 'Linux LVM'.

Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.

sdc にパーティションを作成

vagrant@vagrant:~$ sudo fdisk /dev/sdc

Welcome to fdisk (util-linux 2.31.1).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

Device does not contain a recognized partition table.
Created a new DOS disklabel with disk identifier 0xb7872d57.

Command (m for help): n
Partition type
   p   primary (0 primary, 0 extended, 4 free)
   e   extended (container for logical partitions)
Select (default p): 

Using default response p.
Partition number (1-4, default 1): 
First sector (2048-2097151, default 2048): 
Last sector, +sectors or +size{K,M,G,T,P} (2048-2097151, default 2097151): 

Created a new partition 1 of type 'Linux' and of size 1023 MiB.

Command (m for help): t
Selected partition 1
Hex code (type L to list all codes): 8e
Changed type of partition 'Linux' to 'Linux LVM'.

Command (m for help): w

The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.

パーティションの確認

vagrant@vagrant:~$ lsblk 
NAME                   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda                      8:0    0   64G  0 disk 
└─sda1                   8:1    0   64G  0 part 
  ├─vagrant--vg-root   253:0    0   63G  0 lvm  /
  └─vagrant--vg-swap_1 253:1    0  980M  0 lvm  [SWAP]
sdb                      8:16   0    1G  0 disk 
└─sdb1                   8:17   0 1023M  0 part 
sdc                      8:32   0    1G  0 disk 
└─sdc1                   8:33   0 1023M  0 part 

物理ボリュームを作成する

vagrant@vagrant:~$ sudo pvcreate /dev/sdb1 /dev/sdc1 
  Physical volume "/dev/sdb1" successfully created.
  Physical volume "/dev/sdc1" successfully created.

ボリュームグループを作成する

vagrant@vagrant:~$ sudo vgcreate lvg-test /dev/sdb1 /dev/sdc1 
  Volume group "lvg-test" successfully created

作成したボリュームグループの確認する

vagrant@vagrant:~$ sudo vgdisplay
  --- Volume group ---
  VG Name               lvg-test
  System ID             
  Format                lvm2
  Metadata Areas        2
  Metadata Sequence No  1
  VG Access             read/write
  VG Status             resizable
  MAX LV                0
  Cur LV                0
  Open LV               0
  Max PV                0
  Cur PV                2
  Act PV                2
  VG Size               1.99 GiB
  PE Size               4.00 MiB
  Total PE              510
  Alloc PE / Size       0 / 0   
  Free  PE / Size       510 / 1.99 GiB
  VG UUID               zMcDi1-xfrc-y6ae-KFgL-ZN90-moNz-cteZdu
   
  --- Volume group ---
  VG Name               vagrant-vg
  System ID             
  Format                lvm2
  Metadata Areas        1
  Metadata Sequence No  3
  VG Access             read/write
  VG Status             resizable
  MAX LV                0
  Cur LV                2
  Open LV               2
  Max PV                0
  Cur PV                1
  Act PV                1
  VG Size               <64.00 GiB
  PE Size               4.00 MiB
  Total PE              16383
  Alloc PE / Size       16383 / <64.00 GiB
  Free  PE / Size       0 / 0   
  VG UUID               0hdOem-2LyY-XIrh-cxIR-bYG4-6LXM-WMZjo5

論理ボリュームを作成する

vagrant@vagrant:~$ sudo lvcreate -n lv-test -L 1.99g lvg-test
  Rounding up size to full physical extent 1.99 GiB
  Logical volume "lv-test" created.

論理ボリュームにファイルシステムを作成する

vagrant@vagrant:~$ sudo mkfs.ext4 /dev/lvg-test/lv-test 
mke2fs 1.44.1 (24-Mar-2018)
Creating filesystem with 522240 4k blocks and 130560 inodes
Filesystem UUID: 24bbc802-618d-4b4e-9542-603ddd573de9
Superblock backups stored on blocks: 
    32768, 98304, 163840, 229376, 294912

Allocating group tables: done                            
Writing inode tables: done                            
Creating journal (8192 blocks): done
Writing superblocks and filesystem accounting information: done 

論理ボリュームをマウントする

vagrant@vagrant:~$ sudo mount /dev/lvg-test/lv-test /mnt
vagrant@vagrant:~$ ls /mnt/
lost+found
vagrant@vagrant:~$ df -hT
Filesystem                     Type      Size  Used Avail Use% Mounted on
udev                           devtmpfs  461M     0  461M   0% /dev
tmpfs                          tmpfs      99M  5.1M   94M   6% /run
/dev/mapper/vagrant--vg-root   ext4       62G  1.6G   58G   3% /
tmpfs                          tmpfs     493M     0  493M   0% /dev/shm
tmpfs                          tmpfs     5.0M     0  5.0M   0% /run/lock
tmpfs                          tmpfs     493M     0  493M   0% /sys/fs/cgroup
vagrant                        vboxsf    457G  121G  337G  27% /vagrant
tmpfs                          tmpfs      99M     0   99M   0% /run/user/1000
/dev/mapper/lvg--test-lv--test ext4      2.0G  6.0M  1.9G   1% /mnt

参考

「入門 監視」を読んだメモ

書籍情報

https://www.oreilly.co.jp/books/9784873118642/

1.1 アンチパターン 1: ツール依存

  • 成功したチームが使っているから、という安直な理由でツールを採用してはいけない
  • 自分や自分のチームにツールが合っている必要がある

1.3 アンチパターン 3: チェックボックス監視

  • 「動いている」とはどういう意味か。「動いている」かどうかを監視しよう
    • 高レベルなチェックが実施できるか検討しよう
    • Web アプリケーションなら / に GET して、200 が返るか、ページに特定の文字列があるか、リクエストのレイテンシが小さいかなど
  • メトリクスは最低でも 60 秒に 1 回すべき

1.4 アンチパターン 4: 監視を支えにする

  • 問題を解決するためにアプリケーションを改修するのではなく、監視項目を追加することは避ける

2.1 デザインパターン 1: 組み合わせ可能な監視

  • モノシリックな監視ツールより、専門化されたツールを組み合わせて、監視プラットフォームを作ったほうがいい
  • モノシリックな監視ツール全体を置き換えるより、特定のツールだけ変更できるほうがよい
  • 監視システムを構成する要素
    • データ収集
    • データストレージ
    • 可視化
    • 分析とレポート
    • アラート

データ収集

  • 収集方法にはプッシュ型とプル型が存在する
  • メトリクスデータとは
    • カウンタ: Web サイトの累計訪問者数など
    • ゲージ: ある時点の値
  • ログデータとは
    • 非構造化ログ

      192.34.63.77 - - [26/Jun/2016:14:06:22 -0400] "GET / HTTP/1.1" 301 184 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)Chrome/47.0.2526.111 (StatusCake)" "-"

    • 構造化ログ

      { "remote_addr": "192.34.63.77",
      "remote_user": "-",
      "time": "2016-06-26T14:06:22-04:00",
      "request": "GET / HTTP/1.1",
      "status": “301”,
      "body_bytes_sent": “184”,
      "http_referrer": "-",
      "http_user_agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36
      (KHTML, like Gecko) Chrome/47.0.2526.111 (StatusCake)",
      "http_x_forwarded_for": "-" }

分析とレポート

  • SLO などが関連する
  • 監視対象のシステムが依存コンポーネントを持っている場合、システムはそのコンポーネントの可用性を超えることはできない
    • クラウドプラットフォームの可用性が 99.95% なら、システムの可用性は 99.95% を超えることができない

3.1 どうしたらアラートをよくできるか

  • アラート=緊急の対応が求められるもの
    • 例. Web サーバのダウンなど
  • まずは対応を自動化できないか検討する
  • オンコール担当のローテーション間隔は 3 週間以上を推奨する
  • アラートをレポートとして出力できると、システム改善の役に立つ
  • ある値が X(固定のしきい値) を超えた場合のアラート(チェック監視)には意味がないこともある。変化量を監視したほうがいいこともある(メトリクス監視)
    • 空き容量が 10% 以下でアラート よりも 一晩でディスク使用量が 50% 増加でアラート のほうが役に立つかも
  • 不要なアラートは削除・しきい値の変更をする。または対応を自動化できないか検討する
    • 削除前にはアラートに対する過去のアクションを分析してもいい
  • アラートへの対応
    • すぐにアクションが必要なアラートへの対処(アラートの本来の定義)
      • SMS や PagerDuty などを使う
    • 注意が必要だが、すぐにアクションは必要ないアラートへの対処
      • 社内チャットやメール
    • 履歴や診断のために保存しておくアラートへの対処
      • ログファイルに保存する

4. 統計入門

  • 移動平均
    • 平滑化することで極端な値を隠す。ただし重要かも知れないデータも見えなくなる
  • 中央値
    • 一方向に大きな偏りがあるデータセットを扱うときに使うと良い
  • パーセンタイル
    • 50パーセンタイル=中央値

5. ビジネスを監視する

  • ビジネスの KPI を計測するために技術的なメトリクスを利用する(対応付けをする)
  • Reddit のビジネスの KPI と技術的なメトリクスの対応例
    • 現在サイトに滞在しているユーザ: 現在サイトに滞在しているユーザ
    • ユーザのログイン: ユーザのログイン失敗、ログインのレイテンシ
    • コメント投稿: コメント投稿失敗、投稿のレイテンシ
    • スレッド作成: スレッド作成失敗、作成のレイテンシ
    • 投票: 投票失敗、投票のレイテンシ
    • プライベートメッセージの送信: プライベートメッセージ送信失敗、送信のレイテンシ
    • Gold 購入: 購入失敗、購入のレイテンシ
    • 広告購入: 購入失敗、購入のレイテンシ

6.2 フロントエンド監視の 2 つのアプローチ

  • リアルユーザ監視(Real User Monitoring=RUM)
    • メトリクスに実際のユーザトラフィックを使う
    • 各ページに JavaScript を仕込み、ユーザがページをロードすると、監視サービスに対してメトリクスを送信する
  • シンセティック監視(Synthetic Monitoring)

6.3 DOM

  • ページロード時間は 4 秒以下を目指す
  • ページ表示時にロードされる JavaScript が多いと、それだけロード時間が増加する
  • Navigation Timing API を使い、ロード時間を計測する

7. アプリケーション監視

  • Go の構造化ロガー
  • StatsD: メトリクス保存に使われるツール。アプリケーションにコードを追加する
  • ビルドやデプロイなどの情報も監視データとして保存しておくと、他のメトリクスと組み合わせることができる
    • 特定のデプロイから500エラーが増加しているなどが判明するかも
  • マイクロサービスでは監視に分散トレーシングが使われることがある
  • /health エンドポイントを追加する
    • 特定の IP のみアクセスを許可すること。ユーザには公開しないほうがいい
    • 本文は構造化フォーマットがおすすめ
    • 以下はアプリケーションが依存する MySQL と Redis も確認している例
from django.db import connection as sql_connection
from django.http import JsonResponse
import redis


def check_sql():
    try:
        with sql_connection.cursor() as cursor:
            cursor.execute("SELECT 1 FROM table_name")
            cursor.fetchone()
        return {"okay": True}
    except Exception, e:
        return {"okay": False, "error": e}


def check_redis():
    try:
        redis_connection = redis.StrictRedis()
        result = redis_connection.get("test-key")
        if result == "some-value":
            return {"okay": True}
        else:
            return {"okay": False, "error": "Test value not found"}
    except Exception, e:
        return {"okay": False, "error": e}


def health():
    if all(check_sql().get("okay"), check_redis().get("okay")):
        return JsonResponse({"status": 200}, status=200)
    else:
        return JsonResponse(
            {
                "mysql_okay": check_sql().get("okay"),
                "mysql_error": check_sql().get("error", None),
                "redis_okay": check_redis().get("okay"),
                "redis_error": check_redis().get("error", None),
            },
            status=503,
        )

8. サーバ監視

9. ネットワーク監視

  • 帯域幅
    • ある接続から一度に送れる理論上の最大情報量
    • 秒間メガビット(Mbps)などが使われる。秒間メガバイト(MBps)と混同しないように注意する
    • 高速道路で例えるなら車線数
  • スループット
    • ある接続から一度に送れる実際の最大情報量
    • スループット計測の便利ツール: iperf2
    • 高速道路で例えるなら実際に通行している車の数
  • レイテンシ
    • パケットがネットワークリンクを通じてやり取りされるのにかかる時間
    • 高速道路で道路の長さ

10. セキュリティ監視

付録 C 実践 監視 SaaS

  • 監視 SaaS の利点
    • コスト
    • 難易度の高い監視ツールの運用を SaaS 提供者に任せることができる
    • 利用者はプロダクト開発に集中できる
    • 使いやすいデザインが提供されることで、チーム全員が監視システムに積極的にアクセスでき、属人化を避けることができる
    • バージョンアップ作業が不要
  • OS メトリクスの収集の役割を 健康診断 で例える
    • 健康診断で肝機能の γ-GPT が高かったとする
    • 多分お酒の飲みすぎが原因
    • この場合、本来監視すべきは酒量だが、大まかな変化は検出できた
  • ユーザ視点のシンセティック監視(URL 外形監視)から監視を始めるのがおすすめ