Foto do dia: Laboratório 3: crie uma colagem das fotos mais recentes

1. Visão geral

Neste codelab, você vai criar um novo serviço do Cloud Run, o serviço de colagem, que será acionado pelo Cloud Scheduler em um intervalo regular de tempo. O serviço busca as imagens mais recentes enviadas e cria uma colagem delas. Ele encontra a lista de imagens recentes no Cloud Firestore e faz o download dos arquivos de imagens reais do Cloud Storage.

df20f5d0402b54b4.png

O que você vai aprender

  • Cloud Run
  • Cloud Scheduler
  • Cloud Storage
  • Cloud Firestore

2. Configuração e requisitos

Configuração de ambiente autoguiada

  1. Faça login no Console do Google Cloud e crie um novo projeto ou reutilize um existente. Crie uma conta do Gmail ou do Google Workspace, se ainda não tiver uma.

96a9c957bc475304.png

b9a10ebdf5b5a448.png

a1e3c01a38fa61c2.png

  • O Nome do projeto é o nome de exibição para os participantes do projeto. Ele é uma string de caracteres que não é usada pelas APIs do Google e pode ser atualizada a qualquer momento.
  • O ID do projeto precisa ser exclusivo em todos os projetos do Google Cloud e não pode ser alterado após a definição. O Console do Cloud gera automaticamente uma string única, geralmente não importa o que seja. Na maioria dos codelabs, você precisará fazer referência ao ID do projeto, que geralmente é identificado como PROJECT_ID. Então, se você não gostar dele, gere outro ID aleatório ou crie um próprio e veja se ele está disponível. Em seguida, ele fica "congelado" depois que o projeto é criado.
  • Há um terceiro valor, um Número de projeto, que algumas APIs usam. Saiba mais sobre esses três valores na documentação.
  1. Em seguida, você precisará ativar o faturamento no Console do Cloud para usar os recursos/APIs do Cloud. A execução deste codelab não será muito cara, se tiver algum custo. Para encerrar os recursos e não gerar cobranças além deste tutorial, siga as instruções de "limpeza" encontradas no final do codelab. Novos usuários do Google Cloud estão qualificados para o programa de US$ 300 de avaliação sem custos.

Inicie o Cloud Shell

Embora o Google Cloud e o Spanner possam ser operados remotamente do seu laptop, neste codelab usaremos o Google Cloud Shell, um ambiente de linha de comando executado no Cloud.

No Console do GCP, clique no ícone do Cloud Shell na barra de ferramentas localizada no canto superior direito:

bce75f34b2c53987.png

O provisionamento e a conexão com o ambiente levarão apenas alguns instantes para serem concluídos: Quando o processamento for concluído, você verá algo como:

f6ef2b5f13479f3a.png

Essa máquina virtual contém 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. Todo o trabalho neste laboratório pode ser feito apenas com um navegador.

3. Ativar APIs

Você vai precisar de um Cloud Scheduler para acionar o serviço do Cloud Run em um intervalo regular. Verifique se ele está ativado:

gcloud services enable cloudscheduler.googleapis.com

Você verá que a operação será concluída com sucesso:

Operation "operations/acf.5c5ef4f6-f734-455d-b2f0-ee70b5a17322" finished successfully.

4. Clonar o código

Clone o código, caso ainda não tenha feito isso no codelab anterior:

git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop

Acesse o diretório que contém o serviço:

cd serverless-photosharing-workshop/services/collage/nodejs

Você terá o seguinte layout de arquivo para o serviço:

services
 |
 ├── collage
      |
      ├── nodejs
           |
           ├── Dockerfile
           ├── index.js
           ├── package.json

Dentro da pasta, você tem três arquivos:

  • index.js contém o código Node.js.
  • package.json define as dependências da biblioteca.
  • Dockerfile define a imagem do contêiner.

5. Explorar o código

Dependências

O arquivo package.json define as dependências de biblioteca necessárias:

{
  "name": "collage_service",
  "version": "0.0.1",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "bluebird": "^3.7.2",
    "express": "^4.17.1",
    "imagemagick": "^0.1.3",
    "@google-cloud/firestore": "^4.9.9",
    "@google-cloud/storage": "^5.8.3"
  }
}

Nós dependemos da biblioteca do Cloud Storage para ler e salvar arquivos de imagem no Cloud Storage. Declaramos uma dependência no Cloud Firestore para buscar metadados de imagens armazenados anteriormente. Express é um framework da Web JavaScript / Node. O Bluebird é usado para processar promessas e o Imagemagick é uma biblioteca para manipular imagens.

Dockerfile

Dockerfile define a imagem do contêiner para o aplicativo:

FROM node:14-slim

# installing Imagemagick
RUN set -ex; \
  apt-get -y update; \
  apt-get -y install imagemagick; \
  rm -rf /var/lib/apt/lists/*

WORKDIR /picadaily/services/collage
COPY package*.json ./
RUN npm install --production
COPY . .
CMD [ "npm", "start" ]

Estamos usando uma imagem de base clara do Node 14. A biblioteca imagemagick está sendo instalada. Em seguida, instalamos os módulos NPM necessários para nosso código e executamos o código do nó com npm start.

index.js

Vamos analisar melhor o código index.js:

const express = require('express');
const imageMagick = require('imagemagick');
const Promise = require("bluebird");
const path = require('path');
const {Storage} = require('@google-cloud/storage');
const Firestore = require('@google-cloud/firestore');

Precisamos das várias dependências necessárias para que nosso programa seja executado: Express é o framework da Web do Node que vamos usar, ImageMagick, a biblioteca para fazer manipulação de imagens, Bluebird é uma biblioteca para lidar com promessas de JavaScript, Path é usado para lidar com caminhos de arquivos e diretórios e, em seguida, Storage e Firestore servem para trabalhar respectivamente com o Google Cloud Storage (nossos buckets de imagens) e o repositório de dados do Cloud Firestore.

const app = express();

app.get('/', async (req, res) => {
    try {
        console.log('Collage request');

        /* ... */

    } catch (err) {
        console.log(`Error: creating the collage: ${err}`);
        console.error(err);
        res.status(500).send(err);
    }
});

Acima, temos a estrutura do nosso gerenciador de nós: nosso aplicativo responde a solicitações HTTP GET. Também estamos fazendo alguns ajustes para o caso de algo dar errado. Agora, vamos ver o que há dentro dessa estrutura.

const thumbnailFiles = [];
const pictureStore = new Firestore().collection('pictures');
const snapshot = await pictureStore
    .where('thumbnail', '==', true)
    .orderBy('created', 'desc')
    .limit(4).get();

if (snapshot.empty) {
    console.log('Empty collection, no collage to make');
    res.status(204).send("No collage created.");
} else {

    /* ... */

}

Nosso serviço de colagem precisará de pelo menos quatro fotos (cujas miniaturas foram geradas). Portanto, certifique-se de carregar quatro imagens primeiro.

Recuperamos as quatro últimas fotos enviadas pelos nossos usuários dos metadados armazenados no Cloud Firerstore. Verificamos se a coleção resultante está vazia ou não e, em seguida, prosseguimos na ramificação else do código.

Vamos coletar a lista de nomes de arquivos:

snapshot.forEach(doc => {
    thumbnailFiles.push(doc.id);
});
console.log(`Picture file names: ${JSON.stringify(thumbnailFiles)}`);

Vamos fazer o download de cada um desses arquivos do bucket de miniaturas, cujo nome vem de uma variável de ambiente definida no momento da implantação:

const thumbBucket = storage.bucket(process.env.BUCKET_THUMBNAILS);

await Promise.all(thumbnailFiles.map(async fileName => {
    const filePath = path.resolve('/tmp', fileName);
    await thumbBucket.file(fileName).download({
        destination: filePath
    });
}));
console.log('Downloaded all thumbnails');

Quando as miniaturas mais recentes forem enviadas, usaremos a biblioteca do ImageMagick para criar uma grade 4x4 dessas imagens em miniatura. Usamos a biblioteca Bluebird e a implementação dela para transformar o código orientado por callback em código compatível com async / await. Depois, aguardamos a promessa que está fazendo a colagem da imagem:

const collagePath = path.resolve('/tmp', 'collage.png');

const thumbnailPaths = thumbnailFiles.map(f => path.resolve('/tmp', f));
const convert = Promise.promisify(im.convert);
await convert([
    '(', ...thumbnailPaths.slice(0, 2), '+append', ')',
    '(', ...thumbnailPaths.slice(2), '+append', ')',
    '-size', '400x400', 'xc:none', '-background', 'none',  '-append',
    collagePath]);
console.log("Created local collage picture");

Como a imagem da colagem foi salva em disco localmente na pasta temporária, precisamos fazer upload dela para o Cloud Storage e retornar uma resposta bem-sucedida (código de status 2xx):

await thumbBucket.upload(collagePath);
console.log("Uploaded collage to Cloud Storage bucket ${process.env.BUCKET_THUMBNAILS}");

res.status(204).send("Collage created.");

Agora é hora de fazer o script do Node detectar as solicitações recebidas:

const PORT = process.env.PORT || 8080;

app.listen(PORT, () => {
    console.log(`Started collage service on port ${PORT}`);
});

No final do nosso arquivo de origem, temos as instruções para que o Express inicie nosso aplicativo da Web na porta padrão 8080.

6. Testar localmente

Teste o código localmente para garantir que ele funciona antes de implantá-lo na nuvem.

Dentro da pasta collage/nodejs, instale as dependências de npm e inicie o servidor:

npm install; npm start

Se tudo tiver dado certo, ele deverá iniciar o servidor na porta 8080:

Started collage service on port 8080

Use CTRL-C para sair.

7. Criar e implantar no Cloud Run

Antes de implantar no Cloud Run, defina a região do Cloud Run como uma das regiões e plataformas compatíveis como managed:

gcloud config set run/region europe-west1
gcloud config set run/platform managed

Verifique se a configuração foi definida:

gcloud config list

...
[run]
platform = managed
region = europe-west1

Em vez de criar e publicar a imagem do contêiner usando o Cloud Build manualmente, também é possível confiar no Cloud Run para criar a imagem do contêiner usando o Google Cloud Buildpacks.

Execute o seguinte comando para criar a imagem do contêiner:

BUCKET_THUMBNAILS=thumbnails-$GOOGLE_CLOUD_PROJECT
SERVICE_NAME=collage-service
gcloud run deploy $SERVICE_NAME \
    --source . \
    --no-allow-unauthenticated \
    --update-env-vars BUCKET_THUMBNAILS=$BUCKET_THUMBNAILS

Observe a sinalização –-source. Esta é a implantação baseada na origem no Cloud Run. Se um Dockerfile estiver presente no diretório do código-fonte, o código-fonte enviado será criado usando esse Dockerfile. Se não houver Dockerfile no diretório do código-fonte, os buildpacks do Google Cloud detectarão automaticamente a linguagem que você está usando e buscarão as dependências do código para criar uma imagem de contêiner pronta para produção, usando uma imagem de base segura gerenciada pelo Google. Isso sinaliza o Cloud Run para usar Google Cloud Buildpacks para criar a imagem de contêiner definida em Dockerfile.

A implantação baseada em origem usa o Artifact Registry para armazenar contêineres criados. O Artifact Registry é uma versão moderna do Google Container Registry. A CLI solicitará a ativação da API se ela ainda não estiver ativada no projeto e criará um repositório com o nome cloud-run-source-deploy na região em que você está implantando.

A flag --no-allow-unauthenticated torna o serviço do Cloud Run um serviço interno que só será acionado por contas de serviço específicas.

8. Configurar o Cloud Scheduler

Agora que o serviço do Cloud Run está pronto e implantado, é hora de criar a programação regular para invocar o serviço a cada minuto.

Crie uma conta de serviço:

SERVICE_ACCOUNT=collage-scheduler-sa
gcloud iam service-accounts create $SERVICE_ACCOUNT \
   --display-name "Collage Scheduler Service Account"

Conceda permissão à conta de serviço para invocar o serviço do Cloud Run:

gcloud run services add-iam-policy-binding $SERVICE_NAME \
   --member=serviceAccount:$SERVICE_ACCOUNT@$GOOGLE_CLOUD_PROJECT.iam.gserviceaccount.com \
   --role=roles/run.invoker

Crie um job do Cloud Scheduler para ser executado a cada 1 minuto:

SERVICE_URL=$(gcloud run services describe $SERVICE_NAME --format 'value(status.url)')
gcloud scheduler jobs create http $SERVICE_NAME-job --schedule "* * * * *" \
   --http-method=GET \
   --location=europe-west1 \
   --uri=$SERVICE_URL \
   --oidc-service-account-email=$SERVICE_ACCOUNT@$GOOGLE_CLOUD_PROJECT.iam.gserviceaccount.com \
   --oidc-token-audience=$SERVICE_URL

Acesse a seção do Cloud Scheduler no console do Cloud para verificar se ele está configurado e apontando para o URL do serviço do Cloud Run:

35119e28c1da53f3.png

9. Testar o serviço

Para testar se a configuração está funcionando, procure a imagem de colagem no bucket thumbnails (chamado collage.png). Também é possível verificar os registros do serviço:

93922335a384be2e.png

10. Limpeza (opcional)

Se você não pretende continuar com os outros laboratórios da série, limpe os recursos para economizar custos e ser um bom cidadão da nuvem. É possível limpar recursos individualmente da seguinte maneira.

Exclua o serviço:

gcloud run services delete $SERVICE_NAME -q

Exclua o job do Cloud Scheduler:

gcloud scheduler jobs delete $SERVICE_NAME-job -q

Se preferir, exclua todo o projeto:

gcloud projects delete $GOOGLE_CLOUD_PROJECT

11. Parabéns!

Parabéns! Você criou um serviço programado: graças ao Cloud Scheduler, que envia uma mensagem a cada minuto em um tópico do Pub/Sub, seu serviço de colagem do Cloud Run é invocado e pode anexar imagens para criar a imagem resultante.

O que vimos

  • Cloud Run
  • Cloud Scheduler
  • Cloud Storage
  • Cloud Firestore

Próximas etapas