Diary of a Perpetual Student

Perpetual Student: A person who remains at university far beyond the normal period

OpenTelemetryのテレメトリとMackerelのホストをResource Attributesで紐づけるためのOTelcol Processorを作りました

OpenTelemetryを使い始めるにあたって、既存の監視ツールからいきなり切り替えることは難しく、基本的には一時的に並行稼働させて様子を見ることになると思います。

これまでmackerel-agentをインストールし、Mackerelにホストとして登録してシステムメトリックを監視していたマシンに、新たにOpenTelemetry Collector+Host Metrics Receiverを導入するケースを考えてみましょう*1

Resource Detection Processorなどを併用することで、メトリックにホスト名などをResource Attributesとして付与することができます。しかし、比較のためにMackerel上のホストと対応させて眺めるには今一歩情報が足りません。

そこで、ホストにインストールされたmackerel-agentの設定ファイルを読み、Mackerel上のホストIDやURL、オーガニゼーション名といった情報をResource Attributesとして付与するMackerel Attributes Processorを開発しました。

github.com

以下のグラフは、実際にこのProcessorを利用してHost Metrics Receiver由来のメトリックにResource Attributesを付与したものになります。

属性名 属性値
mackerelio.host.id 4yWDxQP4GA5
mackerelio.host.url https://mackerel.io/orgs/arthur-1/hosts/4yWDxQP4GA5
mackerelio.org.name arthur-1

というような属性が付与されていることがわかります。

特定のattributeがあったらMackerelのホスト画面から対応するOpenTelemetryメトリックが見られる機能ができたらより捗りそうですね。

使い方

OpenTelemetry Collectorを自前でビルドするocbの設定ファイルで以下のように追記し、Mackerel Attributes Processorを含んだCollectorのバイナリを生成しましょう。

 receivers:
   - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver v0.103.0

 processors:
+  - gomod: github.com/Arthur1/opentelemetry-collector-arthur1/processor/mackerelattributesprocessor v0.4.0

 exporters:
   - gomod: go.opentelemetry.io/collector/exporter/otlpexporter v0.103.0

あとは以下のように設定ファイルを用意してOpenTelemetry Collectorを起動させればOKです。

 receivers:
   hostmetrics:
     collection_interval: 60s
     scrapers:
       memory:

+processors:
+  mackerelattributes:

 exporters:
   otlp/mackerel:
     endpoint: otlp.mackerelio.com:4317
     compression: gzip
     headers:
       Mackerel-Api-Key: ***censored****

 service:
   pipelines:
     metrics:
       receivers:
         - hostmetrics
+      processors:
+       - mackerelattributes
       exporters:
         - otlp/mackerel

実装

https://github.com/Arthur1/opentelemetry-collector-arthur1/blob/ff90509af4ac1c9861910e260b8149aa2262a96b/processor/mackerelattributesprocessor/processor.go ファイルがメインの実装部分になります。

起動時にmackerel-agent.confからAPIキーとホストIDファイルの設置場所を読み取っています。APIキーを読んでいる理由は、オーガニゼーション名は設定ファイルに載っておらずMackerel APIを叩いて取得する必要があるからです。ホストIDファイルがわかったらあとはそのファイルを読み取り、これら属性付与に必要な情報を変数に保存します。

以降はこの操作を1分に1回行います。ただし、mackerel-agent.confやホストIDファイルの更新日時を見て、変更されていなければスキップするようにしています。

processorとしてはリソースに紐づく様々なテレメトリがやってきたら、変数の値を読んでその通りにResource Attributesを付与しているだけのシンプルな実装です。このあたりはKubernetes Attributes Processorの実装を参考にしています。

最後に

https://github.com/Arthur1/opentelemetry-collector-arthur1リポジトリではOpenTelemetry Collectorの自作Component群と、それらを同梱した動作検証用のCollectorを公開しています。将来MackerelのOpenTelemetry Collectorディストリビューションが作れたら良いなと思い個人研究をしています。よかったらぜひ使ってみて感想を教えてください。

*1:mackerel-agentが収集するシステムメトリックはOpenTelemetry CollectorのHost Metrics Receiverで同等に取得可能であることはmackerel-agentが作るシステムメトリックグラフをOpenTelemetryで可能な限り再現する - Diary of a Perpetual Studentにてご紹介しました。

OpenTelemetry Metricsを手軽に投稿できるCLIツールotlcのv0.2.0をリリースしました

blog.arthur1.dev

でご紹介した、OpenTelemetry Metricsを手軽に投稿できるCLIツール「otlc」のv0.2.0をリリースしたので再度ご紹介します。

github.com

使い方

インストール

(macOS, Linux)×(x86_64, arm64)向けのバイナリをGitHubのReleaseで配布しています。

Homebrewをお使いの方は以下のコマンド1つでインストールが可能です。

$ brew install Arthur1/tap/otlc
$ otlc --version
otlc is a Command-line Tool to Export Telemetry by OTLP (OpenTelemetry Protocol).
Version:    unknown
Go version: go1.22.4
Arch:       arm64

go install派の方はこちらからどうぞ。こっちならWindowsでも動くかもしれない(未検証)。

go install github.com/Arthur1/otlc/cmd/otlc@latest

メトリックの投稿

まずは環境変数に投稿先の情報を入れます。今回はMackerelのOpenTelemetry対応機能に投稿してみましょう。これらの情報は環境変数を利用しなくても対応するオプションでセット可能です。(詳しくは--helpオプションで確認してください。)

export OTEL_EXPORTER_OTLP_ENDPOINT="otlp.mackerelio.com:4317"
export OTEL_EXPORTER_OTLP_HEADERS="Mackerel-Api-Key=***your_api_key***"

その後、以下のようなコマンドを実行すると、上記で設定した宛先にOTLPでメトリックが投稿されます。

  • メトリック名: awesome.metric
  • メトリック種類: Gauge
  • データポイント属性: hoge="poyo"・fuga="1"
  • データポイント値: 123.45
  • データポイント時刻: 現在
otlc metrics post --name awesome.metric --attrs hoge=poyo,fuga=1 123.45

ちょっとしたメトリックを継続的に投稿する時や検証の際に、SDKを使ったプログラムを書かなくてもコマンドラインでシュッと投稿できて便利です。

どうぞご利用ください。

v0.1.xとの差異

駆け出しなので結構破壊的な変更をしました。今後は仕様を安定させるつもりです。早くv1に上げたいのでフィードバックお待ちしています。

設定ファイルの廃止とOTel Exporterの標準的な環境変数の読み取りに対応

v0.1.xでは投稿先を設定する際にYAMLの設定ファイルを書くのが標準のやり方だったのですが、これを廃止しました。代わりに、同様の情報をコマンドラインオプションで指定できる他、OTLP Exporter onfigurationで定義された名前での環境変数の読み取りに対応しました。

--valueオプションの廃止

メトリックの値を指定する際に--valueオプションを利用していましたが、これをやめて単に引数としました。

--timestampオプションの追加

--timestampオプションでメトリックの時刻をUNIX秒で指定できるようになりました。

otlc metrics post --name awesome.metric --attrs hoge=poyo,fuga=1 --timestamp 1719276600 123.45

過去の時刻で投稿したため、先ほどのグラフが点から線になりました。

投稿時に異常終了するように

メトリックが投稿できない場合、これまではエラー内容がログに出力されるだけでしたが、コマンドとして異常終了するようになりました。

$ otlc metrics post --name awesome.metric --attrs hoge=poyo,fuga=1 123.45
otlc: error: failed to upload metrics: rpc error: code = PermissionDenied desc = authenticate error, please check mackerel-api-key
$ echo $?
1

実装の変化

元々はアプリケーションを計装する際に用いるOpenTelemetry SDK GoのMeterとInstrumentを使って実装していました。Instrumentでメトリックを生成したのちにMeterProviderをすぐにシャットダウンすることで即時に投稿することを期待した、という形です。

https://github.com/Arthur1/otlc/blob/27866d6f826d6aa267f1f0de1687fddfe50b89b9/metrics/post.go#L37-L87

前回のブログで

より低いレイヤーのメソッドを利用して組み立てるとより自由なコマンドラインツールに仕上げられるかもしれません。

と書いた通り、MeterやInstrumentに頼る実装を改め、メトリックデータを直接生成してエクスポートするようにしました。

https://github.com/Arthur1/otlc/blob/94f56eb8612042f799a80664a54110ee6ef5ed5a/internal/metric/generate.go#L23-L76

メトリックの時刻が指定できるようになったり、投稿失敗時に異常終了するようになったりしたのはこの置き換えによるものです。

IPsec VPN実装であるLibreswanの状態を監視できるMackerelプラグイン

IPsec VPN実装であるLibreswanの状態をメトリックとして見られるようにするMackerelプラグインを作りました。

github.com

strongSwanのものは先人が作っていましたが、Libreswanのものがなかったので自作した形です。

nonylene.hatenablog.jp

何が見られるか

Libreswanで作ったVPNサーバの状態が見られます。

  • IPsec Connectionの合計(active/loaded)
  • IPsecのSecurity Associations(total/authenticated/anonymous)
  • IKEのSecurity Associations(total/open/half-open/authenticated/anonymous)

インストール

mkr installに対応した形式で配布しています。

sudo mkr plugin install Arthur1/mackerel-plugin-libreswan

でインストールして

[plugin.metrics.libreswan]
command = ["/opt/mackerel-agent/plugins/bin/mackerel-plugin-libreswan"]

と書けば動きます。

Docker上で動かしているLibreswanにも対応

以下のようにVPNサーバをDockerで動かしている例もあるでしょう。

github.com

ipsecコマンドが実行できるコンテナ向けにこのプラグインを実行したければ、-docker-execオプションを活用できます。例えば、

$ docker exec -it ipsec-vpn-server ipsec version
Libreswan 5.0

のようにしてipsecコマンドを実行できる環境の場合には、

[plugin.metrics.libreswan]
command = ["/opt/mackerel-agent/plugins/bin/mackerel-plugin-libreswan", "-docker-exec", "ipsec-vpn-server"]

のように、-docker-execオプションとしてコンテナ名を渡してあげると動きます。

OTel Collectorでrunnによるシナリオテストの結果を投稿できるrunnreceiverを作りました

blog.arthur1.dev

の続編です。今回はrunnによるシナリオテストの結果を投稿するツールをOpenTelemetry CollectorのReceiver (Scraper)として作ってみました。

リポジトリ

以下のリポジトリで id:arthur-1 によるOpenTelemetry Collectorの自作Component群を公開しています。

github.com

使い方

ビルド

ocb (builder)によってOpenTelemetry Collectorをカスタムビルドする必要があります。カスタムビルドの方法は公式のドキュメントBuilding a custom collector | OpenTelemetryを参照してください。

runnreceivcerをビルドに含める場合には、ocbのconfig YAMLのreceivers部分に、以下のように行を追加しましょう。

 receivers:
+   - gomod: github.com/Arthur1/opentelemetry-collector-arthur1/receiver/runnreceiver v0.2.0

 exporters:
   - gomod: go.opentelemetry.io/collector/exporter/otlpexporter v0.101.0

config

利用する際は以下のようにOpenTelemetry Collectorのconfigを記述します。runbooksにはrunnのYAMLの文字列の配列を記述してください。

receivers:
  runn:
    runbooks:
      - |
        desc: blog.arthur1.dev's test
        runners:
          req: https://blog.arthur1.dev
        steps:
          test1:
            req:
              '/':
                get:
                  body: null
            test: current.res.status == 200
          test2:
            req:
              '/hoge':
                get:
                  body: null
            test: current.res.status == 404
    collection_interval: 1m

exporters:
  otlp/mackerel:
    endpoint: otlp.mackerelio.com:4317
    compression: gzip
    headers:
      Mackerel-Api-Key: ***censored***

service:
  pipelines:
    metrics:
      receivers:
        - runn
      exporters:
        - otlp/mackerel

これで./otelcol-custom .--config ./otelcol-config.yamlのようにしてOpenTelemetry Collectorを実行すると、以下のように、テストが成功したかどうかと、テストに含まれる各ステップの所要時間をメトリックとして投稿します。(以下のグラフはMackerelのOpenTelemetry対応機能によって生成されたグラフを公開したものです。)

将来的にはメトリックだけでなくトレースとしても投稿できるようにする予定です。

おまけ:Receiver自作に関する情報

先日、Pepabo Tech Conference #22 春のSREまつりに登壇させていただき、「otelcol receiver 自作RTA」というタイトルでLT(というよりライブコーディング実演)しました。

speakerdeck.com

こちらのスライドでは、OpenTelemetry CollectorのReceiverを自作する際の流れや気をつけたいTIPSをご紹介しています。ぜひご覧ください。

日本語で書かれた他のエントリにはないおもしろポイントは、

  • mdatagenは現在単純なgo installでインストールできないのでソースコードを持ってくる必要がある
  • mdatagenで記述するYAMLをVSCodeで快適に書くためにJSONスキーマを自作した https://github.com/Arthur1/otelcol-metadata-schema

あたりです。

Go言語のloggerをDefault1つで済ませる方法:slog Handlerがcontextの中身を見てよしなにするパターン

先日、kamakura.go #6にて、「slog登場に伴うloggerの取り回し手法の見直し」という題で登壇しました。Go Conference 2024にこの内容でproposalを出していて落選してしまったのですが、kamakura.go #6のテーマ「昔のGo、今のGo」とピッタリだったので応募して発表の機会を頂いたという経緯です。

speakerdeck.com

この発表は以下のエントリのrefinement版となる内容です。今度は机上論ではなく実践経験をもとに話を組み立てています。

blog.arthur1.dev

loggerの引き回しと、なぜそれが必要だったのか

あるアプリケーション・モジュールの構造化ログについて、常に特定のフィールドを付与したいというニーズがあります。例えばWebアプリケーションなら、ソースコードの関数・モジュール名、リクエストID、ユーザIDといったログの検索に有益な情報をログに付与したいでしょう。

その時に、ログの出力命令ごとにいちいちフィールドを指定しまくるのは大変です。単純にコード量が多いし、仮に付与したいフィールドが増えたときに一行一行変更して回らないといけません。ログに出したい変数を持って回る必要もあります。

log.WithFields(log.Fields{"module": "userService", "user_id": 1}).Info("validation passed")
...
log.WithFields(log.Fields{"module": "userService", "user_id": 1}).Info("succeeded")

(このあたりのサンプルコードは構造化ログのライブラリとしてhttps://github.com/sirupsen/logrusを使用したケースです。)

そこで、必要に応じてloggerインスタンスを作り、呼び出し先でも利用できるように引き回す手法があります。例えば、ユーザIDが確定した時点でユーザIDを常にログに含むようなloggerを作って、それ以降の処理ではこのloggerを利用してログを書くといったやり方です。

logger := log.WithFields(log.Fields{"module": "userService", "user_id": 1})
logger.Info("validation passed")
...
// loggerを作った命令とは別の関数の場合、loggerをここでも使えるように引き回す必要がある
logger.Info("succeeded")

loggerの引き回し手法はいくつかありますが、私個人としては以下の記事で推奨されている手法の1つである「contextにloggerを入れる」パターンが好みです。

zenn.dev

package mylog

import "context"

type key struct{}

func ContextWithLogger(ctx context.Context, logger Logger) context.Context {
    return context.WithValue(ctx, key{}, logger)
}

func FromContext(ctx context.Context) Logger {
    return ctx.Value(key{}).(Logger)
}
func A() {
    logger := log.WithFields(log.Fields{"user_id", 1})
    ctx := mylog.ContextWithLogger(context.Background(), logger)
    B(ctx)
}

func B(ctx context.Context) {
    logger := mylog.FromContext(ctx)
    logger.Info("Hello, world!")
}

ただし、この手法はloggerを利用したい関数でcontextからloggerを取り出す命令を書かなければいけないので、そこだけ若干煩雑でコードの美しさが損なわれると感じていました。

slogの特徴

さて、Go 1.21からはGoビルトインのlog/slogパッケージが登場しました。https://go.googlesource.com/proposal/+/master/design/56345-structured-logging.mdがプロポーザルの内容で、こちらを読むとどのような思想で設計されたのかを知ることができます。

Logger/Handler

LoggerとHandlerという2つのインタフェースに概念が分離されました。

Loggerのほうはフロントエンド・ファサードのような立ち位置で、たとえばloggerのInfo(msg string, args ...any)というメソッドを呼ぶことでINFOレベルのログが書けますというインタフェースが定義されています。

Handlerは実際にログのレコードを作って書き込むという処理を担います。text、jsonといった出力フォーマットを変えることができるのはもちろん、ログレコードに特定のフィールドを追加するといった処理も可能です。

Loggerのインタフェースとして、contextを引数に持つ関数が用意される

InfoContext(ctx context.Context, msg string, args ...any) のように、ログを出力する関数の引数にcontextを持つものが用意されました。Handlerは渡されたcontextの値を利用することができます。

デフォルトLogger

従来のlog packageと同様にslog.SetDefault(l *Logger)というデフォルトのloggerを登録しておける関数が用意されています。デフォルトのloggerを登録しておくと、loggerのインスタンスからログ出力関数を呼び出さなくてもslog.Info()のようにpackage名から直接ログを書き出すことができます。

実装としてはグローバル変数にloggerが格納されているのと大差ないのですが、複数のgoroutineから呼ばれことを考慮してsync/atomicが利用されています*1

contextの中身を見て自動でフィールドを追加するHandlerがあれば……

これらの特徴を見て私は「場所によって構造化ログに自動で入れたいフィールドを制御したいという理由だけで、リクエストごとloggerを作って引き回す必要はなくなったのでは」と思いました。具体的には、contextの値を読み取って自動でフィールドを追加するようなHandlerを作り、これを用いて作ったloggerをデフォルトとして登録してしまえば、リクエストごと作ったloggerを引き回す必要がなくなるという見立てです。

こういった方針で作ってみたのが以下のコードです。contextに含まれる特定のキーの値をHandlerがログレコードのフィールドに追加するようなHandlerができました。

package main

import (
    "context"
    "log/slog"
    "os"
)

var keys = []string{"user_id", "request_id"}

type MyLogHandler struct {
    slog.Handler
}

var _ slog.Handler = &MyLogHandler{}

func (h *MyLogHandler) Handle(ctx context.Context, r slog.Record) error {
    for _, key := range keys {
        if v := ctx.Value(key); v != nil {
            r.AddAttrs(slog.Attr{Key: string(key), Value: slog.AnyValue(v)})
        }
    }
    return h.Handler.Handle(ctx, r)
}

func main() {
    logger := slog.New(&MyLogHandler{slog.NewJSONHandler(os.Stderr, nil)})
    slog.SetDefault(logger)
    A()
}

func A() {
    ctx := context.WithValue(context.Background(), "user_id", 1)
    ctx = context.WithValue(ctx, "request_id", "000")
    B(ctx)
}

func B(ctx context.Context) {
    slog.InfoContext(ctx, "Hello, world!")
}

https://go.dev/play/p/pjs7A-WNptj

slog.InfoContext(ctx, "Hello, world!")という呼び出しで

{"time":"2009-11-10T23:00:00Z","level":"INFO","msg":"Hello, world!","user_id":1,"request_id":"000"}

というログが書き出されます。

任意のkeyでもログに書けるようなHandler

先ほどの手法では、ログに書き出したいcontextのフィールドのリストを定義しておく必要があります。しかし、ログに書き出したいものが増えるたびにリストを編集するのは大変だし、そもそもloggerという存在がアプリケーションの内部のことを知っている(たとえばrequest_idというログに書き出したい存在がいること)ように見えて微妙な感じがします。

contextにsync.Mapを持ち、その中身をログに書くHandler

ログに書き出したいcontextのkeyを1つ1つ管理しなくても良い手法として、contextにmapを持ってしまい、handlerとしてはmapの中身を全部書き出してしまうやり方があります。mapのキーだけ決めてしまえば良いです。

  • context
    • user_id <- このキーをログに出すと管理
    • request_id <- このキーをログに出すと管理

という構造だったのが、

  • context
    • logcontext (map) <- このキーをログに出すと管理
      • user_id
      • request_id
      • その他なんでも

という構造になるイメージです。

ただし、goroutine safeになるようにただのmapではなくsync.Mapを使うと良いでしょう。実際にこの指針で実装されたslog HandlerがGitHubで公開されています。

github.com

2024-05-22 追記:上記slog-contextの実装は、親のcontextに入っているmapを子から破壊する実装になっていました。issueとPRが出ていたのでそのうち直されるかも WithValue on contex with existing fields affects the parent context · Issue #5 · PumpkinSeed/slog-context

OpenTelemetry Baggageの中身をログに書くHandler

また、ログに書き出したいものの置き場としてOpenTelemetryのBaggageを利用するのも手です。

opentelemetry.io

Baggageはテレメトリのためのコンテキスト情報をspan(service)をまたいでも伝達するためのキーバリューストアです。Baggageの基本的な使い方として挙げられるのは、下流のサービスに上流のサービスのコンテキスト情報(何らかのIDとか)を受け渡し、下流のサービスではBaggageから値を取り出してspanのattributeに追加する、といった流れです。Baggageに入れるのはテレメトリに付与したい情報なので、その中身をログに書きだしても大抵は不都合がないでしょう。

OpenTelemetry Baggageをcontextから取り出してログに書くslog handlerについても、すでにサードパーティの実装があります。Baggageの中身だけでなくトレースIDもログレコードに付与してくれるので、ログとトレースを紐づけたいときにとても便利です。

github.com

まとめ

典型的なWebアプリケーションにおいて、「ここだけはJSONログにしたい」など複数のslog handlerを併用したいことは少ないと感じています。やりたいのは高々ログに付与する属性を制御したい程度でしょう。その役割はcontextの中身を見てよしなにするリッチなhandlerに任せることで、リクエストごとにloggerインスタンスを作る必要がなくなります。リクエストごとloggerが作られないのなら、頑張って引き回す必要もなくなり、デフォルトのloggerに登録しておけばどこでもslog packageから呼び出してシンプルにログを書き出すことができます。Webアプリケーションのloggerとしてはこの考え方で十分実用に適うと思います。

エクストラバトルでしばらく使っていたドラゴンルギアを紹介します

普段は技術記事やしょうもない日記を書いている当ブログですが、ポケモンカードの話をこのブログで書きたいなと思ったのでやります。

記念すべき第一弾として、エクストラバトルでしばらく使っていたドラゴンルギアを紹介します。このブログ自体はずっと前から用意していたけど、そろそろ旬が過ぎてしまうので仮完成の状態で出します。

ルギアを握っているのはルギアVが好きだから以上には特にないです。スタンでもルギア使いがち。

デッキ解説

こうして眺めてみるとCL2023優勝ルギアと大体同じですね。

note.com

構築思想

大会ではなくエクストラバトルで優勝できそうかという観点を重視しています。なぜならエクストラの資産をあんまり持っていなくて、プロモカードがいっぱいもらえると嬉しいからです。

エクストラバトルならルギア・レジドラゴ・うらこうさくリザードン・ミュウVmax・ミライドンex・テツノイバラexあたりが多いかな〜という気持ちで組みました。CL愛知2024の結果からエヴォコンも増えてきたので、カード解説ではエヴォコンにも触れています。

先攻/後攻

当初はジャンケンに勝ったら後攻を取っていましたが、構築の方向性的に先攻でも全然アリだな〜という感じです。上振れたらやっぱり後1で起動するのが強いので今のところ後攻を選びつつ、先攻になっても落ち込まない気持ちでいます。

どちらでも良いなら先攻後攻どちらを渡したくないかという観点で決めたいのですが、レジドラゴ、うらこうさくリザードンあたりは先行が欲しく、アオセグ、エレキレイン、エヴォコン、一般的なルギア、ミライドンあたりは後攻が欲しいという環境。なんとも言えないですが、先攻を意識したデッキが増えてきたとはいえまだまだ後攻を選びたい人が多そうな気がします。

ポケモン

ルギアV/ルギアVstar 3枚/3枚

これが立たないと話にならないので、採用枚数としては3-3か4-3の2択を取る人が多いんじゃないかなと思います。4-3にしておけば良かった~と後悔することがあんまりないので今のところ3-3でやってます。

アーケオス 3枚

先攻1ターン目や、後攻1ターン目にミツル→アッセンブルスターの流れが決まらない場合、アーケオスのトラッシュを先にしてしまうと、キリンリキにロストおくりされてしまう可能性があります。これをケアせずにプレイしたい人は4枚入れると良さそうです。ケアする場合には、対戦相手のデッキにキリンリキが入っていそうかエスパーし、入ってそうだなと思ったらバトコンを後回しにする(サイド落ちを確認する手段としてはクイックボールを先に使う)とか、1枚だけアーケオスを落としておくとか、うまいことやりましょう。

エヴォコン対面だと相手の盤面が整うまでにどれだけ頑張れるかゲー(いかにエネルギーを割ったり、グッズロックでサーチャーを縛ったりする余裕をなくすか)なので、1つアーケオス立てばなんとかなるかという気持ちでキリンリキを割り切るのも大事かもしれません。アーケオスよりグズマを送られる方が辛い。

ガブリアスV 1枚

あと10点あればなあと思うこともありますがまあ最強です。このデッキはHP280クラスのポケモンを一発で取る大きな打点が出せるようには組まれておらず、サイドは2-2-2と取るのを基本プランとしています。その戦略が成り立つのは、グズマに頼らずにベンチにいるサイド2枚のシステムポケモンを取れるガブリアスVの存在あってこそでしょう。

グズマの利用に困らない意味でも逃げエネ0なのは嬉しいポイントですが、それが仇となることもあります。ジージーエンド打ちたい盤面でダブドラエネを逃げで剥がしてスペシャルチャージで山に戻すということができないので、ダブドラエネがサイドにあるときは頭の片隅に入れておくと良いでしょう。

殴った後盤面にエネルギーが残らないというのも実は強いポイントで、エヴォコンのジラーチの無敵効果を使わせないプレイができます。当然不利な対面ではあるものの、グッズロックするラフレシアを取れる流れが運よく作れたら、その後に意識したい動きです。

アーゴヨン&アクジキングGX 1枚

2-2-2プランの最後をカオスオーダーGXで決めるのがメインの使い方。特性ぼうしょくでアーケオスをトラッシュすることもできます。

ガブリアス&ギラティナGX 1枚

後1ジージーエンドGXでイージーウィンを狙うのはもちろん、不慮の事故や不利対面時にも、ジージーエンドを打ちつつナンジャモ・マリィによる手札干渉を組み合わせると2ターン以上もらえる可能性がでてきます。テツノイバラexデッキに逆転して勝てたのはこいつのおかげです。アタッカーやシステムポケモンをトラッシュに送りエネルギーテンポや進化テンポをゼロにして、さらに後続が立ちそうならガブリアスVやグズマを利用して狩るといった流れですね。ミラー時にアーケオス2体をトラッシュさせるのもじゃんけん敗北時には定番です。むしろ後1ジージーエンドにこだわりすぎないぐらいの気持ちでやっています。

ドラピオンV 1枚

ミュウVmaxデッキはファンが多い印象があって、スタン落ち後もエクに持ってくる人多いだろうな~と思ってピン刺し。実際には1回も当たってないです。

ドラピオンを採用しても雑に投げると負けます。例えば、6→4→1とサイドを進める(Vポケモンを取ってから、ミュウVmaxをドラピオンVで取る)と、返しのN・ナンジャモで前のミュウVmaxを取る手段も避ける手段もなくて捲くられることがあります。2回目はガブリアスVで裏のサイド2枚のポケモンを取って(6→4→2)、ドラピオンとアゴアクジ両方のプランを取れるようにしつつ、手札干渉への耐性を少し上げておくのがよさそうです。手札2枚まで減らされた状態ならターン開始の1ドローで手札3枚になるので、パソコン通信やハイパーボールも当たり札になります。

そもそもミュウVmaxもこくばバドレックスVmaxも環境になかなかいないので、この枠は抜いてしまって別のアタッカーにしても良いかもしれません。

今自分が回しているリストではこの枠をハバタクカミにして、テツノイバラexやソーナンスへの解答を作ったりうらこうさくリザードンのプレイングミスを拾ったりしています。2点を後ろに置けるのも結構ちゃんと生きる印象です。

大会はさておきエクストラバトルで当たりがちなうらこうさくリザードンをちゃんと見るなら、次弾「ナイトワンダラー」で登場するアンチプラズマのキュレムを入れてみたいですね。

キリンリキ 1枚

ミラー対面で後1アッセンブルスター決められなかった相手のアーケオスをロストに送る、コントロールデッキのサポートを送る、サイド1枚のポケモンで1ターンしのぎつつ後ろを取られないようグズマを送る、レジドラゴのわざマシンもといポケモンを送る、などなどの使い方があります。でも冷静に考えると1ターンロスしている(特に280打点を出せる対面では1-2-3とサイドを取られてしまう)ので、普通に戦っても勝てる試合を別ルートに歩んだだけのことも多いです。レジドラゴについてもメタれているようで少し物足りないところもあります。まあエクストラ普通に疲れるのでミラーのイージーウィンのルートが増えているのは悪くないでしょう。

入ってると思われたら警戒され対策されるので、「ルギアにキリンリキ入ってるんだ~」と(実際に僕が)言われているうちが華でしょう。とはいえ入らないこともないでしょとは思っていますが。

マインドショック70点を使う展開もあるにはあるのですが、この技は弱点・抵抗力を計算しないことに注意してください。一回これでやらかしました。

カプ・テテフGX 2枚

序盤はミツルや博士、中盤以降はグズマにアクセスできるカードとして優秀なので2枚欲しいです。こいつがバトル場スタートすることがそれなりの確率であるのが残念ですが、最悪殴れるので良いということにしておきましょう。ただしこの技もキリンリキ同様に弱点・抵抗力の計算がないので以下略。

ガブリアスVの解説で触れた盤面にエネルギーを残さないプレイをするために、1枚ネオラントVにしてプリズムエネルギーを1枚採用するのも面白いかもしれません。サポートを持ってくるポケモン枠に2枚割いているなら、片方はソーナンスを割り切っても大丈夫な気がします。ネオラントVで120ダメージ当てておくと、ガブギラのカラミティエッジと合わせて360点見れるのも良いですね。

デデンネGX 1枚

デッキの1/10に当たる6枚引くことができるので、後攻の動きの再現性を高めるのに貢献します。終盤にサーチャーなどのキーパーツをサポ権なしで引くのに取っておけることもあります。

クロバットVが不採用の理由も書いておくと、ベンチ枠が足りないのと、そこまで後1アッセンブルスターに拘ってないというところがあります。ワンショットデッキみたいに無理やり回すより、一旦落ち着いて勝ち筋を探る方が好みです。ドラゴン型ルギアは手張り権だけで主要なポケモンが殴れたり、ジージーエンドからひっくり返したりできるので、意外と勝ちを拾えます。

グッズ

クイックボール/ハイパーボール 4枚/4枚

序盤の理想ムーブの実現に必要なポケモンが多いので4枚ずつの採用です。

ハイパーボールはルギアVstarが立ってアッセンブルスターした後は1枚多く山札圧縮できるぐらいのメリットでしかないので、中盤以降はバトルコンプレッサーで落としがちです。クイックボールはなんだかんだ残しておきたい展開が多い印象。

バトルコンプレッサー 4枚

序盤にアーケオスやバトルサーチャー経由で利用したいサポートを落とす、中盤以降は山札の圧のための利用します。序盤に安定してアクセスできる手段が少ないのが残念ポイント。ここが解消されるまで4枚から減らすことはないでしょう。

スペシャルチャージ 4枚

博士で落ちる分のケアや、ガブリアスやガブギラを安定して運用できるという面から4枚採用しています。序盤でデデチェンジ打つことが確定している時のクイックボールのコストにしがち。

フィールドブロアー 2枚

グッズロックされなければダストダスによる特性ロックを解除する手段になります。レジドラゴからグッズロックされたら気合いでキリンリキを用意してオンバーンGXをロストに送るか(盤面に余裕がありグッズロックされる流れになりそうならあらかじめ出しておくのも手かも)、運良く手札にあるザオボーを使います。それ以外だと、勝ち切る盤面で自分が貼ったサイレントラボを剥がすぐらいでしか出番がない(雪道ミュウのようなデッキとあまり対面しない)印象があります。もちろん雑にかるいし剥がせるのも強い。

レスキュータンカ 1枚

ガブリアスやデデンネテテフの再利用はもちろん、バトルサーチャーのポケモン版としての運用(バトコンで落としてから拾う)もできます。便利。

パソコン通信 1枚

説明不要のエーススペック枠。と書いていたものの、シークレットボックスの登場で話は変わってきたかもしれません。ハンド3枚切るのは結構しんどいデッキではあるので、今のところパソコン通信のままにしています。

バトルサーチャー 3枚

サイド落ちの枚数をちゃんと把握しておかないと痛い目を見ます。対面のデッキからどのサポートをいつ何回使いたいかをある程度ゲームプランとして思い描いておくと無駄使いを防げます。エクストラは座学も大事です。

Stall系のデッキ相手に対して入れ替え手段をグズマ以外に持っていないため、グズマをロストに送られないようにすぐサーチャーで回収する展開も覚えておくと役に立つかもしれません。

サポート

博士の研究 2枚

グッズロックされた際の動きが極端に鈍くならないように多投(少なくとも2枚)しておきたいです。モクナシ(ラフレシア)はあんまり見ないけど、オンバーンGXのディストーション・クワガノンVのパラライズボルトは意識したいです。

ゼイユという先行1ターン目に使える神サポートがやってきたので、博士かマリィを1枚ゼイユに置き換えても良いかもしれません。

マリィ 1枚

運悪く後攻アッセンブルセットが揃わなかった時にこれでお茶を濁しています。スタジアムに妨害系のサイレントラボを採用しているのと相性が良い。

ナンジャモ 1枚

Nとナンジャモの選択ですが、ナンジャモにしました。相手が手札に溜まったままの不要なカードを再度引くのを狙うより、自分が勝ちに行くためのパーツを引くことの方が相対的に大事なデッキかなあと考えたからです。

ミツル 1枚

2枚採用にしている人も多いかと思いますが、サイドに埋まっていてもなんとかなるデッキでしょの精神で1枚にしています。あとミツルSRを2枚揃える余裕がないです。

ザオボー 1枚

ダストダスのかるいしを剥がす、ダブルドラゴンエネルギーやダブル無色エネルギーに頼っているデッキのエネルギーテンポを失速させる、などの使い道があります。

グズマ 1枚

高級ポケモン入れ替え。グズマの運用については他のカードの解説で結構触れた気がするのでここでは特に書きません。

スタジアム

サイレントラボ 3枚

ミライドン系統に貼ってグズマハラがないことを祈ったり、テツノイバラexの特性を止めたり、カプ・テテフなどを使わせないようにしたり。エクストラバトルではうらこうさくリザードンに当たる率が高いなと感じており、うらこうさく軸デッキには全く刺さらないのが残念なところ。もはやスカイフィールド型にしても良いのですが、ウソッキーを採用しているデッキもそれなりに見かけるので現状はサイレントラボにしています。

エネルギー

ダブルドラゴンエネルギー 4枚

ジージーエンドラッキーWINを見ている以上ここが3枚になることはありません。

ダブル無色エネルギー 3枚

ずっと4枚でやっていたのですが、後述するエネルギーを採用したくなり1枚削りました。まあルギアで最後まで殴り続けることはあんまりないのでそこまで困らないです。

パワフル無色エネルギー 1枚

テツノカイナex・トドロクツキex・テツノイバラexなどの、HP230・240ライン(ルギアVstarで取れない)でかつ放置できないポケモンがエクストラに増えてきたので1枚だけ入れました。ただ、これを入れるよりはカイザータックルのコライドンexを入れた方が見れる範囲が広がって良いと思います。

ミストエネルギー 2枚

エヴォコンのエネルギー破壊(2枚つければザオボー打たれても耐える)やミラーのジージーエンドでアーケオス2枚飛ばされる動きを意識して2枚採用しました。

レシピには入ってないけど採用したいカード

採用したい理由などは各カード解説で触れたので、ここではリストアップに留めます。

とても採用したい

  • ハバタクカミ(特性:あんやのはばたき)
  • コライドンex(ワザ:カイザータックル)
  • キュレム(ワザ:トライフロスト)

あってもいいかも

  • ゼイユ
  • ネオラントV
  • プリズムエネルギー
  • シークレットボックス

OpenTelemetry MetricsのUnitに何を記述すべきか

OpenTelemetryにおいて、OTel CollectorのReceiverを開発するときやアプリケーションに計装をする際、MetricsのUnit(単位)にどんな文字列を指定すべきかよく分からなかったので調べてみました。

semconv

OpenTelemetryにはSemantic Conventions(semconv)と呼ばれる規約があります。あるデータに対して異なる実装間で共通の名前がついていると便利なので標準を取り決めましょうといった趣旨のものです。Metricsにおいてはメトリック名や属性、単位といったデータについて規約が定められています。

opentelemetry.io

semconvには一般的な規約と具体的な規約があります。具体というと、例えばHTTPに関する規約、AWSなどのクラウドサービスに関する規約、といった形です。「HTTPサーバのリクエストの継続時間を計装する際にはhttp.server.request.durationという名前にして、属性としてHTTPメソッドを必ず付与し、その属性名はhttp.request.methodにしましょう。またそのメトリックの単位はsとしましょう」といった内容がずらずらと並んでいる形です。

opentelemetry.io

一方で、一般的な規約というのは、具体的な規約を策定する際に指針となる、具体ケースに依存しない規約になります。この規約の単位に関する記述を読めば、今回の謎は解決しそうです。ということで該当箇所を読んでみると以下のような記述があります。

https://opentelemetry.io/docs/specs/semconv/general/metrics/#instrument-units

Units should follow the Unified Code for Units of Measure.

単位はUCUMと呼ばれる別のルールに従うべきだと書かれています。さて、新しい概念が出てきたので今度はそちらを追っていきましょう。

UCUM

UCUMは、数量とその単位を電子的な手段で正確に伝達するために生まれた符号システムです。先ほどの引用上のリンクにアクセスするとその仕様を閲覧することができますが、長大で理解するのが大変なので、簡単にここで紹介します。

電子的な伝達を目的としているため、UCUMにおいて単位はUS-ASCII文字だけで記述されます。残念ながら日本語文字や絵文字は使えません。

SI単位系のように原子単位とその組み合わせ(次元)で表現します。アノテーションといって、個数などの無次元の単位に意味を付与できる仕組みがあるのも特徴です。参考までに、Mackerelで現在使える単位をUCUMで表現してみました。

Mackerelの単位 OpenTelemetry(UCUM)の単位表記例
float 1
integer {request}
percentage %
seconds s
milliseconds ms
bytes By
bytes/sec By/s
bits/sec bit/s
iops {operation}/s

semconvで/sを見かけない件

ところで、/sとつくような単位はsemconvで見かけることがありません。従来IOPSとして監視していたようなメトリックも、OpenTelemetryのレイヤーではあくまでI/O Operationの数として扱う(単位は{operation})雰囲気になっています。

ある時間についての割合というのは計測値そのものではなく計算した結果であり、OpenTelemetryとしては生の計測値をできる限りそのまま取り扱おうという指針なのでしょう。SDKやCollector上で行われる集計(aggregation)を除いて、投稿したメトリックをユーザが扱いやすいように加工するのはExportする先のサービスの責務ということになります。OpenTelemetryの責任範囲(Backendに投稿するまで)やObservability(集計によってデータの解像度は不可逆に低下し、結果として可観測性も失われる)を鑑みると自然な価値観であると言えます。

監視サービスの作り手として、投稿したメトリックを自在に引き便利に取り扱うための、Observability Backendの機能設計や実装の難しさを日々痛感しています。