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-send
、 make 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 デーモン だけでなく、smtp や smtpd の動作やログも確認できて、勉強になりました。ただサンドボックス環境の構築が思ったより大変で、構築のために試行錯誤してたら、だいぶ Postfix の勉強ができてしまった感があります...
細かいこと
- Postfix 公式の Docker イメージはなさそうだったので、普通に apt-get install してます
- メール送信用コンテナ、メール受信用コンテナとも main.cf はほぼデフォルトです。変更点は以下を見てもらえるとわかります
recv.localhost
用の MX レコードの設定は不要です。Postfix はメール送信時、 MX レコードが無いと A レコードから、送信先を決定するっぽいです- send/run.sh
- メール送信用コンテナでは、このスクリプトから Postfix の master デーモン を起動します
- コンテナ終了時は
shutdown_handler
でシグナルを受け取りpostfix stop
で master デーモンと、その子プロセスを終了させます。master デーモンのソースコードをみた感じ、子プロセスの終了を待機してなさそうなので、スクリプト側で待機処理を入れています - Postfix は名前解決に
/var/spool/postfix/etc/resolv.conf
を使うので、Postfix 起動前に/etc/resolv.con
をコピーしています。/etc/resolv.conf
はコンテナ起動時に書き換えられるため、コンテナ起動後にコピーします。コピーではなくシンボリックリンクでもいいと思います
「プロフェッショナル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) }
TIn
は json.Unmarshal() に対応している必要があります。
これは外部からの JSON データが []byte
から TIn
に json.Unmarshal()
され、ハンドラに渡されるためです。 2
先ほどの events.CloudWatchEvent
も JSON と構造体のフィールドを対応させる 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)
の形で結果を返せます。
TOut
は TIn
とは逆に 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 }
-
lambda.StartHandler() を使うと複雑な処理をするハンドラが書けるみたいです。こちらのページが詳しいです: https://zenn.dev/wim/articles/get_raw_input_data_by_go_lambda↩
-
https://github.com/aws/aws-lambda-go/blob/0462b0000e7468bdc8a9c456273c1551fab284aa/lambda/handler.go#L115↩
-
https://github.com/aws/aws-lambda-go/blob/0462b0000e7468bdc8a9c456273c1551fab284aa/lambda/handler.go#L24-L32↩
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 diff
や npm run cdk deploy
すると、AWS-CDK 本体(cdk コマンド)
が実行されますが、その際どうやってユーザが書いたスタックを読み込んでいるのか
気になったので調べてみました。
AWS-CDK は v1.146.0 を使いました。 調査には Visual Studio Code の JavaScript Debug Terminal を使いました。
npm run cdk diff 実行時
npm run cdk diff
でnode_modules/.bin/cdk
が実行されますnode_modules/.bin/cdk
はnode_modules/aws-cdk/bin/cdk
へのシンボリックリンクです- 参考: npm パッケージを CLI ツールとして機能させる仕組みについて - 30歳からのプログラミング
- node_modules/aws-cdk/bin/cdk から cdk.ts が実行されます
- cdk.ts からコードを追っていくと 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() にたどり着きます- L54 の
const app = config.settings.get(['app']);
により cdk.json の app が読み込まれます。コマンド引数--app
で指定することも可能です- この動作は https://docs.aws.amazon.com/cdk/v1/guide/cli.html#cli-app-command で説明されています
- ここでは例として
--app='npx ts-node bin/hello-cdk.ts'
とします
- L82 の
await exec(commandLine.join(' '));
でnpx ts-node bin/hello-cdk.ts
が実行され、結果が cdk.out に出力されます- この動作は https://docs.aws.amazon.com/cdk/v1/guide/cli.html#cli-synth で説明されています
- L54 の
readCurrentTemplateWithNestedStacks()
で deploy 済みのスタックを取得しますprintStackDiff()
で deploy 済みのスタックと cdk.out の差分を表示します
npm run cdk deploy 実行時
deploy() を簡単に見てみましたが、処理の流れは diff() と似たような感じだったです
スカイダイビングを体験してきました
2021/12/04 に栃木県の 藤岡スカイダイビングクラブ でスカイダイビングを体験してきました。
藤岡駅からタクシーでスカイダイビング場に行きました。
スカイダイビング場はこんな感じでした。
スタッフさんが飼っている「りょーま」くんです。
かわいい。
ジャンプスーツを着たら、このセスナで高度約 3800m まで行き、インストラクターさんと一緒にジャンプします。
セスナからの景色です。体験中はインストラクターさんが GoPro で撮影してくれます。
3800m まで来たらジャンプします。セスナから飛び出す瞬間が一番怖かったです。
そのまま約 60 秒間落下します。 風圧が凄くて、あまり息ができなかったです。浮遊感はなかったです。
その後パラシュートを開いて、約 5 分間降下します。無事に着地できました。
恐怖でジャンプできるか、かなり不安だったんですが、思ったほど怖くなかったです。 とても良い思い出になりました。皆さんもぜひスカイダイビングしてみてください!
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. 統計入門
5. ビジネスを監視する
- ビジネスの KPI を計測するために技術的なメトリクスを利用する(対応付けをする)
- Reddit のビジネスの KPI と技術的なメトリクスの対応例
- 現在サイトに滞在しているユーザ: 現在サイトに滞在しているユーザ
- ユーザのログイン: ユーザのログイン失敗、ログインのレイテンシ
- コメント投稿: コメント投稿失敗、投稿のレイテンシ
- スレッド作成: スレッド作成失敗、作成のレイテンシ
- 投票: 投票失敗、投票のレイテンシ
- プライベートメッセージの送信: プライベートメッセージ送信失敗、送信のレイテンシ
- Gold 購入: 購入失敗、購入のレイテンシ
- 広告購入: 購入失敗、購入のレイテンシ
6.2 フロントエンド監視の 2 つのアプローチ
- リアルユーザ監視(Real User Monitoring=RUM)
- メトリクスに実際のユーザトラフィックを使う
- 各ページに JavaScript を仕込み、ユーザがページをロードすると、監視サービスに対してメトリクスを送信する
- シンセティック監視(Synthetic Monitoring)
- 外部から動作確認する: Pingdom, Mackerel の URL 外形監視
- 自動テストにも組み込めると良い
- https://en.wikipedia.org/wiki/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. サーバ監視
- サーバ監視に SNMP を使うのは避けたほうがよい。他の優れた方法を使うべき
- collectd, Telegraf, python-diamond など
- Web サーバでの重要なメトリクス
- データベースでの重要なメトリクス
9. ネットワーク監視
- 帯域幅
- ある接続から一度に送れる理論上の最大情報量
- 秒間メガビット(Mbps)などが使われる。秒間メガバイト(MBps)と混同しないように注意する
- 高速道路で例えるなら車線数
- スループット
- ある接続から一度に送れる実際の最大情報量
- スループット計測の便利ツール: iperf2
- 高速道路で例えるなら実際に通行している車の数
- レイテンシ
- パケットがネットワークリンクを通じてやり取りされるのにかかる時間
- 高速道路で道路の長さ