この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
サーバーレス開発部@大阪の岩田です。 サーバーレス開発部の良くある案件パターンとして、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バケットに同期します。
.circleci/config.yml
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に変換する処理の本体はこちらです。
yaml_to_json.py
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ファイルを生成しています。 実行しているシェルスクリプトは下記のような内容です。
create_html.sh
#!/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が出力されます。
index.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を活用したドキュメントの共有方法をご紹介しました。 最初の環境構築には少し時間がかかりますが、一度環境が構築できてしまえば便利に使うことができます。 こういった手法も活用しながら、効率良く開発を進めていきたいですね。
誰かのお役に立てば幸いです。