1. Введение
Обзор
В этом практическом занятии вы настроите Cloud Run для автоматической сборки и развертывания новых версий вашего приложения всякий раз, когда вы отправляете изменения исходного кода в репозиторий GitHub.
Это демонстрационное приложение сохраняет пользовательские данные в Firestore, однако корректно сохраняется лишь часть данных. Вам потребуется настроить непрерывную интеграцию, чтобы при отправке исправления ошибки в ваш репозиторий GitHub это исправление автоматически становилось доступным в новой ревизии.
Что вы узнаете
- Напишите веб-приложение на Express с помощью редактора Cloud Shell.
- Подключите свою учетную запись GitHub к Google Cloud для непрерывного развертывания.
- Автоматически разверните ваше приложение в Cloud Run.
- Узнайте, как использовать HTMLX и TailwindCSS.
2. Настройка и требования
Предварительные требования
- У вас есть аккаунт на GitHub, и вы знакомы с созданием и отправкой кода в репозитории.
- Вы вошли в облачную консоль.
- Вы ранее развернули службу Cloud Run. Например, для начала вы можете воспользоваться руководством по быстрому развертыванию веб-службы из исходного кода .
Активировать Cloud Shell
- В консоли Cloud нажмите «Активировать Cloud Shell» .
.

Если вы запускаете Cloud Shell впервые, вам будет показан промежуточный экран с описанием его возможностей. Если вы увидели промежуточный экран, нажмите «Продолжить» .

Подготовка и подключение к Cloud Shell займут всего несколько минут.

Эта виртуальная машина оснащена всеми необходимыми инструментами разработки. Она предоставляет постоянный домашний каталог объемом 5 ГБ и работает в облаке Google, что значительно повышает производительность сети и аутентификацию. Большая часть, если не вся, ваша работа в этом практическом задании может быть выполнена с помощью браузера.
После подключения к Cloud Shell вы увидите, что прошли аутентификацию и что проект настроен на ваш идентификатор проекта.
- Выполните следующую команду в 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
Для выполнения этого практического задания требуется использование следующих API. Вы можете включить эти API, выполнив следующую команду:
gcloud services enable run.googleapis.com \
cloudbuild.googleapis.com \
firestore.googleapis.com \
iamcredentials.googleapis.com
Настройка переменных среды
Вы можете установить переменные окружения, которые будут использоваться на протяжении всего этого практического занятия.
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 для вызова API Vertex AI Gemini. У этой учетной записи службы также будут разрешения на чтение и запись в 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.
- Включение Google Analytics для этого практического занятия является необязательным.
- Нажмите «Добавить Firebase» .
- После создания проекта нажмите «Продолжить» .
- В меню «Сборка» выберите «База данных Firestore» .
- Нажмите «Создать базу данных» .
- Выберите свой регион из выпадающего списка «Местоположение» , затем нажмите «Далее» .
- Используйте значение по умолчанию «Запустить в производственном режиме» , затем нажмите «Создать» .
6. Напишите заявку.
Сначала создайте директорию для исходного кода и перейдите в неё с помощью команды `cd`.
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>`;
Создайте файл input.css для tailwindCSS.
@tailwind base; @tailwind components; @tailwind utilities;
И создайте файл tailwind.config.js для tailwindCSS.
/** @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
Внутри этой общедоступной директории создайте файл index.html для внешнего интерфейса, который будет использовать HTML.
<!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 (если вы используете свою учетную запись для аутентификации, например, работаете в Cloud Shell), либо вы можете выдать себя за ранее созданную учетную запись пользователя.
Использование АЦП при локальном запуске
Если вы работаете в Cloud Shell, вы уже используете виртуальную машину Google Compute Engine. Ваши учетные данные, связанные с этой виртуальной машиной (как показано при выполнении gcloud auth list ), будут автоматически использоваться Application Default Credentials (ADC), поэтому нет необходимости использовать команду gcloud auth application-default login . Однако вашей учетной записи по-прежнему потребуется роль Datastore User. Вы можете перейти к разделу «Запуск приложения локально» .
Однако, если вы работаете на локальном терминале (то есть не в Cloud Shell), вам потребуется использовать учетные данные приложения по умолчанию для аутентификации в API Google. Вы можете либо 1) войти в систему, используя свои учетные данные (при условии, что у вас есть роль пользователя хранилища данных), либо 2) войти в систему, выдав себя за учетную запись службы, используемую в этом практическом задании.
Вариант 1) Использование ваших учетных данных для ADC
Если вы хотите использовать свои учетные данные, сначала выполните gcloud auth list , чтобы проверить, как вы прошли аутентификацию в gcloud. Затем вам может потребоваться предоставить вашей учетной записи роль пользователя Vertex AI. Если у вашей учетной записи есть роль владельца, у вас уже есть роль пользователя хранилища данных. В противном случае вы можете выполнить эту команду, чтобы предоставить вашей учетной записи роль пользователя Vertex AI и роль пользователя хранилища данных.
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
Если вы хотите использовать учетную запись службы, созданную в этом практическом задании, вашей учетной записи пользователя потребуется роль «Создатель токена учетной записи службы» . Получить эту роль можно, выполнив следующую команду:
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
Запустите приложение локально
Далее убедитесь, что вы находитесь в корневом каталоге cloud-run-github-cd-demo для вашей практической работы.
cd .. && pwd
Теперь перейдём к установке зависимостей.
npm install
Наконец, вы можете запустить приложение, выполнив следующий скрипт. Этот скрипт также сгенерирует файл output.css из tailwindCSS.
npm run dev
Теперь откройте веб-браузер по адресу http://localhost:8080. Если вы используете Cloud Shell, вы можете открыть веб-сайт, нажав кнопку «Предварительный просмотр веб-страницы» и выбрав «Предварительный просмотр порта 8080».

Введите текст в поля «Имя» и «Город» и нажмите «Сохранить». Затем обновите страницу. Вы заметите, что поле «Город» не сохранилось. Вы исправите эту ошибку в следующем разделе.
Остановите локальный запуск приложения Express (например, Ctrl^c на macOS).
8. Создайте репозиторий GitHub.
В локальной директории создайте новый репозиторий, указав в качестве имени ветки по умолчанию ветку main.
git init git branch -M main
Зафиксируйте текущий код, содержащий ошибку. Вы исправите ошибку после настройки непрерывного развертывания.
git add . git commit -m "first commit for express application"
Перейдите на GitHub и создайте пустой репозиторий, который может быть либо приватным, либо публичным. В этом руководстве рекомендуется назвать репозиторий cloud-run-auto-deploy-codelab Чтобы создать пустой репозиторий, оставьте все настройки по умолчанию отключенными или установите значение «none», чтобы при создании репозитория в нем по умолчанию не было никакого контента, например:

Если вы правильно выполнили этот шаг, на странице пустого репозитория вы увидите следующие инструкции:

Для отправки изменений в существующий репозиторий из командной строки выполните следующие команды:
Сначала добавьте удалённый репозиторий, выполнив команду...
git remote add origin <YOUR-REPO-URL-PER-GITHUB-INSTRUCTIONS>
Затем отправьте основную ветку в репозиторий upstream.
git push -u origin main
9. Настройка непрерывного развертывания
Теперь, когда у вас есть код в GitHub, вы можете настроить непрерывное развертывание. Перейдите в консоль Cloud Run .
- Нажмите «Создать службу».
- Нажмите « Непрерывное развертывание из репозитория».
- Нажмите «НАСТРОИТЬ СОЗДАНИЕ ОБЛАКА» .
- В репозитории исходного кода
- Выберите GitHub в качестве поставщика репозитория.
- Нажмите «Управление подключенными репозиториями» , чтобы настроить доступ Cloud Build к репозиторию.
- Выберите свой репозиторий и нажмите «Далее».
- В разделе «Конфигурация сборки»
- Оставить ветку как ^main$
- В поле «Тип сборки» выберите Go, Node.js, Python, Java, .NET Core, Ruby или PHP с помощью buildpack-пакетов Google Cloud.
- Оставьте каталог контекста сборки как
/ - Нажмите «Сохранить».
- В процессе аутентификации
- Нажмите «Разрешить неаутентифицированные вызовы».
- В разделах «Контейнеры», «Тома», «Сеть», «Безопасность»
- На вкладке «Безопасность» выберите учетную запись службы, созданную на предыдущем шаге, например,
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
Откройте веб-браузер. Снова сохраните данные для города и обновите страницу. Вы увидите, что введенные данные о городе корректно сохранились после обновления.
Теперь, когда вы проверили исправление, вы готовы его развернуть. Сначала зафиксируйте исправление.
git add . git commit -m "fixed town bug"
а затем загрузить его в основной репозиторий на GitHub.
git push origin main
Cloud Build автоматически развернет ваши изменения. Для отслеживания изменений в развертывании вы можете перейти в консоль Cloud Console для вашей службы Cloud Run.
Проверьте исправление в рабочей среде.
Как только в консоли Cloud Console для вашей службы Cloud Run отобразится информация о том, что вторая версия теперь обрабатывает 100% трафика, например, https://console.cloud.google.com/run/detail/<ВАШ_РЕГИОН>/<НАЗВАНИЕ_ВАШЕЙ_СЛУЖБЫ>/revisions, вы можете открыть URL-адрес службы Cloud Run в браузере и убедиться, что введенные данные о городе сохраняются после обновления страницы.
11. Поздравляем!
Поздравляем с завершением практического занятия!
Мы рекомендуем ознакомиться с документацией по Cloud Run и непрерывной развертыванию из Git .
Что мы рассмотрели
- Напишите веб-приложение на Express с помощью редактора Cloud Shell.
- Подключите свою учетную запись GitHub к Google Cloud для непрерывного развертывания.
- Автоматически разверните ваше приложение в Cloud Run.
- Узнайте, как использовать HTMLX и TailwindCSS.
12. Уборка
Чтобы избежать непреднамеренных списаний средств (например, если сервисы Cloud Run будут случайно запущены больше раз, чем предусмотрено вашим ежемесячным лимитом на запуск Cloud Run в бесплатном тарифе ), вы можете либо удалить Cloud Run, либо удалить проект, созданный на шаге 2.
Чтобы удалить службу Cloud Run, перейдите в консоль Cloud Run по адресу https://console.cloud.google.com/run и удалите службу Cloud Run, созданную вами в этом практическом задании, например, удалите службу cloud-run-auto-deploy-codelab .
Если вы решите удалить весь проект, перейдите по ссылке https://console.cloud.google.com/cloud-resource-manager , выберите проект, созданный на шаге 2, и нажмите «Удалить». После удаления проекта вам потребуется изменить проекты в вашем Cloud SDK. Список всех доступных проектов можно просмотреть, выполнив gcloud projects list .