1. 개요
이 Codelab에서는 이전 실습을 기반으로 빌드하여 썸네일 서비스를 추가합니다. 썸네일 서비스는 큰 사진을 찍어서 썸네일을 만드는 웹 컨테이너입니다.
사진이 Cloud Storage에 업로드되면 알림이 Cloud Pub/Sub를 통해 Cloud Run 웹 컨테이너로 전송되며, 알림은 이미지 크기를 조절하고 Cloud Storage의 다른 버킷에 다시 저장합니다.
학습할 내용
- Cloud Run
- Cloud Storage
- Cloud Pub/Sub
2. 설정 및 요구사항
자습형 환경 설정
- Google Cloud Console에 로그인하여 새 프로젝트를 만들거나 기존 프로젝트를 재사용합니다. 아직 Gmail이나 Google Workspace 계정이 없는 경우 계정을 만들어야 합니다.
- 프로젝트 이름은 이 프로젝트 참가자의 표시 이름입니다. 이는 Google API에서 사용하지 않는 문자열이며 언제든지 업데이트할 수 있습니다.
- 프로젝트 ID는 모든 Google Cloud 프로젝트에서 고유해야 하며, 변경할 수 없습니다(설정된 후에는 변경할 수 없음). Cloud Console은 고유한 문자열을 자동으로 생성합니다. 일반적으로 신경 쓰지 않아도 됩니다. 대부분의 Codelab에서는 프로젝트 ID를 참조해야 하며(일반적으로
PROJECT_ID
로 식별됨), 마음에 들지 않는 경우 임의로 다시 생성하거나 직접 지정해서 사용할 수 있는지 확인하세요. 프로젝트가 생성되면 프로젝트 ID가 '고정'됩니다. - 세 번째 값은 일부 API에서 사용하는 프로젝트 번호입니다. 이 세 가지 값에 대한 자세한 내용은 문서를 참조하세요.
- 다음으로 Cloud 리소스/API를 사용하려면 Cloud Console에서 결제를 사용 설정해야 합니다. 이 Codelab 실행에는 많은 비용이 들지 않습니다. 이 튜토리얼을 마친 후 비용이 결제되지 않도록 리소스를 종료하려면 Codelab의 끝에 있는 '삭제' 안내를 따르세요. Google Cloud 새 사용자에게는 미화 $300 상당의 무료 체험판 프로그램에 참여할 수 있는 자격이 부여됩니다.
Cloud Shell 시작
Google Cloud를 노트북에서 원격으로 실행할 수 있지만, 이 Codelab에서는 Cloud에서 실행되는 명령줄 환경인 Google Cloud Shell을 사용합니다.
GCP 콘솔에서 오른쪽 상단 툴바의 Cloud Shell 아이콘을 클릭합니다.
환경을 프로비저닝하고 연결하는 데 몇 분 정도 소요됩니다. 완료되면 다음과 같이 표시됩니다.
가상 머신에는 필요한 개발 도구가 모두 들어있습니다. 영구적인 5GB 홈 디렉토리를 제공하고 Google Cloud에서 실행되므로 네트워크 성능과 인증이 크게 개선됩니다. 이 실습의 모든 작업은 브라우저만으로 수행할 수 있습니다.
3. API 사용 설정
이 실습에서는 컨테이너 이미지를 빌드하려면 Cloud Build가 필요하고, 컨테이너를 배포하려면 Cloud Run이 필요합니다.
Cloud Shell에서 두 API를 모두 사용 설정합니다.
gcloud services enable cloudbuild.googleapis.com \ run.googleapis.com
작업이 성공적으로 완료됩니다.
Operation "operations/acf.5c5ef4f6-f734-455d-b2f0-ee70b5a17322" finished successfully.
4. 다른 버킷 만들기
업로드한 사진의 썸네일을 다른 버킷에 저장합니다. gsutil
를 사용하여 두 번째 버킷을 만들어 보겠습니다.
Cloud Shell 내에서 고유한 버킷 이름의 변수를 설정합니다. Cloud Shell에 이미 고유한 프로젝트 ID로 설정된 GOOGLE_CLOUD_PROJECT
가 있습니다. 버킷 이름에 추가할 수 있습니다. 그런 다음 균일한 액세스 권한으로 유럽에 공개 멀티 리전 버킷을 만듭니다.
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
최종적으로 새 공개 버킷이 생성됩니다.
5. 코드 클론
코드를 클론하고 서비스가 포함된 디렉터리로 이동합니다.
git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop cd serverless-photosharing-workshop/services/thumbnails/nodejs
서비스의 파일 레이아웃은 다음과 같습니다.
services | ├── thumbnails | ├── nodejs | ├── Dockerfile ├── index.js ├── package.json
thumbnails/nodejs
폴더 내에 다음 3개의 파일이 있습니다.
index.js
에는 Node.js 코드가 포함됩니다.package.json
는 라이브러리 종속 항목을 정의합니다.Dockerfile
는 컨테이너 이미지를 정의합니다.
6. 코드 살펴보기
Cloud Shell 창 상단에 있는 Open Editor
버튼을 클릭하여 기본 제공되는 텍스트 편집기를 사용하여 코드를 탐색할 수 있습니다.
또한 전용 브라우저 창에서 편집기를 열어 화면 공간을 더 확보할 수도 있습니다.
종속 항목
package.json
파일은 필요한 라이브러리 종속 항목을 정의합니다.
{
"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"
}
}
Cloud Storage 라이브러리는 Cloud Storage 내에서 이미지 파일을 읽고 저장하는 데 사용됩니다. 사진 메타데이터를 업데이트하는 Firestore Express는 JavaScript / Node 웹 프레임워크입니다. 본문 파서 모듈은 수신되는 요청을 쉽게 파싱하는 데 사용됩니다. Bluebird는 Promise 처리에 사용되고 Imagemagick은 이미지 조작을 위한 라이브러리입니다.
Dockerfile
Dockerfile
는 애플리케이션의 컨테이너 이미지를 정의합니다.
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" ]
기본 이미지는 노드 14이고 imagemagick 라이브러리는 이미지 조작에 사용됩니다. 원본 및 썸네일 사진 파일을 보관하기 위해 일부 임시 디렉터리가 생성됩니다. 그러면 npm start
로 코드를 시작하기 전에 코드에 필요한 NPM 모듈이 설치됩니다.
index.js
이 프로그램의 기능을 더 잘 이해할 수 있도록 코드를 부분적으로 살펴보겠습니다.
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());
먼저 필요한 종속 항목을 요구하고 Express 웹 애플리케이션을 만들고, JSON 본문 파서를 사용하려고 한다는 것을 표시합니다. 수신 요청은 실제로 POST 요청을 통해 애플리케이션에 전송된 JSON 페이로드이기 때문입니다.
app.post('/', async (req, res) => {
try {
// ...
} catch (err) {
console.log(`Error: creating the thumbnail: ${err}`);
console.error(err);
res.status(500).send(err);
}
});
수신 페이로드는 / 기본 URL에서 수신되고 있으며, 몇 가지 오류 로직 처리로 코드를 래핑하여 Google Cloud 웹 콘솔의 Stackdriver Logging 인터페이스에 표시되는 로그를 확인하여 코드에서 오류가 발생할 수 있는 이유를 더 잘 파악하고 있습니다.
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}`);
Cloud Run 플랫폼에서 Pub/Sub 메시지는 HTTP POST 요청을 통해 JSON 페이로드 형식의 JSON 페이로드로 전송됩니다.
{
"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"
}
그러나 이 JSON 문서에서 정말 흥미로운 점은 실제로 message.data
속성에 포함되어 있다는 것입니다. 이 속성은 단지 문자열이지만 실제 페이로드를 Base64로 인코딩합니다. 따라서 위 코드가 이 속성의 Base64 콘텐츠를 디코딩합니다. 이 data
속성은 디코딩되면 Cloud Storage 이벤트 세부정보를 나타내는 다른 JSON 문서를 포함하며, 이러한 문서는 다른 메타데이터 중에서 파일 이름과 버킷 이름을 나타냅니다.
{
"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="
}
이미지 및 버킷 이름에 관심이 있습니다. 코드가 썸네일 처리를 위해 버킷에서 해당 이미지를 가져올 것이기 때문입니다.
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}`);
환경 변수에서 출력 스토리지 버킷의 이름을 가져오고 있습니다.
파일 생성이 Cloud Run 서비스를 트리거한 원본 버킷과 결과 이미지를 저장할 대상 버킷이 있습니다. imagemagick 라이브러리가 /tmp
임시 디렉터리에 로컬로 썸네일을 생성하므로 path
내장 API를 사용하여 로컬 파일을 처리합니다. 업로드된 이미지 파일을 다운로드하는 비동기 호출을 await
합니다.
const resizeCrop = Promise.promisify(im.crop);
await resizeCrop({
srcPath: originalFile,
dstPath: thumbFile,
width: 400,
height: 400
});
console.log(`Created local thumbnail in ${thumbFile}`);
imagemagick 모듈은 async
/ await
와 그다지 친숙하지 않으므로 Bluebird 모듈에서 제공하는 JavaScript 프로미스 내에서 래핑합니다. 그런 다음 소스 및 대상 파일의 매개변수와 생성하려는 썸네일 크기를 사용하여 만든 비동기 크기 조절 / 자르기 함수를 호출합니다.
await thumbBucket.upload(thumbFile);
console.log(`Uploaded thumbnail to Cloud Storage bucket ${process.env.BUCKET_THUMBNAILS}`);
썸네일 파일이 Cloud Storage에 업로드되면 Cloud Firestore의 메타데이터도 업데이트하여 이 이미지의 썸네일이 실제로 생성되었음을 나타내는 불리언 플래그를 추가합니다.
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`);
요청이 끝나면 HTTP POST 요청에 파일이 제대로 처리되었다는 응답을 보냅니다.
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log(`Started thumbnail generator on port ${PORT}`);
});
소스 파일의 끝부분에는 Express가 실제로 8080 기본 포트에서 웹 애플리케이션을 시작하도록 하는 안내가 있습니다.
7. 로컬에서 테스트
클라우드에 배포하기 전에 로컬에서 코드를 테스트하여 작동하는지 확인합니다.
thumbnails/nodejs
폴더 내에 npm 종속 항목을 설치하고 서버를 시작합니다.
npm install; npm start
모든 것이 잘 진행되었다면 포트 8080에서 서버를 시작합니다.
Started thumbnail generator on port 8080
종료하려면 CTRL-C
을(를) 사용하세요.
8. 컨테이너 이미지 빌드 및 게시
Cloud Run은 컨테이너를 실행하지만 먼저 Dockerfile
에 정의된 컨테이너 이미지를 빌드해야 합니다. Google Cloud Build를 사용하여 컨테이너 이미지를 빌드한 다음 Google Container Registry에 호스팅할 수 있습니다.
Dockerfile
가 있는 thumbnails/nodejs
폴더 내에서 다음 명령어를 실행하여 컨테이너 이미지를 빌드합니다.
gcloud builds submit --tag gcr.io/$GOOGLE_CLOUD_PROJECT/thumbnail-service
1~2분 후에 빌드가 성공해야 합니다.
Cloud Build '기록' 섹션에도 성공한 빌드가 표시되어야 합니다.
'빌드 아티팩트'에서 빌드 ID를 클릭하여 세부정보 뷰 가져오기 탭에서 컨테이너 이미지가 Cloud Registry (GCR)에 업로드된 것을 확인할 수 있습니다.
원하는 경우 컨테이너 이미지가 Cloud Shell에서 로컬로 실행되는지 다시 확인할 수 있습니다.
docker run -p 8080:8080 gcr.io/$GOOGLE_CLOUD_PROJECT/thumbnail-service
컨테이너의 포트 8080에서 서버를 시작해야 합니다.
Started thumbnail generator on port 8080
종료하려면 CTRL-C
을(를) 사용하세요.
9. Cloud Run에 배포
Cloud Run에 배포하기 전에 Cloud Run 리전을 지원되는 리전 중 하나로 설정하고 플랫폼을 managed
으로 설정합니다.
gcloud config set run/region europe-west1 gcloud config set run/platform managed
다음 명령어로 구성이 설정되었는지 확인할 수 있습니다.
gcloud config list ... [run] platform = managed region = europe-west1
다음 명령어를 실행하여 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
--no-allow-unauthenticated
플래그를 확인합니다. 이렇게 하면 Cloud Run 서비스가 특정 서비스 계정에 의해서만 트리거되는 내부 서비스가 됩니다.
배포에 성공하면 다음과 같은 출력이 표시됩니다.
Cloud 콘솔 UI로 이동하면 서비스가 성공적으로 배포되었음을 확인할 수 있습니다.
10. Pub/Sub를 통해 Cloud Run에 대한 Cloud Storage 이벤트
서비스가 준비되었지만 여전히 새로 만든 Cloud Run 서비스에 Cloud Storage 이벤트를 만들어야 합니다. Cloud Storage는 Cloud Pub/Sub를 통해 파일 생성 이벤트를 전송할 수 있지만, 이를 위해서는 몇 가지 단계를 거쳐야 합니다.
Pub/Sub 주제를 통신 파이프라인으로 만듭니다.
TOPIC_NAME=cloudstorage-cloudrun-topic gcloud pubsub topics create $TOPIC_NAME
파일이 버킷에 저장될 때 Pub/Sub 알림을 만듭니다.
BUCKET_PICTURES=uploaded-pictures-$GOOGLE_CLOUD_PROJECT gsutil notification create -t $TOPIC_NAME -f json gs://$BUCKET_PICTURES
나중에 만들 Pub/Sub 구독의 서비스 계정을 만듭니다.
SERVICE_ACCOUNT=$TOPIC_NAME-sa gcloud iam service-accounts create $SERVICE_ACCOUNT \ --display-name "Cloud Run Pub/Sub Invoker"
서비스 계정에 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
2021년 4월 8일 이전에 Pub/Sub 서비스 계정을 사용 설정한 경우 Pub/Sub 서비스 계정에 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
IAM 변경사항이 전파되는 데 몇 분 정도 걸릴 수 있습니다.
마지막으로 서비스 계정으로 Pub/Sub 구독을 만듭니다.
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
구독이 생성되었는지 확인할 수 있습니다. 콘솔에서 Pub/Sub로 이동하여 gcs-events
주제를 선택하면 하단에 구독이 표시됩니다.
11. 서비스 테스트
설정이 작동하는지 테스트하려면 새 사진을 uploaded-pictures
버킷에 업로드하고 thumbnails
버킷에서 크기가 조절된 새 사진이 예상대로 표시되는지 확인합니다.
Cloud Run 서비스의 여러 단계가 진행됨에 따라 로그를 다시 확인하여 로깅 메시지가 표시되는지 확인할 수도 있습니다.
12. 정리(선택 사항)
이 시리즈의 다른 실습을 계속하지 않으려는 경우 리소스를 삭제하여 비용을 절감하고 전반적으로 클라우드를 잘 활용할 수 있습니다. 다음과 같이 리소스를 개별적으로 삭제할 수 있습니다.
버킷을 삭제합니다.
gsutil rb gs://$BUCKET_THUMBNAILS
서비스 삭제:
gcloud run services delete $SERVICE_NAME -q
Pub/Sub 주제를 삭제합니다.
gcloud pubsub topics delete $TOPIC_NAME
또는 전체 프로젝트를 삭제할 수 있습니다.
gcloud projects delete $GOOGLE_CLOUD_PROJECT
13. 축하합니다.
이제 모든 준비가 완료되었습니다.
- 새 사진이 업로드되면 주제에 관한 Pub/Sub 메시지를 전송하는 알림을 Cloud Storage에 만들었습니다.
- 필수 IAM 바인딩 및 계정을 정의했습니다 (모두 자동화된 Cloud Functions와 달리 여기에서 수동으로 구성됨).
- Cloud Run 서비스가 Pub/Sub 메시지를 수신하도록 구독을 만들었습니다.
- 새로운 Cloud Run 서비스 덕분에 새 사진이 버킷에 업로드될 때마다 사진의 크기가 조정됩니다.
학습한 내용
- Cloud Run
- Cloud Storage
- Cloud Pub/Sub