OpenAPI ドキュメント(OAS 3) から静的HTMLを作成する
モチベーション
OpenAPI (もしくは Swagger) で API ドキュメントを作成し、チームに共有したい。OpenAPI バージョン 3 以降を使いたい。
お手軽なのは SwaggerHub を利用することですが、これはちょっと高い。Swagger UI のコンテナを立ち上げるのもありです。しかし、そこまでしなくとも、可読性のある静的HTMLが得られるだけでいい。
その index.html を共有できればよい。
コンセプト
OpenAPI ドキュメント (OpenAPI の仕様に則った openapi.yaml などのファイル)を静的HTMLとして出力する方法は、以下の Stack Overflow の記事に詳細があります。
Generate static docs with swagger - Stack Overflow
swagger-codegen コマンドに OpenAPI ドキュメントを与えれば良いのですが、swagger-codegen の環境を整えるのは、けっこう面倒だったため、こちらに手順を整理しました。
環境
以下の環境を事前に準備してください。
- OpenJDK 8 以上($ javac -version が使える)
- maven ($ mvn -v が使える)
サンプルデータ
今回は OpenAPI バージョン 3 の yaml をビルドします。もし必要があれば、公式のサンプルデータを利用してください。
OpenAPI-Specification/petstore.yaml at master · OAI/OpenAPI-Specification · GitHub
ビルド手順
swagger-codegen の jar ファイルを作成する
OpenAPI ドキュメントから静的 HTML を生成するために swagger-codegen を利用します。
GitHub - swagger-api/swagger-codegen
現在(2019-05-13)、swagger-codegen は v2 系と v3 系が並行してリリースされています。OpenAPI バージョン 3 をサポートしているのは swagger-codegen v3 のみです。OpenAPI バージョン 3 のビルドには、必ず swagger-codegen v3 以降を利用してください。
以下のコマンドを実行して、swagger-codegen の jar ファイルを作成してください。
初回ビルドでは、ライブラリのダウンロードなどに10分前後の時間がかかってしまうと思いますが、2回目以降はキャッシュされるので、高速に実行できるようになります。
OpenAPI ドキュメント (petstore.yaml) から静的HTMLを作成する
以下のコマンドを実行してください。
これにより out/index.html が作成されたら成功です。
まとめ
これまでの手順を、下記にまとめました。
開発時は Swagger Editor で yaml を編集し、フロントエンドエンジニアに API 仕様を連絡するときには、上記のコマンドを実行して、生成された HTML を配布します。
クラウドストレージへのファイル転送は、クライアントサイドで行うべき
ウェブサービスの開発で、画像などの大きなデータを Amazon S3 にアップロードする実装を行った。API サーバーは Heroku に構築している。
画像データなどを Amazon S3 に転送するのは、これまではバックエンドサーバーの役割であった。なぜかと言うと、クラウドストレージに接続するための API キー は秘密情報であり、クライアントが直接それを利用することはできないので、ユーザー認証機能を持つ API の一部に、ファイルアップロードが含まれていた。
仕方がないこととはいえ、構造上、同じファイルをクライアントからサーバーへ、サーバーからクラウドストレージへと、2度転送する必要があり、これは少々無駄であった。
クライアント・ファイルアップロード
シンプルに考えると、大きなデータの転送は一度で済むほうが、無駄が少ない。しかしそれを、安全に、簡単に実現する方法がこれまでなかった。
AWS に「Browser-Based Upload」という、非常に重要な機能がある。
下記の左の図が従来のファイルアップロード、右の図がクライアントサイドのファイルアップロードだ。ファイル転送の2重送信の無駄がなくなっている。
(引用元:https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-UsingHTTPPOST.html)
これを支えているのが Amazon S3 の presigned post という仕組みだ。
Uploading Objects Using Presigned URLs - Amazon Simple Storage Service
ユーザーはAPIサーバーから、S3 のアップロード先(バケットとキー)と署名を発行してもらう。これによってユーザーは、限定された条件で S3 に直接アップロードする権利を与えられる。
クライアントサイドでのファイル転送が完了したら、後は完了したことを API サーバーにおって知らせればよいのだ。
Heroku が推奨している方法
Heroku についての話であると断ってはいるものの、Heroku はコンテナである。これからのコンテナ世代の API サーバーにも、ほぼ同じことが言えるだろう。
Heroku ではファイルアップロードについて2つの方法を示している。
- Direct Upload
- Pass-through upload
Using AWS S3 to Store Static Assets and File Uploads | Heroku Dev Center
前者は、つまり前述のクライアント・ファイルアップロード。後者はいったん API サーバーを仲介する昔ながらのファイルアップロード。
Heroku は前者を推奨している。
一方、後者は、利用するに当たっては注意事項が添えられている。
API サーバーは効率的にリクエストを処理し、可能な限りスループットを最大化するという役目を持っている。大きなデータをクラウドストレージに送信する処理は、API スレッドを長時間ブロックしてしまい、スループットを大きく落としてしまう可能性がある。
もちろん、これまではそうするしか方法がなかったわけだが、Direct Upload という新しい選択肢が現実的になったおかげで、API サイドのファイル転送を行う欠点が見過ごせなくなってきた、というのが今日の流れではないだろうか。
それでも、サーバーサイドでファイルアップロードしてみた
それでもサーバーサイドでのファイルアップロードを Rails で実装してみた。これは私が関わったプロジェクト特有の制約によって、これをやるしかなかったからだ。
実装することはできたのだけど、結果的には、問題点ばかりが目についた。
サーバーサイドのファイルアップローダーを実装してしばらくすると、ウェブサーバー(Heroku dynos)が明らかに問題を起こし始めた。以下は、アップローダー実装後のサーバーメモリの推移だ。
普段 50% 程度のメモリ使用率で安定するように調整していたのだけど、実装後は使用率が 100% を突破して R14 - Memory quota exceeded (OOM)が多発することになった。
原因を調べたところ、おそらく API サーバーにアップロードされたファイルを S3 に転送する際に、バイナリデータをメモリロードしなければならないので、それがメモリリソースを圧迫しているようだ。
単純計算では 1 dyno に 10 スレッド程度動いているとしたら、10MB 程度のファイルが同時に 10 個アップロードされた場合、瞬間で 100MB のメモリを消費する。(そして残念なことに、実際にはそれ以上にメモリ消費ペースが早かった)
サーバーメモリの過剰消費問題に取り組む
Rails のアップロード済みファイル(ActionDispatch::Http::UploadedFile)を、S3 API の put_object で愚直に送信するというやり方だったので、メモリ消費量については、すこし工夫の余地がある。
バイナリデータを一度に S3 へ送信するのではなく、ちょっとずつデータをロードして、分割して送信するということが可能だ。
これを Multipart Upload という。
Uploading Objects Using Multipart Upload API - Amazon Simple Storage Service
(引用元:https://www.slideshare.net/AmazonWebServices/amazon-s3-multipartuploadwebcast111710)
Rails でファイルアップロードすると、ActionDispatch::Http::UploadedFile オブジェクトが取得できるが、この実体は ruby core の IO#read なので、任意のサイズで逐次ロードできる。
AWS の Multipart Upload は最小転送ファイルサイズが決められており、それは現時点では 5MB である。そのため、小さくすると言っても 5MB が限度なのだ。
実際に Multipart Upload を試してみたけど、メモリ消費の面では大きく貢献しなかった。
Multipart Upload はギガバイトになるようなデータを、効率的に転送するための仕組みなので、これは見当違いということになった。
実装は、下記のサイトを参考にさせていただきました。
Multipart uploads to s3 using aws-sdk v2 for ruby…
見えてきたものは
どうしてもサーバーサイドでのファイルアップロードを行いたいならば、次の決断をしなければならない。
- API サーバーのスループットを犠牲にしてでも、ファイル転送を強行する。この場合、サーバーのメモリリソースをかなり多めに確保する。場合によっては、Multipart Upload を行う。
- もしくは、別の込み入った手段を考える。
こんな苦労は買って出ることはなくて、現在ではまず第一に、素直にクライアントサイドからダイレクトアップロードできないか考えるのが筋がよいと思います。
Heroku から New Relic APM の Admin 操作を実行できない
Heroku の Admin 権限を持つユーザーであっても、 New Relic APM では次のようになってしまうことがある。
Sorry. You don't have access to create policies. Please contact your administrator.
Heroku は New Relic APM と addon 連携するとき、
@herokumanager.com
ドメインを持つ特殊なユーザーを admin に設定しているらしい。
この状態の場合、自力では admin になることができない。
New Relic の Support Center から、 自分のアカウントに admin ロールを追加してもらうように、 依頼をしなければいけないそうだ。
このあたりのトピックで 症状を説明して、New Relic サポートの人に、 admin ロールを追加してと依頼すると、そのサポートチケットを作ってくれます。
しばらくすると、admin ロールが追加されるようです。