CI/CDツールとS3を活用してREST APIやMQTTの通信仕様をステークホルダーと共有する
はじめに
サーバーレス開発部@大阪の岩田です。 サーバーレス開発部の良くある案件パターンとして、Web APIのバックエンド開発やIoTを絡めた案件が挙げられます。 こういった案件では通信仕様の設計からクラメソで対応することが多いのですが、プロジェクトを円滑に進めるためには単に通信仕様を設計するだけでなく、呼び出し側のお客様や他社ベンダーとの認識合わせや仕様の調整が重要になってきます。
現在関わっている案件では、GitリポジトリへのPushをトリガーにS3にドキュメント類をアップロードし、S3の静的Webサイトホスティング機能でドキュメントを自動公開することで開発がスムーズに進むように心がけています。 ドキュメントのイメージはこんな感じです。
本ブログでは、CircleCIを使ってドキュメントをS3の静的Webサイトホスティングで自動公開する手順についてご紹介します。
プロジェクトの概要
簡単にプロジェクトの概要を紹介します。
- ソース管理はGitHubを使用
- CI/CDにはCircleCIを活用
- REST APIの設計にSwaggerを利用
- MQTTのトピック設計にAsyncAPIを利用
- シーケンス図等のUML作成にPlantUMLを利用
Gitリポジトリにはソースコードと合わせてdocs
というディレクトリを配置しており、ドキュメント類はこのディレクトリ配下に作成しています。
docs
ディレクトリの構造は下記の通りです。
docs/ ├── api │ ├── dist │ ├── swagger.yml │ └── yaml_to_json.py ├── mqtt │ ├── asyncapi.yml │ └── dist └── uml ├── create_html.sh ├── operationflow │ └── 01.xxxxx.puml ├── sequence │ ├── 00.xxxxx.puml └── usecase └── 01.xxxxx.puml
利用しているツールの紹介
先ほどのプロジェクト概要と順番が前後しますが、ドキュメント類の作成に使用しているツールをご紹介します。 これらのツールからHTMLファイルを出力し、S3でWebサイトとしてホスティングするのが目標になります。
Swagger
REST APIを記述するための仕様です。周辺ツールまでひっくるめて一括りに「Swagger」ということが多いです。 非常に有名なツールなので、ご存知の方も多いかと思います。
Swaggerの周辺ツールでドキュメントをWeb上で公開するためのSwagger UIというツールがあるので、これを使ってドキュメントを公開します。
AsyncAPI
平たく言うとMQTT版のSwaggerです。 詳細は新井がブログにまとめてくれるそうなので割愛します。 npmにasyncapi-docgenというパッケージがあり、このパッケージを利用する事でドキュメントをHTML形式で出力する事ができます。
PlantUML
UML等の図を作成できるツールです。私はVS Codeのプラグインを利用することが多いです。 http://plantuml.com/
作成した図はPNG等の形式で画像として出力できます。
手順
実際に環境構築の手順を見ていきます。
S3バケットの作成
まずは静的ウェブサイトホスティング用のS3バケットを作成します。 下記のCFnテンプレートから作成します。
AWSTemplateFormatVersion: 2010-09-09 Description: Create Document Hosting Env Resources: S3Bucket: Type: AWS::S3::Bucket DeletionPolicy: Retain Properties: WebsiteConfiguration: IndexDocument: index.html S3BuketPolicy: Type: "AWS::S3::BucketPolicy" Properties: Bucket: !Ref S3Bucket PolicyDocument: Statement: - Action: - "s3:GetObject" Effect: "Allow" Resource: Fn::Join: - "" - - "arn:aws:s3:::" - Ref: "S3Bucket" - "/docs/*" Principal: "*" Condition: IpAddress: aws:SourceIp: - "xxx.xxx.xxx.xxx/32" #アクセスを許可したいアクセス元GIP Outputs: S3BucketName: Value: !Ref S3Bucket S3BucketArn: Value: !GetAtt S3Bucket.Arn
なお、注意点としてS3へのアクセスはHTTPSを強制することができません。 IP制限をかけているとは言え、HTTPでアクセスされると間の経路で盗聴されるリスクが残るので、きちんと構築するならCloudFrontとLambda@EdgeもしくはAWS WAFを組み合わせてHTTPSを強制しつつIP制限をかけるべきです。 このあたりの詳細はこのブログの趣旨から外れるので、今回はS3のみで話を進めます。
CI/CDの設定
実際にCircleCIを使ってジョブを作成していきます。 なお、本ブログで紹介するのはCircelCIですが、手順自体はその他のCI/CDツールにも展開可能です。
まずは設定ファイル全体を貼っておきます。
基本的な思想として、各ツールごとのジョブでドキュメントを作成し、最後に各ジョブの成果物をs3 sync
でS3バケットに同期します。
version: 2 jobs: create_mqtt_docs: docker: - image: circleci/node:8 steps: - checkout - attach_workspace: at: ./ - run: name: create mqtt docs command: | npm install asyncapi-docgen ./node_modules/.bin/adg docs/mqtt/asyncapi.yml -o docs/mqtt/dist/ - persist_to_workspace: root: ./ paths: - docs/mqtt/dist create_plant_uml_docs: docker: - image: circleci/java:8 steps: - checkout - attach_workspace: at: ./ - run: name: create plant uml docs command: | set -x mkdir -p .plant_uml sudo apt-get update sudo apt-get install graphviz fonts-ipafont wget http://sourceforge.net/projects/plantuml/files/plantuml.1.2018.11.jar/download -O ./plantuml.jar java -jar ./plantuml.jar -Dfile.encoding=UTF-8 -DPNG=png docs/uml/**/*.puml -o dist sh docs/uml/create_html.sh ${S3_WEB_HOSTING_URL} >> docs/uml/index.html - persist_to_workspace: root: ./ paths: - docs/uml swagger_yaml_to_json: docker: - image: circleci/python:3.6 steps: - checkout - attach_workspace: at: ./ - run: name: Convert swagger YAML file to JSON command: | pip install pyyaml --user python docs/api/yaml_to_json.py docs/api/swagger.yml - persist_to_workspace: root: ./ paths: - docs/api create_swagger_docs: docker: - image: swaggerapi/swagger-ui steps: - checkout - run: name: install ca-certificates for circleci command: | set -x apk add --no-cache ca-certificates - attach_workspace: at: ./ - run: name: create_swagger_doc command: | set -x sed -i "s|https://petstore.swagger.io/v2/swagger.json|${S3_WEB_HOSTING_URL}docs/api/swagger.json|g" /usr/share/nginx/html/index.html cp /usr/share/nginx/html/* docs/api/dist cp docs/api/swagger.json docs/api/dist - persist_to_workspace: root: ./ paths: - docs/api/dist deploy_docs: docker: - image: circleci/python:3.6 steps: - checkout - attach_workspace: at: ./ - run: name: deploy_docs command: | set -x pip install awscli --user export PATH=$PATH:/home/circleci/.local/bin/ aws s3 sync docs/mqtt/dist s3://${S3_BUCKET_FOR_WEB_HOSTING}/docs/mqtt aws s3 sync docs/api/dist s3://${S3_BUCKET_FOR_WEB_HOSTING}/docs/api aws s3 cp docs/uml/index.html s3://${S3_BUCKET_FOR_WEB_HOSTING}/docs/uml/index.html for dir in `find docs/uml -type d | grep dist` do aws s3 sync $dir s3://${S3_BUCKET_FOR_WEB_HOSTING}/${dir} done workflows: version: 2 deploy_workflow: jobs: - create_plant_uml_docs - create_mqtt_docs - swagger_yaml_to_json - create_swagger_docs: requires: - swagger_yaml_to_json - deploy_docs: requires: - create_mqtt_docs - create_swagger_docs - create_plant_uml_docs
実際には依存ライブラリをキャッシュさせたり、YAMLのアンカーを使って記述を共通化したりしていますが、分かりやすさ重視で少し改変しています。
ジョブの実行に必要になるため事前にCircleCIの設定で下記の環境変数を設定しておいて下さい
- AWS_ACCESS_KEY_ID → AWSアクセスキー
- AWS_SECRET_ACCESS_KEY → AWS シークレットキー
- S3_BUCKET_FOR_WEB_HOSTING → ドキュメントを保存するためのS3バケット名
- S3_WEB_HOSTING_URL → S3の静的Webサイトホスティング機能で公開するURL(https://s3-リージョン.amazonaws.com/S3バケット名>)
ここから個々のジョブの詳細について説明していきます。
Swagger用のジョブ
まずSwaggerのドキュメントをSwagger UIで公開するためのジョブです。 Swagger UIでドキュメントを公開するにはJSON形式の定義ファイルが必要になるため、まず1つ目のジョブでYAMLからJSONに変換します。 ※あまりやらないと思いますが、APIの定義をゴリゴリJSONで書いている場合はこのステップは不要です。
swagger_yaml_to_json: docker: - image: circleci/python:3.6 steps: - checkout - attach_workspace: at: ./ - run: name: Convert swagger YAML file to JSON command: | pip install pyyaml --user python docs/api/yaml_to_json.py docs/api/swagger.yml - persist_to_workspace: root: ./ paths: - docs/api
YAMLをJSONに変換する処理の本体はこちらです。
import decimal import json import os import sys import yaml class DecimalEncoder(json.JSONEncoder): def default(self, o): if isinstance(o, decimal.Decimal): if o % 1 > 0: return float(o) else: return int(o) return super(DecimalEncoder, self).default(o) def main(): args = sys.argv if len(args) != 2: print('please specify swagger def file') quit() with open(args[1]) as file: swagger_def = yaml.load(file.read()) remove_invalid_key(swagger_def.get('paths', {})) json_path = os.path.join(os.path.dirname(__file__), "swagger.json") with open(json_path, 'w') as file: file.write(json.dumps(swagger_def, cls=DecimalEncoder)) def remove_invalid_key(definition: dict): # keyにAmazon独自仕様のkeyがあれば削除 del_keys = list() for key in definition: if key.startswith('x-amazon'): del_keys.append(key) for key in del_keys: del definition[key] # 子供に辞書オブジェクトがあれば再帰呼び出し for key in definition.keys(): if isinstance(definition[key], dict): remove_invalid_key(definition[key]) if __name__ == "__main__": main()
JSONへの変換ついでに、AWS独自仕様のx-amazon
から始まる定義を削除しています。
よく考えたらNode.jsで実装しておけば次のジョブとまとめることができたのですが、このまま進めます。
2つ目のジョブ定義です。
create_swagger_docs: docker: - image: swaggerapi/swagger-ui steps: - checkout - run: name: install ca-certificates for circleci command: | set -x apk add --no-cache ca-certificates - attach_workspace: at: ./ - run: name: create_swagger_doc command: | set -x sed -i "s|https://petstore.swagger.io/v2/swagger.json|${S3_WEB_HOSTING_URL}docs/api/swagger.json|g" /usr/share/nginx/html/index.html cp /usr/share/nginx/html/* docs/api/dist cp docs/api/swagger.json docs/api/dist - persist_to_workspace: root: ./ paths: - docs/api/dist
swaggerapi/swagger-ui
のDockerコンテナ内/usr/share/nginx/html/
に静的サイトを公開するために必要なHTMLやJSが置かれているので、それらのファイルをpersist_to_workspace
で指定して後続のドキュメントデプロイ用ジョブに引き渡します。
Swagger UIのバージョンアップに追随しないのであれば、いちいちdockerでジョブを動かさずにHTML等のファイルをGitリポジトリにコミットしておいても良いと思います。
ポイントは
sed -i "s|https://petstore.swagger.io/v2/swagger.json|${SWAGGER_JSON_URL}|g" /usr/share/nginx/html/index.html
の部分で、Swagger UIに読み込ませる定義ファイルとして先ほど作成したJSON形式のSwagger定義ファイルを指定しています。
AsyncAPI用のジョブ
次にAsyncAPIのドキュメントを公開するためのファイルを抽出します。
create_mqtt_docs: docker: - image: circleci/node:8 steps: - checkout - attach_workspace: at: ./ - run: name: create mqtt doc command: | npm install asyncapi-docgen ./node_modules/.bin/adg docs/mqtt/asyncapi.yml -o docs/mqtt/dist/ - persist_to_workspace: root: ./ paths: - docs/mqtt/dist
npm install
でasyncapi-docgen
を導入し
./node_modules/.bin/adg <asyncapiの定義ファイル> -o <出力先>
でHTMLやCSSを出力しています。
PlantUML用のジョブ
最後にPlantUML用のジョブです。
create_plant_uml_docs: docker: - image: circleci/java:8 steps: - checkout - attach_workspace: at: ./ - run: name: create plant uml docs command: | set -x mkdir -p .plant_uml sudo apt-get update sudo apt-get install graphviz fonts-ipafont wget http://sourceforge.net/projects/plantuml/files/plantuml.1.2018.11.jar/download -O ./plantuml.jar java -jar ./plantuml.jar -Dfile.encoding=UTF-8 -DPNG=png docs/uml/**/*.puml -o dist sh docs/uml/create_html.sh ${S3_WEB_HOSTING_URL} >> docs/uml/index.html - persist_to_workspace: root: ./ paths: - docs/uml
必要なライブラリやフォントを導入した後
java -jar ./plantuml.jar -Dfile.encoding=UTF-8 -DPNG=png docs/uml/**/*.puml -o dist
でUMLの画像を生成し、
sh docs/uml/create_html.sh ${S3_WEB_HOSTING_URL} >> docs/uml/index.html
の部分で画像を表示するHTMLファイルを生成しています。 実行しているシェルスクリプトは下記のような内容です。
#!/bin/bash cat <<EOT <!DOCTYPE html><head><meta charset="utf-8"></head><body> EOT for file in `find docs/uml -type f -name "*.png"` do cat <<EOT <p><img src="$1$file"/></p> EOT done cat <<EOT </body></html> EOT
ひたすらimgタグが並ぶだけのデザインセンス0のHTMLが出力されます。
<html><head><meta charset="utf-8"></head><body> <p><img src="https://s3-ap-northeast-1.amazonaws.com/xxxxx/docs/uml/usecase/dist/01.usecase.png"></p> </body></html>
ドキュメントデプロイ用のジョブ
最後にこれまでのジョブで生成されたファイルをS3に流してドキュメントを公開します。
deploy_docs: docker: - image: circleci/python:3.6 steps: - checkout - attach_workspace: at: ./ - run: name: deploy_docs command: | set -x pip install awscli --user export PATH=$PATH:/home/circleci/.local/bin/ aws s3 sync docs/mqtt/dist s3://${S3_BUCKET_FOR_WEB_HOSTING}/docs/mqtt aws s3 sync docs/api/dist s3://${S3_BUCKET_FOR_WEB_HOSTING}/docs/api aws s3 cp docs/uml/index.html s3://${S3_BUCKET_FOR_WEB_HOSTING}/docs/uml/index.html for dir in `find docs/uml -type d | grep dist` do aws s3 sync $dir s3://${S3_BUCKET_FOR_WEB_HOSTING}/${dir} done
ドキュメントを更新してプッシュしてみる
準備ができたので、適当にドキュメントを更新してGitHubにプッシュしてみます。 すると・・・
WorkFlowが動き出しました! 全てのジョブ完了後にS3でホスティングしている静的Webサイトにアクセスすると、冒頭で紹介したような画像を確認することができます!! これでステークホルダーとの認識合わせもスムーズに行えそうです。
まとめ
CI/CDツールとS3を活用したドキュメントの共有方法をご紹介しました。 最初の環境構築には少し時間がかかりますが、一度環境が構築できてしまえば便利に使うことができます。 こういった手法も活用しながら、効率良く開発を進めていきたいですね。
誰かのお役に立てば幸いです。