Google Cloud Innovators Gym Japan に参加して Professional Cloud Developer に合格した #giggcp

Google Cloud Innovators Gym JapanというGoogle Cloud主催のトレーニングプログラムに第二期生として参加しました。

その結果Professional Cloud Developerに合格することができた*1のでそのレポートです。

cloud.google.com

トレーニングを受ける前の経験

  • Google Cloudの実務経験はなし
    • AWSの経験はあるけど資格は一つも取ってない
  • Kubernetesに関してもPod、ReplicaSet、Deployment、Serviceが何を指しているかはわかるくらいの知識くらいしかない状態

試験に向けて勉強したこと

基本的にはトレーニングプログラムで用意されたものを使って対策しました。

具体的にはpixivさんの記事にあるとおりで Google Cloudのエンジニアが行うセッション+ハンズオンと、Courseraのコースが無償で提供されるのでそれを進めていくという感じです。

inside.pixiv.blog

例えばこんなコースを受けていました。 www.coursera.org

Courseraに関しては動画を見る・Qwiklabsと連携したハンズオンを行う・テストを受けるのサイクルで結構効率よく学べました。

下の記事にもある通り、Qwiklabsのハンズオンが非常に良くできていて、実際にGoogle Cloudの環境を用意してハンズオンをすることができました。 コマンドを打つときも基本的にCloud ShellとCloud Shell Editorを使う手順になっているので手元の環境に依存することはないです。

dekotech.dekokun.info

その他では本を読むとかはしていなくて、Courseraのテストや模試で間違えたところはちゃんとドキュメントを見るというのをやったくらいです。

掛けた時間としてはセッション+ハンズオンとCourseraのコースを8つほど受けて、大体50時間弱くらいかなと思います。

スケジュールとしては6月に入ってからCourseraのコースをやり始めて6/24にAssociate Cloud Engineerに合格、7/6にProfessional Cloud Developerという感じです。

感想

普段業務で触れていないGoogle Cloudについて知る機会になってとても楽しかったです。

試験の内容自体もクラウドネイティブなアプリケーションの構築ができるかどうかを問われている問題も多く、腕試しという感じで楽しく受けられました。

せっかくなのでトレーニングプログラムの期間中にProfessional Cloud Architectも取得目指してみたいなと思いました。

Webサービスのモニタリングについてのチェックリスト

この記事は はてなエンジニア Advent Calendar 2020 11日目の記事です。

はじめに

運用しているWebサービスについて、より良いモニタリングにしていくために普段考えていることをチェックリスト形式でまとめてみました。

全部にチェック付いていないとダメというわけでもなく、Webサービスの性質によっては当てはまらないものもあると思うので参考程度に現状を把握するツールとして使ってください。

チェックリスト

疎通確認

  • それぞれのコンポーネントに対して疎通確認を行っている
  • localhostでなくネットワーク経由でのコンポーネントへの疎通確認を行っている
  • 疎通が取れなかったときに正しくアラートになることを確認している

メトリック

  • 可能な限りチェック監視ではなくメトリック経由での監視にしている
  • ビジネスロジックに関わる値をメトリックにしている

ログ

  • 利用しているログ配送システムの可用性や完全性について理解している
  • ログをメトリックにする際に詰まってしまった場合のことを考慮できている

アラート通知

  • 閾値の根拠をアラート通知から確認できるようにしている
  • アラート発生時にどうすれば良いのかをアラート通知から確認できるようにしている
  • SLOに関係するような重大なものとそうでないものでアラートのレベルを使い分けている
  • 通知の再送を設定している

HTTPリクエストの場合

  • 5xx系だけでなく4xx系もモニタリングしている
  • nginxを使っている場合、499とその他の4xx系を区別してモニタリングしている
  • レイテンシの平均及びパーセンタイル値をモニタリングしている
  • 以上の項目をエンドポイント単位でモニタリングしている

バッチの場合

  • バッチの実行に成功したかどうかをモニタリングしている
  • バッチの実行時間をモニタリングしている
  • 決まった時間に実行されているということを監視している

ジョブキューの場合

  • 正しい結果が返ってくる割合をモニタリングしている
  • 処理の遅延状況をモニタリングしている
  • ジョブキューに入ってきたジョブの数と処理したジョブの数をモニタリングしている

ダッシュボード

  • The Four Golden Signalsに従ってダッシュボードを構築している
  • 単なる数字ではなく割合を確認している
  • 1日前や7日前と比較した数値を確認している
  • リリースタイミングをダッシュボードから確認できるようにしている
  • 開発イテレーションの期間内だけでなく数ヶ月以上での傾向も確認している

開発メンバーとの振り返り

  • リリース・障害に伴って数字に変化は現れたか確認している
  • 監視項目について議論し見直しを行っている
  • 半期や四半期の単位で起きた障害について原因を分類できている

その他

  • サービスの構成図やリクエストのフロー図を用意している
  • 依存している他サービスがあれば、そのサービスが動かなかったときの影響を考慮できている

おわり

他にも色々あったような気がしますが、とりあえず思いついたものはこのあたりです。他にもあれば教えて下さい。

2020年におけるmackerel-plugin-jsonの使い方

この記事は Mackerel Advent Calendar 2020 2日目の記事です。

はじめに

皆さんはmackerel-plugin-jsonを使っていますか?

個人的にはかなり好きなプラグインなのですが、2017年に書かれたMackerel プラグインアドベントカレンダー以外ではあまり解説されていませんでした。

soudai.hatenablog.com

この記事よりも後に追加された機能を紹介している記事も見当たらなかったので、今回はそれらに絞って紹介していきます。

前回実行したときの結果との差分を投稿する-diffオプション

-diff オプションの後に正規表現を書くと、その正規表現にマッチしたメトリックに対して前回の実行時からの差分をMackerelに投稿することができます。

アプリケーション内に単調増加するカウンターとその値をJSONで返すエンドポイントを実装しておくことでプラグイン側が差分を計算し、例えば1分間でのリクエスト数などそれだけで簡単に取得できるようになります。

GoのアプリケーションであればカウンターとJSONで返すエンドポイントの実装を公式のexpvarパッケージを使うことで簡単に実現することができます。

このように書くことでアプリケーション側(今回はapp)で記録していたカウンターをJSONで返すことができます。

func Handler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    fmt.Fprintln(w, expvar.Get("app"))
}

今まではこういった差分を送信したい場合はアプリケーション側で差分管理をきちんと実装してあげる必要がありましたが、プラグイン側で差分を計算するようになったためにそういった必要はなくなりました。

特にステータスコードでは表現しにくいアプリケーション固有のメトリックはログに書いてそれを集計するという方法が一般的だと思いますが、ログはディスクの容量や配送経路などいくつか考える必要が出てくるため準備も少し大変でした。

そういった場合でログから集計する方法を取りづらい場合にも役に立つと思います。

上のブログでもこうしたカウンター値にする機能がないことが欠点として書かれていましたが、その欠点をほぼ解決する機能になっていると思います。

ただし注意点としてmackerel-plugin-jsonはそのままの値をグラフしますのでカウンタ-値に自分で変換したい場合はやはりpluginにする必要があります。

エンドポイントの監視 ~ mackerel-plugin-jsonを読み解く - そーだいなるらくがき帳

このオプションは2018/12/13にリリースされた v1.1.0 以降で使えます。

HTTP リクエストヘッダを指定できる-headerオプション

外形監視や check-http のプラグインと同様に-header オプションを指定することでHTTP リクエストヘッダを設定できるようになりました。

-header オプションは -header=User-Agent:mackerel-http-checker/0.0.1 -header=Cache-Control:no-cache のように複数指定することもできます。

このオプションの使い道の一つに、インターネット上から監視用のエンドポイントにmackerel-plugin-jsonでリクエストしたい場合があります。

今までは一般にアクセスをさせないようにするにはIPアドレスで制限するといったことが多かったと思います。

それがこのオプションによって、リクエストヘッダベースの制限も掛けやすくなると思います。

ではどんな時に良いでしょうか? 1つ目は直接自分の管理下に置いていないServer、つまりはレンタルサーバーなどです。 この場合はmackerel-agentのインストールが難しく、監視対象側から定期的にMackerelのAPIを叩くことが難しいでしょう。 そんな場合はモニタリング用のエンドポイントを作成してそこをmackerel-plugin-json経由で監視すればいいのです。

エンドポイントの監視 ~ mackerel-plugin-jsonを読み解く - そーだいなるらくがき帳

このオプションは2020/11/26にリリースされた v1.2.0 以降で使えます。

インストールの手間が改善されている

2017年の記事の当時では自分でビルドしてホストに配布する必要がありました。

mackerel-plugin-jsonのインストールですがmackerel-plugin-jsonは公式パッケージに同梱されていません。 そのため自分でbuildする必要があります。

エンドポイントの監視 ~ mackerel-plugin-jsonを読み解く - そーだいなるらくがき帳

しかし、現在はmkr plugin install mackerel-plugin-json というコマンドを実行するだけでインストールができるようになっているため、簡単に使い始めることができます。

また、2019/7以降にはmackerel-container-agentのプラグイン同梱版のイメージ(mackerel/mackerel-container-agent:plugins)にはデフォルトで同梱されるようになっています。

mackerel-plugin-json は公式プラグインとしては同梱されてはいないのですが、今回 mackerel-container-agent の同梱プラグインとして追加いたしました。

mackerel-container-agent のアップデートを行いました - Mackerel ブログ #mackerelio

おわりに

アプリケーション側で監視用のエンドポイントを持つというのはここ最近かなり一般的になってきたように感じます。

その際に今のmackerel-plugin-jsonでできることを知ることで、そういったエンドポイントをより良く活用できるようになるのではと思っています。

mrubyを触ってみた

最近ngx_mrubyを触っていて、RubyのTime#httpdateを使いたかったもののmrubyにはないということがあった。

そもそもmruby自体にも全然慣れていないという課題もあったので、このメソッドを実装することを目標にライブラリを作りながらmrubyに慣れるということをやってみた。

mrubyをビルドする

ビルド手順は非常に簡単で、mrubyのリポジトリをcloneしてmakeするだけで良い。

git clone https://github.com/mruby/mruby.git
cd mruby
make

bin/ ディレクトリ以下にいくつか実行ファイルが作られて試すことが出来て、irbのmruby版であるmirbというものも使うことが出来る。

~/c/s/g/m/mruby ❯❯❯ bin/mirb
mirb - Embeddable Interactive Ruby Shell

> p Time.now
2020-10-20 22:30:13 +0900
 => 2020-10-20 22:30:13 +0900
>

ライブラリの作り方

これで最低限mrubyを使う準備はできたので、次はライブラリを作っていく。

mrubyのライブラリはmrbgem(mgem)と呼ばれており、 mrbgem.rakemrblib/ など決まったファイル・ディレクトリ構造に従ってファイルを作成していくというものになっている。

~/c/s/g/c/mruby-time-library ❯❯❯ tree
.
├── LICENSE
├── README.md
├── mrbgem.rake
├── mrblib
│   └── mrb_time_library.rb
└── test
    └── mrb_time_library.rb

2 directories, 5 files

実装がある程度できたら動くかどうかテストをする準備をしていく。

テストの実行は少し特殊で、ライブラリのリポジトリではなくmrubyのリポジトリに以下のような build_config.rb というファイルを作成する。 conf.gemには作成しているライブラリのパスを指定する。

MRuby::Build.new do |conf|
  toolchain :gcc
  conf.gembox 'default'
  conf.gem '../mruby-time-library'
  conf.enable_test
end

そしてmrubyのリポジトリで rake test を実行することでライブラリのテストを走らせることが出来る。

そうすれば「ライブラリの実装とテストを修正し続けて rake test を実行する」というサイクルで開発をすすめることができる。

GitHub ActionsでCIする

手元環境の準備が整ったので、今度はリポジトリ上でCIできるようにする。

mrubyのライブラリはTravis CIを使っていることも多いけれど、せっかくなのでGitHub Actionsでやれるようにした。

CIでやる場合もmrubyのリポジトリでbuild_config.rbを使って指定するというのは変わらずだけど、手元のパスではなくGitHub上のリポジトリとブランチを指定するのでそこを工夫して書き換える必要がある。

ただし、ActionsのトリガーではブランチへのコミットとPRとでは作業ブランチの取得方法が微妙に異なるのでビルド前にいい感じにブランチ名を取っておくというのも必要。

      - name: get branch name
        if: github.event_name != 'pull_request'
        run: echo "BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_ENV
      - name: get pull request branch name
        if: github.event_name == 'pull_request'
        run: echo "BRANCH_NAME=$(echo $GITHUB_HEAD_REF)" >> $GITHUB_ENV

この BRANCH_NAME を使ってbuild_config.rbを作ってrake testをしてやれば良い。

      - name: Rake test
        env:
          TEMPLATE: |
            MRuby::Build.new do |conf|
              toolchain :gcc
              enable_debug
              conf.enable_bintest
              conf.enable_test
              conf.gembox 'default'
              conf.gem :github => '${{ github.repository }}', :branch => '${BRANCH_NAME}'
            end
        run: |
          printf "${TEMPLATE}" > build_config.rb
          cd mruby
          env MRUBY_CONFIG=../build_config.rb rake test

そうして完成したワークフローは以下。

https://github.com/cohalz/mruby-time-library/blob/master/.github/workflows/test.yml

ライブラリを作った

以上で、httpdateを含むtimeライブラリの特定のフォーマットの文字列にするメソッド(インスタンスメソッド)を使えるようにするライブラリを作成できた。

github.com

~/c/s/g/m/mruby ❯❯❯ bin/mirb
mirb - Embeddable Interactive Ruby Shell

> Time.now.httpdate
 => "Tue, 20 Oct 2020 14:44:03 GMT"
>

文字列をパースするメソッドに関しては、mrubyにはデフォルトでは正規表現が使えないというのもあり実装を後回しにしているけど、既に欲しくなってきたので近いうちに作りたい。

mackerel-plugin-axslogを使ってみた

Mackerelでアクセスログを集計したいときに、普段は公式プラグインであるmackerel-plugin-accesslogを使っていた。

そんな中ふと下のエントリーを思い出し、高速であるという点も気になるし、いくつかオプションも異なるということで試しに使ってみることにした。

kazeburo.hatenablog.com

mkr plugin installをすることでインストールできる。*1

mkr plugin install --upgrade kazeburo/mackerel-plugin-axslog@v0.2.1

インストールされたのを見たら、あとは必要なオプションをいくつか入れることですぐ使い始められる。

(mackerel-plugin-accesslogとオプション名が結構異なっているので注意)

[plugin.metrics.axslog]
command = '/opt/mackerel-agent/plugins/bin/mackerel-plugin-axslog --key-prefix=all --format=ltsv --status-key=status --ptime-key=reqtime --logfile=/var/log/nginx/access_log'

これだけで準備完了であとは待っていたらmackerel-plugin-accesslogに似たようなメトリックが表示されるようになる。

アクセス数の数字について

実際にアクセスしてメトリックを確認したところ、このようにアクセス数の数字が小数点以下の数字になった。

f:id:cohalz:20200628002338p:plain

当然1分間に0.0833回アクセスしたわけではないので、どういう実装になっているか見てみたところこのようになっていた。

fmt.Printf("axslog.access_num_%s.2xx_count\t%f\t%d\n", keyPrefix, s.c2xx/s.duration, now)

https://github.com/kazeburo/mackerel-plugin-axslog/blob/v0.2.1/axslog/axslog.go#L200-L206

カウント(s.c2xx)を期間(s.duration)で割っているということがわかる。

計測された数を期間で割っているのでつまりは「平均して1秒あたりにどのくらいのリクエストが来たか」というメトリックになっているということがわかった。 *2

何故こうなっているのか不思議だったけど、おそらく「プラグインの実行が何らかの形で失敗した場合、集計されるべきだった分が次回に加算されてしまい数字が大きく出てしまう」ということを回避するための仕組みなんだろうと思った。*3

一度プラグインの実行に失敗して次回1分後の集計の範囲が倍になったとしても、カウント数も期間も等しく倍になっているためメトリックとしては大きく変わることはないということになる。

このようにメトリック投稿を復帰させたあとのグラフに悪影響を及ぼさない上にメトリック自体も集計タイミングに依存しない、という感じでわかってしまえばなかなか合理的なメトリックになっているということがわかった。

filterについて

Usageにある通り、mackerel-plugin-axslogにはfilterオプションが存在する。

--filter= text for filtering log

このfilterの使い方についても調べてみた。

使い方

コードを見たところ、JSON/LTSVの文字列をパースする直前にbytes.Indexでマッチしているのか見ているようだった。

if len(filter) > 0 {
    if bytes.Index(b, filter) < 0 {
        continue
    }
}
c, pt, st := r.Parse(b)

https://github.com/kazeburo/mackerel-plugin-axslog/blob/v0.2.1/main.go#L50-L55

パースした結果を見るわけではないので、例えばLTSV形式のログでフィルタを使いたい場合は --filter="method:POST" のようにkey:valueの形式で指定するという使い方になる。

使い所

そしてこのフィルタ機能がmackerel-plugin-axslogを選ぶ大きな利点になっていると個人的に思っている。

というのもmackerel-plugin-accesslogではログからすべてのレイテンシとステータスコードを集計するため、ログに傾向の違うリクエストが混ざっている場合に実感とは異なるメトリックの傾向になるということがよくあった。(99パーセンタイル値だけが高い値を示しているなど)

そういう場合に、このフィルタ機能を使うことで重要なエンドポイントに絞ったレイテンシやステータスコードの集計ができるようになりそう。

そしてプラグインは複数実行できるので、confにフィルタを別にしたプラグインを複数書くことでフィルタしたメトリックをいくつも出すことができる。

ただし、並列で何回も同じアクセスログを読み直すので効率は悪く負荷が増え、メトリック数もその分増えるのでその点は考慮して工夫する必要がある。

例えば、本当に重要な条件のみに絞ったり、ログの形式をフィルタに合わせた形に変たりなどして複数実行の数を減らすとか、無理が出たらmackerel-plugin-axslogじゃなくてちゃんとログ集計基盤を作るとかになるとは思う。

とはいえアクセスログから1つ2つ重要な部分のみに絞った集計を少ない手間で可視化できるというのは相当に便利だとは思う。

まとめ

mackerel-plugin-axslogは後発のプラグインということもあり、フィルタのようなmackerel-plugin-accesslogにはない機能もある、ということでかなり面白いプラグインということがわかった。

*1:mkr plugin installでプラグインをインストールする - Mackerel ヘルプ

*2:試しに期間を正確に1分と仮定すると0.0833 * 60 = 4.998となり確かにほぼ整数に近い値になった。

*3:fluent-plugin-mackerelを使ってMackerelにメトリックを投稿する際にも、途中のログ配送が詰まると復旧後に加算されてしまうという現象が起こる。

Varnishでレスポンスをカスタマイズする方法

表題の方法を実現したくなって、調べたところ実現する方法が結構面白かったのでメモ。

例えば以下のような「キャッシュ破棄リクエストを受け付けて200を返す」というVCLがあったとする。

sub vcl_recv {
    if (req.method == "BAN") {
        ban("req.http.host == " + req.http.host + " && req.url == " + req.url);

        return(synth(200, "Ban added"));
    }
}

この状態で curl -XBAN localhost:6081 を実行してみると、レスポンスは以下のように Ban added だけではなく他のHTMLタグや諸々が含まれて返ってくる。

<!DOCTYPE html>
<html>
  <head>
    <title>200 Ban added</title>
  </head>
  <body>
    <h1>Error 200 Ban added</h1>
    <p>Ban added</p>
    <h3>Guru Meditation:</h3>
    <p>XID: 196749</p>
    <hr>
    <p>Varnish cache server</p>
  </body>
</html>

そしてこのレスポンスをもし Ban added だけにしたい場合にどのようにするとよいかという話。

結論としては、この状態から以下のように vcl_synth を変更することで実現できる。

sub vcl_synth {
    if (req.method == "BAN") {

        synthetic(resp.reason);

        # これも上と同等の処理
        # set resp.body = resp.reason;

        return (deliver);
    }
}

結構不思議な感じだけど、これで何故動くのかを説明していく。

まず synthetic 関数について、公式ドキュメントには以下のように説明されており、レスポンスをセットする関数になっている。

synthetic(STRING) Prepare a synthetic response body containing the STRING. Available in vcl_synth and vcl_backend_error. Identical to set resp.body / set beresp.body.

https://varnish-cache.org/docs/6.0/reference/vcl.html#synthetic-string

そして resp.reason について。

return synthした際の2つの引数は resp.status , resp.reason にそれぞれセットされる。*1

今回はそのうちの resp.reason を使って参照し、レスポンスを Ban added だけに書き換えることができたという理由になる。

ちなみに注意事項として、synthetic を呼ぶ際は vcl_recv でreturnしたときと同じ条件で囲わないとBANメソッド以外の場合にもレスポンスが書き換わってしまうというのがあるため、もし元々のHTMLも返したい場合には気をつける必要がある。

これを応用してこのような固定のレスポンスを返すだけではなく、例えばリクエストの User-Agent をレスポンスとして返すみたいなことも以下のようにして実現できる。

return(synth(200, req.http.User-Agent));

もちろんVarnishはキャッシュサーバなので複雑なレスポンスを組み立てて返すというのは向いていないけれど、例えばキャッシュ破棄のようなロジックを入れる際にレスポンスを少しカスタマイズする事はあると思う。

例えばvmod_xkey*2を使ってタグベースのキャッシュを行った際に、キャッシュ破棄できた数を返すことで「複数のVarnishサーバで合計でどのくらいキャッシュ破棄できたのか」の集計がしやすくなるなどがありそう。