エンジニアリンググループ、AI・機械学習チームでデータエンジニア/チームリーダーをしている笹川です。 趣味は文字列と、筋トレです。
先日、このところのWFHの影響で、運動不足が顕著だったので、2個で60kg弱のダンベルを注文したのですが、自宅に届けてくれた運送会社のお兄さんのものすごく嫌そうな顔が忘れられません (ごめんなさい)。
今回は、このところ個人的に取り組んでいたOSS活動について紹介したいと思います。 AIチームの業務で利用している、Goで書かれた2つのツールに、まだ実装されていない、あると便利な機能があったので、機能拡張に取り組んでみました。
- zerolog の Goaプラグイン
- docker registry に登録された image のタグ一覧を列挙する dockertags
- まとめ
- We are hiring!
- オンライン勉強会やります!
zerolog の Goaプラグイン
まず1つ目は、AIチームで利用している Goa というAPIを書くためのフレームワークから、ロギングライブラリを使うための、プラグインの実装です。 Goaについては、以前の記事に簡単な解説もあるので、ぜひご参照ください。
今回実装したのは、zerolog のGoaプラグインです。
zerologは、Netflix社などでも利用されているGoのロギングライブラリで、処理中のリフレクションや、メモリアロケーションを極力排して、高速なパフォーマンスを実現しています。 今回これを実装した理由は2つあります。
1つ目の理由は、zerologはログレベルの指定、sampling rateの指定、構造化ログの出力など業務で欲しくなる基本的な機能が揃った上で、インタフェースが好みであることです。 例えば、以下のように型付きで、メソッドチェインのように、ロギングしたいものを連ねていきます。
logger.Info().Str("foo", "bar").Int("key", 12345)
2つ目は、Goの有名なロギングライブラリである、zapのプラグインが、すでに実装されていたことです。 zapはzerologとよく似たインタフェースでロギングするライブラリなので、既存のzapプラグインの実装を読んでめちゃくちゃ参考にしました (というか、プラグインの実装自体はほぼ同じ)。 既存実装を読むのは大変勉強になり、処理が複雑な部分はGoa本体のコードも調査したので、結果として、業務で使っているGoa自体にも詳しくなることができました。
実装としては、大して複雑なことはしておらず、Goa本体からgenerateされて渡ってくるコードの一部を、以下のサンプルコードのように,ひたすらzerologの仕様に沿って書き換える (string.Replaceしまくる) 処理を実装しました。
for _, s := range f.file.SectionTemplates { s.Source = strings.Replace(s.Source, `logger = log.New(os.Stderr, "[{{ .APIPkg }}] ", log.Ltime)`, `logger = log.New("{{ .APIPkg }}", false)`, 1) s.Source = strings.Replace(s.Source, "adapter = middleware.NewLogger(logger)", "adapter = logger", 1) ... }
plugins/generate.go at v3 · goadesign/plugins · GitHub
また、ログの構造化のために、出力したいkey-valueの列を成形して、zerologに渡す部分が必要になるのですが、今回は、Goaのv1に付属している、logrusというロギングライブラリに対応するためのadopterの実装を参考にして実装することで、多少愚直ですが、実現できました (実装の該当箇所は以下)。
plugins/generate.go at v3 · goadesign/plugins · GitHub
今回実装したプラグインは、AIチームのいくつかのプロダクトに組み込んで構造化ログを出力させる目的で利用しており、パフォーマンスなども問題になっていません。 ただし、このパフォーマンスの観点は、zerologの導入成果というよりは、ログを出すことにものすごく神経質になるべきな、例えば広告まわりのコンポーネントなどの応用先でないことと、ログ出力の量が問題になるほど多いわけではないことに起因すると考えられます。 構造化ログは、ある程度スキーマフルな状態でデータをBigQueryなどの分析用の環境へ連携することができるので、データ分析がしやすくしたり、運用時のメトリクスの可視化が容易になるなど、メリットが大きいです。
参考
docker registry に登録された image のタグ一覧を列挙する dockertags
2つ目は、docker registryに登録されたdocker imageのタグの一覧を表示するためのコマンドラインツールであるWantedly社で実装された、dockertagsというツールです。
利用例は以下の通りで、引数にターゲットとなるコンテナレジストリのリポジトリ名を指定すると、タグ名が新しいものから順に列挙されます。
$ dockertags mysql latest 8 8.0 8.0.4 8.0.4-rc ...
このツールは、例えば、Kubernetesクラスタ上で動くアプリケーションが参照するコンテナイメージを変更する際に、registoryに登録されている最新のタグ名を、さっと確認する場面などで,大変便利に利用しています。
元の実装では、Docker Hubと、Quay.ioに対応していたのですが、今回は、追加でAWS ECRと、GCP GCRに対応させました。
エムスリーでは、技術選定が現場に任せられているため、技術スタックもチームごとやプロジェクトごとにいろいろなものが利用されているのですが、そのご多分に洩れず、クラウドインフラもAWS, GCPの両者を利用しています。Container registryもそれぞれのものを使っているので、この対応が必要であったという背景があります。
まず、手始めにgo modulesを適用するところからはじめました。 これは、特にのちのAWS ECRに対応する機能追加のために、aws-sdk-go packageを導入したかったのが理由です。 go modulesの導入は、go mod initと、すでに導入されていたglideというpackage managerの依存の排除するという作業を実施しました。 ちなみに、既に実装されていたDocker HubとQuay.ioのためのコードはどちらも、サービス側から提供されているAPIに対して、http周りの標準ライブラリのみを用いて通信する実装になっていて、とても見通しがよいものでした。
次に、AWS ECRと、GCP GCRに対応する実装を行いました。
ECRの対応部分
dockertags/ecr.go at master · wantedly/dockertags · GitHub
GCRの対応部分
dockertags/gcr.go at master · wantedly/dockertags · GitHub
どちらもマージするまでには、レビューでたくさんコメントをいただき、最終的にシンプルな実装に仕上がったのですが、PR作成時点での実装は、自分が把握していなかったGoの機能を使っていなかったために、無駄なコードがたくさんあったり、一部のケースでエラーになるバグを含んでいるなど、散々な出来 伸び代が満載のコードでした (本題とは逸れますが筆者個人にとっても、とても勉強になりました)。
まとめ
最近取り組んだOSS活動について紹介しました。 今回扱ったライブラリ、ツールは、日頃の業務でもすでに利用しており、今回の機能追加でますます便利にすることができました。 また,取り組みの中で、副次的に自身の理解を見直すきっかけにもなって「OSSって本当にいいものですね〜」(参考: 水野晴郎) となりました。
We are hiring!
エムスリーでは、OSS (とか) を利用して、医療を前進させるプロダクトを開発するエンジニアを募集しています! 我こそは!という方はぜひ以下よりご応募ください!
オンライン勉強会やります!
また、7/9 にエムスリーのプロダクト開発を紹介する勉強会をオンラインで実施します! エムスリーで開発しているアプリ、サービスの技術的な背景や、解決したい課題に対してどのように取り組んでいるのか? を詳しく紹介しますので、こちらもぜひご参加ください!