Günlük Resim: Google Yerel Java İstemci Kitaplıklarıyla Resimleri Depolayın ve Analiz Edin

1. Genel Bakış

İlk kod laboratuvarında resimleri bir pakette depolayacaksınız. Bu işlem, Cloud Run'da dağıtılan bir hizmet tarafından işlenecek bir dosya oluşturma etkinliği oluşturur. Hizmet, görüntü analizi yapmak için Vision API'yi çağırır ve sonuçları bir veri deposuna kaydeder.

427de3100de3a61e.png

Neler öğreneceksiniz?

  • Cloud Storage
  • Cloud Run
  • Cloud Vision API
  • Cloud Firestore

2. Kurulum ve Gereksinimler

Yönlendirmesiz ortam kurulumu

  1. Google Cloud Console'da oturum açın ve yeni bir proje oluşturun veya mevcut bir projeyi yeniden kullanın. Gmail veya Google Workspace hesabınız yoksa hesap oluşturmanız gerekir.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Proje adı, bu projenin katılımcıları için görünen addır. Google API'leri tarafından kullanılmayan bir karakter dizesidir. Dilediğiniz zaman bunu güncelleyebilirsiniz.
  • Proje kimliği, tüm Google Cloud projelerinde benzersiz olmalı ve sabittir (ayarlandıktan sonra değiştirilemez). Cloud Console, benzersiz bir dizeyi otomatik olarak oluşturur. Genellikle bu dizenin ne olduğuyla ilgilenmezsiniz. Çoğu codelab'de proje kimliğine (genellikle PROJECT_ID olarak tanımlanır) başvurmanız gerekir. Oluşturulan kimliği beğenmezseniz başka bir rastgele kimlik oluşturabilirsiniz. Dilerseniz kendi adınızı deneyerek kullanılabilir olup olmadığını kontrol edebilirsiniz. Bu adımdan sonra değiştirilemez ve proje süresince geçerli kalır.
  • Bazı API'lerin kullandığı üçüncü bir değer olan Proje Numarası da vardır. Bu üç değer hakkında daha fazla bilgiyi belgelerde bulabilirsiniz.
  1. Ardından, Cloud kaynaklarını/API'lerini kullanmak için Cloud Console'da faturalandırmayı etkinleştirmeniz gerekir. Bu codelab'i tamamlamak neredeyse hiç maliyetli değildir. Bu eğitimin ötesinde faturalandırma ücreti alınmaması için kaynakları kapatmak üzere oluşturduğunuz kaynakları veya projenin tamamını silebilirsiniz. Google Cloud'un yeni kullanıcıları 300 ABD doları değerinde ücretsiz deneme programından yararlanabilir.

Cloud Shell'i başlatma

Google Cloud, dizüstü bilgisayarınızdan uzaktan çalıştırılabilir. Ancak bu codelab'de, Cloud'da çalışan bir komut satırı ortamı olan Google Cloud Shell'i kullanacaksınız.

Google Cloud Console'da sağ üstteki araç çubuğunda Cloud Shell simgesini tıklayın:

55efc1aaa7a4d3ad.png

Ortamın temel hazırlığı ve bağlanması yalnızca birkaç dakikanızı alır. İşlem tamamlandığında aşağıdakine benzer bir sonuç görürsünüz:

7ffe5cbb04455448.png

Bu sanal makine, ihtiyaç duyacağınız tüm geliştirme araçlarını içerir. 5 GB boyutunda kalıcı bir ana dizin sunar ve Google Cloud üzerinde çalışır. Bu sayede ağ performansı ve kimlik doğrulama önemli ölçüde güçlenir. Bu codelab'deki tüm çalışmalarınızı tarayıcıda yapabilirsiniz. Herhangi bir şey yüklemeniz gerekmez.

3. API'leri etkinleştir

Bu laboratuvarda Cloud Functions ve Vision API'yi kullanacaksınız ancak önce bunların Cloud Console'da veya gcloud ile etkinleştirilmesi gerekir.

Cloud Console'da Vision API'yi etkinleştirmek için arama çubuğunda Cloud Vision API simgesini arayın:

cf48b1747ba6a6fb.png

Cloud Vision API sayfasına yönlendirilirsiniz:

ba4af419e6086fbb.png

ENABLE düğmesini tıklayın.

Alternatif olarak, gcloud komut satırı aracını kullanarak Cloud Shell'de de etkinleştirebilirsiniz.

Cloud Shell'de aşağıdaki komutu çalıştırın:

gcloud services enable vision.googleapis.com

İşlemin başarıyla tamamlandığını görmeniz gerekir:

Operation "operations/acf.12dba18b-106f-4fd2-942d-fea80ecc5c1c" finished successfully.

Cloud Run ve Cloud Build'ü de etkinleştirin:

gcloud services enable cloudbuild.googleapis.com \
  run.googleapis.com

4. Paketi oluşturma (konsol)

Resimler için bir depolama paketi oluşturun. Bu işlemi Google Cloud Platform Console'dan ( console.cloud.google.com) veya Cloud Shell ya da yerel geliştirme ortamınızdan gsutil komut satırı aracıyla yapabilirsiniz.

"Hamburger" (☰) menüsünden Storage sayfasına gidin.

1930e055d138150a.png

Paketinizi adlandırın

CREATE BUCKET düğmesini tıklayın.

34147939358517f8.png

CONTINUE simgesini tıklayın.

Konum seçme

197817f20be07678.png

Seçtiğiniz bölgede (burada Europe) çok bölgeli bir paket oluşturun.

CONTINUE simgesini tıklayın.

Varsayılan depolama sınıfını seçme

53cd91441c8caf0e.png

Verileriniz için Standard depolama sınıfını seçin.

CONTINUE simgesini tıklayın.

Erişim denetimini ayarlama

8c2b3b459d934a51.png

Herkese açık olarak erişilebilen resimlerle çalışacağınız için bu pakette depolanan tüm resimlerimizin aynı tek tip erişim denetimine sahip olmasını istiyorsunuz.

Uniform erişim denetimi seçeneğini belirleyin.

CONTINUE simgesini tıklayın.

Koruma/Şifreleme Ayarlama

d931c24c3e705a68.png

Kendi şifreleme anahtarlarınızı kullanmayacağınız için varsayılanı (Google-managed key)) koruyun.

Paket oluşturma işlemini tamamlamak için CREATE simgesini tıklayın.

allUsers'ı depolama alanı görüntüleyeni olarak ekleme

Permissions sekmesine gidin:

d0ecfdcff730ea51.png

Kovaya aşağıdaki gibi allUsers rolüne sahip bir Storage > Storage Object Viewer üyesi ekleyin:

e9f25ec1ea0b6cc6.png

SAVE simgesini tıklayın.

5. Paketi oluşturma (gsutil)

Paket oluşturmak için Cloud Shell'deki gsutil komut satırı aracını da kullanabilirsiniz.

Cloud Shell'de benzersiz paket adı için bir değişken ayarlayın. Cloud Shell'de GOOGLE_CLOUD_PROJECT, benzersiz proje kimliğinize ayarlanmıştır. Bunu paket adına ekleyebilirsiniz.

Örneğin:

export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}

Avrupa'da standart bir çok bölgeli bölge oluşturun:

gsutil mb -l EU gs://${BUCKET_PICTURES}

Tek tip paket düzeyinde erişim sağlama:

gsutil uniformbucketlevelaccess set on gs://${BUCKET_PICTURES}

Paketi herkese açık hale getirin:

gsutil iam ch allUsers:objectViewer gs://${BUCKET_PICTURES}

Konsolun Cloud Storage bölümüne giderseniz herkese açık bir uploaded-pictures paketi görmeniz gerekir:

a98ed4ba17873e40.png

Önceki adımda açıklandığı gibi, pakete resim yükleyebildiğinizi ve yüklenen resimlerin herkese açık olduğunu test edin.

6. Pakete herkese açık erişimi test etme

Depolama tarayıcısına geri döndüğünüzde, listenizde "Herkese açık" erişim izniyle (herkesin bu paketin içeriğine erişebileceğini hatırlatan bir uyarı işaretiyle birlikte) paketinizi görürsünüz.

89e7a4d2c80a0319.png

Paketiniz artık resim almaya hazır.

Paket adını tıkladığınızda paket ayrıntılarını görürsünüz.

131387f12d3eb2d3.png

Burada, pakete resim ekleyebildiğinizi test etmek için Upload files düğmesini deneyebilirsiniz. Dosya seçici pop-up penceresinde bir dosya seçmeniz istenir. Seçilen dosya, bucket'ınıza yüklenir ve bu yeni dosyaya otomatik olarak atanan public erişimini tekrar görürsünüz.

e87584471a6e9c6d.png

Public erişim etiketinin yanında küçük bir bağlantı simgesi de görürsünüz. Bu URL tıklandığında tarayıcınız, şu biçimde olan resmin herkese açık URL'sine yönlendirilir:

https://storage.googleapis.com/BUCKET_NAME/PICTURE_FILE.png

BUCKET_NAME, paketiniz için seçtiğiniz global olarak benzersiz ad, ardından resminizin dosya adı olmalıdır.

Resim adının yanındaki onay kutusunu tıkladığınızda DELETE düğmesi etkinleştirilir ve bu ilk resmi silebilirsiniz.

7. Veritabanını hazırlama

Vision API tarafından verilen resimle ilgili bilgileri hızlı, tümüyle yönetilen, sunucusuz ve bulutta yerel bir NoSQL belge veritabanı olan Cloud Firestore veritabanında saklayacaksınız. Cloud Console'un Firestore bölümüne giderek veritabanınızı hazırlayın:

9e4708d2257de058.png

İki seçenek sunulur: Native mode veya Datastore mode. Çevrimdışı destek ve gerçek zamanlı senkronizasyon gibi ek özellikler sunan yerel modu kullanın.

SELECT NATIVE MODE simgesini tıklayın.

9449ace8cc84de43.png

Çok bölgeli bir konum seçin (burada Avrupa'da, ancak ideal olarak işlevinizin ve depolama paketinizin bulunduğu bölgeyle aynı bölge olmalıdır).

CREATE DATABASE düğmesini tıklayın.

Veritabanı oluşturulduktan sonra aşağıdakileri görürsünüz:

56265949a124819e.png

+ START COLLECTION düğmesini tıklayarak yeni bir koleksiyon oluşturun.

pictures adlı koleksiyonu adlandırın.

75806ee24c4e13a7.png

Doküman oluşturmanız gerekmez. Yeni resimler Cloud Storage'da depolanıp Vision API tarafından analiz edildikçe bunları programatik olarak eklersiniz.

Save simgesini tıklayın.

Firestore, yeni oluşturulan koleksiyonda ilk varsayılan dokümanı oluşturur. Bu doküman herhangi bir faydalı bilgi içermediğinden güvenle silebilirsiniz:

5c2f1e17ea47f48f.png

Koleksiyonumuzda programatik olarak oluşturulacak dokümanlar 4 alan içerir:

  • name (dize): Yüklenen resmin dosya adı. Bu ad, belgenin anahtarıdır.
  • labels (dizeler dizisi): Vision API tarafından tanınan öğelerin etiketleri
  • color (dize): Baskın rengin onaltılık renk kodu (ör. #ab12ef)
  • created (tarih): Bu resmin meta verilerinin depolandığı zaman damgası
  • thumbnail (boolean): Bu resim için küçük resim oluşturulmuşsa mevcut ve doğru olacak isteğe bağlı bir alan

Küçük resimleri olan fotoğrafları bulmak için Firestore'da arama yapacağımız ve oluşturulma tarihine göre sıralama yapacağımız için bir arama dizini oluşturmamız gerekir.

Cloud Shell'de aşağıdaki komutla dizini oluşturabilirsiniz:

gcloud firestore indexes composite create \
  --collection-group=pictures \
  --field-config field-path=thumbnail,order=descending \
  --field-config field-path=created,order=descending

Alternatif olarak, sol taraftaki gezinme sütununda Indexes simgesini tıklayıp aşağıda gösterildiği gibi bir bileşik dizin oluşturarak da Cloud Console'dan yapabilirsiniz:

ecb8b95e3c791272.png

Create simgesini tıklayın. Dizin oluşturma işlemi birkaç dakika sürebilir.

8. Kodu klonlayın

Önceki kod laboratuvarında henüz yapmadıysanız kodu klonlayın:

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

Ardından, laboratuvarı oluşturmaya başlamak için hizmeti içeren dizine gidebilirsiniz:

cd serverless-photosharing-workshop/services/image-analysis/java

Hizmet için aşağıdaki dosya düzenine sahip olursunuz:

f79613aff479d8ad.png

9. Hizmet kodunu keşfetme

Öncelikle, Java istemci kitaplıklarının pom.xml içinde BOM kullanılarak nasıl etkinleştirildiğine bakarsınız:

Öncelikle, Java işlevimizin bağımlılıklarını listeleyen pom.xml dosyasını düzenleyin. Cloud Vision API Maven bağımlılığını eklemek için kodu güncelleyin:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>cloudfunctions</groupId>
  <artifactId>gcs-function</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <maven.compiler.target>11</maven.compiler.target>
    <maven.compiler.source>11</maven.compiler.source>
  </properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>com.google.cloud</groupId>
        <artifactId>libraries-bom</artifactId>
        <version>26.1.1</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <dependency>
      <groupId>com.google.cloud.functions</groupId>
      <artifactId>functions-framework-api</artifactId>
      <version>1.0.4</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>
  </dependencies>

İşlev, EventController sınıfında uygulanır. Pakete her yeni resim yüklendiğinde hizmet, işleme için bir bildirim alır:

@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 {
...
}

Kod, Cloud Events üstbilgilerini doğrulamaya devam eder:

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);
} 

Artık bir istek oluşturulabilir ve kod, Vision API adresine gönderilecek böyle bir isteği hazırlar:

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);

Vision API'nin 3 temel özelliğini istiyoruz:

  • Etiket algılama: Bu resimlerde ne olduğunu anlamak için
  • Resim özellikleri: Resmin ilgi çekici özelliklerini vermek için (Resmin baskın rengiyle ilgileniyoruz)
  • Güvenli Arama: Resmin gösterilmesinin güvenli olup olmadığını belirlemek için (yetişkinlere uygun, tıbbi, müstehcen veya şiddet barındıran içerik barındırmamalıdır)

Bu noktada, Vision API'yi çağırabiliriz:

...
logger.info("Calling the Vision API...");
BatchAnnotateImagesResponse result = vision.batchAnnotateImages(requests);
List<AnnotateImageResponse> responses = result.getResponsesList();
...

Referans olarak, Vision API'den gelen yanıtın nasıl göründüğünü aşağıda bulabilirsiniz:

{
  "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
}

Hata döndürülmezse devam edebiliriz. Bu nedenle, aşağıdaki if bloğunu kullanırız:

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);
}

Resimde tanınan öğelerin, kategorilerin veya temaların etiketlerini alacağız:

List<String> labels = response.getLabelAnnotationsList().stream()
    .map(annotation -> annotation.getDescription())
    .collect(Collectors.toList());
logger.info("Annotations found:");
for (String label: labels) {
    logger.info("- " + label);
}

Resmin baskın rengini öğrenmek istiyoruz:

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);
}

Resmin gösterilmesinin güvenli olup olmadığını kontrol edelim:

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);
}

Muhtemel veya çok muhtemel olup olmadığını görmek için yetişkinlere uygun içerik, parodi, tıbbi içerik, şiddet ve müstehcenlik özelliklerini kontrol ediyoruz.

Güvenli arama sonucu uygunsa meta verileri Firestore'da saklayabiliriz:

// Saving result to Firestore
if (isSafe) {
    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());

    ApiFuture<WriteResult> writeResult = doc.set(data, SetOptions.merge());

    logger.info("Picture metadata saved in Firestore at " + writeResult.get().getUpdateTime());
}

10. GraalVM ile uygulama görüntüleri oluşturma (isteğe bağlı)

Bu isteğe bağlı adımda, GraalVM kullanarak JIT(JVM) based app image ve ardından AOT(Native) Java app image oluşturacaksınız.

Derlemeyi çalıştırmak için uygun bir JDK'ya ve native-image oluşturucunun yüklü ve yapılandırılmış olduğundan emin olmanız gerekir. Birkaç seçenek vardır.

To start, GraalVM 22.2.x Community Edition'ı indirip GraalVM yükleme sayfasındaki talimatları uygulayın.

Bu süreç, SDKMAN! yardımıyla büyük ölçüde basitleştirilebilir.

SDKman ile uygun JDK dağıtımını yüklemek için önce yükleme komutunu kullanın:

sdk install java 22.2.r17-grl

SDKman'e hem JIT hem de AOT derlemeleri için bu sürümü kullanmasını söyleyin:

sdk use java 22.2.0.r17-grl

GraalVM için native-image utility'yi yükleyin:

gu install native-image

Cloudshell içinde, kolaylık sağlamak için GraalVM'yi ve native-image yardımcı programını şu basit komutlarla yükleyebilirsiniz:

# install GraalVM in your home directory
cd ~

# download GraalVM
wget https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.2.0/graalvm-ce-java17-linux-amd64-22.2.0.tar.gz
ls
tar -xzvf graalvm-ce-java17-linux-amd64-22.2.0.tar.gz

# configure Java 17 and GraalVM 22.2
echo Existing JVM: $JAVA_HOME
cd graalvm-ce-java17-22.2.0
export JAVA_HOME=$PWD
cd bin
export PATH=$PWD:$PATH

echo JAVA HOME: $JAVA_HOME
echo PATH: $PATH

# install the native image utility
java -version
gu install native-image

cd ../..

Öncelikle GCP proje ortamı değişkenlerini ayarlayın:

export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)

Ardından, laboratuvarı oluşturmaya başlamak için hizmeti içeren dizine gidebilirsiniz:

cd serverless-photosharing-workshop/services/image-analysis/java

JIT(JVM) uygulama görüntüsünü oluşturun:

./mvnw package -Pjvm

Terminaldeki derleme günlüğünü inceleyin:

...
[INFO] --- spring-boot-maven-plugin:2.7.3:repackage (repackage) @ image-analysis ---
[INFO] Replacing main artifact with repackaged archive
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  24.009 s
[INFO] Finished at: 2022-09-26T22:17:32-04:00
[INFO] ------------------------------------------------------------------------

AOT(Native) görüntüsünü oluşturun:

./mvnw package -Pnative -DskipTests

Yerel görüntü derleme günlükleri de dahil olmak üzere terminaldeki derleme günlüğünü inceleyin:

Derlemenin, test ettiğiniz makineye bağlı olarak biraz daha uzun sürebileceğini unutmayın.

...
[2/7] Performing analysis...  [**********]                                                              (95.4s @ 3.57GB)
  23,346 (94.42%) of 24,725 classes reachable
  44,625 (68.71%) of 64,945 fields reachable
 163,759 (70.79%) of 231,322 methods reachable
     989 classes, 1,402 fields, and 11,032 methods registered for reflection
      63 classes,    69 fields, and    55 methods registered for JNI access
       5 native libraries: -framework CoreServices, -framework Foundation, dl, pthread, z
[3/7] Building universe...                                                                              (10.0s @ 5.35GB)
[4/7] Parsing methods...      [***]                                                                      (9.7s @ 3.13GB)
[5/7] Inlining methods...     [***]                                                                      (4.5s @ 3.29GB)
[6/7] Compiling methods...    [[6/7] Compiling methods...    [********]                                                                (67.6s @ 5.72GB)
[7/7] Creating image...                                                                                  (8.7s @ 4.59GB)
  62.21MB (54.80%) for code area:   100,371 compilation units
  50.98MB (44.91%) for image heap:  465,035 objects and 365 resources
 337.09KB ( 0.29%) for other data
 113.52MB in total
------------------------------------------------------------------------------------------------------------------------
Top 10 packages in code area:                               Top 10 object types in image heap:
   2.36MB com.google.protobuf                                 12.70MB byte[] for code metadata
   1.90MB i.g.xds.shaded.io.envoyproxy.envoy.config.core.v3    6.66MB java.lang.Class
   1.73MB i.g.x.shaded.io.envoyproxy.envoy.config.route.v3     6.47MB byte[] for embedded resources
   1.67MB sun.security.ssl                                     4.61MB byte[] for java.lang.String
   1.54MB com.google.cloud.vision.v1                           4.37MB java.lang.String
   1.46MB com.google.firestore.v1                              3.38MB byte[] for general heap data
   1.37MB io.grpc.xds.shaded.io.envoyproxy.envoy.api.v2.core   1.96MB com.oracle.svm.core.hub.DynamicHubCompanion
   1.32MB i.g.xds.shaded.io.envoyproxy.envoy.api.v2.route      1.80MB byte[] for reflection metadata
   1.09MB java.util                                          911.80KB java.lang.String[]
   1.08MB com.google.re2j                                    826.48KB c.o.svm.core.hub.DynamicHub$ReflectionMetadata
  45.91MB for 772 more packages                                6.45MB for 3913 more object types
------------------------------------------------------------------------------------------------------------------------
                        15.1s (6.8% of total time) in 56 GCs | Peak RSS: 7.72GB | CPU load: 4.37
------------------------------------------------------------------------------------------------------------------------
Produced artifacts:
 /Users/ddobrin/work/dan/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis (executable)
 /Users/ddobrin/work/dan/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis.build_artifacts.txt (txt)
========================================================================================================================
Finished generating 'image-analysis' in 3m 41s.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  03:56 min
[INFO] Finished at: 2022-09-26T22:22:29-04:00
[INFO] ------------------------------------------------------------------------

11. Kapsayıcı görüntüleri oluşturma ve yayınlama

Bir kapsayıcı görüntüsünü iki farklı sürümde oluşturalım: biri JIT(JVM) image, diğeri ise AOT(Native) Java image olarak.

Öncelikle GCP proje ortamı değişkenlerini ayarlayın:

export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)

JIT(JVM) görüntüsünü oluşturun:

./mvnw package -Pjvm-image

Terminaldeki derleme günlüğünü inceleyin:

[INFO]     [creator]     Adding layer 'process-types'
[INFO]     [creator]     Adding label 'io.buildpacks.lifecycle.metadata'
[INFO]     [creator]     Adding label 'io.buildpacks.build.metadata'
[INFO]     [creator]     Adding label 'io.buildpacks.project.metadata'
[INFO]     [creator]     Adding label 'org.opencontainers.image.title'
[INFO]     [creator]     Adding label 'org.opencontainers.image.version'
[INFO]     [creator]     Adding label 'org.springframework.boot.version'
[INFO]     [creator]     Setting default process type 'web'
[INFO]     [creator]     Saving docker.io/library/image-analysis-jvm:r17...
[INFO]     [creator]     *** Images (03a44112456e):
[INFO]     [creator]           docker.io/library/image-analysis-jvm:r17
[INFO]     [creator]     Adding cache layer 'paketo-buildpacks/syft:syft'
[INFO]     [creator]     Adding cache layer 'cache.sbom'
[INFO] 
[INFO] Successfully built image 'docker.io/library/image-analysis-jvm:r17'
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  02:11 min
[INFO] Finished at: 2022-09-26T13:09:34-04:00
[INFO] ------------------------------------------------------------------------

AOT(Native) görüntüsünü oluşturun:

./mvnw package -Pnative-image

Yerel görüntü oluşturma günlükleri ve UPX kullanılarak görüntü sıkıştırma dahil olmak üzere terminaldeki derleme günlüğünü inceleyin.

Derlemenin, test ettiğiniz makineye bağlı olarak biraz daha uzun sürebileceğini unutmayın.

...
[INFO]     [creator]     [2/7] Performing analysis...  [***********]                    (147.6s @ 3.10GB)
[INFO]     [creator]       23,362 (94.34%) of 24,763 classes reachable
[INFO]     [creator]       44,657 (68.67%) of 65,029 fields reachable
[INFO]     [creator]      163,926 (70.76%) of 231,656 methods reachable
[INFO]     [creator]          981 classes, 1,402 fields, and 11,026 methods registered for reflection
[INFO]     [creator]           63 classes,    68 fields, and    55 methods registered for JNI access
[INFO]     [creator]            4 native libraries: dl, pthread, rt, z
[INFO]     [creator]     [3/7] Building universe...                                      (21.1s @ 2.66GB)
[INFO]     [creator]     [4/7] Parsing methods...      [****]                            (13.7s @ 4.16GB)
[INFO]     [creator]     [5/7] Inlining methods...     [***]                              (9.6s @ 4.20GB)
[INFO]     [creator]     [6/7] Compiling methods...    [**********]                     (107.6s @ 3.36GB)
[INFO]     [creator]     [7/7] Creating image...                                         (14.7s @ 4.87GB)
[INFO]     [creator]       62.24MB (51.35%) for code area:   100,499 compilation units
[INFO]     [creator]       51.99MB (42.89%) for image heap:  473,948 objects and 473 resources
[INFO]     [creator]        6.98MB ( 5.76%) for other data
[INFO]     [creator]      121.21MB in total
[INFO]     [creator]     --------------------------------------------------------------------------------
[INFO]     [creator]     Top 10 packages in code area:           Top 10 object types in image heap:
[INFO]     [creator]        2.36MB com.google.protobuf             12.71MB byte[] for code metadata
[INFO]     [creator]        1.90MB i.g.x.s.i.e.e.config.core.v3     7.59MB byte[] for embedded resources
[INFO]     [creator]        1.73MB i.g.x.s.i.e.e.config.route.v3    6.66MB java.lang.Class
[INFO]     [creator]        1.67MB sun.security.ssl                 4.62MB byte[] for java.lang.String
[INFO]     [creator]        1.54MB com.google.cloud.vision.v1       4.39MB java.lang.String
[INFO]     [creator]        1.46MB com.google.firestore.v1          3.66MB byte[] for general heap data
[INFO]     [creator]        1.37MB i.g.x.s.i.e.envoy.api.v2.core    1.96MB c.o.s.c.h.DynamicHubCompanion
[INFO]     [creator]        1.32MB i.g.x.s.i.e.e.api.v2.route       1.80MB byte[] for reflection metadata
[INFO]     [creator]        1.09MB java.util                      910.41KB java.lang.String[]
[INFO]     [creator]        1.08MB com.google.re2j                826.95KB c.o.s.c.h.DynamicHu~onMetadata
[INFO]     [creator]       45.94MB for 776 more packages            6.69MB for 3916 more object types
[INFO]     [creator]     --------------------------------------------------------------------------------
[INFO]     [creator]         20.4s (5.6% of total time) in 81 GCs | Peak RSS: 6.75GB | CPU load: 4.53
[INFO]     [creator]     --------------------------------------------------------------------------------
[INFO]     [creator]     Produced artifacts:
[INFO]     [creator]      /layers/paketo-buildpacks_native-image/native-image/services.ImageAnalysisApplication (executable)
[INFO]     [creator]      /layers/paketo-buildpacks_native-image/native-image/services.ImageAnalysisApplication.build_artifacts.txt (txt)
[INFO]     [creator]     ================================================================================
[INFO]     [creator]     Finished generating '/layers/paketo-buildpacks_native-image/native-image/services.ImageAnalysisApplication' in 5m 59s.
[INFO]     [creator]         Executing upx to compress native image
[INFO]     [creator]                            Ultimate Packer for eXecutables
[INFO]     [creator]                               Copyright (C) 1996 - 2020
[INFO]     [creator]     UPX 3.96        Markus Oberhumer, Laszlo Molnar & John Reiser   Jan 23rd 2020
[INFO]     [creator]     
[INFO]     [creator]             File size         Ratio      Format      Name
[INFO]     [creator]        --------------------   ------   -----------   -----------
 127099880 ->  32416676   25.50%   linux/amd64   services.ImageAnalysisApplication
...
[INFO]     [creator]     ===> EXPORTING
...
[INFO]     [creator]     Adding cache layer 'paketo-buildpacks/native-image:native-image'
[INFO]     [creator]     Adding cache layer 'cache.sbom'
[INFO] 
[INFO] Successfully built image 'docker.io/library/image-analysis-native:r17'
------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  05:28 min
[INFO] Finished at: 2022-09-26T13:19:53-04:00
[INFO] ------------------------------------------------------------------------

Resimlerin oluşturulduğunu doğrulayın:

docker images | grep image-analysis

İki resmi etiketleyin ve GCR'ye gönderin:

# JIT(JVM) image
docker tag image-analysis-jvm:r17 gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-jvm:r17
docker push gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-jvm:r17

# AOT(Native) image
docker tag image-analysis-native:r17 gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-native:r17
docker push  gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-native:r17

12. Cloud Run'a dağıt

Hizmeti dağıtma zamanı.

Hizmeti iki kez dağıtacaksınız. Bir kez JIT(JVM) görüntüsünü, ikinci kez ise AOT(Native) görüntüsünü kullanarak dağıtın. Her iki hizmet dağıtımı da karşılaştırma amacıyla aynı resmi paralel olarak işler.

Öncelikle GCP proje ortamı değişkenlerini ayarlayın:

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

JIT(JVM) görüntüsünü dağıtın ve konsoldaki dağıtım günlüğünü inceleyin:

gcloud run deploy image-analysis-jvm \
     --image gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-jvm:r17 \
     --region europe-west1 \
     --memory 2Gi --allow-unauthenticated

...
Deploying container to Cloud Run service [image-analysis-jvm] in project [...] region [europe-west1]
✓ Deploying... Done.                                                                                                                                                               
  ✓ Creating Revision...                                                                                                                                                           
  ✓ Routing traffic...                                                                                                                                                             
  ✓ Setting IAM Policy...                                                                                                                                                          
Done.                                                                                                                                                                              
Service [image-analysis-jvm] revision [image-analysis-jvm-00009-huc] has been deployed and is serving 100 percent of traffic.
Service URL: https://image-analysis-jvm-...-ew.a.run.app

AOT(Native) görüntüsünü dağıtın ve konsoldaki dağıtım günlüğünü inceleyin:

gcloud run deploy image-analysis-native \
     --image gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-native:r17 \
     --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. Eventarc tetikleyicilerini ayarlama

Eventarc, ayrılmış mikro hizmetler arasında durum değişikliklerinin akışını (etkinlikler) yönetmek için standartlaştırılmış bir çözüm sunar. Eventarc, tetiklendiğinde bu etkinlikleri Pub/Sub abonelikleri aracılığıyla çeşitli hedeflere (bu belgedeki Etkinlik hedefleri bölümüne bakın) yönlendirirken sizin için teslimat, güvenlik, yetkilendirme, gözlemlenebilirlik ve hata işlemeyi yönetir.

Cloud Run hizmetinizin, belirtilen bir etkinlik veya etkinlik grubuyla ilgili bildirimler alması için Eventarc tetikleyicisi oluşturabilirsiniz. Tetikleyici için filtreler belirterek etkinlik kaynağı ve hedef Cloud Run hizmeti de dahil olmak üzere etkinliğin yönlendirilmesini yapılandırabilirsiniz.

Öncelikle GCP proje ortamı değişkenlerini ayarlayın:

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

Cloud Storage hizmet hesabına pubsub.publisher izni verin:

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'

Resmi işlemek için hem JVM(JIT) hem de AOT(Native) hizmet resimleri için Eventarc tetikleyicileri ayarlayın:

gcloud eventarc triggers list --location=eu

gcloud eventarc triggers create image-analysis-jvm-trigger \
     --destination-run-service=image-analysis-jvm \
     --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    

İki tetikleyicinin oluşturulduğunu gözlemleyin:

gcloud eventarc triggers list --location=eu

14. Hizmet Sürümlerini Test Etme

Hizmet dağıtımları başarılı olduktan sonra Cloud Storage'a bir resim gönderecek, hizmetlerimizin çağrılıp çağrılmadığını, Vision API'nin ne döndürdüğünü ve meta verilerin Firestore'da depolanıp depolanmadığını göreceksiniz.

Cloud Storage simgesine geri gidin ve laboratuvarın başında oluşturduğumuz paketi tıklayın:

ff8a6567afc76235.png

Paket ayrıntıları sayfasına geldiğinizde resim yüklemek için Upload files düğmesini tıklayın.

Örneğin, GeekHour.jpeg resmi, /services/image-analysis/java altında kod tabanınızla birlikte sağlanır. Bir resim seçin ve Open button tuşuna basın:

347b76e8b775f2f5.png

Artık hizmetin yürütülmesini kontrol edebilirsiniz. Önce image-analysis-jvm, ardından image-analysis-native ile başlayın.

"Hamburger" (☰) menüsünden Cloud Run > image-analysis-jvm hizmetine gidin.

Günlükler'i tıklayın ve çıkışı inceleyin:

810a8684414ceafa.png

Günlük listesinde, JIT(JVM) hizmetinin image-analysis-jvm çağrıldığını görebiliyorum.

Günlükler, hizmet yürütülmesinin başlangıcını ve sonunu gösterir. Ayrıca, aradaki bölümde INFO düzeyindeki günlük ifadeleriyle işlevimize yerleştirdiğimiz günlükleri görebiliriz. Gördüğümüz:

  • İşlevimizi tetikleyen etkinliğin ayrıntıları,
  • Vision API çağrısından alınan ham sonuçlar,
  • Yüklediğimiz resimde bulunan etiketler,
  • Baskın renkler bilgisi,
  • Resmin gösterilmesinin güvenli olup olmadığı,
  • Sonunda resimle ilgili bu meta veriler Firestore'da depolanır.

image-analysis-native hizmeti için işlemi tekrarlarsınız.

"Hamburger" (☰) menüsünden Cloud Run > image-analysis-native hizmetine gidin.

Günlükler'i tıklayın ve çıkışı inceleyin:

b80308c7d0f55a3.png

Resim meta verilerinin Firestore'da depolanıp depolanmadığını kontrol edin.

Yine "hamburger" (☰) menüsünden Firestore bölümüne gidin. Data alt bölümünde (varsayılan olarak gösterilir), yeni bir doküman eklenmiş pictures koleksiyonunu görmeniz gerekir. Bu doküman, az önce yüklediğiniz resme karşılık gelir:

933a20a9709cb006.png

15. Temizleme (isteğe bağlı)

Serideki diğer laboratuvarlara devam etmeyi düşünmüyorsanız maliyetleri düşürmek ve genel olarak iyi bir bulut kullanıcısı olmak için kaynakları temizleyebilirsiniz. Kaynakları tek tek aşağıdaki şekilde temizleyebilirsiniz.

Paketi silme:

gsutil rb gs://${BUCKET_PICTURES}

İşlevi silme:

gcloud functions delete picture-uploaded --region europe-west1 -q

Koleksiyondan Koleksiyonu sil'i seçerek Firestore koleksiyonunu silin:

410b551c3264f70a.png

Alternatif olarak, projenin tamamını silebilirsiniz:

gcloud projects delete ${GOOGLE_CLOUD_PROJECT} 

16. Tebrikler!

Tebrikler! Projenin ilk anahtar hizmetini başarıyla uyguladınız.

İşlediğimiz konular

  • Cloud Storage
  • Cloud Run
  • Cloud Vision API
  • Cloud Firestore
  • Yerel Java Görüntüleri

Sonraki Adımlar