ラベル GCE の投稿を表示しています。 すべての投稿を表示
ラベル GCE の投稿を表示しています。 すべての投稿を表示

2023/07/03

サーバレス アーキテクチャ

サーバレス アーキテクチャ って何

ちょっとネットで調べてみればわかるのですが、サーバレス アーキテクチャについて書かれた記事はそこそこ数はあります。しかしどれを読んでも結局サーバレスがどういうものなのか理解できない記事ばかりが目立ちます。よく見るのは以下のような例です。

  • こんなサービスですといくつか具体的に紹介されているものの、肝心のサーバレス アーキテクチャそのものについての記述がない。
  • あれではない、これでもないと該当しないサービスの例が挙げてあるものの、では何がサーバレスなのかについては記述がない。
  • 「サーバレス アーキテクチャとはサーバレスな構成です」としか書いてない。←いや説明を説明してw

このような状況なので、調べてもなかなか良い記事にたどり着きません。記事を書いてる人たちも理解していないのではないかと疑いたくなります。


いきなり結論

サーバレス アーキテクチャとは、常時起動しているサーバが存在しない構成

のことです。文字どおりに「サーバがない」と解釈してしまうと「ではどうやって処理するのか」と疑問が湧くだけですが、常時かどうかがポイントです。

以下、従来と比べるとより分かりやすいと思います。


非サーバレス

サーバレスではない従来の構成では、サーバは起動しておいてリクエストが来るのを待っています。リクエストが来なければ、基本的には何もしないでずっと待ち続けています。

GCPでWebサーバを構成する例だと、GCE/GKEに従来のオンプレミスでの構成と同様にWebフレームワーク等をインストールして構成します。

クラウドでは従量課金が基本ですが、アプリが何もしないで待っている間でもプラットフォームから見れば稼働中なので課金されます。またアプリを動かすインフラがスケーリングに対応していない場合、想定される最大の負荷にあわせて構成することになります。その分ランニングコストが高くなります。


サーバレス アーキテクチャ

アプリが必要になった時点で、プラットフォームがアプリを自動的に立ち上げてくれます。アプリが立ち上がったら非サーバレスと同様に、アプリによってリクエストが処理されます。アプリが稼働を停止していると判断されたら、フラットフォームが自動的にアプリを停止してくれます。

クラウドではこのような動作を可能にするプラットフォームが用意されています。当然アプリもそれにふさわしい作り・設定が必要です。アプリが停止した後は、それまでアプリが動作していたインフラ(CPU、メモリ、ネットワーク、etc.)は、別のアプリのために使用されます。そのためインフラ全体では稼働効率をよくすることができます。

また、アプリ起動がインスタンスの増加と同じになるので、水平スケーリングにも対応が容易です。

GCPでWebサーバを構成するなら、GAEを利用します(ただし最小インスタンス数=0設定)。小規模ならCloud Run/Cloud Functionsも利用できます。GKEでも最小Pod数を0にしてネットワークからリクエストで起動するように設定することも可能(なはず)です。負荷の増減に対しても、その瞬間の負荷をさばくのに必要な数だけアプリを立ち上げればいいだけです。

従量課金だとアプリが稼働している間だけ、立ち上がっている数だけ課金されるので、ランニングコストを抑えることができます。


2023/04/12

Cassandraって何者?

現在参画しているプロジェクトで、はじめてApache Cassandraを使っています。Cassandraはオープンソースのデータベースで、クラウド上に実装して使う例も多数あります。今回参画しているプロジェクトもそうです。

本記事では、これを分類してみましょう。


Let's 分類


分散型

複数のインフラに分散実装し、全体として1つのデータベースとして機能する分散型データベースであるのが、たぶん最大の特徴です。この点はシステム屋さんには重要でも、純粋にコードを書くだけのソフト屋さんにとっては意識することはありません。

リレーショナル or ドキュメント指向 or キーバリュー型

リレーショナルデータベースと同様にあらかじめスキーマの定義が必要なスキーマ・オン・ライトですが、JOINはありません。あらかじめスキーマの定義が必要ということは半構造化データを前提としているわけではないので(JSON型が使えるのは別の話)、ドキュメント指向でもキーバリュー型でもありません。
Cassandraは、どれでもないですね。
(20230/04/17修正 : キーバリュー型に分類されるそうです。しかしキーバリュー型の代表とも言えるredisとも似てない。)

SQL or NoSQL

Cassandraでのデータ操作はSQLではなく、SQLに似たCQLで行います。SQL互換ではないのでNoSQLに分類されるようです。しかしNoSQLというと「SQLライクな言語」さえもなくてAPIで操作するものも珍しくない(Firestoreとか)と思うので、NoSQLに分類するのも違うと感じます。
Cassandraは、どちらに分類してもしっくりきません。

まとめ?

他に似ていない独自ですね。「俺か、俺以外か」という分類の仕方がいいかもしれません。

2022/02/01

GCEの料金が見えない

GCEのVM インスタンスの料金のページですが、肝心の料金表が404エラーで見えなくなっています。



ここ数日は続いているので、いい加減気付いて直していただきたいものです。問い合わせは営業につながるだけなので、このような不具合の報告を直接行える手段がないのは、いかがなものかと思ってます。

とりあえず英語を選べば料金が表示されますし、東京・大阪リージョンも選べるので、何とかならなくはないのですが。


2021/09/11

GCEでCloud Loggingを使用してログ出力してみる

 GCPでログを利用したいときはCloud Loggingが便利です。

GAE/Javaならjava.util.logging.Loggerクラスを使えば当たり前にGCPコンソールでログが見れます。もっと緻密に制御したいなら、Javaライブラリならcom.google.cloud.loggingパッケージを使用すれば、構造を持ったログも可能になります。ログに適当な名前を付けてその名前でフィルタして、GCSに長期保存とか、BigQueryで解析に使うとかも出来るようになります。

GAEと同じようにGCEからCloud Loggingにログ出力しようとしたのですが、意外と苦労しました。とりあえずCloud Loggingにログ出力するまでの手順を紹介します。とりあえずなのですべての方法を説明するわけではありません。


ログ出力の手段

GCPにはログ出力の手段がたくさんあります。
  1. エージェントを使用する
    公式ドキュメントでは「Loggingエージェント」または「以前のLoggingエージェント」と記載されいるものと、「Opsエージェント」の2種類があります。どちらもsyslogや既存のアプリケーションが出力するログをCloud Loggingにリダイレクトしてくれるものです。
    自分で作ったアプリのログを出力するには、自分で構成を行う必要があります。自作アプリのデバッグなどの用途では、ちょっと手間が増えます。
  2. Loggingライブラリを使用する
    こちらはコード中から任意のタイミング・内容でログを出力できるので、自作アプリのデバッグなどの用途に向きます。
    Javaで使えるものはさらに複数あり、logbackアベンダーを使用する/java.util.loggingパッケージを使用する/Cloud Loggingクライアントライブラリを使用する、となります。
  3. REST APIを使用する
    REST APIのリクエストエンドポイントを直接コールする方法です。これを簡略化してくれるものがCloud Loggingライブラリと思って間違いないと思います。

この記事で紹介するのは「2. Loggingライブラリを使用する」のjava.util.loggingパッケージを使用する方法です。Javaの標準パッケージなのでおなじみですね。

VMインスタンスの作成

まずは準備として「Loggingツールを使用したクイックスタート」に記載されているとおりにVMインスタンスを作成します。注意が必要なのはアクセススコープで「すべての Cloud API に完全アクセス権を許可」のチェックが必要なくらいのはずです。

不安なら「gcloudツールを使用してログエントリを書き込む」に記載の手順で、ログ出力を試してみることもできます。


java.util.loggingパッケージの使用

準備

java.util.loggingハンドラ」に載っているとおりに、pom.xmlを編集してライブラリを読み込みます。
logging.properties構成ファイルは、なくてもデフォルト設定で動作します。

Javaコード

java.util.loggingハンドラ」に載っているスニペットそのままでは、ログは標準出力にしか出ません。ここでしばらくつまづきましたが、再トライして気付きました。
「デフォルトの Java Logging API ハンドラから」の『ハンドラ』の文字だけハイパーリンクになっていますが、ここからcom.google.cloud.logging.LoggingHandlerクラスのリファレンスに遷移できます。このクラスの説明には以下の記述があります。

To add a LoggingHandler to an existing Logger and be sure to avoid infinite recursion when logging, use the addHandler(Logger, LoggingHandler) method.

つまりLoggerインスタンスに、LoggingHandler.addHander()メソッドを利用してLoggingHanderインスタンスを追加する必要があります。

しかし実際にこのメソッドを追加してLogger.info()メソッドでログ出力をしてみると、以下の例外が発生しました。

Sep 08, 2021 4:59:15 AM io.grpc.internal.ManagedChannelImpl$2 uncaughtException
SEVERE: [Channel<1>: (logging.googleapis.com:443)] Uncaught exception in the SynchronizationContext. Panic!
java.lang.IllegalStateException: Could not find policy 'pick_first'. Make sure its implementation is either registered to LoadBalancerRegistry or included in M
ETA-INF/services/io.grpc.LoadBalancerProvider from your jar files.
        at io.grpc.internal.AutoConfiguredLoadBalancerFactory$AutoConfiguredLoadBalancer.<init>(AutoConfiguredLoadBalancerFactory.java:92)
        at io.grpc.internal.AutoConfiguredLoadBalancerFactory.newLoadBalancer(AutoConfiguredLoadBalancerFactory.java:63)
        at io.grpc.internal.ManagedChannelImpl.exitIdleMode(ManagedChannelImpl.java:406)
        at io.grpc.internal.ManagedChannelImpl$RealChannel$2.run(ManagedChannelImpl.java:978)
        at io.grpc.SynchronizationContext.drain(SynchronizationContext.java:95)
        at io.grpc.SynchronizationContext.execute(SynchronizationContext.java:127)
        at io.grpc.internal.ManagedChannelImpl$RealChannel.newCall(ManagedChannelImpl.java:975)
        at com.google.api.gax.grpc.GrpcChannelUUIDInterceptor.interceptCall(GrpcChannelUUIDInterceptor.java:52)
        at io.grpc.ClientInterceptors$InterceptorChannel.newCall(ClientInterceptors.java:156)
        at com.google.api.gax.grpc.GrpcHeaderInterceptor.interceptCall(GrpcHeaderInterceptor.java:80)
        at io.grpc.ClientInterceptors$InterceptorChannel.newCall(ClientInterceptors.java:156)
        at com.google.api.gax.grpc.GrpcMetadataHandlerInterceptor.interceptCall(GrpcMetadataHandlerInterceptor.java:55)
        at io.grpc.ClientInterceptors$InterceptorChannel.newCall(ClientInterceptors.java:156)
        at io.grpc.internal.ManagedChannelImpl.newCall(ManagedChannelImpl.java:917)
        at io.grpc.internal.ForwardingManagedChannel.newCall(ForwardingManagedChannel.java:63)
        at com.google.api.gax.grpc.ChannelPool.newCall(ChannelPool.java:143)
        at com.google.api.gax.grpc.GrpcClientCalls.newCall(GrpcClientCalls.java:99)
        at com.google.api.gax.grpc.GrpcDirectCallable.futureCall(GrpcDirectCallable.java:60)
        at com.google.api.gax.grpc.GrpcExceptionCallable.futureCall(GrpcExceptionCallable.java:64)
        at com.google.api.gax.rpc.AttemptCallable.call(AttemptCallable.java:87)
        at com.google.api.gax.rpc.RetryingCallable.futureCall(RetryingCallable.java:63)
        at com.google.api.gax.rpc.RetryingCallable.futureCall(RetryingCallable.java:41)
        at com.google.api.gax.tracing.TracedUnaryCallable.futureCall(TracedUnaryCallable.java:75)
        at com.google.api.gax.rpc.UnaryCallable$1.futureCall(UnaryCallable.java:126)
        at com.google.api.gax.rpc.UnaryCallable.futureCall(UnaryCallable.java:87)
        at com.google.cloud.logging.spi.v2.GrpcLoggingRpc.write(GrpcLoggingRpc.java:278)
        at com.google.cloud.logging.LoggingImpl.writeAsync(LoggingImpl.java:836)
        at com.google.cloud.logging.LoggingImpl.writeLogEntries(LoggingImpl.java:804)
        at com.google.cloud.logging.LoggingImpl.write(LoggingImpl.java:766)
        at com.google.cloud.logging.LoggingHandler.publish(LoggingHandler.java:237)
        at java.util.logging.Logger.log(Logger.java:738)
        at java.util.logging.Logger.doLog(Logger.java:765)
        at java.util.logging.Logger.log(Logger.java:788)
        at java.util.logging.Logger.info(Logger.java:1490)
        at xxx.xxxx.TestApp.main(TestApp.java:30)

この例外についてネットで検索してみると、以下の記事が見つかります。Cloud LoggingではなくFirestoreのアクセスのようですが、発生している例外はほぼ同じ内容です。

最初この記事を見たときは回答が1つしかついていないので解決していないのかと思ったのですが、「and it works fine now!」の記述があるとおり解決していました。約1週間かけての自己解決でした。

この記事を参考にログ出力が可能になったのが、以下のスニペットです。背景が黄色の行が、公式ドキュメントのスニペットに対して追加した部分です。公式ドキュメントにはLoadBalancerRegistryは一切登場しないので正しい解決方法かどうかは分かりませんが、期待どおりに動いています。標準出力に出ていたログはそのままで、GCPコンソールのログビューアでログが見えるようになりました。

    private static final Logger LOGGER = Logger.getLogger(SampleLogger.class.getName());

    public static void main(String[] args) {
        LoggingHandler.addHandler(LOGGER, new LoggingHandler());
        LoadBalancerRegistry.getDefaultRegistry().register(new PickFirstLoadBalancerProvider());

        LOGGER.info("test log");
    }


ログ出力結果

出力結果には以下の様に、各行あたり2つの日時が出力されてしまいます。
  • Cloud Loggingが追加した日時 : GCPコンソールのロケール依存
  • VMインスタンスのjava.util.logging.Loggerが追加した日時 : UTC
java.util.logging.Loggerをカスタマイズして日時をとってしまえば、2つ日時が出るのを解消できそうです。

おまけ : Cloud Loggingクライアントライブラリ

java.util.loggingパッケージでは単純な文字列の出力が行えるのみです。しかしCloud Loggingクライアントライブラリ(com.google.cloud.loggingパッケージ)を使用すれば、構造を持ったログも出力可能になります。
GAEなら「Using the client library」に記載のスニペットどおりで期待動作したのですが、GCEのVMインスタンスから出力してGCPコンソールのログビューアで見えるまではまだ出来ていません。
機会があったら、いずれまた。

2021/08/10

GCEにスケールアウトワークロード向けVMの追加

 GCEに新たな仮想マシンタイプ「Tau VM」が追加されるようです。


GCEでオートスケールを利用出来るVMはマネージドインスタンスグループしかなかったと思うのですが、ステートレスで構成しなければならない(=ディスクに頼れない)とか、スケールアウト/インのルールのカスタマイズが思うようにできないとか、不便でした。

まだドキュメントが公開されていなくて詳細は不明です。柔軟なスケールアウト/インが出来て、VMインスタンスの作成も簡単に出来るものだと嬉しいです。期待してます。

2021/07/26

LotteryServの開発

 LotteryServを開発してみて、苦労した点です。


1. 決済

最も苦労したのは決済です。
クレジットカード決済の実装が初めてで知識も経験もなかったというのもありますが、売掛で1度は実装したためにそれをクレジットカード決済対応に修正する必要が生じてしまったことも、作業量を増やす原因になってしまいました。
まず決済代行サービスの調査・選択から始まりました。決済代行サービスは20社以上出てきたのですが、ほとんどはリアル店舗での小売業を対象としたもので、ネットでAPIを使えるようにしているサービスは限られています。さらに各社から資料を取り寄せてAPIを精査してみると、1回ごとにクレジットカードの情報入力が必要なものと、予めクレジットカード情報を登録しておいてそれを使いまわせるものに分かれています。LotteryServでは応募数によって提供するサービスの途中で決済価格が変わるので、予めクレジットカード情報を登録できる決済代行サービスでないと、主催者に不便を強いることになります。そのため予めクレジットカード情報を登録できる決済代行サービスであることを確認する必要がありました。
またAPIの仕様が古くて、ちょっと使いたくないなと思わされるようなものもありました。10年以上前に作って、新しい技術が使われるようになっても仕様を更新することなくそのままになっている感じです。アクセス元を確認するために固定IPが必要など、GAEでは対応できない仕様を要求するものももあります。GCEなら対応できますが、決済のためだけにGCEを立ち上げたのではランニングコストが増えますし、何よりレスポンスが悪くてUIの構築が面倒になります。このような古い仕様のものは出来れば避けたいものです。
実装を始めてからも、最初は公開されているリクエストエンドポイントを直接たたいていたのですが、POSTのみリクエストを受け付けてもらえないという現象に悩まされました。GETは1発でクリアしたんですけどね。リクエストを受けた際のログなどが見れないので、どこに原因があるかわからず、解析が行き詰ってしまうことになりました。結局これは提供されているライブラリを利用することで回避しました。

2. 抽選

2番目が抽選でした。
応募数が多くなると抽選に時間がかかるようになるのは必然なので、応募数にあわせて並列処理することにしました。最初はとりあえずシングルスレッドで抽選処理の原型を作成。次にGAEで1インスタンスあたりどのくらいのスレッド数まで性能低下が起きないかを調査しておきます。
そしてどう並列実行を実装するのがベストかの調査です。
お手軽なGAEでの実装では、自動スケール/基本スケール/手動スケールそれぞれでパラメータを変えて試してみましたが、思ったようにインスタンスが増えてくれません。増えてほしいのになかなか増えててくれないか、まだ増えなくてもいいのに増えて何もせずに落ちていくとか。やはりGAEで思ったとおりにインスタンスを増減するのは難しいようです。ある程度おおざっぱな制御になります。デフォルト設定でもオートスケールしてくれるのは便利なんですけどね。
結局GAEは諦めてGCEで実装しています。GCEでは自分でインスタンスとスレッドの起動をコントロールするので思いどおりです。GCEでの実装の欠点は、無限にインスタンスを増やすことができないことです。あらかじめ用意しておいたディスクの数だけしかインスタンスを起動できません。これは運用で解決することにしました。


その他は、紹介してもつまらないものなので、割愛します。LotteryServの特徴の1つであるアクセス集中への耐性ですが、これも対応方法さえ知っていればどういうほどのことはありません。小さな工夫はたくさんありますが、長くなるのでまたの機会に。

しかし運用するためにそろえなければならない機能が多く、サービス開始までには思ったより時間がかかりました。