AWS/GCP/AzureのPaaSを使って特性の違いを理解する

＃本記事は前職社内向けのアドベントカレンダーの記事として、外に出てから学んだことを中心にまとめたものです。

最近のクラウドではコンテナ/k8s対応やAI関連などのサービスが取り上げられることが多く、普通のWebアプリ開発のプラットフォームは落ち着いてきた感があります。とはいうもののこれからクラウドへの移行を検討しているという話もまだまだ多く聞きますので、運用負荷を減らすPaaS前提でクラウドの特徴をなるべく具体的に紹介したいと思います。

自身の経験としては以下の通りです。顧客・案件に応じて複数のクラウドを利用しています。

* AWS : チョットデキル
* GCP : チョットデキル
* Azure　: 横目で見てた程度


## 想定するアプリケーション

いわゆる普通のWebアプリケーション開発を想定しています

* Java+SpringBootでの業務系Webアプリケーション(APIサーバでもよい）
 - LoadBalancer、冗長構成
 - ネットワーク設定
 - オートスケール
 - ログ参照
* MySQLなどのDB

デモアプリのコードはこちらで公開しています。今回は作った環境ではDBの組み込みまでは入っていません。
https://github.com/nyasba/spring-integration-cloud-demo


## 細かいところまで手が届くAWS

クラウド利用で必ず候補として上がるAWS。そのPaaSといえば2011年に発表された **ElasticBeanstalk** です。

公式：https://aws.amazon.com/jp/elasticbeanstalk/
> お客様はコードをアップロードするだけで、Elastic Beanstalk が、キャパシティーのプロビジョニング、ロードバランシング、Auto Scaling からアプリケーションの状態モニタリングまで、デプロイを自動的に処理します。同時に、お客様のアプリケーションが稼動している AWS リソースの完全なコントロールを維持でき、いつでも基本的なリソースにアクセスすることができます。

### 構成

ELBの配下にAutoscaleするEC2インスタンスが複数あるような構成です。
RDSもBeanstalk配下で管理することは可能ですが、ライフサイクルが違うため、ELB、EC2などAPサーバの範囲のみをBeanstalkで管理する構成をとります。インフラ（ネットワーク設定など）やDB周りは別で事前に作っておくことになります。
<img width="700" alt="aws.png" src="https://qiita-image-store.s3.amazonaws.com/0/47589/49dbfa08-8928-b975-dde8-c4b8ddc74ec0.png">
図からもわかる通り、BeanstalkはAWSの各サービスを寄せ集めて1つのPaaSとして成り立っていることが大きな特徴です。これから詳細をみていきましょう。


### アプリコードの修正

beanstalkにデプロイするアプリの準備です。デプロイ時はjarファイルおよび.ebextensions（任意）を使います。
.ebextensionsは立ち上げたインスタンス上でスクリプトを実行し、初期設定をする際などに使います。タイムゾーンや言語設定などでよく使われます。

build.gradleにzipタスクを追加しておくことで、デプロイ用のbuild/distribution/demo.zipを作成することができ、それを元にアプリを立ち上げることになります

```build.gradle
task zip(type: Zip, dependsOn: jar) {
	from ('etc/.ebextensions') {
		into '.ebextensions'
	}
	from(jar.outputs.files){
		into '.'
	}
	archiveName = "${project.name}.zip"
}
```

参考
https://github.com/nyasba/spring-integration-cloud-demo/pull/1/files

### クラウドでのアプリ環境立ち上げ

Beanstalkのメニューから上記のzipをアップロードすることで環境を立ち上げることが可能です。

詳細には説明しませんが、気をつけるところはこのあたりです

* セキュリティグループにてアクセス可能なIPを絞っておく
* APサーバの環境変数で `SERVER_PORT=5000`を設定する（Beanstalkはport5000でリクエストを受け付けるため）
* ELBを有効化

数分で環境が立ち上がり、URLをクリックするとアプリにアクセス可能です

### Beanstalkの設定

環境設定はこのような画面で確認できます。
<img width="1200" alt="ebconsole.png" src="https://qiita-image-store.s3.amazonaws.com/0/47589/2a1ee5f4-f7b2-ebc5-f6d3-755c4fb8e982.png">

ここからもわかる通り、各構成要素でかなり細かい設定が可能です。
これは熟練者にとっては細かく制御できるのよいのですが、初心者にとっては何をどうすればいいかがわからないという問題があります。

CDパイプラインを設定する際はこれらをCode化して管理することになります。
初回は「環境の保存」というメニューから保存すると、S3バゲット`elasticbeanstalk-ap-northeast-1-{アカウント番号}`の`resources/templates/{アプリケーション名}`に出力されるものから始めると良いかと思います。

```yaml
EnvironmentConfigurationMetadata:
  DateCreated: '1544329847000'
  DateModified: '1544329847000'
Platform:
  PlatformArn: arn:aws:elasticbeanstalk:ap-northeast-1::platform/Java 8 running on 64bit Amazon Linux/2.7.7
OptionSettings:
  AWSEBAutoScalingGroup.aws:autoscaling:asg:
    MaxSize: '1'
  aws:elasticbeanstalk:application:environment:
    SERVER_PORT: '5000'
  aws:elasticbeanstalk:cloudwatch:logs:
    RetentionInDays: '3'
    StreamLogs: true
  aws:elasticbeanstalk:environment:
    ServiceRole: aws-elasticbeanstalk-service-role
    LoadBalancerType: application
  aws:elasticbeanstalk:healthreporting:system:
    SystemType: enhanced
  aws:autoscaling:launchconfiguration:
    SecurityGroups: defaultsg
    IamInstanceProfile: aws-elasticbeanstalk-ec2-role
EnvironmentTier:
  Type: Standard
  Name: WebServer
AWSConfigurationTemplateVersion: 1.1.0.0
```

これがあれば　`eb create`, `eb deploy`コマンドで環境の作成,更新が実行できる状態となります。
デプロイはEC2を一から立ち上げているため、数分単位で時間がかかります（Autoscalingも同様）

### Cloudformationとの関係

Beanstalkは実際のところ、AWSのリソースを設定ファイルから一括で作成・管理することができるCloudformationのラッパーです。Cloudformationのコンソールからも確認できます。

[補足] 最近、Cloudformationに**ドリフト検知機能**が追加されました。Cloudformation以外で更新されたことを検知する機能であり、これによりInfrastructure as a Codeがさらに徹底できるようになりました:thumbsup:

<img width="1200" alt="cfn.png" src="https://qiita-image-store.s3.amazonaws.com/0/47589/8524f1ba-84b4-bcaf-bfde-ed6534fb66c0.png">

Beanstalkの環境立ち上げでエラーとなった場合は、Cloudformationの機能にて管理しているリソースの状態がロールバックされます。エラー原因を特定したり、環境作成の状況を見るためにもこの画面は重宝しています。
ただし、管理対象のAWSのリソースは非常に細かく、ELBを追加するだけでも「ロードバランサー自体の設定」、「ELBのリスナー(接続先のAPサーバポートなど)の設定」など複数の設定が必要になります。そのため、AWSリソースの関係まで理解しないと使いこなせないというのが難点です。

設定できる項目は以下を参照ください。
https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/command-options-general.html

### EC2との関係

同様にEC2のメニューから情報は参照できますし、メニューから固定IPの付与などを実施することも可能です。（推奨はしませんが）普通にEC2インスタンスを立ち上げた時と同等の使い方ができます。

### ログ

環境設定を行うだけで、以下の出力が可能です。

* CloudwatchLogs
* S3への定期エクスポート(インスタンスが落ちる際にエクスポートするのは個別対処)
* X-ray

Cloudwatchで確認できる例です。内部で動作しているnginxのaccess/errorログやアプリケーションのログ(web-1.log）が取れますので、これを元にメトリクスを作って監視することも簡単に組み込めます。
<img width="400" alt="awslog.png" src="https://qiita-image-store.s3.amazonaws.com/0/47589/11b30d8c-4a0a-b51d-b3cb-1ddede584690.png">

### AWSまとめ

AWSはIaaSから成り立ってきている背景から下回りとなるサービスの構造や機能を理解している前提で上記のPaaSを利用することが求められます。逆に言うと、下回りを理解していればPlatformの制約に縛られずインフラレベルでなんとかなるというのが一番の特徴です。
やりたいと思ったことは頑張ればなんとかなるのもAWSならではですね。


## ストイックだがハマれば強いGCP

2008年から提供されている **GoogleAppEngine**（GAE）と呼ばれるPaaSがあります。今回はそのStandard Environment（古くからある方）を取り上げます。

公式：　https://cloud.google.com/appengine/?hl=ja
> オープンなクラウド プラットフォームで最新のウェブ アプリケーションやモバイルアプリを開発でき、独自の言語ランタイム、フレームワーク、サードパーティ ライブラリを導入することも可能です。Google App Engine はインフラストラクチャが完全に抽象化されたフルマネージド プラットフォームであり、デベロッパーはコードの記述に専念できます。ゼロの状態からグローバルな規模までスケーリング可能で、今日、大きな成功を収めている企業でも活用されています。

### 構成

GAEの場合はPlatform内にロードバランシングやスケールアウトの仕組み、さらにはmemcache、タスクキュー、cronなど充実した機能がふくまれているため、構成図上ではかなりシンプルな構成となります。
![gcp1.png](https://qiita-image-store.s3.amazonaws.com/0/47589/1b78e327-bbe4-b841-e2a6-7cd06d983702.png)
Stackdriverはログマネジメントサービスです。AWSで言うCloudwatchですが、機能は充実しています。


### アプリコードの修正

GAEではJettyベースで動いているため、組み込みTomcatの依存関係を外し、ServletAPIを追加します。
さらに、GAEはコンソールからのデプロイができないため、[gradle-plugin](https://github.com/GoogleCloudPlatform/app-gradle-plugin)を使ってデプロイします。

```build.gradle
configurations {
	// servletAPIを使うので組み込みtomcatをexclude
	compile.exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
}

dependencies {
	implementation('org.springframework.boot:spring-boot-starter-web')
	testImplementation('org.springframework.boot:spring-boot-starter-test')

	// GAE
	implementation('com.google.appengine:appengine-api-1.0-sdk:+')
	providedCompile('javax.servlet:javax.servlet-api:3.1.0')
}

appengine {
	// local config ( dev_appserver )
	run {
		port = 8081
	}

	deploy {
		stopPreviousVersion = true
		promote = true
	}
}
```

以下のコマンドでローカル起動できます(port:8081)

```
./gradlew clean appengineRun
```

### クラウドでのアプリ環境立ち上げ

デプロイタスクを実行するだけでOKです。

```
./gradlew clean appengineDeploy
```

1分程度でデプロイが完了します。GCPではアプリを「サービス」という単位で管理しており、今回はdefaultのサービスとしてデプロイしました。プロジェクトIDに応じたエンドポイントが自動的に作成されます。
デプロイしたアプリもversionという概念で管理されており、トラフィックを切り替えることでBlueGreenデプロイやカナリアリリースも可能です。

```
descriptor:      [/Users/..../demo/build/staged-app/app.yaml]
source:          [/Users/..../demo/build/staged-app]
target project:  [PROJECT_NAME]
target service:  [default]
target version:  [20181209t155241]
target url:      [https://PROJECT_NAME.appspot.com]


Beginning deployment of service [default]...
#============================================================#
#= Uploading 130 files to Google Cloud Storage              =#
#============================================================#
File upload done.
Updating service [default]...
...............done.
Setting traffic split for service [default]...
.......done.
Deployed service [default] to [https://PROJECT_NAME.appspot.com]
```

参考
https://github.com/nyasba/spring-integration-cloud-demo/pull/2/files


GCPコンソールではこのようなものが見れます。
![gcpconsole.png](https://qiita-image-store.s3.amazonaws.com/0/47589/41cf77c3-7739-d4bc-f93b-fe8675a53035.png)

### 制約と誓約

microserviceの概念で構成されており一見すると使いやすく、機能も揃ってそうに見えるGCPですが、使いこなすためには制約と上手く付き合っていく必要があります。automatic scalingの前提では以下の制約があります。

* リクエストは60秒以内
* アップロード可能なファイルサイズはMax32MB
* [インスタンスタイプ](https://cloud.google.com/appengine/docs/standard/#instance_classes)のスペックが低い（F系）
* インスタンスの起動時間が60秒を越えると起動失敗となる

これらの制約を設けることで、秒単位のscalingが実現でき、突発的なアクセス増にも即座にインスタンスを増やしてリクエストを処理することができるようになっています。（リクエストを受けてから新しくアプリが起動してリクエストを捌くという概念は驚きました。言語によってはミリ秒単位でのスケールも可能なようです）

ただ、GoogleAppEngineの制約を回避する別環境もFlexibleEnvironmentという名前で提供されています。
こちらはDockerベースで環境をデプロイできるためかなり柔軟ですが、Standardでしか使えない機能も多く、悩ましいです。

TaskQueueにデータを入れるのがStandardからしかできなかった（受ける方はhttpなので可能）のが一番辛かったですが、CloudTasksという新サービスとして提供されるようになったため、今後は対応されていきそうな期待を持って待っています。
https://cloud.google.com/blog/products/application-development/announcing-cloud-tasks-a-task-queue-service-for-app-engine-flex-and-second-generation-runtimes

### ログ

Stackdriverのログは以下のようにリクエスト単位で確認できます。最初はすこし戸惑いましたが、これは便利です。
![stackdriver.png](https://qiita-image-store.s3.amazonaws.com/0/47589/67fa4f82-dc09-3789-b7be-f55407c49530.png)

さらにトレース画面にてAPIの処理の中でどこにどれだけ時間がかかっているかが可視化できます。細かくログを出しておくとその単位でわかるのですごく便利です（ここはAWSのX-rayも同様なのかと思いますが、まだ使ってません）
![stackdriver2.png](https://qiita-image-store.s3.amazonaws.com/0/47589/92c79ca8-0340-1787-72ff-1e04d2b3cd55.png)

その他、ErrorReportingで例外発生時のログを確認できるなどStackdriverは素晴らしいですが、作成したカスタムメトリクスから該当のログに飛べないところだけはつらいのでなんとかしてください・・。

### GCPまとめ

良いところを中心に紹介してきましたが、やはりGCPは起動が遅いJava言語とは相性がよいとは言えません。Spring Frameworkを使うことでさらにロードに時間がかかるため、ComponentScanの対象を絞るなどさらにコードに制約が発生します。
そのようなストイックな環境ですが、使いどころを見誤らなければ非常に使えるものではないかと感じています。最近はエンタープライズ向けに力を入れているようで、そういった機能が充実してくればもっと使えるものになってくるのではないかと期待しています。すぐにデプロイできてフィードバックループも早い開発プロセスが実現できるので、個人的には好きです。


## バランスのよいAzure

AzureのPaaSはAppServiceです。

公式：https://azure.microsoft.com/ja-jp/services/app-service/
>あらゆるプラットフォームで稼働するエンタープライズグレードの Web アプリ、モバイル アプリ、API アプリをすばやく構築、デプロイ、およびスケーリングできます。パフォーマンス、スケーラビリティ、セキュリティ、コンプライアンスの厳しい要件を満たしながら、フル マネージド プラットフォームを使用してインフラストラクチャ メンテナンスを行います。

* Windows/Linux環境に対応
* HotPoolのため、アプリのデプロイが早い
* ログはApplicationInsight
* （PaaSと関係ないけど）AzurePortalがつらい

どちらかというとGAEに近い位置付けですが、そこまで制約があるわけでもなくバランスもよいと感じています。
（自分が触っていないのはPortalの権限まわりでいつもハマるからです・・

### 参考: ColdPool/HotPool

* ColdPool : リソースが必要になったら環境を一から作る
* HotPool : アプリがデプロイできる環境は用意してあり、必要になったタイミングでデプロイのみ実施して提供する

AppService,GAEはHotPool、BeanstalkはColdPoolに分類されます。

詳しくは岡さんの資料を参照ください。
https://www.slideshare.net/hiromasaoka/documentdbapp-service


## まとめ

SpringのWebアプリをクラウドにデプロイするには、という観点でAWS（Beanstalk), GCP(AppEngine), Azure(AppService)についての特徴をまとめました。WebアプリのPaaSとしてはどのクラウドでも実現はできるため、機能の裏側まで想像して成り立ちや構造を理解し、考え方にあったものを選ぶことがクラウド選定において重要になってくると感じています。

いろいろ触って違いを理解するのは単純に楽しい上に、新しい発表があったときにまた違った視点で新機能を評価できるので面白いのでそのきっかけになれば幸いです。