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-codegenv2 系と v3 系が並行してリリースされていますOpenAPI バージョン 3 をサポートしているのは swagger-codegen v3 のみです。OpenAPI バージョン 3 のビルドには、必ず swagger-codegen v3 以降を利用してください。

 

以下のコマンドを実行して、swagger-codegen の jar ファイルを作成してください。

 

初回ビルドでは、ライブラリのダウンロードなどに10分前後の時間がかかってしまうと思いますが、2回目以降はキャッシュされるので、高速に実行できるようになります。

 

OpenAPI ドキュメント (petstore.yaml) から静的HTMLを作成する

以下のコマンドを実行してください。

 

これにより out/index.html が作成されたら成功です。

f:id:komiyak:20190514095740p:plain

出力された index.html の例です。

 

まとめ

これまでの手順を、下記にまとめました。

 

開発時は Swagger Editor で yaml を編集し、フロントエンドエンジニアに API 仕様を連絡するときには、上記のコマンドを実行して、生成された HTML を配布します。

クラウドストレージへのファイル転送は、クライアントサイドで行うべき

ウェブサービスの開発で、画像などの大きなデータを Amazon S3 にアップロードする実装を行った。API サーバーは Heroku に構築している。

 

画像データなどを Amazon S3 に転送するのは、これまではバックエンドサーバーの役割であった。なぜかと言うと、クラウドストレージに接続するための API キー は秘密情報であり、クライアントが直接それを利用することはできないので、ユーザー認証機能を持つ API の一部に、ファイルアップロードが含まれていた。

 

f:id:komiyak:20190418232804p:plain

 

仕方がないこととはいえ、構造上、同じファイルをクライアントからサーバーへ、サーバーからクラウドストレージへと、2度転送する必要があり、これは少々無駄であった。

 

 

クライアント・ファイルアップロード

シンプルに考えると、大きなデータの転送は一度で済むほうが、無駄が少ない。しかしそれを、安全に、簡単に実現する方法がこれまでなかった。

 

AWS に「Browser-Based Upload」という、非常に重要な機能がある。

Authenticating Requests in Browser-Based Uploads Using POST (AWS Signature Version 4) - Amazon Simple Storage Service

  

下記の左の図が従来のファイルアップロード、右の図がクライアントサイドのファイルアップロードだ。ファイル転送の2重送信の無駄がなくなっている。

f:id:komiyak:20190418234046p:plain

(引用元: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つの方法を示している。

  1. Direct Upload
  2. 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)が明らかに問題を起こし始めた。以下は、アップローダー実装後のサーバーメモリの推移だ。

f:id:komiyak:20190419002115p:plain

普段 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

f:id:komiyak:20190419004018p:plain

(引用元: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.

f:id:komiyak:20190223114047p:plain

Heroku は New Relic APM と addon 連携するとき、 @herokumanager.com ドメインを持つ特殊なユーザーを admin に設定しているらしい。

この状態の場合、自力では admin になることができない。

New Relic の Support Center から、 自分のアカウントに admin ロールを追加してもらうように、 依頼をしなければいけないそうだ。

このあたりのトピックで 症状を説明して、New Relic サポートの人に、 admin ロールを追加してと依頼すると、そのサポートチケットを作ってくれます。

しばらくすると、admin ロールが追加されるようです。