GCP Gradle Kotlin Spring インフラ

Spring(Kotlin/Gradle)×AppEngine×CloudSQLを使ったサーバーレスアプリの作成

TD;DR

2022年末にこんなアプリを作りました。

そして、転職先でJava(Kotlin)をメインで使うということがあったので、まずはこのPythonのCloud Functionsとスプレッドシートを使った構成から、Spring(Kotlin)のAppEngineとCloudSQLを使った構成に置き換えます。

そして、Reactから通知を飛ばす番組を選択できるようにします。

また通知先はメールからSlackに変更予定です。

この構成を作るにあたって、今回はSpring(Kotlin)とAppEngineとCloudSQLに置き換えるところまでやりました。

そして、めちゃくちゃハマりました…

というわけで、今回はSpring(Kotlin/Gradle)×AppEngine×CloudSQLを使ったアプリの構築手順について書きたいと思います。

なお、今回のアプリケーションはREST APIを処理するためのアプリケーションです。

環境

  • アプリケーション環境
    • Spring Boot: 3.0.2
    • Kotlin(Java: 17)
    • Gradle - Kotlin
    • MySQL: 8.0
  • GCP環境
    • AppEngine: Java17 スタンダード環境
    • Cloud SQL: MySQL8.0, プライベートIPを付与
    • VPCネットワーク: サーバーレスVPCコネクタ

プロジェクトの初期構成はSpring initializrで作成します。

この時ですが、PackagenameにはJarを選択しておいてください。

(WarだとTomcatを含めてAppEngineへデプロイするための別途設定が必要になってきます。)

ここで解説すること

Springのアプリケーション構築自体には言及しません。

Spring/JPAを使ったアプリの構築については、公式ドキュメントこちらのサイトの内容が参考になることでしょう。

今回はローカル環境にてSpring/JPAを使ってMySQLとのやりとりができている前提で、ではそれをAppEngineとCloud SQLで動かそうという試みです。

ざっくりした手順

  • AppEngineの準備
  • VPCの作成とサーバーレスVPCアクセスコネクタの作成
  • Cloud SQLの準備
  • 本番用の設定ファイルの準備
  • GradleでJarファイルをビルド
  • デプロイ

という感じです。

次から詳しく行きます。

本格的に手順

AppEngineの準備

GCPのコンソールからプロジェクトを作成し、メニューのAppEngineからデプロイ先となるサービスを作成します。

このとき注意が必要なのはリージョンです。

作成するVPC,Cloud SQLとおなじリージョンにAppEngineが存在しないとダメです。

AppEngine側では、この時点ではこれ以上何もする必要がありません。

VPCの作成とサーバーレスVPCアクセスコネクタの作成

CloudSQLにプライベートIPを付与するために必要なVPCネットワークを作成します。

このときリージョンを選択する必要がありますが、このリージョンはAppEngineを作成したリージョンと同じである必要があります。

またサブネット範囲を入力するよう言われますが、今回はサブネット内部のIPの数はCloudSQLが使う分だけあれば今は大丈夫なので最小限でいいでしょう。

他の選択値についてはデフォルトのままで大丈夫です。

VPCが作成できたら、サーバーレスVPCアクセスコネクタを作成します。

同じくVPCのメニューにあります。

ここでリージョンはAppEngineとVPCと同じリージョン。

ネットワークは先ほど作成したVPC名が表示されるはずなので、それを選択します。

Cloud SQLの準備

CloudSQLでDBを作成します。

このとき気をつけるのが、「インスタンスのカスタマイズ」→「接続」にある「プライベートIP」のチェックをONにし、「ネットワーク」に表示されている先ほど作成したVPCを選択します。

これで先ほど作成したVPCサブネット内で利用できる内部IPアドレスがこのDBインスタンスに紐づきます。

イメージとしてはこういう感じ。

https://cloud.google.com/vpc/docs/serverless-vpc-access?hl=ja

要はAppEngineとCloudSQLはどちらもGCP内部にあるサービスなので、わざわざパブリックネットワーク経由で通信するのではなく、内部IPで通信すればいいよねというわけです。

ただし、AppEngineのサービスはリクエストのたびにどこかのマシン上にホストされて起動することになるわけなので、リージョンは決まっていてもどのゾーンで起動するかはわからないわけです。

そこでAppEngineにサーバーレスVPCアクセスコネクタの情報を教えてあげて、内部リソースへの通信経路を教えてあげます。

その情報をもとにサーバーレスVPCアクセスコネクタの先(=作成したVPCに紐づけられたサービス=今回の例だとCloudSQL)のIPが振られているサービスと通信するというわけです。

ここまででクラウド側の準備は完了です。

本番用設定ファイルの準備

ライブラリの追加

まずAppEngineからCloudSQLへ接続するためのライブラリを入れます。

dependencies {
 ↓これを追加
	implementation("com.google.cloud:spring-cloud-gcp-starter-sql-mysql:3.0.0")
}

これがAppEngineにデプロイされた場合に、application.ymlに記載した設定内容をもとにCloudSQLへの接続をよしなにしてくれるライブラリです。

これがないとAppEngineでアプリを起動した際にエラーとなって落ちます。

application.ymlの設定

別途application-dev.ymlをsrc/main/resourcesフォルダに作成し、デフォルトのapplication.ymlの内容をこのファイルに貼り付けます。

これは開発環境用の設定ファイルとして利用します。

そして、今度は本番用にapplication-prod.ymlを作成します。

こちらの内容は以下のような感じです。

server:
  port: 8080
database: mysql
cloud:
  gcp:
    sql:
      database-name: CloudSQLに作成したデータベース名
      instance-connection-name: CloudSQLの接続名
spring:
  cloud:
    gcp:
      sql:
        database-name: CloudSQLに作成したデータベース名
        instance-connection-name: CloudSQLの接続名
  datasource:
    url: jdbc:mysql://CloudSQLに割り当てられた内部IP:3306/CloudSQLに作成したデータベース名
    username: ユーザー名
    password: パスワード
    driver-class-name: com.mysql.jdbc.Driver
    database-name: CloudSQLに作成したデータベース名
    instance-connection-name: CloudSQLの接続名

何回同じことを書くんだという声が聞こえてきそうなのですが、これらを全て書かないと起動時に落ちてしまいます。

落ちている理由なのですが、一つ前の手順で入れたライブラリのうちのクラスがdatabase-nameとinstance-connection-nameをこの3ヶ所分見ているようで、そのクラスがnewされるタイミングでこの設定値をとりにくるようです。

そして、設定値が見つからない場合は例外を吐いて止まっていました。

なぜこの3箇所を別に見ているのかまではわかりませんでしたが、とにかく書かないと落ちるので仕方ない…

application-prod.ymlが作成できたら、元のapplication.ymlの内容を以下のように書き換えます。

spring:
  profiles:
    active: prod

これはここで作ったapplication-dev.ymlとapplication-prod.ymlを切り替えるための設定です。

activeにdevを設定するとapplication-dev.ymlが、prodに設定するとapplication-prod.ymlが適用されてアプリケーションがビルドされます。

必要に応じてこの設定は切り替えてください。

GradleでJarファイルをビルド

プロジェクトのルートディレクトリで以下のコマンドを実行

gradle build

これでビルドに成功すると、./build/libsにJarが出来上がります。

AppEngineへのデプロイには、plainがついていないJarファイルを使います。

デプロイ

デプロイ用のフォルダとapp.yamlの作成

AppEngineスタンダード環境へのデプロイを行うには、実行用JarとAppEngine自体の設定ファイルであるapp.yamlの二つが含まれるディレクトリが必要です。

なので、ディレクトリを作ってあげる必要があります。

今回は、buildフォルダの直下にstaged-appという名前のフォルダを作ります。

それから、AppEngine用設定ファイルのapp.yamlを作ります。

app.yamlは以下の内容でプロジェクトの直下に作ります。

ちなみにapp.yamlの拡張子はymlではなくyamlじゃないとデプロイ時に怒られるので注意。

それとインスタンスクラスにはF2を指定しています。

今回はデータベースへの接続等をやるので256MBではメモリが足りませんでした。

(筆者環境だと286MBを使って落ちました)

なので最初からF2に設定しておきます。(オートスケールの設定をする方はF1でも問題ないと思います。)

AppEngineのマシンスペックは別途こちらをご参照ください。

runtime: java17
vpc_access_connector:
  name: projects/プロジェクトID/locations/作成したサーバーレスVPCアクセスコネクタのリージョン/connectors/作成したサーバーレスVPCアクセスコネクタの名前
instance_class: F2

だいぶごちゃごちゃしてきたので今のプロジェクト構成を見せるとこんな感じ。

ここで、app.yamlをバージョン管理対象にしつつデプロイまでを楽にしたいのでシェルファイルを作っておきます。

デプロイ用シェル

以下のシェルを実行することで、Jarのビルドからデプロイまでを勝手にやってもらおうという魂胆です。

プロジェクト直下にapp.yamlを作っておくことでバージョン管理対象にしつつ、このシェルで毎回デプロイのたびにデプロイ用のフォルダに集めてもらおうというわけです。

#!/bin/bash

rm -f ./build/libs/UP対象のJarファイル
rm -f ./build/staged-app/UP対象のJarファイル
rm -f ./build/staged-app/app.yaml

gradle build

cp ./build/libs/UP対象のJarファイル ./build/staged-app
cp ./app.yaml ./build/staged-app

cd ./build/staged-app

gcloud app deploy

デプロイ

Jarファイルをapp.yamlをデプロイ用のディレクトリの下に配置して「gcloud app deploy」コマンドを叩く、もしくは先のシェルを実行するとデプロイが起こります。

デプロイが終わったら、あとはブラウザから正常にリクエストが実行されることを確認してみてください。

所感

ひとまずAppEngineにデプロイすることができたのでよかったです。

ここからは

Spring×Kotlin×Gradleで作成したアプリをAppEngineスタンダード環境で動かす記事が検索しても有用なものが全く出てこなかったので、この記事が後の誰かの役に立てばとても嬉しいです。

おそらくみなさんCloudRunを使ってるのかなと思っています。

直近の推し5選!

1

みなさんこんばんは。 こんにちは、Kanon です。今回は… しめさば先生の『君は僕の後悔』の感想記事です。 しめさば先生はこれまでにも『ひげを剃る。そして女子高生を拾う』や『きみは本当に僕の天使なの ...

2

リンク あらすじ 人生オール80点。 そんな俺が託されたのは、元トップアイドル・香澄ミルの世話だった。 ファン対応がしみつきなかなかクラスに馴染めないミル。 そんな彼女に頼られるうち、俺たちは図らずも ...

3

リンク 二丸先生の他の作品はこちら あらすじ 記憶喪失の湖西廻の前に現れたのは、清純で素朴な美少女、丹沢白雪。 「──私、廻くんと恋人だったの」  白雪はそう言って顔を赤らめ、廻の頬にキスをする。   ...

4

リンク あらすじ その夜、僕の青春は〈炎〉とともに産声をあげた――  スマホを忘れて夜の学校に忍び込んだ在原有葉(ありはらあるは)は、屋上を照らす奇妙な光に気づく。そこで出会ったのは、闇夜の中で燃え上 ...

5

こんにちは、Kanon です。今回は… しめさば先生の『きみは本当に僕の天使なのか』の感想記事です。 表紙とタイトルを見るに幻想的な恋の話のように思えるのですが、実はタイトルはそんな幻のようなものでは ...

-GCP, Gradle, Kotlin, Spring, インフラ
-, , , , , , ,