Pic-a-daily: laboratório 1: armazenar e analisar imagens (Java nativo)
Sobre este codelab
1. Visão geral
No primeiro codelab, você vai armazenar imagens em um bucket. Isso vai gerar um evento de criação de arquivo que será processado por um serviço implantado no Cloud Run. O serviço chamará a API Vision para fazer a análise de imagens e salvar os resultados em um repositório de dados.
O que você vai aprender
- Cloud Storage
- Cloud Run
- API Cloud Vision
- Cloud Firestore
2. Configuração e requisitos
Configuração de ambiente autoguiada
- 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.
- O Nome do projeto é o nome de exibição para os participantes do projeto. É uma string de caracteres não usada pelas APIs do Google e pode ser atualizada quando você quiser.
- O ID do projeto precisa ser exclusivo em todos os projetos do Google Cloud e não pode ser mudado após a definição. O console do Cloud gera automaticamente uma string exclusiva. Em geral, não importa o que seja. Na maioria dos codelabs, é necessário fazer referência ao ID do projeto, normalmente identificado como
PROJECT_ID
. Se você não gostar do ID gerado, crie outro aleatório. Se preferir, teste o seu e confira se ele está disponível. Ele não pode ser mudado após essa etapa e permanece durante o projeto. - Para sua informação, há um terceiro valor, um Número do projeto, que algumas APIs usam. Saiba mais sobre esses três valores na documentação.
- Em seguida, ative o faturamento no console do Cloud para usar os recursos/APIs do Cloud. A execução deste codelab não vai ser muito cara, se tiver algum custo. Para encerrar os recursos e evitar cobranças além deste tutorial, exclua os recursos criados ou exclua o projeto. 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 Google Cloud, clique no ícone do Cloud Shell na barra de ferramentas superior à direita:
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:
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. Neste codelab, todo o trabalho pode ser feito com um navegador. Você não precisa instalar nada.
3. Ativar APIs
Neste laboratório, você usará o Cloud Functions e a API Vision, mas primeiro eles precisam ser ativados no console do Cloud ou com o gcloud
.
Para ativar a API Vision no Console do Cloud, procure Cloud Vision API
na barra de pesquisa:
Você será direcionado à página da API Cloud Vision:
Clique no botão ENABLE
.
Se preferir, ative o Cloud Shell usando a ferramenta de linha de comando gcloud.
No Cloud Shell, execute o seguinte comando:
gcloud services enable vision.googleapis.com
Você verá que a operação será concluída com sucesso:
Operation "operations/acf.12dba18b-106f-4fd2-942d-fea80ecc5c1c" finished successfully.
Ative também o Cloud Run e o Cloud Build:
gcloud services enable cloudbuild.googleapis.com \ run.googleapis.com
4. Criar o bucket (console)
Criar um bucket de armazenamento para as imagens. Faça isso no console do Google Cloud Platform ( console.cloud.google.com) ou com a ferramenta de linha de comando gsutil do Cloud Shell ou seu ambiente de desenvolvimento local.
Acesse "Armazenamento"
No menu "hambúrguer", (☰) menu, navegue até a página Storage
.
Nomeie seu bucket
Clique no botão CREATE BUCKET
.
Clique em CONTINUE
.
Escolher local
Crie um bucket multirregional na região de sua escolha (aqui Europe
).
Clique em CONTINUE
.
Escolher a classe de armazenamento padrão
Escolha a classe de armazenamento Standard
para seus dados.
Clique em CONTINUE
.
Definir controle de acesso
Como você vai trabalhar com imagens acessíveis publicamente, o ideal é que todas as nossas imagens armazenadas no bucket tenham o mesmo controle de acesso uniforme.
Escolha a opção de controle de acesso Uniform
.
Clique em CONTINUE
.
Definir proteção/criptografia
Mantenha o padrão (Google-managed key)
, já que você não vai usar suas próprias chaves de criptografia.
Clique em CREATE
para finalizar a criação do bucket.
Adicionar allUsers como leitor do armazenamento
Acesse a guia Permissions
:
Adicione um membro allUsers
ao bucket, com um papel de Storage > Storage Object Viewer
, da seguinte maneira:
Clique em SAVE
.
5. Criar o bucket (gsutil)
Também é possível usar a ferramenta de linha de comando gsutil
no Cloud Shell para criar buckets.
No Cloud Shell, defina uma variável para o nome exclusivo do bucket. O Cloud Shell já tem GOOGLE_CLOUD_PROJECT
definido como seu ID do projeto exclusivo. É possível anexar isso ao nome do bucket.
Exemplo:
export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}
Crie uma zona multirregional padrão na Europa:
gsutil mb -l EU gs://${BUCKET_PICTURES}
Garanta acesso uniforme no nível do bucket:
gsutil uniformbucketlevelaccess set on gs://${BUCKET_PICTURES}
Torne o bucket público:
gsutil iam ch allUsers:objectViewer gs://${BUCKET_PICTURES}
Se você acessar a seção Cloud Storage
do console, terá um bucket uploaded-pictures
público:
Verifique se é possível fazer upload das imagens para o bucket e se elas estão disponíveis publicamente, conforme explicado na etapa anterior.
6. Testar o acesso público ao bucket
De volta ao navegador do Storage, você verá seu bucket na lista, com "Público" acesso (incluindo um sinal de aviso para lembrar que qualquer pessoa tem acesso ao conteúdo desse bucket).
Seu bucket está pronto para receber imagens.
Se clicar no nome do bucket, você verá os detalhes dele.
Lá, você pode usar o botão Upload files
para testar se é possível adicionar uma imagem ao bucket. Um pop-up do seletor de arquivos solicitará que você escolha um arquivo. Depois de selecionado, ele será enviado para seu bucket, e você verá novamente o acesso public
que foi atribuído automaticamente a esse novo arquivo.
Junto ao marcador de acesso Public
, você também verá um pequeno ícone de link. Ao clicar nela, o navegador navegará para o URL público dessa imagem, que terá o seguinte formato:
https://storage.googleapis.com/BUCKET_NAME/PICTURE_FILE.png
No qual BUCKET_NAME
é o nome globalmente exclusivo escolhido para o bucket e depois o nome do arquivo da imagem.
Ao clicar na caixa de seleção ao lado do nome da imagem, o botão DELETE
será ativado e você poderá excluir esta primeira imagem.
7. Preparar o banco de dados
Você vai armazenar informações sobre a imagem fornecida pela API Vision no banco de dados do Cloud Firestore, um banco de dados de documentos NoSQL rápido, totalmente gerenciado, sem servidor e nativo da nuvem. Prepare o banco de dados acessando a seção Firestore
do Console do Cloud:
Há duas opções disponíveis: Native mode
ou Datastore mode
. Use o modo nativo, que oferece recursos extras como suporte off-line e sincronização em tempo real.
Clique em SELECT NATIVE MODE
.
Escolha um local multirregional (aqui na Europa, mas idealmente pelo menos a mesma região da função e do bucket de armazenamento).
Clique no botão CREATE DATABASE
.
Depois que o banco de dados for criado, você verá o seguinte:
Crie uma nova coleção clicando no botão + START COLLECTION
.
Conjunto de nomes pictures
.
Não é necessário criar um documento. Você as adicionará programaticamente à medida que novas imagens forem armazenadas no Cloud Storage e analisadas pela API Vision.
Clique em Save
.
O Firestore cria um primeiro documento padrão na coleção recém-criada. É possível excluir esse documento com segurança, já que ele não contém informações úteis:
Os documentos que serão criados programaticamente na nossa coleção terão quatro campos:
- name (string): o nome do arquivo da imagem enviada, que também é a chave do documento.
- labels (matriz de strings): os rótulos dos itens reconhecidos pela API Vision
- color (string): o código de cor hexadecimal da cor dominante (ou seja, #ab12ef).
- created (data): o carimbo de data/hora de quando os metadados da imagem foram armazenados
- miniatura (booleano): um campo opcional que vai estar presente e ser verdadeiro se uma imagem em miniatura tiver sido gerada para a imagem
Como vamos pesquisar no Firestore para encontrar imagens que tenham miniaturas disponíveis e classificar ao longo da data de criação, precisaremos criar um índice de pesquisa.
É possível criar o índice com o seguinte comando no Cloud Shell:
gcloud firestore indexes composite create \
--collection-group=pictures \
--field-config field-path=thumbnail,order=descending \
--field-config field-path=created,order=descending
Também é possível fazer isso no Console do Cloud, clicando em Indexes
na coluna de navegação à esquerda e criando um índice composto, conforme mostrado abaixo:
Clique em Create
. A criação do índice pode levar alguns minutos.
8. 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 para começar a criar o laboratório:
cd serverless-photosharing-workshop/services/image-analysis/java
Você terá o seguinte layout de arquivo para o serviço:
9. conheça o código de serviço
Para começar, veja como as bibliotecas de cliente do Java são ativadas no pom.xml
usando uma BoM:
Primeiro, abra o arquivo pom.xml
, que lista as dependências do app Java. vamos focar no uso das APIs Vision, Cloud Storage e Firestore
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0-M3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>services</groupId>
<artifactId>image-analysis</artifactId>
<version>0.0.1</version>
<name>image-analysis</name>
<description>Spring App for Image Analysis</description>
<properties>
<java.version>17</java.version>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
<spring-cloud.version>2023.0.0-M2</spring-cloud.version>
<testcontainers.version>1.19.1</testcontainers.version>
</properties>
...
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>libraries-bom</artifactId>
<version>26.24.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
—
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-web</artifactId>
</dependency>
<dependency>
<groupId>com.google.cloud.functions</groupId>
<artifactId>functions-framework-api</artifactId>
<version>1.1.0</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-firestore</artifactId>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-vision</artifactId>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-storage</artifactId>
</dependency>
A funcionalidade é implementada na classe EventController
. Sempre que uma nova imagem é carregada no bucket, o serviço recebe uma notificação para processar:
@RestController
public class EventController {
private static final Logger logger = Logger.getLogger(EventController.class.getName());
private static final List<String> requiredFields = Arrays.asList("ce-id", "ce-source", "ce-type", "ce-specversion");
@RequestMapping(value = "/", method = RequestMethod.POST)
public ResponseEntity<String> receiveMessage(
@RequestBody Map<String, Object> body, @RequestHeader Map<String, String> headers) throws IOException, InterruptedException, ExecutionException {
...
}
O código vai continuar para validar os cabeçalhos Cloud Events
:
System.out.println("Header elements");
for (String field : requiredFields) {
if (headers.get(field) == null) {
String msg = String.format("Missing expected header: %s.", field);
System.out.println(msg);
return new ResponseEntity<String>(msg, HttpStatus.BAD_REQUEST);
} else {
System.out.println(field + " : " + headers.get(field));
}
}
System.out.println("Body elements");
for (String bodyField : body.keySet()) {
System.out.println(bodyField + " : " + body.get(bodyField));
}
if (headers.get("ce-subject") == null) {
String msg = "Missing expected header: ce-subject.";
System.out.println(msg);
return new ResponseEntity<String>(msg, HttpStatus.BAD_REQUEST);
}
Agora, uma solicitação pode ser criada, e o código vai preparar uma solicitação para ser enviada ao Vision API
:
try (ImageAnnotatorClient vision = ImageAnnotatorClient.create()) {
List<AnnotateImageRequest> requests = new ArrayList<>();
ImageSource imageSource = ImageSource.newBuilder()
.setGcsImageUri("gs://" + bucketName + "/" + fileName)
.build();
Image image = Image.newBuilder()
.setSource(imageSource)
.build();
Feature featureLabel = Feature.newBuilder()
.setType(Type.LABEL_DETECTION)
.build();
Feature featureImageProps = Feature.newBuilder()
.setType(Type.IMAGE_PROPERTIES)
.build();
Feature featureSafeSearch = Feature.newBuilder()
.setType(Type.SAFE_SEARCH_DETECTION)
.build();
AnnotateImageRequest request = AnnotateImageRequest.newBuilder()
.addFeatures(featureLabel)
.addFeatures(featureImageProps)
.addFeatures(featureSafeSearch)
.setImage(image)
.build();
requests.add(request);
Estamos solicitando três recursos principais da API Vision:
- Detecção de rótulos: para entender o que aparece nessas fotos.
- Propriedades de imagem: para fornecer atributos interessantes da imagem (estamos interessados na cor dominante da imagem)
- Pesquisa segura: para saber se a imagem é segura para exibição (ela não deve conter conteúdo adulto / médico / potencialmente ofensivo / violento)
Agora, podemos fazer a chamada para a API Vision:
...
logger.info("Calling the Vision API...");
BatchAnnotateImagesResponse result = vision.batchAnnotateImages(requests);
List<AnnotateImageResponse> responses = result.getResponsesList();
...
Como referência, confira a resposta da API Vision:
{
"faceAnnotations": [],
"landmarkAnnotations": [],
"logoAnnotations": [],
"labelAnnotations": [
{
"locations": [],
"properties": [],
"mid": "/m/01yrx",
"locale": "",
"description": "Cat",
"score": 0.9959855675697327,
"confidence": 0,
"topicality": 0.9959855675697327,
"boundingPoly": null
},
✄ - - - ✄
],
"textAnnotations": [],
"localizedObjectAnnotations": [],
"safeSearchAnnotation": {
"adult": "VERY_UNLIKELY",
"spoof": "UNLIKELY",
"medical": "VERY_UNLIKELY",
"violence": "VERY_UNLIKELY",
"racy": "VERY_UNLIKELY",
"adultConfidence": 0,
"spoofConfidence": 0,
"medicalConfidence": 0,
"violenceConfidence": 0,
"racyConfidence": 0,
"nsfwConfidence": 0
},
"imagePropertiesAnnotation": {
"dominantColors": {
"colors": [
{
"color": {
"red": 203,
"green": 201,
"blue": 201,
"alpha": null
},
"score": 0.4175916016101837,
"pixelFraction": 0.44456374645233154
},
✄ - - - ✄
]
}
},
"error": null,
"cropHintsAnnotation": {
"cropHints": [
{
"boundingPoly": {
"vertices": [
{ "x": 0, "y": 118 },
{ "x": 1177, "y": 118 },
{ "x": 1177, "y": 783 },
{ "x": 0, "y": 783 }
],
"normalizedVertices": []
},
"confidence": 0.41695669293403625,
"importanceFraction": 1
}
]
},
"fullTextAnnotation": null,
"webDetection": null,
"productSearchResults": null,
"context": null
}
Se nenhum erro for retornado, podemos prosseguir. É por isso que temos este bloco "if":
if (responses.size() == 0) {
logger.info("No response received from Vision API.");
return new ResponseEntity<String>(msg, HttpStatus.BAD_REQUEST);
}
AnnotateImageResponse response = responses.get(0);
if (response.hasError()) {
logger.info("Error: " + response.getError().getMessage());
return new ResponseEntity<String>(msg, HttpStatus.BAD_REQUEST);
}
Vamos obter os rótulos das coisas, categorias ou temas reconhecidos na imagem:
List<String> labels = response.getLabelAnnotationsList().stream()
.map(annotation -> annotation.getDescription())
.collect(Collectors.toList());
logger.info("Annotations found:");
for (String label: labels) {
logger.info("- " + label);
}
Estamos interessados em saber a cor dominante da imagem:
String mainColor = "#FFFFFF";
ImageProperties imgProps = response.getImagePropertiesAnnotation();
if (imgProps.hasDominantColors()) {
DominantColorsAnnotation colorsAnn = imgProps.getDominantColors();
ColorInfo colorInfo = colorsAnn.getColors(0);
mainColor = rgbHex(
colorInfo.getColor().getRed(),
colorInfo.getColor().getGreen(),
colorInfo.getColor().getBlue());
logger.info("Color: " + mainColor);
}
Vamos verificar se a imagem pode ser exibida:
boolean isSafe = false;
if (response.hasSafeSearchAnnotation()) {
SafeSearchAnnotation safeSearch = response.getSafeSearchAnnotation();
isSafe = Stream.of(
safeSearch.getAdult(), safeSearch.getMedical(), safeSearch.getRacy(),
safeSearch.getSpoof(), safeSearch.getViolence())
.allMatch( likelihood ->
likelihood != Likelihood.LIKELY && likelihood != Likelihood.VERY_LIKELY
);
logger.info("Safe? " + isSafe);
}
Estamos verificando as características de conteúdo adulto / paródia / médica / violência / conteúdo potencialmente ofensivo para ver se elas são prováveis ou muito prováveis.
Se o resultado da pesquisa segura estiver correto, poderemos armazenar os metadados no Firestore:
// Saving result to Firestore
if (isSafe) {
ApiFuture<WriteResult> writeResult =
eventService.storeImage(fileName, labels,
mainColor);
logger.info("Picture metadata saved in Firestore at " +
writeResult.get().getUpdateTime());
}
...
public ApiFuture<WriteResult> storeImage(String fileName,
List<String> labels,
String mainColor) {
FirestoreOptions firestoreOptions = FirestoreOptions.getDefaultInstance();
Firestore pictureStore = firestoreOptions.getService();
DocumentReference doc = pictureStore.collection("pictures").document(fileName);
Map<String, Object> data = new HashMap<>();
data.put("labels", labels);
data.put("color", mainColor);
data.put("created", new Date());
return doc.set(data, SetOptions.merge());
}
10. Criar imagens de apps com o GraalVM
Nesta etapa opcional, você vai criar um JIT based app image
e depois um Native Java app image
usando o GraalVM.
Para executar a compilação, será necessário garantir que você tenha um JDK apropriado e o criador de imagens nativas instalados e configurados. Há várias opções disponíveis.
To start
, faça o download do GraalVM 22.3.x Community Edition e siga as instruções na página de instalação do GraalVM.
Esse processo pode ser bastante simplificado com a ajuda do SDKMAN!
Para instalar a distribuição JDK adequada com SDKman
, comece usando o comando de instalação:
sdk install java 17.0.8-graal
Instrua o SDKman a usar essa versão para builds JIT e AOT:
sdk use java 17.0.8-graal
No Cloudshell
, para facilitar, instale o GraalVM e o utilitário de imagem nativa com estes comandos simples:
# download GraalVM wget https://download.oracle.com/graalvm/17/latest/graalvm-jdk-17_linux-x64_bin.tar.gz tar -xzf graalvm-jdk-17_linux-x64_bin.tar.gz ls -lart # configure Java 17 and GraalVM for Java 17 # note the name of the latest GraalVM version, as unpacked by the tar command echo Existing JVM: $JAVA_HOME cd graalvm-jdk-17.0.8+9.1 export JAVA_HOME=$PWD cd bin export PATH=$PWD:$PATH echo JAVA HOME: $JAVA_HOME echo PATH: $PATH cd ../.. # validate the version with java -version # observe Java(TM) SE Runtime Environment Oracle GraalVM 17.0.8+9.1 (build 17.0.8+9-LTS-jvmci-23.0-b14) Java HotSpot(TM) 64-Bit Server VM Oracle GraalVM 17.0.8+9.1 (build 17.0.8+9-LTS-jvmci-23.0-b14, mixed mode, sharing)
Primeiro defina as variáveis de ambiente do projeto do GCP:
export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
Acesse o diretório que contém o serviço para começar a criar o laboratório:
cd serverless-photosharing-workshop/services/image-analysis/java
Crie a imagem do aplicativo JIT:
./mvnw package
Observe o registro do build no terminal:
... [INFO] Results: [INFO] [INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] [INFO] --- maven-jar-plugin:3.3.0:jar (default-jar) @ image-analysis --- [INFO] Building jar: /home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis-0.0.1.jar [INFO] [INFO] --- spring-boot-maven-plugin:3.2.0-M3:repackage (repackage) @ image-analysis --- [INFO] Replacing main artifact /home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis-0.0.1.jar with repackaged archive, adding nested dependencies in BOOT-INF/. [INFO] The original artifact has been renamed to /home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis-0.0.1.jar.original [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 15.335 s [INFO] Finished at: 2023-10-10T19:33:25Z [INFO] ------------------------------------------------------------------------
Crie a imagem nativa(usa AOT):
./mvnw native:compile -Pnative
Observe o registro do build no terminal, incluindo os registros de build da imagem nativa:
Observe que o build demora um pouco mais, dependendo da máquina em que você está testando.
... [2/7] Performing analysis... [*********] (124.5s @ 4.53GB) 29,732 (93.19%) of 31,905 classes reachable 60,161 (70.30%) of 85,577 fields reachable 261,973 (67.29%) of 389,319 methods reachable 2,940 classes, 2,297 fields, and 97,421 methods registered for reflection 81 classes, 90 fields, and 62 methods registered for JNI access 4 native libraries: dl, pthread, rt, z [3/7] Building universe... (11.7s @ 4.67GB) [4/7] Parsing methods... [***] (6.1s @ 5.91GB) [5/7] Inlining methods... [****] (4.5s @ 4.39GB) [6/7] Compiling methods... [******] (35.3s @ 4.60GB) [7/7] Creating image... (12.9s @ 4.61GB) 80.08MB (47.43%) for code area: 190,483 compilation units 73.81MB (43.72%) for image heap: 660,125 objects and 189 resources 14.95MB ( 8.86%) for other data 168.84MB in total ------------------------------------------------------------------------------------------------------------------------ Top 10 packages in code area: Top 10 object types in image heap: 2.66MB com.google.cloud.vision.v1p4beta1 18.51MB byte[] for code metadata 2.60MB com.google.cloud.vision.v1 9.27MB java.lang.Class 2.49MB com.google.protobuf 7.34MB byte[] for reflection metadata 2.40MB com.google.cloud.vision.v1p3beta1 6.35MB byte[] for java.lang.String 2.17MB com.google.storage.v2 5.72MB java.lang.String 2.12MB com.google.firestore.v1 4.46MB byte[] for embedded resources 1.64MB sun.security.ssl 4.30MB c.oracle.svm.core.reflect.SubstrateMethodAccessor 1.51MB i.g.xds.shaded.io.envoyproxy.envoy.config.core.v3 4.27MB byte[] for general heap data 1.47MB com.google.cloud.vision.v1p2beta1 2.50MB com.oracle.svm.core.hub.DynamicHubCompanion 1.34MB i.g.x.shaded.io.envoyproxy.envoy.config.route.v3 1.17MB java.lang.Object[] 58.34MB for 977 more packages 9.19MB for 4667 more object types ------------------------------------------------------------------------------------------------------------------------ 13.5s (5.7% of total time) in 75 GCs | Peak RSS: 9.44GB | CPU load: 6.13 ------------------------------------------------------------------------------------------------------------------------ Produced artifacts: /home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis (executable) /home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis.build_artifacts.txt (txt) ======================================================================================================================== Finished generating '/home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis' in 3m 57s. [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 04:28 min [INFO] Finished at: 2023-10-10T19:53:30Z [INFO] ------------------------------------------------------------------------
11. Criar e publicar imagens de contêiner
Vamos criar uma imagem de contêiner em duas versões diferentes: uma como JIT image
e outra como Native Java image
.
Primeiro defina as variáveis de ambiente do projeto do GCP:
export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
Crie a imagem JIT:
./mvnw spring-boot:build-image -Pji
Observe o registro do build no terminal:
[INFO] [creator] Timer: Saving docker.io/library/image-analysis-maven-jit:latest... started at 2023-10-10T20:00:31Z [INFO] [creator] *** Images (4c84122a1826): [INFO] [creator] docker.io/library/image-analysis-maven-jit:latest [INFO] [creator] Timer: Saving docker.io/library/image-analysis-maven-jit:latest... ran for 6.975913605s and ended at 2023-10-10T20:00:38Z [INFO] [creator] Timer: Exporter ran for 8.068588001s and ended at 2023-10-10T20:00:38Z [INFO] [creator] Timer: Cache started at 2023-10-10T20:00:38Z [INFO] [creator] Reusing cache layer 'paketo-buildpacks/syft:syft' [INFO] [creator] Adding cache layer 'buildpacksio/lifecycle:cache.sbom' [INFO] [creator] Timer: Cache ran for 200.449002ms and ended at 2023-10-10T20:00:38Z [INFO] [INFO] Successfully built image 'docker.io/library/image-analysis-maven-jit:latest' [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 43.887 s [INFO] Finished at: 2023-10-10T20:00:39Z [INFO] ------------------------------------------------------------------------
Crie a imagem AOT(nativa):
./mvnw spring-boot:build-image -Pnative
Observe o registro do build no terminal, incluindo os registros de build da imagem nativa.
Observação:
- que o build demora um pouco mais, dependendo da máquina em que você está testando
- as imagens podem ser comprimidas ainda mais com UPX, mas têm um pequeno impacto negativo no desempenho da inicialização, portanto esta versão não usa UPX - é sempre uma pequena compensação
... [INFO] [creator] Saving docker.io/library/image-analysis-maven-native:latest... [INFO] [creator] *** Images (13167702674e): [INFO] [creator] docker.io/library/image-analysis-maven-native:latest [INFO] [creator] Adding cache layer 'paketo-buildpacks/bellsoft-liberica:native-image-svm' [INFO] [creator] Adding cache layer 'paketo-buildpacks/syft:syft' [INFO] [creator] Adding cache layer 'paketo-buildpacks/native-image:native-image' [INFO] [creator] Adding cache layer 'buildpacksio/lifecycle:cache.sbom' [INFO] [INFO] Successfully built image 'docker.io/library/image-analysis-maven-native:latest' [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 03:37 min [INFO] Finished at: 2023-10-10T20:05:16Z [INFO] ------------------------------------------------------------------------
Valide se as imagens foram criadas:
docker images | grep image-analysis
Marque e envie as duas imagens ao GCR:
# JIT image docker tag image-analysis-maven-jit gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-jit docker push gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-jit # Native(AOT) image docker tag image-analysis-maven-native gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-native docker push gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-native
12. Implantar no Cloud Run
Hora de implantar o serviço.
Você implantará o serviço duas vezes: uma com a imagem JIT e a segunda com a imagem AOT(nativa). As duas implantações de serviço processarão a mesma imagem do bucket em paralelo, para fins de comparação.
Primeiro defina as variáveis de ambiente do projeto do GCP:
export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project) gcloud config set project ${GOOGLE_CLOUD_PROJECT} gcloud config set run/region gcloud config set run/platform managed gcloud config set eventarc/location europe-west1
Implante a imagem JIT e observe o registro de implantação no console:
gcloud run deploy image-analysis-jit \ --image gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-jit \ --region europe-west1 \ --memory 2Gi --allow-unauthenticated ... Deploying container to Cloud Run service [image-analysis-jit] in project [...] region [europe-west1] ✓ Deploying... Done. ✓ Creating Revision... ✓ Routing traffic... ✓ Setting IAM Policy... Done. Service [image-analysis-jit] revision [image-analysis-jvm-00009-huc] has been deployed and is serving 100 percent of traffic. Service URL: https://image-analysis-jit-...-ew.a.run.app
Implante a imagem nativa e observe o registro de implantação no console:
gcloud run deploy image-analysis-native \ --image gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-native \ --region europe-west1 \ --memory 2Gi --allow-unauthenticated ... Deploying container to Cloud Run service [image-analysis-native] in project [...] region [europe-west1] ✓ Deploying... Done. ✓ Creating Revision... ✓ Routing traffic... ✓ Setting IAM Policy... Done. Service [image-analysis-native] revision [image-analysis-native-00005-ben] has been deployed and is serving 100 percent of traffic. Service URL: https://image-analysis-native-...-ew.a.run.app
13. Configurar gatilhos do Eventarc
O Eventarc oferece uma solução padronizada para gerenciar o fluxo de alterações de estado, chamadas de eventos, entre microsserviços separados. Quando acionado, o Eventarc encaminha esses eventos usando assinaturas do Pub/Sub para vários destinos (neste documento, consulte "Destinos de eventos") enquanto gerencia entrega, segurança, autorização, observabilidade e tratamento de erros.
É possível criar um gatilho do Eventarc para que o serviço do Cloud Run receba notificações de um evento ou conjunto de eventos especificado. Ao especificar filtros para o gatilho, é possível configurar o roteamento do evento, incluindo a origem do evento e o serviço de destino do Cloud Run.
Primeiro defina as variáveis de ambiente do projeto do GCP:
export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project) gcloud config set project ${GOOGLE_CLOUD_PROJECT} gcloud config set run/region gcloud config set run/platform managed gcloud config set eventarc/location europe-west1
Conceda pubsub.publisher
à conta de serviço do Cloud Storage:
SERVICE_ACCOUNT="$(gsutil kms serviceaccount -p ${GOOGLE_CLOUD_PROJECT})" gcloud projects add-iam-policy-binding ${GOOGLE_CLOUD_PROJECT} \ --member="serviceAccount:${SERVICE_ACCOUNT}" \ --role='roles/pubsub.publisher'
Configure gatilhos do Eventarc para as imagens de serviço JIT e nativas para processar a imagem:
gcloud eventarc triggers list --location=eu gcloud eventarc triggers create image-analysis-jit-trigger \ --destination-run-service=image-analysis-jit \ --destination-run-region=europe-west1 \ --location=eu \ --event-filters="type=google.cloud.storage.object.v1.finalized" \ --event-filters="bucket=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}" \ --service-account=${PROJECT_NUMBER}-compute@developer.gserviceaccount.com gcloud eventarc triggers create image-analysis-native-trigger \ --destination-run-service=image-analysis-native \ --destination-run-region=europe-west1 \ --location=eu \ --event-filters="type=google.cloud.storage.object.v1.finalized" \ --event-filters="bucket=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}" \ --service-account=${PROJECT_NUMBER}-compute@developer.gserviceaccount.com
Os dois acionadores foram criados:
gcloud eventarc triggers list --location=eu
14. Testar versões de serviço
Quando as implantações do serviço forem concluídas, você publicará uma imagem no Cloud Storage para ver se nossos serviços foram invocados, o que a API Vision retorna e se os metadados estão armazenados no Firestore.
Navegue de volta para Cloud Storage
e clique no bucket que criamos no início do laboratório:
Na página de detalhes do bucket, clique no botão Upload files
para fazer upload de uma imagem.
Por exemplo, uma imagem GeekHour.jpeg
é fornecida com sua base de código em /services/image-analysis/java
. Selecione uma imagem e pressione Open button
:
Agora é possível verificar a execução do serviço, começando com image-analysis-jit
, seguido por image-analysis-native
.
No menu "hambúrguer", (☰) menu, navegue até o serviço Cloud Run > image-analysis-jit
.
Clique em "Logs" e observe a saída:
Na lista de registros, é possível ver que o serviço JIT image-analysis-jit
foi invocado.
Os registros indicam o início e o fim da execução do serviço. E, enquanto isso, podemos ver os registros que colocamos na função com os log statements no nível INFO. Vemos:
- Os detalhes do evento que aciona a função,
- Os resultados brutos da chamada da API Vision
- Os marcadores que foram encontrados na imagem que carregamos,
- As informações sobre cores dominantes,
- Se a imagem pode ser exibida,
- Por fim, os metadados sobre a imagem foram armazenados no Firestore.
Você repetirá o processo para o serviço image-analysis-native
.
No menu "hambúrguer", (☰) menu, navegue até o serviço Cloud Run > image-analysis-native
.
Clique em "Logs" e observe a saída:
Observe agora se os metadados da imagem foram armazenados no Fiorestore.
Mais uma vez, no "hambúrguer", (☰) no menu, acesse a seção Firestore
. Na subseção Data
(mostrada por padrão), você vai encontrar a coleção pictures
com um novo documento adicionado, correspondente à imagem que você acabou de enviar:
15. 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.
Excluir o bucket:
gsutil rb gs://${BUCKET_PICTURES}
Exclua a função:
gcloud functions delete picture-uploaded --region europe-west1 -q
Para excluir a coleção do Firestore, selecione "Excluir coleção da coleção":
Se preferir, exclua todo o projeto:
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
16. Parabéns!
Parabéns! Você implementou o primeiro serviço de chaves do projeto.
O que vimos
- Cloud Storage
- Cloud Run
- API Cloud Vision
- Cloud Firestore
- Imagens nativas do Java