1. Présentation
Dans le premier atelier de programmation, vous allez stocker des images dans un bucket. Cette opération génère un événement de création de fichier qui sera géré par un service déployé dans Cloud Run. Le service appellera l'API Vision pour analyser des images et enregistrer les résultats dans un datastore.
Points abordés
- Cloud Storage
- Cloud Run
- API Cloud Vision
- Cloud Firestore
2. Préparation
Configuration de l'environnement au rythme de chacun
- Connectez-vous à la console Google Cloud, puis créez un projet ou réutilisez un projet existant. (Si vous ne possédez pas encore de compte Gmail ou Google Workspace, vous devez en créer un.)
- Le nom du projet est le nom à afficher pour les participants au projet. Il s'agit d'une chaîne de caractères non utilisée par les API Google. Vous pourrez toujours le modifier.
- L'ID du projet est unique parmi tous les projets Google Cloud et non modifiable une fois défini. La console Cloud génère automatiquement une chaîne unique (en général, vous n'y accordez d'importance particulière). Dans la plupart des ateliers de programmation, vous devrez indiquer l'ID de votre projet (généralement identifié par
PROJECT_ID
). Si l'ID généré ne vous convient pas, vous pouvez en générer un autre de manière aléatoire. Vous pouvez également en spécifier un et voir s'il est disponible. Après cette étape, l'ID n'est plus modifiable et restera donc le même pour toute la durée du projet. - Pour information, il existe une troisième valeur (le numéro de projet) que certaines API utilisent. Pour en savoir plus sur ces trois valeurs, consultez la documentation.
- Vous devez ensuite activer la facturation dans la console Cloud pour utiliser les ressources/API Cloud. L'exécution de cet atelier de programmation est très peu coûteuse, voire sans frais. Pour désactiver les ressources et éviter ainsi que des frais ne vous soient facturés après ce tutoriel, vous pouvez supprimer le projet ou les ressources que vous avez créées. Les nouveaux utilisateurs de Google Cloud peuvent participer au programme d'essai sans frais pour bénéficier d'un crédit de 300 $.
Démarrer Cloud Shell
Bien que Google Cloud puisse être utilisé à distance depuis votre ordinateur portable, nous allons nous servir de Google Cloud Shell pour cet atelier de programmation, un environnement de ligne de commande exécuté dans le cloud.
Dans la console Google Cloud, cliquez sur l'icône Cloud Shell dans la barre d'outils supérieure :
Le provisionnement et la connexion à l'environnement prennent quelques instants seulement. Une fois l'opération terminée, le résultat devrait ressembler à ceci :
Cette machine virtuelle contient tous les outils de développement nécessaires. Elle comprend un répertoire d'accueil persistant de 5 Go et s'exécute sur Google Cloud, ce qui améliore nettement les performances du réseau et l'authentification. Vous pouvez effectuer toutes les tâches de cet atelier de programmation dans un navigateur. Vous n'avez rien à installer.
3. Activer les API
Dans cet atelier, vous allez utiliser Cloud Functions et l'API Vision, mais vous devez d'abord les activer dans la console Cloud ou avec gcloud
.
Pour activer l'API Vision dans la console Cloud, recherchez Cloud Vision API
dans la barre de recherche:
Vous arrivez sur la page de l'API Cloud Vision:
Cliquez sur le bouton ENABLE
.
Vous pouvez également l'activer dans Cloud Shell à l'aide de l'outil de ligne de commande gcloud.
Dans Cloud Shell, exécutez la commande suivante:
gcloud services enable vision.googleapis.com
L'opération doit s'afficher pour se terminer correctement:
Operation "operations/acf.12dba18b-106f-4fd2-942d-fea80ecc5c1c" finished successfully.
Activez également Cloud Run et Cloud Build:
gcloud services enable cloudbuild.googleapis.com \ run.googleapis.com
4. Créer le bucket (console)
Créez un bucket de stockage pour les photos. Vous pouvez le faire depuis la console Google Cloud Platform ( console.cloud.google.com) ou à l'aide de l'outil de ligne de commande gsutil depuis Cloud Shell ou votre environnement de développement local.
Accéder à Storage
Du "hamburger", (Twilio), accédez à la page Storage
.
Attribuer un nom au bucket
Cliquez sur le bouton CREATE BUCKET
.
Cliquez sur CONTINUE
.
Choisir un emplacement
Créez un bucket multirégional dans la région de votre choix (ici Europe
).
Cliquez sur CONTINUE
.
Choisir la classe de stockage par défaut
Choisissez la classe de stockage Standard
pour vos données.
Cliquez sur CONTINUE
.
Configurer le contrôle des accès
Comme vous allez travailler avec des images accessibles publiquement, vous souhaitez que toutes les images stockées dans ce bucket aient le même contrôle d'accès uniforme.
Choisissez l'option de contrôle d'accès Uniform
.
Cliquez sur CONTINUE
.
Définir la protection/le chiffrement
Conservez la valeur par défaut (Google-managed key)
, car vous n'utiliserez pas vos propres clés de chiffrement.
Cliquez sur CREATE
pour finaliser la création du bucket.
Ajouter allUsers en tant que lecteur de l'espace de stockage
Accédez à l'onglet Permissions
:
Ajoutez un membre allUsers
au bucket, avec le rôle Storage > Storage Object Viewer
, comme suit:
Cliquez sur SAVE
.
5. Créer le bucket (gsutil)
Vous pouvez également créer des buckets à l'aide de l'outil de ligne de commande gsutil
dans Cloud Shell.
Dans Cloud Shell, définissez une variable pour le nom unique du bucket. GOOGLE_CLOUD_PROJECT
est déjà défini sur votre ID de projet unique dans Cloud Shell. Vous pouvez l'ajouter au nom du bucket.
Exemple :
export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}
Créez une zone multirégionale standard en Europe:
gsutil mb -l EU gs://${BUCKET_PICTURES}
Assurez-vous que l'accès est uniforme au niveau du bucket:
gsutil uniformbucketlevelaccess set on gs://${BUCKET_PICTURES}
Rendez le bucket public :
gsutil iam ch allUsers:objectViewer gs://${BUCKET_PICTURES}
Si vous accédez à la section Cloud Storage
de la console, vous devriez disposer d'un bucket uploaded-pictures
public:
Vérifiez que vous pouvez importer des photos dans le bucket et qu'elles sont accessibles au public, comme expliqué à l'étape précédente.
6. Tester l'accès public au bucket
Dans le navigateur de stockage, votre bucket apparaît dans la liste, avec la mention "Public". y compris un panneau d'avertissement vous rappelant que n'importe qui a accès au contenu de ce bucket.
Votre bucket est maintenant prêt à recevoir des photos.
Si vous cliquez sur le nom du bucket, ses informations s'affichent.
Appuyez sur le bouton Upload files
pour tester l'ajout d'une image au bucket. Un pop-up de sélection de fichier vous invite à sélectionner un fichier. Une fois sélectionné, il sera importé dans votre bucket, et vous verrez de nouveau l'accès public
attribué automatiquement à ce nouveau fichier.
Une petite icône de lien apparaît également le long du libellé d'accès Public
. Lorsque vous cliquez dessus, votre navigateur accédera à l'URL publique de cette image, qui se présentera sous la forme suivante:
https://storage.googleapis.com/BUCKET_NAME/PICTURE_FILE.png
BUCKET_NAME
étant le nom unique que vous avez choisi pour votre bucket, suivi du nom de fichier de votre image.
Cliquez sur la case à cocher à côté du nom de l'image pour activer le bouton DELETE
et supprimer cette première image.
7. Préparer la base de données
Vous allez stocker des informations sur l'image fournie par l'API Vision dans la base de données Cloud Firestore, une base de données de documents NoSQL sans serveur, cloud native, entièrement gérée et rapide. Préparez votre base de données en accédant à la section Firestore
de Cloud Console:
Deux options sont proposées: Native mode
ou Datastore mode
. Utilisez le mode natif, qui offre des fonctionnalités supplémentaires telles que le fonctionnement hors connexion et la synchronisation en temps réel.
Cliquez sur SELECT NATIVE MODE
.
Choisissez un emplacement multirégional (ici en Europe, mais idéalement au moins la même région que votre fonction et votre bucket de stockage).
Cliquez sur le bouton CREATE DATABASE
.
Une fois la base de données créée, la page suivante doit s'afficher:
Créez une collection en cliquant sur le bouton + START COLLECTION
.
Nom de la collection pictures
.
Vous n'avez pas besoin de créer de document. Vous allez les ajouter par programmation, car de nouvelles images sont stockées dans Cloud Storage et analysées par l'API Vision.
Cliquez sur Save
.
Firestore crée un premier document par défaut dans la collection que vous venez de créer. Vous pouvez supprimer ce document en toute sécurité, car il ne contient aucune information utile:
Les documents qui seront créés par programmation dans notre collection contiendront quatre champs:
- name (chaîne): nom de fichier de l'image importée, qui est également la clé du document.
- labels (tableau de chaînes): étiquettes des éléments reconnus par l'API Vision
- color (chaîne): code hexadécimal de la couleur dominante (par exemple, #ab12ef)
- créé (date): horodatage correspondant au moment où les métadonnées de cette image ont été stockées
- thumbnail (valeur booléenne): champ facultatif qui est présent et a la valeur "true" si une vignette a été générée pour cette image.
Comme nous allons rechercher dans Firestore des images pour lesquelles des vignettes sont disponibles, et trier les dates de création, nous devons créer un index de recherche.
Vous pouvez créer l'index à l'aide de la commande suivante dans Cloud Shell:
gcloud firestore indexes composite create \
--collection-group=pictures \
--field-config field-path=thumbnail,order=descending \
--field-config field-path=created,order=descending
Vous pouvez également le faire depuis la console Cloud. Pour ce faire, cliquez sur Indexes
dans la colonne de navigation à gauche, puis créez un index composite comme indiqué ci-dessous:
Cliquez sur Create
. La création de l'index peut prendre quelques minutes.
8. Cloner le code
Clonez le code (si vous ne l'avez pas déjà fait dans l'atelier de programmation précédent) :
git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop
Vous pouvez ensuite accéder au répertoire contenant le service pour commencer à créer l'atelier:
cd serverless-photosharing-workshop/services/image-analysis/java
La mise en page des fichiers du service est la suivante:
9. Explorer le code de service
Commencez par examiner comment les bibliothèques clientes Java sont activées dans pom.xml
à l'aide d'une nomenclature:
Tout d'abord, ouvrez le fichier pom.xml
qui répertorie les dépendances de notre application Java. nous allons nous concentrer sur l'utilisation des API Vision, Cloud Storage et 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>
La fonctionnalité est implémentée dans la classe EventController
. Chaque fois qu'une nouvelle image est importée dans le bucket, le service reçoit une notification pour traiter les éléments suivants:
@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 {
...
}
Le code validera les en-têtes 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);
}
Une requête peut maintenant être créée, et le code préparera une requête de ce type à envoyer au 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);
Nous avons besoin de trois fonctionnalités clés de l'API Vision:
- Détection de thèmes: pour comprendre le contenu de ces images
- Propriétés de l'image: pour fournir des attributs intéressants de l'image (nous nous intéressons à la couleur dominante de l'image)
- SafeSearch: permet de savoir si l'image peut être affichée sans risque (elle ne doit pas contenir de contenu réservé aux adultes, médical, pour adultes ou violent)
À ce stade, nous pouvons appeler l'API Vision:
...
logger.info("Calling the Vision API...");
BatchAnnotateImagesResponse result = vision.batchAnnotateImages(requests);
List<AnnotateImageResponse> responses = result.getResponsesList();
...
Pour référence, voici à quoi ressemble la réponse de l'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
}
Si aucune erreur ne s'affiche, nous pouvons passer à autre chose. C'est pourquoi nous avons ce bloc "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);
}
Nous allons faire reconnaître les étiquettes des choses, des catégories ou des thèmes sur l'image:
List<String> labels = response.getLabelAnnotationsList().stream()
.map(annotation -> annotation.getDescription())
.collect(Collectors.toList());
logger.info("Annotations found:");
for (String label: labels) {
logger.info("- " + label);
}
Nous souhaitons connaître la couleur dominante de l'image:
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);
}
Vérifions que l'image peut être affichée sans risque:
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);
}
Nous vérifions les caractéristiques des adultes, de parodie, médicales, de violence ou pour adultes afin de déterminer s'ils sont peu probables ou très probables.
Si le résultat de la recherche sécurisée est correct, nous pouvons stocker des métadonnées dans 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. Créer des images d'application avec GraalVM
Au cours de cette étape facultative, vous allez créer une JIT based app image
, puis une Native Java app image
, à l'aide de GraalVM.
Pour exécuter la compilation, vous devez vous assurer que vous disposez d'un JDK approprié et que le compilateur d'images natives est installé et configuré. Plusieurs options sont disponibles.
To start
, téléchargez GraalVM 22.3.x Community Edition et suivez les instructions de la page d'installation de GraalVM.
Ce processus peut être considérablement simplifié grâce à SDKMAN!
Pour installer la distribution JDK appropriée avec SDKman
, commencez par utiliser la commande d'installation:
sdk install java 17.0.8-graal
Demandez à SDKman d'utiliser cette version pour les builds JIT et AOT:
sdk use java 17.0.8-graal
Dans Cloudshell
, pour plus de commodité, vous pouvez installer GraalVM et l'utilitaire native-image à l'aide de ces commandes 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)
Commencez par définir les variables d'environnement du projet GCP:
export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
Vous pouvez ensuite accéder au répertoire contenant le service pour commencer à créer l'atelier:
cd serverless-photosharing-workshop/services/image-analysis/java
Créez l'image de l'application JIT:
./mvnw package
Observez le journal des builds dans le 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] ------------------------------------------------------------------------
Créez l'image native(utilise AOT) :
./mvnw native:compile -Pnative
Observez le journal de compilation dans le terminal, y compris les journaux de compilation d'images natives:
Notez que la compilation prend un peu plus de temps, selon la machine sur laquelle vous effectuez le test.
... [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. Créer et publier des images de conteneurs
Créons une image de conteneur dans deux versions différentes: l'une en tant que JIT image
et l'autre en tant que Native Java image
.
Commencez par définir les variables d'environnement du projet GCP:
export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
Créez l'image JIT :
./mvnw spring-boot:build-image -Pji
Observez le journal des builds dans le 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] ------------------------------------------------------------------------
Créez l'image AOT(native) :
./mvnw spring-boot:build-image -Pnative
Observez le journal de compilation dans le terminal, y compris les journaux de compilation d'images natives.
Remarque :
- que la compilation prend un peu plus de temps, selon la machine sur laquelle vous testez
- les images peuvent être compressées davantage avec UPX, mais elles ont un léger impact négatif sur les performances de démarrage. C'est pourquoi ce build n'utilise pas le format UPX (il s'agit toujours d'un léger compromis).
... [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] ------------------------------------------------------------------------
Vérifiez que les images ont été créées:
docker images | grep image-analysis
Ajoutez un tag aux deux images et transmettez-les à 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. Déployer dans Cloud Run
Il est temps de déployer le service.
Vous déploierez le service deux fois : une fois à l'aide de l'image JIT et la seconde fois avec l'image AOT(native). Les deux déploiements de service traiteront en parallèle la même image du bucket, à des fins de comparaison.
Commencez par définir les variables d'environnement du projet 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
Déployez l'image JIT et observez le journal de déploiement dans la 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
Déployez l'image native et observez le journal de déploiement dans la 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. Configurer des déclencheurs Eventarc
Eventarc offre une solution standardisée pour gérer le flux de changements d'état, appelés événements, entre des microservices découplés. Lorsqu'il est déclenché, Eventarc achemine ces événements via des abonnements Pub/Sub vers différentes destinations (dans ce document, voir "Destinations de l'événement") tout en gérant pour vous la distribution, la sécurité, l'autorisation, l'observabilité et la gestion des erreurs.
Vous pouvez créer un déclencheur Eventarc afin que votre service Cloud Run reçoive des notifications pour un événement ou un ensemble d'événements spécifié. En spécifiant des filtres pour le déclencheur, vous pouvez configurer le routage de l'événement, y compris la source de l'événement et le service Cloud Run cible.
Commencez par définir les variables d'environnement du projet 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
Accordez pubsub.publisher
au compte de service 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'
Configurez des déclencheurs Eventarc pour que les images JIT et de service natif puissent traiter l'image:
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
Notez que les deux déclencheurs ont été créés:
gcloud eventarc triggers list --location=eu
14. Tester les versions de service
Une fois les déploiements de services effectués, vous publierez une image dans Cloud Storage, puis vérifierez si nos services ont été appelés, ce que renvoie l'API Vision et si les métadonnées sont stockées dans Firestore.
Revenez à Cloud Storage
et cliquez sur le bucket que nous avons créé au début de l'atelier:
Une fois sur la page d'informations du bucket, cliquez sur le bouton Upload files
pour importer une photo.
Par exemple, une image GeekHour.jpeg
est fournie avec votre codebase sous /services/image-analysis/java
. Sélectionnez une image et appuyez sur le Open button
:
Vous pouvez maintenant vérifier l'exécution du service, en commençant par image-analysis-jit
, puis image-analysis-native
.
Du "hamburger", (Twilio), accédez au service Cloud Run > image-analysis-jit
.
Cliquez sur "Logs" (Journaux) et observez le résultat:
Dans la liste des journaux, je constate que le service JIT image-analysis-jit
a été appelé.
Les journaux indiquent le début et la fin de l'exécution du service. Entre les deux, nous pouvons voir les journaux que nous avons placés dans notre fonction avec les instructions de journalisation au niveau INFO. Voici ce que nous voyons:
- Les détails de l'événement qui déclenche notre fonction,
- Les résultats bruts de l'appel de l'API Vision
- Les étiquettes qui ont été trouvées dans l'image que nous avons importée,
- Les informations sur les couleurs dominantes,
- Si l'image peut être affichée en toute sécurité,
- Et finalement, ces métadonnées sur l'image ont été stockées dans Firestore.
Vous répéterez la procédure pour le service image-analysis-native
.
Du "hamburger", (Twilio), accédez au service Cloud Run > image-analysis-native
.
Cliquez sur "Logs" (Journaux) et observez le résultat:
Vous allez maintenant vérifier si les métadonnées d'image ont été stockées dans Fiorestore.
Encore une fois,
pour le « hamburger », (Twilio), accédez à la section Firestore
. Dans la sous-section Data
(affichée par défaut), vous devriez voir la collection pictures
avec un nouveau document ajouté, correspondant à l'image que vous venez d'importer:
15. Nettoyer (facultatif)
Si vous n'avez pas l'intention de suivre les autres ateliers de la série, vous pouvez nettoyer les ressources pour réduire les coûts et utiliser globalement le cloud comme il se doit. Vous pouvez nettoyer les ressources individuellement comme suit.
Supprimez le bucket :
gsutil rb gs://${BUCKET_PICTURES}
Supprimez la fonction:
gcloud functions delete picture-uploaded --region europe-west1 -q
Supprimez la collection Firestore en sélectionnant "Supprimer la collection de la collection" :
Vous pouvez également supprimer l'intégralité du projet:
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
16. Félicitations !
Félicitations ! Vous avez réussi à mettre en œuvre le premier service de clés du projet.
Points abordés
- Cloud Storage
- Cloud Run
- API Cloud Vision
- Cloud Firestore
- Images Java natives