1. Introdução
Visão geral
Neste codelab, você vai configurar o Cloud Run para criar e implantar automaticamente novas versões do seu aplicativo sempre que enviar as mudanças do código-fonte para um repositório do GitHub.
Esse app de demonstração salva dados do usuário no Firestore. No entanto, apenas uma parte deles é salva corretamente. Você vai configurar implantações contínuas de modo que, quando enviar uma correção de bug ao seu repositório do GitHub, veja automaticamente a correção ser disponibilizada em uma nova revisão.
O que você vai aprender
- Criar um aplicativo da Web Express com o Editor do Cloud Shell
- Conecte sua conta do GitHub ao Google Cloud para implantações contínuas
- Implante seu aplicativo automaticamente no Cloud Run
- Saiba como usar o HTMX e o TailwindCSS
2. Configuração e requisitos
Pré-requisitos
- Você tem uma conta do GitHub e sabe como criar e enviar códigos para repositórios.
- Você fez login no console do Cloud.
- Você já implantou um serviço do Cloud Run. Por exemplo, siga o guia de início rápido sobre a implantação de um serviço da Web pelo código-fonte.
Ativar o Cloud Shell
- No Console do Cloud, clique em Ativar o Cloud Shell.
Se você estiver iniciando o Cloud Shell pela primeira vez, verá uma tela intermediária com a descrição dele. Se aparecer uma tela intermediária, clique em Continuar.
Leva apenas alguns instantes para provisionar e se conectar ao Cloud Shell.
Essa máquina virtual tem todas as ferramentas de desenvolvimento necessárias. Ela oferece um diretório principal persistente de 5 GB, além de ser executada no Google Cloud. Isso aprimora o desempenho e a autenticação da rede. Grande parte do trabalho neste codelab, se não todo, pode ser feito em um navegador.
Depois de se conectar ao Cloud Shell, você verá sua autenticação e o projeto estará configurado com o ID do seu projeto.
- Execute o seguinte comando no Cloud Shell para confirmar se a conta está autenticada:
gcloud auth list
Resposta ao comando
Credentialed Accounts ACTIVE ACCOUNT * <my_account>@<my_domain.com> To set the active account, run: $ gcloud config set account `ACCOUNT`
- Execute o seguinte comando no Cloud Shell para confirmar que o comando gcloud sabe sobre seu projeto:
gcloud config list project
Resposta ao comando
[core] project = <PROJECT_ID>
Se o projeto não estiver configurado, configure-o usando este comando:
gcloud config set project <PROJECT_ID>
Resposta ao comando
Updated property [core/project].
3. Ative APIs e defina variáveis de ambiente
Ativar APIs
Este codelab requer o uso das APIs a seguir. É possível ativar essas APIs executando o seguinte comando:
gcloud services enable run.googleapis.com \ cloudbuild.googleapis.com \ firestore.googleapis.com \ iamcredentials.googleapis.com
Configurar as variáveis de ambiente.
Você pode definir variáveis de ambiente que serão usadas neste 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. Criar uma conta de serviço
Essa conta de serviço será usada pelo Cloud Run para chamar a API Gemini da Vertex AI. Essa conta de serviço também terá permissões para ler e gravar no Firestore e ler secrets do Secret Manager.
Primeiro, execute este comando para criar a conta de serviço:
gcloud iam service-accounts create $SERVICE_ACCOUNT \ --display-name="Cloud Run access to Firestore"
Agora conceda à conta de serviço acesso de leitura e gravação no Firestore.
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \ --role=roles/datastore.user
5. Criar e configurar um projeto do Firebase
- No Console do Firebase, clique em Adicionar projeto.
- Insira <YOUR_PROJECT_ID> para adicionar o Firebase a um projeto atual do Google Cloud
- Se solicitado, leia e aceite os Termos do Firebase.
- Clique em Continuar.
- Clique em Confirmar plano para confirmar o plano de faturamento do Firebase.
- A ativação do Google Analytics neste codelab é opcional.
- Clique em Adicionar Firebase:
- Quando o projeto estiver pronto, clique em Continuar.
- No menu Build, clique em Banco de dados do Firestore.
- Clique em Criar banco de dados.
- Escolha sua região no menu suspenso Local e clique em Próxima.
- Use a opção padrão Iniciar no modo de produção e depois clique em Criar.
6. Programar o aplicativo
Primeiro, crie um diretório para o código-fonte e use cd nele.
mkdir cloud-run-github-cd-demo && cd $_
Em seguida, crie um arquivo package.json
com o seguinte conteúdo:
{ "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" } }
Primeiro, crie um arquivo de origem app.js
com o conteúdo abaixo. Esse arquivo contém o ponto de entrada para o serviço e a lógica principal do app.
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); });
Crie um arquivo com o nome 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>`;
Crie um arquivo input.css
para o tailwindCSS.
@tailwind base; @tailwind components; @tailwind utilities;
E crie o arquivo tailwind.config.js
para tailwindCSS
/** @type {import('tailwindcss').Config} */ module.exports = { content: ["./**/*.{html,js}"], theme: { extend: {} }, plugins: [] };
E crie um arquivo .gitignore
.
node_modules/ npm-debug.log coverage/ package-lock.json .DS_Store
Agora, crie um novo diretório public
.
mkdir public cd public
Dentro desse diretório público, crie o arquivo index.html
para o front-end, que usará 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. Executar o aplicativo no local
Nesta seção, você vai executar o aplicativo localmente para confirmar que há um bug nele quando o usuário tenta salvar dados.
Primeiro, você precisa ter o papel de Usuário do Datastore para acessar o Firestore (se estiver usando sua identidade para autenticação, por exemplo, ao executar no Cloud Shell) ou pode representar a conta de usuário criada anteriormente.
Como usar o ADC ao executar localmente
Se você estiver executando no Cloud Shell, já está em execução em uma máquina virtual do Google Compute Engine. As credenciais associadas a essa máquina virtual, conforme mostrado ao executar gcloud auth list
, serão usadas automaticamente pelo Application Default Credentials (ADC). Por isso, não é necessário usar o comando gcloud auth application-default login
. No entanto, sua identidade ainda precisará do papel Usuário do Datastore. Pule para a seção Executar o app localmente.
No entanto, se você estiver executando no seu terminal local (ou seja, não no Cloud Shell), será necessário usar o Application Default Credentials para se autenticar nas APIs do Google. Você pode 1) fazer login usando suas credenciais (desde que tenha o papel de usuário do Datastore) ou 2) fazer login representando a conta de serviço usada neste codelab.
Opção 1: usar suas credenciais para o ADC
Se você quiser usar suas credenciais, primeiro execute gcloud auth list
para verificar como você está autenticado na gcloud. Em seguida, pode ser necessário conceder à sua identidade o papel de Usuário da Vertex AI. Se sua identidade tem o papel de Proprietário, você já tem esse papel de usuário do Datastore. Caso contrário, execute este comando para conceder à sua identidade o papel de usuário da Vertex AI e o papel de usuário do Datastore.
USER=<YOUR_PRINCIPAL_EMAIL> gcloud projects add-iam-policy-binding $PROJECT_ID \ --member user:$USER \ --role=roles/datastore.user
Em seguida, execute o seguinte comando:
gcloud auth application-default login
Opção 2: Como representar uma conta de serviço para o ADC
Para usar a conta de serviço criada neste codelab, sua conta de usuário precisará ter o papel de Criador de token da conta de serviço. Você pode conseguir esse papel executando o seguinte comando:
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member user:$USER \ --role=roles/iam.serviceAccountTokenCreator
Em seguida, execute o comando a seguir para usar o ADC com a conta de serviço
gcloud auth application-default login --impersonate-service-account=$SERVICE_ACCOUNT_ADDRESS
Executar o aplicativo localmente
Em seguida, verifique se você está no diretório raiz cloud-run-github-cd-demo
do codelab.
cd .. && pwd
Agora você vai instalar as dependências.
npm install
Por fim, inicie o app executando o script a seguir. Esse script também gera o arquivo output.css a partir do tailwindCSS.
npm run dev
Agora abra o navegador da Web em http://localhost:8080. Se você estiver no Cloud Shell, abra o site abrindo o botão "Visualização na Web" e selecionando "Porta de visualização 8080".
Insira texto nos campos de nome e cidade e clique em "Salvar". Em seguida, atualize a página. Você notará que o campo de cidade não persistiu. Você vai corrigir esse bug na próxima seção.
Interrompa a execução local do app Express (por exemplo, Ctrl^c no MacOS).
8. Criar um repositório do GitHub
No seu diretório local, crie um novo repositório com "main" como o nome padrão da ramificação.
git init git branch -M main
Confirme a base de código atual que contém o bug. O bug vai ser corrigido depois que a implantação contínua for configurada.
git add . git commit -m "first commit for express application"
Acesse o GitHub e crie um repositório vazio que seja particular para você ou público. Este codelab recomenda o nome cloud-run-auto-deploy-codelab
para seu repositório.Para criar um repositório vazio, deixe todas as configurações padrão desmarcadas ou defina-as como nenhuma. Assim, nenhum conteúdo estará no repositório por padrão quando for criado, por exemplo,
Se você concluiu essa etapa corretamente, verá as seguintes instruções na página do repositório vazio:
Siga as instruções para enviar um repositório existente pela linha de comando com os seguintes comandos:
Primeiro, adicione o repositório remoto executando
git remote add origin <YOUR-REPO-URL-PER-GITHUB-INSTRUCTIONS>
e, em seguida, enviar por push a ramificação principal para o repositório upstream.
git push -u origin main
9. Configurar a implantação contínua
Agora que o código está em um GitHub, você pode configurar a implantação contínua. Acesse o Console do Cloud para Cloud Run.
- Clique em Criar um serviço
- Clique em Implantar continuamente a partir de um repositório.
- Clique em CONFIGURAR O CLOUD BUILD.
- No Repositório de origem
- Selecione GitHub como o provedor do repositório
- Clique em Gerenciar repositórios conectados para configurar o acesso do Cloud Build ao repositório
- Selecione seu repositório e clique em Próxima
- Em Configuração da compilação
- Deixar ramificação como ^main$
- Em "Tipo de build", selecione Go, Node.js, Python, Java, .NET Core, Ruby ou PHP usando os buildpacks do Google Cloud
- Deixe o diretório de contexto de build como
/
. - Clique em Salvar.
- Em Autenticação
- Clique em Permitir invocações não autenticadas
- Em Contêiner(es), Volumes, Rede, Segurança
- Na guia "Segurança", selecione a conta de serviço que você criou em uma etapa anterior, por exemplo,
Cloud Run access to Firestore
- Na guia "Segurança", selecione a conta de serviço que você criou em uma etapa anterior, por exemplo,
- Clique em CRIAR.
Isso implantará o serviço do Cloud Run que contém o bug que você corrigirá na próxima seção.
10. Corrigir o erro
Corrigir o bug no código
No editor do Cloud Shell, abra o arquivo app.js
e acesse o comentário //TODO: fix this bug
.
altere a linha a seguir de
//TODO: fix this bug await doc.set({ name: name });
a
//fixed town bug await doc.set({ name: name, town: town });
Verifique a correção executando
npm run start
e abra o navegador da Web. Salve os dados da cidade novamente e atualize. Você verá que os dados recém-inseridos sobre cidades persistiram corretamente na atualização.
Agora que você verificou a correção, está tudo pronto para implantá-la. Primeiro, confirme a correção.
git add . git commit -m "fixed town bug"
e, em seguida, enviá-lo por push para o repositório upstream no GitHub.
git push origin main
O Cloud Build vai implantar as alterações automaticamente. Acesse o console do Cloud do serviço do Cloud Run para monitorar as alterações na implantação.
Verificar a correção na produção
Quando o console do Cloud do serviço do Cloud Run mostrar que uma segunda revisão está exibindo 100% do tráfego. Por exemplo: https://console.cloud.google.com/run/detail/<YOUR_REGION>/<YOUR_SERVICE_NAME>/revisions, abra o URL do serviço do Cloud Run no navegador e verifique se os dados da cidade recém-inseridos continuam após a atualização da página.
11. Parabéns!
Parabéns por concluir o codelab.
Recomendamos a leitura da documentação do Cloud Run e da implantação contínua do git.
O que vimos
- Criar um aplicativo da Web Express com o Editor do Cloud Shell
- Conecte sua conta do GitHub ao Google Cloud para implantações contínuas
- Implante seu aplicativo automaticamente no Cloud Run
- Saiba como usar o HTMX e o TailwindCSS
12. Limpar
Para evitar cobranças acidentais (por exemplo, se os serviços do Cloud Run forem invocados por engano mais vezes do que sua alocação mensal de invocação do Cloud Run no nível sem custo financeiro), exclua o Cloud Run ou o projeto criado na etapa 2.
Para excluir o serviço do Cloud Run, acesse o console do Cloud Run do Cloud em https://console.cloud.google.com/run e exclua o serviço do Cloud Run que você criou neste codelab, por exemplo: exclua o serviço cloud-run-auto-deploy-codelab
.
Se você optar por excluir o projeto inteiro, acesse https://console.cloud.google.com/cloud-resource-manager, selecione o projeto criado na etapa 2 e escolha "Excluir". Se você excluir o projeto, precisará alterar os projetos no SDK Cloud. Para conferir a lista de todos os projetos disponíveis, execute gcloud projects list
.