Pic-a-daily: Laboratuvar 1: Resimleri depolama ve analiz etme (Yerel Java)

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.

c0650ee4a76db35e.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.

295004821bab6a87.png

37d264871000675d.png

96d86d3d5655cdbe.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 benzersizdir 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ğinizi (genellikle PROJECT_ID olarak tanımlanır) belirtmeniz 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 olması açısından, 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ırmanın tekrarlanmasını önlemek amacıyla kaynakları kapatmak için oluşturduğunuz kaynakları silebilir veya projeyi 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:

84688aa223b1c3a2.png

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

320e18fedb7fbe0.png

İ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:

8f3522d790bb026c.png

Cloud Vision API sayfasına yönlendirilirsiniz:

d785572fa14c87c2.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.

d08ecb0ae29330a1.png

Paketinizi adlandırma

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

8951851554a430d2.png

CONTINUE simgesini tıklayın.

Konum Seçin

24b24625157ab467.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

9e7bd365fa94a2e0.png

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

CONTINUE simgesini tıklayın.

Erişim Denetimini Ayarlama

1ff4a1f6e57045f5.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

2d469b076029d365.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:

19564b3ad8688ae8.png

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

d655e760c76d62c1.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:

65c63ef4a6eb30ad.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).

e639a9ba625b71a6.png

Paketiniz artık resim almaya hazır.

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

1f88a2290290aba8.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.

1209e7ebe1f63b10.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:

e57a673537b5deca.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.

1a2e363fae5c7e96.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:

7dcc82751ed483fb.png

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

pictures ad koleksiyonu.

dce3d73884ac8c83.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:

63e95c844b3f79d3.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:

2236d3a024a59232.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:

4c2a18a2c8b69dc5.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 uygulamamızın bağımlılıklarının listelendiği pom.xml dosyasını açın; Vision, Cloud Storage ve Firestore API'lerinin kullanımına odaklanıyor.

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

İş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) {
          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. GraalVM ile Uygulama Görüntüleri Derleme

Bu isteğe bağlı adımda, GraalVM kullanarak önce bir JIT based app image, ardından bir 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.3.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 17.0.8-graal

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

sdk use java 17.0.8-graal

Cloudshell ürününde, aşağıdaki basit komutlarla GraalVM ve yerel görüntü yardımcı programını yükleyebilirsiniz:

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

İ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 uygulama görüntüsünü oluşturun:

./mvnw package

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

...
[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] ------------------------------------------------------------------------

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

./mvnw native:compile -Pnative

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...  [*********]                                                              (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. Kapsayıcı Görüntüleri Oluşturma ve Yayınlama

Şimdi, biri JIT image ve diğeri 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 görüntüsünü oluşturun:

./mvnw spring-boot:build-image -Pji

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

[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] ------------------------------------------------------------------------

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

./mvnw spring-boot:build-image -Pnative

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

Not:

  • üzerinde test ettiğiniz makineye bağlı olarak, derlemenin çok daha uzun sürmesi
  • resimler UPX ile daha fazla sıkıştırılabilir, ancak başlangıç performansı üzerinde küçük bir olumsuz etkiye sahiptir. Bu nedenle, bu derleme UPX kullanmaz. Her zaman hafif bir denge sağlar.
...
[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] ------------------------------------------------------------------------

Resimlerin derlendiğini doğrulayın:

docker images | grep image-analysis

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

# 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. Cloud Run'a dağıt

Hizmeti dağıtma zamanı.

Hizmeti, biri JIT 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 görüntüsünü dağıtın ve konsolda dağıtım günlüğünü inceleyin:

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

Yerel görüntüyü 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-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. 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'

Resmi işlemek üzere hem JIT hem de Yerel hizmet görüntüleri için Eventarc tetikleyicileri oluşturun:

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    

İ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:

33442485a1d76921.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:

d57529452f62bd32.png

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

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

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

ae1a4a94c7c7a166.png

Hatta günlük listesinde image-analysis-jit JIT 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:

4afe22833c1fd14c.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:

82d6c468956e7cfc.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:

6cc86a7b88fdb4d3.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