こんにちはあるいはこんばんは
ふそやん@azihsoynです。 こちらの記事はGunosy Advent Calendar 2021の11日目の記事です。
昨日の記事はjohnmanjiroさんの DenoでTodoリストAPIを作ってみた でした。
今回はYAMLの生成にCUEを使ってみた話を紹介します。 本記事ではCUEがどういうものかは触れずに、どのように使ったかを説明します。
背景
グノシーのサーバーサイドはk8s上で動いているものが多く、そのため色んな設定ファイルがYAMLで管理されています。
グノシーのサーバーサイドの環境は主に3種類あり、
- 本番環境(prd)
- ステージング環境(stg)
- 個別の開発環境(dev)
それぞれの設定ファイルを個別に用意するのはメンテナンスコストが高いため、templateに環境毎に違う値を埋め込んで生成するという方法で管理していました。
envoyの設定ファイルもその1つで、CUEを使う前はenvsubstで環境変数を埋め込んで環境別の設定ファイルを生成していました。
こちらはYAMLの一部です
# envoy.template.yaml static_resources: clusters: - name: microservice_api_cluster drain_connections_on_host_removal: true connect_timeout: 0.250s type: STRICT_DNS lb_policy: round_robin http2_protocol_options: { } health_checks: - timeout: 1s interval: 1s unhealthy_threshold: 3 healthy_threshold: 5 unhealthy_interval: 0.1s reuse_connection: false grpc_health_check: { } common_lb_config: ignore_new_hosts_until_first_hc: true load_assignment: cluster_name: microservice_api_cluster endpoints: - lb_endpoints: - endpoint: address: socket_address: address: ${MICROSERVICE_API_HOST} port_value: 80
MICROSERVICE_API_HOSTが環境毎に違う値です。
# envoy.env MICROSERVICE_API_HOST=stg-microservice-api.default.svc.cluster.local
このようなenvファイルを用意して
env $(cat envoy.env) envsubst < envoy.template.yaml
このようにenvsubstコマンドを使って環境変数を展開したYAMLファイルを生成していました。
この方法でも問題ないサービスもあるのですが、
prd, stg環境ではenvoyのjwtを検証するhttp_filterを使いたいが、個別のdev環境では認証をなくしたい
という要件が出てきました。
具体的にはこのようなYAMLです。
# envoy.yaml http_filters: # ここから - name: envoy.filters.http.jwt_authn config: providers: onelogin: issuer: https://gunosy.onelogin.com/oidc/2 forward_payload_header: "X-Verified-Jwt-Payload" forward: true from_headers: - name: x-amzn-oidc-accesstoken remote_jwks: http_uri: uri: https://gunosy.onelogin.com/oidc/2/certs cluster: onelogin_jwks_cluster timeout: "3.0s" cache_duration: seconds: 300 rules: - match: prefix: /api/ping - match: prefix: / requires: provider_name: onelogin # ここまでを環境によって出し分けたい - name: envoy.gzip config: {} - name: envoy.router config: {}
envsubstでは値の埋め込みはできますが条件によってブロックを消したりすることはできない(そもそもYAML専用のコマンドではない)ので、別のツールを探す必要がありました。
ツール選定
YAMLを生成するツールに関してはk8s全盛期の昨今色んな解決策があります(というかtemplateエンジンを使えば無限にありますね)。
- dhall
- jsonnet
- ytt
- ...etc
いくつか調べた結果CUEに落ち着きました。
CUEを選択した理由ですが、
- 書き慣れているGoと似ている
- templateっぽい書き方をしなくていい
- YAMLと似ている
- 標準のformatterがある
- 既存のYAMLをimportできる
などがポイント高かったです。
CUEを使うと先述のブロックの出し分けをどのように解決できるかというと、
# envoy.cue _env: "prd" | "stg" | "dev" @tag(env) http_filters: [ if _env != "dev" { name: "envoy.filters.http.jwt_authn" config: { providers: onelogin: { issuer: "https://gunosy.onelogin.com/oidc/2" forward_payload_header: "X-Verified-Jwt-Payload" forward: true from_headers: [{ name: "x-amzn-oidc-accesstoken" }] remote_jwks: { http_uri: { uri: "https://gunosy.onelogin.com/oidc/2/certs" cluster: "onelogin_jwks_cluster" timeout: "3.0s" } cache_duration: seconds: 300 } } rules: [{ match: prefix: "/api/ping" }, { match: prefix: "/" requires: provider_name: "onelogin" }] } }, { name: "envoy.gzip" config: {} }, { name: "envoy.router" config: {} }, ]
このようなCUEファイルを用意して
cue eval -t env=stg envoy.cue --out yaml
or
cue eval -t env=dev envoy.cue --out yaml
と実行することでブロックを出し分けることができます。
envsubstで行っていた値の埋め込みもCUEで実現できます。
# envoy.cue package envoy static_resources: clusters: [{ name: "microservice_api_cluster" drain_connections_on_host_removal: true connect_timeout: "0.250s" type: "STRICT_DNS" lb_policy: "round_robin" http2_protocol_options: {} health_checks: [{ timeout: "1s" interval: "1s" unhealthy_threshold: 3 healthy_threshold: 5 unhealthy_interval: "0.1s" reuse_connection: false grpc_health_check: {} }] common_lb_config: ignore_new_hosts_until_first_hc: true load_assignment: { cluster_name: "microservice_api_cluster" endpoints: [{ lb_endpoints: [{ endpoint: address: socket_address: { address: _MICROSERVICE_API_HOST port_value: 80 } }] }] } }]
# env.cue package envoy _MICROSERVICE_API_HOST: "stg-microservice-api.default.svc.cluster.local"
cue eval env.cue envoy.cue --out yaml
ここで、YAMLを生成するだけなのにCUEの構文覚えるのめんどくさいよと思った方。安心してください。元のYAMLをimportしてCUEファイルにすることができます。
cue import <yaml file>
あとは必要な箇所を少し直せばtemplateとして使えると思います。
さいごに
CUE自体は2年前ぐらいに フューチャーさんの技術ブログ で読んでから気になってはいたのですが、ついに導入という運びになりました。
今回はCUEのほんの一部の機能を使ってYAMLをtemplate化しました。
実はCUEはこれ以外にも多種多様な機能があり、全貌を知ってから利用しようとするとオーバースペックに思えてしまうかもしれません。小さく使うところから始めることをおすすめします。
CUEのその他の機能が気になったらまずはチュートリアルからやってみるのがよいと思います。
余談ですが、このブログを書くに当たって公式のチュートリアルを一通りさらっておこうと思ったのですが、どうやら一部ドキュメントが古いらしくチュートリアルの通りのoutputになりません(ignoreオプションが動かないとのこと)。
現在CUEの最新バージョンは0.4.0ですが、0.2.2で動かすと公式のチュートリアルと同じ出力になりました。 試してみる場合は気にとめておくとよいかもです(実際にツールとして利用する場合は0.4.0の方が機能追加やbug fixなども含まれているので最新版を使う方がよいと思います)。
余談終わり
YAMLファイルの生成は他にも色々な方法があると思うので便利なツールがあったら教えていただけると嬉しいです。
明日は大曽根さんの『インスティテューショナルメモリとメタアナリシス』という記事です。お楽しみに!