Posts for: #Ci/Cd

api ドキュメント保守の考察

今晩から東京出張なので運動はおやすみ。縄跳びだけもっていく。

api ドキュメントを継続的に保守することへの考察

お手伝い先のプロダクトを1年半以上、継続している。web api の機能もどんどん増えてきて50は超えていると思う。api ドキュメントをビルドするために redocly cli というツールを使っている。次のスクリプトは .gitlab-ci.yml の設定だけど、このぐらいの手間で openapi.yml から1つの html ドキュメントを生成してくれる。この index.html を api サーバーに同梱している。

  before_script:
    - node --version
    - npm --version
    - npm install @redocly/cli@latest
    - npx redocly --version
  script:
    - |+
      npx redocly build-docs schema/openapi.yml \
        --output index.html \
        --theme.openapi.schemaExpansionLevel=10 \
        --theme.openapi.expandResponses=all \
        --theme.openapi.requiredPropsFirst=true \
        --theme.openapi.jsonSampleExpandLevel=10 \
        --theme.openapi.hideLoading=true \
        --theme.openapi.pathInMiddlePanel=true      
    - mkdir -p public
    - mv index.html public/

web api の機能数が少ないときはこれで十分だった。しかし、50を超えてくると yml で api ドキュメントを保守するのが辛くなってくる。xml/yml/json を問わず、サイズの大きいこれらのファイルを保守するのはつらい。人間の能は複数種別の情報を同時処理できるが、一定量を超えた情報量をうまく処理できない。もちろん機能別にファイル分割して管理しているのだけど、それでも openapi.yml のエンドポイントの定義はどんどん増えていく。それがだんだん辛くなってきたのが現状になる。

以前 静的サイトジェネレーター勉強会の手伝い をしたときに slate という markdown で記述できる api ドキュメントツールがあることを教えてもらった。これはいいなとそのとき思ったのだけど、コミット履歴などをみているともう保守モードで活発に開発されていない。oss あるある話しで継続的に開発されていないツールはすぐに廃れるのでいまから採用するには躊躇してしまう。

postman という api プラットフォームがある。昔から api クライアントとして使う開発者も多かったのでツール自体は知っていた。この機会に私も実際に使ってみて評価した。既存の openapi.yml + redocly cli を使っているドキュメント管理と postman との比較をした。

  • postman はドキュメントのバージョン管理ができる
  • postman アプリを api ドキュメントのエディターとして使える
    • テキストの説明欄に markdown を記述できる
    • エクスポートすると json データになる
      • ドキュメントのデータとして yml と大きな違いはないが、エディターで隠蔽されるからドキュメントの保守はやりやすくなる
        • 但し、エディターは postman アプリに依存する
  • postman はリクエスト CLI とレスポンスの対応関係をセットで管理できる
  • ドキュメントの構造や見た目はあまり変わらない

postman そのものは悪くないけど、うちらのいまの開発のワークフローにすぐに適用できる状況でもない。postman の機能を有効に活用できるように開発のやり方を変えないといけない。そうしないと、ドキュメント管理だけに使うには外部サービス依存が大きくて学習コストも高くなってしまう。

別のアプローチとして openapi.yml の編集をテキストファイルで行うのではなく、人間にとって操作しやすいエディター上で行えるようにしたものが stoplight studio になる。ググると評判もよいし、私も実際に使ってみて機能はまさに探していたツールではある。しかし、このツールの先行きも怪しい。これまでデスクトップ版のエディターは無償で提供されていて、いまもアカウント登録すれば無償で使える。しかし、昨年あたりに SmartBear 社 (swagger を作ってた会社ね) に買収されている。おそらく swagger editor の移行先を探していてちょうど都合がよいのだと思う。SmartBear 社は過去の openapi 移行時のゴダゴダがあって印象がよくないし、ビジネス寄りの会社になるので将来的に stoplight studio を無償で使い続けられるかどうかにも懸念がある。

今回は調査したどのツールにも懸念があって採用を見送ることにした。api ドキュメントを書くツールを探すのは難しい。

寝台特急

久しぶりの寝台特急。11時25分頃に乗車券を使って JR 三ノ宮駅の改札を通過しようとするとエラーになった。30日から有効とは書いてあるものの、以前は通過できていた気がしたので駅員さんに聞いたら30分前から通れるはずとの。その後11時38分には改札を通れた。これまで改札でエラーになったことがなかったのは11時30分をまわってからだったんだと初めて気付いた。運が悪いことに0時11分発の予定が32分遅延した。調子の悪いときはこんなもので段取りもうまくいかない。最終的に東京駅には15分ほど遅れになった。

今日は普通のB寝台シングルを予約した。下側の狭い部屋でゆっくり寝ることにした。乗車後10分ほどして車掌さんが切符確認にきた。これもわかっているから扉を開けて切符も用意して待ってた。寝台特急は薄い布団しかついていないから冬はちょっと寒い。逆に夏はエアコンが効いているし薄い布団で問題ない。寝台特急は暖かい時期に乗るのがよさそうだということもわかってきた。

gitlab ci/cd のトークン移行

今日は荷解きしていたので運動はお休み。収納はたくさんあるものの、まだ部屋に不慣れで使い勝手がわかってないからどこに何を入れるか、決まらなくてなかなか進まない。

gitlab ci/cd のセキュリティ対応

gitlab のアクセストークンが 16.0 以前までは無期限に利用できたのが最長1年の有効期限が設定されるようになった。16.0 移行で無期限のアクセストークンは移行後から1年の有効期限となる。

gitlab ci/cd のジョブでパッケージレジストリの操作が、これまでは ci job token (ジョブ実行のタイミングで自動生成される短期間有効なトークン) でアクセスできなかった。そこに project access token を発行して組み込んでいた。しかし、そのトークンが1年ごとに有効期限切れするようになったら保守コストがかかるからこれをやめたい。調べてみると、ちょうど ci job token も進化して 16.1 以上でパッケージレジストリへのアクセスができるようになった。

移行の注意事項として project access token は PRIVATE-TOKEN というヘッダーを使うが、ci job token は JOB-TOKEN というヘッダーを使う。curl なら次のようにして project access token を使わなくてもよくなった。

$ curl --fail-with-body -s -H "JOB-TOKEN: ${CI_JOB_TOKEN}" ...

しかし、ci job token でリポジトリへの push はまだできない。これもいま修正中なので 17.2 から対応されるようだ。

社宅既定の作成

家賃計算 を終えて社宅関連の手続きは完了かな?と税理士さんに相談していたら社宅既定を作るのが望ましいと教えてもらった。

うちの会社は就業規則もなく、既定はほとんど作っていないものの、社宅のような特性を、法人と個人の境界を曖昧に運用するのもガバナンス的によくないというのは理解できる。来月あたり時間のあるときにぼちぼち既定を作っていこうと思う。

google マップのクチコミ

社宅探しを手伝っていただいた 不動産仲介会社 さんに感謝を伝える意図も含めて google マップにクチコミを書いた。35レビューで 5.0 しかないということからしょうみさんの信頼の高さが伺える。このままクチコミが増えていくと、しょうみさんの会社がどのように成長していくのか。この先の展望も楽しみにしている。きっと最後はうまくいくと思う。

テンパった開発状況

今日の運動は腹筋ローラー,スクワットをした。統計を 運動の記録 にまとめる。

開発がなかなか進まない

本当は次の開発課題に着手しないといけないところだが、先週からずっと新機能のための、既存コードのリファクタリングに時間を費やしている。データ構造を変えたり、主キーを変えたりしているので影響範囲が大きくて、テストコードもあちこち修正しないといけなくて時間がかかる。結合テストの実行そのものにも時間がかかるし。

なかなか開発がテンパっているものの、今日でようやく一通りのリファクタリングを完了して、意図していた新機能のための web api が動くところまで確認できた。なんというか、新機能を作るために既存コードの改善に8割の時間を費やした感じ。おかげで新機能はなにも開発していないのに勝手に動いた (違) ような気持ちになった。今後、同様に個別データの拡張や新たなデータ追加のときは既存データに影響を与えず、独立して追加しやすい設計となっている。将来的に私がいなくなった後に拡張するときにこの設計は役に立つだろうと思う。

awesome-go へ道

昨日の続き 。contribution guidelines を読んでいて最低限やれと書いてあることの1つに Go Report Card report を作れと書いてあった。これは静的解析したスコアのようなものが表示される。すべて100%が表示されるようにした。

カバレッジも測れとあったので github actions の workflow ファイル を設定して計測してみた。63.9 % といまいちだった。コアなところしかテスト書いてないからかな。ユーティリティや cli のテストもちゃんと書いたら 80-90% ぐらいはいくのかな?また後日、時間のあるときにやってみる。余談だけど、久しぶりに github actions を触ったらもうワークフローファイルの文法とか覚えてなくてずっと触ってないとすぐ忘れるとか思ったりもした。

if the library/program is testable, then coverage should be >= 80% for non-data-related packages and >=90% for data-related packages. (Note: the tests will be reviewed too. We will check your coverage manually if your package’s coverage is just a benchmark result);

Quality standards

結合テストのデバッグ

1時に寝て5時半に起きて7時半に起きた。なんか週の前半からバテている。

gitlab ci/cd の dind で mongodb のレプリカセット接続ができない

先日対応した mongodb のレプリカセット対応 で残った最後の課題。ローカルで実行すれば結合テストは動くが、gitlab ci/cd 環境では動作しないという問題が残っていた。gitlab-runner をローカルで実行できる ようにして、設定やパラメーターを変えたり、デバッグコードを埋め込んだり、コンテナに attach して振る舞いを確認したり、いろいろデバッグして原因はレプリカセット接続におけるホスト名の解決がコンテナ間でできていなかったことがわかった。

mongodb の結合テストは dockertest を使って実装している。これを gitlab ci/cd で動かすには dind を有効にする必要がある。dind 環境では2つのコンテナを使って結合テストが実行されるわけだが、テストが実行されるコンテナと mongodb コンテナが起動するコンテナの2つが生成される。このときにテストが実行されるコンテナから実際に mongodb が起動するコンテナのホスト名の解決と、mongodb が起動するコンテナ上での自分のホスト名の解決の2つが成立していないとレプリカセット接続ができない。要は1台のローカルホスト上で結合テストを実行するのと、2つのコンテナ上で実行されるのでは設定を変更する必要があるということに気付いた。

具体的には dockertest の次のパラメーターを、実行環境から解決するホスト名を考慮して設定すればよいと気付いた。

pool.RunWithOptions(&dockertest.RunOptions{
    ... 
    Hostname:   executor,
    Env: []string{
        ...
        fmt.Sprintf("MONGODB_ADVERTISED_HOSTNAME=%s", executor),
        ...
    }
})

たったこれだけの修正だし、現状の動作の振る舞いが分かればすぐに直せるものではあるけれど、このデバッグにはまた2-3時間を費やした。mongodb のレプリカセット接続はなかなか大変。

gitlab ci/cd のローカルデバッグ

23時頃から寝始めて3時に起きて5時半に起きて8時過ぎに起きた。久しぶりに寝坊した。

gitlab-runner のデバッグ

mongodb のレプリカセット対応して、ローカルでは結合テストが動くものの、gitlab ci/cd 環境では動かなくなった。gitlab ci/cd は GitLab Runner によって提供されている。そのデバッグのため、ローカルに gitlab-runner をインストールして調査した。

GitLab Runner のインストール ドキュメントにそれぞれの OS 向けのドキュメントがある。Debian/Ubuntu/Mint 向けのインストール手順を行う。

$ curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash
$ sudo apt-get install gitlab-runner
$ gitlab-runner --version
Version:      16.6.1
Git revision: f5da3c5a
Git branch:   16-6-stable
GO version:   go1.20.10
Built:        2023-11-24T21:11:36+0000
OS/Arch:      linux/amd64

.gitlab-ci.yml があるディレクトリへ移動して、ジョブを指定して実行する。ローカルでの変更内容を検証するときはブランチにコミットしないといけない。コミットしていないと次のワーニングが表示される。

WARNING: You most probably have uncommitted changes. 
WARNING: These changes will not be tested.         

dind なジョブを実行するときは --docker-privileged で特権を付けて実行する。環境変数は --env KEY=VALUE で渡せるが、CI_JOB_TOKEN のような組み込みの環境変数は上書きできない。

$ cd path/to/repo
$ gitlab-runner exec docker --docker-privileged ${ジョブ名}

go の処理系も驚く sdk のコード生成

0時に寝て何度か起きて6時に起きた。そのまま7時過ぎまでだらだらしてた。しんどい。

msgraph-sdk-go のサイズ問題

先週 msgraph-sdk-go を使った開発 を終えてデプロイする段階になってライブラリのサイズが大きくて、コンパイル速度が遅くなったり、バイナリサイズが大きくなったりする弊害があることに気付いた。コンパイル速度は2-3倍遅くなり (3分が7-10分ぐらい)、バイナリサイズも2-3倍大きくなる (30 MiB が 100 MiB とか) 。たまたまこのリポジトリは他のツール類からも依存パッケージとして使われるものなので想定よりも影響が大きいことに気付いた。

朝からチームのメンバーとミーティングして、本来は qa に入ったこの時期にこんな変更をすべきではないが、これは放置するデメリットが大きいのでリポジトリ分割 (モジュール分割) しようと提案して了承を得た。私がやれば作業は1日もあれば完了するだろうと見積もって、見積もり通り、夕方には分割したモジュールをテスト環境にデプロイして当面の解決を得た。アプリケーションのモジュール構造をちゃんとレイヤー化して作ってあるから、今回みたいに急遽、モジュール分割が必要になってもほぼ変更する必要はなかった (たった1箇所だけ) 。

この本質的な問題は次の issue のコメントで説明されている。

ざっと機械翻訳してみる。

コンテキスト

この SDK は kiota を使用してメタデータから自動的に生成されます。オリジナルのメタデータは、Microsoft Graph の配下にあるすべてのサービスチーム(v1用とベータ用)によって入力された CSDL です。この CSDL は最終的に OpenAPI のフォーマットに変換されますが、これは非常に大きなものです(1k 以上のエンドポイント、1.5k のモデル …)。API のサイズが大きいため、完全な API surface の SDK を手作りすることは実現不可能でしょう。

私たちは、SDK を複数のサブモジュール(ファイル用、メール用など)に “スライス” して、人々が関心のあるものだけを簡単に入手できるようにすることをしばらく考えてきました。実際、私たちは PowerShell でこれを実現しました。しかし、“グラフ” の性質(すべてのモデルは互いにある程度関連している)と構築されるアプリケーションの多様性により、スライスは誰にとっても “正しい” ものにはならない(大きすぎたり、小さすぎたり、モデルの重複につながったり…)。

そのような理由から、私たちは「完全なSDK」を提供することにしました。すべての人にとって理想的とは言えないかもしれませんが、Go開発者の中には「アプリケーションを作るためのSDKが欲しいだけ」という人もいると感じています(以下で説明する2つ目のオプションとは対照的です)。

Go の欠点

Go の探求を通して、いくつかの欠点に気づいた。現時点では、私たちのプロジェクトやパッケージが適切にセットアップされていないせいなのか、Go や大規模プロジェクトの制限のせいなのかはわからない:

go build は、変更されておらず、依存関係も変更されていないサブパッケージをリビルドすることが多い。go build が直前に実行されていても、go test がリビルドすることがよくあります。なぜある種のキャッシュに頼らないのでしょうか?同じ問題が go lint にもある。
私には、たくさんのサブパッケージがある大きなプロジェクトをビルドするコストは、依存関係が更新されたり、キャッシュが削除されたり、コードが変更されたりしない限り、「セッションごとに一度」だけ支払われるべきだと感じます。

私たちのプロジェクト構成/構造において、そのような状況を改善するための最適化について、自由に概説してください。また、Goコミュニティ(Goコンパイラを開発している人たちなど)と関わって、そのようなフィードバックを提供する方法があれば、喜んでそうします。私たちのプロジェクトは、その規模の大きさから、世の中にあるほとんどのGoパッケージと比べると、ちょっと変わり者だとわかっています。

適切なサイズの SDK

最後に、すべてのエンドポイントを備えた完全な SDK を持つことは、様々な理由からすべての人に適しているわけではないことを認識しています。私たちは新しい “適切なサイズのセルフサービスSDKエクスペリエンス” を可能にするために取り組んでいます。そこでは、APIユーザーは誰でも、この SDK と同じように見え、同じように感じる SDK を生成することができますが、完全な API サーフェスの代わりに、彼らのアプリケーションのために彼らが気にするエンドポイント/モデルのみが含まれています。 私たちは今、そのような取り組みに本当に早くから取り組んでいますが、それでもフィードバックをいただけるとうれしいです。大まかな手順はこんな感じだ:

  1. 新しいgoプロジェクトを作成するか、既存のプロジェクトを特定する。
  2. kiotaの依存関係を追加するか、msgraph-sdk-go-coreを追加します(これはKiotaの依存関係をプルし、いくつか追加します)。
  3. グラフエクスプローラで必要なリソースを選択(左パネル、2番目のタブ、…、“コレクションに追加”)。
  4. コレクションをプレビューをクリックし、postmanコレクションとしてエクスポートします。
  5. hidi を postmanコレクションと先ほど共有したOpenAPIの完全な説明文と一緒に使って、“フィルタリングされた” OpenAPI フォーマットを生成する。
  6. kiotaを使って、プロジェクトにMicrosoft Graph用のGoクライアントを生成する。
  7. APIの呼び出しを開始する。

この時点で、私たちはこれらのステップをすべて文書化し、効率化するために取り組んでいます(おそらくステップ4~5を圧縮しています)。このアプローチの素晴らしいところは、ステップ5から7までが、Microsoft Graphだけでなく、呼び出したいOpenAPIで記述されたAPIで動作することだ。
繰り返しますが、この最後の提案はまだ初期段階です。自由に試して、様々な場所でフィードバックを提供してください。

この長い投稿で、私たちがどこに向かっているのかが明らかになり、Goコミュニティからこれらの側面すべてについてさらにフィードバックが得られると本当に助かる!

簡単に言えば、ms graph api の体系が巨大過ぎて、その定義は openapi.yaml にあるが、この定義からすべてコード生成すると巨大なモデル定義をもつ sdk が出来上がってしまったという話しである。後半に書いてあるワークアラウンドとして kiota で必要なモデルだけを選択して専用 sdk を生成すればサイズを小さくできるとある。しかし、それはそれで graph explorer で選択しないといけなかったりして面倒そうではある。次のドキュメントでもその手順について書いてある。

うちの用途ではモジュール分割により、局所化したのでひとまずこの問題は大きな影響をもたないようになった。また余裕があるときにモデルを選択して専用 sdk を自動的に生成する仕組みを構築できるならそれに挑戦してもよいかもしれない。

msgraph-sdk-go のビルド問題

1時に寝て何度か起きて7時に起きた。昨日は少し早めにお仕事を終えて家で休んでいたので少し回復した。

msgraph-sdk-go を使った開発

昨日の続き 。前日に作ったマージリクエストをチームのメンバーにレビューしてもらっていくつか修正して、マージを終えた。一段落。

さらにこの sdk を使うことで8月の前半に開発していた差分比較のところも変更しないといけないことに気付いた。public な構造体のメンバーにアクセスして差分比較する処理を実装していたが、この sdk は getter で構造体のメンバーにアクセスしないといけないことに気付いた。reflectoin の処理に追加で実装を入れるだけなのでそんなに難しくはない。そういった修正をしていたら1日終わってしまった。開発していると時間が過ぎるのは早い。

たまたま ci/cd ジョブの実行時間の上限を10分にしていて超えるときがあってジョブが失敗した。 調べてみると、msgraph-sdk-go の api が巨大過ぎてメモリを浪費したりコンパイルに時間がかかったりするという issue をみつけた。

私のローカル環境で測ってみると、約36秒で完了していたテストが2分23秒かかるようになっていた。テストの実行が4-5倍ぐらい遅くなった。さらにコンテナイメージのサイズは 36 MiB から 109 MiB と3倍ほど増えた。無駄に開発を遅らせる環境要因になっているのでこれは別途調査して対応しないといけないことに気付いた。

docker image のマルチプラットフォーム対応

22時に寝て0時に起きてやや吐き気で苦しんで4時に起きて7時に起きた。夜遅くに食べてないのに調子悪かった。

docker image のマルチプラットフォーム対応

やぎさんが docker buildx でマルチプラットフォームのイメージを作成する の記事を書いてて buildx プラグインがあることを知った。ちょうどいまお仕事でコンテナベースのプロダクトを開発している。まずはオンプレミス向けが対象なので amd64 でビルドしていた。今後はクラウド向けにも提供していくので arm64 ビルドもいずれ追加しないといけないと考えていた。ちょうどリリースを終えて調査時間の余裕があるのでこの機会に buildx について調べてみることにした。

Docker Engine 23.0 release notes をみると次のように書いてある。

  • Set Buildx and BuildKit as the default builder on Linux. moby/moby#43992
    • Alias docker build to docker buildx build. docker/cli#3314
    • The legacy builder can still be used by explicitly setting DOCKER_BUILDKIT=0.
    • There are differences in how BuildKit and the legacy builder handle multi-stage builds. For more information, see Multi-stage builds.

23 からデフォルトのビルダーとして buildx が使われるようになっているらしい。Building multi-platform images を一通り読んで試してみた。

buildx ではカスタムビルダーを定義して複数プラットフォーム向けの docker image を一緒にビルドできる。このとき個々のビルド環境を builder instance と呼び、ビルド環境を抽象した概念として扱われている。マルチプラットフォーム対応の文脈で言えば amd64 や arm64 のビルド環境をそれぞれに作る。例えば amd64 のマシン上で arm64 のビルド環境を作るときは qemu を使ってエミュレーションしたビルド環境を用意したりもできる。builder instance はカスタムビルダーで制御する。

$ docker buildx create --name mybuilder --platform linux/amd64,linux/arm64
$ docker buildx ls
NAME/NODE    DRIVER/ENDPOINT             STATUS   BUILDKIT PLATFORMS
mybuilder    docker-container
  mybuilder0 unix:///var/run/docker.sock inactive          linux/amd64*, linux/arm64*

ここで作ったカスタムビルダーの driver は、デフォルトビルダーの docker ではなく、docker-container になる。おそらく builder instance の実体であるビルド環境がコンテナ上に構築されるのだと思う。これらの builder instance を使ってビルドされる。カスタムビルダーをデフォルトで使うには次のように実行する。

$ docker buildx use mybuilder

このカスタムビルダーを使って docker image をビルドする。カスタムビルダー側にプラットフォームの設定をもっているので --platform は指定しなくてもよいけど、明示した方がわかりやすいだろう。

$ docker buildx build --platform linux/amd64,linux/arm64 -t myimage:latest .

ビルドの処理は進んでいたが、正常終了しなかったので途中でやめた。なにか設定の漏れがあるかもしれないが、だいたいの雰囲気はつかめた。

ここでいま gitlab ci/cd 環境では kaniko というツールを使って docker image をビルドしている。そもそも kaniko と buildx の違いもわからなくなって README を読み返すと次のようなことが書いてあった。

kaniko は Docker デーモンに依存せず、Dockerfile 内の各コマンドを完全にユーザースペースで実行します。これにより、標準的な Kubernetes クラスタのように、Docker デーモンを簡単かつ安全に実行できない環境でもコンテナイメージを構築することができます。

ローカルで検証しているとしばしば忘れてしまうが、docker cli を使うには docker daemon を起動しておかないといけない。ci/cd におけるビルド環境がそもそも dokcer で動いていたりすると docker daemon を使えるかどうか (dind: docker in docker) はセキュリティ上の大きな違いになってくる。ci/cd 環境によっては docker daemon が使えないという状況はありえる。そういった環境でも docker image をビルドできるのが kaniko のメリットと言える。

kaniko はマルチプラットフォーム対応なビルドができるのかどうか?issue でもそういった質問がいくつかみつかる。

結論から言うと、kaniko そのものはマルチプラットフォーム対応のビルド機能をもっていない。アーキテクチャごとのビルド環境があればそれぞれビルドするだけになる。しかし、マルチプラットフォーム対応の docker image というのは manifest list というのを作ってコンテナレジストリに push すればよいという仕組みらしい。この manifest を作るためのツールとして manifest-tool というのがある。このツールと組み合わせれば、kaniko でもマルチプラットフォーム対応の docker image をコンテナレジストリに登録できる。実際に試したわけではないので想像で書くが次のような手順だと推測する。

  1. kaniko でそれぞれのプラットフォームの docker image をビルドする
    1. amd64 向けにビルドする => latest-amd64
    2. arm64 向けにビルドする => latest-arm64
  2. ビルドされた複数プラットフォームの docker image に対して manifest-tool でコンテナレジストリに push する
    1. latest- prefix のタグをもつ docker image の manifest list を作る
    2. manifest list を使ってコンテナレジストリへ push する

ci/cd 環境でこういった手順のパイプライン処理やジョブを定義して実行すれば kaniko でもマルチプラットフォーム対応な docker image を push できると思う。

gitlab packages api の使い方

0時に寝て何度か起きて6時半に起きた。先週より少し早く起きれるようになってきた。

gitlab ci/cd で別プロジェクト (リポジトリ) の成果物を取得する

gitlab では複数のパッケージリポジトリに対応しているが、それらに当てはまらない汎用の成果物向けに GitLab Generic Packages Repository というものがある。zip でもバイナリファイルでも何でも置くためのリポジトリと言える。但し、同じパッケージ名でバージョン管理するといった作りにはなっていなくて、同じパッケージ名でアップロードしても別のパッケージ id が割り当てられて管理される。他バージョンとの紐付け自体はできているので、おそらく歴史的経緯でそういう仕様なのだと思う。そのために、あるパッケージの最新のバージョンを取得したいときは作成日の降順でソートして最初のパッケージを取得するといったコードを書かないといけない。それは Packages API を駆使して簡単なスクリプトを書くことになる。

もう1つ分からないことにトークンの使い分けがある。なるべく ci/cd での処理は GitLab CI/CD job token を使いたいところだが、どうも Packages API の呼び出しはできなくて別途プロジェクトでアクセストークンを作成して呼び出すようにしている。これはもしかしたら別の設定で CI/CD job token でも呼び出しできるかもしれない。rest api への呼び出し権限そのものがないのかもしれない。

最終的には次のようなスクリプトで任意のプロジェクトの generic リポジトリの最新の成果物を取得できた。

rm -rf ${TARGET_DIR}
mkdir -p ${TARGET_DIR}
for project in $PROJECTS
do
  prj=$(echo "$project" | jq -Rr @uri)
  base="${CI_API_V4_URL}/projects/${prj}"
  pkg=$(curl -s -H "PRIVATE-TOKEN: $PROJECT_ACCESS_TOKEN" "${base}/packages?order_by=created_at&sort=desc&per_page=1" | jq '.[0]')
  pkg_id=$(echo $pkg | jq -r .id)
  pkg_name=$(echo $pkg | jq -r .name)
  pkg_version=$(echo $pkg | jq -r .version)
  file_names=$(curl -s -H "PRIVATE-TOKEN: $PROJECT_ACCESS_TOKEN" "${base}/packages/${pkg_id}/package_files" | jq -r '.[].file_name')
  for file_name in $file_names
  do
    dw_endpoint="${base}/packages/generic/${pkg_name}/${pkg_version}/${file_name}"
    curl -s -H "PRIVATE-TOKEN: $PROJECT_ACCESS_TOKEN" "${dw_endpoint}" -o "${TARGET_DIR}/${file_name}"
  done
done
find ${TARGET_DIR} -type f

コンテナの運用ツールを作る

1時に寝て明け方に起きて7時に起きた。あまり眠れてない雰囲気がある。

docker/compose の運用

いま作っているアプリケーションは docker compose で構成している。マージ単位で gitlab ci/cd から docker image をビルドしていて、テスト環境のデプロイスクリプトで最新の docker image を取得してコンテナを再作成するようにしている。デプロイスクリプトは docker cli と docker compose cli の2つを組み合わせてシェルスクリプトで実装しているが、複数のアプリケーションやミドルウェアがあるのでそれらを統合的に扱うことはできないし、さまざまな状況を想定して動くようにもなっていない。がんばれば doccker/compose cli と jq とシェルスクリプトで細かい要件を実装することもできるけど、それをお客さんの本番環境においても使うには一定の cli 操作に慣れが必要な上、docker/compose の知識やスキルも要求してしまう。少なくとも頻繁にある運用作業として docker image の更新やコンテナの再作成が想定される。ローリングアップデートまでは実装しないけど、アプリケーションの要件にあわせた docker image の更新とコンテナの再作成 (アプリケーションの再起動) ぐらいはまとめてやってしまってよいと思う。

github.com/docker/docker は github.com/moby/moby にリダイレクトされる。docker は開発ツール、moby はインフラやライブラリという住み分けでそれぞれに関心のあることに注力するようにモジュール構成を分離している。それが2019年に行われていまもおそらくまだ途上だと思う。あと docker のモジュール群は go modules に対応していない。大きなプロジェクトが移行するのが大変なのは理解できるけれど、依存解決のような複雑なところを放置するのはまったく賛成できない。そこが不健全だと依存ライブラリの整理やモジュール分割がうまく進まない気がする。docker の client は https://github.com/moby/moby/tree/master/client に定義されていて、readme のサンプルコードにあるようにすぐに使えるようになっている。一方で compose の spec は github.com/compose-spec/compose-go で定義されていて、github.com/docker/compose はまだライブラリとして使いやすいようにはなっていない。仕様と実装が混在していて動いている状態。そういう issue もあげられている。

testcontainers-go というアプリケーションが compose をライブラリとして使う実装をしている。このコードをみれば compose をどう使えばよいのかはわかる。

自分で compose を実装してもよいけれど、compose の service を扱うための project やオプションの設定が煩雑なことも伺える。仕様と実装が混在しているというのはそこら変の整理ができていないようにみえるからだ。testcontainers-go も自前の client を用意して使いやすいようにしているのでそれを再利用した方が運用ツールを作るのは簡単になる。testcontainers-go の compose client 経由で compose の up/down を制御する。その他のコンテナの操作は docker client を直接使って実装する。この組み合わせで自分たちのアプリケーション向けの運用ツールを作ろうと思う。

プライベートリポジトリの go アプリケーションの依存解決

0時に寝て6時半に起きた。面倒なお仕事を朝から集中して片づけた。

go.mod のプライベートリポジトリの依存解決

非公開のプライベートリポジトリで開発している go アプリケーションを他のリポジトリから依存ライブラリとして使う方法を調べた。go modules は基本的に公開されたパブリックなリポジトリを前提としている。go.mod のワークフローで他の依存ライブラリと同様にバージョン管理ができるようにするには、プライベートリポジトリであることを go.mod に認識させ、トークンなどを使って認証する必要がある。

go 1.13 から GOPROXYGOPRIVATE という環境変数が追加された。デフォルトでは GOPROXY は https://proxy.golang.org に設定されており、このプロキシサーバー経由で依存ライブラリを取得する。これは公開リポジトリを、ある日、作者が急に削除したり非公開にしたときにビルドできないといった問題を防ぐために依存ライブラリのリポジトリをキャッシュしてくれる役割を担っている。

一方でインターネット上のプロキシサーバーからはプライベートリポジトリへアクセスできないので GOPRIVATE を設定してアクセスできないサイトを go.mod へ教えてあげる必要がある。

$ go env -w GOPRIVATE="private.repo.jp"
$ go env | grep GOPRIVATE
GOPRIVATE="private.repo.jp"

通常 go.mod は https で依存ライブラリをリポジトリから取得 (クローン) しようとする。このときに git の設定を変更することで特定のサイトへのアクセスを ssh 経由に変更できる。次の例では https://private.repo.jp へのアクセスをすべて ssh://git@private.repo.jp でクローンできる。

$ git config --global url."ssh://git@private.repo.jp".insteadOf "https://private.repo.jp"`
$ tail -n 2 ~/.gitconfig
[url "ssh://git@gitlab.osstech.co.jp"]
        insteadOf = https://gitlab.osstech.co.jp

これで git リポジトリのクローンは ssh で行われるようになるが、go.mod のワークフローでは https でもアクセスする処理があるようにみえる。https でもアクセスできるように PAT などのトークンを取得して次のように ~/.netrc に https でプライベートリポジトリへアクセスするための認証設定を追加する。

machine private.repo.jp
login t2y 
password ${PERSONAL_ACCESS_TOKEN}

コワーキングのオンラインイベント

月例のカフーツさんのオンラインイベントに参加した。先月の所感はここ 。いとうさんの近況を把握していないが、どうやらバリ島 (インドネシア) のコワーキングスペースをいくつか訪問してきたらしい。その旅程を写真をみながらふりかえるようなイベントだった。バリ島の自然や建物の雰囲気が伺えてとてもおもしろかった。

いとうさんからバリ島はデジタルノマドの先進的な取り組みをしているように聞いていた。例えば バリ島でリモートも可能!?インドネシアのノマドビザは最長5年を検討中 にあるように最長5年のビザを用意しているという話しが日本で盛り上がっているが、現地ではまだ正式にそのようなビザがあるようには存在が確認されていないらしい。それに近い長期のビザを取得するには、一定の金融資産があることを証明しないといけないらしく、日本円だと数千万円程度ないとビザを取得できないのではないか?といった話題もあったと思う。基本的にインドネシア政府は海外から金持ちを呼び込みたいらしく、金持ち向けに待遇のよいビザを整備するのではないかとのこと。ビザの話しはともかく、ここ2-3年でバリ島のコワーキングスペースもいくつか廃業しているらしい。それは利用者の大半が外国人であったため、コロナ禍により、インドネシア政府からの要請もあって外国人に帰国を促したという。外国人利用者の半分ぐらいが自分たちの国に帰ってしまい、施設の運用コストを維持できなくなった。バリ島は今後の成長が見込まれていることから数年前から外資が入ってきて土地や家賃が急騰しているために利用者が激減すると運用コストを維持できなくなったとのこと。

肝心のバリ島のコワーキングスペースの雰囲気を聞いていると、利用者は外国人が大半で、感覚的にはヨーロッパから来ている人が多そうだといとうさんが話された。バリ島のコワーキングスペースでいとうさんが見学していたときには、現地の人たちと外国人のコミュニティが活発に活動しているようにはあまりみえなかったらしい。利用者もそう多くはなかったし、その人たちも静かにただ作業しているだけのように映ったという。いとうさんはコワーキングスペースとはコミュニティやコラボレーションが重要という話しをよくしているけれど、バリ島のコワーキングスペースに関しては裕福な外国のデジタルノマドを呼び込むのに成功しただけのような、もしかしたらコロナ禍でそのコミュニティが破壊されてしまったのかもしれないが、普段このイベントで話しているような高い期待値に応えられるような状況ではないようにみえたという。

とはいえ、日本の田舎よりは遥かにデジタルノマドが集まる場所として世界的に認知されているところなので写真をみながら私もいつか行ってみたいと思えた。