Spring Boot 2.2.x の Web アプリを 2.3.x へバージョンアップする ( その2 )( Spring Boot を 2.2.2 → 2.2.9 へ、Gradle を 6.0.1 → 6.5.1 へバージョンアップする )
概要
記事一覧はこちらです。
Spring Boot 2.2.x の Web アプリを 2.3.x へバージョンアップする ( その1 )( 概要 ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
- Spring Boot のバージョンを 2.2 系の最新バージョンである 2.2.9 へ、Gradle のバージョンを 6.x 系の最新バージョンである 6.5.1 に上げて build できることを確認します。
- 今回は問題がなければライブラリはバージョンアップしません。
参照したサイト・書籍
目次
手順
2.3.x ブランチの作成
master から 2.3.x ブランチを、2.3.x から feature/135-issue ブランチを作成します。
Spring Boot を 2.2.2 → 2.2.9 にバージョンアップする
build.gradle の以下の点を変更します。
buildscript { ext { group "ksbysample" version "2.2.9-RELEASE" } repositories { mavenCentral() maven { url "https://repo.spring.io/release/" } gradlePluginPortal() } } plugins { id "java" id "eclipse" id "idea" id "org.springframework.boot" version "2.2.9.RELEASE" id "io.spring.dependency-management" version "1.0.9.RELEASE" id "groovy" id "checkstyle" id "com.github.spotbugs" version "3.0.0" id "pmd" id "net.ltgt.errorprone" version "1.1.1" id "com.gorylenko.gradle-git-properties" version "2.2.0" } ..........
- buildscript block の以下の点を変更します。
version "2.2.2-RELEASE"
→version "2.2.9-RELEASE"
- plugins block の以下の点を変更します。
id "org.springframework.boot" version "2.2.2.RELEASE"
→id "org.springframework.boot" version "2.2.9.RELEASE"
id "io.spring.dependency-management" version "1.0.8.RELEASE"
→id "io.spring.dependency-management" version "1.0.9.RELEASE"
Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新した後、clean タスク実行 → Rebuild Project 実行。。。すると以下の画像のエラーが出ました。
Web で検索しても該当しそうな記事が見つかりません。
groovy の compile 時のエラーのようなので groovy のバージョンを確認したところ、2.2.2 の時は 2.5.8、2.2.9 の時は 2.5.13(https://mvnrepository.com/artifact/org.codehaus.groovy/groovy を見ると 2.5 系の最終バージョンの模様)でした。
build.gradle で groovy のバージョンを 2.5.12 にバージョンダウンすると Rebuild Project 実行時にエラーが出なくなりました。
dependencyManagement { imports { // mavenBom は以下の URL のものを使用する // https://repo.spring.io/release/org/springframework/boot/spring-boot-starter-parent/2.2.9.RELEASE/ // bomProperty に指定可能な property は以下の URL の BOM に記述がある // https://repo.spring.io/release/org/springframework/boot/spring-boot-dependencies/2.2.9.RELEASE/spring-boot-dependencies-2.2.9.RELEASE.pom mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES) { // Spring Boot の BOM に定義されているバージョンから変更する場合には、ここに以下のように記述する // bomProperty "thymeleaf.version", "3.0.9.RELEASE" bomProperty "groovy.version", "2.5.12" } mavenBom("org.junit:junit-bom:5.5.2") } }
- dependencyManagement block に
bomProperty "groovy.version", "2.5.12"
を追加します。
build タスクを実行すると、今度は以下の画像のエラーが出ました。Caused by: org.dbunit.dataset.DataSetException Caused by: org.xml.sax.SAXNotSupportedException
で大量のエラーが発生しています。
Project Tool Window で src/test でコンテキストメニューを表示して「Run 'All Tests'」を選択してテストを実行し、もう少し詳しいエラーメッセージを見てみると、以下のエラーメッセージが出力されていました。
java.lang.RuntimeException: org.dbunit.dataset.DataSetException: not supported setting property http://xml.org/sax/properties/lexical-handler at ksbysample.common.test.extension.db.TestDataExtension.after(TestDataExtension.java:129) at ksbysample.common.test.extension.db.TestDataExtension.afterEach(TestDataExtension.java:75) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeAfterEachCallbacks$11(TestMethodTestDescriptor.java:245) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeAllAfterMethodsOrCallbacks$12(TestMethodTestDescriptor.java:256) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeAllAfterMethodsOrCallbacks$13(TestMethodTestDescriptor.java:256) at java.base/java.util.ArrayList.forEach(ArrayList.java:1541) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeAllAfterMethodsOrCallbacks(TestMethodTestDescriptor.java:255) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeAfterEachCallbacks(TestMethodTestDescriptor.java:244) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:141) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:69) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80) at java.base/java.util.ArrayList.forEach(ArrayList.java:1541) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80) at java.base/java.util.ArrayList.forEach(ArrayList.java:1541) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80) at java.base/java.util.ArrayList.forEach(ArrayList.java:1541) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32) at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51) at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:229) at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:197) at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:211) at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:191) at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128) at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69) at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33) at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230) at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58) Caused by: org.dbunit.dataset.DataSetException: not supported setting property http://xml.org/sax/properties/lexical-handler at org.dbunit.dataset.xml.XmlProducer.buildException(XmlProducer.java:182) at org.dbunit.dataset.xml.FlatXmlProducer.produce(FlatXmlProducer.java:373) at org.dbunit.dataset.CachedDataSet.<init>(CachedDataSet.java:80) at org.dbunit.dataset.xml.FlatXmlDataSet.<init>(FlatXmlDataSet.java:110) at org.dbunit.dataset.xml.FlatXmlDataSetBuilder.buildInternal(FlatXmlDataSetBuilder.java:264) at org.dbunit.dataset.xml.FlatXmlDataSetBuilder.build(FlatXmlDataSetBuilder.java:111) at ksbysample.common.test.extension.db.TestDataExtension.restoreDb(TestDataExtension.java:207) at ksbysample.common.test.extension.db.TestDataExtension.after(TestDataExtension.java:126) ... 60 more Caused by: org.xml.sax.SAXNotSupportedException: not supported setting property http://xml.org/sax/properties/lexical-handler at org.gjt.xpp.sax2.Driver.setProperty(Driver.java:204) at org.dbunit.dataset.xml.FlatDtdProducer.setLexicalHandler(FlatDtdProducer.java:132) at org.dbunit.dataset.xml.FlatXmlProducer.produce(FlatXmlProducer.java:358) ... 66 more
build.gradle で testImplementation("org.dbunit:dbunit:2.6.0")
→ testImplementation("org.dbunit:dbunit:2.7.0")
にバージョンアップしても状況は変わりません(一旦 2.6.0 に戻しました)。
Web で調べると The SAX parser pull-parser conflicts with Tomcat SAX parser という Issue が見つかりました。同じエラーメッセージが出ていますが、pull-parser-2.jar を削除すると解消したとのこと。Error Parsing /index.xhtml: not supported setting property http://xml.org/sax/properties/lexical-handler の記事も見つけました。dom4j の optional なので外しても問題ないようです。
gradlew dependencies
を実行して pull-parser-2.jar に依存しているモジュールを探すと com.github.spotbugs:spotbugs:4.0.0-beta4
がヒットしました。
.......... +--- com.github.spotbugs:spotbugs:4.0.0-beta4 | +--- org.ow2.asm:asm:7.1 | +--- org.ow2.asm:asm-analysis:7.1 | | \--- org.ow2.asm:asm-tree:7.1 | | \--- org.ow2.asm:asm:7.1 | +--- org.ow2.asm:asm-commons:7.1 | | +--- org.ow2.asm:asm:7.1 | | +--- org.ow2.asm:asm-tree:7.1 (*) | | \--- org.ow2.asm:asm-analysis:7.1 (*) | +--- org.ow2.asm:asm-tree:7.1 (*) | +--- org.ow2.asm:asm-util:7.1 | | +--- org.ow2.asm:asm:7.1 | | +--- org.ow2.asm:asm-tree:7.1 (*) | | \--- org.ow2.asm:asm-analysis:7.1 (*) | +--- org.apache.bcel:bcel:6.3.1 | +--- net.jcip:jcip-annotations:1.0 | +--- org.dom4j:dom4j:2.1.1 -> 2.1.3 | | +--- jaxen:jaxen:1.1.6 -> 1.2.0 | | +--- javax.xml.stream:stax-api:1.0-2 | | +--- net.java.dev.msv:xsdlib:2013.6.1 | | | \--- relaxngDatatype:relaxngDatatype:20020414 | | +--- javax.xml.bind:jaxb-api:2.2.12 -> 2.3.1 | | | \--- javax.activation:javax.activation-api:1.2.0 | | +--- pull-parser:pull-parser:2 | | \--- xpp3:xpp3:1.1.4c | +--- jaxen:jaxen:1.1.6 -> 1.2.0 | +--- commons-lang:commons-lang:2.6 | +--- org.slf4j:slf4j-api:1.8.0-beta4 -> 1.7.30 | +--- net.sf.saxon:Saxon-HE:9.9.1-2 | | \--- com.ibm.icu:icu4j:63.1 | \--- com.github.spotbugs:spotbugs-annotations:4.0.0-beta4 | \--- com.google.code.findbugs:jsr305:3.0.2 ..........
build.gradle で com.github.spotbugs:spotbugs:4.0.0-beta4
の依存関係から pull-parser:pull-parser:2
を除外してみます。
dependencies { .......... // for SpotBugs compileOnly("com.github.spotbugs:spotbugs:${spotbugsVersion}") { exclude group: "pull-parser", module: "pull-parser" } compileOnly("net.jcip:jcip-annotations:1.0") compileOnly("com.github.spotbugs:spotbugs-annotations:${spotbugsVersion}") testImplementation("com.google.code.findbugs:jsr305:3.0.2") spotbugsStylesheets("com.github.spotbugs:spotbugs:${spotbugsVersion}") spotbugsPlugins("com.h3xstream.findsecbugs:findsecbugs-plugin:1.10.1") }
exclude group: "pull-parser", module: "pull-parser"
を追加します。
Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新してから、再び clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると今度は無事 "BUILD SUCCESSFUL" のメッセージが出力されました。
Gradle を 6.0.1 → 6.5.1 にバージョンアップする
build.gradle の wrapper タスクの記述を以下のように変更します。
wrapper {
gradleVersion = "6.5.1"
distributionType = Wrapper.DistributionType.ALL
}
gradleVersion = "6.0.1"
→gradleVersion = "6.5.1"
に変更します。
コマンドプロンプトから gradlew wrapper --gradle-version 6.5.1
、gradlew --version
コマンドを実行します。
gradle/wrapper/gradle-wrapper.properties は以下の内容になります。
distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists
JVM を呼び出す時のメモリ割り当ての記述が元に戻るので、gradlew.bat 内の記述を set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
→ set DEFAULT_JVM_OPTS="-Xmx4096m"
に変更します(gradlew も同じような変更をします)。
Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します(少し時間がかかります)。
clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると、spotbugsMain タスクでエラーになりました。'org.gradle.process.internal.worker.SingleRequestWorkerProcessBuilder org.gradle.process.internal.worker.WorkerProcessFactory.singleRequestWorker(java.lang.Class, java.lang.Class)'
というエラーメッセージが出力されています。
SpotBugs は後でバージョンアップする予定なので今回はコメントアウトすることにします。
build.gradle で SpotBugs の記述をコメントアウトします。 ただしソース内で SpotBug s のアノテーションを記述しているところがあるので compileOnly("com.github.spotbugs:spotbugs-annotations:${spotbugsVersion}")
だけは残します。
.......... plugins { id "java" id "eclipse" id "idea" id "org.springframework.boot" version "2.2.9.RELEASE" id "io.spring.dependency-management" version "1.0.9.RELEASE" id "groovy" id "checkstyle" // id "com.github.spotbugs" version "3.0.0" id "pmd" id "net.ltgt.errorprone" version "1.1.1" id "com.gorylenko.gradle-git-properties" version "2.2.0" } .......... //spotbugs { // toolVersion = "4.0.0-beta4" // ignoreFailures = true // effort = "max" // spotbugsTest.enabled = false //} //tasks.withType(com.github.spotbugs.SpotBugsTask) { // reports { // xml.enabled = false // html.enabled = true // html.stylesheet = resources.text.fromArchiveEntry(configurations.spotbugsStylesheets, "color.xsl") // } //} .......... dependencies { .......... // for SpotBugs // compileOnly("com.github.spotbugs:spotbugs:${spotbugsVersion}") { // exclude group: "pull-parser", module: "pull-parser" // } // compileOnly("net.jcip:jcip-annotations:1.0") compileOnly("com.github.spotbugs:spotbugs-annotations:${spotbugsVersion}") // testImplementation("com.google.code.findbugs:jsr305:3.0.2") // spotbugsStylesheets("com.github.spotbugs:spotbugs:${spotbugsVersion}") // spotbugsPlugins("com.h3xstream.findsecbugs:findsecbugs-plugin:1.10.1") }
再び clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると今度は無事 "BUILD SUCCESSFUL" のメッセージが出力されました。
履歴
2020/08/09
初版発行。
Spring Boot 2.2.x の Web アプリを 2.3.x へバージョンアップする ( その1 )( 概要 )
概要
記事一覧はこちらです。
- 「Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る」で作成した Web アプリケーション ( ksbysample-webapp-lending ) の Spring Boot のバージョンを 2.2.2 → 2.3.x へバージョンアップします。
- 進め方は以下の方針とします。
- Git のブランチは 2.3.x を作成して、そちらで作業します。Spring Boot のバージョンと合わせます。
- Spring Boot のバージョンを 2.2 系の最新バージョンである 2.2.9 へ、Gradle のバージョンを 6.x 系の最新バージョンである 6.5.1 に上げて build できることを確認します。この時点ではライブラリはバージョンアップしません。
- Spring Boot のバージョン番号を 2.3.x にします。
- Spring Initializr で 2.3.x のプロジェクトを作成して、修正した方がよさそうな点があれば反映します。
- ライブラリは最新バージョンにアップデートします。ただし、この時点では checkstyle, spotbugs, pmd, Error Prone のバージョンは上げません。
- プロジェクトを build し直してエラーが出る点があれば修正し、まずはここまでで動くようにします。
- その後で 2.3 系ではこう書くべきという点があるか確認し、変更した方がよいところを変更します。
- checkstyle, spotbugs, pmd, Error Prone を1つずつ最新バージョンに上げます。変更した方がよいところがあれば変更します。
- docker-compose で使用している image を最新バージョンに上げます。
2.3 の Release Notes はこちらです。
Spring Boot 2.3 Release Notes
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.3-Release-Notes
履歴
2020/08/08
初版発行。
Spring Boot 2.2.x の Web アプリを 2.3.x へバージョンアップする ( 大目次 )
- その1 ( 概要 )
- その2 ( Spring Boot を 2.2.2 → 2.2.9 へ、Gradle を 6.0.1 → 6.5.1 へバージョンアップする )
- その3 ( Spring Boot を 2.2.9 → 2.3.2 へバージョンアップする )
- その4 ( Release Notes を見て必要な箇所を変更する )
- その5 ( Checkstyle を 8.28 → 8.35 へバージョンアップする )
- その6 ( PMD を 6.20.0 → 6.26.0 へバージョンアップする )
- その7 ( Error Prone を 2.3.4 → 2.4.0 へバージョンアップする )
- その8 ( SpotBugs を 4.0.0-beta4 → 4.1.1 へバージョンアップする )
- その9 ( Docker コンテナの image をバージョンアップする、postgres・pgadmin4・flyway・docker-mailserver 編 )
- その10 ( Spring Boot を 2.3.2 → 2.3.7 へバージョンアップする+Docker コンテナの image をバージョンアップする、redis・redis_exporter 編 )
- その11 ( Docker コンテナの image をバージョンアップする、rabbitmq・haproxy・rabbitmq_exporter 編 )
- その12 ( Docker コンテナの image をバージョンアップする、prometheus・grafana 編 )
- その13 ( Docker コンテナの image をバージョンアップする、Grafana の RabbitMQ 用の Dashboard を RabbitMQ Monitoring → RabbitMQ-Overview に切り替える )
- その14 ( Docker コンテナの image をバージョンアップする、Spring Boot Statistics のデータが表示されていない Panel を修正する+Dashboard に JVM (Micrometer) を追加する )
- その15 ( Doma 2 を 2.26.0 → 2.44.3 へバージョンアップする+domaGen タスクを doma-codegen-plugin を利用したものに作り直す )
- その16 ( ValuesHelper クラスで guava の ClassPath を使用しないよう変更する )
- 番外編 ( IntelliJ IDEA の docToolchain/diagrams.net-intellij-plugin を追加する )
- その17 ( app コンテナを実行する Alpine Linux で ja_JP.utf8 のロケールを使えるようにする )
- その18 ( Build OCI images with Cloud Native Buildpacks を試してみる )
- その19 ( Docker で複数の Tomcat を起動して動作確認する )
- 感想
AdoptOpenJDK を 11.0.7+10.2 → 11.0.8+10 へ、IntelliJ IDEA を 2020.1.3 → 2020.1.4 へ、Git for Windows を 2.27.0 → 2.28.0 へバージョンアップ
AdoptOpenJDK を 11.0.7+10.2 → 11.0.8+10 へバージョンアップする
※ksbysample-webapp-lending プロジェクトを開いた状態でバージョンアップしています。
https://adoptopenjdk.net/?variant=openjdk11&jvmVariant=hotspot を見ると 11.0.8+10 がダウンロードできるようになっていましたので、11.0.8+10 へバージョンアップします。
インストール時に削除されるかもしれないので D:\Java\jdk-11.0.7.10-hotspot → D:\Java\jdk-11.0.7.10-hotspotx にリネームします。
OpenJDK11U-jdk_x64_windows_hotspot_11.0.8_10.msi をダウンロードして D:\Java\jdk-11.0.8.10-hotspot へインストールした後、環境変数 JAVA_HOME のパスを D:\Java\jdk-11.0.8.10-hotspot へ変更します。
コマンドプロンプトから
java -version
を実行し、11.0.8
に変更されていることを確認します。D:\Java\jdk-11.0.7.10-hotspotx → D:\Java\jdk-11.0.7.10-hotspot に戻します。
ダイアログ下部の「Configure」-「Structure for New Projects」を選択します。
「Project Structure for New Projects」ダイアログが表示されます。画面左側で「Project Settings」-「Project」を選択後、画面右側の「Project SDK」の「New...」ボタン。。。がいつの間にか消えていますね。ドロップダウンリストを表示すると D:\Java\jdk-11.0.8.10-hotspot が検知されて表示されていたので選択します。
「Project SDK」の「Edit」ボタンをクリックします。
画面左側で「Platform Settings」-「SDKs」が選択された状態になるので、画面右上の入力フィールドで "11 (2)" → "11.0.8.10" へ変更します。
次に中央のリストから「11.0.7.10」を選択した後、リストの上の「-」ボタンをクリックして削除します。「11」も不要なので削除します。
「OK」ボタンをクリックして「Project Structure for New Projects」ダイアログを閉じます。
「Welcome to IntelliJ IDEA」ダイアログに戻ったら、ksbysample-webapp-lending プロジェクトを開きます。
IntelliJ IDEA のメイン画面が開いたら、メニューから「File」-「Project Structure...」を選択します。
「Project Structure」ダイアログが表示されます。以下の画像の状態になっているので、
「Project SDK」を選択し直します。「Project SDK」を「11.0.8.10」に変更すると「Project language level」も自動で「SDK default (11 - Local variable syntax for lambda param」が選択されました。
「OK」ボタンをクリックして「Project Structure」ダイアログを閉じます。
メイン画面に戻ると画面右下に「Indexing...」の表示が出るので、終了するまで待ちます。
Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。
clean タスク実行 → Rebuild Project 実行 → build タスクを実行して、"BUILD SUCCESSFUL" のメッセージが出力されることを確認します。
Project Tool Window で src/test でコンテキストメニューを表示して「Run 'All Tests' with Coverage」を選択し、テストが全て成功することを確認します。
特に問題は発生しませんでした。11.0.8+10 で開発を進めます。
IntelliJ IDEA を 2020.1.3 → 2020.1.4 へバージョンアップする
IntelliJ IDEA の 2020.1.4 がリリースされているのでバージョンアップします。2020.2 もリリースされていますが、今回はバージョンアップしません。
- IntelliJ IDEA 2020.1.4 is Available
https://blog.jetbrains.com/idea/2020/07/intellij-idea-2020-1-4-is-available/
※ksbysample-webapp-lending プロジェクトを開いた状態でバージョンアップしています。
IntelliJ IDEA のメインメニューから「Help」-「Check for Updates...」を選択します。
「IDE and Plugin Updates」ダイアログが表示されます。右下に「Update and Restart」ボタンが表示されていますので、「Update and Restart」ボタンをクリックします。
Plugin の update も表示されました。このまま「Update and Restart」ボタンをクリックします。
Patch がダウンロードされて IntelliJ IDEA が再起動します。
IntelliJ IDEA が起動すると画面下部に「Indexing…」のメッセージが表示されますので、終了するまで待機します。
IntelliJ IDEA のメインメニューから「Help」-「About」を選択し、2020.1.4 へバージョンアップされていることを確認します。
Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。
clean タスク実行 → Rebuild Project 実行 → build タスクを実行して、"BUILD SUCCESSFUL" のメッセージが出力されることを確認します。
Project Tool Window で src/test でコンテキストメニューを表示して「Run 'All Tests' with Coverage」を選択し、テストが全て成功することを確認します。
Git for Windows を 2.27.0 → 2.28.0 へバージョンアップする
Git for Windows の 2.28.0 がリリースされていたのでバージョンアップします。
https://gitforwindows.org/ の「Download」ボタンをクリックして Git-2.28.0-64-bit.exe をダウンロードします。
Git-2.28.0-64-bit.exe を実行します。
「Git 2.28.0 Setup」ダイアログが表示されます。インストーラーの画面を一通り見たいので「Only show new options」のチェックを外してから [Next >] ボタンをクリックします。
「Select Components」画面が表示されます。「Git LFS(Large File Support)」だけチェックした状態で [Next >]ボタンをクリックします。
「Choosing the default editor used by Git」画面が表示されます。「Use Vim (the ubiquitous text editor) as Git's default editor」が選択された状態で [Next >]ボタンをクリックします。
「Adjusting your PATH environment」画面が表示されます。中央の「Git from the command line and also from 3rd-party software」が選択されていることを確認後、[Next >]ボタンをクリックします。
「Choosing HTTPS transport backend」画面が表示されます。「Use the OpenSSL library」が選択されていることを確認後、[Next >]ボタンをクリックします。
「Configuring the line ending conversions」画面が表示されます。一番上の「Checkout Windows-style, commit Unix-style line endings」が選択されていることを確認した後、[Next >]ボタンをクリックします。
「Configuring the terminal emulator to use with Git Bash」画面が表示されます。「Use Windows'default console window」が選択されていることを確認した後、[Next >]ボタンをクリックします。
「Choose the default behavior of
git pull
」画面が表示されます(新画面)。「Default (fast-forward or merge)」が選択されていることを確認した後、[Next >]ボタンをクリックします。「Choose a credential helper」画面が表示されます。前回のバージョンアップではなかった画面です。「None」が選択されていることを確認した後、[Next >]ボタンをクリックします。
「Configuring extra options」画面が表示されます。「Enable file system caching」だけがチェックされていることを確認した後、[Next >]ボタンをクリックします。
「Configuring experimental options」画面が表示されます。何もチェックせずに [Install]ボタンをクリックします。
インストールが完了すると「Completing the Git Setup Wizard」のメッセージが表示された画面が表示されます。中央の「View Release Notes」のチェックを外した後、[Next >]ボタンをクリックしてインストーラーを終了します。
コマンドプロンプトを起動して
git --version
を実行し、git のバージョンがgit version 2.28.0.windows.1
になっていることを確認します。特に問題はないようですので、2.28.0 で作業を進めたいと思います。
Serverless Framework で deploy 用ディレクトリへ移動→環境変数を設定する方法で deploy する環境を切り替える(その3、CircleCI に deploy する)
概要
記事一覧はこちらです。
Serverless Framework で deploy 用ディレクトリへ移動→環境変数を設定する方法で deploy する環境を切り替える(その2) の続きです。
参照したサイト・書籍
Making Serverless CI/CD Easier with CircleCI and Serverless Framework
https://aws.amazon.com/jp/blogs/apn/making-serverless-ci-cd-easier-with-circleci-and-serverless-framework/Welcome to CircleCI Documentation
https://circleci.com/docs/ja/2.0/- CircleCI のドキュメントの日本語版。
Configuring CircleCI
https://circleci.com/docs/2.0/configuration-reference/あなたがnpm installをしてはいけない時
https://blog.minimalcorp.com/users/jigen/posts/6f325dc9b8a00370b6aedf47a34cb3cenpm ciを使おう あるいはより速く
https://qiita.com/mstssk/items/8759c71f328cab802670まさかPushデバッグしてないよね? よく使うCircleCIのデバッグ方法
https://blog.vtryo.me/entry/circleci-debug-methodいまさらだけどCircleCIに入門したので分かりやすくまとめてみた
https://qiita.com/gold-kou/items/4c7e62434af455e977c2
目次
- .circleci/config.yml を作成する
- CircleCI で Project の設定をする
- AWS マネジメントコンソールで circle-ci ユーザーを作成し、アクセスキーを作成する
- CircleCI の管理画面から AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY を設定する
- .circleci/config.yml で
NODE_ENV=ci npm run deploy:all
を実行するよう変更する - commit&push して CircleCI から deploy する
- AWS に作成したリソースを確認する
- 動作確認する
- 何も変更せずに deploy し直すと cache の効果でどのくらい時間が短縮されるのか?
- stages/prod/custom-shared-package-layer.yml の説明
- CircleCI で deploy したリソース一式を削除する
手順
.circleci/config.yml を作成する
.circleci/config.yml を新規作成し、まずは checkout だけ記述します。
version: 2.1 jobs: deploy: docker: - image: lambci/lambda:build-python3.8 steps: - checkout workflows: version: 2 deploy: jobs: - deploy: filters: branches: only: master
Docker Image は serverless-python-requirements プラグインが使用している lambci/lambda:build-python3.8 を使用します。以下の理由で使いやすかったからです。
- Making Serverless CI/CD Easier with CircleCI and Serverless Framework の記事で https://circleci.com/orbs/registry/orb/circleci/serverless-framework の存在を知ったが、npm のインストールと serverless-python-requirements プラグインを利用した deploy 方法がよく分からなかった。
- lambci/lambda は serverless-python-requirements プラグインを利用した deploy 時に使用されているので、この点については調査不要。
- npm のインストールについては、lambci/lambda が AWS Labmda の実行環境を作成するためのもので Node.js もサポートしており、docker-lambda/nodejs12.x/build/Dockerfile を見れば Node.js のインストール方法が分かった。
CircleCI で Project の設定をする
CircleCI の管理画面から「Proejcts」の画面を表示し、ksbysample-serverless-deploy の「Set Up Project」ボタンをクリックします。
.circleci/config.yml は追加してあるので「Add Manually」ボタンをクリックします。
下の画像のダイアログが表示されるので「Start Building」ボタンをクリックします。
workflow が実行されて SUCCESS になります。
AWS マネジメントコンソールで circle-ci ユーザーを作成し、アクセスキーを作成する
本番環境(prod)を deploy するアカウントで circle-ci ユーザーを作成し、アクセスキーも作成します。アクセス権限は AdministratorAccess ポリシーをアタッチします。
CircleCI の管理画面から AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY を設定する
CircleCI の ksbysample-serverless-deploy の画面右上の「Project Settings」ボタンをクリックします。
Project Settings 画面が表示されたら左側の「Environment Variables」を選択した後、右側の「Add Environment Variable」ボタンをクリックして AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY を追加します。
.circleci/config.yml で NODE_ENV=ci npm run deploy:all
を実行するよう変更する
.circleci/config.yml を以下のように変更します。
version: 2.1 jobs: deploy: docker: - image: lambci/lambda:build-python3.8 environment: AWS_DEFAULT_REGION: ap-northeast-1 STAGE: prod LOG_LEVEL: ERROR POWERTOOLS_TRACE_DISABLED: true NODE_PATH: /opt/nodejs/node12/node_modules:/opt/nodejs/node_modules:/var/runtime/node_modules steps: - checkout - run: name: install nodejs12.x command: curl https://lambci.s3.amazonaws.com/fs/nodejs12.x.tgz | tar -zx -C / - run: node -v - run: npm -v # Caching Dependencies # https://circleci.com/docs/2.0/caching/ - restore_cache: keys: - asset-cache-v1-{{ arch }}-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }} - asset-cache-v1-{{ arch }}-{{ .Branch }} - asset-cache-v1 - run: npm ci - run: pip install -r requirements.txt - run: python -m unittest -v - run: NODE_ENV=ci npm run deploy:all - save_cache: key: asset-cache-v1-{{ arch }}-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }} paths: # https://docs.npmjs.com/cli/ci#example - $HOME/.npm - /var/lang/lib/python3.8 - /root/project/.cache workflows: version: 2 deploy: jobs: - deploy: filters: branches: only: master
- 環境変数 AWS_DEFAULT_REGION を CircleCI の管理画面で設定すると API Gateway の endpoint の URL の一部が
*
で表示されるのですが、手間なので config.yml に定義して*
表示されないようにします。 - direnv はないので環境変数 STAGE、LOG_LEVEL、POWERTOOLS_TRACE_DISABLED を config.yml に設定します。
- NODE_PATH は docker-lambda/nodejs12.x/build/Dockerfile で設定されていたのでコピーしてきました。
- restore_cache、save_cache の keys は Full Example of Saving and Restoring Cache のをそのままコピーしています。
- save_cache の paths は、
- npm ciを使おう あるいはより速く の記事を見て npm がキャッシュしている
$HOME/.npm
を追加。 pip install -r requirements.txt
のインストール先である/var/lang/lib/python3.8
を追加。- serverless-python-requirements プラグインの cache ディレクトリである
/root/project/.cache
を追加。
- npm ciを使おう あるいはより速く の記事を見て npm がキャッシュしている
commit&push して CircleCI から deploy する
master ブランチに commit&push すると worflow が動作して無事 SUCCESS になりました。
各 step にかかった時間は以下の通りです。
NODE_ENV=ci npm run deploy:all
の状況を見ると per-env の後に cross-env-shell "cd $SERVICE_DIR && npx sls deploy -v"
が実行されており、npm-scripts の deploy-service:ci
が実行されていることが分かります。
AWS に作成したリソースを確認する
作成されたリソースを確認すると全てに STAGE 名である prod
が入っています。
■Lambda 関数
■Lambda レイヤー
■IAM ロール
■CloudWatch ロググループ
■S3 バケット
■CloudFormation スタック
■SQS キュー
■DynamoDB テーブル
動作確認する
Postman から API Gateway の endpoint にアクセスすると 200 OK が返ってきて、
DynamoDB の sample-table-prod テーブルにデータが保存されました。
ksbysample-upload-bucket-prod バケットに sample.jpg をアップロードすると、
ksbysample-resize-bucket-prod バケットに sample_thumb.jpg が生成されました。
Pillow を使用したリサイズ処理も問題なく動作しています。
何も変更せずに deploy し直すと cache の効果でどのくらい時間が短縮されるのか?
.circleci/config.yml 内で1行改行してから revert して master ブランチに commit&push してみると、前回と比較して deploy 時間が約半分になりました。
ただし、この時気づきましたが Lambda Layer の shared_package_layer のバージョンが1つ上がっていました。少し試してみたのですが deploy すると変更点がなくてもバージョンが1つ上がってしまうようです。変更がなければバージョンが上がらないようにしたいのですが、CI/CD 前提ならば Lambda Layer は別レポジトリに分けた方が良いのかもしれません。。。
stages/prod/custom-shared-package-layer.yml の説明
stages/prod/custom-shared-package-layer.yml に以下のように記述しましたが、
pythonRequirements: # dockerizePip: true fileName: ../../requirements.txt noDeploy: - aws-lambda-context - boto3 - moto useStaticCache: true useDownloadCache: true cacheLocation: /root/project/.cache staticCacheMaxVersions: 3 layer: name: "shared-package-layer-${env:STAGE}" description: 共通パッケージ用 Lambda Layer
- デフォルトの cacheLocation ではエラーになるため、CircleCI の working_directory の下のディレクトリを指定します。
- useStaticCache、useDownloadCache を true にして cache を使うよう設定します。
- serverless-python-requirements プラグインの cache を CircleCI の cache 機能で cache するので、staticCacheMaxVersions で履歴数を少なめに指定してサイズが大きくならないようにします。
CircleCI で deploy したリソース一式を削除する
stages/prod/.envrc で AWS_PROFILE に削除可能な profile を設定しておいてから、以下のコマンドを実行すれば削除されます。
$ cd stages/prod $ npm run remove:all
履歴
2020/07/19
初版発行。
Serverless Framework で deploy 用ディレクトリへ移動→環境変数を設定する方法で deploy する環境を切り替える(その2)
概要
記事一覧はこちらです。
Serverless Framework で deploy 用ディレクトリへ移動→環境変数を設定する方法で deploy する環境を切り替える(その1) の続きです。
参照したサイト・書籍
目次
- deploy/remove 用の npm-scripts を記述する
- stages/dev に移動して開発環境(dev)に deploy する
- 動作確認する(dev)
- stages/stg に移動してステージング環境(stg)に deploy する
- 動作確認する(stg)
手順
deploy/remove 用の npm-scripts を記述する
package.json に deploy/remove 用の npm-scripts を記述します。
{ "name": "ksbysample-serverless-deploy", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "deploy-service": "per-env", "deploy-service:development": "cross-env-shell \"cd $SERVICE_DIR && aws-vault exec $AWS_PROFILE -- bash -c \\\"npx sls deploy -v\\\"\"", "deploy-service:ci": "cross-env-shell \"cd $SERVICE_DIR && npx sls deploy -v\"", "remove-service": "per-env", "remove-service:development": "cross-env-shell \"cd $SERVICE_DIR && aws-vault exec $AWS_PROFILE -- bash -c \\\"npx sls remove -v\\\"\"", "remove-service:ci": "cross-env-shell \"cd $SERVICE_DIR && npx sls remove -v\"", "deploy:shared-package-layer": "cross-env SERVICE_DIR=layers/shared_package_layer run-s deploy-service", "remove:shared-package-layer": "cross-env SERVICE_DIR=layers/shared_package_layer run-s remove-service", "deploy:image-service": "cross-env SERVICE_DIR=services/image_service run-s deploy-service", "remove:image-service": "cross-env SERVICE_DIR=services/image_service run-s remove-service", "deploy:sample-service": "cross-env SERVICE_DIR=services/sample_service run-s deploy-service", "remove:sample-service": "cross-env SERVICE_DIR=services/sample_service run-s remove-service", "deploy:layers": "run-p deploy:shared-package-layer", "remove:layers": "run-p remove:shared-package-layer", "deploy:services": "run-s deploy:image-service deploy:sample-service", "remove:services": "run-s remove:image-service remove:sample-service", "deploy:all": "run-s deploy:layers deploy:services", "remove:all": "run-s remove:services remove:layers" }, "repository": { "type": "git", "url": "git+https://github.com/ksby/ksbysample-serverless-deploy.git" }, "keywords": [], "author": "", "license": "ISC", "bugs": { "url": "https://github.com/ksby/ksbysample-serverless-deploy/issues" }, "homepage": "https://github.com/ksby/ksbysample-serverless-deploy#readme", "devDependencies": { "cross-env": "^7.0.2", "npm-run-all": "^4.1.5", "per-env": "^1.0.2", "serverless": "^1.74.1", "serverless-python-requirements": "^5.1.0" } }
deploy:all
タスクのフローは以下のようになります。
eploy:shared-package-layer
やdeploy:image-service
、deploy:sample-service
では cross-env で環境変数 SERVICE_DIR に移動先のディレクトリのパスをセットしてdeploy-service
タスクを呼び出します。ポイントとしては npm-scripts は stages/dev の下に cd してから実行しても実行時のディレクトリはプロジェクトのルートディレクトリ(node_modules のあるディレクトリ)になるので、環境変数 SERVICE_DIR に設定するパスはそこからの相対パスにする必要があるという点です。
試しに
deploy-service:development
タスクを"deploy-service:development": "cross-env-shell \"ls -l\""
に変更してからnpm run deploy:all
を実行してみると、deploy-service:development
タスクが呼び出される度にプロジェクトのルートディレクトリにある node_modules ディレクトリが表示されています。deploy-service
タスクが呼び出されると per-env により NODE_ENV の値が未セットの場合(deployment になる)にはdeploy-service:development
タスクが、NODE_ENV=ci がセットされている場合にはdeploy-service:ci
タスクが呼び出されます。deploy-service:development
及びdeploy-service:ci
タスクでは cross-env-shell(cross-env に含まれている)でcd
及びsls deploy
を実行します。cross-env-shell で実行することで git-bash や cross-env で設定された環境変数をcd
及びsls deploy
側で利用できるようになります。
stages/dev に移動して開発環境(dev)に deploy する
stages/dev に移動してから npm run deploy:all
を実行します。
作成されたリソースを確認すると全てに STAGE 名である dev
が入っています。
■Lambda 関数
■Lambda レイヤー
■IAM ロール
■CloudWatch ロググループ
■S3 バケット
■CloudFormation スタック
■SQS キュー
■DynamoDB テーブル
動作確認する(dev)
Postman から API Gateway の endpoint にアクセスすると 200 OK が返ってきて、
DynamoDB の sample-table-dev テーブルにデータが保存されました。
ksbysample-upload-bucket-dev バケットに sample.jpg をアップロードすると、
ksbysample-resize-bucket-dev バケットに sample_thumb.jpg が生成されました。
S3 --> Lambda --> S3 の処理をする時の X-Ray を見てみると以下のように表示されていました。
問題なく動作しています。
stages/stg に移動してステージング環境(stg)に deploy する
stages/dev で deploy したリソースは残したままの状態で stages/stg に移動して deploy します。
(..........途中省略..........)
作成されたリソースを確認すると全てに STAGE 名である stg
が入っていました(キャプチャは省略)。
動作確認する(stg)
Postman から API Gateway の endpoint にアクセスすると 200 OK が返ってきて、
DynamoDB の sample-table-stg テーブルにデータが保存されました。
ksbysample-upload-bucket-stg バケットに sample2.jpg をアップロードすると、
ksbysample-resize-bucket-stg バケットに sample2_thumb.jpg が生成されました。
dev のリソースがある状態でも stg も問題なく動作しました。
履歴
2020/07/19
初版発行。
Serverless Framework で deploy 用ディレクトリへ移動→環境変数を設定する方法で deploy する環境を切り替える(その1)
概要
記事一覧はこちらです。
開発環境(dev)、ステージング環境(stg)、本番環境(prod)を異なるアカウント、あるいは同一アカウント内でリソース名を切り替えて deploy する方法を記述します。
Terraform の場合、適用先の環境用ディレクトリに移動してから terraform apply
コマンドを実行するようにしていて(direnv を利用して .envrc に記述した AWS_PROFILE 等の環境変数を設定します)、
root_directory └ stages ├ dev ← ここに移動してから terraform apply すれば開発環境に適用される │ └ .envrc ├ stg ← ここに移動してから terraform apply すればステージング環境に適用される │ └ .envrc └ prod ← ここに移動してから terraform apply すれば本番環境に適用される └ .envrc
個人的にこの方法がやりやすいので、Serverless Framework でも同じ方法で deploy できないかと思い考えてみました。
今回は CircleCI から deploy する方法も記述するので、新規の Repository を作成します。 https://github.com/ksby/ksbysample-serverless-deploy
ローカルPC から開発環境(dev)、ステージング環境(stg)を deploy する方法を2回に分けて記述し、CircleCI から本番環境(prod)に deploy する方法をその後に1回で記述します。
参照したサイト・書籍
AWS - Credentials
https://www.serverless.com/framework/docs/providers/aws/guide/credentials/cross-env
https://www.npmjs.com/package/cross-envnpm-run-all
https://www.npmjs.com/package/npm-run-allaws-lambda-context
https://pypi.org/project/aws-lambda-context/
目次
- 概要
- clone してプロジェクトを設定する
- npm から cross-env、npm-run-all、per-env をインストールする
- Python で使用するモジュールを pip でインストールし、requirements.txt を作成する
- リリース用フォルダ(stages/dev、stages/stg、stages/prod)を作成し .envrc と serverless.yml の custom セクション用 yaml ファイルを作成する
- Lambda Layer 用の layers/shared_package_layer を作成し serverless.yml を変更する
- services/image_service を作成し serverless.yml を変更する
- services/sample_service を作成し serverless.yml を変更する
- ユニットテストを作成する
- 続く。。。
手順
概要
- deploy 先の環境用ディレクトリへ移動してから npm-scripts(内部で
sls deploy
を呼び出す)を実行することで deploy する。例えば開発環境(dev)へ deploy する場合には以下のコマンドを実行する。
> cd stages/dev ※direnv により環境変数 AWS_PROFILE、AWS_DEFAULT_REGION、STAGE が設定される。 > npm run deploy:all
- CircleCI から deploy する場合には管理画面か .circleci/config.yml で環境変数 AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY、AWS_DEFAULT_REGION、STAGE を設定しておいて、以下のコマンドを実行する。
> NODE_ENV=ci npm run deploy:all
- STAGE は開発環境(dev)、ステージング環境(stg)、本番環境(prod)の3つとする。
- 開発環境(dev)、ステージング環境(stg)はローカルPC から、本番環境(prod)は CircleCI から deploy する。
- ローカルPC から deploy する時には
aws-vault exec $AWS_PROFILE -- bash -c "npx sls deploy -v"
コマンドを、CircleCI から deploy する時にはnpx sls deploy -v
コマンドを実行したいので、NODE_ENV の値を見て npm-scripts を切り替えられる per-env を使用する。 - serverless.yml の provider.profile は記述しない。
sls deploy
の--aws-profile
オプションも使用しない。 - Lambda Layer x 1、Service x 2(sample-service、image-service)を作成する。以下の目的である。
- Lambda Layer を利用する複数の Service を deploy するサンプルを作成する。
- OS 依存のバイナリがある Pillow を含む deploy をしても正常に動作するサンプルを作成する。
- Python で使用するモジュール(aws-lambda-powertools、Pillow)は全て Lambda Layer にインストールする。全ての Lambda 関数はこの Lambda Layer を使用する。
- deploy は直接
sls deploy
コマンドを実行するのではなく npm-scripts 経由で実行する。 - git-bash で設定された環境変数を npm-scripts から呼び出された
sls deploy
で利用できるようにするために cross-env を使用する。 - Lambda 関数の実装で aws-lambda-powertools を使用する。
- ユニットテストも作成する。
- aws-lambda-powertools を利用した実装でユニットテストを作成すると引数 context に必要な値がセットされたオブジェクトを渡す必要があるので、aws-lambda-context を使用する。
- requirements.txt は Lambda Layer や Service 用のディレクトリの下ではなくプロジェクトのルートディレクトリ直下に作成する。
clone してプロジェクトを設定する
https://github.com/ksby/ksbysample-serverless-deploy の repository を clone して必要な設定を行います。
具体的な手順は IntelliJ IDEA+Node.js+npm+serverless framework+Python の組み合わせで開発環境を構築して AWS Lambda を作成してみる 参照。
- .gitignore を https://github.com/ksby/ksbysample-serverless からコピーする。
- Python の仮想環境を作成する。
- Serverless Framework をインストールする。
npm init -y
npm install --save-dev serverless
- serverless-python-requirements をインストールする。
npm install --save-dev serverless-python-requirements
npm から cross-env、npm-run-all、per-env をインストールする
npm で今回使用する cross-env、npm-run-all、per-env のパッケージをインストールします。
cross-env は npm-scripts 内で環境変数を設定したり、git-bash に設定されている環境変数を npm-scripts に渡すために使用します。
npm-run-all は npm-scripts を sequential あるいは parallel に実行するために使用します。ただし run-p を使って service の deploy を parallel に実行しようと思ったのですがエラーが出てダメでした。。。
per-env は npm-scripts を実行する時に設定されている NODE_ENV(何も設定していなければ development になる)の値により実際に実行される npm-scripts を切り替えるために使用します。
npm install --save-dev cross-env
npm install --save-dev npm-run-all
npm install --save-dev per-env
Python で使用するモジュールを pip でインストールし、requirements.txt を作成する
実装に必要なモジュールとして aws-lambda-powertools、boto3、Pillow をインストールします。
pip install aws-lambda-powertools
pip install boto3
pip install Pillow
ユニットテストで使用するモジュールとして aws-lambda-context、moto をインストールします。
pip install aws-lambda-context
pip install moto
最後にプロジェクトのルートディレクトリ直下に requirements.txt を作成し、インストールしたモジュールを全て記述します。
aws-lambda-context==1.1.0 aws-lambda-powertools==1.0.1 boto3==1.14.20 moto==1.3.14 Pillow==7.2.0
リリース用フォルダ(stages/dev、stages/stg、stages/prod)を作成し .envrc と serverless.yml の custom セクション用 yaml ファイルを作成する
プロジェクトのルートディレクトリ直下に stages ディレクトリを作成し、その下に dev、stg、prod ディレクトリを作成します。
各ディレクトリの下に .envrc、custom-services.yml、custom-shared-package-layer.yml を作成します。
- .envrc
- direnv が参照するファイル。設定する環境変数を記述する。
- custom-services.yml
- services ディレクトリの下に作成する各サービスの serverless.yml の custom セクションに読み込ませるファイル。
- 今回は内容は全て同じ。環境毎に custom セクションに読み込ませる値を変更できることを示すためにわざと分けて書いている。
- custom-shared-package-layer.yml
- layers/shared_package_layer の下の serverless.yml の custom セクションに読み込ませるファイル。
- ローカルPC から deploy する開発環境(dev)、ステージング環境(stg)と、CircleCI から deploy する本番環境(prod)で設定が異なる。
dev ディレクトリの下に .envrc、custom-services.yml、custom-shared-package-layer.yml を作成し、以下の内容を記述します。
■.envrc
export AWS_PROFILE=<deployで使用するprofile名> export AWS_DEFAULT_REGION=ap-northeast-1 export STAGE=dev
■custom-services.yml
queueName: "sample-queue-${env:STAGE}" tableName: "sample-table-${env:STAGE}" uploadBucketName: "ksbysample-upload-bucket-${env:STAGE}" resizeBucketName: "ksbysample-resize-bucket-${env:STAGE}"
■custom-shared-package-layer.yml
pythonRequirements: dockerizePip: true fileName: ../../requirements.txt noDeploy: - aws-lambda-context - boto3 - moto layer: name: "shared-package-layer-${env:STAGE}" description: 共通パッケージ用 Lambda Layer
stg ディレクトリの下に .envrc、custom-services.yml、custom-shared-package-layer.yml を作成し、以下の内容を記述します。
■.envrc
export AWS_PROFILE=<deployで使用するprofile名> export AWS_DEFAULT_REGION=ap-northeast-1 export STAGE=stg
■custom-services.yml
※dev と同じ。
■custom-shared-package-layer.yml
※dev と同じ。
prod ディレクトリの下に .envrc、custom-services.yml、custom-shared-package-layer.yml を作成し、以下の内容を記述します。custom-shared-package-layer.yml の dev、stg ディレクトリ版との違いは CircleCI の deploy を記述する回で説明します。
■.envrc
export AWS_PROFILE=<deployで使用するprofile名> export AWS_DEFAULT_REGION=ap-northeast-1 export STAGE=prod
■custom-services.yml
※dev と同じ。
■custom-shared-package-layer.yml
pythonRequirements: # dockerizePip: true fileName: ../../requirements.txt noDeploy: - aws-lambda-context - boto3 - moto useStaticCache: true useDownloadCache: true cacheLocation: /root/project/.cache staticCacheMaxVersions: 3 layer: name: "shared-package-layer-${env:STAGE}" description: 共通パッケージ用 Lambda Layer
Lambda Layer 用の layers/shared_package_layer を作成し serverless.yml を変更する
プロジェクトのルートディレクトリ直下に layers ディレクトリを作成し、git-bash から layers ディレクトリの下へ移動した後 npx sls create --template aws-python3 --path shared_package_layer
を実行します。
handler.py は不要なので削除します。
serverless.yml には以下の内容を記述します。
service: shared-package-layer plugins: - serverless-python-requirements custom: ${file(../../stages/${env:STAGE}/custom-shared-package-layer.yml)} provider: name: aws runtime: python3.8 stage: ${env:STAGE} region: ${env:AWS_DEFAULT_REGION} resources: Outputs: # 他の Stack から Lambda Layer を参照できるようにする # Value に記載している "PythonRequirementsLambdaLayer" はこの文字列固定である SharedPackageLayer: Value: Ref: PythonRequirementsLambdaLayer
services/image_service を作成し serverless.yml を変更する
プロジェクトのルートディレクトリ直下に services ディレクトリを作成します。
git-bash から services ディレクトリの下へ移動した後 npx sls create --template aws-python3 --path image_service
を実行します。
handler.py → s3_handler.py にリネームした後、https://github.com/ksby/ksbysample-serverless-deploy/blob/master/services/image_service/s3_handler.py の内容を記述します(今回の本題ではないのでコードは載せません)。
serverless.yml には以下の内容を記述します。
service: image-service custom: ${file(../../stages/${env:STAGE}/custom-services.yml)} provider: name: aws runtime: python3.8 stage: ${env:STAGE} region: ${env:AWS_DEFAULT_REGION} environment: # aws-lambda-powertools 用環境変数 LOG_LEVEL: DEBUG POWERTOOLS_LOGGER_LOG_EVENT: false POWERTOOLS_METRICS_NAMESPACE: serverless-deploytest-project POWERTOOLS_SERVICE_NAME: image-service tracing: lambda: true iamRoleStatements: - Effect: Allow Action: - s3:GetObject Resource: - "arn:aws:s3:::${self:custom.uploadBucketName}/*" - Effect: Allow Action: - s3:PutObject Resource: - "arn:aws:s3:::${self:custom.resizeBucketName}/*" functions: resize: handler: s3_handler.resize environment: RESIZE_BUCKET_NAME: ${self:custom.resizeBucketName} events: - s3: ${self:custom.uploadBucketName} layers: - ${cf:shared-package-layer-${env:STAGE}.SharedPackageLayer} resources: Resources: KsbysampleResizeBucket: Type: AWS::S3::Bucket Properties: BucketName: ${self:custom.resizeBucketName}
services/sample_service を作成し serverless.yml を変更する
git-bash から services ディレクトリの下へ移動した後 npx sls create --template aws-python3 --path sample_service
を実行します。
handler.py → apigw_handler.py にリネームした後、https://github.com/ksby/ksbysample-serverless-deploy/blob/master/services/sample_service/apigw_handler.py の内容を記述します。
sqs_handler.py を作成した後、https://github.com/ksby/ksbysample-serverless-deploy/blob/master/services/sample_service/sqs_handler.py の内容を記述します。
serverless.yml には以下の内容を記述します。
service: sample-service custom: ${file(../../stages/${env:STAGE}/custom-services.yml)} provider: name: aws runtime: python3.8 stage: ${env:STAGE} region: ${env:AWS_DEFAULT_REGION} environment: # aws-lambda-powertools 用環境変数 LOG_LEVEL: INFO POWERTOOLS_LOGGER_LOG_EVENT: false POWERTOOLS_METRICS_NAMESPACE: serverless-deploytest-project POWERTOOLS_SERVICE_NAME: sample-service # service 固有の設定 QUEUE_URL: !Ref SampleQueue TABLE_NAME: ${self:custom.tableName} tracing: apiGateway: true lambda: true iamRoleStatements: - Effect: Allow Action: - sqs:* Resource: - Fn::GetAtt: [ SampleQueue, Arn ] - Effect: Allow Action: - dynamodb:Query - dynamodb:Scan - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem Resource: - Fn::GetAtt: [ SampleTable, Arn ] functions: hello: handler: apigw_handler.hello events: - http: path: hello method: get cors: true layers: - ${cf:shared-package-layer-${env:STAGE}.SharedPackageLayer} saveTable: handler: sqs_handler.save_table events: - sqs: arn: Fn::GetAtt: [ SampleQueue, Arn ] layers: - ${cf:shared-package-layer-${env:STAGE}.SharedPackageLayer} resources: Resources: SampleQueue: Type: AWS::SQS::Queue Properties: QueueName: "${self:custom.queueName}" SampleTable: Type: AWS::DynamoDB::Table Properties: TableName: "${self:custom.tableName}" AttributeDefinitions: - AttributeName: timestamp AttributeType: S KeySchema: - AttributeName: timestamp KeyType: HASH ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1
ユニットテストを作成する
プロジェクトのルートディレクトリ直下に tests ディレクトリを作成し、その下に common、image_service、sample_service ディレクトリを作成します。最終的には以下のディレクトリ・ファイル構成になります。
プロジェクトのルートディレクトリ直下に .envrc を作成し、以下の内容を記述します。
export LOG_LEVEL=ERROR export POWERTOOLS_TRACE_DISABLED=true
- ログの出力を抑制したいので LOG_LEVEL は ERROR に設定します。
- aws-lambda-powertools の Tracer を使用しているとそのままではユニットテストが失敗するため、POWERTOOLS_TRACE_DISABLED=true を設定します。
__init__.py
を tests、tests/image_service、tests/sample_service の下に作成します。
tests/common の下に test_utils.py を作成し、以下の内容を記述します。aws_lambda_context を使用して context の mock を作成するメソッドです。
from aws_lambda_context import LambdaContext def mock_context(): context = LambdaContext() context.function_name = 'test' context.function_version = 'test' context.invoked_function_arn = 'test' context.memory_limit_in_mb = 'test' context.aws_request_id = 'test' context.log_group_name = 'test' context.log_stream_name = 'test' return context
tests/common の下に aws_resource.py も作成し、以下の内容を記述します。setUp、tearDown で AWS リソースの mock を作成しますが、都度ユニットテストのクラスに記述するのは冗長になるのでこのファイルに必要なメソッドを記述するようにしました。
import os import boto3 UPLOAD_BUCKET_NAME = 'ksbysample-upload-bucket' RESIZE_BUCKET_NAME = 'ksbysample-resize-bucket' QUEUE_NAME = 'sample-queue-test' TABLE_NAME = 'sample-table-test' def create_s3_bucket(self): s3_client = boto3.client('s3') s3_client.create_bucket(Bucket=UPLOAD_BUCKET_NAME) s3_client.create_bucket(Bucket=RESIZE_BUCKET_NAME) self._upload_bucket_name = UPLOAD_BUCKET_NAME self._resize_bucket_name = RESIZE_BUCKET_NAME def create_sqs_queue(self): sqs_client = boto3.client('sqs') response = sqs_client.create_queue(QueueName=QUEUE_NAME) self._queue_url = response['QueueUrl'] def create_dynamodb_table(self): dynamodb_client = boto3.client('dynamodb') dynamodb_client.create_table( TableName=TABLE_NAME, AttributeDefinitions=[ { "AttributeName": "timestamp", "AttributeType": "S" } ], KeySchema=[ { "AttributeName": "timestamp", "KeyType": "HASH" } ], ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1}, ) self._table_name = TABLE_NAME def delete_s3_bucket(self): s3 = boto3.resource('s3') upload_bucket = s3.Bucket(UPLOAD_BUCKET_NAME) upload_bucket.objects.all().delete() upload_bucket.delete() resize_bucket = s3.Bucket(RESIZE_BUCKET_NAME) resize_bucket.objects.all().delete() resize_bucket.delete() def delete_sqs_queue(self): with self.env: sqs_client = boto3.client('sqs') sqs_client.delete_queue( QueueUrl=os.environ['QUEUE_URL'] ) def delete_dynamodb_table(self): with self.env: dynamodb_client = boto3.client('dynamodb') dynamodb_client.delete_table(TableName=os.environ['TABLE_NAME'])
tests/image_service の下に test_resize.py を作成し、以下の内容を記述します。
import json import os import unittest from unittest.mock import patch import boto3 from moto import mock_s3 from tests.common import aws_resource, test_utils @mock_s3 class TestResizeService(unittest.TestCase): def setUp(self): aws_resource.create_s3_bucket(self) self.env = patch.dict('os.environ', { 'UPLOAD_BUCKET_NAME': self._upload_bucket_name, 'RESIZE_BUCKET_NAME': self._resize_bucket_name }) def tearDown(self): aws_resource.delete_s3_bucket(self) def test_resize(self): with self.env: from services.image_service import s3_handler s3_client = boto3.client('s3') s3_client.upload_file('tests/image_service/sample.jpg', os.environ['UPLOAD_BUCKET_NAME'], 'sample.jpg') with open('tests/image_service/s3_event.json', 'r') as f: event = json.load(f) s3_handler.resize(event, test_utils.mock_context()) thumb_object = s3_client.get_object(Bucket=os.environ['RESIZE_BUCKET_NAME'], Key='sample_thumb.jpg') self.assertEqual(thumb_object['ResponseMetadata']['HTTPStatusCode'], 200) self.assertGreater(int(thumb_object['ResponseMetadata']['HTTPHeaders']['content-length']), 0) # 生成されたサムネイル画像をダウンロードすることも出来る(実際に作成される) # s3_client.download_file(TestResizeService.RESIZE_BUCKET, 'sample_thumb.jpg', # 'tests/sample_thumb.jpg')
tests/sample_service の下に test_apigw_handler.py を作成し、以下の内容を記述します。
import json import os import unittest from unittest.mock import patch import boto3 from moto import mock_sqs from tests.common import aws_resource, test_utils @mock_sqs class TestApigwHandler(unittest.TestCase): def setUp(self): aws_resource.create_sqs_queue(self) self.env = patch.dict('os.environ', { 'QUEUE_URL': self._queue_url, }) def tearDown(self): aws_resource.delete_sqs_queue(self) def test_hello(self): with self.env: from services.sample_service import apigw_handler sqs_resource = boto3.resource('sqs') queue = sqs_resource.Queue(os.environ['QUEUE_URL']) with open('tests/sample_service/apigw_event.json', encoding='utf-8', mode='r') as f: apigw_event = json.load(f) response = apigw_handler.hello(apigw_event, test_utils.mock_context()) self.assertEqual(response['statusCode'], 200) messages = queue.receive_messages(QueueUrl=os.environ['QUEUE_URL']) self.assertEqual(len(messages), 1) self.assertEqual(messages[0].body, "これはテストです")
tests/sample_service の下に test_sqs_handler.py を作成し、以下の内容を記述します。
import json import os import unittest from unittest.mock import patch import boto3 from moto import mock_sqs, mock_dynamodb2 from tests.common import aws_resource, test_utils @mock_sqs @mock_dynamodb2 class TestSqsHandler(unittest.TestCase): def setUp(self): aws_resource.create_sqs_queue(self) aws_resource.create_dynamodb_table(self) self.env = patch.dict('os.environ', { 'QUEUE_URL': self._queue_url, 'TABLE_NAME': self._table_name, }) def tearDown(self): aws_resource.delete_sqs_queue(self) aws_resource.delete_dynamodb_table(self) def test_save_table(self): with self.env: from services.sample_service import sqs_handler dynamodb_sample_table_tbl = boto3.resource('dynamodb').Table(os.environ['TABLE_NAME']) with open('tests/sample_service/sqs_event.json', encoding='utf-8', mode='r') as f: sqs_event = json.load(f) sqs_handler.save_table(sqs_event, test_utils.mock_context()) items = dynamodb_sample_table_tbl.scan()['Items'] self.assertEqual(len(items), 1) self.assertEqual(items[0]['message'], "これはテストです")
python -m unittest -v
を実行してユニットテストが成功することを確認します。.envrc でユニットテストに必要な環境変数を設定しているので git-bash 上で実行します。
IntelliJ IDEA 上でもユニットテストが成功するようにします。メインメニューから「Run」-「Edit Configurations...」を選択して「Run/Debug Configurations」ダイアログを表示した後、「Templates」-「Python tests」-「Unittests」を選択して以下の画像の赤枠の部分を設定します。
Project Tool Window 上で tests ディレクトリを選択してコンテキストメニューを表示してから「Run 'Unittests in tests'」を選択してユニットテストを実行し、成功することを確認します。
続く。。。
次回は npm-scripts を定義した後、ローカルPC から開発環境(dev)、ステージング環境(stg)を deploy して動作確認します。
履歴
2020/07/19
初版発行。