🛰️

SORACOM Orbit向けGitHub Actionを作りました

2023/05/22に公開

この記事は

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できなかったのと、戻り値がうまく取得できませんでした。該当部分は以下になります。

使ってみる

さて、それでは実際に使ってみます。ワークフローファイルは以下のようになります。
ワークフローファイルの全体図については、先述の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されたときは、アップロードした新しいバージョンを参照するように本番環境の設定を変更する

図で表すとこんな感じです。

staging/masterブランチへのプッシュと、ステージング/運用環境から参照するsoraletバージョンの関係

つまり、必要になるのは「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開発並びに運用のお役に立てれば幸いです。

GitHubで編集を提案

Discussion