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 ve sonuçları bir veri deposuna kaydetmek için Vision API'ye çağrı yapar.

427de3100de3a61e.png

Neler öğreneceksiniz?

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

2. Kurulum ve Gereksinimler

Kendi hızınızda ortam kurulumu

  1. Google Cloud Console'da oturum açıp 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. İstediğiniz zaman güncelleyebilirsiniz.
  • Proje Kimliği, tüm Google Cloud projelerinde benzersiz olmalıdır ve değiştirilemez (belirlendikten sonra değiştirilemez). Cloud Console, otomatik olarak benzersiz bir dize oluşturur. bunun ne olduğunu umursamıyorsunuz. Çoğu codelab'de proje kimliğini (genellikle PROJECT_ID olarak tanımlanır) referans almanız gerekir. Oluşturulan kimliği beğenmezseniz rastgele bir kimlik daha oluşturabilirsiniz. Alternatif olarak, kendi ölçümünüzü deneyip mevcut olup olmadığına bakabilirsiniz. Bu adımdan sonra değiştirilemez ve proje süresince kalır.
  • Bilginiz için bazı API'lerin kullandığı üçüncü bir değer, yani Proje Numarası daha vardır. Bu değerlerin üçü hakkında daha fazla bilgiyi belgelerde bulabilirsiniz.
  1. Sonraki adımda, Cloud kaynaklarını/API'lerini kullanmak için Cloud Console'da faturalandırmayı etkinleştirmeniz gerekir. Bu codelab'i çalıştırmanın maliyeti, yüksek değildir. Bu eğitim dışında faturalandırma yapılmaması için kaynakları kapatmak isterseniz oluşturduğunuz kaynakları silebilir veya projenin tamamını silebilirsiniz. Yeni Google Cloud kullanıcıları, 300 ABD doları değerindeki ücretsiz denemeden yararlanabilir.

Cloud Shell'i başlatma

Google Cloud dizüstü bilgisayarınızdan uzaktan çalıştırılabilse de 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 bulunan Cloud Shell simgesini tıklayın:

55efc1aaa7a4d3ad.png

Ortamı sağlamak ve bağlamak yalnızca birkaç dakika sürer. Tamamlandığında şuna benzer bir sonuç görmeniz gerekir:

7ffe5cbb04455448

İhtiyacınız olan tüm geliştirme araçlarını bu sanal makinede bulabilirsiniz. 5 GB boyutunda kalıcı bir ana dizin sunar ve Google Cloud üzerinde çalışarak ağ performansını ve kimlik doğrulamasını büyük ölçüde iyileştirir. Bu codelab'deki tüm çalışmalarınız tarayıcıda yapılabilir. Herhangi bir şey yüklemeniz gerekmez.

3. API'leri etkinleştir

Bu laboratuvarda Cloud Functions ve Vision API'yi kullanacaksınız ancak bunların önce 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 ifadesini 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'i de etkinleştirebilirsiniz.

Cloud Shell'in içinde aşağıdaki komutu çalıştırın:

gcloud services enable vision.googleapis.com

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

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

Cloud Run ve Cloud Build'i 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 konsolundan ( console.cloud.google.com) veya Cloud Shell'den ya da yerel geliştirme ortamınızdan gsutil komut satırı aracıyla yapabilirsiniz.

"Hamburger"den (Gönder) 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çin

197817f20be07678.png

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

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

Herkesin erişebileceği görüntülerle çalışacağınız için bu pakette depolanan tüm resimlerimizin aynı tek tip erişim denetimine sahip olmasını istersiniz.

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

CONTINUE simgesini tıklayın.

Korumayı/Şifrelemeyi Ayarlama

d931c24c3e705a68.png

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

Paket oluşturma işlemini tamamlamak için CREATE düğmesini tıklayın.

allUsers'ı depolama alanı görüntüleyici olarak ekle

Permissions sekmesine gidin:

d0ecfdcff730ea51.png

Aşağıdaki gibi, Storage > Storage Object Viewer rolüne sahip bir allUsers üyesini pakete ekleyin:

e9f25ec1ea0b6cc6.png

SAVE simgesini tıklayın.

5. Paket (gsutil) oluşturma

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 zaten ayarlanmış. Bunu paket adına ekleyebilirsiniz.

Örneğin:

export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}

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

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

Tek tip paket düzeyinde erişim sağlayın:

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 paketiniz olur:

a98ed4ba17873e40.png

Pakete resim yükleyemediğinizi ve yüklenen resimlerin önceki adımda açıklandığı gibi herkese açık olup olmadığını test edin.

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

Depolama tarayıcısına geri döndüğünüzde, paketinizi listede "Herkese açık" olarak görürsünüz. (ilgili grubun içeriğine herhangi birinin erişimi olduğunu hatırlatan bir uyarı işareti dahil).

89e7a4d2c80a0319.png

Paketiniz artık resim almaya hazır.

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

131387f12d3eb2d3.png

Buradan, pakete resim ekleyip ekleyebileceğinizi test etmek için Upload files düğmesini deneyebilirsiniz. Dosya seçici pop-up'ında bir dosya seçmeniz istenir. Seçildikten sonra paketinize yüklenir ve bu yeni dosyayla otomatik olarak ilişkilendirilen 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öreceksiniz. Resmi tıkladığınızda, tarayıcınız ilgili resmin herkese açık URL'sine gider. Bu URL şu biçimde olur:

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

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

Resim adının yanındaki onay kutusunu işaretleyerek DELETE düğmesi etkinleşir ve bu ilk resmi silebilirsiniz.

7. Veritabanını hazırlama

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

9e4708d2257de058.png

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

SELECT NATIVE MODE seçeneğini tıklayın.

9449ace8cc84de43.png

Çoklu bölge seçin (Avrupa'da ancak tercihen en azından işlevinizin ve depolama paketinizin bulunduğu bölgeyle aynı bölge).

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

Veritabanı oluşturulduktan sonra şunu göreceksiniz:

56265949a124819e.png

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

pictures ad koleksiyonu.

75806ee24c4e13a7.png

Doküman oluşturmanız gerekmez. Yeni resimler Cloud Storage'da depolanıp Vision API tarafından analiz edilirken bu resimleri programatik olarak eklersiniz.

Save simgesini tıklayın.

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

5c2f1e17ea47f48f.png

Koleksiyonumuzda programlı olarak oluşturulacak dokümanlar 4 alan içerecektir:

  • name (dize): yüklenen resmin dosya adı ve aynı zamanda dokümanın anahtarıdır
  • labels (dize dizisi): Vision API tarafından tanınan öğelerin etiketleri
  • color (dize): baskın rengin onaltılık renk kodu (ör. #ab12ef)
  • create (tarih): Bu görsele ait meta verilerin saklandığı zaman damgası
  • thumbnail (boole): Bu resim için bir küçük resim oluşturulmuşsa mevcut ve doğru olacak isteğe bağlı bir alan

Küçük resimleri olan resimleri bulmak için Firestore'da arama yapacağımızdan ve oluşturma tarihine göre sıralayacağımız için bir arama dizini oluşturmamız gerekecek.

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

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

Bu işlemi Cloud Console'dan da yapabilirsiniz. Bunun için soldaki gezinme sütununda Indexes simgesini tıklayın ve ardından aşağıda gösterildiği gibi bir birleşik dizin oluşturun:

ecb8b95e3c791272.png

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

8. Kodu klonlama

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

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

Ardından, hizmeti içeren dizine giderek laboratuvarı derlemeye başlayabilirsiniz:

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şfedin

Bir BOM kullanarak, pom.xml içindeki Java İstemci Kitaplıklarının nasıl etkinleştirildiğine bakarak başlayın:

Ö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>

İşlevler, EventController sınıfında uygulanmıştır. Pakete her yeni resim yüklendiğinde hizmet, aşağıdakileri işlemesi 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 başlıklarını 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, bu tür bir isteği Vision API cihazına göndermek için 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: Resimlerdekileri 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ı öğrenmek (yetişkinlere uygun / tıbbi / müstehcen / şiddet barındıran içerik barındırmamalıdır)

Bu noktada Vision API'ye çağrı yapabiliriz:

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

Referans olarak, Vision API'nin yanıtı şu şekilde görünür:

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

Herhangi bir hata döndürülmezse işleme devam edebiliriz. Dolayısıyla, engelleme durumunda neden şu hatayı gördüğümü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 algılanan öğ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);
}

Olası veya çok olası olup olmadıklarını görmek için yetişkinlere uygun / adres sahteciliği / tıbbi / şiddet / müstehcenlik özelliklerini kontrol ediyoruz.

Güvenli aramanın sonucu uygunsa meta verileri Firestore'da depolayabiliriz:

// 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 Derleme (isteğe bağlı)

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

Derlemeyi çalıştırmak için uygun bir JDK'nın ve yerel görüntü 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'ı indirin ve GraalVM yükleme sayfasındaki talimatları uygulayın.

Bu işlem, SDKMAN!

SDKman ile uygun JDK dağıtımını yüklemek için yükleme komutunu kullanarak başlayın:

sdk install java 22.2.r17-grl

SDKman'a hem JIT hem de AOT derlemeleri için bu sürümü kullanma talimatı verin:

sdk use java 22.2.0.r17-grl

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

gu install native-image

Cloudshell ürününde, aşağıdaki basit komutlarla GraalVM ve yerel görüntü yardımcı programını 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 ../..

İlk olarak GCP proje ortamı değişkenlerini ayarlayın:

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

Ardından, hizmeti içeren dizine giderek laboratuvarı derlemeye başlayabilirsiniz:

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

JIT(JVM) uygulama görüntüsünü derleyin:

./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(Yerel) görüntüsünü oluşturun:

./mvnw package -Pnative -DskipTests

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

Derlemenin test ettiğiniz makineye bağlı olarak çok daha uzun süreceğ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

Şimdi, biri JIT(JVM) image ve diğeri AOT(Native) Java image olacak şekilde iki farklı sürümde bir container görüntüsü oluşturalım.

İlk olarak 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(Yerel) görüntüsünü oluşturun:

./mvnw package -Pnative-image

UPX kullanarak yerel görüntü derleme günlükleri ve görüntü sıkıştırma dahil olmak üzere terminaldeki derleme günlüğünü inceleyin.

Derlemenin test ettiğiniz makineye bağlı olarak çok daha uzun süreceğ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 derlendiğini doğrulayın:

docker images | grep image-analysis

İki görüntüyü etiketleyip GCR'ye aktarın:

# 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, biri JIT(JVM) görüntüsünü, ikincisinde AOT(Yerel) görüntüsünü kullanarak iki kez dağıtacaksınız. Her iki hizmet dağıtımı da karşılaştırma amacıyla paketteki aynı görüntüyü paralel olarak işler.

İlk olarak 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 dağıtım günlüğünü konsolda 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(Yerel) görüntüsünü dağıtın ve dağıtım günlüğünü konsolda 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 Tetikleyicileri Ayarlama

Eventarc, ayrıştırılan mikro hizmetler arasındaki durum değişikliklerinin (etkinlik adı verilen) akışını yönetmek amacıyla standartlaştırılmış bir çözüm sunar. Eventarc, tetiklendiğinde bu etkinlikleri Pub/Sub abonelikleri üzerinden çeşitli hedeflere (bu belgede, Etkinlik hedeflerine bakın) yönlendirir ve sizin için yayınlama, güvenlik, yetkilendirme, gözlemlenebilirlik ve hata işleme süreçlerini yönetir.

Cloud Run hizmetinizin belirli bir etkinlik veya etkinlik kümesinden bildirim alması için bir Eventarc tetikleyicisi oluşturabilirsiniz. Tetikleyici için filtreler belirterek, etkinlik kaynağı ve hedef Cloud Run hizmeti de dahil olmak üzere etkinliğin yönlendirmesini yapılandırabilirsiniz.

İlk olarak 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'

Görüntüyü işlemek üzere hem JVM(JIT) hem de AOT(Yerel) hizmet görüntüleri 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 inceleyin:

gcloud eventarc triggers list --location=eu

14. Test Hizmeti Sürümleri

Hizmet dağıtımları başarılı olduktan sonra Cloud Storage'da bir resim yayınlayarak 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örürsünüz.

Cloud Storage hizmetine geri dönün 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, /services/image-analysis/java altında kod tabanınızla birlikte bir GeekHour.jpeg resmi sağlanır. Bir resim seçip Open button tuşuna basın:

347b76e8b775f2f5.png

Artık hizmetin yürütülmesini image-analysis-jvm ile başlayıp image-analysis-native ile devam ettirebilirsiniz.

"Hamburger"den (Gönder) menüsünden Cloud Run > image-analysis-jvm hizmetine gidin.

Günlükler'i tıklayın ve sonucu inceleyin:

810a8684414ceafa.png

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

Günlükler, hizmet yürütme işleminin başlangıcını ve bitişini gösterir. Bunların arasında, işlevimize koyduğumuz günlükleri, günlük ifadeleriyle INFO düzeyinde görebiliriz. Şunları görürüz:

  • İşlevimizi tetikleyen olayın ayrıntıları,
  • Vision API çağrısından gelen ham sonuçlar,
  • Yüklediğimiz resimde bulunan etiketler,
  • Baskın renklerle ilgili bilgiler,
  • Resmin gösterilmesinin güvenli olup olmadığı,
  • Sonunda resimle ilgili bu meta veriler Firestore'da saklanır.

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

"Hamburger"den (Gönder) menüsünden Cloud Run > image-analysis-native hizmetine gidin.

Günlükler'i tıklayın ve sonucu inceleyin:

b80308c7d0f55a3.png

Resim meta verilerinin Fiorestore'da depolanıp depolanmadığını şimdi gözlemlemek isteyebilirsiniz.

Yine "hamburger"den (Gönder) menüsünden Firestore bölümüne gidin. Data alt bölümünde (varsayılan olarak gösterilmektedir), az önce yüklediğiniz resimle ilgili yeni bir doküman eklenmiş pictures koleksiyonunu görürsünüz:

933a20a9709cb006.png

15. Temizleme (İsteğe bağlı)

Serideki diğer laboratuvarlarla devam etmeyi düşünmüyorsanız maliyet tasarrufu yapmak ve genel olarak iyi bir bulut vatandaşı olmak için kaynakları temizleyebilirsiniz. Kaynakları aşağıda açıklandığı şekilde tek tek temizleyebilirsiniz.

Paketi silin:

gsutil rb gs://${BUCKET_PICTURES}

İşlevi silin:

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.

İşlediklerimiz

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

Sonraki Adımlar