本記事では、「Azure Spring Apps」のローカル実行について、ご紹介したいと思います。
サービス名は最初「Azure Spring Cloud」となりましたが、2022年5月頃「Azure Spring Apps」と改名されました。
Azure Spring Apps では、コードをほとんど変更せずに、Spring Boot アプリケーションを Azure に簡単にデプロイできます。Spring アプリケーションのインフラストラクチャはこのサービスによって管理されるため、開発者はビジネスロジック・コードに専念できるメリットがございます。
詳しくは公式資料をご覧ください。
https://docs.microsoft.com/ja-jp/azure/spring-apps/overview
Azure Spring Apps アプリケーションを Azure へデプロイする前に、ローカル環境での実行方法について説明します。アプリケーションの依存関係次第で環境構築と実行方法が違います。データベースとキャッシュなどの依存がなければ、普通の Spring Boot アプリケーションとしてローカル環境で実行できます。
これから Azure Cosmos DB と接続する Azure Spring Apps アプリケーションを例としてローカル環境での実行方法を試してみます。
対象アプリ City-service
https://github.com/microsoft/azure-spring-cloud-training/tree/master/06-build-a-reactive-spring-boot-microservice-using-cosmosdb/city-service
依存関係: Cosmos DB と接続し、city という Collection からデータを抽出・表示
プロジェクトコードをローカルへダウンロードします。
ローカル環境
OpenJDK 64-Bit Server VM (build 17.0.2+8-86, mixed mode, sharing)
Apache Maven 3.8.5
OS: Windows10
Git & Git Bash: version 2.35.1.windows.2
設定
1. ローカル環境で DB をセットアップ
ローカル環境から Azure Cosmos DB へ直接接続できないため、Azure Cosmos DB Emulator を利用します。Azure Cosmos DB Emulator は、Azure Cosmos DB サービスを開発目的でエミュレートするローカル環境となります。ローカルでの開発とテストは、Azure のコストをかけずに実施できます。現時点は、Azure Cosmos DB Emulator は Windows、Linux、macOS、および Windows Docker 環境をサポートしています。
1.1 インストール
以下の公式手順の「エミュレーターをインストールする」部分よりインストールを行います。
https://docs.microsoft.com/ja-jp/azure/cosmos-db/local-emulator?tabs=ssl-netstd21
完了後自動的に立ち上がり、Windows タスク バーの通知領域にアイコンが表示されます。
以下の URL より Emulator のデータ エクスプローラーを開きます。
https://localhost:8081/_explorer/index.html
データ エクスプローラーの画面に、 URI と Primary Key の値をメモしておきます。後の設定ファイルに使われます。
1.2 データベース作成
まず、コンテナーの追加を行います。
左側のメニューから「Explorer」へ移動、「 New Container 」をクリックします。
表示された New Container 画面に以下の設定を入力します。
* Database id: spring-apps-cosmosdb002
* Container id: City
* Partition key: /name
最後「OK」ボタンを押下します。
1.3 データ投入
上記作成したデータベースを展開して、item まで表示させます。
次は、「New Item」をクリックします。
Json データーを 2 回分けて右側の入力欄に貼り付けて、「Save」ボタンを押します。
{
"name": "Paris, France"
}
同様で以下のデータを登録します。
{
"name": "London, UK"
}
登録した 2 件のレコードを確認します。
2. アプリケーション設定
ローカル環境実行用の application.proerpties を作成します。
プロジェクト直下の src/main/resources へ移動し、以下の手順を実行します。
touch application-local.properties
vi touch application-local.properties
azure.cosmosdb.uri = 上記メモの URI を貼り付け
azure.cosmosdb.key = 上記メモの Primary Key 貼り付け
azure.cosmosdb.database = Cosmos DB を設定した際に、New Container 画面に入力した Database id を貼り付け
eureka.client.register-with-eureka= false
eureka.client.fetch-registry= false
spring.cloud.config.import-check.enabled= false
3. JDK 設定
Java トラストストアへ認証局証明書をインポートする必要があります。
理由:Cosmos DB の URI は https となっているため、Java は Https のアプリへ接続すると、接続先の認証局証明書はトラストストア($JAVA_HOME/lib/security/cacerts)に存在する必要があります。Java のトラストストアにすでに主なルート認証局の証明書が入っています。もし新規追加しないと、Java はその証明書を信用せず、アプリケーションへの接続に失敗します。エラーの詳細は本記事の補足部分をご参照ください。
3.1 証明書ダウンロード
https://localhost:8081/_explorer/index.html アクセス(Chromeの場合)
まずアドレス欄のロックアイコンをクリックします。ポップアップ画面に「この接続は保護されています」を選択します。
次の画面に「証明書は有効です」を選択します。
表示された画面に「詳細」タブへ切り替え、「選択した証明書をエクスポート」ボタンをクリックします。保存先を設定して、デフォルトの拡張子( Base64 エンコード ASCII 形式の単一の証明書)でファイル名を入力・保存します。保存先のフォルダに出力した crt ファイルを確認します。
Chrome からエクスポートの代わりに、Windows 証明書マネージャーを使用してダウンロードする方法もあります。詳細は公式資料 https://docs.microsoft.com/ja-jp/azure/cosmos-db/local-emulator-export-ssl-certificates ご参照ください。
3.2 証明書をインポート
cd $JAVA_HOME /lib/security/cacerts
keytool -cacerts -importcert -alias cosmos_emulator -file { 上記保存した箇所のパス} /{ crtファイル名}
次は、パスワード提示の画面が表示されます。「changeit」を入力します。
Enter keystore password:
最後は「この証明書を信頼しますか? 」と聞かれます、「yes」を入れます。
Trust this certificate? [no]: yes
Certificate was added to keystore
Azure Cosmos DB Emulator の Certificate TLS/SSL 証明書がインストールされると、アプリケーションはローカルの Azure Cosmos DB Emulator に接続し、それを使用できるようになります。 問題が発生した場合は、SSL/TLS 接続のデバッグに関する記事を参照してください。https://docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/ReadDebug.html
実行
次のコマンドを実行してアプリを起動させます。
cd { プロジェクトフォルダ}
mvn clean package -DskipTests
mvn spring-boot:run -Dspring-boot.run.profiles= local
起動後のコンソールは以下の通りです。
動作確認
curl http://localhost:8080/cities またはブラウザで http://localhost:8080/cities へアクセスして、Http Status = 200 & 正常レスポンスを確認します。
$ curl -v http://localhost:8080/cities
* Trying 127.0 .0.1:8080.. .
* Connected to localhost ( 127.0 .0.1) port 8080 (
> GET /cities HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< Content-Type: application/json
<
[ [ { "name" : "Paris, France" } ,{ "name" : "London, UK" } ] ] * Connection
まとめ
上記の手順で Spring Apps をローカル環境実行ができましたが、あくまで単体の機能となります。Configure Server と Eureka などの部分、また Cosmos DB 以外依存する Spring Apps は今後の記事で確認・検証します。
補足:警告・エラー対処
1. ImportException
!
01:41:18.857 [main] DEBUG org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter - Application failed to start due to an exception
org.springframework.cloud.commons.ConfigDataMissingEnvironmentPostProcessor$ImportException: No spring.config.import set
at org.springframework.cloud.commons.ConfigDataMissingEnvironmentPostProcessor.postProcessEnvironment(ConfigDataMissingEnvironmentPostProcessor.java:82)
at org.springframework.boot.env.EnvironmentPostProcessorApplicationListener.onApplicationEnvironmentPreparedEvent(EnvironmentPostProcessorApplicationListener.java:102)
at org.springframework.boot.env.EnvironmentPostProcessorApplicationListener.onApplicationEvent(EnvironmentPostProcessorApplicationListener.java:87)
at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:176)
at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:169)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:143)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:131)
at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:85)
at org.springframework.boot.SpringApplicationRunListeners.lambda$environmentPrepared$2(SpringApplicationRunListeners.java:66)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:120)
at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:114)
at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:65)
at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:344)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:302)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295)
at com.example.demo.DemoApplication.main(DemoApplication.java:16)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.boot.maven.AbstractRunMojo$LaunchRunner.run(AbstractRunMojo.java:496)
at java.base/java.lang.Thread.run(Thread.java:833)
01:41:18.862 [main] ERROR org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter -
APPLICATION FAILED TO START
Description:
No spring.config.import property has been defined![]
Action:
Add a spring.config.import=configserver: property to your configuration.
If configuration is not required add spring.config.import=optional:configserver: instead.
To disable this check, set spring.cloud.config.enabled=false or
spring.cloud.config.import-check.enabled=false.
設定サーバーと関係する設定がない場合のエラーです。上記提示された Action 通りで、もし設定サーバーを使うと、「spring.config.import=configserver: property」を .properties ファイルに定義します、例:spring.config.import=configserver:http://localhost:8888
必須では無ければ、spring.cloud.config.enabled=false or spring.cloud.config.import-check.enabled=false を .properties ファイルに記載します。
2. InstanceInfoReplicatorの警告
!
2022-09-15 10:25:12.316 WARN 13216 --- [nfoReplicator-0] c.n.discovery.InstanceInfoReplicator : There was a problem with the instance info replicator
com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server
at com.netflix.discovery.shared.transport.decorator.RetryableEurekaHttpClient.execute(RetryableEurekaHttpClient.java:112) ~[eureka-client-1.10.17.jar:1.10.17]
at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.register(EurekaHttpClientDecorator.java:56) ~[eureka-client-1.10.17.jar:1.10.17]
at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator$1.execute(EurekaHttpClientDecorator.java:59) ~[eureka-client-1.10.17.jar:1.10.17]
at com.netflix.discovery.shared.transport.decorator.SessionedEurekaHttpClient.execute(SessionedEurekaHttpClient.java:77) ~[eureka-client-1.10.17.jar:1.10.17]
at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.register(EurekaHttpClientDecorator.java:56) ~[eureka-client-1.10.17.jar:1.10.17]
at com.netflix.discovery.DiscoveryClient.register(DiscoveryClient.java:876) ~[eureka-client-1.10.17.jar:1.10.17]
at com.netflix.discovery.InstanceInfoReplicator.run(InstanceInfoReplicator.java:121) ~[eureka-client-1.10.17.jar:1.10.17]
at com.netflix.discovery.InstanceInfoReplicator$1.run(InstanceInfoReplicator.java:101) ~[eureka-client-1.10.17.jar:1.10.17]
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539) ~[na:na]
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[na:na]
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) ~[na:na]
at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
Eureka は Netflix 社当初開発したフレームワークです。マイクロサービス構成のシステムに新規配布したサービス検出・登録・管理できる機能を持っています。英訳は「Service Discovery」となります。詳細は公式資料をご参照ください。
https://spring.io/projects/spring-cloud-netflix
本記事のローカル環境は Eureka 部分をカーバーしていないため、上記の手順通りで application-local.properties に Eureka に関する設定を無効にします。 設定しないと、警告が出力されます。
3. sun.security.validator.ValidatorException
Java トラストストアへ認証局証明書をインポートしていない場合、Exception が発生します。上記の手順で解決できます。その他、SSL アクセス用のキーの認証手続きをスキップさせる手もあります、接続用の HttpClient パラメーターをいじって対応可能ですが、今回 Lib を使っているため、適切な選択肢ではないです。詳しくは、以下の記事を参照ください。
https://stackoverflow.com/questions/1828775/how-to-handle-invalid-ssl-certificates-with-apache-httpclient
Discussion