1. Przegląd
W tym laboratorium będziesz kontynuować pracę z poprzedniego laboratorium i dodasz usługę miniatur. Usługa miniatur to kontener internetowy, który pobiera duże obrazy i tworzy z nich miniatury.
Gdy obraz zostanie przesłany do Cloud Storage, powiadomienie jest wysyłane przez Cloud Pub/Sub do kontenera internetowego Cloud Run, który zmienia rozmiar obrazów i zapisuje 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 nazwa wyświetlana dla uczestników tego projektu. Jest to ciąg znaków, który nie jest używany przez interfejsy API Google. Możesz go w każdej chwili 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 laboratoriów będziesz musiał odwoływać się do identyfikatora projektu (zwykle oznaczanego jako
PROJECT_ID), więc 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 identyfikator jest „zamrożony”. - Istnieje też trzecia wartość – numer projektu, której 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 móc korzystać z zasobów i interfejsów API w chmurze. Wykonanie tego laboratorium nie powinno Cię nic kosztować. Aby wyłączyć zasoby i uniknąć naliczania opłat po zakończeniu tego samouczka, postępuj zgodnie z instrukcjami „czyszczenia” na końcu laboratorium. Nowi użytkownicy Google Cloud mogą skorzystać z programu bezpłatnego okresu próbnego o wartości 300 USD.
Uruchamianie Cloud Shell
Chociaż Google Cloud można obsługiwać zdalnie z laptopa, w tym laboratorium będziesz używać Google Cloud Shell, czyli środowiska wiersza poleceń działającego w chmurze.
W konsoli Google Cloud Platform kliknij ikonę Cloud Shell na pasku narzędzi w prawym górnym rogu:

Uzyskanie dostępu do środowiska i połączenie się z nim może zająć kilka chwil. Gdy to się uda, zobaczysz 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 laboratorium możesz wykonać za pomocą przeglądarki.
3. Włącz interfejsy API
W tym laboratorium 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 się zakończyć:
Operation "operations/acf.5c5ef4f6-f734-455d-b2f0-ee70b5a17322" finished successfully.
4. Utwórz kolejny zasobnik
Miniatury przesłanych zdjęć będziesz przechowywać w innym zasobniku. Użyjmy gsutil, aby utworzyć drugi zasobnik.
W Cloud Shell ustaw zmienną dla unikalnej nazwy zasobnika. Cloud Shell ma już ustawioną zmienną GOOGLE_CLOUD_PROJECT na Twój unikalny identyfikator projektu. Możesz ją dołączyć 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 powinieneś mieć nowy zasobnik publiczny:

5. Sklonuj kod
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 taką strukturę plików:
services
|
├── thumbnails
|
├── nodejs
|
├── Dockerfile
├── index.js
├── package.json
W folderze thumbnails/nodejs masz 3 pliki:
index.jszawiera kod Node.js.package.jsonokreśla zależności biblioteki.Dockerfileokreśla obraz kontenera.
6. Poznaj kod
Aby poznać kod, możesz użyć wbudowanego edytora tekstu, klikając przycisk Open Editor u góry okna Cloud Shell:

Możesz też otworzyć edytor w osobnym oknie przeglądarki, aby mieć więcej miejsca na ekranie.
Zależności
Plik package.json określa potrzebne 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 do aktualizowania metadanych 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. Tworzone są tymczasowe katalogi do przechowywania oryginalnych plików obrazów i miniatur. Następnie instalowane są moduły NPM potrzebne do działania kodu, a potem kod jest uruchamiany za pomocą polecenia npm start.
index.js
Przyjrzyjmy się kodowi po kolei, 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 potrzebnych zależności i tworzymy aplikację internetową Express, a także wskazujemy, że chcemy używać analizatora JSON, ponieważ przychodzące żądania to w rzeczywistości tylko ładunki JSON wysyłane za pomocą żądania POST do naszej aplikacji.
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 pod adresem URL / i otaczamy nasz kod logiką obsługi błędów, aby uzyskać więcej informacji o tym, dlaczego coś może się nie udać w naszym kodzie, sprawdzając logi, które będą widoczne w interfejsie Stackdriver Logging w konsoli 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 nasz kod powyżej dekoduje zawartość Base64 tego atrybutu. Atrybut data po zdekodowaniu zawiera kolejny dokument JSON, który reprezentuje szczegóły zdarzenia Cloud Storage, w tym między innymi 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 jego 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}`);
Pobieramy nazwę wyjściowego zasobnika z zmiennej środowiskowej.
Mamy zasobnik źródłowy, którego utworzenie pliku aktywował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. await oczekuje na asynchroniczne wywołanie, 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, dlatego otaczamy go obietnicą JavaScript (dostarczaną przez moduł Bluebird). Następnie wywołujemy utworzoną przez nas asynchroniczną funkcję zmiany rozmiaru / przycinania z parametrami plików źródłowych i docelowych oraz wymiarami 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 HTTP POST, ż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 mamy instrukcje, aby Express uruchomił naszą aplikację internetową na domyślnym porcie 8080.
7. Testowanie lokalne
Przed wdrożeniem w chmurze przetestuj kod lokalnie, aby sprawdzić, czy działa.
W folderze thumbnails/nodejs zainstaluj zależności npm i uruchom serwer:
npm install; npm start
Jeśli wszystko pójdzie dobrze, serwer powinien się uruchomić na porcie 8080:
Started thumbnail generator on port 8080
Aby wyjść, użyj kombinacji klawiszy 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żna używać Google Cloud Build.
W folderze thumbnails/nodejs, w którym znajduje się Dockerfile, wykonaj 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” Cloud Build powinna się też wyświetlić informacja o pomyślnej kompilacji:

Kliknij identyfikator kompilacji, aby wyświetlić szczegóły. Na karcie „Artefakty kompilacji” powinna się wyświetlić informacja, że obraz kontenera został 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 on uruchomić serwer na porcie 8080 w kontenerze:
Started thumbnail generator on port 8080
Aby wyjść, użyj kombinacji klawiszy 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
Uruchom to polecenie, aby wdrożyć obraz kontenera w Cloud Run:
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 niej usługa Cloud Run będzie usługą wewnętrzną, która będzie aktywowana tylko przez określone konta usługi.
Jeśli wdrożenie się powiedzie, powinny się wyświetlić te dane wyjściowe:

Jeśli otworzysz interfejs konsoli Cloud, zobaczysz też, że usługa została wdrożona:

10. Zdarzenia Cloud Storage w Cloud Run za pomocą Pub/Sub
Usługa jest gotowa, ale musisz jeszcze skonfigurować zdarzenia Cloud Storage w nowo utworzonej usłudze Cloud Run. Cloud Storage może wysyłać zdarzenia utworzenia pliku za pomocą Cloud Pub/Sub, ale aby to działało, trzeba wykonać kilka czynności.
Utwórz temat Pub/Sub jako potok komunikacyjny:
TOPIC_NAME=cloudstorage-cloudrun-topic gcloud pubsub topics create $TOPIC_NAME
Utwórz powiadomienia 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 do niego rolę iam.serviceAccountTokenCreator:
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
Zastosowanie zmian IAM może potrwać kilka minut.
Na koniec utwórz subskrypcję Pub/Sub z kontem 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 się wyświetlić 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ć, czy pojawiają się w nich komunikaty, gdy usługa Cloud Run przechodzi przez różne etapy:

12. Zwalnianie miejsca (opcjonalne)
Jeśli nie zamierzasz kontynuować pracy z innymi laboratoriami z tej serii, możesz zwolnić miejsce, aby zaoszczędzić koszty i być dobrym obywatelem chmury. Możesz zwalniać miejsce na zasobach pojedynczo 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ż gotowe:
- W Cloud Storage utworzono powiadomienie, które wysyła wiadomości Pub/Sub w temacie, gdy przesyłany jest nowy obraz.
- Zdefiniowano wymagane powiązania i konta IAM (w przeciwieństwie do Cloud Functions, gdzie wszystko jest zautomatyzowane, tutaj konfiguracja jest ręczna).
- Utworzono subskrypcję, aby nasza usługa Cloud Run otrzymywała wiadomości Pub/Sub.
- Gdy nowy obraz zostanie przesłany do zasobnika, jego rozmiar zostanie zmieniony dzięki nowej usłudze Cloud Run.
Omówione zagadnienia
- Cloud Run
- Cloud Storage
- Cloud Pub/Sub