Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

Ruby를 사용하여 Google Cloud 함수 빌드, 테스트 및 배포

Serverless Functions는 클라우드 서비스를 개발하고 배포하는 새로운 프로그래밍 패러다임입니다. 서버리스 세계에서 우리는 클라우드 공급자에 대한 백엔드 서비스의 프로비저닝, 유지 관리 및 확장을 추상화합니다. 이는 개발자가 특정 문제 해결에 집중할 수 있도록 하여 개발자 생산성을 크게 향상시킵니다. 서버리스 기능을 구축하는 데에는 많은 장점과 단점이 있지만, 이를 구축할 때 고려해야 할 한 가지는 언어 지원입니다. 최근 Google은 Google Cloud Functions용 ​​Ruby 2.7 지원을 발표했으며 이 기사에서는 Ruby on Google Cloud Functions에서 서버리스 기능을 빌드, 테스트 및 배포하는 방법과 서버리스 기능의 장단점에 대해 설명합니다.

서버리스 OTP 시스템 구축

OTP(일회성 비밀번호)는 은행에서 신원 확인을 위해 OPT를 문자로 보낼 때와 같이 인증 목적으로 사용되는 짧은 숫자 코드입니다.

이 기사에서는 세 가지 핵심 책임을 처리하는 OTP 기능을 구축할 것입니다.

POST /otp :OTP 메시지를 생성하여 제공된 phone_number로 전송 .

# Request
{
  "phone_number": "+2347012345678"
}

# Response
{
  "status": true,
  "message": "OTP sent successfully",
  "data": {
    "phone_number": "+2347012345678",
    "otp": 6872,
    "expires_at": "2021-02-09 07:15:25 +0100"
  }
}

PUT /otp/verify :사용자가 제공한 OTP에 대해 OTP를 검증합니다.

# Request
{
  "phone_number": "+2347012345678",
  "otp": 7116
}

# Response
{
  "status": true,
  "message": "OTP verified",
  "data": {}
}

PUT /otp/resend :제공된 phone_number로 OTP 생성 및 재전송 시도 .

# Request
{
  "phone_number": "+2347012345678"
}

# Response
{
  "status": true,
  "message": "OTP sent successfully",
  "data": {
    "phone_number": "+2347012345678",
    "otp": 8533,
    "expires_at": "2021-02-09 08:59:16 +0100"
  }
}

단순화를 위해 클라우드 기능은 전체 SQL 또는 NoSQL 데이터베이스가 아닌 Cloud MemoryStore(GCP의 Redis 또는 Memcache)에 의해 지원됩니다. 이를 통해 상태 비저장 환경의 공유 상태에 대해서도 배울 수 있습니다.

Ruby로 Google Cloud 함수 작성

GCF에서 함수를 작성하기 위해 우리는 Functions Framework에 의존할 것입니다. GCF 기능 구축을 위해 Google Cloud 팀에서 제공합니다(자세한 내용은 나중에 설명).

먼저 App 디렉토리를 생성하고 디렉토리를 입력합니다.

mkdir otp-cloud-function && cd otp-cloud-function

다음으로 Gemfile을 만들고 설치합니다.

대부분의 표준 Ruby 애플리케이션과 마찬가지로 bundler를 사용합니다. 함수의 종속성을 관리하기 위해

source "https://rubygems.org"

# Core
gem "functions_framework", "~> 0.7"

# Twilio for Sms
gem 'twilio-ruby', '~> 5.43.0'

# Database
gem 'redis'

# Connection Pooling
gem 'connection_pool'

# Time management
gem 'activesupport'

# API Serialization
gem 'active_model_serializers', '~> 0.10.0'

group :development, :test do
  gem 'pry'
  gem 'rspec'
  gem 'rspec_junit_formatter'
  gem 'faker', '~> 2.11.0'
end
bundle install

함수 생성

일반적으로 다른 호스팅 환경에서는 함수가 작성되는 다른 파일을 지정할 수 있습니다. 그러나 Google Cloud Functions에서는 app.rb여야 합니다. 프로젝트 디렉토리의 루트에 있습니다. 이제 함수를 작성할 준비가 되었습니다.

app.rb 열기 함수를 생성합니다.

# Cloud Functions Entrypoint

require 'functions_framework'
require 'connection_pool'
require 'active_model_serializers'
require './lib/store'
require './lib/send_sms_notification'
require './lib/response'
require './lib/serializers/models/base_model'
require './lib/serializers/models/otp_response'
require './lib/serializers/application_serializer'
require './lib/serializers/base_model_serializer'
require './lib/serializers/otp_response_serializer'

FunctionsFramework.on_startup do |function|
  # Setup Shared Redis Client
  require 'redis'
  set_global :redis_client, ConnectionPool.new(size: 5, timeout: 5) { Redis.new }
end

# Define HTTP Function
FunctionsFramework.http "otp" do |request|

  store = Store.new(global(:redis_client))
  data = JSON.parse(request.body.read)

  if  request.post? && request.path == '/otp'
    phone_number = data['phone_number']
    record = store.get(phone_number)
    unless record.nil? || record.expired?
      data = Models::OtpResponse.new(phone_number: phone_number,
                                      otp: record['otp'],
                                      expires_at: record['expires_at'])
      json = Response.generate_json(status: true,
                            message: 'OTP previously sent',
                            data: data)

      return json
    end

    otp = rand(1111..9999)
    record = store.set(phone_number, otp)
    SendSmsNotification.new(phone_number, otp).call

    data = Models::OtpResponse.new(phone_number: phone_number,
                                    otp: record['otp'],
                                    expires_at: record['expires_at'])

    Response.generate_json(status: true,
                          message: 'OTP sent successfully',
                          data: data)

  elsif request.put? && request.path == '/otp/verify'
    phone_number = data['phone_number']
    record = store.get(phone_number)

    if record.nil?
      return Response.generate_json(status: false, message: "OTP not sent to number")
    elsif record.expired?
      return Response.generate_json(status: false,  message: 'OTP code expired')
    end

    is_verified = data['otp'] == record['otp']

    if is_verified
      return Response.generate_json(status: true, message: 'OTP verified')
    else
      return Response.generate_json(status: false, message: 'OTP does not match')
    end

  elsif request.put? && request.path == '/otp/resend'
    phone_number = data['phone_number']
    store.del(phone_number)

    otp = rand(1111..9999)
    record = store.set(phone_number, otp)
    SendSmsNotification.new(phone_number, otp).call

    data = Models::OtpResponse.new(phone_number: phone_number,
                                    otp: record['otp'],
                                    expires_at: record['expires_at'])

    json = Response.generate_json(status: true,
                          message: 'OTP sent successfully',
                          data: data)
  else
    Response.generate_json(status: false,
                            message: 'Request method and path did not match')
  end
end

이것은 많은 코드이므로 다음과 같이 분류하겠습니다.

  • Functions_Framework.on_startup 함수가 요청 처리를 시작하기 전에 Ruby 인스턴스별로 실행되는 코드 블록입니다. 함수가 호출되기 전에 모든 형태의 초기화를 실행하는 것이 이상적입니다. 이 경우 Redis 서버에 대한 연결 풀을 만들고 공유하는 데 사용합니다.

    set_global :redis_client, ConnectionPool.new(size: 5, timeout: 5) { Redis.new }
    

    이를 통해 두려움 없이 여러 동시 함수 호출에서 Redis 연결 개체 풀을 공유할 수 있습니다. 여러 시작을 정의할 수 있습니다. 정의된 순서대로 실행됩니다. Functions Framework 함수 완료 후 실행할 특별한 후크를 제공하지 않습니다.

  • Functions_Framework.http 'otp' do |request| 함수의 요청 및 응답 처리를 처리합니다. 이 기능은 세 가지 다른 경로 패턴을 지원합니다. 다른 유형의 함수를 정의할 수 있습니다(예:Functions_Framework.cloud_event 'otp' do |event| ) 다른 Google 서비스의 이벤트를 처리합니다. 동일한 파일에 여러 기능을 정의할 수도 있지만 독립적으로 배포됩니다.

  • store = Store.new(global(:redis_client))에서 , global 메서드는 전역 공유 상태에 저장된 개체를 검색하는 데 사용됩니다. 위에서 사용된 것처럼 startup의 전역 설정에 정의된 연결 풀에서 Redis 클라이언트를 검색합니다. 차단합니다.

  • ResponseModels::OtpResponse active_model_serializers로 응답 직렬화를 처리합니다. 올바른 형식의 JSON 응답을 제공합니다.

로컬에서 기능 테스트하기

Functions Framework 라이브러리를 사용하면 기능을 클라우드에 배포하기 전에 로컬에서 쉽게 테스트할 수 있습니다. 로컬에서 테스트하기 위해 다음을 실행합니다.

bundle exec functions-framework-ruby --target=otp --port=3000

--target 배포할 기능을 선택하는 데 사용됩니다.

수동 테스트도 훌륭하지만 자동 테스트와 자체 테스트 소프트웨어는 테스트의 성배입니다. Functions Framework Minitest 모두에 대한 도우미 메서드를 제공합니다. 및 RSpechttp에 대한 기능을 테스트하는 데 도움이 됩니다. 및 cloudevents 핸들러. 다음은 테스트의 예입니다.

require './spec/spec_helper.rb'
require 'functions_framework/testing'

describe  'OTP Functions' do
  include FunctionsFramework::Testing

  describe 'Send OTP', redis: true do
    let(:phone_number) { "+2347012345678" }
    let(:body) { { phone_number: phone_number }.to_json }
    let(:headers) { ["Content-Type: application/json"] }

    it 'should send OTP successfully' do
      load_temporary "app.rb" do
        request = make_post_request "/otp", body, headers

        response = call_http "otp", request
        expect(response.status).to eq 200
        expect(response.content_type).to eq("application/json")

        parsed_response = JSON.parse(response.body.join)
        expect(parsed_response['status']).to eq true
        expect(parsed_response['message']).to eq 'OTP sent successfully'
      end
    end
  end
end

기능 배포

먼저 Google Cloud Memorystore를 사용하여 Redis 서버를 배포해야 합니다. , 우리의 기능이 의존합니다. Redis 서버를 GCP에 배포하는 방법은 이 문서의 범위를 벗어나므로 여기에서 더 자세히 설명하지 않겠습니다.

Google Cloud Functions 환경에 함수를 배포하는 방법에는 머신에서 배포, GCP 콘솔에서 배포, 코드 저장소에서 배포 등 여러 가지가 있습니다. 최신 소프트웨어 엔지니어링은 대부분의 개발에 CI/CD 프로세스를 권장하며, 이 기사의 목적을 위해 deploy-cloud-functions를 사용하여 Github Actions와 함께 Github에서 Cloud Function을 배포하는 데 중점을 둘 것입니다.

배포 파일(.github/workflows/deploy.yml)을 설정해 보겠습니다.

name: Deployment
on:
  push:
    branches:
      - main
jobs:
  deploy:
    name: Function Deployment
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - id: deploy
        uses: google-github-actions/deploy-cloud-functions@main
        with:
          name: otp-cloud-function
          runtime: ruby26
          credentials: ${{ secrets.gcp_credentials }}
          env_vars: "TWILIO_ACCOUNT_SID=${{ secrets.TWILIO_ACCOUNT_SID }},TWILIO_AUTH_TOKEN=${{ secrets.TWILIO_AUTH_TOKEN }},TWILIO_PHONE_NUMBER=${{ secrets.TWILIO_PHONE_NUMBER }},REDIS_URL=${{ secrets.REDIS_URL }}"

환경 변수

위의 코드에서 마지막 줄을 사용하면 GCP 환경에서 함수에 사용할 수 있는 환경 변수를 지정할 수 있습니다. 보안상의 이유로 코드 기반에서 이러한 변수를 노출하지 않습니다. 대신 이 정보를 비공개로 유지하기 위해 Github 작업 비밀을 활용하고 있습니다. 토큰이 제대로 배포되었는지 확인하려면 아래와 같이 Google 콘솔에서 클라우드 기능을 확인하세요.

Ruby를 사용하여 Google Cloud 함수 빌드, 테스트 및 배포

인증

Service Account 생성 Cloud Functions Admin 사용 및 Service Account User 역할.

서비스 계정은 기계 간 IAM에 사용됩니다. 따라서 시스템이 Google Cloud에서 실행 중인지 여부에 관계없이 Google Cloud의 다른 시스템과 통신할 때 Google 리소스에 대한 액세스를 요청하는 사람을 식별하는 데 도움이 되는 서비스 계정이 필요합니다. Cloud Functions Admin 역할 및 Service Account User 사용자가 리소스에 액세스할 수 있는 권한이 있는지 여부를 확인할 수 있습니다. 이 시나리오에서 Github Action 실행자는 기능을 배포하는 데 필요한 권한이 있는 서비스 계정으로 인증하는 Google Cloud와 통신합니다.

Ruby를 사용하여 Google Cloud 함수 빌드, 테스트 및 배포

Ruby를 사용하여 Google Cloud 함수 빌드, 테스트 및 배포

서비스 계정 키를 만들고 JSON을 다운로드한 다음 GitHub Secrets에 추가합니다.

Ruby를 사용하여 Google Cloud 함수 빌드, 테스트 및 배포

Ruby를 사용하여 Google Cloud 함수 빌드, 테스트 및 배포

짜잔! 🎉 Cloud Function이 성공적으로 배포되었습니다.

Cloud Functions 한도와 AWS 한도 비교

다음은 가장 큰 서버리스 기능 제공업체 두 곳을 자세히 비교한 것입니다.

Ruby를 사용하여 Google Cloud 함수 빌드, 테스트 및 배포

함수 프레임워크 계약 대 서버리스 프레임워크

이 문서에서는 Google Cloud Functions용 ​​클라우드 기능 구축에 중점을 두었습니다. 이 부분에서는 Functions Framework와 Serverless Framework로 빌드를 비교하려고 합니다.

  • Serverless Framework serverless.yml을 기반으로 합니다. , Functions Framework는 Functions Framework Contract를 기반으로 합니다. , Google Cloud Infrastructure 전반에 서버리스 기능을 배포하는 데 사용됩니다.
  • Serverless Framework 사용 , 몇 가지 예만 있으며 Ruby를 사용하여 서버리스 기능을 빌드하고 다양한 Google 서버리스 환경(Cloud Functions, Cloud Run 및 Knative 환경)에 배포하는 방법이 명확하지 않습니다. Functions Framework Contract 사용 다양한 Google 제품에서 Ruby로 빌드하는 것은 간단합니다.
    • 이전 포인트에 이어 Functions Framework Contract 배포 프로세스를 크게 변경하지 않고도 기능 뒤의 지원 언어를 매우 쉽게 전환할 수 있습니다.
  • 이 글을 쓰는 시점에서 Functions Framework Google Cloud Serverless 환경 및 Knative 환경 간의 상호 운용성만 지원합니다. Serverless Framework 그러나 여러 제공업체에서 여러 플랫폼을 지원합니다.

참고로 전체 코드는 여기에서 볼 수 있습니다.