APIの開発を行う場合、APIの仕様書に従ってアプリケーションを実装し、それを更に開発者自身やテストチーム・QAチームがテストする。
APIの仕様が変更された場合やアプリケーションの実装を変更した場合にはテストを毎回行うが、これを手動でやるのは面倒なのでCI/CDパイプラインを実装して自動化してみようと思う。
なお、APIの開発・デプロイにGitOps・DevOpsの概念を適用したものをAPIOpsと呼ぶ模様。今回の実装はAPIOpsをどう小さく実現するかの検証にもなっている。
ここでは以下のような条件で自動化する。
- APIはアプリ側で管理せず、API Gatewayで管理する
- API Gatewayの設定も自動で変更する
- ラボは用意せず、自身のPCで完結させる(テストなどは自PC上で動くようにし、API Gatewayも自PC上に立てる)
- GitOps部分はGitHubを利用する
ユーザがPullRequest(PR)かPushをすると、GitHubがRunnerをキックして自PC内のツールを使ってテストや設定のデプロイなどを自動で行う。
なお、Runnerが実行するコードをミスると自PCにも影響を及ぼす可能性があるため、この構成を推奨している訳ではない点は注意。
また、前提として以下で進める。
- APIの仕様はOpenAPIのSpecificationに準じてYAML形式で作成する
- APIの仕様はInsomniaで作成する
- 対象とするInsomniaのプロジェクトはこちらの記事で作成したQiitaAPIとする
- API GatewayにはDocker版Kongを使用する
今回、動作確認で使う自PCは以下のものを利用した。
- MacBook Pro M2 (32GBRAM)
メモリ使用量を見ている感じだと16GBでも行けそうではある。
手順としては以下の順番で進める。
- Kong Gatewayの構築
- GitOpsに使うリポジトリ・Runnerの用意
- テストやデプロイに使うCLIの用意
- CLIの動作確認
- GitHubActionsのYAMLの作成と動作確認
Kong Gatewayの構築
GitHubのKongのGetting Startedに従ってMac上に起動すると各コンテナは正常に起動するのだが、何故かDBlessモードと判断されて、後々以下のエラーが出てGateway Servicesの作成に失敗する。
Error: 1 errors occurred:
while processing event: Create service Qiita_API failed: HTTP status 405 (message: "cannot create or update 'services' entities when not using a database")
なので、ここではGitHubで提供されているやり方ではなく、KongのDocsで提供されているやり方でKong Gatewayを起動する。
以下でスクリプトを生成する。
cat <<EOF > ./run.sh
#!/bin/bash
COMM=$1
if [ "$COMM" == "start" ]; then
docker network create kong-net
docker run -d --name kong-database \
--network=kong-net \
-p 5432:5432 \
-e "POSTGRES_USER=kong" \
-e "POSTGRES_DB=kong" \
-e "POSTGRES_PASSWORD=kongpass" \
postgres:13
docker run --rm --network=kong-net \
-e "KONG_DATABASE=postgres" \
-e "KONG_PG_HOST=kong-database" \
-e "KONG_PG_PASSWORD=kongpass" \
-e "KONG_PASSWORD=test" \
kong/kong-gateway:3.6.1.1 kong migrations bootstrap
docker run -d --name kong-gateway \
--network=kong-net \
-e "KONG_DATABASE=postgres" \
-e "KONG_PG_HOST=kong-database" \
-e "KONG_PG_USER=kong" \
-e "KONG_PG_PASSWORD=kongpass" \
-e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \
-e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \
-e "KONG_PROXY_ERROR_LOG=/dev/stderr" \
-e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \
-e "KONG_ADMIN_LISTEN=0.0.0.0:8001" \
-e "KONG_ADMIN_GUI_URL=http://localhost:8002" \
-e KONG_LICENSE_DATA \
-p 8000:8000 \
-p 8443:8443 \
-p 8001:8001 \
-p 8444:8444 \
-p 8002:8002 \
-p 8445:8445 \
-p 8003:8003 \
-p 8004:8004 \
kong/kong-gateway:3.6.1.1
elif [ "$COMM" == "stop" ]; then
docker kill kong-gateway
docker kill kong-database
docker container rm kong-gateway
docker container rm kong-database
docker network rm kong-net
else
echo "Unknown argument."
fi
EOF
chmod +x ./run.sh
スクリプトを実行する。なお、止めたい時は引数をstop
に変更する。
./run.sh start
起動したらブラウザでhttp://localhost:8002につなげばKong ManagerのUIが確認できる。
リポジトリとRunnerの準備
GitHub上に適当なリポジトリをPrivateで作成する。
Runnerを登録する。
Settings
->Actions
->Runners
からNew self-hosted runner
を選択し、Runnerを自PCで動かすための設定を行う。
ここではMacで進めるため、Macを選択して表示されたコードを実行する。
mkdir actions-runner && cd actions-runner
curl -o actions-runner-osx-x64-2.314.1.tar.gz -L https://github.com/actions/runner/releases/download/v2.314.1/actions-runner-osx-x64-2.314.1.tar.gz
tar xzf ./actions-runner-osx-x64-2.314.1.tar.gz
./config.sh --url https://github.com/imurata/sandbox-actions --token AOSRR5YX6xxxxxxxxxxxx
config.sh
を実行すると色々聞かれるが、これは全てデフォルトのままでOK。
公式手順では最後にrun.sh
を実行するが、サービス化して自動実行したい人は「セルフホストランナーアプリケーションをサービスとして設定する」を参考にsvc.sh
を使ってサービスをインストール・実行するとよい。
問題なく追加できると、GitHub上でStatusが緑で表示されるようになる。
RunnerのホストにCLIをインストール
API作成・管理の前提としてInsomnia、Kongを利用するとしていたが、これらを使っている場合、それらの機能を利用するためのCLIが
Insomniaにはinso
コマンド、KongにはdecK
コマンドとして用意されている。
これら各コマンドをRunnerに実行させて自動化を進める。
自動化したい内容とCLIのマッピングは以下となる。
自動化項目 | コマンド |
---|---|
Linterでのチェック | inso lint spec |
APIのテスト | inso run test |
API Gatewayの設定の生成・チェック | inso export spec deck file openapi2kong deck gateway validate |
API Gatewayの設定の反映 | deck gateway sync |
動作確認 | http --check-status |
ということで、上記のコマンドをインストールする。
brew install inso
brew install deck
brew install httpie
コマンドの確認
Runnerに実行させる前に一度手でコマンドを実行する。
Linterでのチェック
inso
CLIの前提として、.insomnia
ディレクトリがない場合は以下のディレクトリを参照する。
OS | 場所 |
---|---|
Windows | %APPDATA%\Insomnia |
Linux |
$XDG_CONFIG_HOME/Insomnia か~/.config/Insomnia
|
Mac | ~/Library/Application\ Support/Insomnia |
今回は開発端末上でinso
コマンドを実行するので問題ないが、Dockerコンテナ上でinso
コマンドを使う場合は注意。(参考:公式のDockerでの利用手順)
またコマンドのリファレンスはCLI Command Referenceに記載がある。
APIのSpecのチェックはinso lint spec
で行うことが出来、引数にInsomniaのプロジェクト作成後に作ったドキュメントの名前を渡すことで、そのドキュメントのチェックが行われる。また、--verbose
をつけると詳細情報が出るので、これも併せて渡しておく。
inso lint spec my-spec.yaml --verbose
実行した結果は以下のようになった。
$ inso lint spec my-spec.yaml --verbose
› Data store configured from app data directory at /Users/imurata/Library/Application Support/Insomnia
› Load api specification with identifier my-spec.yaml from data store
› Found 1.
› Linting specification from database contents
No linting errors or warnings.
$ echo $?
0
なお、コマンドの引数にファイル名を渡さない場合、対話型でファイルを指定することが出来る。
APIのテスト
テストはinso run test
で実行できる。
基本的にはテストスイートの名前と環境変数の箱の名前を指定して実行する。またこちらも--verbose
による詳細化が可能なのでつけるようにしておく。
inso run test "Qiita Test Suite" --env myenv --verbose
ここでは紹介しないがオプションでテストケースを絞ったり、テスト失敗時の挙動を変更したり出来るので、CI/CDの中でテストケースを弄りたい場合はどういうオプションがあるかを確認しておくとよい。
実行結果は以下となる。
$ inso run test "Qiita Test Suite" --env myenv --verbose
› Data store configured from app data directory at /Users/imurata/Library/Application Support/Insomnia
› Load api specification with identifier Qiita Test Suite from data store
› Found 0.
› Load workspace with identifier Qiita Test Suite from data store
› Found 0.
› Load unit test suite with identifier Qiita Test Suite from data store
› Found 1.
› Load base environment for the workspace wrk_cb60ad72964441d2bc9656c516bf46b5 from data store
› Found 1.
› Load sub environment with identifier myenv from data store
› Found 1
Qiita Test Suite
[network] Response succeeded req=req_59c8b171e7f74c6d8cec4c09d06ecc99 status=200
✔ Returns 200 (1019ms)
[network] Response succeeded req=req_f1c6f73cd4f945e89bc6ca5d1acc3386 status=200
✔ Returns 200 (206ms)
[network] Response succeeded req=req_77c3a7e52760490fa13707f8348eb084 status=200
✔ Returns 200 (210ms)
3 passing (1s)
ちなみにテストに失敗した時はInsomniaのUIから実行したときと比べてCLIで実行した時の方が細かくエラー情報が表示される。
API Gatewayの設定の生成・チェック
ここでは以下の手順で設定ファイルを生成・チェックしていく。
- OpenAPIの仕様をファイル化(
inso export spec
) - OpenAPIの仕様が書かれたファイルからKongの設定ファイルを生成(
deck file openapi2kong
- Kongと通信して設定ファイルの有効性を検証(
deck gateway validate
)
OpenAPIの仕様をファイル化
以下のコマンドでInsomniaのDB内にあるOpenAPIの仕様をYAMLファイルとして出力する。
inso export spec my-spec.yaml --verbose --output ./my-spec-export.yaml
こちらも--verbose
をつけて詳細情報を出すようにしておく。また、--output
オプションがないと標準出力に表示するため、--output
をつけてファイル化する。
OpenAPIの仕様が書かれたファイルからKongの設定ファイルを生成
ここからはdeck
コマンドで行う。deck
コマンドはKong API Gatewayの設定を管理するためのCLIであり、コマンド仕様がinso
とは少し異なる。
OpenAPIの仕様をKongの設定ファイルに変換するには以下のコマンドを実行する。
cat my-spec-export.yaml | deck file openapi2kong --inso-compatible --output-file my-spec-kong.yaml --verbose 1
deck file openapi2kong
は標準出力を入力として受け取るため、cat
で吐いたものをパイプで渡す。
また、互換性のため--inso-compatible
をつけることが推奨されているため、こちらも付与しておく。
Kongと通信して設定ファイルの有効性を検証
以下のコマンドで先ほど作成したKongの設定ファイルの検証を行う。
deck gateway validate my-spec-kong.yaml --verbose 1
--verbose 1
については、つけておくと確認先のKong Gatewayのアドレスが表示されるので、トラシュ用につけている。
なお、参照先のKong Gatewayのアドレスはデフォルトでhttp://localhost:8001
となっている。
これは自PCにKongを立ち上げている人は問題ないが、別の環境にKongを立てている人は--kong-addr
もしくは環境変数DECK_KONG_ADDR
で変更してあげる必要がある。
API Gatewayの設定の反映
最後にdeck gateway sync
で設定を反映する。
ただ、ここではお試しで確認するので、反映後に元に戻せるようにしておきたい。
そのために、先にバックアップを取っておく。バックアップはdeck gateway dump
で取得できる。
deck gateway dump -o backup.yaml
また、設定を適用したらどう変わるかを確認したい場合はdeck gateway diff
で確認できる。
$ deck gateway diff my-spec-kong.yaml
creating service Qiita_API
creating route Qiita_API-getitems
creating route Qiita_API-getitemsid
creating route Qiita_API-getuserinfo
Summary:
Created: 4
Updated: 0
Deleted: 0
バックアップや変更点の確認が終わったら、実際にコマンドを実行して設定を反映する。
deck gateway sync my-spec-kong.yaml
問題なければ以下のような出力が得られる。
creating service Qiita_API
creating route Qiita_API-getitems
creating route Qiita_API-getuserinfo
creating route Qiita_API-getitemsid
Summary:
Created: 4
Updated: 0
Deleted: 0
http://localhost:8002を開いてKong ManagerからGateway Services
を確認すると、Qiita_APIというのが作成されているのがわかる。
以下のようにRouteも作成されており、Kong経由でアクセスできるようになった。
動作確認
http
コマンドはcurl
の上位版みたいなコマンドで、このコマンドの--check-status
がCI/CDパイプラインに使いやすいのでここではこのコマンドで動作確認する。
Kong Gatewayはデフォルトでポート8000にアクセスすることで、先ほどKong ManagerのUIで確認したRoute内のパスにルーティングしてくれるので、以下のような感じで各リクエストを発行する。
http -h --check-status localhost:8000/items
http -h --check-status localhost:8000/items/bf1a1aa9f2a609864e7e
http -h --check-status --auth-type bearer --auth 56738b89775bcxxxxxx localhost:8000/authenticated_user
-h
オプションでヘッダのみ表示し、--check-status
オプションで200以外の値の時に非ゼロでコマンドの戻り値を返すようにしている。
認証のチェックでは--auth-type
と--auth
でBearerの指定およびトークン渡しを実施している。
問題なければそれぞれHTTP/1.1 200 OK
が返ってくる。
確認が終えたら先程のバックアップを使ってKong Gatewayの設定を元に戻す。
$ deck gateway sync backup.yaml
deleting route Qiita_API-getuserinfo
deleting route Qiita_API-getitems
deleting route Qiita_API-getitemsid
deleting service Qiita_API
Summary:
Created: 0
Updated: 0
Deleted: 4
CLIでの手動確認は以上となる。
GitHubActionsの設定
ここからは先程確認したCLIの内容をGitHubActionsに落とし込んでいく。
最初にBearerのトークンをYAMLに直接書くのを避けるために、トークンをActionsのSecretととして定義する。
リポジトリのSetting
->Secrets and variables
->Actions
からNew repository secret
を選択して値を作成する。
次にActionsのYAMLを書いていく。
先ほど用意したリポジトリをCloneしてディレクトリに移動する。
git clone https://github.com/imurata/sandbox-actions.git
cd sandbox-actions
GitHub Actionsは.github/workflows
の下のYAMLを解釈して動作するため、ディレクトリを作成してYAMLをその中に作成する。
ワークフロー構文を見ながら以下のような感じで作成する。
mkdir -p .github/workflows
cat <<'EOF' > ./.github/workflows/push.yaml
name: APIOps Test
on:
push:
branches:
- main
jobs:
apiops_test:
runs-on: self-hosted
name: Test, Generate Config, Deploy
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Linting
run: inso lint spec my-spec.yaml --verbose
- name: Testing
run: inso run test "Qiita Test Suite" --env myenv
- name: Export OpenAPI Spec
run: inso export spec my-spec.yaml --verbose --output ./my-spec-export.yaml
- name: Convert from OpenAPI Spec to Kong Confing
run: cat my-spec-export.yaml | deck file openapi2kong --inso-compatible --output-file my-spec-kong.yaml --verbose 1
- name: Validate Kong Config
run: deck gateway validate my-spec-kong.yaml --verbose 1
- name: Deploy Kong Config
run: deck gateway sync my-spec-kong.yaml
- name: Confirmation
run: |
sleep 5
http -h --check-status GET localhost:8000/items
http -h --check-status GET localhost:8000/items/bf1a1aa9f2a609864e7e
http -h --check-status --auth-type bearer --auth ${{secrets.QIITA_API_TOKEN}} GET localhost:8000/authenticated_user
EOF
今回はガチ実装ではなくAPIOps実装の検証目的であるため、PR時の処理は見送った。
基本的に手作業で確認した際のコマンドをコピペしているが、Bearerトークンの箇所だけ${{secrets.QIITA_API_TOKEN}}
に置き換えている点に注意。
また、Kongへの設定反映後、アクセスが早すぎるとRouteが作成されていなくてhttp
コマンドが失敗するため、sleepを入れている。
あと原因が分からないが、Runnerから実行した場合に限りhttp
コマンドがなぜかPOST
で動いて上手く動かなかったので、明示的にGET
で動くよう引数を追加している。
上記ファイルを作成後、pushする。
git add .
git commit -m "initial commit"
git push origin main
PushするとGitHub Actionsが動き出し、上記で定義したYAMLの内容が実行される。
走りきった後にはKong ManagerからServiceやRouteも確認できる。
終わりに
ということで、GitHubの力を借りて自PCだけでAPIOps的なCI/CDパイプラインを実装することが出来た。
と言いつつ、大事な点に触れていなかったことにお気づきだろうか。
OpenAPIのYAMLをGitで管理していないので、パイプラインのトリガーがOpenAPIの仕様変更に対応していなかったことに。
実はInsomniaのVersion8でフリー版からGit Sync機能が削除されたので、Insomnia利用時にAPIの仕様変更契機で直接キックすることが出来なさそう。
Insomniaを使った厳密なGitOpsをやりたい場合はVersion7系を使うか、有料版を使う必要がある点は注意が必要となる。
(※Insomniaを無理して使わず、OpenAPIのYAMLをリポジトリ上で管理してしまえばもっと簡単に実現は出来る)
その点を置いておけば、GitHubをGitLabに置き換えQiitaのAPIをローカルなサービスに置き換えれば、自PCで全て完結するオフラインでも利用可能なAPIOps環境なんかも作れそうなので、それなりに使えそうではある。