はじめに
こんにちは。SRE部MLOpsチームの築山(@2kyym)です。
Infrastructure as Code(IaC)が一般的になり、またパブリッククラウドをフル活用したインフラ構築が当たり前となりつつあります。そんな中で、インフラの構成管理にTerraformを用いているチームも多いのではないでしょうか。本記事ではTerraformを用いたインフラ構成管理において避けては通れないTerraformやProviderのバージョンアップを自動化し、IaCの運用負荷を削減する方法をご紹介します。MLOpsチームでの運用を参考に、具体的な設定やハマりどころを交えつつ解説できればと思います。
目次
- はじめに
- 目次
- Terraformとは
- MLOpsチームにおけるTerraform運用の背景
- tfupdateの紹介と使い方
- 自動化の方針
- GHAワークフローにtfupdateを組み込む
- 意外と多いハマりどころとその解決策
- Tips:Workflow Dispatchを使おう
- おわりに
Terraformとは
TerraformとはHashiCorp Inc.が開発しているIaCを実現するためのオープンソースツールです。GCPやAWSなどパブリッククラウドのインフラ構成を始め、各種SaaS上の設定も含めてリソースという単位でコードとして定義できます。例えば以下はGoogle Cloud Storageバケットのリソースを定義する例です。
resource "google_storage_bucket" "sample-bucket" { name = "sample-bucket" location = "asia" }
このような定義を記述したファイル(以下tfファイル)を用意し、インフラ構成を適用するコマンドであるterraform apply
を実行することで、リソースの作成・変更・削除が行われます。
MLOpsチームにおけるTerraform運用の背景
まずTerraformのバージョンアップの自動化について述べる前に、MLOpsチームにおけるTerraform運用の背景をご紹介します。チームの状況を踏まえ、運用にどういった課題が生じていたのか説明します。
Terraform管理の対象リソース
MLOpsチームの関わるプロジェクトでは基本的にGCP(Google Cloud Platform)を用いてインフラを構築しています。そしてGCPのリソースは、基本的にすべてTerraformを用いてGitHubでコード・バージョン管理をしています。またDatadogやSentry、PagerDutyなどSaaS上の設定に関しても同様にTerraformを用いてGitHub上で管理しています。
これらのtfファイルとKubernetesのマニフェストなどを合わせてインフラ用リポジトリとし、プロジェクトや機能ごとに存在しています。このリソース定義やGKEマニフェストは、それを変更するPull Request(以下PR)のMergeをトリガーにして、自動的にデプロイされるようになっています。この自動化にはGitHub Actionsのワークフロー(以下GHAワークフロー)を用いています。
Terraform運用において生じた課題
ここでMLOpsチームを取り巻く状況について少し説明します。
以前MLOps基盤に関して取り上げた記事でも説明したとおり、ここ1年でMLOpsチームの関わるプロジェクトや機能の数はどんどん増えています。それに伴ってインフラ用リポジトリも増えているという状況があります。
そしてTerraform運用において避けては通れないのがTerraform自体のバージョンアップと、各種リソース定義を提供するProviderのバージョンアップです。
MLOpsチームでは月次でGKEやTerraformのバージョンアップを行う運用がされており、この作業はチームメンバーが手動で実行していました。この作業自体は各tfファイルやGHAワークフロー定義ファイル1に記述されているTerraformやProviderのバージョンを書き換えるという単純なものです。しかし、インフラ用リポジトリの数が増えるに従ってこのバージョンアップの運用負荷も大きくなるのが課題でした。バージョンアップの頻度を落とすという選択肢もありますが、古いバージョンを使用する期間が長くなると以下のようなリスクが発生します。
- 一度のバージョンアップで変更されるコード量が多くなるので、レビュー作業が大変
- 非推奨設定を推奨設定へ変更するために設けられたバージョンを飛ばしてしまい、非推奨設定が警告なしに使用不可となる
そこで、自動化することによってこのようなリスクを減らしつつ運用負荷も削減したいと考えました。
tfupdateの紹介と使い方
今回の課題を解決するために、GitHubで公開されているオープンソースのCUIツールであるtfupdateを使用しました。このツールの機能はシンプルで、tfファイルに記述されているTerraform/Provider/Moduleのバージョンを一括更新してくれるというものです。詳細な使い方についてはtfupdate開発者であるminamijoyoさんの記事に譲りますが、この章では簡単なサンプルを紹介します。
tfupdateを用いたTerraform本体とProviderのバージョン更新
例として、以下のバージョン定義を含むtfファイルを想定します。これらのバージョンは2021/08/01時点での最新バージョンです。
terraform { required_version = "1.0.3" required_providers { google = "3.77.0" google-beta = "3.77.0" } }
まずTerraform本体のバージョンを最新版に更新するには、tfファイルを含むディレクトリの配下で以下のコマンドを実行します。
$ tfupdate terraform .
もしくは以下のように、バージョンと更新対象を指定できます。
$ tfupdate terraform -v 1.0.5 main.tf
コマンド実行後はTerraformのバージョンが2021/08/30時点での最新である1.0.5
に更新されています。
terraform { required_version = "1.0.5" required_providers { google = "3.77.0" google-beta = "3.77.0" } }
次に、Providerのバージョンアップです。Providerを更新するには、同じディレクトリ配下で以下のコマンドを実行します。
$ tfupdate release latest -s tfregistryProvider hashicorp/google > 3.82.0 $ tfupdate release latest -s tfregistryProvider hashicorp/google-beta > 3.82.0 $ tfupdate provider google -v 3.82.0 . $ tfupdate provider google-beta -v 3.82.0 .
Terraform本体に対するバージョンアップとは異なり、Providerのバージョンアップでは、バージョン番号の指定が必須です。各Providerの最新バージョンは、上のようにlatest
の指定で取得できますので、それを用いてProviderをバージョンアップします。最終的に以下のように更新が完了しました。
terraform { required_version = "1.0.5" required_providers { google = "3.82.0" google-beta = "3.82.0" } }
自動化の方針
ここからが本題です。運用負荷が高くなりつつあるTerraformのバージョンアップを自動化するため、以下の方針を取りました。
- 先述したtfupdateとGHAを組み合わせてバージョンアップを自動化する
- GHAワークフローの実行頻度は、現状の更新頻度と合わせて月次とする
- PRを作成し、チームメンバーにレビューを依頼するところまでを責務とする
PR毎に実行されるCI/CD用のGHAワークフローとは別に、独立したGHAワークフローを作成する方針としました。現状の更新運用に合わせ、Terraformのバージョン更新はPRのMerge毎ではなく月次で行うためです。
なお、バージョンアップによってSyntaxなどに変更が生じる場合でも、tfupdateは関知しません。tfupdateはあくまでもバージョンのみを意識します。Syntaxの不整合によってGHAワークフローでエラーが発生した際はチームメンバーが手動修正する運用です。
以下の章ではこの方針に沿ったGHAワークフローの実装を順を追って説明していきます。また、実装を始めてみるとハマりどころが多かったので、後半にそれらの解決策もまとめています。Terraformのバージョンアップに限らず、GHAを用いた自動化全般で必要なTipsも含みます。
GHAワークフローにtfupdateを組み込む
この見出しでは実際のGHAワークフローを例に挙げながら、tfupdateとシェルスクリプトを用いたバージョンアップの自動化について説明します。後述するハマりどころを避けるため、意図的に記述を簡素化している部分があります。ご了承ください。
Branchのセットアップ
実際のGHAワークフロー定義ファイルを示しつつ、GHAワークフローの処理単位であるStep毎に説明していきます。まずはtfupdateのセットアップと、Branchの準備をしている部分です。
steps: - id: check-branch # NOTE: Shows lots of warnings because of https://github.com/octokit/request-action#warnings uses: octokit/[email protected] with: route: GET /repos/:repository/git/ref/:ref repository: ${{ github.repository }} ref: heads/${{ env.TFUPDATE_BRANCH }} continue-on-error: true - uses: actions/checkout@v2 if: steps.check-branch.outputs.status == 200 with: ref: ${{ env.TFUPDATE_BRANCH }} - uses: actions/checkout@v2 if: steps.check-branch.outputs.status != 200 with: ref: ${{ env.TFUPDATE_BASE_BRANCH }} - name: Create tfupdate branch if not exist if: steps.check-branch.outputs.status != 200 run: | git branch ${{ env.TFUPDATE_BRANCH }} git branch --set-upstream-to=origin/${{ env.TFUPDATE_BASE_BRANCH }} ${{ env.TFUPDATE_BRANCH }}
まず最初にtfupdateによる変更をCommitするBranch(env.TFUPDATE_BRANCH
)を用意します。この時、前回の実行時に作られたtfupdateのPRがまだMergeされていないというケースをカバーするため、条件分岐を作ります。
TFUPDATE_BRANCH
が既にある場合はCheckout- 無い場合はBranchを切る(すなわち
git checkout -b
)
actions/checkout
の最新版であるv2では、存在しないBranchを指定した場合にエラー(check-branch
Stepのステータスが非200)を返します。この仕様を利用して、分岐します。
tfupdateのセットアップ
次にtfupdateコマンドを利用可能にするための処理を示します。
- name: Setup tfupdate from binary run: | set -o pipefail wget -P /tmp ${TFUPDATE_BINARY} basename ${TFUPDATE_BINARY} | xargs -I {} tar xvf /tmp/{} -C /tmp sudo mv /tmp/tfupdate /usr/local/bin sudo chmod +x /usr/local/bin/tfupdate
tfupdateのセットアップ自体はシンプルで、リポジトリからバイナリを取得して/bin
ディレクトリ配下に配置しているだけです。
当初はDocker Hubに公開されているtfupdateのImageを用い、tfupdateのセットアップを省略する予定でした。しかしこのImageではTerraformコマンドの実行に必要なhashicorp/setup-terraform
プラグインを実行することができません。tfupdateのImageはalpineベースですが、setup-terraformはalpineベースのImageでは実行がサポートされていないためです。そのため、上記のバイナリを取得して展開する方針に転換しました。
tfupdateの実行とCommit & Push
- name: Get the commit sha before tfupdate id: before-sha run: | echo ::set-output name=sha::$(git log --pretty=%H | head -n1) - name: (${{ env.TF_KIND }}) tfupdate terraform . run: | cd ${TF_DIR} tfupdate terraform . - uses: EndBug/add-and-commit@v7 with: branch: ${{ env.TFUPDATE_BRANCH }} message: "Update terraform/${{ env.TF_KIND }}: tfupdate terraform ." - name: Get the commit sha after tfupdate id: after-sha run: | echo ::set-output name=sha::$(git log --pretty=%H | head -n1)
ここでは、tfupdateコマンドを実行して、envに定義したTFUPDATE_BRANCH
へ変更差分をCommitします。set-outputはワークフローコマンドの1つです。他のStepで利用する値を設定できます。この例では、tfupdateコマンドの実行前後でCommitハッシュ値を取得しています。変更差分がある時のみ後続のStepでPRを作成するためで、set-output
したCommitハッシュ値を後続のStepで参照し、分岐しています。
Pull Requestの作成
- uses: repo-sync/pull-request@v2 if: ${{ steps.before-sha.outputs.sha != steps.after-sha.outputs.sha }} with: source_branch: ${{ env.TFUPDATE_BRANCH }} destination_branch: ${{ env.TFUPDATE_BASE_BRANCH }} pr_title: Update Terraform Version & Terraform Providers Version pr_body: | **This pull request is created automatically by the CI: [tfupdate](https://github.com/${{ github.repository }}/actions?query=workflow%3Atfupdate)** --- Run the below code to try this changes locally. \`\`\` cd path/to/${{ github.repository }} gh pr checkout ${{ env.TFUPDATE_BRANCH }} \`\`\` pr_label: tfupdate pr_allow_empty: false
先程CommitしたTFUPDATE_BRANCH
からTFUPDATE_BASE_BRANCH
(通常はmain branch)に対してPRを作成するStepです。tfupdateの前後で取得したCommitハッシュ値を比較し、差分がある時のみ実行します。
このStepによって作成されたPRを、運用メンバーが確認してMergeするだけでバージョンアップ完了です。
さて、ここまでの記述で実現できるのはTerraform本体のバージョンアップです。実際には、加えて以下の考慮も必要です。
- Terraformディレクトリは基本的に複数存在する
- 先述したようにGCPリソース以外もTerraformで管理しているため
- Terraform自体のバージョンだけでなく、使っているProviderのバージョンも更新が必要
これらについて次の見出しで述べていきます。
複数ディレクトリに対して実行する
以下にtfファイルを配置するディレクトリの構成を示します。GCPリソースに加え、SaaSごとのディレクトリが存在します。
└── terraform/ ├── gcp/ ├── monitoring/ └── sentry/
そして以下のYAMLが、複数ディレクトリに対して実行するための実際の定義です。
jobs: tfupdate-terraform: runs-on: ubuntu-latest strategy: fail-fast: false max-parallel: 1 matrix: tfupdate: - KIND: gcp - KIND: monitoring - KIND: sentry env: TF_KIND: ${{ matrix.tfupdate.KIND }} TF_DIR: ./terraform/${{ matrix.tfupdate.KIND }} steps: - id: check-branch # snip
複数ディレクトリに対するtfupdateの実行については、Matrix Buildという機能を利用します。strategy.matrix
に定義した変数によって単一Job定義内の変数を書き換え、複数のJobを作成します。ここで言うJobとはGHAワークフローにおける一連のStepの組み合わせを指し、GHAワークフローは複数のJobを直列もしくは並列に実行できます。
例えば、上のようにtfupdate.KIND
にディレクトリ名を複数定義すると、Jobtfupdate-terraform
は合計3回実行され、Jobや各Stepでそれらの値を参照できます。tfupdate-terraform
はBranchの存在確認からPRを作成するまでの一連をまとめたJobです。対象のディレクトリ名を除けば処理は同一なため、冗長なGHAワークフローの記述を避けることができます。
最終的に、各ディレクトリに対する変更全てを1つのPRにまとめます。Jobの中にPRを作成するStepを含むため、PR作成のタイミングが衝突しないようにmax-parallel
は1に設定しています。このMatrix Buildで実行される3つのJobは上から順に直列実行されます。
Providerのバージョンを更新する
Providerの更新をするための方針は以下の通りです。
- 複数ディレクトリ配下に存在する複数Providerのバージョンを更新するために、Matrix Buildにディレクトリ名とProvider名を定義する
- Provider更新の処理は、Terraform自体の更新とは別のJobに分離する
- バージョンアップ処理の重複を防ぐため
以下のYAMLが、実際の定義です。
tfupdate-provider: runs-on: ubuntu-latest needs: [tfupdate-terraform] strategy: fail-fast: false max-parallel: 1 matrix: tfupdate: - KIND: gcp PROVIDER: hashicorp/google - KIND: gcp PROVIDER: hashicorp/google-beta - KIND: monitoring PROVIDER: hashicorp/google - KIND: monitoring PROVIDER: hashicorp/google-beta - KIND: sentry PROVIDER: jianyuan/sentry env: TFUPDATE_PROVIDER: ${{ matrix.tfupdate.PROVIDER }} TF_KIND: ${{ matrix.tfupdate.KIND }} TF_DIR: ./terraform/${{ matrix.tfupdate.KIND }}
ProviderをバージョンアップするJobは、先述のTerraformをバージョンアップするJobとは別のJobとして定義し、本体→Providerと直列で実行するようにしました。Jobをまとめてしまうと、Matrix BuildによってTerraform本体のアップデートが同じディレクトリ内で何度も実行されてしまうためです。上の例ではgcp
とmonitoring
が2回ずつ呼び出されるため、Jobが1つの場合はtfupdate terraform
も複数回実行されていまいます。
Jobに含まれるStepは、以下のとおりです。
- name: Get the commit sha before tfupdate id: before-sha run: | echo ::set-output name=sha::$(git log --pretty=%H | head -n1) - name: (${{ env.TF_KIND }}) tfupdate release latest -s tfregistryProvider ${{ env.TFUPDATE_PROVIDER }} . id: get-latest-version run: | cd ${TF_DIR} echo ::set-output name=version::$(tfupdate release latest -s tfregistryProvider ${TFUPDATE_PROVIDER}) - name: (${{ env.TF_KIND }}) tfupdate provider ${{ env.TFUPDATE_PROVIDER }} . run: | cd ${TF_DIR} tfupdate provider ${TFUPDATE_PROVIDER} -v ${{ steps.get-latest-version.outputs.version }} . - uses: EndBug/add-and-commit@v7 with: branch: ${{ env.TFUPDATE_BRANCH }} message: "Update terraform/${{ env.TF_KIND }}: tfupdate provider ${{ env.TFUPDATE_PROVIDER }} ." - name: Get the commit sha after tfupdate id: after-sha run: | echo ::set-output name=sha::$(git log --pretty=%H | head -n1)
更新の実行前後にCommitハッシュ値の差分を取得しているのは、本体のアップデートと同様です。
冒頭で述べたように、Providerの更新はバージョンを必ず指定する必要があります。そのためtfupdate release latest
を実行して取得したバージョンの値を::set-output
し、tfupdate provider
実行時に参照する方法としました。なおTF_KIND
、TFUPDATE_PROVIDER
はMatrix Buildで定義したものをenvに渡した値です。
ここまでの記述で、tfファイルに記載されているTerraformとProviderのアップデート自動化ができました。
意外と多いハマりどころとその解決策
この見出しでは、GHAワークフローを実際の運用に乗せるにあたって出てきたハマりどころとその対処法について説明します。一見すると先のGHAワークフローを定期実行するだけで要件は満たせそうなのですが、運用する内に以下のような課題が分かってきました。
- Terraformの依存ロックファイルもGitHubで管理しているため、バージョンアップに合わせてこちらの更新も必要だった
- GHAワークフロー定義ファイル自体にもTerraformのバージョン定義があり、tfupdateではこれを書き換えられない
- GHAワークフロー内で実行されるCommit Authorが「直近PRをMergeした人」となり、混乱を招く
これらを順を追って説明してきます。
バージョンアップに合わせてTerraformの依存ロックファイルの更新も必要
Terraform v0.14以降、.terraform.lock.hclという依存ロックファイルが導入されており、私達のチームではtfファイルだけでなくこちらもGitHubで管理しています。この依存ロックファイルはterraform init
実行時に自動生成されるもので、公式ドキュメントでもGitHubなどでバージョン管理が推奨されています。
provider "registry.terraform.io/hashicorp/google" { version = "3.77.0" constraints = "3.77.0" hashes = [ ..., ] }
上記のようにProviderのバージョンも含むため、こちらもバージョンアップに合わせて更新が必要です。
- name: (${{ env.TF_KIND }}) Update .terraform.lock.hcl provider ${{ env.TFUPDATE_PROVIDER }}) if: ${{ steps.before-sha.outputs.sha != steps.after-sha.outputs.sha }} run: | cd ${TF_DIR} terraform init -upgrade terraform providers lock -platform linux_amd64 -platform darwin_amd64 - uses: EndBug/add-and-commit@v7 if: ${{ steps.before-sha.outputs.sha != steps.after-sha.outputs.sha }} with: branch: ${{ env.TFUPDATE_BRANCH }} message: "Update terraform/${{ env.TF_KIND }}/.terraform.lock.hcl: provider ${{ env.TFUPDATE_PROVIDER }})"
方法はシンプルで、Providerを更新するJobに上のStepを足すことで実現できます。terraform init -upgrade
を実行すると更新後のtfファイルに基づいて依存ロックファイルが再生成されます。
またterraform providers lock
によって開発時にローカルでterraform init
を行った際に依存ロックファイルが変更されてしまうことを防いでいます。terraform init
と依存ロックファイルの詳しい挙動についてはminamijoyoさんのスライドを参照ください。
GHAワークフロー定義ファイル自体にもTerraformのバージョン定義がある
tfupdateコマンドを実行することで、tfファイルに記載されているTerraformとProviderのバージョン更新の自動化ができました。しかしインフラ用リポジトリでは、tfファイルだけでなくGHAワークフロー定義ファイルにも以下のようにTerraformのバージョン記述があります。
- uses: hashicorp/setup-terraform@v1 with: terraform_version: "1.0.3"
PR作成時やMerge時に実行されるGHAワークフローで、インフラ構成を環境に適用するためterraform plan
やterraform apply
を実行しています。tfupdateではtfファイル内のバージョンアップのみをサポートしているため、GHAワークフロー定義ファイルにあるバージョン記述は書き換えることができません。そこで、以下のようなワンライナーを用意しました。
- name: Update terraform_version in GHA yamls if: ${{ steps.before-sha.outputs.sha != steps.after-sha.outputs.sha }} run: | sed -i -e "s|terraform_version: [0-9.\"']\+|terraform_version: \"$(tfupdate release latest hashicorp/terraform)\"|g" .github/workflows/*.yaml
このワンライナーでは.github/workflows/
以下にあるGHAワークフロー定義ファイルsetup-terraform
でのバージョン指定を書き換えています。このStepをtfupdate terraform
の後ろに置くことで、tfファイルの更新と同じタイミングでGHAワークフロー定義ファイルも更新します。
GHAワークフロー定義ファイルの書き換えにはPersonal Access Tokenの権限が必要
次の問題は、権限不足です。
当初はStep実行時のトークンとしてsecrets.GITHUB_TOKEN
2 を利用していました。このトークンはリポジトリ毎に自動生成されるもので、リポジトリに対して一定の権限が付与されており、GHAワークフロー内での認証に使用できます。
ところが、実際にワンライナーでGHAワークフロー定義ファイルsetup-terraform
の書き換えを行い、GITHUB_TOKEN
でCommit & Pushを行おうとすると以下のようなエラーが発生してしまいました。
! [remote rejected] HEAD -> automated-tfupdate (refusing to allow a GitHub App to create or update workflow`.github/workflows/main.yaml`without`workflows`permission)
ドキュメントに記載された権限内容を確認すると、GITHUB_TOKEN
にはworkflows
スコープの権限が付与されていません。表に記載されていないスコープの権限が必要であったり、他のリポジトリに対する権限を必要な場合はGITHUB_TOKEN
でなくPersonal Access Token (以下PAT)が必要です。
チームメンバーのGitHub Accountに紐付いたPATも利用できますが、権限の変更が本人にしかできなかったり、退職時にExpireしたりと、管理上の問題があります。そのため、マシンアカウントを用いてチーム向けのBot GitHub Accountを作成することにしました。このBotアカウントにチームメンバーと同様の権限(workflows
スコープを含む)を付与することで、GHAワークフロー定義ファイルの書き換えが可能となりました。
GHAワークフロー内で実行されるCommitのAuthorが「直近main branchにMergeした人」となってしまう
最後はTerraformのバージョン更新とは少し外れて、GHAを用いて自動でCommitやPRを作成する際にハマるポイントです。
tfupdateを実行するGHAワークフローで自動生成されたPRには、tfファイルや依存ロックファイルの更新など複数のCommitが含まれています。BotアカウントがCommit Authorになることを想定していましたが、実際は以下のように直近main branchにMergeした人がAuthorになってしまいました。
上の画像ではPR作成者はBotアカウントですが、Commit Authorは直近mainに対してMergeを行った自分のアカウントとなってしまっています。
この問題は、add-and-commit
Stepで以下のようにBotアカウントの登録情報を渡すことで解決します。
- uses: EndBug/add-and-commit@v7 with: branch: ${{ env.TFUPDATE_BRANCH }} message: "Update terraform/${{ env.TF_KIND }}: tfupdate terraform ." author_name: ${{ env.BOT_USERNAME }} author_email: ${{ env.BOT_EMAIL }}
Tips:Workflow Dispatchを使おう
皆さんWorkflow Dispatchはご存知でしょうか。
ドキュメント上では地味な記載があるだけの機能ですが、これを設定することでGitHubのUIから以下の画像のように特定のワークフローを手動実行できます。
今回のtfupdateのような、単発で実行したいケースがある自動化系のGHAワークフローにおいては特に有用です。通常のGHAワークフローでも、大きな変更を入れた後のデバッグ時に便利だったりと、使い勝手の良い機能です。設定に必要な記述も以下の通りとてもシンプルですので、気軽に導入してみてはいかがでしょうか。
on: workflow_dispatch: schedule: - cron: '0 0 1 * *' # monthly
おわりに
本記事ではGHAとtfupdateを利用し、Terraformを用いたインフラ構成管理におけるTerraform周りのバージョンアップを自動化する方法をご紹介しました。
要件はシンプルなものでしたが、いざGHAワークフローを動かしてみると、Terraformに限らずGHAを用いた諸々の自動化で考慮が必要な点が多く、それらを含めたまとめ記事になりました。複数プロジェクトにおいてTerraformを積極的に使っており、バージョンアップを面倒に感じつつある、もしくは放置してしまっている方は是非参考にしていただければと思います!
最後まで読んでいただきありがとうございました。
ZOZOテクノロジーズでは一緒にサービスを作り上げてくれる仲間を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください!
https://tech.zozo.com/recruit/tech.zozo.com hrmos.co
-
この文章ではGHAワークフロー定義ファイルとは
.github/workflows
以下に配置するyamlファイルで、GHAワークフローの処理対象や処理内容を記載するものを指します。↩ - ワークフローで認証する - GitHub Docs↩