YAMAGUCHI::weblog

海水パンツとゴーグルで、巨万の富を築きました。カリブの怪物、フリーアルバイター瞳です。

Go Conference 2019 SpringのPaperCallを初めて使ってみた感想+α

はじめに

こんにちは、Stackdriver担当者です。先日、Go Conference 2019 Springのセッションの応募にはじめてPaperCallというCfP as a Serviceを使ってみました。

www.papercall.io

使ってみての感想がいくつかあったので忘れないうちにメモしておこうと思います。

運営として

これまでGoConでのCfPはGoogle Formで作ったテキストエリアにただAbstractとDescriptionの中間くらいの分量のものを書いてもらって、その内容だけで判断していたのですが、今後のイベントとしての方向性を考えてPaperCallを使ってみました。

運営としてPaperCallがいいなと思った点は

  • コミッティーがセッションに求める内容をCfP Descriptionをきちんと書ける
  • 世界のコミュニティーに直接リーチできる

の2点です。

CfP Descriptionが書ける

今回のCfPでは、セッションとしてどういう内容を期待しているか、を書いていました。(そして応募されたProposalのうちの かなりの割合 がそれを無視していましたがw)

Selection Criteria Proposals will be reviewed based on the selection criteria below.

Relevance: The talk is relevant to the Go community. GoCon is not a general software conference, our audience wants to hear about topics that relate to the Go programming language.

Clarity: You’ve clearly explained what you are going to talk about.

Correctness: You’ve demonstrated knowledge of your topic. You don’t have to be an expert, but you are expected to be speaking from experience.

Achievability: You’ve thought about how to present your material in the time available. Impact. The goal of the talk. What new idea, technique, tool, or information will the audience leave your presentation with? Note: This clear selection criterion is based on GopherCon’s one. Thank you.

今回は8人のレビュワーが全Proposalを5段階評価をしたあとに、その平均点と標準偏差を取り、各トークフォーマットごとに平均点を降順にならべて、高得点のものから採用としていきました。(標準偏差はばらつきが大きいものだけ各人の評価を確認するためだけに使用)

やはり採択された中でも得点が高かったもの(4点以上)は、上記の条件すべてに当てはまっているものが多かったです。やはりGo Conferenceなので、上にあるように

  • そのセッションがどうGoに関係しているのか
  • そのセッションはどのような問題を解決してくれるのか
  • そのセッションで登壇者が伝えたいことの独自性はなにか

こうしたものが伝わってくるようなProposalは、それを読むだけでワクワクしましたし、ぜひ発表を聞きたいと感じ自ずと点が高くなりました。

世界のコミュニティーに直接リーチできる

PaperCallを利用したもう一つの理由は、多くの海外のカンファレンスでもそれがCfP用プラットフォームとして利用されていて、海外のエンジニアがPaperCallに公開されているイベントには、まず勢いで登録しているからです。

今回CfP Descriptionなどをほとんど英語で書いたのも、そうした海外のエンジニアに向けて情報を伝えられるように配慮した結果でした。

で、今回のGo Conferenceでの結果はどうだったのかというと、数は少なかったものの、きちんと応募がありました!そしてその少ない応募のうち2名は海外から参加されます!これは本当に嬉しいことで、すでに登壇者の方とはメッセージでやり取りをしています。

一人は「スピーカーに渡航費等の補助はあるのか」と質問してきたのですが、残念ながらいまのGoConではそれは賄えないと伝えると、それでも来てくれると言ってくれました。本当に嬉しい限りです。もう一名の方もビザの準備等がありまだ参加が確定しているわけではないのですが、それでも楽しみにしていると言ってくれました。

こうした方々の参加が今後ますます増えてくるようなイベントにしたいですね。スピーカーの渡航費補助などもできる仕組みを整えたいなと考えています。(イベント自体の有償化も視野に入れて)

応募者側として

逆に応募者側として何がいいかなと思ったときに次の内容が挙げられるなと思いました。(自分も1セッション応募した)

  • 応募者側としてタイトル、概要(Abstract)、セッション詳細(Description)、追加情報(Note)をきちんと分けて書ける
  • 同じProposalを複数のイベントに登録できる

各セクションが別れている

Proposalの書き方は、学会のポスターセッションなどに近い感覚でした。そうしたものに慣れていない人にも、タイトル→概要→セッション詳細→追加情報という形でフォームが広くなっているので、セッション自体を考えるプロセスにも使えるのが良かったです。

その中でもやはり概要とセッション詳細が肝だなと思いました。概要に書く内容は、セッションスライドで言えば、最初あるいは最後のスライドにくるような「何を伝えるセッションなのか」ということ。ここを先のCfP Descriptionに一致させたあとに、実際セッション詳細で、細かな内容を記述できるわけです。

今回の応募者の中にはトークフォーマットに合わせて、各内容をどれくらいの時間をかけて話す、ということまで説明してくれていた人もいました。そういった内容があればProposalのレビューをする際にも、セッションの様子をより具体的にイメージすることができるので助かるなと実感しました。

同じProposalを複数のイベントに登録できる

今回、Go Conference 2019 Spring と Go Conference 2019 Summer in Fukuoka のCfPの期間がかぶっていました。 どうなるかわからなかったので、GoConとGoCon Fukuokaには両方同じTalkを登録しました。Submissionごとにあとで編集できるので、出してから変更すればいいのですが、自分の得意なテーマがある場合にはそれを様々なイベントに対してチャレンジできるのは便利だと思いました。

早速 GoCon 2019 Spring の Reject Conf もあり、そこでもPaperCallを使うようなので、残念ながら通らなかった人も、PaperCallの機能を使ってそちらに登録してみてもらいたいです。

コミッティーとしての感想

Proposalをたくさんいただいた中で、やはりもうちょっと改善してもらいたいなと思うProposalはあって、何段階かあったのですがつぎのようなものが多かったです。

  1. セッション詳細がない or 雑
  2. 応募者が登壇しなければならない理由がわからない

まず1つめの「セッション詳細がない or 雑」に関して言うと、前者は自明として、後者はどういうものかといえば、ブログか何かのURLが貼ってあってそれでおしまい、というようなもの。レビュワーもかなりの数(今回のレビューで言えば合計で5万字くらい読んでます)のProposalに目を通すので、その中でそういう応募があれば、熱意を感じられず加点をする気持ちが起きません。ブログのURLだけが貼ってあるものを見たときには私は中身も確認せずに「このブログ記事をシェアすればいいだけなのだから、イベントで登壇枠を作る必要はない」と判断しました。

2つめの「応募者が登壇しなければならない理由がわからない」に関しては、ライブラリやツールなどの基本的な使い方の説明、などです。ドキュメントに書いてあるような内容しかセッション詳細で読み取れないときには、やはりこれもドキュメントをシェアすれば済んでしまう話になります。

今回コミッティーとして分量があるProposalを多く読み、あらためてProposalに込められた熱意というのは大事だなと実感しました。今後PaperCallやそれに類似したサービス使うイベントは増えてくると思いますが、このサービスのおかげで様々なイベントでのセッションの質もあがるのではないかという期待が湧いてきました。

今回は初めてのPaperCallでしたが、次回は2回めで運営側も参加者側も慣れていると思うので、今回よりももっと充実した投稿が増えることでしょう。早くも秋のイベントが楽しみです。

追記 2019.04.25 09:57AM

普通にウェブサイトにはProposalのタイトルと概要だけ載せたのですが(そうしないと発表内容全部公開になってしまうので面白くない)、何がウェブサイトに記載されるか書いていなかったため、何人かの方から「詳細も載せてほしい」「概要を変更させてほしい」というリクエストが来ました。 これらに関しては運営側がCfP Descriptionにその旨を書いていなかったのが悪いので反省しています。しかし詳細を載せてしまうとセッション内容が全部わかってしまい、個人的には興ざめになってしまうので、そのリクエストはお断りしました。また概要を変更するのも、人手が圧倒的に足りないので都度修正する手間などを考えてお断りしています。

右綴じ書籍のスキャン済みPDFをKindleで読む

はじめに

こんにちは、Stackdriver担当者です。海外旅行に向けて準備をしているのですが、Kindleに読みたい書籍をLinuxの端末のみを使って入れるのに苦戦したのでメモを書いておきます。

TL;DR

  • 右綴じ書籍対応のためにPDFを全部逆順にした(pdftk 使用)
  • PDFのサイズ圧縮にGhostscriptを使った
  • これでなんとか Send to Kindle が使えた

PDFのページを逆順にする

Kindleアプリは使ってないのでわかりませんが、ハードウェアのKindle*1では、典型的な日本語の書籍(縦書き右綴じ)の書籍を開くのに対応していません。

そこで仕方なく、PDFページを全部逆順にしたあとに末尾ページ(=元ファイルの先頭ページ)から戻していくことで右開きを実現しました。

$ pdftk input.pdf cat N-1 output output.pdf

ここで入力値は以下の通り

  • input.pdf: 元のPDFファイル名
  • N: PDFのページ数
  • output.pdf: 出力する逆順にしたPDFファイル名

PDFサイズを圧縮する

さて、これで Send to Kindle by Email でさくっと送るぞと思ったら、Gmailの添付するファイルサイズの上限を超えていて「Google Driveのリンクをシェアしますか?」とか聞かれたので困った。見てみたらファイルサイズが50MBを超えている。

ちょっと調べてみたらGhostscriptを使ってPDFを簡単に圧縮できるようだったので試してみた。

$ gs -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dCompatibilityLevel=1.5 -dPDFSETTINGS=/ebook -sOutputFile=output.pdf input.pdf

ここで入力値は以下の通り

  • input.pdf: 先程逆順にしたPDFファイル名
  • output.pdf: 圧縮済みのPDFファイル名

また各種オプションはmanや公式ドキュメントを見てもらったほうが良いですが、メモとして残しておくと

  • -dBATCH と -dNOPAUSE: PDFファイルの各ページごとに停止して確認しながらループするのではなく、全ページ一気に処理するときに指定
  • -sDEVICE: 出力先デバイスの指定なんだけど、PDFファイルに書き込む場合はここで pdfwrite を指定
  • -dCompatibilityLevel: 圧縮する際のAdobe Distiller 5互換のパラメータ(ここの表参照)
  • -dPDFSETTINGS: Adobe Distillerに対するプリセットの設定群を適用できるラベル。電子書籍用であれば /ebook を指定するのが良いと思う。(/screen だと粗すぎると感じた)

処理結果

% ls -lh *.pdf
-rw-r--r-- 1 ymotongpoo users 20M  4月 14 19:23 reversed-compressed.pdf
-rw-r--r-- 1 ymotongpoo users 53M  4月 14 18:50 reversed.pdf
-rw-r--r-- 1 ymotongpoo users 53M  4月 14 18:47 original.pdf

53MBが20MBに圧縮されました!

参照

*1:自分が持っているのはKindle Paperwhite

OpenCensusでStackdriver Monitoringにメトリクスを送信する

はじめに

こんにちは、Stackdriver担当者です。先日Raspberry Pi Zero WにつけたBMP680から得たデータをStackdriver Monitoring API v3を使って送信してダッシュボードを作るという記事を書来ました。

ymotongpoo.hatenablog.com

しかしBMP680のデータを眺めていると結構精度が怪しいのでキャリブレーション用のデータが必要だなと思い、外気温のデータもStackdriver Monitoringに送ることにしました。それにOpenCensusを使ったのですが、GCPUG SlackでStackdriverに送るまとめをOpenCensus meetup vol.1までに公開すると言っていたのを忘れてたので慌てて記事にしました。

TL;DR

OpenWeatherMapとから外気の気温、湿度、大気圧、その他諸々のデータを取得し、OpenCensusを使ってStackdriver Monitoringにデータを送った。

OpenCensus Stats/Metrics

OpenCensusには大きく分けてTraceとStats/Metricsという2つの機能があり、それぞれStackdriver TraceとStackdriver Monitoringに対応したデータを送信できます。

OpenCensus Stats Exporter for Go

今回は OpenCensus Stats/Metrics の Stackdriver Exporter を使って Stackdriver Monitoring に送信するわけですが、Stackdriver Monitoring API v3を使っている用語と OpenCensus で使っている用語が違うので、公式ドキュメントにもある対応表をまず理解したほうが、いろいろなドキュメントが読みやすいです。

OpenCensus Stackdriver Monitoring 補足
Exporter N/A OpenCensusとモニタリングバックエンドをつなぐためのインターフェース
View MetricsDescriptor 記録するメトリクスのメタ情報
Measure MetricKind メトリクスのデータ型の定義
Aggregation ValueType メトリクス送信時の時系列データとしての扱い
Measurement Point ある一時点でのメトリクスの記録
View Data TimeSeries メトリクスの実データを送信前にバッファしておく入れ物
Tag LabelDescriptor ViewっやMeasureに対するラベル(OpenCensusではResourceとMetricに区別したラベルを付けない)

その上でサンプルコードを読むと大まかな雰囲気がわかります。

サンプルコードはPrometheus用のコードになっているので Exporter の部分だけ、Stackdriver Monitoring 用に置き換えてやる必要があります。といっても必要なのは Exporter の初期化の部分だけです。

type GenericNodeMonitoredResource struct {
    Location    string
    NamespaceId string
    NodeId      string
}

func NewGenericNodeMonitoredResource(location, namespace, node string) *GenericNodeMonitoredResource {
    return &GenericNodeMonitoredResource{
        Location:    location,
        NamespaceId: namespace,
        NodeId:      node,
    }
}

func (mr *GenericNodeMonitoredResource) MonitoredResource() (string, map[string]string) {
    labels := map[string]string{
        "location":  mr.Location,
        "namespace": mr.NamespaceId,
        "node_id":   mr.NodeId,
    }
    return "generic_node", labels
}

func GetMetricType(v *view.View) string {
    return fmt.Sprintf("custom.googleapis.com/%s", v.Name)
}

func InitExporter() *stackdriver.Exporter {
    mr := NewGenericNodeMonitoredResource(ResourceLocation, ResourceNamespace, "public-data")
    labels := &stackdriver.Labels{}
    exporter, err := stackdriver.NewExporter(stackdriver.Options{
        ProjectID:               os.Getenv("GOOGLE_CLOUD_PROJECT"),
        Location:                ResourceLocation,
        MonitoredResource:       mr,
        DefaultMonitoringLabels: labels,
        GetMetricType:           GetMetricType,
    })
    if err != nil {
        log.Fatal("failed to initialize ")
    }
    return exporter
}

Exporterの設定ではStackdriver特有の設定を行うところがポイントで、このGoDocをとりあえずガン見することになると思います。

godoc.org

ここのOptions構造体のドキュメントをよく読んでおけば設定ではまることはあまりないはずです。あるとすれば、 MonitoredResourceDefaultMonitoringLabels あたり。基本的にStackdriver Monitoring側で事前定義されているようなラベルは MonitoredResource で作るわけですが、ちょっとはまりどころとして、これが monitoredresource.Interface 型であるということ。Stackdriver用の事前定義の設定ではGKE、GCE、EC2ぐらいしか使わない前提で構造体がほとんど作られていないので(パッケージ参照)、その他のリソースに関しては上のように構造体を自前実装する必要があります。

DefaultMonitoringLabels はそれ以外で固定でつけるようなラベルを入れておくと良いです。ドキュメントにもありますが、ここを設定しないとデフォルトは opencensus_task というラベルで値にプロセス名が入ったものが勝手に送られてしまうので、そうしたくない場合は空の *stackdriver.Labels を設定すれば大丈夫です。

OpenCensus Stats

Exporterの設定は上記ぐらいなので、次にメトリクスを取得する部分であるViewの設定です。これはベストプラクティスとして、View、Measure、Key、といったものはすべてパッケージグローバルで設定しておくというのがあります。

const (
    // OCReportInterval is the interval for OpenCensus to send stats data to
    // Stackdriver Monitoring via its exporter.
    // NOTE: this value should not be no less than 1 minute. Detailes are in the doc.
    // https://cloud.google.com/monitoring/custom-metrics/creating-metrics#writing-ts
    OCReportInterval = 60 * time.Second

    // Measure namess for respecitive OpenCensus Measure
    MeasureTemperature = "temperature"
    MeasurePressure    = "pressure"
    MeasureHumidity    = "humidity"
    MeasureWindSpeed   = "windspeed"
    MeasureWindDeg     = "winddeg"

    // Units are used to define Measures of OpenCensus.
    TemperatureUnit = "C"
    PressureUnit    = "hPa"
    HumidityUnit    = "%"
    WindSpeedUnit   = "mps"
    WindDegUnit     = "degree"

    // ResouceNamespace is used for the exporter to have resource labels.
    ResourceNamespace = "ymotongpoo"
)

var (
    // Measure variables
    MTemperature = stats.Float64(MeasureTemperature, "air temperature", TemperatureUnit)
    MPressure    = stats.Float64(MeasurePressure, "barometric pressure", PressureUnit)
    MHumidity    = stats.Int64(MeasureHumidity, "air humidity", HumidityUnit)
    MWindSpeed   = stats.Float64(MeasureWindSpeed, "wind speed", WindSpeedUnit)
    MWindDeg     = stats.Float64(MeasureWindDeg, "wind degree from North", WindDegUnit)

    TemperatureView = &view.View{
        Name:        MeasureTemperature,
        Measure:     MTemperature,
        TagKeys:     []tag.Key{KeySource},
        Description: "air temperature",
        Aggregation: view.LastValue(),
    }

    PressureView = &view.View{
        Name:        MeasurePressure,
        Measure:     MPressure,
        TagKeys:     []tag.Key{KeySource},
        Description: "barometric pressure",
        Aggregation: view.LastValue(),
    }

    HumidityView = &view.View{
        Name:        MeasureHumidity,
        Measure:     MHumidity,
        TagKeys:     []tag.Key{KeySource},
        Description: "air humidity",
        Aggregation: view.LastValue(),
    }

    WindSpeedView = &view.View{
        Name:        MeasureWindSpeed,
        Measure:     MWindSpeed,
        TagKeys:     []tag.Key{KeySource},
        Description: "wind speed",
        Aggregation: view.LastValue(),
    }

    WeatherReportViews = []*view.View{
        TemperatureView,
        PressureView,
        HumidityView,
        WindSpeedView,
    }

    // KeySource is the key for label in "generic_node",
    KeySource, _ = tag.NewKey("source")
)

パッケージグローバルにとどまらず、そもそもこうした変数や定数だけを持ったパッケージを公開するのも良いでしょう。実際、アプリケーション内の複数のマイクロサービスで共通で使われるようなメトリクスなどは、そうしておくことでViewの初期化が簡単になります。たとえば gRPC 用の事前定義パッケージでは、よく使われるViewが事前定義されています。

これらと先程作成したExporterを使ってViewを初期化する手続きはたったこれだけです。

func InitOpenCensusStats(exporter *stackdriver.Exporter) {
    view.SetReportingPeriod(OCReportInterval)
    view.RegisterExporter(exporter)
    view.Register(WeatherReportViews...)
}

ここで一つだけ注意したいのがViewがStackdriverにレポートを送る間隔の設定(SetReportPeriod)です。これはOpenCensusのドキュメントやStackdriver MonitoringのAPIドキュメントにもあるように、1分以下に設定してしまうと「間隔が短い」と怒られます。

Note: each exporter makes different promises about what the lowest supported duration is. For example, the Stackdriver exporter recommends a value no lower than 1 minute. Consult each exporter per your needs.

Don't make the calls faster than one time per minute.

値を記録する

あとは値を記録するだけです。 stats.Record で Measurement を記録するだけです。記録さえしておけば、Viewがよしなに設定した間隔でStackdriver Monitoringにデータを送ってくれます。

func RecordMeasurement(id string, w *Weather) error {
    ctx, err := tag.New(context.Background(), tag.Upsert(KeySource, id))
    if err != nil {
        logger.Errorf("failed to insert key: %v", err)
        return err
    }

    stats.Record(ctx,
        MTemperature.M(w.Temperature),
        MPressure.M(w.Pressure),
        MHumidity.M(int64(w.Humidity)),
        MWindSpeed.M(w.WindSpeed),
    )
    return nil
}

コードも貼ったので長く見えますが、たったこれだけの手順で簡単にStackdriver MonitoringにOpenCensusを使ってデータを送れるようになります。

参照

OpenCensus + Stackdriver Monitoring

OpenWeatherMap API

Dark Sky API

オブザーバビリティ(可観測性)がなぜ必要だと考えるのか

はじめに

こんにちは、Stackdriver担当者です。本記事は完全に個人の意見です。(念押し)

GCP的に担当製品がわかりやすいのでStackdriverの担当と書いてますが、仕事での担当領域的には「オブザーバビリティ (Observability、可観測性)」 です。この「オブザーバビリティ」という言葉が近年SREの文脈で語られることが増え、また今年に入って「入門 監視 ("Practical Monitoring" の日本語訳)」が刊行されたことで、日本でもより多く耳にするようになりました。

SRE サイトリライアビリティエンジニアリング ―Googleの信頼性を支えるエンジニアリングチーム

SRE サイトリライアビリティエンジニアリング ―Googleの信頼性を支えるエンジニアリングチーム

入門 監視 ―モダンなモニタリングのためのデザインパターン

入門 監視 ―モダンなモニタリングのためのデザインパターン

その「オブザーバビリティ」がなぜ必要だと考えるのか、自分なりに言語化してみました。

TL;DR

ハードウェア性能とシステムの開発スピードの向上、アプリケーションのコンポーネント化、パブリッククラウドサービスの普及により、システムの系全体の中でのアプリケーションの占める割合が大きくなった結果、オブザーバビリティの重要性が高まっている。

「オブザーバビリティ」の定義

「オブザーバビリティ」は昨今の盛り上がりによって耳にすることは増えたものの、その定義が明確に書かれていることが少ないので何を指しているのか、という疑問も同時に耳にします。しかし自分はそれは「マイクロサービスとはなにか」という疑問と同じ性質のもので、ある程度中心となる定義が徐々に認識されていき、定義の輪郭は人々によって違いがあるものではないかと考えています。

オブザーバビリティ(Observability、可観測性)という言葉は、最近のITシステム界隈では新しい概念として受け取られ、バズワードとして受け取られがちですが*1、自分は大学の学科が機械系だったこともあり、言葉そのものは馴染みがあるものとして受け取っていました。機械制御の分野ではオブザーバビリティ(可観測性)を次のように定義しています。

システムの出力からある時点でのシステムの内部状態を一意に知ることが出来る

これはコントローラビリティ(Controllabiliity、可制御性)と対をなす概念で、可制御性とは

システムへある入力を行うことで任意の最終状態に到達できる

と定義されています。

では、オブザーバビリティがITシステムの文脈で用いられる場合、一体どう定義できるでしょうか。この定義があやふやなのでバズワードと捉えられるのだと思いますが、私は次のような定義だと考えています。

オブザーバビリティとは、システムが運用する上で必要な内部状態の情報を取得できる状態にあること

つまりこれはシステムが持つ性質の性質の一つであり、たとえば

  • テスト可能性(Testability)
  • コンポーネント化可能性(Composability)
  • メンテナンス性(Maintenancability)

といった性質と同様に語られるべきものだと思います。*2

オブザーバビリティとモニタリング

基本的にITシステムは、自前で実装するプログラムや既存のアプリケーションの構成で作られるため、可制御性は担保されていると考えられるでしょう。*3

翻って、オブザーバビリティに関しては、アプリケーションのロジックには寄与しません。あくまでシステムに関わる側がシステムの内部状態を知る、あるいは振り返るためにログ、メトリクス、プロファイルといった出力を得られるように付加的に確保するものです。しかしながら、そうした性質を能動的に獲得していくことがオブザーバビリティには求められています。

オブザーバビリティと比較して挙げられる言葉で「モニタリング(Monitoring、監視)」があります。 "monitoring" は "monitor" の動名詞なので、語をそのまま捉えれば「監視をすること」です。これは運用者側の行為を捉えたものであり、システムの性質ではありません。

そういう意味で私は「オブザーバビリティ」と「モニタリング」は比較されること自体がおかしくて、本来は「モニタリングを円滑に行うためにオブザーバビリティを確保する」という形で、それぞれ車の両輪のようにお互いがあって成立する言葉だと考えています。

ではあえてなぜいまオブザーバビリティなのか

では、モニタリング(監視)自体は古くから行われていることなのに、あえてなぜいまオブザーバビリティという言葉が重要となっている(と考えられる)のでしょうか。それにはいくつかの理由があると考えています。

コンピューター性能の向上

計算資源がまだ潤沢でなかった時代では、アプリケーションそのものを動かすことですら性能的な制約が大きく、それ以外の負荷(外形監視やプロファイリング)を恒常的に行うこと(≒サンプリング)が制約上難しかったことがまず挙げられるでしょう。

また資源に対してハードウェア的な制限(ディスク、メモリ、ネットワーク)やOSやランタイム(JVMなど)といったアプリケーションの外側にあるもの(以下、簡単のため「インフラ」と呼ぶ)のシステム内での比重が特に大きかったこともあり、必然的にそちらに焦点を当てた監視が主流とされていました。

しかしながらコンピューター性能が向上し、システム全体においてのアプリケーションの比重が大きくなるにしたがって、アプリケーションそのものの監視の重要度も大きくなってきました。またアプリケーション以外に監視のためだけにエージェントを常駐させたり、外形監視を頻度高く行うことも可能となってきました。

アーキテクチャーの複雑化

またDevOpsが進み、それに伴ってコンポーネント化やコンテナ化が進む中で、昨今のようにマイクロサービスを導入する企業も少なくありません。そこまで行かなくても、アプリケーション、ミドルウェア、データベースと言った形でコンテナ化され、需要に合わせてインスタンス数を増減するといった構成も普通に採られるようになりました。

システム全体が分散したコンポーネント間の通信によって成立するようになり、あるリクエストを成立させるための一連のフローを追跡するためには、これまで見ていたようなインフラを主体とした監視よりも、コンポーネント間の依存関係と各コンポーネントでのボトルネックを視覚化するような監視の重要性が高まってきました。

実際2010年にDapperの論文が発表されてから、分散トレーシングは広く普及し、現在多くのAPM(Application Performance Management)バックエンドで採用されています。

これは、私はユニットテストの延長、あるいは実データを用いた結合テストの類だと考えています。実際コンポーネント間の依存関係が複雑になればなるほど、全体を通したテストを行うことは難しくなります。アプリケーション開発において、個々のコンポーネントではそれぞれ単体テストやシナリオテスト、あるいは負荷テストを継続的に行うことはだいぶ普及しているように思います。しかしながら、それらが複雑に絡み合ったシステム全体のテストはなかなか行えません。ステージング環境などでブラックボックステストを行っても、ユーザーではなく開発側が想定したシナリオで行っているため、実際にユーザーが行うリクエストを用いたテストはできません。

そういった背景において、ユーザーが作るリクエストを用いたテストとしてのオブザーバビリティという考え方ができるのではないでしょうか。

また同時に、複雑化したシステムを、少ない人数で効率よく運用するDevOpsであれば、そのための自動化のシグナルとして、システムから得られた指標を元にすることも必要になり、そのためにもオブザーバビリティは必要となるでしょう。

パブリッククラウドサービスの普及

マイクロサービスアーキテクチャに限らず、Kubernetesインスタンスオーケストレーションに利用する事例が増え、GCPのGKEをはじめ各社がKubernetesのマネージドサービスを展開し、アプリケーションがコンテナオーケストレーションの結果で捉えられる機会が増えています。さらに踏み込んで、Function as a Service のような形で、インフラを意識せずアプリケーションをモジュールとしてデプロイしていくことが当たり前になってきています。

このようなパブリッククラウドサービスを前提としたシステムになると、インフラそのものの管理よりも、その上で動くアプリケーションを動かすという方向に視点が移動します。監視をしていた対象が物理的なマシンから、一段抽象化されたコンテナであったり仮想マシンに変わり、インフラの運用者の役割も

  • 管理しているインスタンスの状態の管理
  • インスタンス上で動いている個々のアプリケーションコンテナの状態の監視
  • 連携しているコンテナ同士の状態の監視

という、いわば調停役のようなものに変わってきます。実際、パブリッククラウドを用いる場合、運用者はハードウェアを直接見ることはなく、個々のモジュールの役割に似た各種サービス(ストレージ、ロードバランサー、データベース、アプリケーションランタイムなど)が想定したとおりに稼働しているかを監視することが主な仕事となります。結局最終的にシステムに関わる人間全員が成し遂げたいことは「システム全体を健全な状態に保つ」ことなので、パブリッククラウドサービスに載せていくことでこのような視点の移動が起きるのは必然だと思います。

その上で、クラウドサービスの上に載せるアプリケーションにおいては、状態を知るためのオブザーバビリティ(ログ、メトリクス、プロファイル、トレースなど)の確保が肝心となってくるでしょう。つまりアプリケーション開発者がオブザーバビリティに必要なコードをアプリケーション内に書く(instrumentation: インスツルメンテーション)ことになります。

一方で、クラウドプラットフォームも、インフラを隠蔽した以上、運用担当者がその状態を意識できるように、各種サービスのオブザーバビリティをきちんと確保し、必要な情報を提供することがますます求められるでしょう。

システム運用者は、アプリケーションとクラウドプラットフォームの両者から得られる情報を整理し、管理していくことが仕事の上で大きな役割となります。

クラウドプラットフォームがモニタリングSaaSを提供する意義

自分はStackdriverというモニタリングSaaSを担当しています。他にも独立系のモニタリングSaaSは数多く存在していますが、そんな中でなぜクラウドサービス事業者がモニタリングSaaSを提供すべきなのでしょうか。

先にも述べたように、クラウドプラットフォームがインフラのインフラになっているいま、私はクラウドプラットフォームしか提供できない情報を提供する責務があるとともに、それをどういう形で確認すると運用者や開発者にとって有益であると想定しているかを示すためにもモニタリングSaaSを提供すべきだと考えています。

そうした形で提示することで、たたき台としてフィードバックが得られやすくなることも期待できるでしょう。先にも定義したように、オブザーバビリティというのはモニタリング製品を使うことではなく「必要な情報をどう取得するか」という部分に意味があると思うので、たたき台があることで多くの人が製品を利用することを通じて、必要な情報について考え、「問題解決のためにどういう情報がなぜ必要なのか」がフィードバックされることで、より運用しやすい環境が作られやすくなると思います。

オブザーバビリティについてワイワイやりたい

以上、つらつらと書いてきましたが、まだまだオブザーバビリティという言葉は普及段階です。ここから皆が手探りで進めて行くことになるかもしれません。しかし「オブザーバビリティ」それ自身はまったくの新しい領域というわけではなく、これまで「モニタリング」を行う上ですでに自然と行っていたような内容も多く含まれています。それらを踏まえつつ、今の時代のアーキテクチャーに合った形の指標の取り方を多くの人で共有できれば、普及を加速できるのではないかと思っています。

discord.gg

そういうことを@johtaniさん、@songmuさん、@ladicleさんなんかと話していて、なんか面白いことができそうという話になったので、勇み足かもしれませんが、「オブザーバビリティ」について意見が交わせられるような場が出来ればと思い、Discordのサーバーを立ててみました。まだ #general チャンネルしかありませんが、会話をする中自然と増えてくるかなーと思います。 特にここで話したいと思っていることは

  • オブザーバビテリティに関する問題とその解決案

です。イベントを企画してもいいのですが、平日夜だったり週末になってしまい参加しづらい人もいるでしょうし、地理的な条件で参加出来ない人もいるので、まずはオンラインメインでできないかなと考えています。オンラインで話したような内容をオンラインで共有できるような形にして、皆が参照できるようにして、そこからまた話が発展していけば最高です。オフラインイベントは、他の関連しそうなイベントにお邪魔したり、自分たちで開催するにしても、そこまで頻度の高さは考えずにできればいいなと思います。

とはいえ、最初からいろいろ考えてもしょうがないので、まずはオブザーバビリティに興味がある人が集まって、どのようなことに興味があるのか知りたいです。ぜひご参加ください!

おわりに

まだまだこれからもオブザーバビリティに関する議論はこれからも続くと思います。これからオブザーバビリティ界隈がどう盛り上がってくるか、楽しみです。

あときっと @songmu さんもオブザーバビリティに関する記事書いてくれるはず。

書かなかったこと

  • SREに関わる話(SLI・SLOやError Budgetの話)
  • サービスメッシュの話
  • メトリクスの標準化の話
  • E2Eモニタリングの話
  • 細かな製品の話
    • OpenCensus、Stackdriver、Istioなど

参照

「オブザーバビリティ」がなにかを考えるために過去に読んだ記事などのリストです。

書籍もいくつか

SRE サイトリライアビリティエンジニアリング ―Googleの信頼性を支えるエンジニアリングチーム

SRE サイトリライアビリティエンジニアリング ―Googleの信頼性を支えるエンジニアリングチーム

入門 監視 ―モダンなモニタリングのためのデザインパターン

入門 監視 ―モダンなモニタリングのためのデザインパターン

詳解 システム・パフォーマンス

詳解 システム・パフォーマンス

*1:オブザーバビリティという語の初出はTwitter Engineering blogだと記憶しています

*2:ソフトウェアのテスト可能性の中にもオブザーバビリティ(可観測性)が出てきますが、私は昨今の「オブザーバビリティ」はその延長であると解釈しています。

*3:実際は予期せぬ入力によって、予期せぬ出力が得られることがありますが、それを修正していくこと自体がソフトウェアの開発サイクルに入っていると思うので、ここでは担保されているものとします。

Raspberry Pi Zero WとStackdriver Monitoringで部屋の空気のモニタリングをする

はじめに

こんにちは、Stackdriver担当者です。同僚の @proppy と Pimoroni 見てたら、便利そうなHATとセンサーがあったので買って遊んでいます。

TL;DR

BME680で取得したデータをStackdriver Monitoringでダッシュボード作って見てみると楽しい。

BME680

BME680とは

BME680は、BOSCH製の空気質センサーで、この3mm四方の小さなセンサーだけで気温、湿度、大気圧、空気質が測定できます。

BME680

さらにこのセンサーのブレイクアウトがPimoroniから販売していて、I2Cを使って簡単に数値を読み取れるというわけです。

セットアップ

RaspberryPi Zero W は Raspbian (Stretch) が動いている前提です。

$ sudo apt-get install python3-venv i2c-tools read-edid libi2c-dev python3-smbus
$ python3 -m venv --system-site-packages .venv
$ .venv/bin/pip install bme680
$ wget https://raw.githubusercontent.com/pimoroni/bme680-python/master/examples/read-all.py
$ .venv/bin/python3 read-all.py

これでちゃんと数値が表示されたらBME680を無事に認識しているので準備完了。 これを使って部屋の気温、湿度、気圧、空気室などを記録してみます!

Stackdriver Monitoring へのデータの送信

Stackdriver Monitoringとは

Stackdriver Monitoringは、通常Monitoring Agentと呼ばれるエージェントをインスタンス上で実行し、システムやアプリケーションのメトリクスをサンプリングすることを想定しています。特によく利用されるデータベースやミドルウェアに関しては事前定義のメトリクスを簡単にStackdriver Monitoringに送信できます。

カスタム指標(カスタムメトリクス)

そして、もちろん自分のアプリケーションの状況を伝えるためのカスタム指標をエージェント経由で送信することもできますが、今回は別の方法としてStackdriver Monitoring API v3 を使って送信してみます。

cloud.google.com

今回は上のようにBME680のデータをPythonで取ってきているので、そのままPythonクライアントを使って送信しようと思います。

ライブラリセットアップ

上記に加えて google-cloud-monitoring のパッケージをインストールする必要がありますが、ここで一つ嵌りどころとして依存先の grpcio パッケージのインストールがあります。Raspberry Pi はSDカード上でOSを走らせているので、なるべくSDカードの寿命を延命させるために /tmp/var/tmp を tmpfs 上でマウントしたりします。自分も何も考えずにそうしていたのですが、これが grpcio パッケージのインストール時にネックとなります。

google-cloud-monitoring パッケージが依存している grpcio パッケージの最新バージョンは本記事執筆時当時は 1.19.0 です。執筆時の Raspbian Stretch でのデフォルトのPython 3のバージョンは 3.5 なのですが、上のPyPIのページを見るわかるように 1.19.0 には CPython 3.5 の ARMv6ターゲットの wheel がありません。 また piwheels を見ても 1.18.0 までは CPython 3.5 の ARMv6 ターゲットの wheel があるのですが、1.19.0 はありません。

そうなると手っ取り早い方法としては選択肢は2つなります。*1

  1. constraints.txt などで grpcio パッケージは 1.18.0 以前に固定(依存的には 1.8.0 以上であればよい)し、 --extra-index-url=https://www.piwheels.org/simple のオプションを pip install に加える
  2. grpcio 1.19.0 をローカルでビルドすることに決めて、 pip--build--cache-dir のオプションを /tmp 以外の場所に設定する

自分は新しいライブラリで問題ないかも確認したかったので grpcio 1.19.0 をローカルでビルドすることにして、何も設定せずにそのまま google-cloud-monitoring パッケージをインストールしました。(結構時間がかかる)

$ TMPDIR=$HOME/tmp .venv/bin/pip install --cache-dir=$TMPDIR/cache --build=$TMPDIR/build google-cloud-monitoring

1. メトリクスの作成

先に挙げたドキュメントのとおりなのですが、カスタムメトリクスを記録するためには、まずカスタムメトリクス自体をStackdriver Monitoringに登録する必要があります。 流れとしては

  1. Stackdriver Monitoringのクライアントのインスタンスを作成
  2. MetricDescriptorインスタンスを作成し、カスタムメトリクス名、メトリクスのタイプ、メトリクスの値の型、メトリクスの説明書き、メトリクスのラベルを設定
  3. クライアントに今作成した MetricDescriptorインスタンスを渡して、メトリクスを作成

のようになります。次は temperature というカスタムラベルを作成しています。実際にはセンサーが取得できる presssurehumiditygas_resistance についても同様に作成しています。

client = monitoring_v3.MetricServiceClient()
project_name = client.project_path(project_id)

descriptor = monitoring_v3.types.MetricDescriptor()
descriptor.type = "custom.googleapis.com/temperature"
descriptor.metric_kind = (
    monitoring_v3.enums.MetricDescriptor.MetricKind.GAUGE)
descriptor.value_type = (
    monitoring_v3.enums.MetricDescriptor.ValueType.DOUBLE)
rainfall_label = descriptor.labels.add()
rainfall_label.key = "rainfall"
rainfall_label.value_type = (
    monitoring_v3.enums.LabelDescriptor.ValueType.INT64)
rainfall_label.description = "rainfall observation report from weather forecast service"
descriptor.description = "room temperature"

descriptor = client.create_metric_descriptor(project_name, descriptor)

ここでメトリクスのラベルを設定していますが、これは取得するメトリクスに紐づいて取得時ごとに変化する付加情報を入れるためのものです。今回のサンプルでは温度や湿度を取るわけですが、その際に外の天気との関係性を知りたくなるでしょう。そのため、天気予報サイトから雨量の情報を取得し、 rainfall ラベルに入れることにしました。

実際のユースケースでは、例えばウェブアプリケーションの場合、「レスポンスデータのサイズ」をメトリクスにしていた場合、ステータスコードが400番台のものだけのグラフを作りたい場合に、「ステータスコード」をラベルとして設定しておきます。

またこの create_metric_descriptor はまったく同盟のメトリクスが存在する場合は特にエラーが起きず上書きされ、エラーになることはありません。(メトリクスが存在するかどうか事前に確認しなくても大丈夫です。)

これをプログラムの開始時にだけ1度実行します。

2. メトリクスデータの書き込み

こちらもドキュメントのとおりです。ある瞬間のセンサーデータを登録する場合の流れは

  1. TimeSeries インスタンスを作成
  2. カスタムメトリクス名、メトリクスのラベルの値を設定
  3. リソースのラベルを設定
  4. TimeSeries にメトリクスデータが入った Point を追加
  5. クライアント経由で TimeSeries をStackdriver Monitoringに送信
# 1. TimeSeriesの作成
series = monitoring_v3.types.TimeSeries()
# 2. カスタムメトリクス名、メトリクスのラベルの値を設定
series.metric.type = "custom.googleapis.com/temperature"
series.metric.labels['rainfall'] = fetch_rainfall()
# 3. リソースのラベルを設定
# https://cloud.google.com/monitoring/custom-metrics/creating-metrics#which-resource
series.resource.type = 'generic_node'
series.resource.labels['location'] = 'asia-northeast1-a'
series.resource.labels['namespace'] = RESOURCE_NAMESPACE
series.resource.labels['node_id'] = socket.gethostname()
# 4. `TimeSeries` にメトリクスデータが入った `Point` を追加
sensor = bme680.BME680() # 実際はプログラム起動時にインスタンス作成
sensor.get_sensor_data()
point = series.points.add()
point.value.double_value = value
now = time.time()
point.interval.end_time.seconds = int(now)
point.interval.end_time.nano = int(
    (now - point.interval.end_time.seconds) * 10**9)
# 5. クライアント経由で `TimeSeries` をStackdriver Monitoringに送信
client.create_time_series(project_name, [series])

リソースのラベルを設定する部分では generic_node を設定しています。(本来は global を設定すべきかも)リソースラベルはメトリクスラベルと違い、時間が経過してもそのデータを取得する上において変化しない属性を記録します。たとえば、マシンのホスト名などです。

どのリソースタイプでどのリソースラベルを設定するかは次のページに記載されています。

Point の追加で注意しなければいけないことは、TimeSeries を送信する際には Point が2つ以上含まれていてはいけないということ。公式ドキュメントを引用します。

When creating a time series, this field must contain exactly one point and the point's type must be the same as the value type of the associated metric. If the associated metric's descriptor must be auto-created, then the value type of the descriptor is determined by the point's type, which must be BOOL, INT64, DOUBLE, or DISTRIBUTION.

したがって、上のように Point を1つだけ追加しておしまいです。 *2

逆に TimeSeriesインスタンスはクライアント経由でまとめて送信できます。今回はセンサーで取れる値が温度、湿度、気圧、空気質の4つあるので、上と同じ流れで4つ TimeSeries をつくって、まとめて送信します。

これを10秒おきなど、定期的に実行します。

Stackdriver Monitoringの設定

以上のようなコードでデータをStackdriver Monitoringに送信し続けると、ダッシュボードでメトリクスとして選択できるようになります。

f:id:ymotongpoo:20190318084145p:plain

この自分で作成した custom/temperature のメトリクスを選択すると、送信されたデータを元に作成されたグラフが表示され、さらにFilterの項目ではリソースラベルとメトリクスラベルでの絞り込みができるようになっています。

f:id:ymotongpoo:20190318084514p:plain

それ以下、Aggregationの項目ではグラフの書き方などについての一般的な設定を行っていき、SAVEボタンでチャートを保存します。そのようにして以下、湿度、気圧、空気質に関しても同様に行っていくとダッシュボードが完成します。

f:id:ymotongpoo:20190318084736p:plain

部屋の温度が日の出とともにぐっと上昇する様子や、湿度が人間の生活時間に合わせて上昇する様子、今日は高気圧であるなとわかるなど、記録を取るだけでかなり面白かったです。次はこれを複数台に増やしてチャートに重ねてみようと思います。

参照

BME680

Stackdriver Monitoring V3

*1:他にも自分でQEMUを使ってビルドする方法などもあります

*2:Pointのintervalでend_timeしか設定していないのは、start_timeがoptionalだからです。詳細はproroファイルを参照。