この記事は、 NTT Communications Advent Calendar 2022 16日目の記事です。
はじめに
こんにちは、イノベーションセンター テクノロジー部門の池田です。 普段はSkyWayというプラットフォームを開発しています。
この記事では、GitHub ActionsからGoogle Cloud Platform(以下GCP)のCloud FunctionsにPipenvを利用したPythonアプリケーションをデプロイした際の話をGitHubのEnvironmentsなどに触れつつ紹介したいと思います。
モチベーション
SkyWayで使うPythonのアプリケーションをクラウド上にデプロイしたかったのですが、毎度手動でデプロイするのはもちろん面倒です。 また、自動化した場合でもproduction,stagingなどの環境ごとに条件分岐を書いたり、意図しない自動デプロイが発生したりする可能性もあります。
これらの問題をEnvironmentsを使うことで解消できそうだったため導入しました。
行ったこと
Environmentsの利用
EnvironmentsはGithub Actionsの機能で、それぞれの環境に対して別々の設定をできます。
例えば、production環境向けではGCS_BUCKET_PRODUCTION
という環境変数の値のバケットに、staging環境向けではGCS_BUCKET_STAGING
という環境変数の値のバケットにファイルをアップロードするということを行っていた場合、
それら接尾辞をつけた2つの環境変数を登録して条件分岐して利用するなどが必要でした。
しかし、Environmentsを利用することで環境毎にGCS_BUCKET
へ別の値を入れて利用することで、不要な情報を減らして管理や開発ができます。
また、デプロイ前に承認を必須とすることが可能で、上記の不意のデプロイを防いだりデプロイ権限を持つ人の承認を得るというプロセスを構築したりできます。 デプロイ元のブランチも制限できますが正直なところyaml内でブランチを列挙するのと比較してどういった嬉しさがあるかは理解できていません。
一点、フリープランではプライベートリポジトリでのEnvironmentsが利用不能なのでご注意ください。
Environmentsの作成
Environmentsの設定にはリポジトリ毎のSettingsの左のメニューからアクセスできます。
Required reviewers
を設定することで、実行する前にレビュアーの誰かに承認されることを必須とできます。誰が承認したかは各ワークフローの詳細ページから確認可能です。
環境毎のSecretは左のメニューのSecretsからではなくこちらのページの下のところで設定します。 環境間のSecretは名前が重複しても大丈夫ですし、むしろ同名の方が使う時の利便性が高いです。
GCPのサービスアカウントの生成&IAMの設定
GitHub ActionsからCloud Functionsに関数をデプロイするには、デプロイ用と関数実行用の2種類のサービスアカウントを用意する必要があります。 デプロイ及び実行に必要なすべての権限を持たせたサービスアカウントを共用することも仕組み上は可能ですが、デプロイ時・関数実行時共に余分な権限を持った状態で動作させることになるのでおすすめしません。
Cloud Functionsへデプロイする用のサービスアカウントではGoogleの提供するActionsのドキュメントにあるような以下の権限が必要になります。
- Cloud Functions 管理者
- サービス アカウント ユーザー
Cloud Functionsを実行する用のサービスアカウントについては実行される関数の機能に基づいて設定する必要があります。 例えば以下で指定する様にSecret Manager内の値を使うのであればそれらへのアクセス権限を付与する必要があります。 同様にBigQueryやCloud Storageなどのサービスにアクセスするならそれらへの権限が必要です。
Secret Managerへの値の追加
特に機密性の高い情報を扱う場合は、GCPのSecret Managerを利用することがあるかと思います。 これを用いるとGitHub上に情報を置かず、Cloud Functionsの環境変数からも見えない値を利用できる様になります。 Secret Managerでのシークレットの作成については説明を省略しますが、作成後に概要タブにあるリソースID1は後で利用するのでメモしておくと良いです。
ワークフローを書く
ここまででデプロイに必要な設定の準備をおこなったので、GitHub Actionsで実行するワークフローを設定していきます。
タイムアウトの設定
この用途に限りませんがタイムアウトの設定は重要です。
デフォルトでは6時間に設定されているため、知らないうちにタスクが実行されたままだと時間の枠を消費してしまいますし、Self-hosted runnerの場合は他のジョブがブロックされてしまいます。
今回は通常時だと6分ほどでデプロイが終わるので余裕を持たせて10分で設定しました。
これはtimeout-minutes: 10
のように設定するだけです。
Environmentの設定
導入部分で環境による条件分岐が不要になると書きましたが、1回だけ条件文を書く必要があります。それがどの環境を使うのかの宣言部分です。しかし、environmentではifを使うことができません。 そのため、三項演算子で条件分岐する方法を参考2,3にしました。具体的には以下の様な形です。 このようにすると、mainブランチではproduction環境として、それ以外ではstaging環境として動きます。
environment: name: ${{ (github.ref == 'refs/heads/main' && 'production') || 'staging' }}
requirements.txtの生成
Cloud Functionsへのデプロイ時にはCloud Buildが利用されており、その中でbuildpacksを利用しているようです。
必要な依存情報の収集にはrequirements.txt
を利用しているようで、このファイルが無い状態でデプロイしようとすると、デプロイには成功しますが実行時にモジュールが無いためエラーが発生します。
Pipenvでrequirements.txt
を生成するにはpipenv requirements > requirements.txt
を実行すれば生成できます。
requirements.txt
は他のファイルと同様にリポジトリ内に置いてバージョン管理しても良いですが、Pipfile
やPipfile.lock
との二重管理になったり更新漏れの可能性があったりするためCIの中で一時的に作成するのが良いと思います。
requirements.txt
とは関連しませんが、2022/12/12時点においてCloud FunctionsではPython 3.11には対応していないため、Pipfile
でpython_version = "3.11"
の様な設定をしているとデプロイ後の環境と差分が生まれるので注意が必要です。
GitHub Actionsの認証
google-github-actions/auth
を用いてgcloudコマンドをGitHub Actionsから実行できるようにします。
色々と認証情報を渡す方法はありますが、JSON形式のサービスアカウントの鍵を用いる場合、credentials_json
にJSONを渡す必要があります。
この値はyaml内に直接埋め込むわけにはいかないので環境毎のSecretに登録しておきます。
デプロイ
GitHub ActionsからCloud Functionsにデプロイするには大きく2つの方法があります。
1つはgoogle-github-actions/setup-gcloud
を用いてgcloudコマンドを利用できる様にした後、gcloudを次のステップで叩くという方法です。
もう1つはgoogle-github-actions/deploy-cloud-functions
を用いてgcloudコマンドを使わずにデプロイする方法です。
前者はコマンドを構築する必要があり、後者は対応していない引数4があるなどどちらも一長一短です。
以下は設定可能な項目で重要だと思った部分です。
キーはgoogle-github-actions/deploy-cloud-functions
のものを利用しています。
service_account_email
上で設定したCloud Functionsを実行する用のサービスアカウントの情報を渡すためのオプションです。
gcloudコマンドでは--service-account
となっており紛らわしいですが、ここで渡すのはメールアドレスの文字列でありJSONの鍵ではないことに注意が必要です。
env_vars
Cloud Functionsに環境変数として渡される値をKEY1=VALUE1,KEY2=VALUE2
のように渡します。
google-github-actions/deploy-cloud-functions
では--set-env-vars
相当の動作しかできず、このオプションの有無に関わらず既存の環境変数は消えます。
gcloudコマンドでは--update-env-vars
を使うことで既存の環境変数を残したまま一部に対して更新や追加が可能です。
secret_environment_variables
'API_KEY=projects/xxxx/secrets/yyyy/5'
のようにSecret Managerのリソースへの参照を渡します。
gcloudコマンドでいう--set-secrets
相当です。gcloudコマンドとは異なりバージョンを指定しなかった場合にエラーにはならずにlatestとして扱ってくれます。
gcloudコマンドでは--update-secrets
が環境変数と同様に存在します。
おわりに
本記事ではGitHub ActionsからCloud FunctionsにPipenvを利用したPythonアプリケーションをデプロイする方法について紹介しました。 Python以外のアプリケーションだったりCloud Functions以外のデプロイ先だったりに対しても活かせる部分があったのではないかと思います。 この記事の内容がより良い自動化につながると嬉しいです。
今回デプロイしたリポジトリは社内のGitHub Enterprise以下のOrganizationを利用しました。 社内のGitHub Enterpriseの詳細についてはこちらをご覧ください。
-
projects/xxxx/secrets/yyyy
の様な形式の文字列です。↩ - https://qiita.com/technote-space/items/cbeed6ddd0488499afaa↩
- https://dev.classmethod.jp/articles/github-actions-get-only-necessary-secrets-according-to-branch-name-in-ternary-operator-like-description/↩
- たとえば第二世代の関数をデプロイできません。↩