GitHub ActionsとCloud RunでZennの記事を限定公開する
はじめに
こんにちは、M-Yamashitaです。
今回の記事は、GitHub Actionsを使用してZennの記事を限定公開としてデプロイする話です。
以前「Fukuoka.rb 0x100 回 LT 大会 (#256)」で登壇した内容を、記事として起こしました。
この話では、スライドに加えて、登壇で話せなかったGCPの権限周りの話や作成したワークフローファイルについても記載しています。
この記事で伝えたいこと
- 限定公開のワークフローを作るまでの背景
- ワークフローを使用するための準備
- 作成したワークフローの紹介
きっかけ
以前から技術記事を投稿しており、その技術記事の執筆ではQiitaやZennを使用していました。今は主にZennを使用しています。
Zennを使って執筆した記事のほとんどは、公式サイトや公開されている情報を個人で調査してまとめていた記事でした。そのため、公開前に特定の人だけに見せる事はありませんでした。
そんな中、以下記事を執筆している最中、公式のリファレンスに書いてある仕様について、リファレンスの文章を読んでも実際に動かしてもよく分からない問題に出会いました。
この仕様についてコミュニティで質問したところ、具体例や仕様の説明について回答を頂くことができ、記事を作成することができました。
この記事は回答していただいた方がいらっしゃったことで完成できたので、その方に記事への名前の掲載可否を尋ねようと思いました。また、記事を見せてほしいと伝えられることもあると思い、間違い等ないか確認していただくために、限定公開としてプレビューをすぐ出せるようにしたいと考えました。
ただ、Zennには限定公開の機能がないようでした(本記事執筆時点でもその機能はないようです)。そのため、似た事を考えた人がいるはずと思い調べたところ、「Zennの記事を限定公開する方法」という記事がヒットしました。
この記事によると、ZennにはCLIがありnpx zenn preview
でプレビューが表示できるため、それを活用することで限定公開ができるとありました。
紹介されていた限定公開の全体図:
記事で紹介されていた限定公開の手順は以下のとおりです。
まずはZenn CLIを使って記事を作成します
Zenn CLIをinstallしてnpx zenn new:article を実行すると、articles配下に記事の雛形ファイルができるので、このファイルに記事を書きます。
. └─ articles └── example-article1.md
次にDockerfileを作成します。
FROM node:lts-alpine3.12 WORKDIR /app RUN apk add --no-cache --virtual .build-deps git \ && npm init --yes \ && npm install zenn-cli \ && npx zenn init \ && apk del .build-deps COPY articles articles COPY books books ENTRYPOINT ["npx", "zenn", "preview"]
Dockerfileを作成したら、指定のディレクトリにそのDockerfileを配置します。
記事のリポジトリのrootにDockerfileを起きます
. ├─ articles │ └── example-article1.md └─ Dockerfile
このディレクトリ上でgcloud
コマンドを使用し、Container Registryにイメージをプッシュし、Cloud Runにデプロイすることで、Cloud Run上でZennのプレビューを見ることができる専用のURLが生成されます。
この方法により、当初抱えていた
記事を見せてほしいと伝えられることもあると思い、間違い等ないか確認していただくために、限定公開としてプレビューをすぐ出せるようにしたい
が解決できました。
課題
この限定公開を何度か行っているうち、以下3つの課題が出てきました。
- 複数のコマンドを手動実行するのは手間がかかる
- ZennとGitHubを連携済みなので記事作成のPull Requestをトリガーにプレビューを見たい
- プレビューに使ったCloud Runのサービスやコンテナイメージの消し忘れを避けたい
複数のコマンドを手動実行するのは手間がかかる
限定公開のためには以下のコマンドを順番に実行する必要があります。
- 指定したプロジェクト名に対して実行するように
gcloud
コマンドのコンフィグを設定 -
gcloud
コマンドでdockerを使用できるようにする - プロジェクト名を含む特定のタグでコンテナーのビルド、プッシュ
- 上記タグとデプロイ用のサービス名を指定し
gcloud
コマンドでデプロイ
この実行において、プロジェクト名やタグ名はタイポする可能性があります。また時間が経ってしまうとそれらの名前やコマンド自体を忘れてしまうことが考えられます。そのたびに、毎回調べたり思い出しながら実行したりするのは大変です。
ZennとGitHubを連携済みなので記事作成のPull Requestをトリガーにプレビューを見たい
私の環境では、Zenn CLIをインストールし、かつZennとGitHubを連携した環境を構築していました。
Zennには、記事内のpublished
をtrue
にして、GitHubの特定のブランチにpushすると、自動でZennに投稿する仕組みがあります。
- 同期するブランチ名を確認
デプロイページで同期したいブランチ名を確認・変更します。
ここで登録されている名前のブランチに変更があったときに自動でデプロイが行われます。
記事を zenn.dev 上で公開するにはpublishedオプションがtrueになっていることを確認したうえで、ファイルをコミットし、Zenn と連携されている GitHub リポジトリにプッシュします。
Zenn と連携したリポジトリの登録ブランチにプッシュされると、同期(デプロイ)が開始されPます。
私の環境ではその特定のブランチをmainブランチとしているため、published
をtrue
としてmainブランチに誤ってpushしてしまうと、確認なしでZennに投稿されてしまいます。
そのため、mainブランチでの作業を行わず、別ブランチで作業しマージする事を考えました。
この方法は、OSSでのPull RequestによるContributionsと似ていると思い、その流れに沿って、Zennの記事を新しく作成したらPull Requestを出すようにしました。
Pull Requestを作成した際、Cloud Runへのデプロイが自動で実行されたら嬉しいなと思っていました。その際問題になったのは、デプロイで生成される限定公開のURLをどこに表示するかです。
デプロイをするとそのコンソール上で限定公開のURLが表示されるので、コンソールを見に行く方法を検討してみました。ですが、毎回コンソール上のURLを見に行ったり、URLをコピペしてブラウザに貼り付けるのは手間がかかります。
そのためデプロイが完了したら、Pull Requestの画面に限定公開のURLを表示したいと思いました。これにより、ワンクリックで限定公開を見ることができ、とても楽になると思いました。
プレビューに使ったCloud Runのサービスやコンテナイメージの消し忘れを避けたい
ローカルPC上でgcloud
コマンドを使用してデプロイした際、何度かCloud RunのサービスやContainer Registryのイメージを消し忘れることがありました。
Cloud Runには無料枠があったり、Container Registryは重いイメージでなければ料金は微々たる範囲に収まったりしますが、不要なものをそのままにしていたために料金を請求されることは避けたいと思いました。
またセキュリティの面でも、不要なものをそのままにしておくのはあまりよろしくありません。
そのため、限定公開での確認が終わったら自動的に削除する仕組みがほしいと思いました。
課題の解決
さきほどの課題3つを解決するために、GitHub Actionsを使用することにしました。
- 課題に対する解決方法
- 複数のコマンドを手動実行するのは手間がかかる
→ GitHub Actionsのワークフローに必要なコマンドを書いておく - ZennとGitHubを連携済みなので記事作成のPull Requestをトリガーにプレビューを見たい
→ Pull Requestが作成されたり更新された場合にGitHub Actionsを実行し、デプロイしたCloud RunのURLを、botを使ってPull Requestにコメントで投稿する - プレビューに使ったCloud Runのサービスやコンテナイメージの消し忘れを避けたい
→ Pull Requestのイベントが起きるたびに毎回Cloud Runのサービスやイメージを削除する
- 複数のコマンドを手動実行するのは手間がかかる
なお、なぜGitHub Actionsを採用したのか?Circle CIなどの選択肢もあったのでは?については、仕事やプライベートで使っていて知見があることや、私自身がGitHub Actionsを使いたかったからです。
全体構成
構成は以下の通りです。
この構成上で、GitHub Actionsのワークフローで行うことは以下のとおりです。
- プレビュー表示ジョブ
(実行タイミング: Pull Requestのopen、reopen、pushによる更新発生時)- 既存のCloud Runのサービスを削除
- 既存のコンテナイメージを削除
- Container Registryへイメージプッシュ
- Cloud Runにデプロイ
- デプロイ完了後にプレビューのURLをPull Requestに投稿
- プレビュークローズジョブ
(実行タイミング: Pull Requestのclose時)- 既存のCloud Runのサービスを削除
- 既存のコンテナイメージを削除
ワークフロー作成前の準備
Secretsの設定
ワークフローではGCPのプロジェクトIDと、GitHub ActionsからGoogleCloudを使用するためのサービスアカウントを使用します。
そのためこれらを取得し、リポジトリのSecretsに保存します。
プロジェクトIDの取得
GCP上でプロジェクトを作成し、プロジェクトIDを取得します。
プロジェクト作成、プロジェクトIDの取得に関しては以下公式ドキュメントを参照してください。
取得したプロジェクトIDをPROJECT_ID
として、リポジトリのSecretsに保存します。
GitHub ActionsからGoogleCloudと認証するためのサービスアカウント取得
GitHub ActionsからGoogle Cloudとの認証を行うため、ワークフローではgoogle-github-actions/authのアクションを使用しています。
このアクションでは認証のための連携方法を選べるので、今回はWorkload Identity Federationを採用しました。
Workload Identity Federationを使用するための事前準備は、以下クラスメソッドさんの記事や、google-github-actions/authアクションの「Setting up Workload Identity Federation」の手順を基に実施しました。
上記手順を行い、gcloud iam workload-identity-pools providers describe・・・
のコマンドまで実行すると、Workload Identityプロバイダーの名前を取得できます。
この名前をWORKLOAD_IDENTITY_PROVIDER
として、リポジトリのSecretsに保存します。
作成したサービスアカウントに対してロールを追加する
「GitHub ActionsからGoogleCloudと認証するためのサービスアカウント取得」項目で作成したサービスアカウントの権限だけでは、Container RegistryへのプッシュやCloud Runへのデプロイができません。
そのためロールを追加します。
-
Container Registryを使用するためのロール追加
- Container Registryへのpushに関しては、公式にて必要な権限とロールが記載してあります。このリファレンスを基に、作成したサービスアカウントにストレージ管理者(
roles/storage.admin
)のロールを紐付けます。
https://cloud.google.com/container-registry/docs/access-controlNote: Pushing images requires object read and write permissions as well as the storage.buckets.get permission.
- Container Registryへのpushに関しては、公式にて必要な権限とロールが記載してあります。このリファレンスを基に、作成したサービスアカウントにストレージ管理者(
-
Cloud Runを使用するためのロール追加
- Cloud Runへのデプロイも同様に、必要な権限とロールがあります。このリファレンスを基に、作成したサービスアカウントに、Cloud Run管理者(
roles/run.admin
)、サービス アカウント ユーザー(roles/iam.serviceAccountUser
)のロールを紐付けます。
https://cloud.google.com/run/docs/reference/iam/rolesA user needs the following permissions to deploy new Cloud Run services or revisions:
- run.services.create and run.services.update on the project level are required. run.services.get is not strictly required, but is recommended in order to read the status of the created service. Typically assigned through the roles/run.admin role. It can be changed in the project permissions admin page.
- iam.serviceAccounts.actAs for the Cloud Run runtime service account. By default, this is PROJECT_NUMBER-compute@developer.gserviceaccount.com. The permission is typically assigned through the roles/iam.serviceAccountUser role.
- Cloud Runへのデプロイも同様に、必要な権限とロールがあります。このリファレンスを基に、作成したサービスアカウントに、Cloud Run管理者(
これでロールの追加は完了です。
ワークフローファイル
作成したワークフローファイルはこちらになります。
name: zenn-preview
on:
pull_request:
branches: [ main ]
types: [opened, synchronize, reopened, closed]
workflow_dispatch:
jobs:
display-preview:
if: ${{ github.event.action != 'closed' }}
runs-on: ubuntu-latest
permissions:
contents: 'read'
id-token: 'write'
pull-requests: 'write'
steps:
- uses: actions/checkout@v3
- name: 'Authenticate to Google Cloud'
uses: 'google-github-actions/auth@v0'
with:
workload_identity_provider: ${{ secrets.WORKLOAD_IDENTITY_PROVIDER }}
service_account: 'my-service-account@${{ secrets.PROJECT_ID }}.iam.gserviceaccount.com'
- name: 'Set up Cloud SDK'
uses: 'google-github-actions/setup-gcloud@v0'
- name: 'Delete services from Cloud Run'
run: |
gcloud run services list \
| sed 1d \
| awk '{print $2}' \
| xargs -n 1 -I{} gcloud run services delete {} --platform managed --region asia-northeast1 --quiet
- name: 'Delete images from Container Registry'
run: |
gcloud container images list-tags "gcr.io/${{ secrets.PROJECT_ID }}/zenn-preview" \
| sed 1d \
| awk '{print $1}' \
| xargs -n 1 -I{} gcloud container images delete "gcr.io/${{ secrets.PROJECT_ID }}/zenn-preview@sha256:{}" --quiet --force-delete-tags
- name: 'Configure docker for gcloud'
run: gcloud auth configure-docker
- name: 'Build image'
run: docker build -t "gcr.io/${{ secrets.PROJECT_ID }}/zenn-preview" .
- name: 'Push image'
run: docker push "gcr.io/${{ secrets.PROJECT_ID }}/zenn-preview"
- id: create_service_name
name: 'Create service name'
run: |
SERVICE_NAME="zenn-preview-xxxxx"
echo "::set-output name=SERVICE_NAME::${SERVICE_NAME}"
- id: deploy
name: 'Deploy to Cloud Run'
uses: 'google-github-actions/deploy-cloudrun@v0'
with:
service: "${{ steps.create_service_name.outputs.SERVICE_NAME }}"
image: "gcr.io/${{ secrets.PROJECT_ID }}/zenn-preview"
region: 'asia-northeast1'
flags: --allow-unauthenticated --port=8000
- name: 'Post comments'
uses: actions/github-script@v5
with:
script: |
var preview_url_message = `Preview URL: ${{ steps.deploy.outputs.url }}\n`
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: preview_url_message
})
close-preview:
if: ${{ github.event.action == 'closed' }}
runs-on: ubuntu-latest
permissions:
contents: 'read'
id-token: 'write'
steps:
- name: 'Authenticate to Google Cloud'
uses: 'google-github-actions/auth@v0'
with:
workload_identity_provider: ${{ secrets.WORKLOAD_IDENTITY_PROVIDER }}
service_account: 'my-service-account@${{ secrets.PROJECT_ID }}.iam.gserviceaccount.com'
- name: 'Set up Cloud SDK'
uses: 'google-github-actions/setup-gcloud@v0'
- name: 'Delete services from Cloud Run'
run: |
gcloud run services list \
| sed 1d \
| awk '{print $2}' \
| xargs -n 1 -I{} gcloud run services delete {} --platform managed --region asia-northeast1 --quiet
- name: 'Delete images from Container Registry'
run: |
gcloud container images list-tags "gcr.io/${{ secrets.PROJECT_ID }}/zenn-preview" \
| sed 1d \
| awk '{print $1}' \
| xargs -n 1 -I{} gcloud container images delete "gcr.io/${{ secrets.PROJECT_ID }}/zenn-preview@sha256:{}" --quiet --force-delete-tags
実行結果
Pull Requestを作成するとGitHub Actionsが実行され、botによってURLがPull Requestに投稿されます。
このURLをクリックすると、Cloud Run上で表示されたZennの記事を見ることができます。
おわりに
この記事では、GitHub ActionsとCloud RunでZennの記事を限定公開する方法について説明しました。
ワークフロー内のコマンド作成やGCPの権限周りの理解が少し大変でしたが、今後記事を継続して投稿していく上でプレビューを必ず見るため、時間をかけた分の価値はあったと感じています。
この記事が誰かのお役に立てれば幸いです。
Discussion