Kotlin MultiplatformサポートのGoogle Analyticsライブラリ「Measurer」をつくった
年始から個人的に大流行のKotlin Multiplatform。開発が楽しい。BFF Server触るエンジニアとネイティブエンジニアの距離感が非常に近くなる。「いや、チームとして当たり前でしょ」みたいなツッコミはあるだろうけど、新しい技術を軸にエンジニア達が「あーだこーだ」言ってる楽しい時間がKotlin Multiplatformでは職域が違うエンジニアが議論することで更に熱が加速する現象を目の当たりにしてます。
モチベーション
私はBFF Serverのほうを主に開発していましたが、プラットフォーム横断で共通するロジックをcommonモジュールにつくった辺りからKotlin Multiplatformサポートのライブラリの開発欲が湧いてきました。
当時ですと既存していた2つのライブラリに着目しました。
Kissmeはセキュアストレージの扱いをマルチプラットフォーム化できて、KonformはValidationのユーティリティなライブラリです。また社内からも自作ライブラリが飛び出していたりと活発な流れを感じていました。
どのライブラリも「プラットフォーム横断で共通するロジック」であればKotlin Multiplatformにするコンセプトがあり、私も何か作りたいなーと感じていました。同僚の@AAkiraからGoogle Analyticsのログ送信部分をライブラリするのはどうだろうか、という話をしたのをきっかけにGoogle Analyticsライブラリを作るモチベーションが高まりました。
Measurer
そして開発をしたのがMeasurerです。
GoogleはMeasurement ProtocolというHTTPリクエストのプロトコルを設計しています。このプロトコルをつかえばHTTP/1.1でGoogle Analyticsへログが送信できます。ライブラリはMeasurement Protocolに準拠して開発を行いました。
2019/2時点で公開されているパラメータとヒットタイプの全てをサポートしています。また1件ずつ送信するのではなくバッチリクエストにも対応していて20件までは一度に送信できます。
今のところJVMとAndroidをサポートしていてiOSとJSは順次対応していく予定です。
使い方
Readmeに記載しているので簡単に使い方を紹介します。
Measurerをプロジェクトに依存させたらcommon moduleに送信部分を実装します。
object GoogleAnalytics { private val httpClient = SampleHttpClient(SampleHttpClientConfig.httpClient, SampleNapierLogger()) suspend fun pageTracking(ua: String) { val mp = MeasurementProtocol .Builder( trackingId = ua, httpClient = httpClient ).build() mp.also { ga -> ga.pageView("mydemo.com", "/home").apply { clientId = "555" documentTitle = "homgepage" contentGroup = ContentGroup(index = 1).apply { value = "news/sports" } } ga.pageView("mydemo.com", "/home").apply { clientId = "555" documentTitle = "homgepage" contentGroup = ContentGroup(index = 2).apply { value = "news/finance" } } }.send() } }
上記のGoogleAnalyticsをJVMとAndroidのプラットフォームから呼び出すだけです。
GoogleAnalytics.pageTracking(trackingId)
Google Analyticsの送信部分がワンソースで管理され各プラットフォームでマルチユースできています。
ライブラリ標準でHTTPクライアントはKtor、ログ出力はNapierを使っています。この部分はプロジェクトで使いたいライブラリに置き換えられるようにしています。
「ライブラリに置き換えられるようにしている」としましたが、HTTPクライアントやログのライブラリは2019/2時点ではKtorとNapierの選択肢しかないです。Kotlin Multiplatform界隈のライブラリはそこまで出てきてない状況をお分かりいただけると思います。
まとめ
- Kotlin MultiplatformサポートのGoogle AnalyticsライブラリであるMeasurerの紹介でした。
- まだiOS、JSのサポートが残っているので引き続きやっていきなモチベーションです。
- Kotlin Multiplatformは楽しい。Kotlin言語を軸にしたAndroid, iOSの両方ができるエンジニアは楽しめる領域です。
- 私はiOS, JS頑張らないとなーと伸ばすべき領域を感じました。足りないスキルセットを見つめ直す機会が体験できるのもKotlin Multiplatformの良きポイントですね。
Prowの真骨頂であるTideでPRの自動マージを導入する。
前回のエントリ「ProwではじめるChatOps on GitHub。」からProwを完全理解した!と言ってはいけない。Prowの真骨頂はTideにあると思う。
Enable kustomize in kubectl by Liujingfang1 · Pull Request #70875 · kubernetes/kubernetes · GitHub
上記のProwが有効になったPRのフローの最後を見てほしい。BotアカウントがPRをマージしているのだ。この仕組みはProwのTideが実現してくれる。
Tideは一定の条件を満たした上でPRをマージしてくれる。マージを行うアカウントはProwに設定したGitHubのアクセストークンになるのでBotアカウントになる。一定の条件は下記のようなyamlでセットアップを行う。
tide: merge_method: kubeflow/community: squash target_url: https://prow.k8s.io/tide.html queries: - repos: - kubeflow/community - kubeflow/examples labels: - lgtm - approved missingLabels: - do-not-merge - do-not-merge/hold - do-not-merge/work-in-progress - needs-ok-to-test - needs-rebase
上記のqueriesに含まれるreposやlabelsの条件を満たせばTideがPRをマージしてくれる。
このエントリでは
このエントリでは、Tideのセットアップの方法とPRフローに必要そうなProwプラグインのセットアップ方法をまとめていく。
Triggerプラグインを有効にする
Triggerプラグインは /ok-to-test
, /test xxxx
, /retest
などのキーワード入力からProwJobsを実行するプラグインである。(Prow Plugin Catalogのtriggerを参照)
Triggerプラグインのセットアップ手順をまとめていく。次のようなconfig.yamlを用意する。
presubmits: soushin/bazel-multiprojects: - name: unit-test always_run: false skip_report: false spec: containers: - image: alpine command: ["/bin/printenv"]
上記のコンフィグレーションにより /test unit-test
というキーワードが有効になった。Prowはキーワードとレポジトリ(soushin/bazel-multiprojects)がマッチすればPodを起動してalpineイメージから /bin/printenv
のコマンドを実行する。e2eテストが実行できるコンテナイメージを用意すればGitHubのコメントからe2eテストが実行できるようになる。ここらへんはプロジェクトに応じてしっかりとした準備と検討が必要なところだ。
always_runやskip_reportはプロジェクトに応じてセットアップする。詳細はjobs.mdにまとまっているので参照してほしい。
上記のconfig.yamlを用意したらConfigMapに反映する。
(test-infra) $ cat config.yaml presubmits: soushin/bazel-multiprojects: - name: unit-tests always_run: false skip_report: false spec: containers: - image: alpine command: ["/bin/printenv"] (test-infra) $ kubectl create configmap config \ --from-file=config.yaml=config.yaml --dry-run -o yaml \ | kubectl replace configmap config -f -
またplugin.yamlにはTriggerプラグインを有効にした上でConfigMapに反映する。
(test-infra) $ cat plugins.yaml plugins: soushin/bazel-multiprojects: - size - trigger (test-infra) $ kubectl create configmap plugins \ --from-file=plugins.yaml=plugins.yaml --dry-run -o yaml \ | kubectl replace configmap plugins -f -
これでProwのhookポッドにTriggerプラグインとTrigger対象のProwJobs(unit-tests)が有効になった。
Triggerを実行する
任意のPRを作成して /test unit-tests
を入力した後にCheckにProwJobsが加わっている。
もしProwJobs(unit-tests)がエラーになればBotアカウントがお知らせしてくれる。
エラーになればコードを修正して /retest
を入力して全てのテストを実行する。Prowは登録されているProwJobsを実行して結果をGitHubに返す。
Triggerの流れを整理
Triggerプラグインを有効にしたProwは次のような流れになる。
- ReviewerはPRコードを確認してOKなら
/ok-to-test
を入力する。 - Prowは
ok-to-test
のラベルを付与する。 - Comitter(またはReviewer)は
/test xxxx
を入力してProwJobsを実行する。 - Prowはレポジトリに有効になったProwJobsを実行して結果を報告する。
- エラーの場合は
/retest
を促す。 - ReviewerはChecksを確認して必要なProwJobsがすべてSuccessedになっているか確認する。
上記のChatOpsのフローを交えてPRマージまで進める。
LGTM, Holdプラグインを有効にする
Tideを有効にする前にLGTM, Holdプラグインを確認していきたい。(Prow Plugin Catalogのlgtm, holdを参照)
このプラグインは/lgtm
,/hold
のキーワードを入力するとそれぞれlgtm
,do-not-merge/hold
のラベルを付与してくれるプラグインである。これを有効にしてTideの設定値であるlabelと組み合わせる。
次のようにlgtm
とhold
を有効にしてConfigMapに反映する。
(test-infra) $ cat plugins.yaml plugins: soushin/bazel-multiprojects: - size - trigger - lgtm - hold (test-infra) $ kubectl create configmap plugins \ --from-file=plugins.yaml=plugins.yaml --dry-run -o yaml \ | kubectl replace configmap plugins -f -
/hold
、/hold cancel
でdo-not-merge/hold
ラベルの付与と取外しを行い/lgtm
でlgtm
ラベルを付与している。
※ 1人でエントリをまとめていたので/lgtm
と/hold cancel
をBotアカウントが行ってしまっているが本来はコントリビューターが行うことになる。
Tideを有効にする
PRマージを行うまでに必要なテスト(Trigger)とラベル付与(LGTM, Hold)を有効にするプラグインをまとめきた。これでTideを有効にしたPRフローの準備ができた。
Tideはconfig.yamlに次のようなコンフィグレーションを追加して有効にした。
tide: queries: - repos: - soushin/bazel-multiprojects labels: - lgtm missingLabels: - do-not-merge/hold
PRマージの対象のレポジトリと必要なラベルと必要ないラベルの設定が有効になっている。
これまでと同様にconfig.yamlをConfigMapに反映する。
(test-infra) $ kubectl create configmap config \ --from-file=config.yaml=config.yaml --dry-run -o yaml \ | kubectl replace configmap config -f -
Tideが有効になったPRフローを確認する
Tideが有効になったPRにはChecksにtide
が追加されている。
lgtm
のラベルが必要なのでラベルを付与するとProwが自動でPRをマージしくれる。
まとめ
- ProwのTideを有効にする方法をまとめた。
- またTideをPRフローに合わせるために必要なProwプラグインをまとめた。
- TideがあればマージまでのPRフローが明確になる。オープンなレポジトリを運営する際のコントリビュートのルールやプロジェクトチームのPRルールにProwを導入すればコミッターは迷わずPRフローを進めることができる。
- Prowプラグインには
assign
やwip
などPRフローに必要なものがあるのでチームにフィットしたプラグイン選びをしていきたい。
ProwではじめるChatOps on GitHub。
Kubernetesのtest-infraレポジトリにあるProw
を試す。ProwはKubernates環境を基盤としたCIとCDのシステムである。
モチベーション
Kubernatesを追っている人であれば次のようなコメントのやり取りをGitHub上で見かけたことがあるだろう。
Enable kustomize in kubectl by Liujingfang1 · Pull Request #70875 · kubernetes/kubernetes · GitHub
/hold
や/test
などのキーワードをコメント欄に入力してオペレーションを実行している。/hold cancel
でラベルを取り除いたり/test
でテストを実行している。入力したキーワードはWebhookで稼働しているProwのエンドポイントに送信されてGitHubイベントと合わせてProwのプラグインが実行される仕組みである。
このProwの導入手順をまとめていきプロジェクトにフィットするか検討したいというのがエントリのモチベーションである。ProwはKubernetesをはじめIstio、Knative、Prometheusなどのオーガナイゼーションにも利用されている。
Prowをデプロイする
Prowが含まれるレポジトリはkubernetes/test-infra
である。
デプロイ手順はgetting_started_deploy.md
を参考に進めている。
test-infra/getting_started_deploy.md at master · kubernetes/test-infra · GitHub
gcpを利用している人であればTackle
というユーティリティが用意されているので対話式にProwをセットアップできるが私はローカル(Docker For Desktop)でKubernatesを起動させているためManual deployment
を参考にすすめた。
またClusterとNamespaceはdocker-for-desktop
とdefault
としている。実際の運用ではプロジェクトに合わせた選択が求められる。
事前に準備するものを一覧にする。
- Prowを試すレポジトリ
- Botアカウント(GitHubのBot専用アカウント)
- BotアカウントのAccessToken
- ご自身のアカウントのAccessToken
Kubernatesはk8s-ci-robot
というBotアカウントを使用している。
k8s-ci-robot (Kubernetes Prow Robot) · GitHub
kubernetes/test-infra
をチェックアウト
$ git clone git@github.com:kubernetes/test-infra.git $ cd test-infra
以降の手順はすべてtest-infra
のディレクトリで行う。
GitHubとProwを連携するための準備
GitHubとProwを連携するための準備としてWebhookで送信するSecretキーを生成しSecretリソースに追加する。
(test-infra) $ openssl rand -hex 20 > hook_secret (test-infra) $ kubectl create secret generic hmac-token --from-file=hmac=hook_secret
BotアカウントのGitHubアクセストークンをSecretリソースに追加する。
# BotアカウントのGitHubアクセストークンを`bot_oauth_secret`に保存している (test-infra) $ kubectl create secret generic oauth-token --from-file=oauth=bog_oauth_secret
Prowのコンポーネントをデプロイする
(test-infra) $ kubectl apply -f prow/cluster/starter.yaml
Deploymentは次のようになっている。
(test-infra) $ kubectl get deployments NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE deck 2 2 2 0 3m hook 2 2 2 2 3m horologium 1 1 1 1 3m plank 1 1 1 1 3m sinker 1 1 1 1 3m tide 1 1 1 1 3m
deckがエラーになっているがOAuth Appを利用するケースのため割愛する。
test-infra/pr_status_setup.md at master · kubernetes/test-infra · GitHub
WebhookをGitHubに追加する
私の環境はローカルのためIngressにIPアドレスが割り振られないがGitHubのWebhookから叩かれるエンドポイントのhook
PodはNodePortで開かれているのでngrockをつかってプロキシしている。
NodePortを確認する。
(test-infra) $ kubectl describe service hook Name: hook Namespace: default Labels: <none> Annotations: kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"hook","namespace":"default"},"spec":{"ports":[{"port":8888}],"sel... Selector: app=hook Type: NodePort IP: 10.98.120.195 LoadBalancer Ingress: localhost Port: <unset> 8888/TCP TargetPort: 8888/TCP NodePort: <unset> 31797/TCP Endpoints: 10.1.7.24:8888,10.1.7.25:8888 Session Affinity: None External Traffic Policy: Cluster Events: <none>
確認したNodePort 31797
をngrockでプロキシする。
(test-infra) $ ngrok http 31797
Forwardingに表示されているhttps://xxxxxx.ap.ngrok.io
のURLをWebhookに利用する。
Webhookを追加するユーティリティをつかってみる
ProwにはWebhookを追加するユーティリティが用意されている。ユーティリティを使わなければ手動でGitHubからWebhookを追加すれば良い。
(test-infra) $ bazel run //experiment/add-hook -- \ --hmac-path=/path/to/hook_secret \ --github-token-path=/path/to/own_oauth_secret \ --hook-url https://xxxxxx.ap.ngrok.io/hook \ --repo soushin/bazel-multiprojects \ --confirm=true
- hmac-pathには生成した
hook_secret
を指定(注:絶対パス) - github-token-pathにはご自身のGitHubアクセストークンを保存した
own_oauth_secret
を指定(注:絶対パス) - hook-urlにはngrockでプロキシした
https://xxxxxx.ap.ngrok.io
に/hook
のエンドポイントを追加して指定 - repoにはProwを試すレポジトリを指定
実行後にINFOが次のように出力されていれば完了。
INFO[0000] ListRepoHooks(soushin, bazel-multiprojects) client=github INFO[0000] CreateRepoHook(soushin, bazel-multiprojects) client=github
GitHubのSettings -> Webhooks
のページにWebhookが追加されていてグリーンのチェックマークが点いていれば正常に完了。
これでKubernatesにデプロイしたProwのコンポーネントとGitHubがWebhookで連携できた。次にプラグインを追加してProwのChatOpsを体験していく。
Prowプラグインを追加する
プラグインを追加する前にProwのhook
Podのログにno plugins
のエラーが確認できる。
hook-64448bb489-42v8b hook {"component":"hook","level":"warning","msg":"no plugins specified-- check syntax?","time":"2019-01-08T03:07:15Z"} hook-64448bb489-42v8b hook {"component":"hook","level":"warning","msg":"no plugins specified-- check syntax?","time":"2019-01-08T03:08:15Z"}
つまり何かしらプラグインを追加して初めてProwのChatOpsが体験できるわけだ。
Prowのプラグインはこちらにまとまっている。
Sizeプラグインを試す
SizeプラグインはPushしたコード量に応じてBotアカウントがsize/S
やSize/XL
などのラベルをプルリクエストに付与してくれるプラグインである。
plugins.yamlを次のように作成する。
(test-infra) $ echo 'plugins: soushin/bazel-multiprojects: - size' > ./plugins.yaml
soushin
のbazel-multiprojects
レポジトリにsizeプラグインを有効にしている。soushin
のみを指定してオーガナイゼーション全体にプラグインを有効にすることも可能である。
plugin.yamlをConfigMapに追加する。
(test-infra) $ kubectl create configmap plugins \ --from-file=plugins.yaml=plugins.yaml --dry-run -o yaml \ | kubectl replace configmap plugins -f -
追加したConfigMapはhook
Podにマウントされてプラグインが有効になる。
適当なプルリクエストを追加するとBotアカウントがSizeラベルを付与してくれる。
まとめ
- Prowの導入手順をまとめた。
- Kubernatesは1日にテストを1,000回以上実行するそうでスケーラビリティが求められる。ProwはKubernates環境で稼働するためニーズとマッチした仕組みである。
- Jenkins XはKnativeとProwを基盤に実現したサーバレスJenkinsで、Knativeというサーバレスの仕組みにProwプラグインの拡張性が組み合わさった感じなのかな、という印象を持った。
- 今回は既存のプラグインを試したが
trigger
というプラグインはtestに関連していそうで非常に気になる。 - プロジェクトにマッチするか引き続きProwを触っていきたい。