1. はじめに
概要
この Codelab では、ソースコードの変更を GitHub リポジトリに push するたびに、アプリケーションの新しいバージョンを 自動的にビルドしてデプロイするように Cloud Run を構成します。
このデモ アプリケーションはユーザーデータを Firestore に保存しますが、データの一部しか正しく保存されません。バグ修正を GitHub リポジトリに push すると、新しいリビジョンで修正が自動的に利用できるようになるように、継続的デプロイを構成します。
学習内容
- Cloud Shell エディタで Express ウェブ アプリケーションを作成する
- 継続的デプロイのために GitHub アカウントを Google Cloud に接続する
- アプリケーションを Cloud Run に自動的にデプロイする
- HTMX と TailwindCSS の使用方法を学習する
2. 設定と要件
前提条件
- GitHub アカウントがあり、リポジトリへのコードの作成と push に慣れていること。
- Cloud コンソールにログインしていること。
- Cloud Run サービスをデプロイ済みであること。たとえば、ソースコードからウェブサービスをデプロイするクイックスタートに沿って作業を開始できます。
Cloud Shell をアクティブにする
- Cloud Console で、[Cloud Shell をアクティブにする ] をクリックします
。

Cloud Shell を初めて起動した場合は、その内容を説明する画面が表示されます。その場合は、[続行] をクリックしてください。

すぐにプロビジョニングが実行され、Cloud Shell に接続されます。

この仮想マシンには、必要な開発ツールがすべて用意されています。仮想マシンは Google Cloud で稼働し、永続的なホーム ディレクトリが 5 GB 用意されているため、ネットワークのパフォーマンスと認証が大幅に向上しています。 この Codelab で行う作業のほとんどはブラウザから実行できます。
Cloud Shell に接続すると、認証が完了し、プロジェクトがプロジェクト ID に設定されていることがわかります。
- Cloud Shell で次のコマンドを実行して、認証されたことを確認します。
gcloud auth list
コマンド出力
Credentialed Accounts
ACTIVE ACCOUNT
* <my_account>@<my_domain.com>
To set the active account, run:
$ gcloud config set account `ACCOUNT`
- Cloud Shell で次のコマンドを実行して、gcloud コマンドがプロジェクトを認識していることを確認します。
gcloud config list project
コマンド出力
[core] project = <PROJECT_ID>
上記のようになっていない場合は、次のコマンドで設定できます。
gcloud config set project <PROJECT_ID>
コマンド出力
Updated property [core/project].
3. API を有効にして環境変数を設定する
API を有効にする
この Codelab では、次の API を使用する必要があります。次のコマンドを実行して、これらの API を有効にできます。
gcloud services enable run.googleapis.com \
cloudbuild.googleapis.com \
firestore.googleapis.com \
iamcredentials.googleapis.com
環境変数を設定する
この Codelab 全体で使用する環境変数を設定できます。
REGION=<YOUR-REGION> PROJECT_ID=<YOUR-PROJECT-ID> PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)') SERVICE_ACCOUNT="firestore-accessor" SERVICE_ACCOUNT_ADDRESS=$SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com
4. サービス アカウントを作成する
このサービス アカウントは、Cloud Run が Vertex AI Gemini API を呼び出すために使用されます。このサービス アカウントには、Firestore の読み取りと書き込み、Secret Manager からのシークレットの読み取りの権限もあります。
まず、次のコマンドを実行してサービス アカウントを作成します。
gcloud iam service-accounts create $SERVICE_ACCOUNT \ --display-name="Cloud Run access to Firestore"
次に、Firestore に対する読み取りと書き込みのアクセス権をサービス アカウントに付与します。
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \ --role=roles/datastore.user
5. Firebase プロジェクトを作成して構成する
- [Firebase コンソール] で [プロジェクトを追加] をクリックします。
- <YOUR_PROJECT_ID> を入力して、Firebase を既存の Google Cloud プロジェクトのいずれかに追加します。
- Firebase の利用規約が表示されたら、内容を読み、同意します。
- [続行] をクリックします。
- [プランを確認] をクリックして Firebase のお支払いプランを確認します。
- この Codelab で Google アナリティクスを有効にするかどうかは任意です。
- [Firebase を追加] をクリックします。
- プロジェクトが作成されたら、[続行] をクリックします。
- [ビルド] メニューから [Firestore データベース] をクリックします。
- [データベースを作成] をクリックします。
- [ロケーション] プルダウンからリージョンを選択し、[次へ] をクリックします。
- デフォルトの [**本番環境モードで開始**] を使用し、[**作成**] をクリックします。
6. アプリケーションを作成する
まず、ソースコードのディレクトリを作成し、そのディレクトリに移動します。
mkdir cloud-run-github-cd-demo && cd $_
次に、次の内容の package.json ファイルを作成します。
{
"name": "cloud-run-github-cd-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node app.js",
"nodemon": "nodemon app.js",
"tailwind-dev": "npx tailwindcss -i ./input.css -o ./public/output.css --watch",
"tailwind": "npx tailwindcss -i ./input.css -o ./public/output.css",
"dev": "npm run tailwind && npm run nodemon"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@google-cloud/firestore": "^7.3.1",
"axios": "^1.6.7",
"express": "^4.18.2",
"htmx.org": "^1.9.10"
},
"devDependencies": {
"nodemon": "^3.1.0",
"tailwindcss": "^3.4.1"
}
}
まず、次の内容の app.js ソースファイルを作成します。このファイルには、サービスのエントリ ポイントとアプリのメインロジックが含まれています。
const express = require("express");
const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
const path = require("path");
const { get } = require("axios");
const { Firestore } = require("@google-cloud/firestore");
const firestoreDb = new Firestore();
const fs = require("fs");
const util = require("util");
const { spinnerSvg } = require("./spinnerSvg.js");
const service = process.env.K_SERVICE;
const revision = process.env.K_REVISION;
app.use(express.static("public"));
app.get("/edit", async (req, res) => {
res.send(`<form hx-post="/update" hx-target="this" hx-swap="outerHTML">
<div>
<p>
<label>Name</label>
<input class="border-2" type="text" name="name" value="Cloud">
</p><p>
<label>Town</label>
<input class="border-2" type="text" name="town" value="Nibelheim">
</p>
</div>
<div class="flex items-center mr-[10px] mt-[10px]">
<button class="btn bg-blue-500 text-white px-4 py-2 rounded-lg text-center text-sm font-medium mr-[10px]">Submit</button>
<button class="btn bg-gray-200 text-gray-800 px-4 py-2 rounded-lg text-center text-sm font-medium mr-[10px]" hx-get="cancel">Cancel</button>
${spinnerSvg}
</div>
</form>`);
});
app.post("/update", async function (req, res) {
let name = req.body.name;
let town = req.body.town;
const doc = firestoreDb.doc(`demo/${name}`);
//TODO: fix this bug
await doc.set({
name: name
/* town: town */
});
res.send(`<div hx-target="this" hx-swap="outerHTML" hx-indicator="spinner">
<p>
<div><label>Name</label>: ${name}</div>
</p><p>
<div><label>Town</label>: ${town}</div>
</p>
<button
hx-get="/edit"
class="bg-blue-500 text-white px-4 py-2 rounded-lg text-sm font-medium mt-[10px]"
>
Click to update
</button>
</div>`);
});
app.get("/cancel", (req, res) => {
res.send(`<div hx-target="this" hx-swap="outerHTML">
<p>
<div><label>Name</label>: Cloud</div>
</p><p>
<div><label>Town</label>: Nibelheim</div>
</p>
<div>
<button
hx-get="/edit"
class="bg-blue-500 text-white px-4 py-2 rounded-lg text-sm font-medium mt-[10px]"
>
Click to update
</button>
</div>
</div>`);
});
const port = parseInt(process.env.PORT) || 8080;
app.listen(port, async () => {
console.log(`booth demo: listening on port ${port}`);
//serviceMetadata = helper();
});
app.get("/helper", async (req, res) => {
let region = "";
let projectId = "";
let div = "";
try {
// Fetch the token to make a GCF to GCF call
const response1 = await get(
"http://metadata.google.internal/computeMetadata/v1/project/project-id",
{
headers: {
"Metadata-Flavor": "Google"
}
}
);
// Fetch the token to make a GCF to GCF call
const response2 = await get(
"http://metadata.google.internal/computeMetadata/v1/instance/region",
{
headers: {
"Metadata-Flavor": "Google"
}
}
);
projectId = response1.data;
let regionFull = response2.data;
const index = regionFull.lastIndexOf("/");
region = regionFull.substring(index + 1);
div = `
<div>
This created the revision <code>${revision}</code> of the
Cloud Run service <code>${service}</code> in <code>${region}</code>
for project <code>${projectId}</code>.
</div>`;
} catch (ex) {
// running locally
div = `<div> This is running locally.</div>`;
}
res.send(div);
});
spinnerSvg.js というファイルを作成します。
module.exports.spinnerSvg = `<svg id="spinner" alt="Loading..."
class="htmx-indicator animate-spin -ml-1 mr-3 h-5 w-5 text-blue-500"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>`;
tailwindCSS 用の input.css ファイルを作成します。
@tailwind base; @tailwind components; @tailwind utilities;
tailwindCSS 用の tailwind.config.js ファイルを作成します。
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./**/*.{html,js}"],
theme: {
extend: {}
},
plugins: []
};
.gitignore ファイルを作成します。
node_modules/ npm-debug.log coverage/ package-lock.json .DS_Store
次に、新しい public ディレクトリを作成します。
mkdir public cd public
その public ディレクトリ内に、フロントエンド用の index.html ファイルを作成します。このファイルは htmx を使用します。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<script
src="https://unpkg.com/htmx.org@1.9.10"
integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC"
crossorigin="anonymous"
></script>
<link href="./output.css" rel="stylesheet" />
<title>Demo 1</title>
</head>
<body
class="font-sans bg-body-image bg-cover bg-center leading-relaxed"
>
<div class="container max-w-[700px] mt-[50px] ml-auto mr-auto">
<div class="hero flex items-center">
<div class="message text-base text-center mb-[24px]">
<h1 class="text-2xl font-bold mb-[10px]">
It's running!
</h1>
<div class="congrats text-base font-normal">
Congratulations, you successfully deployed your
service to Cloud Run.
</div>
</div>
</div>
<div class="details mb-[20px]">
<p>
<div hx-trigger="load" hx-get="/helper" hx-swap="innerHTML" hx-target="this">Hello</div>
</p>
</div>
<p
class="callout text-sm text-blue-700 font-bold pt-4 pr-6 pb-4 pl-10 leading-tight"
>
You can deploy any container to Cloud Run that listens for
HTTP requests on the port defined by the
<code>PORT</code> environment variable. Cloud Run will
scale automatically based on requests and you never have to
worry about infrastructure.
</p>
<h1 class="text-2xl font-bold mt-[40px] mb-[20px]">
Persistent Storage Example using Firestore
</h1>
<div hx-target="this" hx-swap="outerHTML">
<p>
<div><label>Name</label>: Cloud</div>
</p><p>
<div><label>Town</label>: Nibelheim</div>
</p>
<div>
<button
hx-get="/edit"
class="bg-blue-500 text-white px-4 py-2 rounded-lg text-sm font-medium mt-[10px]"
>
Click to update
</button>
</div>
</div>
<h1 class="text-2xl font-bold mt-[40px] mb-[20px]">
What's next
</h1>
<p class="next text-base mt-4 mb-[20px]">
You can build this demo yourself!
</p>
<p class="cta">
<button
class="bg-blue-500 text-white px-4 py-2 rounded-lg text-center text-sm font-medium"
>
VIEW CODELAB
</button>
</p>
</div>
</body>
</html>
7. アプリケーションをローカルで実行する
このセクションでは、アプリケーションをローカルで実行して、ユーザーがデータを保存しようとしたときにアプリケーションにバグがあることを確認します。
まず、Firestore にアクセスするには、Datastore ユーザーロールが必要です(認証に ID を使用する場合、Cloud Shell で実行している場合など)。または、以前に作成したユーザー アカウントの権限を借用できます。
ローカルで実行する場合に ADC を使用する
Cloud Shell で実行している場合は、Google Compute Engine 仮想マシンで実行されています。この仮想マシンに関連付けられている認証情報(gcloud auth list を実行すると表示されます)は、アプリケーションのデフォルト認証情報(ADC)によって自動的に使用されるため、gcloud auth application-default login コマンドを使用する必要はありません。ただし、ID には Datastore ユーザーロールが必要です。アプリをローカルで実行する セクションまでスキップできます。
ただし、ローカル ターミナルで実行している場合(Cloud Shell でない場合)は、アプリケーションのデフォルト認証情報を使用して Google API を認証する必要があります。1)認証情報を使用してログインする(Datastore ユーザーロールがある場合)、または 2)この Codelab で使用するサービス アカウントの権限を借用してログインできます。
オプション 1)認証情報を使用して ADC を使用する
認証情報を使用する場合は、まず gcloud auth list を実行して、gcloud での認証方法を確認します。次に、ID に Vertex AI ユーザーロールを付与する必要がある場合があります。ID にオーナーロールがある場合は、この Datastore ユーザーロールがすでに付与されています。そうでない場合は、次のコマンドを実行して、ID に Vertex AI ユーザーロールと Datastore ユーザーロールを付与できます。
USER=<YOUR_PRINCIPAL_EMAIL> gcloud projects add-iam-policy-binding $PROJECT_ID \ --member user:$USER \ --role=roles/datastore.user
次のコマンドを実行します。
gcloud auth application-default login
オプション 2)ADC のサービス アカウントの権限を借用する
この Codelab で作成したサービス アカウントを使用する場合は、ユーザー アカウントに サービス アカウント トークン作成者のロールが必要です。このロールを取得するには、次のコマンドを実行します。
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member user:$USER \ --role=roles/iam.serviceAccountTokenCreator
次に、次のコマンドを実行して、サービス アカウントで ADC を使用します。
gcloud auth application-default login --impersonate-service-account=$SERVICE_ACCOUNT_ADDRESS
アプリをローカルで実行する
次に、Codelab のルート ディレクトリ cloud-run-github-cd-demo に移動します。
cd .. && pwd
次に、依存関係をインストールします。
npm install
最後に、次のスクリプトを実行してアプリを起動します。このスクリプトは、tailwindCSS から output.css ファイルも生成します。
npm run dev
ウェブブラウザを開いて http://localhost:8080 にアクセスします。Cloud Shell を使用している場合は、[ウェブでプレビュー] ボタンを開き、[プレビュー ポート 8080] を選択してウェブサイトを開くことができます。
![ウェブ プレビュー - [ポート 8080 でプレビュー] ボタン](https://codelabs.developers.google.com/static/codelabs/how-to-deploy-github-cloud-run-using-cloud-build/img/PreviewPort8080.png?hl=ja)
名前と町の入力フィールドにテキストを入力して、[保存] をクリックします。ページを再読み込みします。町のフィールドが保持されていないことがわかります。このバグは次のセクションで修正します。
ローカルでの Express アプリの実行を停止します(例: MacOS で Ctrl^c)。
8. GitHub リポジトリを作成する
ローカル ディレクトリに、デフォルトのブランチ名として main を使用して新しいリポジトリを作成します。
git init git branch -M main
バグを含む現在のコードベースを commit します。継続的デプロイが構成されたら、バグを修正します。
git add . git commit -m "first commit for express application"
GitHub に移動し、非公開または公開の空のリポジトリを作成します。この Codelab では、リポジトリに cloud-run-auto-deploy-codelab という名前を付けることをおすすめします。空のリポジトリを作成するには、デフォルトの設定をすべてオフにするか、[なし] に設定 して、作成時にデフォルトでリポジトリにコンテンツが含まれないようにします。例:

この手順を正しく完了すると、空のリポジトリ ページに次の手順が表示されます。

次のコマンドを実行して、コマンドラインから既存のリポジトリを push する 手順に沿って操作します。
まず、次のコマンドを実行してリモート リポジトリを追加します。
git remote add origin <YOUR-REPO-URL-PER-GITHUB-INSTRUCTIONS>
次に、main ブランチをアップストリーム リポジトリに push します。
git push -u origin main
9. 継続的デプロイを設定する
GitHub にコードが用意できたので、継続的デプロイを設定できます。Cloud Run の Cloud コンソールに移動します。
- [サービスを作成] をクリックします。
- [リポジトリから継続的にデプロイする] をクリックします。
- [CLOUD BUILD を設定] をクリックします。
- [ソース リポジトリ] で次の操作を行います。
- [リポジトリ プロバイダ] で [GitHub] を選択します。
- [接続されたリポジトリを管理] をクリックして、リポジトリへの Cloud Build アクセスを構成します。
- リポジトリを選択して [次へ] をクリックします。
- [ビルド構成] で次の操作を行います。
- [ブランチ] は ^main$ のままにします。
- [ビルドタイプ] で [Go、Node.js、Python、Java、.NET Core、Ruby、PHP(Google Cloud の Buildpacks を使用)] を選択します。
- [ビルド コンテキストのディレクトリ] は
/のままにします。 - [保存] をクリックします。
- [認証] で次の操作を行います。
- [未認証の呼び出しを許可] をクリックします。
- [コンテナ、ボリューム、ネットワーキング、セキュリティ] で次の操作を行います。
- [セキュリティ] タブで、前の手順で作成したサービス アカウント(
Cloud Run access to Firestoreなど)を選択します。
- [セキュリティ] タブで、前の手順で作成したサービス アカウント(
- [作成] をクリックします。
これにより、次のセクションで修正するバグを含む Cloud Run サービスがデプロイされます。
10. バグを修正する
コードのバグを修正する
Cloud Shell エディタで app.js ファイルを開き、//TODO: fix this bug というコメントに移動します。
次の行を
//TODO: fix this bug
await doc.set({
name: name
});
に置き換えます。
//fixed town bug
await doc.set({
name: name,
town: town
});
次のコマンドを実行して修正を確認します。
npm run start
ウェブブラウザを開きます。町のデータをもう一度保存して、更新します。更新時に、新しく入力した町のデータが正しく保持されていることがわかります。
修正を確認したので、デプロイする準備ができました。まず、修正を commit します。
git add . git commit -m "fixed town bug"
GitHub のアップストリーム リポジトリに push します。
git push origin main
Cloud Build によって変更が自動的にデプロイされます。Cloud Run サービスの Cloud コンソールに移動して、デプロイの変更をモニタリングできます。
本番環境で修正を確認する
Cloud Run サービスの Cloud コンソールに、2 番目のリビジョンが 100% のトラフィックを処理していることが表示されたら(https://console.cloud.google.com/run/detail/<YOUR_REGION>/<YOUR_SERVICE_NAME>/revisions など)、ブラウザで Cloud Run サービスの URL を開き、ページを更新した後、新しく入力した町のデータが保持されていることを確認します。
11. 完了
以上で、この Codelab は完了です。
Cloud Run と Git からの継続的デプロイのドキュメントを確認することをおすすめします。
学習した内容
- Cloud Shell エディタで Express ウェブ アプリケーションを作成する
- 継続的デプロイのために GitHub アカウントを Google Cloud に接続する
- アプリケーションを Cloud Run に自動的にデプロイする
- HTMX と TailwindCSS の使用方法を学習する
12. クリーンアップ
Cloud Run サービスを削除するには、Cloud Run Cloud コンソール(https://console.cloud.google.com/run)に移動し、この Codelab で作成した Cloud Run サービス(cloud-run-auto-deploy-codelab サービスなど)を削除します。
プロジェクト全体を削除する場合は、https://console.cloud.google.com/cloud-resource-manager に移動し、ステップ 2 で作成したプロジェクトを選択して、[削除] を選択します。プロジェクトを削除する場合は、Cloud SDK でプロジェクトを変更する必要があります。gcloud projects list を実行すると、使用可能なすべてのプロジェクトのリストが表示されます。