SORACOM Orbit向けGitHub Actionを作りました
この記事は
SORACOM Orbitの開発・運用をより効率的かつ安全に行うためのGitHub Actionを作ったお話です。
これまでのSORACOM Orbitの開発とその課題
SORACOM Orbitの開発は、基本的には公式ドキュメントに沿って進めていきます。
ざっくりと手順を説明しますと、
- 開発環境をダウンロードする
- 環境に含まれているdevContainerを使って開発する
- ビルドしたsoraletをSORACOMコンソールないしはSORACOM cliでアップロードする
- アップロードしたsoraletをSIMグループに紐付ける
という流れになります。
アップロードされたsoraletはSORACOM内部でバージョン管理が行われており、SIMグループに紐付ける(Orbitの設定をする)際は特定のバージョンか、LATEST(その時の最新のバージョン)を指定します。
さて、この流れでやってると以下のような問題が起こることがあります。
- 間違えて古いバイナリをアップロードしてしまう
- テストをしていないバイナリをアップロードしてしまう
- アップロード時のパラメータ指定を間違えて、想定していないsoraletにアップロードしてしまう
- アップロード先のsoraletのLATESTを参照しているSIMグループがあって、運用中のシステムに不具合が発生する
SORACOM Orbitの開発/運用以外でも似たような話、結構耳にしますよね?
こういった事故を防ぐためには、ちゃんとCI/CDのパイプラインを作っておくのが大切です。そこで、GitHub Actionを使ってCI/CDのパイプラインを作ってみました。パイプラインを作った件については会社ブログに書いたので、以下の記事を参照してください。
GitHubを活用してSORACOM Orbitの開発を加速する: CI/CD編
さて、上記記事ではひとまずGitHubでのCI/CDは実現できていますが、ワークフロー定義内でSORACOM cliコマンドを駆使していて、シェルスクリプトを書いてるのとほぼ変わらない状態です。これはちょっと美しくないし、使い回しにくいです。そこで、カスタムGitHub Actionを作ってみることにしました。
soraletをアップロードするカスタムアクションを作る
GitHubのカスタムアクションの作り方としては、Dockerを使う方法とjavascript(Node.js)を使う方法があります。
Dockerを使う場合は、SORACOM cliをインストールしたカスタムイメージ内で、SORACOM cliを実行するシェルスクリプトを実行するという実装方法が考えられます。この方法は比較的簡単に実現はできますが、ワークフローファイル内でコマンドを書いているのと変わらないので面白くないですね。そこで今回はjavascriptで作ることにしました。
javascriptで作る場合、action.yml
で指定されたjavascriptファイルがNode.jsで実行されます。そのため、このjavascriptファイルの中でSORACOM APIを呼び出していくことになります。
地道にREST APIの呼び出しを書いていくのもいいですが、SORACOM APIはOpenAPIの定義があるので、OpenAPI Generatorを使ってjavascriptのコードを生成しました。
SORACOM APIのOpenAPI定義はこちらにありますので、これをダウンロードしてきます。
openapi-generatorで出力できる言語はこちらに一覧がありますが、今回はtypescript-node
を使うことにしました。
適当なディレクトリで以下のコマンドを実行します。
docker run --rm -v ${PWD}:/local openapitools/openapi-generator-cli generate\
-i /local/soracom-api.ja.yaml\
-g typescript-node\
-o /local/out/typescript-node
これでout/typescript-node
ディレクトリにtypescriptのコードが生成されます。
生成されたコードを用いて、以下の機能を実装していきます。
- 指定されたAuthKeyId/AuthKeyを用いてログインする
- 指定されたsoralet_idのsoraletがなければ作成する
- 指定されたsoralet_idのsoraletに、指定されたファイルをアップロードする
- アップロード時に「バージョンが多すぎる」とエラーになった場合は、使われていないバージョンのうち最も古いものを消して、再度アップロードする
- この削除機能は利用するかどうかを設定で変更できるようにする
実装の詳細は今回は割愛しますが、実際のソースコードを参照してみてください。
kenichiro-kimura/action-soracom-upload-soralet
openapi-generatorで生成されたコードから、2箇所だけ変更を加えています。ここを変更しないとsoraletのアップロードでバイナリーファイルをPOSTできなかったのと、戻り値がうまく取得できませんでした。該当部分は以下になります。
- POSTのパラメータで
json:false
としないとバイナリファイルを正しく処理できない json:false
としたので、API呼び出しの戻り値が自動でJSONにマップされないため、JSON.parse()
を呼ぶ
使ってみる
さて、それでは実際に使ってみます。ワークフローファイルは以下のようになります。
ワークフローファイルの全体図については、先述のGitHubを活用してSORACOM Orbitの開発を加速する: CI/CD編を参照してください。
name: SORACOM Orbit CI/CD
on:
push:
branches:
- master
env:
SORALET_ID: soralet-github-action-test
COVERAGE: jp
jobs:
build-and-test:
(ビルド部分省略)
- name: upload assets
uses: actions/upload-artifact@v2
with:
name: soracom-orbit
path: build/soralet-optimized.wasm
deploy:
runs-on: ubuntu-latest
needs: build-and-test
steps:
- name: download artifact
uses: actions/download-artifact@v2
with:
name: soracom-orbit
- name: upload
uses: kenichiro-kimura/action-soracom-upload-soralet@1.2.0
id: upload
with:
soracom_auth_key: ${{ secrets.AUTH_KEY }}
soracom_auth_key_id: ${{ secrets.AUTH_KEY_ID}}
soracom_coverage: ${{ env.COVERAGE }}
soracom_soralet_id: ${{ env.SORALET_ID }}
soracom_soralet_filename: soralet-optimized.wasm
soracom_delete_old_soralet: true
upload
ステップで、今回作ったカスタムアクションを使ってsoraletをアップロードしています。
master
ブランチにpushされたときに、soraletがアップロードされるようになりましたので、「間違って古いものをアップロードする」「パラメータを間違えて別のsoraletにアップロードする」という事故は防げるようになりました。
ステージング環境を作りたい
しかし、これではアップロード先のsoraletのLATESTバージョンが常にmaster
ブランチのもので更新されます。できればステージング環境にアップロードして、動作検証をしたいところです。ステージング環境を実現するには、deploy
ジョブを例えば以下のようにするという手が考えられます。
on:
push:
branches:
- master
- staging
env:
SORALET_ID_PROD: soralet-github-action-test
SORALET_ID_STG: soralet-github-action-test-staging
COVERAGE: jp
(省略)
deploy:
runs-on: ubuntu-latest
needs: build-and-test
steps:
- name: download artifact
uses: actions/download-artifact@v2
with:
name: soracom-orbit
- name: upload to staging
uses: kenichiro-kimura/action-soracom-upload-soralet@1.2.0
id: upload-staging
if: github.ref == 'refs/heads/staging'
with:
soracom_auth_key: ${{ secrets.AUTH_KEY }}
soracom_auth_key_id: ${{ secrets.AUTH_KEY_ID}}
soracom_coverage: ${{ env.COVERAGE }}
soracom_soralet_id: ${{ env.SORALET_ID_STG }}
soracom_soralet_filename: soralet-optimized.wasm
soracom_delete_old_soralet: true
- name: upload to production
uses: kenichiro-kimura/action-soracom-upload-soralet@1.2.0
id: upload-prod
if: github.ref == 'refs/heads/master'
with:
soracom_auth_key: ${{ secrets.AUTH_KEY }}
soracom_auth_key_id: ${{ secrets.AUTH_KEY_ID}}
soracom_coverage: ${{ env.COVERAGE }}
soracom_soralet_id: ${{ env.SORALET_ID_PROD }}
soracom_soralet_filename: soralet-optimized.wasm
soracom_delete_old_soralet: true
本番環境用とは別にステージング環境用のsoraletを作成し、staging
ブランチにpushすると、そちらを更新するようにします。
そして、ステージング環境用のSIMグループはSORALET_ID_STG
のLATESTを使うように設定します。本番環境用のSIMグループはSORALET_ID_PROD
のLATESTを使うように設定します。
とりあえずこれでうまくいきますが、ステージング環境だけでなく開発環境も・・・と、環境が増えるたびにSIMグループだけでなくsoraletも増えることになります。せっかくSORACOMプラットフォーム側でsoraletのバージョンが管理されているのですから、もうちょっとスマートにやりたいところです。
それぞれの環境がLATESTを参照しているからsoraletを分割しないといけなかったわけですから、手順で行けば以下のようにしてあげればいいことになります。
- アップロード先のsoraletは
SORALET_ID
にする - ステージング環境は
SORALET_ID
のLATESTを参照するように設定する - 本番環境は、
SORALET_ID
の特定のバージョンを参照するように設定する -
staging
ならびにmaster
ブランチにpushされたら、SOLALET_ID
にアップロードする。これによってステージング環境は最新のsoraletになる -
master
ブランチにpushされたときは、アップロードした新しいバージョンを参照するように本番環境の設定を変更する
図で表すとこんな感じです。
つまり、必要になるのは「SIMグループのOrbitの設定を変更する」カスタムアクションです。次はこれを作ります。
SIMグループのOrbitの設定を変更するカスタムアクションを作る
作り方は基本的にアプロード用のカスタムアクションと同様です。OpenAPI Generatorで出力した、SORACOM APIを叩くtypescriptを使います。
そして、以下の機能を実装します。
生成されたコードを用いて、以下の機能を実装していきます。
- Orbitの設定に必要なパラメータを指定する
- 指定されたAuthKeyId/AuthKeyを用いてログインする
- 指定されたGroupIDのSIMグループのOrbitの設定を更新する。
Orbitの設定で指定するパラメータは以下の通りです。
パラメータ名 | 必須 | 説明 |
---|---|---|
CodeSRN | 〇 | soraletのSRN |
Direction | 〇 |
uplink またはdownlink またはuplink,downlink
|
ContentType | デフォルトはapplication/json
|
|
Enabled | デフォルトはfalse
|
|
UseLocation | デフォルトはfalse
|
|
UseMetadata | デフォルトはfalse
|
そして、パラメータのCodeSRN
に、アップロードステップの出力に含まれるsrnを渡してやればOKです。
実装の詳細は今回は割愛しますが、実際のソースコードを参照してみてください。
kenichiro-kimura/action-soracom-configure-orbit
使ってみる
早速使ってみましょう。デプロイ部分を以下のように変更します。
on:
push:
branches:
- master
- staging
env:
SORALET_ID: soralet-github-action-test
COVERAGE: jp
jobs:
(省略)
deploy:
runs-on: ubuntu-latest
needs: build-and-test
steps:
- name: download artifact
uses: actions/download-artifact@v2
with:
name: soracom-orbit
- name: upload
uses: kenichiro-kimura/action-soracom-upload-soralet@1.2.0
id: upload
with:
soracom_auth_key: ${{ secrets.AUTH_KEY }}
soracom_auth_key_id: ${{ secrets.AUTH_KEY_ID}}
soracom_coverage: ${{ env.COVERAGE }}
soracom_soralet_id: ${{ env.SORALET_ID }}
soracom_soralet_filename: soralet-optimized.wasm
soracom_delete_old_soralet: true
- name: deploy
uses: kenichiro-kimura/action-soracom-simgroup-soralet@1.0.0
if: github.ref == 'refs/heads/master'
with:
soracom_auth_key: ${{ secrets.AUTH_KEY }}
soracom_auth_key_id: ${{ secrets.AUTH_KEY_ID}}
soracom_coverage: ${{ env.COVERAGE }}
soracom_soralet_code_srn: ${{ steps.upload.outputs.soralet_srn }}
soracom_soralet_direction: uplink
soracom_soralet_enabled: true
soracom_group_id: ${{ secrets.SORACOM_GROUP_ID }}
スッキリしましたね!
ステージを増やしたくなったら、条件とグループIDを変えたdeploy
ステップを追加するだけでOKです。
今回作ったアクション
今回作ったアクションの、GitHub Marketplaceでのリンクは以下になります。
まとめ
Soraletのコードは小規模で、それほど頻繁に更新されることもないからCI/CDパイプラインまで作るほどではない・・・と思われている方も多いかもしれません。
しかし、Orbitがデバイスとのデータのやり取りの部分に必ず挟まるということを考えると、soraletの更新はSORACOMプラットフォーム(ならびにOrbit)を使ったみなさまのシステムの動作全体に影響することになります。つまり、運用中のシステムでのOrbitの設定ミスは、システム全体の事故につながるということです。
重大な事故を避け、安心して開発を行うためにはやはり開発初期段階でCI/CDパイプラインを組んでおくのが重要だといえます。
今回私が作ったカスタムアクションを使えば、このCI/CDパイプラインは短期間で構築できます(必要なのはSIMグループならびにGitHubのシークレット設定程度で、ほぼコピペで作れる)。
皆様のsoralet開発並びに運用のお役に立てれば幸いです。
Discussion