1. Przegląd
W tym module rozwijasz poprzedni moduł i dodajesz usługę miniatur. Usługa miniatur to kontener internetowy, który pobiera duże zdjęcia i tworzy z nich miniatury.
Gdy obraz zostanie przesłany do Cloud Storage, za pomocą Cloud Pub/Sub do kontenera internetowego Cloud Run zostanie wysłane powiadomienie. Kontener zmieni rozmiar obrazów i zapisze je z powrotem w innym zasobniku w Cloud Storage.

Czego się nauczysz
- Cloud Run
- Cloud Storage
- Cloud Pub/Sub
2. Konfiguracja i wymagania
Samodzielne konfigurowanie środowiska
- Zaloguj się w konsoli Google Cloud i utwórz nowy projekt lub użyj istniejącego. Jeśli nie masz jeszcze konta Gmail ani Google Workspace, musisz je utworzyć.



- Nazwa projektu to wyświetlana nazwa uczestników tego projektu. Jest to ciąg znaków, który nie jest używany przez interfejsy API Google. Możesz go w dowolnym momencie zaktualizować.
- Identyfikator projektu musi być unikalny we wszystkich projektach Google Cloud i jest niezmienny (nie można go zmienić po ustawieniu). Konsola Cloud automatycznie generuje unikalny ciąg znaków. Zwykle nie musisz się nim przejmować. W większości modułów z kodem musisz odwoływać się do identyfikatora projektu (zwykle oznaczanego jako
PROJECT_ID). Jeśli Ci się nie podoba, wygeneruj inny losowy identyfikator lub spróbuj użyć własnego i sprawdź, czy jest dostępny. Po utworzeniu projektu jest on „zamrażany”. - Istnieje też trzecia wartość, czyli numer projektu, którego używają niektóre interfejsy API. Więcej informacji o tych 3 wartościach znajdziesz w dokumentacji.
- Następnie musisz włączyć płatności w konsoli Cloud, aby korzystać z zasobów i interfejsów API Google Cloud. Ukończenie tego laboratorium nie powinno wiązać się z dużymi kosztami, a nawet z żadnymi. Aby wyłączyć zasoby i uniknąć naliczenia opłat po zakończeniu tego samouczka, postępuj zgodnie z instrukcjami „czyszczenia” na końcu ćwiczenia. Nowi użytkownicy Google Cloud mogą skorzystać z bezpłatnego okresu próbnego, w którym mają do dyspozycji środki w wysokości 300 USD.
Uruchamianie Cloud Shell
Z Google Cloud można korzystać zdalnie na laptopie, ale w tym module praktycznym będziesz używać Google Cloud Shell, czyli środowiska wiersza poleceń działającego w chmurze.
W konsoli GCP kliknij ikonę Cloud Shell na pasku narzędzi w prawym górnym rogu:

Uzyskanie dostępu do środowiska i połączenie się z nim powinno zająć tylko kilka chwil. Po zakończeniu powinno wyświetlić się coś takiego:

Ta maszyna wirtualna zawiera wszystkie potrzebne narzędzia dla programistów. Zawiera również stały katalog domowy o pojemności 5 GB i działa w Google Cloud, co znacznie zwiększa wydajność sieci i usprawnia proces uwierzytelniania. Wszystkie zadania w tym module możesz wykonać w przeglądarce.
3. Włącz interfejsy API
W tym module będziesz potrzebować Cloud Build do tworzenia obrazów kontenerów i Cloud Run do wdrażania kontenera.
Włącz oba interfejsy API w Cloud Shell:
gcloud services enable cloudbuild.googleapis.com \ run.googleapis.com
Operacja powinna zakończyć się powodzeniem:
Operation "operations/acf.5c5ef4f6-f734-455d-b2f0-ee70b5a17322" finished successfully.
4. Tworzenie kolejnego zasobnika
Miniatury przesłanych zdjęć będziesz przechowywać w innym zasobniku. Użyjmy polecenia gsutil, aby utworzyć drugi zasobnik.
W Cloud Shell ustaw zmienną dla unikalnej nazwy zasobnika. W Cloud Shell zmienna GOOGLE_CLOUD_PROJECT jest już ustawiona na Twój unikalny identyfikator projektu. Możesz dodać go do nazwy zasobnika. Następnie utwórz publiczny zasobnik z wieloma regionami w Europie z jednolitym poziomem dostępu:
BUCKET_THUMBNAILS=thumbnails-$GOOGLE_CLOUD_PROJECT gsutil mb -l EU gs://$BUCKET_THUMBNAILS gsutil uniformbucketlevelaccess set on gs://$BUCKET_THUMBNAILS gsutil iam ch allUsers:objectViewer gs://$BUCKET_THUMBNAILS
Na koniec powinna pojawić się nowa publiczna pula:

5. Klonowanie kodu
Sklonuj kod i przejdź do katalogu zawierającego usługę:
git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop cd serverless-photosharing-workshop/services/thumbnails/nodejs
Usługa będzie miała ten układ pliku:
services
|
├── thumbnails
|
├── nodejs
|
├── Dockerfile
├── index.js
├── package.json
W folderze thumbnails/nodejs znajdują się 3 pliki:
index.jszawiera kod Node.js.package.jsonokreśla zależności biblioteki.Dockerfiledefiniuje obraz kontenera.
6. Poznaj kod
Aby przejrzeć kod, możesz użyć wbudowanego edytora tekstu. W tym celu kliknij przycisk Open Editor u góry okna Cloud Shell:

Możesz też otworzyć edytor w osobnym oknie przeglądarki, aby zyskać więcej miejsca na ekranie.
Zależności
Plik package.json określa wymagane zależności biblioteki:
{
"name": "thumbnail_service",
"version": "0.0.1",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"bluebird": "^3.7.2",
"express": "^4.17.1",
"imagemagick": "^0.1.3",
"@google-cloud/firestore": "^4.9.9",
"@google-cloud/storage": "^5.8.3"
}
}
Biblioteka Cloud Storage służy do odczytywania i zapisywania plików obrazów w Cloud Storage. Firestore, aby zaktualizować metadane obrazu. Express to platforma internetowa JavaScript / Node. Moduł body-parser służy do łatwego analizowania przychodzących żądań. Bluebird służy do obsługi obietnic, a Imagemagick to biblioteka do manipulowania obrazami.
Dockerfile
Dockerfile określa obraz kontenera aplikacji:
FROM node:14-slim
# installing Imagemagick
RUN set -ex; \
apt-get -y update; \
apt-get -y install imagemagick; \
rm -rf /var/lib/apt/lists/*; \
mkdir /tmp/original; \
mkdir /tmp/thumbnail;
WORKDIR /picadaily/services/thumbnails
COPY package*.json ./
RUN npm install --production
COPY . .
CMD [ "npm", "start" ]
Obrazem bazowym jest Node 14, a do manipulowania obrazami używana jest biblioteka ImageMagick. Niektóre katalogi tymczasowe są tworzone w celu przechowywania oryginalnych plików obrazów i plików miniatur. Następnie instalowane są moduły NPM potrzebne do działania naszego kodu, a potem kod jest uruchamiany za pomocą polecenia npm start.
index.js
Przyjrzyjmy się poszczególnym częściom kodu, aby lepiej zrozumieć, co robi ten program.
const express = require('express');
const imageMagick = require('imagemagick');
const Promise = require("bluebird");
const path = require('path');
const {Storage} = require('@google-cloud/storage');
const Firestore = require('@google-cloud/firestore');
const app = express();
app.use(express.json());
Najpierw wymagamy niezbędnych zależności i tworzymy aplikację internetową Express, a także wskazujemy, że chcemy używać analizatora treści JSON, ponieważ przychodzące żądania to w rzeczywistości tylko ładunki JSON wysyłane do naszej aplikacji za pomocą żądania POST.
app.post('/', async (req, res) => {
try {
// ...
} catch (err) {
console.log(`Error: creating the thumbnail: ${err}`);
console.error(err);
res.status(500).send(err);
}
});
Otrzymujemy te przychodzące ładunki na adresie URL / i otaczamy nasz kod logiką obsługi błędów, aby uzyskać lepsze informacje o tym, dlaczego coś może się nie udać w naszym kodzie. W tym celu sprawdzamy logi, które będą widoczne w interfejsie Stackdriver Logging w konsoli internetowej Google Cloud.
const pubSubMessage = req.body;
console.log(`PubSub message: ${JSON.stringify(pubSubMessage)}`);
const fileEvent = JSON.parse(Buffer.from(pubSubMessage.message.data, 'base64').toString().trim());
console.log(`Received thumbnail request for file ${fileEvent.name} from bucket ${fileEvent.bucket}`);
Na platformie Cloud Run wiadomości Pub/Sub są wysyłane za pomocą żądań HTTP POST jako ładunki JSON w formacie:
{
"message": {
"attributes": {
"bucketId": "uploaded-pictures",
"eventTime": "2020-02-27T09:22:43.255225Z",
"eventType": "OBJECT_FINALIZE",
"notificationConfig": "projects/_/buckets/uploaded-pictures/notificationConfigs/28",
"objectGeneration": "1582795363255481",
"objectId": "IMG_20200213_181159.jpg",
"payloadFormat": "JSON_API_V1"
},
"data": "ewogICJraW5kIjogInN0b3JhZ2Ujb2JqZWN...FQUU9Igp9Cg==",
"messageId": "1014308302773399",
"message_id": "1014308302773399",
"publishTime": "2020-02-27T09:22:43.973Z",
"publish_time": "2020-02-27T09:22:43.973Z"
},
"subscription": "projects/serverless-picadaily/subscriptions/gcs-events-subscription"
}
Jednak to, co jest naprawdę interesujące w tym dokumencie JSON, to zawartość atrybutu message.data, który jest po prostu ciągiem znaków, ale koduje rzeczywisty ładunek w formacie Base64. Dlatego w naszym kodzie powyżej dekodujemy zawartość tego atrybutu w formacie Base64. Ten atrybut data po zdekodowaniu zawiera kolejny dokument JSON, który reprezentuje szczegóły zdarzenia Cloud Storage, w tym nazwę pliku i nazwę zasobnika.
{
"kind": "storage#object",
"id": "uploaded-pictures/IMG_20200213_181159.jpg/1582795363255481",
"selfLink": "https://www.googleapis.com/storage/v1/b/uploaded-pictures/o/IMG_20200213_181159.jpg",
"name": "IMG_20200213_181159.jpg",
"bucket": "uploaded-pictures",
"generation": "1582795363255481",
"metageneration": "1",
"contentType": "image/jpeg",
"timeCreated": "2020-02-27T09:22:43.255Z",
"updated": "2020-02-27T09:22:43.255Z",
"storageClass": "STANDARD",
"timeStorageClassUpdated": "2020-02-27T09:22:43.255Z",
"size": "4944335",
"md5Hash": "QzBIoPJBV2EvqB1EVk1riw==",
"mediaLink": "https://www.googleapis.com/download/storage/v1/b/uploaded-pictures/o/IMG_20200213_181159.jpg?generation=1582795363255481&alt=media",
"crc32c": "hQ3uHg==",
"etag": "CLmJhJu08ecCEAE="
}
Interesują nas nazwy obrazu i zasobnika, ponieważ nasz kod pobierze ten obraz z zasobnika w celu utworzenia miniatury:
const bucket = storage.bucket(fileEvent.bucket);
const thumbBucket = storage.bucket(process.env.BUCKET_THUMBNAILS);
const originalFile = path.resolve('/tmp/original', fileEvent.name);
const thumbFile = path.resolve('/tmp/thumbnail', fileEvent.name);
await bucket.file(fileEvent.name).download({
destination: originalFile
});
console.log(`Downloaded picture into ${originalFile}`);
Nazwę wyjściowego zasobnika na dane pobieramy ze zmiennej środowiskowej.
Mamy zasobnik źródłowy, w którym utworzenie pliku wywołało naszą usługę Cloud Run, oraz zasobnik docelowy, w którym będziemy przechowywać wynikowy obraz. Do obsługi plików lokalnych używamy wbudowanego interfejsu API path, ponieważ biblioteka ImageMagick utworzy miniaturę lokalnie w katalogu tymczasowym /tmp. Wysyłamy await w przypadku wywołania asynchronicznego, aby pobrać przesłany plik obrazu.
const resizeCrop = Promise.promisify(im.crop);
await resizeCrop({
srcPath: originalFile,
dstPath: thumbFile,
width: 400,
height: 400
});
console.log(`Created local thumbnail in ${thumbFile}`);
Moduł imagemagick nie jest zbyt przyjazny dla async / await, więc umieszczamy go w obietnicy JavaScript (zapewnianej przez moduł Bluebird). Następnie wywołujemy utworzoną przez nas asynchroniczną funkcję zmiany rozmiaru lub przycinania, podając parametry plików źródłowego i docelowego oraz wymiary miniatury, którą chcemy utworzyć.
await thumbBucket.upload(thumbFile);
console.log(`Uploaded thumbnail to Cloud Storage bucket ${process.env.BUCKET_THUMBNAILS}`);
Gdy plik miniatury zostanie przesłany do Cloud Storage, zaktualizujemy też metadane w Cloud Firestore, aby dodać flagę logiczną wskazującą, że miniatura tego obrazu została wygenerowana:
const pictureStore = new Firestore().collection('pictures');
const doc = pictureStore.doc(fileEvent.name);
await doc.set({
thumbnail: true
}, {merge: true});
console.log(`Updated Firestore about thumbnail creation for ${fileEvent.name}`);
res.status(204).send(`${fileEvent.name} processed`);
Po zakończeniu żądania odpowiadamy na żądanie POST HTTP, że plik został prawidłowo przetworzony.
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log(`Started thumbnail generator on port ${PORT}`);
});
Na końcu pliku źródłowego znajdują się instrukcje, które powodują, że Express uruchamia aplikację internetową na domyślnym porcie 8080.
7. Testowanie lokalne
Przetestuj kod lokalnie, aby sprawdzić, czy działa, zanim wdrożysz go w chmurze.
W folderze thumbnails/nodejs zainstaluj zależności npm i uruchom serwer:
npm install; npm start
Jeśli wszystko przebiegło pomyślnie, serwer powinien zostać uruchomiony na porcie 8080:
Started thumbnail generator on port 8080
Aby wyjść, użyj CTRL-C.
8. Tworzenie i publikowanie obrazu kontenera
Cloud Run uruchamia kontenery, ale najpierw musisz utworzyć obraz kontenera (zdefiniowany w Dockerfile). Do tworzenia obrazów kontenerów i hostowania ich w Google Container Registry możesz użyć Google Cloud Build.
W folderze thumbnails/nodejs, w którym znajduje się plik Dockerfile, uruchom to polecenie, aby utworzyć obraz kontenera:
gcloud builds submit --tag gcr.io/$GOOGLE_CLOUD_PROJECT/thumbnail-service
Po minucie lub dwóch kompilacja powinna się zakończyć:

W sekcji „Historia” kompilacji Cloud Build powinna być widoczna udana kompilacja:

Kliknij identyfikator kompilacji, aby wyświetlić szczegóły. Na karcie „Artefakty kompilacji” powinien być widoczny obraz kontenera przesłany do Cloud Registry (GCR):

Jeśli chcesz, możesz sprawdzić, czy obraz kontenera działa lokalnie w Cloud Shell:
docker run -p 8080:8080 gcr.io/$GOOGLE_CLOUD_PROJECT/thumbnail-service
Powinien uruchomić serwer na porcie 8080 w kontenerze:
Started thumbnail generator on port 8080
Aby wyjść, użyj CTRL-C.
9. Wdrożenie w Cloud Run
Przed wdrożeniem w Cloud Run ustaw region Cloud Run na jeden z obsługiwanych regionów, a platformę na managed:
gcloud config set run/region europe-west1 gcloud config set run/platform managed
Możesz sprawdzić, czy konfiguracja jest ustawiona:
gcloud config list ... [run] platform = managed region = europe-west1
Aby wdrożyć obraz kontenera w Cloud Run, uruchom to polecenie:
SERVICE_NAME=thumbnail-service
gcloud run deploy $SERVICE_NAME \
--image gcr.io/$GOOGLE_CLOUD_PROJECT/thumbnail-service \
--no-allow-unauthenticated \
--update-env-vars BUCKET_THUMBNAILS=$BUCKET_THUMBNAILS
Zwróć uwagę na flagę --no-allow-unauthenticated. Dzięki temu usługa Cloud Run staje się usługą wewnętrzną, która będzie wywoływana tylko przez określone konta usługi.
Jeśli wdrożenie się powiedzie, powinny się wyświetlić te dane wyjściowe:

Jeśli przejdziesz do interfejsu konsoli Google Cloud, zobaczysz, że usługa została wdrożona:

10. Zdarzenia Cloud Storage do Cloud Run za pomocą Pub/Sub
Usługa jest gotowa, ale nadal musisz kierować zdarzenia Cloud Storage do nowo utworzonej usługi Cloud Run. Cloud Storage może wysyłać zdarzenia utworzenia pliku za pomocą Cloud Pub/Sub, ale aby to działało, musisz wykonać kilka czynności.
Utwórz temat Pub/Sub jako potok komunikacyjny:
TOPIC_NAME=cloudstorage-cloudrun-topic gcloud pubsub topics create $TOPIC_NAME
Tworzenie powiadomień Pub/Sub, gdy pliki są przechowywane w zasobniku:
BUCKET_PICTURES=uploaded-pictures-$GOOGLE_CLOUD_PROJECT gsutil notification create -t $TOPIC_NAME -f json gs://$BUCKET_PICTURES
Utwórz konto usługi na potrzeby subskrypcji Pub/Sub, którą utworzymy później:
SERVICE_ACCOUNT=$TOPIC_NAME-sa
gcloud iam service-accounts create $SERVICE_ACCOUNT \
--display-name "Cloud Run Pub/Sub Invoker"
Przyznaj kontu usługi uprawnienia do wywoływania usługi Cloud Run:
SERVICE_NAME=thumbnail-service gcloud run services add-iam-policy-binding $SERVICE_NAME \ --member=serviceAccount:$SERVICE_ACCOUNT@$GOOGLE_CLOUD_PROJECT.iam.gserviceaccount.com \ --role=roles/run.invoker
Jeśli konto usługi Pub/Sub zostało włączone 8 kwietnia 2021 r. lub wcześniej, przypisz rolę iam.serviceAccountTokenCreator do konta usługi Pub/Sub:
PROJECT_NUMBER=$(gcloud projects describe $GOOGLE_CLOUD_PROJECT --format='value(projectNumber)')
gcloud projects add-iam-policy-binding $GOOGLE_CLOUD_PROJECT \
--member=serviceAccount:service-$PROJECT_NUMBER@gcp-sa-pubsub.iam.gserviceaccount.com \
--role=roles/iam.serviceAccountTokenCreator
Rozpowszechnienie zmian w IAM może potrwać kilka minut.
Na koniec utwórz subskrypcję Pub/Sub za pomocą konta usługi:
SERVICE_URL=$(gcloud run services describe $SERVICE_NAME --format 'value(status.url)') gcloud pubsub subscriptions create $TOPIC_NAME-subscription --topic $TOPIC_NAME \ --push-endpoint=$SERVICE_URL \ --push-auth-service-account=$SERVICE_ACCOUNT@$GOOGLE_CLOUD_PROJECT.iam.gserviceaccount.com
Możesz sprawdzić, czy subskrypcja została utworzona. W konsoli otwórz Pub/Sub, wybierz temat gcs-events, a u dołu powinna być widoczna subskrypcja:

11. Testowanie usługi
Aby sprawdzić, czy konfiguracja działa, prześlij nowy obraz do zasobnika uploaded-pictures i sprawdź, czy w zasobniku thumbnails pojawiają się nowe obrazy o zmienionym rozmiarze.
Możesz też sprawdzić logi, aby zobaczyć pojawiające się wiadomości logowania w trakcie wykonywania poszczególnych kroków usługi Cloud Run:

12. Zwalnianie miejsca (opcjonalnie)
Jeśli nie zamierzasz kontynuować pracy z innymi ćwiczeniami z tej serii, możesz usunąć zasoby, aby zaoszczędzić pieniądze i być dobrym użytkownikiem chmury. Możesz zwolnić miejsce, czyszcząc poszczególne zasoby w ten sposób:
Usuń zasobnik:
gsutil rb gs://$BUCKET_THUMBNAILS
Usuń usługę:
gcloud run services delete $SERVICE_NAME -q
Usuń temat Pub/Sub:
gcloud pubsub topics delete $TOPIC_NAME
Możesz też usunąć cały projekt:
gcloud projects delete $GOOGLE_CLOUD_PROJECT
13. Gratulacje!
Wszystko jest już na miejscu:
- utworzono powiadomienie w Cloud Storage, które wysyła wiadomości Pub/Sub w temacie, gdy przesyłane jest nowe zdjęcie;
- Określ wymagane powiązania i konta IAM (w przeciwieństwie do Cloud Functions, gdzie wszystko jest zautomatyzowane, tutaj konfiguracja jest przeprowadzana ręcznie).
- Utworzyliśmy subskrypcję, aby usługa Cloud Run otrzymywała wiadomości Pub/Sub.
- Za każdym razem, gdy do zasobnika zostanie przesłany nowy obraz, zostanie on przeskalowany dzięki nowej usłudze Cloud Run.
Omówione zagadnienia
- Cloud Run
- Cloud Storage
- Cloud Pub/Sub