Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Rubyを使用したGoogleCloud関数の構築、テスト、デプロイ

サーバーレス機能は、クラウドサービスの開発と展開の新しいプログラミングパラダイムです。サーバーレスの世界では、バックエンドサービスのプロビジョニング、メンテナンス、スケーリングをクラウドプロバイダーに抽象化します。これにより、開発者は特定の問題の解決に集中できるため、開発者の生産性が大幅に向上します。サーバーレス関数を構築することには多くの長所と短所がありますが、それらを構築する際に考慮すべきことの1つは言語サポートです。最近、GoogleはGoogle CloudFunctionsでのRuby2.7のサポートを発表しました。この記事では、Ruby on Google Cloud Functionsでのサーバーレス機能の構築、テスト、デプロイと、サーバーレス機能の長所と短所に焦点を当てます。

サーバーレスOTPシステムの構築

ワンタイムパスワード(OTP)は、銀行が身元を確認するためにテキストを介してOPTを送信する場合など、認証目的で使用される短い数値コードです。

この記事では、3つの主要な責任を処理する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 / send :OTPを生成し、指定された phone_numberに再送信しようとします 。

# 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を使用したGoogleCloudFunctionの作成

GCFで関数を作成するには、 Functions Frameworkに依存します。 GCF機能を構築するためにGoogleCloudチームによって提供されます(これについては後で詳しく説明します)。

まず、アプリディレクトリを作成し、ディレクトリに入ります。

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 | 関数の要求と応答の処理を処理します。この機能は、3つの異なるルートパターンをサポートします。他のタイプの関数を定義することもできます(例: Functions_Framework.cloud_event'otp' do | event | )他のGoogleサービスからのイベントを処理します。同じファイルに複数の関数を定義することもできますが、独立してデプロイされます。

  • store =Store.new(global(:redis_client))で 、 global メソッドは、グローバル共有状態に格納されているオブジェクトを取得するために使用されます。上記で使用されているように、 startupのグローバルセットアップで定義された接続プールからRedisクライアントを取得します ブロック。

  • 応答 およびModels::OtpResponse active_model_serializersを使用して応答のシリアル化を処理します 適切にフォーマットされたJSON応答を提供します。

関数をローカルでテストする

Functions Framework ライブラリを使用すると、関数をクラウドにデプロイする前に、ローカルで関数を簡単にテストできます。ローカルでテストするには、

を実行します
bundle exec functions-framework-ruby --target=otp --port=3000

-ターゲット デプロイする関数を選択するために使用されます。

手動テストは素晴らしいですが、自動テストと自己テストソフトウェアはテストの聖杯です。 Functions Framework 両方のMinitestのヘルパーメソッドを提供します およびRSpec 両方のhttpの関数をテストするのに役立ちます および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 CloudFunctionの環境に関数をデプロイする方法は複数あります。マシンからデプロイする方法、GCPコンソールからデプロイする方法、コードリポジトリからデプロイする方法です。最新のソフトウェアエンジニアリングでは、ほとんどの開発でCI / CDプロセスが推奨されています。この記事では、deploy-cloud-functionsを使用してGithubアクションを使用してGithubからクラウド関数をデプロイすることに焦点を当てます。

デプロイファイル(.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 }}"
環境変数

上記のコードの最後の行では、GoogleCloud環境の関数で使用できる環境変数を指定できます。セキュリティ上の理由から、これらの変数はコードベースで公開されていないことに注意してください。代わりに、Githubアクションシークレットを利用してこの情報を非公開にしています。トークンが適切にデプロイされているかどうかを確認するには、以下に示すように、Googleコンソールでクラウド機能を確認します。

Rubyを使用したGoogleCloud関数の構築、テスト、デプロイ

認証

サービスアカウントを作成します Cloud Functions Adminを使用 およびサービスアカウントユーザー 役割。

サービスアカウントは、マシンツーマシンIAMに使用されます。したがって、システムがGoogle Cloudで実行されているかどうかに関係なく、システムがGoogle Cloud上の別のシステムと通信する場合、Googleリソースへのアクセスをリクエストしているユーザーを特定するためのサービスアカウントが必要です。役割CloudFunctions Admin およびサービスアカウントユーザー ユーザーがリソースへのアクセスを許可されているかどうかを判断できるようにします。このシナリオでは、GithubActionランナーがGoogleCloudと通信し、関数をデプロイするために必要な権限を持つサービスアカウントとして認証します。

Rubyを使用したGoogleCloud関数の構築、テスト、デプロイ

Rubyを使用したGoogleCloud関数の構築、テスト、デプロイ

サービスアカウントキーを作成し、JSONをダウンロードして、GitHubシークレットに追加します。

Rubyを使用したGoogleCloud関数の構築、テスト、デプロイ

Rubyを使用したGoogleCloud関数の構築、テスト、デプロイ

出来上がり! 🎉クラウド機能は正常にデプロイされました。

クラウド機能の制限とAWSの制限

以下は、2つの最大のサーバーレス機能プロバイダーの詳細な比較です。

Rubyを使用したGoogleCloud関数の構築、テスト、デプロイ

関数フレームワークコントラクトとサーバーレスフレームワーク

この記事では、GoogleCloudFunctionsのクラウ​​ド関数の構築に焦点を当てました。このセグメントでは、FunctionsFrameworkとServerlessFrameworkを使用した構築を比較したいと思います。

  • サーバーレスフレームワーク serverless.ymlに基づいています 、FunctionsFrameworkはFunctionsFrameworkコントラクトに基づいています 、GoogleCloudInfrastructure全体にサーバーレス機能をデプロイするために使用されます。
  • Serverless Frameworkを使用する 、例はごくわずかであり、Rubyを使用してサーバーレス関数を構築してさまざまなGoogleサーバーレス環境(Cloud Functions、Cloud Run、およびKnative環境)にデプロイする方法は明確ではありません。 Functions Framework Contractを使用 機能、これらのさまざまなGoogle製品間でRubyを使用して構築するのは簡単です。
    • 前のポイントに続いて、 Functions Framework Contract デプロイメントプロセスを大幅に変更することなく、関数の背後にあるバッキング言語を非常に簡単に切り替えることができます。
  • この記事の執筆時点では、 Functions Framework GoogleCloudServerless環境とKnative環境間の相互運用性のみをサポートします。 Serverless Framework ただし、複数のプロバイダーにわたる複数のプラットフォームをサポートします。

参考までに、完全なコードはここから入手できます。


  1. FluentdとObjectRocketを使用したハイブリッドクラウドへのログイン

    この投稿はHartHooverとRyanWalkerが共同で作成したものです 最近、Rackspace DevOps Automationチームは、NewRelicからRackspaceサポートにアラートを送信するサービスを発表しました。これらのアラートは、DevOpsエンジニアが応答するためのチケットを生成するため、アラートが午前3時に生成されたときに、お客様はぐっすりと眠ることができます。お客様の環境について収集された他のデータポイントと組み合わせると、エンジニアは問題がどこにあるかを特定し、適切な行動方針を実行します。 このサービスのインフラストラクチャを設計しているときに、New

  2. Google クラウド プリントとその仕組み

    Google クラウド プリントは、Google が開発したサービスで、どこからでもどのデバイスからでもファイルを印刷できます。クラウド経由で接続するだけで、離れた場所にあるプリンターからファイルが印刷されます。オフィスの割り当て、お子様や大切な方への写真を印刷するのに効果的な方法です。インターネット接続が機能しているコンピューターに接続されている任意のプリンターで動作します。コンピュータを接続したくない場合は、プリンタを Google クラウド プリント対応プリンタにする必要があります。クラウドからクラウド プリンターに印刷できる複数のユーザーを追加または削除することもできます。それでは、G