Rubyを使用したGoogleCloud関数の構築、テスト、デプロイ
サーバーレス機能は、クラウドサービスの開発と展開の新しいプログラミングパラダイムです。サーバーレスの世界では、バックエンドサービスのプロビジョニング、メンテナンス、スケーリングをクラウドプロバイダーに抽象化します。これにより、開発者は特定の問題の解決に集中できるため、開発者の生産性が大幅に向上します。サーバーレス関数を構築することには多くの長所と短所がありますが、それらを構築する際に考慮すべきことの1つは言語サポートです。最近、GoogleはGoogle CloudFunctionsでのRuby2.7のサポートを発表しました。この記事では、Ruby on Google Cloud Functionsでのサーバーレス機能の構築、テスト、デプロイと、サーバーレス機能の長所と短所に焦点を当てます。
ワンタイムパスワード(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コンソールでクラウド機能を確認します。
サービスアカウント
を作成します Cloud Functions Admin
を使用 およびサービスアカウントユーザー
役割。
サービスアカウントは、マシンツーマシンIAMに使用されます。したがって、システムがGoogle Cloudで実行されているかどうかに関係なく、システムがGoogle Cloud上の別のシステムと通信する場合、Googleリソースへのアクセスをリクエストしているユーザーを特定するためのサービスアカウントが必要です。役割CloudFunctions Admin
およびサービスアカウントユーザー
ユーザーがリソースへのアクセスを許可されているかどうかを判断できるようにします。このシナリオでは、GithubActionランナーがGoogleCloudと通信し、関数をデプロイするために必要な権限を持つサービスアカウントとして認証します。
サービスアカウントキーを作成し、JSONをダウンロードして、GitHubシークレットに追加します。
出来上がり! 🎉クラウド機能は正常にデプロイされました。
以下は、2つの最大のサーバーレス機能プロバイダーの詳細な比較です。
この記事では、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
ただし、複数のプロバイダーにわたる複数のプラットフォームをサポートします。
参考までに、完全なコードはここから入手できます。
-
FluentdとObjectRocketを使用したハイブリッドクラウドへのログイン
この投稿はHartHooverとRyanWalkerが共同で作成したものです 最近、Rackspace DevOps Automationチームは、NewRelicからRackspaceサポートにアラートを送信するサービスを発表しました。これらのアラートは、DevOpsエンジニアが応答するためのチケットを生成するため、アラートが午前3時に生成されたときに、お客様はぐっすりと眠ることができます。お客様の環境について収集された他のデータポイントと組み合わせると、エンジニアは問題がどこにあるかを特定し、適切な行動方針を実行します。 このサービスのインフラストラクチャを設計しているときに、New
-
Google クラウド プリントとその仕組み
Google クラウド プリントは、Google が開発したサービスで、どこからでもどのデバイスからでもファイルを印刷できます。クラウド経由で接続するだけで、離れた場所にあるプリンターからファイルが印刷されます。オフィスの割り当て、お子様や大切な方への写真を印刷するのに効果的な方法です。インターネット接続が機能しているコンピューターに接続されている任意のプリンターで動作します。コンピュータを接続したくない場合は、プリンタを Google クラウド プリント対応プリンタにする必要があります。クラウドからクラウド プリンターに印刷できる複数のユーザーを追加または削除することもできます。それでは、G