$30 off During Our Annual Pro Sale. View Details »

DockerでProtobufをコンパイルしたい!

logica
March 27, 2022

 DockerでProtobufをコンパイルしたい!

このスライドは、traP LT 2022( https://connpass.com/event/242713/ )の発表資料です。
Protocol Buffers (Protobuf)を使ったWebアプリを作る機会がありました。デプロイする際Dockerイメージにビルドする必要があるのですが、Protobuf公式からはProtoc (Protobuf Compiler)が動くイメージが公開されていません。「コンパイルで自動生成されたコードをリポジトリに入れるのは美しくない!」というこだわりを持った僕が、DockerでProtocを動かし、Protoファイルから各種コードのコンパイルを成功させるまでの道のりをお話しします。

logica

March 27, 2022
Tweet

More Decks by logica

Other Decks in Technology

Transcript

  1. DockerでProtobufをコンパイルしたい!




    logica
    Twitter: @logica_dev

    GitHub: logica0419

    Zenn: logica0419

    View Slide

  2. 軽く自己紹介
    20B、学部新3年
    ヒヨッコWebエンジニア(学習始めてからまだ1年弱位)
    今年はtraQのフルスタックなメンテナーだった
    最近Goでサーバーばっか書いてる
    たまにReact + TSでクライアントも書く
    最近はgRPC、Kubernetes、イベント駆動アーキテクチャ辺りがマイブーム
    春休みにやってたこと
    KLab様とpixiv様のインターンに連続して参加していた(だいぶ体力がヤバい)
    3/31まで続くので死なないように頑張ります
    何個かブログを書いていた

    View Slide

  3. Dockerの説明は割愛
    まあ簡単に言うとパソコンの中で仮想のパソコンを立ち上げられるやつ
    ↑を実現する技術の中でもトップレベルに軽い
    アプリケーションを動かすとき、すごく管理がしやすい
    結論: Dockerはいいぞ

    View Slide

  4. 昨年12月のこと
    traPで冬ハッカソンがあった
    自分は5人チームでWebアプリケーションを作ることに
    サーバー・インフラを担当することになった
    自分含めたサーバーサイド二人で技術スタックを選んでいた時
    相方「gRPCっての面白そうだから使ってみたい」
    自分「おk」
    Protocol Buffersの世界に入門した
    ちなみにgRPCはクライアント - サーバー通信には向かなかったので、ハッカ
    ソンではProtocol Buffersのみ使用
    ※ gRPCでも今回の話はほぼ共通
    REST APIとWebSocketに乗っけた

    View Slide

  5. Protocol Buffersとは
    公式ページ: https://developers.google.com/protocol-buffers
    通称Protobuf
    Googleが開発した
    公式ページの紹介文をふんわり翻訳すると
    言語に依存しない
    プラットフォームに依存しない
    前方互換・後方互換を保ったまま
    構造化されたデータをシリアライズするための
    拡張可能なメカニズム

    View Slide

  6. ??????????????

    View Slide

  7. シンプルに理解しよう
    構造化されたデータとは
    JSONとかRDBの中身とか
    「配列と連想配列から構成されたデータ」といえる?


    API通信で情報を送るとき、整頓したデータが送れるので非常に扱いやすい
    REST APIなどでよくJSONスキーマが使われる所以
    ↑Protobufはちょうど「JSONスキーマ」の部分に相当する概念!

    View Slide

  8. シンプルに理解しよう
    Protobufとは
    主にネットワークを介したAPI通信に使われる
    多くのAPI通信で application/json が担っている部分を、

    より軽量に

    より安全に

    より簡単に

    より広域で

    扱うために開発された技術
    ということさえ押さえられれば今回の話は理解できます

    View Slide

  9. Jsonと比較して特徴を理解する

    View Slide

  10. より軽量
    JSON
    JSON文字列(text) <-> インメモリデータ(言語によるデータ型) の変換
    Protobuf
    バイナリ <-> インメモリデータ(言語によるデータ型) の変換
    バイナリへエンコードされる
    連想配列のキー名など、最低限の伝達を担保するのに不必要な情報をギリギ
    リまで削っているので、JSONよりもわずかに軽い

    View Slide

  11. より安全
    スキーマの型安全性が、デフォルトでわかりやすく確保できる!
    Proto言語
    Protobufのデータ構造を定義する言語
    これが「Protobuf」と呼ばれることも
    あって紛らわしい
    「message」という単位でスキーマを定義
    フィールドにはID・名前・型をつける
    「repeated」prefixで配列になる
    書きやすい・わかりやすい
    YAML・JSONに比べてより言語ライク

    View Slide

  12. より簡単 / より広域
    Protobufは、Proto言語で書かれた定義ファイルから
    スキーマに対応するクラス・構造体
    バイナリへのエンコード・バイナリからのデコードをする関数
    を自動生成(コンパイル)して使う(バイナリの中身が共通化される)
    Protoc(Protobuf Compiler)を使って、数多くの言語実装をコンパイルできる
    デフォルト - C++ / C# / Java / JS / Kotlin / Objective-C / Python / Ruby
    アドオンを入れると - Go / Dart / C / PHP / Rust / Scala / TSなどなど
    API通信をする双方のサービスで、どちらも同じProto言語定義からコンパイルす
    ればよいので、スキーマ駆動な開発がしやすい!

    View Slide

  13. より簡単 / より広域
    JSONだったら...
    JSON文字列へのエンコード・デコードは言語頼り
    完全に共通化されてはいない
    スキーマ駆動にしたい場合、SwaggerやらOpenAPIが主流
    でもYAMLは長いしダルい


    Protobufは、コンパイル環境を整え、コンパイルされる関数の使い方さえ押さえ
    てしまえば、すごく手軽にスキーマ駆動な開発が始められる

    View Slide

  14. Protobuf is 神
    ...だけど、Gitリポジトリに入れようとしたとき少し悩ましいことがある

    View Slide

  15. 自動生成コードのGitリポジトリ上での扱い
    自動生成コードやライブラリのローカルキャッシュ(node_modules)
    別のファイルから一意的に状態が復元できる
    無駄な容量を食う・生成すると無駄なDiffが増える・生成し忘れが発生するな
    どの理由で、一部の例外を覗いてGitリポジトリに含めるのは好ましくない
    Protobufの自動生成コードもこれにあたると自分は考える
    でも、ビルド時には必要
    なんとかしてビルドするタイミングでコンパイルしないといけない!
    コンパイルに必要なもの
    Protocのバイナリ
    アドオン

    View Slide

  16. CI / CDワークフロー上でのコンパイル
    CI
    自分の場合
    クライアント: TypeScript
    サーバー: Go
    GitHub Actionsを使う場合、 arduino/setup-protoc が使える
    有志が用意してくれた、actions内でProtocバイナリを用意してくれるjob
    アドオン
    TSはnpmのパッケージをインストールするだけ
    Goの場合 go install で入れられる
    🎉解決🎉

    View Slide

  17. CI / CDワークフロー上でのコンパイル
    CD
    自分の場合
    Dockerでビルドし、ghcrに上げる
    Protobufのコンパイルをどこでするか
    プログラムのビルドをDockerのフロー内でするなら、Protobufのコンパイル
    もそうあった方が美しい
    Dockerのフロー内で用意するもの
    Protocのバイナリ
    アドオン

    View Slide

  18. DockerでProtobufをコンパイルしたい!
    こっからはGoに注目して話を進めます

    基本的にNode環境上ででTSにコンパイルするのも同じ

    View Slide

  19. 二つの大きな壁
    個人的にめっちゃ苦しめられた壁が二つあった

    1. Distroの壁

    2. CPUアーキテクチャの壁

    View Slide

  20. Distroの壁
    公式から出されている protoc イメージみたいなのが無い
    自分でやるしかない...
    pull時間の短縮のため、軽量なイメージをベースとしたかった
    golang:1.*.*-alpine を使用
    とりあえず、バイナリ取ってきてPATH通せばええやろ!

    View Slide

  21. できたDockerfile

    View Slide

  22. イメージの中でコンパイル
    詳細は省くが、 RUN protoc --go_out=proto protobuf/*.proto でできるはず


    log
    ...

    #8 0.379 /bin/sh: protoc: not found

    View Slide

  23. View Slide

  24. View Slide

  25. イメージの中でコンパイル
    PATHが通ってないだけかもしれない...

    フルパスで指定すれば...

    RUN /temp/protobuf/bin/protoc --go_out=proto protobuf/*.proto


    log
    ...

    #8 0.480 /bin/sh: /temp/protobuf/bin/protoc: not found

    View Slide

  26. View Slide

  27. View Slide

  28. ちなみに ls したらちゃんと指定した場所にDLされていた

    which protoc ではちゃんと /temp/protobuf/bin/protoc が返ってくる

    View Slide

  29. 何がいけなかったのか
    調べたら...
    ProtobufのコアがC++で書かれていた
    ことが大きな理由だったことが判明した
    配布バイナリのビルドがGNU C Library(glibc)に強く依存している
    Alpine Linuxはこの環境がない(muslってやつを使ってる)
    対応してない = 認識されなかったと推定できる

    View Slide

  30. 解決策
    調べた限りでは
    1. alpineのパッケージマネージャー、apkからインストール
    2. ソースからビルド
    3. alpineをあきらめる

    View Slide

  31. apkから
    メリット
    手軽、コマンド一つで入る
    速い
    デメリット
    ちょっとバージョンが古い
    現在最新版は3.19.4
    更新が去年10月...
    Protobufの更新と対応してない

    View Slide

  32. ソースからビルド
    必要パッケージをインストールして、alpine上でビルドすればalpine上で動く
    公式で説明があるのでやりやすい
    https://github.com/protocolbuffers/protobuf/blob/master/src/README.md
    メリット
    alpineを捨てずに最新版が取ってこれる
    一番usageとして綺麗かな~(個人的な感想)
    デメリット
    (多分)Dockerビルドが遅い
    ソースからビルドする時間かかるので...

    View Slide

  33. alpineをあきらめる
    debianにすれば行けるらしい

    View Slide

  34. alpineをあきらめる
    ビルドが通った!!!!!

    View Slide

  35. CPUアーキテクチャの壁
    手元でのビルドは通った

    問題は、GitHub Actionsでビルドをしたときに起こった

    View Slide

  36. マルチCPUアーキテクチャサポート
    先輩がやっていたので、マルチCPUアーキテクチャ向けにビルドをしていた
    Actionsの設定
    ...

    - name: Build and push

    uses: docker/build-push-action@v2

    with:

    context: .

    push: true

    platforms: linux/amd64, linux/arm64

    tags: `タグ名`

    結果
    > [linux/arm64 builder 8/8] protoc ...
    protoc: not found

    View Slide

  37. またお前か

    View Slide

  38. 敗因を探せ!
    > [linux/arm64 builder 8/8] protoc ...
    protoc: not found

    View Slide

  39. 正解
    wget "https://.../protoc-3.19.4-linux-x86_64.zip" の x86_64 でした
    AMD64向けバイナリだからARM64で動くわけがない
    > [linux/arm64 builder 8/8] protoc ... <- 毎回armだったので、arch怪しそうの顔になる

    protoc: not found

    CPUアーキテクチャの理解がおざなりなままマルチアーキテクチャ向けビルドを
    したツケが回ってきた

    View Slide

  40. ARMにも対応させる
    ARM64用のバイナリ(aarch_64)があるので、対応は可能
    どう切り替えようか?

    View Slide

  41. TARGETARCH ARG
    (BuildKitを使用している時のみ)あらかじめ定義されたARG変数として使える
    TARGETPLATFORM
    ビルド結果のプラットフォーム
    linux/amd64、 linux/arm/v7、 windows/amd64 など
    TARGETARCH
    TARGETPLATFORM のアーキテクチャー部分
    これ使えば、ビルドするアーキテクチャに合わせて変えられる!
    ただ、 amd64 / arm64 の形なので x86_64 と aarch_64 に直す必要が

    View Slide

  42. shell芸で筋肉解決

    View Slide

  43. これでマルチアーキテクチャ向けビルドも通る
    めでたしめでたし

    View Slide

  44. 今後の展望
    汎用イメージを作って公開したい
    以前一瞬作ったけど、マルチアーキテクチャ向けビルドで引っかかって「ダ
    メだ!」ってなって削除してしまった
    ソースからビルドで作れば、軽量な汎用イメージも夢じゃない
    管理がめんどいので、dependabotからのアップデートを完全に自動化したい
    Actions芸頑張ります...

    View Slide

  45. ありがとうございました~
    参考文献
    Protocol Buffers - https://developers.google.com/protocol-buffers
    Alpine Linux 上で gRPC を使ってはまった話 -
    https://qiita.com/takkeybook/items/5eae085d902957f0fe5b
    "protoc: not found" on an Alpine-based Docker container running Protocol
    Buffers - https://stackoverflow.com/questions/64447731/protoc-not-found-
    on-an-alpine-based-docker-container-running-protocol-buffers
    protoc(Alpine Linux Packages) -
    https://pkgs.alpinelinux.org/package/edge/main/x86/protoc

    View Slide