Google Cloud Platform을 사용하여 서버리스 프런트엔드 애플리케이션 구축
게시 됨: 2022-03-10최근에 애플리케이션의 개발 패러다임은 애플리케이션 내에서 사용되는 리소스를 수동으로 배포, 확장 및 업데이트해야 하는 것에서 이러한 리소스 관리의 대부분을 타사 클라우드 서비스 제공업체에 의존하는 방식으로 전환되기 시작했습니다.
가능한 한 가장 빠른 시간 내에 시장에 적합한 애플리케이션을 구축하고자 하는 개발자 또는 조직은 구성, 배포 및 스트레스 테스트에 더 적은 시간을 소비하면서 핵심 애플리케이션 서비스를 사용자에게 제공하는 데 주요 초점을 맞출 수 있습니다. 너의 어플리케이션. 이것이 사용 사례라면 서버리스 방식으로 애플리케이션의 비즈니스 로직을 처리하는 것이 최선의 선택이 될 수 있습니다. 하지만 어떻게?
이 문서는 애플리케이션 내에서 특정 기능을 구축하려는 프런트엔드 엔지니어 또는 Google Cloud Platform에 배포된 서버리스 애플리케이션을 사용하여 기존 백엔드 서비스에서 특정 기능을 추출하고 처리하려는 백엔드 엔지니어에게 유용합니다.
참고 : 여기에서 다룰 내용을 활용하려면 React로 작업한 경험이 있어야 합니다. 서버리스 애플리케이션에 대한 사전 경험이 필요하지 않습니다.
시작하기 전에 서버리스 애플리케이션이 실제로 무엇이며 프런트 엔드 엔지니어의 컨텍스트 내에서 애플리케이션을 구축할 때 서버리스 아키텍처를 사용할 수 있는 방법을 이해합시다.
서버리스 애플리케이션
서버리스 애플리케이션은 재사용 가능한 작은 이벤트 기반 기능으로 세분화된 애플리케이션으로, 애플리케이션 작성자를 대신하여 퍼블릭 클라우드 내에서 타사 클라우드 서비스 공급자가 호스팅하고 관리합니다. 이는 특정 이벤트에 의해 트리거되고 요청 시 실행됩니다. 서버리스 단어에 붙은 " less " 접미사는 서버가 없음을 나타내지만 100% 그런 것은 아닙니다. 이러한 애플리케이션은 여전히 서버 및 기타 하드웨어 리소스에서 실행되지만 이 경우 해당 리소스는 개발자가 아닌 타사 클라우드 서비스 공급자가 프로비저닝합니다. 따라서 애플리케이션 작성자에게는 서버 리스 이지만 여전히 서버에서 실행되며 공용 인터넷을 통해 액세스할 수 있습니다.
서버리스 애플리케이션의 사용 사례는 랜딩 페이지를 방문하고 제품 출시 이메일 수신을 구독하는 잠재 사용자에게 이메일을 보내는 것입니다. 이 단계에서는 백엔드 서비스를 실행하지 않고 이메일을 보내야 하기 때문에 백엔드 서비스를 생성, 배포 및 관리하는 데 필요한 시간과 리소스를 희생하고 싶지 않을 것입니다. 여기에서 이메일 클라이언트를 사용하는 단일 파일을 작성하고 서버리스 애플리케이션을 지원하는 모든 클라우드 제공업체에 배포하고 이 서버리스 애플리케이션을 랜딩 페이지에 연결하는 동안 그들이 귀하를 대신하여 이 애플리케이션을 관리하도록 할 수 있습니다.
서버리스 애플리케이션 또는 FAAS(Functions As A Service)를 호출할 때 활용하는 것을 고려할 수 있는 많은 이유가 있지만 프런트 엔드 애플리케이션의 경우 고려해야 할 몇 가지 주목할만한 이유는 다음과 같습니다.
- 애플리케이션 자동 확장
서버리스 애플리케이션은 수평으로 확장되며 이 "수평 확장 "은 호출량에 따라 클라우드 제공자가 자동으로 수행하므로 개발자는 애플리케이션에 과부하가 걸릴 때 리소스를 수동으로 추가하거나 제거할 필요가 없습니다. - 비용 효율성
이벤트 중심의 서버리스 애플리케이션은 필요할 때만 실행되며 이는 호출된 횟수를 기준으로 청구되므로 요금에 반영됩니다. - 유연성
서버리스 애플리케이션은 재사용 가능성이 높도록 구축되었으며 이는 단일 프로젝트 또는 애플리케이션에 구속되지 않음을 의미합니다. 특정 기능을 서버리스 애플리케이션으로 추출하여 여러 프로젝트 또는 애플리케이션에 배포 및 사용할 수 있습니다. 서버리스 애플리케이션은 애플리케이션 작성자가 선호하는 언어로 작성할 수도 있지만 일부 클라우드 제공업체는 더 적은 수의 언어만 지원합니다.
서버리스 애플리케이션을 사용할 때 모든 개발자는 사용할 퍼블릭 클라우드 내에 방대한 클라우드 제공업체를 보유하고 있습니다. 이 기사의 맥락에서 우리는 Google Cloud Platform의 서버리스 애플리케이션, 즉 애플리케이션을 생성, 관리, 배포하는 방법과 Google Cloud의 다른 제품과 통합하는 방법에 중점을 둘 것입니다. 이를 위해 다음 프로세스를 진행하는 동안 이 기존 React 애플리케이션에 새로운 기능을 추가합니다.
- 클라우드에 사용자 데이터 저장 및 검색
- Google Cloud에서 크론 작업 생성 및 관리
- Google Cloud에 Cloud Functions 배포
참고 : 서버리스 애플리케이션은 React에만 바인딩되지 않습니다. 선호하는 프론트엔드 프레임워크 또는 라이브러리가 HTTP
요청을 할 수 있는 한 서버리스 애플리케이션을 사용할 수 있습니다.
구글 클라우드 기능
Google Cloud를 통해 개발자는 Cloud Functions를 사용하여 서버리스 애플리케이션을 만들고 Functions Framework를 사용하여 실행할 수 있습니다. 호출될 때 Cloud 함수는 사용 가능한 6개의 이벤트 트리거 중 특정 트리거를 수신 대기하고 실행하도록 작성된 작업을 수행하기 위해 GCP에 배포된 재사용 가능한 이벤트 기반 함수입니다.
수명이 짧은 클라우드 기능( 기본 실행 제한 시간은 60초, 최대 9분 )은 JavaScript, Python, Golang 및 Java를 사용하여 작성하고 런타임을 사용하여 실행할 수 있습니다. 자바스크립트에서는 사용 가능한 일부 버전의 Node 런타임만 사용하여 실행할 수 있으며 GCP에서 실행될 기본 기능으로 내보내지기 때문에 일반 자바스크립트를 사용하여 CommonJS 모듈 형태로 작성됩니다.
클라우드 함수의 예는 아래 함수가 사용자 데이터를 처리하기 위한 빈 상용구인 것입니다.
// index.js exports.firestoreFunction = function (req, res) { return res.status(200).send({ data: `Hello ${req.query.name}` }); }
위에 함수를 내보내는 모듈이 있습니다. 실행되면 HTTP
경로와 유사한 요청 및 응답 인수를 수신합니다.
참고 : 클라우드 기능은 요청이 있을 때 모든 HTTP
프로토콜과 일치합니다. 클라우드 기능 실행을 요청할 때 첨부된 데이터가 GET
요청의 쿼리 본문에 있는 동안 POST
요청의 요청 본문에 있기 때문에 요청 인수의 데이터를 예상할 때 이것은 주목할 가치가 있습니다.
작성된 기능이 있는 동일한 폴더 내에 @google-cloud/functions-framework
패키지를 설치하거나 npm i -g @google-cloud/functions-framework
을 실행하여 여러 기능에 사용하도록 전역 설치를 수행하여 개발 중에 클라우드 기능을 로컬로 실행할 수 있습니다. 명령줄에서 npm i -g @google-cloud/functions-framework
. 일단 설치되면 아래와 유사한 내보낸 모듈의 이름을 사용하여 package.json
스크립트에 추가해야 합니다.
"scripts": { "start": "functions-framework --target=firestoreFunction --port=8000", }
위의 스크립트에는 functions-framework를 실행하고 firestoreFunction
을 포트 8000
에서 로컬로 실행할 대상 함수로 지정하는 package.json
파일의 단일 명령이 있습니다.
curl을 사용하여 localhost의 포트 8000
에 GET
요청을 함으로써 이 함수의 끝점을 테스트할 수 있습니다. 터미널에 아래 명령을 붙여넣으면 그렇게 하고 응답을 반환합니다.
curl https://localhost:8000?name="Smashing Magazine Author"
위의 명령어는 GET HTTP
메소드로 요청하고 쿼리에 추가된 이름이 포함된 객체 데이터와 200
상태 코드로 응답합니다.
Cloud 함수 배포
사용 가능한 배포 방법 중 로컬 머신에서 클라우드 기능을 배포하는 빠른 방법 중 하나는 클라우드 SDK를 설치한 후 사용하는 것입니다. GCP에서 프로젝트로 gcloud sdk를 인증한 후 터미널에서 아래 명령어를 실행하면 로컬에서 생성된 함수가 Cloud Function 서비스에 배포됩니다.
gcloud functions deploy "demo-function" --runtime nodejs10 --trigger-http --entry-point=demo --timeout=60 --set-env-vars=[name="Developer"] --allow-unauthenticated
아래 설명된 플래그를 사용하여 위의 명령은 " demo-function "이라는 이름으로 Google 클라우드에 HTTP 트리거 함수를 배포합니다.
- 이름
클라우드 기능을 배포할 때 부여되는 이름으로 필수 항목입니다. -
region
클라우드 기능이 배포될 지역입니다. 기본적으로us-central1
에 배포됩니다. -
trigger-http
이렇게 하면 함수의 트리거 유형으로 HTTP가 선택됩니다. -
allow-unauthenticated
이렇게 하면 호출자가 인증되었는지 확인하지 않고 생성된 엔드포인트를 사용하여 인터넷을 통해 Google Cloud 외부에서 함수를 호출할 수 있습니다. -
source
터미널에서 배포할 기능이 포함된 파일까지의 로컬 경로입니다. -
entry-point
함수가 작성된 파일에서 배포할 특정 내보낸 모듈입니다. -
runtime
이 허용 런타임 목록 중 기능에 사용할 언어 런타임입니다. -
timeout
이것은 시간이 초과되기 전에 함수를 실행할 수 있는 최대 시간입니다. 기본값은 60초이며 최대 9분까지 설정할 수 있습니다.
참고 : 함수를 인증되지 않은 요청을 허용하도록 설정하면 함수의 끝점이 있는 모든 사용자가 권한을 부여하지 않고도 요청할 수 있습니다. 이를 완화하기 위해 환경 변수를 통해 엔드포인트를 사용하거나 각 요청에 대해 인증 헤더를 요청하여 엔드포인트가 비공개로 유지되도록 할 수 있습니다.
이제 데모 기능이 배포되고 끝점이 있으므로 이 기능을 autocannon의 전역 설치를 사용하여 실제 응용 프로그램에서 사용하는 것처럼 테스트할 수 있습니다. 열린 터미널에서 autocannon -d=5 -c=300 CLOUD_FUNCTION_URL
을 실행하면 5초 동안 클라우드 기능에 대한 300개의 동시 요청이 생성됩니다. 이것은 클라우드 기능을 시작하고 기능의 대시보드에서 탐색할 수 있는 몇 가지 메트릭을 생성하기에 충분합니다.
참고 : 함수의 끝점은 배포 후 터미널에 인쇄됩니다. 그렇지 않은 경우 터미널에서 gcloud function describe FUNCTION_NAME
을 실행하여 엔드포인트를 포함하여 배포된 함수에 대한 세부정보를 가져옵니다.
대시보드의 메트릭 탭을 사용하여 수행된 호출 수, 지속 시간, 함수의 메모리 공간 및 요청을 처리하기 위해 회전된 인스턴스 수로 구성된 마지막 요청의 시각적 표현을 볼 수 있습니다.
위 이미지의 Active Instances 차트를 자세히 살펴보면 Cloud Functions의 수평 확장 용량을 볼 수 있습니다. autocannon을 사용하여 만든 요청을 처리하기 위해 몇 초 안에 209개의 인스턴스가 회전된 것을 볼 수 있습니다.
Cloud 함수 로그
Google 클라우드에 배포된 모든 기능에는 로그가 있으며 이 기능이 실행될 때마다 해당 로그에 새 항목이 만들어집니다. 함수 대시보드의 로그 탭에서 클라우드 함수의 모든 로그 항목 목록을 볼 수 있습니다.
다음은 autocannon
을 사용하여 만든 요청의 결과로 생성된 배포된 demo-function
의 로그 항목입니다.
위의 각 로그 항목은 함수가 실행된 시간, 실행 시간 및 종료 상태 코드를 정확히 보여줍니다. 함수로 인한 오류가 있는 경우 발생한 행을 포함하여 오류의 세부 정보가 여기 로그에 표시됩니다.
GCP의 로그 탐색기를 사용하여 클라우드 기능의 로그에 대한 보다 포괄적인 세부정보를 볼 수 있습니다.
프론트엔드 애플리케이션이 있는 Cloud Functions
클라우드 기능은 프런트 엔드 엔지니어에게 매우 유용하고 강력합니다. 백엔드 애플리케이션 관리에 대한 지식이 없는 프론트엔드 엔지니어는 기능을 클라우드 기능으로 추출하고, GCP에 배포하고, 엔드포인트를 통해 클라우드 기능에 HTTP
요청을 함으로써 프론트엔드 애플리케이션에서 사용할 수 있습니다.
프론트엔드 애플리케이션에서 클라우드 기능을 사용하는 방법을 보여주기 위해 이 React 애플리케이션에 더 많은 기능을 추가할 것입니다. 애플리케이션에는 이미 인증과 홈 페이지 설정 간의 기본 라우팅이 있습니다. 생성된 클라우드 기능의 사용이 애플리케이션 리듀서 내에서 수행되므로 React Context API를 사용하여 애플리케이션 상태를 관리하도록 확장할 것입니다.
시작하기 위해 createContext
API를 사용하여 애플리케이션 컨텍스트를 생성하고 애플리케이션 내에서 작업을 처리하기 위한 리듀서를 생성합니다.
// state/index.js import { createContext } from “react”;
export const UserReducer = (action, state) => { switch (action.type) { case “CREATE-USER”: break; case "UPLOAD-USER-IMAGE": break; case “FETCH-DATA” : break case “LOGOUT” : break; 기본값: console.log(
${action.type} is not recognized
) } };내보내기 const userState = { 사용자: null, isLoggedIn: false };
내보내기 const UserContext = createContext(userState);
위에서 우리는 스위치 문이 포함된 UserReducer
함수를 만드는 것으로 시작하여 디스패치된 작업 유형에 따라 작업을 수행할 수 있습니다. switch 문에는 4가지 경우가 있으며 이것이 우리가 처리할 작업입니다. 지금은 아직 아무 것도 하지 않지만 클라우드 기능과 통합을 시작할 때 수행할 작업을 점진적으로 구현할 것입니다.
또한 React createContext API를 사용하여 애플리케이션 컨텍스트를 생성 및 내보냈으며 인증 후 null에서 사용자 데이터로 업데이트될 현재 사용자 값을 포함하는 userState
객체의 기본값과 다음 여부를 알기 위한 isLoggedIn
부울 값을 제공했습니다. 사용자가 로그인했는지 여부.
이제 컨텍스트를 사용할 수 있지만 그 전에 자식 구성 요소가 컨텍스트의 값 변경을 구독할 수 있도록 UserContext
에 연결된 Provider로 전체 애플리케이션 트리를 래핑해야 합니다.
// index.js import React from "react"; import ReactDOM from "react-dom"; import "./index.css"; import App from "./app"; import { UserContext, userState } from "./state/"; ReactDOM.render( <React.StrictMode> <UserContext.Provider value={userState}> <App /> </UserContext.Provider> </React.StrictMode>, document.getElementById("root") ); serviceWorker.unregister();
루트 구성 요소에서 UserContext
공급자로 입력 응용 프로그램을 래핑하고 이전에 만든 userState
기본값을 value prop에 전달했습니다.
이제 애플리케이션 상태가 완전히 설정되었으므로 클라우드 기능을 통해 Google Cloud Firestore를 사용하여 사용자 데이터 모델을 생성할 수 있습니다.
애플리케이션 데이터 처리
이 애플리케이션 내의 사용자 데이터는 고유 ID, 이메일, 비밀번호 및 이미지 URL로 구성됩니다. 클라우드 기능을 사용하면 이 데이터는 Google Cloud Platform에서 제공되는 Cloud Firestore 서비스를 사용하여 클라우드에 저장됩니다.
Google Cloud Firestore , 유연한 NoSQL 데이터베이스는 오프라인 데이터 지원과 함께 더 풍부하고 빠른 쿼리를 허용하는 새로운 향상된 기능과 함께 Firebase 실시간 데이터베이스에서 조각되었습니다. Firestore 서비스 내의 데이터는 MongoDB와 같은 다른 NoSQL 데이터베이스와 유사한 컬렉션 및 문서로 구성됩니다.
Firestore는 Google Cloud Console을 통해 시각적으로 액세스할 수 있습니다. 시작하려면 왼쪽 탐색 창을 열고 데이터베이스 섹션으로 스크롤한 다음 Firestore를 클릭합니다. 그러면 기존 데이터가 있는 사용자의 컬렉션 목록이 표시되거나 기존 컬렉션이 없을 때 사용자에게 새 컬렉션을 만들라는 메시지가 표시됩니다. 우리는 애플리케이션에서 사용할 사용자 컬렉션을 생성합니다.
Google Cloud Platform의 다른 서비스와 마찬가지로 Cloud Firestore에는 노드 환경에서 사용하도록 빌드된 JavaScript 클라이언트 라이브러리도 있습니다( 브라우저에서 사용하는 경우 오류가 발생함 ). 즉석에서 @google-cloud/firestore
패키지를 사용하여 클라우드 기능에서 Cloud Firestore를 사용합니다.
Cloud 함수와 함께 Cloud Firestore 사용
시작하려면 demo-function
에서 생성한 첫 번째 함수의 이름을 firestoreFunction
으로 변경한 다음 Firestore와 연결하고 사용자 컬렉션에 데이터를 저장하도록 확장합니다.
require("dotenv").config(); const { Firestore } = require("@google-cloud/firestore"); const { SecretManagerServiceClient } = require("@google-cloud/secret-manager"); const client = new SecretManagerServiceClient(); exports.firestoreFunction = function (req, res) { return { const { email, password, type } = req.body; const firestore = new Firestore(); const document = firestore.collection("users"); console.log(document) // prints details of the collection to the function logs if (!type) { res.status(422).send("An action type was not specified"); } switch (type) { case "CREATE-USER": break case "LOGIN-USER": break; default: res.status(422).send(`${type} is not a valid function action`) } };
fire-store와 관련된 더 많은 작업을 처리하기 위해 응용 프로그램의 인증 요구 사항을 처리하기 위해 두 가지 경우가 있는 switch 문을 추가했습니다. 우리의 switch 문은 우리의 애플리케이션에서 이 함수에 대한 요청을 할 때 요청 본문에 추가하는 type
표현식을 평가하고 이 type
데이터가 요청 본문에 없을 때마다 요청은 잘못된 요청 및 400
상태 코드로 식별됩니다. 누락된 type
이 응답으로 전송되었음을 나타내는 메시지와 함께 표시됩니다.
Cloud Firestore 클라이언트 라이브러리 내의 ADC(Application Default Credentials) 라이브러리를 사용하여 Firestore와의 연결을 설정합니다. 다음 줄에서 다른 변수의 컬렉션 메서드를 호출하고 컬렉션 이름을 전달합니다. 우리는 이것을 사용하여 포함된 문서 수집에 대한 다른 작업을 추가로 수행할 것입니다.
참고 : GCP의 서비스용 클라이언트 라이브러리는 생성자를 초기화할 때 전달된 생성된 서비스 계정 키를 사용하여 해당 서비스에 연결합니다. 서비스 계정 키가 없으면 기본적으로 애플리케이션 기본 자격 증명을 사용하여 클라우드 기능에 할당된 IAM
역할을 사용하여 연결합니다.
Gcloud SDK를 사용하여 로컬에 배포된 기능의 소스 코드를 편집한 후 터미널에서 이전 명령을 다시 실행하여 클라우드 기능을 업데이트하고 재배포할 수 있습니다.
이제 연결이 설정되었으므로 CREATE-USER
사례를 구현하여 요청 본문의 데이터를 사용하여 새 사용자를 만들 수 있습니다.
require("dotenv").config(); const { Firestore } = require("@google-cloud/firestore"); const path = require("path"); const { v4 : uuid } = require("uuid") const cors = require("cors")({ origin: true }); const client = new SecretManagerServiceClient(); exports.firestoreFunction = function (req, res) { return cors(req, res, () => { const { email, password, type } = req.body; const firestore = new Firestore(); const document = firestore.collection("users"); if (!type) { res.status(422).send("An action type was not specified"); } switch (type) { case "CREATE-USER": if (!email || !password) { res.status(422).send("email and password fields missing"); } const id = uuid() return bcrypt.genSalt(10, (err, salt) => { bcrypt.hash(password, salt, (err, hash) => { document.doc(id) .set({ id : id email: email, password: hash, img_uri : null }) .then((response) => res.status(200).send(response)) .catch((e) => res.status(501).send({ error : e }) ); }); }); case "LOGIN": break; default: res.status(400).send(`${type} is not a valid function action`) } }); };
저장하려는 문서의 ID로 사용할 uuid 패키지를 사용하여 UUID를 생성하고 문서의 set
메소드에 전달하고 사용자 ID도 함께 전달합니다. 기본적으로 삽입된 모든 문서에 임의의 ID가 생성되지만 이 경우 이미지 업로드를 처리할 때 문서를 업데이트하고 UUID는 업데이트할 특정 문서를 가져오는 데 사용됩니다. 사용자의 비밀번호를 일반 텍스트로 저장하는 대신 bcryptjs를 사용하여 먼저 솔트한 다음 결과 해시를 사용자의 비밀번호로 저장합니다.
firestoreFunction
클라우드 기능을 앱에 통합하여 사용자 리듀서 내의 CREATE_USER
사례에서 사용합니다.
계정 만들기 버튼을 클릭하면 입력된 이메일과 비밀번호가 포함된 POST
요청을 firestoreFunction
함수의 끝점에 보내기 위해 CREATE_USER
유형의 리듀서에 작업이 전달됩니다.
import { createContext } from "react"; import { navigate } from "@reach/router"; import Axios from "axios"; export const userState = { user : null, isLoggedIn: false, }; export const UserReducer = (state, action) => { switch (action.type) { case "CREATE_USER": const FIRESTORE_FUNCTION = process.env.REACT_APP_FIRESTORE_FUNCTION; const { userEmail, userPassword } = action; const data = { type: "CREATE-USER", email: userEmail, password: userPassword, }; Axios.post(`${FIRESTORE_FUNCTION}`, data) .then((res) => { navigate("/home"); return { ...state, isLoggedIn: true }; }) .catch((e) => console.log(`couldnt create user. error : ${e}`)); break; case "LOGIN-USER": break; case "UPLOAD-USER-IMAGE": break; case "FETCH-DATA" : break case "LOGOUT": navigate("/login"); return { ...state, isLoggedIn: false }; default: break; } }; export const UserContext = createContext(userState);
위에서 Axios를 사용하여 firestoreFunction
에 요청했고 이 요청이 해결된 후 사용자 초기 상태를 null
에서 요청에서 반환된 데이터로 설정하고 마지막으로 사용자를 인증된 사용자로 홈 페이지로 라우팅합니다. .
이 시점에서 새 사용자는 성공적으로 계정을 만들고 홈 페이지로 이동할 수 있습니다. 이 프로세스는 Firestore를 사용하여 클라우드 기능에서 기본 데이터 생성을 수행하는 방법을 보여줍니다.
파일 스토리지 처리
응용 프로그램에서 사용자 파일을 저장하고 검색하는 것은 대부분 응용 프로그램 내에서 가장 필요한 기능입니다. node.js 백엔드에 연결된 애플리케이션에서 Multer는 업로드된 파일이 들어오는 multipart/form-data를 처리하는 미들웨어로 자주 사용됩니다. 그러나 node.js 백엔드가 없으면 온라인 파일을 사용할 수 있습니다. 정적 애플리케이션 자산을 저장하기 위한 Google Cloud Storage와 같은 스토리지 서비스.
Google Cloud Storage는 전 세계적으로 사용 가능한 파일 스토리지 서비스로, 버킷에 애플리케이션용 객체로 데이터를 저장하는 데 사용됩니다. 소규모 및 대규모 애플리케이션 모두에 대한 정적 자산의 스토리지를 처리할 수 있을 만큼 충분히 유연합니다.
애플리케이션 내에서 Cloud Storage 서비스를 사용하기 위해 사용 가능한 Storage API 엔드포인트를 사용하거나 공식 노드 Storage 클라이언트 라이브러리를 사용할 수 있습니다. 그러나 Node Storage 클라이언트 라이브러리는 브라우저 창 내에서 작동하지 않으므로 라이브러리를 사용할 Cloud Function을 사용할 수 있습니다.
생성된 Cloud Bucket에 파일을 연결하고 업로드하는 아래 Cloud Function이 그 예입니다.
const cors = require("cors")({ origin: true }); const { Storage } = require("@google-cloud/storage"); const StorageClient = new Storage(); exports.Uploader = (req, res) => { const { file } = req.body; StorageClient.bucket("TEST_BUCKET") .file(file.name) .then((response) => { console.log(response); res.status(200).send(response) }) .catch((e) => res.status(422).send({error : e})); }); };
위의 클라우드 기능에서 다음 두 가지 주요 작업을 수행합니다.
먼저
Storage constructor
내에서 Cloud Storage에 대한 연결을 생성하고 Google Cloud의 ADC(Application Default Credentials) 기능을 사용하여 Cloud Storage로 인증합니다.둘째,
.file
메소드를 호출하고 파일 이름을 전달하여 요청 본문에 포함된 파일을TEST_BUCKET
에 업로드합니다. 이것은 비동기 작업이기 때문에 이 작업이 해결된 시점을 알기 위해 약속을 사용하고200
응답을 다시 보내 호출의 수명 주기를 종료합니다.
이제 위의 Uploader
Cloud Function을 확장하여 사용자 프로필 이미지 업로드를 처리할 수 있습니다. 클라우드 기능은 사용자의 프로필 이미지를 수신하여 애플리케이션의 클라우드 버킷에 저장한 다음 Firestore 서비스의 사용자 컬렉션 내에서 사용자의 img_uri
데이터를 업데이트합니다.
require("dotenv").config(); const { Firestore } = require("@google-cloud/firestore"); const cors = require("cors")({ origin: true }); const { Storage } = require("@google-cloud/storage"); const StorageClient = new Storage(); const BucketName = process.env.STORAGE_BUCKET exports.Uploader = (req, res) => { return Cors(req, res, () => { const { file , userId } = req.body; const firestore = new Firestore(); const document = firestore.collection("users"); StorageClient.bucket(BucketName) .file(file.name) .on("finish", () => { StorageClient.bucket(BucketName) .file(file.name) .makePublic() .then(() => { const img_uri = `https://storage.googleapis.com/${Bucket}/${file.path}`; document .doc(userId) .update({ img_uri, }) .then((updateResult) => res.status(200).send(updateResult)) .catch((e) => res.status(500).send(e)); }) .catch((e) => console.log(e)); }); }); };
이제 위의 업로드 기능을 확장하여 다음 추가 작업을 수행합니다.
- 먼저 Firestore 생성자를 초기화하여
users
컬렉션을 가져오기 위해 Firestore 서비스에 새로 연결하고 ADC(Application Default Credentials)를 사용하여 Cloud Storage에 인증합니다. - 요청 본문에 추가된 파일을 업로드한 후 업로드된 파일에서
makePublic
메소드를 호출하여 공개 URL을 통해 액세스할 수 있도록 공개합니다. Cloud Storage의 기본 접근 제어에 따르면 파일을 공개하지 않으면 인터넷을 통해 파일에 접근할 수 없으며 애플리케이션이 로드될 때 이를 수행할 수 있습니다.
참고 : 파일을 공개로 설정하면 애플리케이션을 사용하는 모든 사람이 파일 링크를 복사할 수 있고 파일에 제한 없이 액세스할 수 있습니다. 이를 방지하는 한 가지 방법은 서명된 URL을 사용하여 버킷 내의 파일을 완전히 공개하는 대신 임시 액세스 권한을 부여하는 것입니다.
- 다음으로 업로드된 파일의 URL을 포함하도록 사용자의 기존 데이터를 업데이트합니다. Firestore의
WHERE
쿼리를 사용하여 특정 사용자의 데이터를 찾고 요청 본문에 포함된userId
를 사용한 다음 새로 업데이트된 이미지의 URL을 포함하도록img_uri
필드를 설정합니다.
위의 클라우드 Upload
기능은 Firestore 서비스 내에 등록된 사용자가 있는 모든 애플리케이션에서 사용할 수 있습니다. 엔드포인트에 POST
요청을 하고 사용자의 IS와 이미지를 요청 본문에 넣는 데 필요한 모든 것입니다.
애플리케이션 내에서 이에 대한 예는 함수에 대한 POST
요청을 만들고 요청에서 반환된 이미지 링크를 애플리케이션 상태에 두는 UPLOAD-FILE
케이스입니다.
# index.js import Axios from 'axios' const UPLOAD_FUNCTION = process.env.REACT_APP_UPLOAD_FUNCTION export const UserReducer = (state, action) => { switch (action.type) { case "CREATE-USER" : # .....CREATE-USER-LOGIC .... case "UPLOAD-FILE": const { file, id } = action return Axios.post(UPLOAD_FUNCTION, { file, id }, { headers: { "Content-Type": "image/png", }, }) .then((response) => {}) .catch((e) => console.log(e)); default : return console.log(`${action.type} case not recognized`) } }
위의 전환 사례에서 Axios를 사용하여 요청 본문에 포함될 추가된 파일을 전달하는 UPLOAD_FUNCTION
에 POST
요청을 수행하고 요청 헤더에 이미지 Content-Type
도 추가했습니다.
업로드에 성공한 후 클라우드 기능에서 반환된 응답에는 Google 클라우드 스토리지에 업로드된 이미지의 유효한 URL을 포함하도록 업데이트된 사용자 데이터 문서가 포함됩니다. 그런 다음 새 데이터를 포함하도록 사용자의 상태를 업데이트할 수 있으며 이는 프로필 구성 요소의 사용자 프로필 이미지 src
요소도 업데이트합니다.
크론 작업 처리
사용자에게 이메일을 보내거나 특정 시간에 내부 작업을 수행하는 것과 같은 반복적인 자동화 작업은 대부분 애플리케이션에 포함된 기능입니다. 일반 node.js 애플리케이션에서 이러한 작업은 node-cron 또는 node-schedule을 사용하여 cron 작업으로 처리될 수 있습니다. Google Cloud Platform을 사용하여 서버리스 애플리케이션을 구축할 때 Cloud Scheduler도 크론 작업을 수행하도록 설계되었습니다.
참고 : Cloud Scheduler는 미래에 실행되는 작업을 생성할 때 Unix cron 유틸리티와 유사하게 작동하지만 Cloud Scheduler는 cron 유틸리티처럼 명령을 실행하지 않는다는 점에 유의해야 합니다. 오히려 지정된 대상을 사용하여 작업을 수행합니다.
이름에서 알 수 있듯이 Cloud Scheduler를 사용하면 사용자가 나중에 수행할 작업을 예약할 수 있습니다. 각 작업을 작업 이라고 하며 GCP Console의 스케줄러 섹션에서 작업을 시각적으로 생성, 업데이트, 삭제할 수도 있습니다. 이름 및 설명 필드를 제외하고 Cloud Scheduler의 작업은 다음으로 구성됩니다.
- 빈도
이것은 Cron 작업의 실행을 예약하는 데 사용됩니다. 일정은 Linux 환경에서 cron 테이블에 백그라운드 작업을 생성할 때 원래 사용되는 unix-cron 형식을 사용하여 지정됩니다. unix-cron 형식은 각각 시점을 나타내는 5개의 값이 있는 문자열로 구성됩니다. 아래에서 우리는 5개의 문자열 각각과 그들이 나타내는 값을 볼 수 있습니다.
- - - - - - - - - - - - - - - - minute ( - 59 ) | - - - - - - - - - - - - - hour ( 0 - 23 ) | | - - - - - - - - - - - - day of month ( 1 - 31 ) | | | - - - - - - - - - month ( 1 - 12 ) | | | | - - - - - -- day of week ( 0 - 6 ) | | | | | | | | | | | | | | | | | | | | | | | | | * * * * *
Crontab 생성기 도구는 작업에 대한 빈도-시간 값을 생성하려고 할 때 유용합니다. 시간 값을 함께 배치하는 것이 어렵다면 Crontab 생성기에 일정을 구성하는 값을 선택하고 생성된 값을 복사하여 빈도로 사용할 수 있는 시각적 드롭다운이 있습니다.
- 시간대
cron 작업이 실행되는 시간대입니다. 시간대 간의 시차로 인해 다른 지정된 시간대에서 실행되는 cron 작업의 실행 시간이 다릅니다. - 표적
이것은 지정된 Job의 실행에 사용되는 것입니다. 대상은 작업이 지정된 시간에 URL 또는 작업이 메시지를 게시하거나 App Engine 애플리케이션에서 메시지를 가져올 수 있는 Pub/Sub 주제에 대해 요청하는HTTP
유형일 수 있으며 마지막으로 App Engine 애플리케이션일 수 있습니다.
Cloud Scheduler는 HTTP 트리거 Cloud Functions와 완벽하게 결합됩니다. 대상이 HTTP로 설정된 Cloud Scheduler 내 작업이 생성되면 이 작업을 사용하여 클라우드 기능을 실행할 수 있습니다. 클라우드 함수의 끝점을 지정하고 요청의 HTTP 동사를 지정한 다음 표시된 본문 필드에 함수에 전달해야 하는 데이터를 추가하기만 하면 됩니다. 아래 샘플에서 볼 수 있듯이:
위 이미지의 cron 작업은 매일 오전 9시에 실행되어 클라우드 기능의 샘플 엔드포인트에 POST
요청을 보냅니다.
cron 작업의 보다 현실적인 사용 사례는 Mailgun과 같은 외부 메일링 서비스를 사용하여 지정된 간격으로 사용자에게 예약된 이메일을 보내는 것입니다. 이 작업을 확인하기 위해 Mailgun에 연결하기 위해 nodemailer JavaScript 패키지를 사용하여 HTML 이메일을 지정된 이메일 주소로 보내는 새로운 클라우드 기능을 만들 것입니다.
# index.js require("dotenv").config(); const nodemailer = require("nodemailer"); exports.Emailer = (req, res) => { let sender = process.env.SENDER; const { reciever, type } = req.body var transport = nodemailer.createTransport({ host: process.env.HOST, port: process.env.PORT, secure: false, auth: { user: process.env.SMTP_USERNAME, pass: process.env.SMTP_PASSWORD, }, }); if (!reciever) { res.status(400).send({ error: `Empty email address` }); } transport.verify(function (error, success) { if (error) { res .status(401) .send({ error: `failed to connect with stmp. check credentials` }); } }); switch (type) { case "statistics": return transport.sendMail( { from: sender, to: reciever, subject: "Your usage satistics of demo app", html: { path: "./welcome.html" }, }, (error, info) => { if (error) { res.status(401).send({ error : error }); } transport.close(); res.status(200).send({data : info}); } ); default: res.status(500).send({ error: "An available email template type has not been matched.", }); } };
Using the cloud function above we can send an email to any user's email address specified as the receiver value in the request body. It performs the sending of emails through the following steps:
- It creates an SMTP transport for sending messages by passing the
host
,user
andpass
which stands for password, all displayed on the user's Mailgun dashboard when a new account is created. - Next, it verifies if the SMTP transport has the credentials needed in order to establish a connection. If there's an error in establishing the connection, it ends the function's invocation and sends back a
401 unauthenticated
status code. - Next, it calls the
sendMail
method to send the email containing the HTML file as the email's body to the receiver's email address specified in theto
field.
Note : We use a switch statement in the cloud function above to make it more reusable for sending several emails for different recipients. This way we can send different emails based on the type
field included in the request body when calling this cloud function.
Now that there is a function that can send an email to a user; we are left with creating the cron job to invoke this cloud function. This time, the cron jobs are created dynamically each time a new user is created using the official Google cloud client library for the Cloud Scheduler from the initial firestoreFunction
.
We expand the CREATE-USER
case to create the job which sends the email to the created user at a one-day interval.
require("dotenv").config();cloc const { Firestore } = require("@google-cloud/firestore"); const scheduler = require("@google-cloud/scheduler") const cors = require("cors")({ origin: true }); const EMAILER = proccess.env.EMAILER_ENDPOINT const parent = ScheduleClient.locationPath( process.env.PROJECT_ID, process.env.LOCATION_ID ); exports.firestoreFunction = function (req, res) { return cors(req, res, () => { const { email, password, type } = req.body; const firestore = new Firestore(); const document = firestore.collection("users"); const client = new Scheduler.CloudSchedulerClient() if (!type) { res.status(422).send({ error : "An action type was not specified"}); } switch (type) { case "CREATE-USER":
const job = { httpTarget: { uri: process.env.EMAIL_FUNCTION_ENDPOINT, httpMethod: "POST", body: { email: email, }, }, schedule: "*/30 */6 */5 10 4", timezone: "Africa/Lagos", }
if (!email || !password) { res.status(422).send("email and password fields missing"); } return bcrypt.genSalt(10, (err, salt) => { bcrypt.hash(password, salt, (err, hash) => { document .add({ email: email, password: hash, }) .then((response) => {
client.createJob({ parent : parent, job : job }).then(() => res.status(200).send(response)) .catch(e => console.log(`unable to create job : ${e}`) )
}) .catch((e) => res.status(501).send(`error inserting data : ${e}`) ); }); }); default: res.status(422).send(`${type} is not a valid function action`) } }); };
From the snippet above, we can see the following:
- A connection to the Cloud Scheduler from the Scheduler constructor using the Application Default Credentials (ADC) is made.
- We create an object consisting of the following details which make up the cron job to be created:
-
uri
The endpoint of our email cloud function in which a request would be made to. -
body
This is the data containing the email address of the user to be included when the request is made. -
schedule
The unix cron format representing the time when this cron job is to be performed.
-
- After the promise from inserting the user's data document is resolved, we create the cron job by calling the
createJob
method and passing in the job object and the parent. - The function's execution is ended with a
200
status code after the promise from thecreateJob
operation has been resolved.
After the job is created, we'll see it listed on the scheduler page.
From the image above we can see the time scheduled for this job to be executed. We can decide to manually run this job or wait for it to be executed at the scheduled time.
결론
Within this article, we have had a good look into serverless applications and the benefits of using them. We also had an extensive look at how developers can manage their serverless applications on the Google Cloud using Cloud Functions so you now know how the Google Cloud is supporting the use of serverless applications.
Within the next years to come, we will certainly see a large number of developers adapt to the use of serverless applications when building applications. If you are using cloud functions in a production environment, it is recommended that you read this article from a Google Cloud advocate on “6 Strategies For Scaling Your Serverless Applications”.
The source code of the created cloud functions are available within this Github repository and also the used front-end application within this Github repository. The front-end application has been deployed using Netlify and can be tested live here.
참고문헌
- 구글 클라우드
- 클라우드 함수
- 클라우드 소스 저장소
- Cloud Scheduler overview
- 클라우드 파이어스토어
- “6 Strategies For Scaling Your Serverless Applications,” Preston Holmes