SpringOne 2020 Day 2 視聴ログ
SpringOne 2020 のセッション動画が公開されたのを視聴して面白かったポイントをメモ。こちらは Day 2。Day 1 はこちら。
タイトルの 👍 の数は面白かった度合いです。
- Connect Your Functions with RSocket 👍👍
- Introduction to WebMvc.fn 👍👍
- Spring Security Patterns 👍👍
- Automated Virtualized Testing (AVT) with Docker, Kubernetes, WireMock and Gatling 👍
- Enabling Cloud Native Buildpackas for Windows Containers 👍
- Spring Boot Omakase: A First-Paced “Chef’s Choice” Dive into Fun and Useful Topics! 👍👍
Connect Your Functions with RSocket 👍👍
Conect Your Function with RSocket
www.slideshare.net
RSocket と Spring CLoud Function を連携する話。
RSocket の基本的な説明
- Reactive Stream で重要なバックプレッシャーをわかりやすく説明するブログ を読んでね!
- それはそれとして RSocket の基本的な説明
Spring Cloud Function と RSocket の関係
- 別人かってくらいやる気がなくなってるけどライブコーディング始めたら少し元気になってきた
- spring-cloud-stream-binder-rabbit を依存ライブラリに追加するだけで RabbitMQ につながる!
- 起動引数だけで有効化する Function Bean を制御できる --spring.cloud.function.definition=nnn
- RSocket を使うには spring-cloud-function-rsocket を依存ライブラリに追加する
- Function Bean は4種類のどのコミュニケーションモデルでも利用できる
- Function Routing という考え方が登場する
- functionRouter という組み込みの部品を使う
- メッセージに応じて Function Bean を分配するための仕組み
- 例: ヘッダーで判断する spring.cloud.function.routing-expression=headers.func (func というキーの値で制御する)
- コンテンツタイプとかいろいろ制御できる
Spring Cloud Function の Function Routing の制限をどうにかする仕組み
- Spring Cloud Gateway から分離して rsocket の incubator プロジェクトとしてやっている RSocket Routing Broker という部品の機能 (rsocket-routing-client-spring という依存ライブラリ)
- n 対 n 同期型のブローカークラスタを形成できるみたい
- RabbitMQ で見たやつだ
Introduction to WebMvc.fn 👍👍
www.slideshare.net
WebMvc.fn の基本要素を紹介。
Spring Web MVC のアノテーション中心の設計をプログラミングで実現するような感じで面白い。
メソッド引数への自動的なバインディングは無くなるけど、ルーティングの自由度は高まる。
Handler Functions
- ServerRequest から ServerResponse へ変換する役割
- RequestEntity を ServetRequest に、ResponseEntity を ServerResponse に書き換えるだけのような感じ
- 便利アノテーションが使えなくなるけどそれは Router Function や Filter Function で対応する
- Validated が使えなくなるのは不便だなぁ
Router Functions
- ServerRequest に HandlerFunction を対応付ける役割
- アノテーション @RequestMapping(vakye=”/path”, method={”GET”}) をビルダー route().GET(“/path”, handlerFunction) に置き換えるような感じ
- ビルダーを構成する RequestPredicate にはもっと柔軟な制御をする機能がある
Request Predicates
- 機能
- 特徴
- Predicate は合成できる predicateA.and(predicateB) predicateA.or(predicateB)
Filter Functions
- Router Function の機能の一部
- Spring Web MVC の ExceptionHandler をビルダーの onError で置き換える感じ
Spring Security Patterns 👍👍
www.slideshare.net
Spring Security の始め方から簡単な使い方までの紹介
Spring Security の始め方
- 依存ライブラリに spring-boot-starter-securityを追加するだけで basic 認証が有効になる
- 自前のエンドポイントだけでなく組み込みのエンドポイントにも
- Secure by Default という考え方を覚えておくこと
- 初期ユーザーは user、初期パスワードはランダム文字列
- デフォルトプロファイルを本番プロファイルにしておくとよい
- プロファイルの指定を忘れたときのため
- ローカルファイルや h2 を使うプロファイルは dev とかにしておく
- 未知のエンドポイントへのアクセスには 404 ではなく 401 を返すほうがいい
- エンドポイントが存在するかどうかも秘匿すべき情報
- パスワード再発行ページでは ユーザーが存在しない エラーを伝えるべきではない
- ユーザーが存在してたらメールを送信しておくね くらいがちょうどいい
認証情報の使い方
- Security Context Holder の static メソッドにアクセスする
- DI する
- Authentication オブジェクト
- @CurrentSecurityContext(expression=”authentication.name”) String name
例: OAuth2 のリソースサーバーを構成する
- Basic 認証は弱いのでトークンベースの認証に切り替えたほうがいい
- たとえば OAuth2
- 依存ライブラリにspring-boot-starter-security-oauth2-authorization-server を追加する
- UserDetailService の代わりに RegisteredClientRepository Bean を登録する
- クライアントが認証・認可サーバーから取得したクレームに含まれるスコープを Authentication#authorities (認可情報)として利用できる
- JwtGrantedAuthoritiesConverter という組み込み実装を利用すると便利
- トークンを GrantedAuthority に変換する Converter を構成できる
- 継承ではなくコンポジションで利用するのが Spring Security の作法なので注意
- 段階的に機能を拡張できるのがいいところ
例: 認証・認可情報の判断をビジネスロジックから追い出す
- 宣言的セキュリティモデル、という考え方
- メソッドベースのセキュリティ
- @PreAuthorize(“hasAuthority('captain')”) みたいなメソッドアノテーションを利用する
- リクエストベースのセキュリティ
- WebSecurityConfigureAdapter を継承した設定クラスで DSL により構成する
Automated Virtualized Testing (AVT) with Docker, Kubernetes, WireMock and Gatling 👍
Automated Virtualized Testing (AVT) with Docker, Kubernetes, WireMock and Gatling
www.slideshare.net
AVT (自動化仮想テスト?) という考え方の紹介
伝統的なテストの課題
- 不安定な環境
- システム間の依存性
- テストできる期間が限られてる
- 信頼できない結果
AVT
- 外部環境との結合を分離してコンテナ環境でモックを対象にテストする考え方
- Liberty Mutual では Bitbucket Server + Bamboo を利用してる
- コンテナ環境は Docker Enterprise
- 性能テストのクライアントも同じコンテナ環境で実行する
- 十分に負荷をかけるのが大変そうだけどどうなんだろうな
- kompose で docker-compose.yml をマニフェストに変換してる
- テスト結果は S3 経由で InfluxDB に記録して Grafana ダッシュボードで確認できるようにしてる、など
Enabling Cloud Native Buildpackas for Windows Containers 👍
Enabling Cloud Native Buildpackas for Windows Containers
www.slideshare.net
Build a Windows app · Cloud Native Buildpacks
Windows Container をビルドできる CNB を作った話
- pack-cli によるビルダーイメージの構築部分を改修するためいろいろ頑張った
- ライフサイクル管理の実装は bash を前提にしてたので改修するのがとてもとても大変だった
- Windows イメージのレイヤリングは Linux イメージと全然違う
- 特別な形式の TAR が必要になった
C:\Windows\System32\tar.exe
を利用してるのかな
- いろいろ大変だったけど完成した
- Tanzu Build Service でベータ版を利用できる
- pack-cli から利用できる
- nanoserver のコンテナイメージは 4GB くらいある
Spring Boot Omakase: A First-Paced “Chef’s Choice” Dive into Fun and Useful Topics! 👍👍
Spring Boot Omakase: A First-Paced “Chef’s Choice” Dive into Fun and Useful Topics!
SpringOne 2020 最後のセッション。Omakase = シェフのおまかせ。コンフィギュレーションやデプロイメントのおすすめレシピを紹介する話。
全編ライブコーディングなので動画を見ないとわからないやつ。🎥
Configuration
プロパティファイル、環境変数、アプリケーション引数をいい感じに利用する話。
- Dtoクラスに ConigurationProperties で対応するプロパティキーを束縛できる
- Bean メソッドにも ConfigurationProperties を指定できる
- 便利
Deployment
- Shaded jar と Spring Boot の Executable Jar の違い
- Shaded Jar や Uber Jar は依存ライブラリを展開して単一の jar ファイルに格納する
- Spring Boot の Executable Jar は依存ライブラリをネストするのでクラスの読み込みとかが少し速い
- Value で DI した値の取得元は Actuator で参照できる
- 便利
SpringOne 2020 Day 1 視聴ログ
SpringOne 2020 のセッション動画が公開されたのを視聴して面白かったポイントをメモ。こちらは Day 1。Day 2 はこちら。
タイトルの 👍 の数は面白かった度合いです。
- Introducing Spring Framework 5.3 👍👍👍
- Game of Streams 🐉: How to Tame and Get the Most from Your Messaging Platforms 👍
- If Hemingway Wrote JavaDocs 👍
- Functions: Implement Once, Execut Anyware ! 👍👍
- Making Spring Home (customization & extensibility) 👍👍
- A Deep Dive into Spring Application Events 👍👍👍
- 雑感
Introducing Spring Framework 5.3 👍👍👍
Introducing Spring Framework 5.3 | SpringOne | September 2–3, 2020
www.slideshare.net
Spring Framework 5.x の歴史と 5.3 の新フィーチャー、そして未来の話。
- Spring Framework 5.3 は LTS リリース
- 2024 年くらいまでを想定してる
- Spring Boot 2.4 および 2.5 は 5.3 ベースになる
- JDK 8,11,15,17 をサポートする
- 5.x 系列は打ち止め
- 5.4 は出ない
- 次のメジャーバージョン 6.x でやりたいこと
- JavaEE から JakartaEE への移行
- GraalVM の継続的なサポート
- Java 14 で導入された record 型が活用されそう
Game of Streams 🐉: How to Tame and Get the Most from Your Messaging Platforms 👍
www.slideshare.net
メッセージングによるシステム連携を Spring Cloud Stream で簡単に実現してみようという話
- Spring Boot: Up and Running という本を O’Reilly から出版するみたい (予定では 2021-02 になってる)
- 「XXX: Up and Runnig」ていう本は日本の「YYY 実践入門」に相当する感じ
- Auto Configuration とプロパティの定義だけで RabbitMQ や Kafka を利用したメッセージングが簡単に実現できる様子を実演してた
- プロパティの命名規則に従ってキューやチャンネルを作るのはお勧めできないような気もする
- デモンストレーションにはいいんだけど
If Hemingway Wrote JavaDocs 👍
If Hemingway Wrote JavaDocs | SpringOne | September 2–3, 2020
www.slideshare.net
Spring Project のテクニカルライターの人がどんなことに気をつけてるか紹介してた。JavaDoc の話はない。
- 読者には初心者、中堅、エキスパートがいる
- 釣鐘上の分布をしている
- 壁を乗り越えて中堅からエキスパートに至る初心者はごくわずか
- 探し物をしに来ているのであって文章を読みに来ているのではない
- 冗長な表現をやめて、できるだけ簡潔に表現すること(ヘミングウェイとかの文豪からこの辺の格言を引用してた)
- 不必要な言葉を消す
- 現在を表す時制にする
- Before: “In this tutorial, we will define a Web service that is created by a Human Resources department.”
- After: “In this tutorial, we define a Web service that is created by a Human Resources department.”
- 短い表現にする
- “utilize” => “use”
- “allows you to” => “lets you”
- 主語を置いて直接的な表現にする
- RFC とか重要な情報への外部リンクを設けるのは大事だけど “ここ” みたいに代名詞をリンク文字列にするのはよくない
- 文字通りすべての読者を対象に考慮すること
- ローカルな表現を入れない
- i.e とか e.g みたいな古いラテン語由来の省略記法は理解できない人が多いので避けること
- 自分が翻訳するときは軒並み文章に展開してるなぁ
- 短縮形やジョークを入れない、一貫した用語を使う
- 文学作品を目指すのではなく、ユーザーヘルプを目指す
Functions: Implement Once, Execut Anyware ! 👍👍
Functions: Implement Once, Execute Anywhere! | SpringOne | September 2–3, 2020
www.slideshare.net
Spring Cloud Function の紹介。
- Spring Cloud Function は Java 8 Functional Interface の上位版として使える雰囲気がある。特に関数合成とかカリー化とか
- Functional Interface を Spring Bean として登録、利用できる
- Spring Cloud Function は FunctionCatalog という SpringBean を提供してる
- 文字列リテラルで Fuction Bean にアクセスできる
- たとえば “uppercase” という Bean には
catalog.lookup(“uppercase”)
でアクセスできる - 関数合成もできる
funA.andThen(funB)
をcatalog.lookup(“funA | funB”)
で実現できる
- (真偽不明) Function Bean の引数は Spring の Converter で解決してる
spring-boot-starter-web
を追加するだけで Function Bean のための http エンドポイントを公開できる *たとえば
uppercaseという Function Bean を登録していたら、
http://localhost/uppercase/xxx` みたいにアクセスできる- なんかすげー
Making Spring Home (customization & extensibility) 👍👍
Make Spring Home (Spring Customization and Extensibility) | SpringOne | September 2–3, 2020
www.slideshare.net
GitHub - sasiperi/alexa-spring-boot
Spring Boot で Alexa Skill を 作る話。
- 毎回準備するのは大変なので spring-boot-starter にしたほうがい
- 開発者体験を向上するには spring-initilizr から利用できるようにしないといけない
- spring-initilizr を fork してカスタマイズしてた
- その場で作ってデモンストレーション
- Spring Security を利用して OAuth2 の認可コードフローを実装して、Alexa Skill 利用者がアカウントを紐づけする
- ローカルPC で実行したアプリを ngrok で公開し、Alexa からアクセスできるようにしてた
A Deep Dive into Spring Application Events 👍👍👍
A Deep Dive into Spring Application Events
www.slideshare.net
Spring Data の中の人が Appliation Event について紹介してる。DDD や Event Sourcing の知識があるとわかりやすいな。
基礎
- ApplicationEventListener のコールバック呼び出しは同期的に行われる
- @EventListener をメソッドの付けるだけでリスナーになれる
- イベントの種類はアノテーションで指定してもいいし、メソッド引数の型で指定してもいい
- @Async を付ければ非同期にもできる
- Spring の抽象 ApplicationEventPublisher をビジネスロジックに DI するのは嫌だよね
- Spring Data の抽象クラス Aggregate にはドメインイベントの考え方が実装されてる
- registerEvent すればアプリケーションイベントを発行できる
- Eventuate-Tram でも利用してた気がする
- Baeldung でも詳しく説明されてる
トランザクション制御
- EventListener はトランザクション範囲に含まれる
- 自分ではトランザクションを開始しない
- 途中で失敗したら後続のリスナーは呼び出さない
- TransactionalEentListener はそれ自体がトランザクション範囲になるイベントリスナー
- それぞれのリスナーは独立してイベントを受信する
- 実用例
- バッチ的なリトライロジックの実現
spring-domain-events-starter
という個人プロジェクトを利用してるみたい (もう削除されてた)- 別々のログを記録する @TransactionalEventListener を用意して、それぞれ成功したら捨てる。すると次回から空振りするようになる
- バッチ的なリトライロジックの実現
アーキテクチャ
- Inventory と Orders の関係性を考えてる
- completeOrder というメソッドにはいろいろな機能追加がありそうだし Inventory を操作するのは違うんじゃないかという疑問
- トランザクション範囲がどんどん広がってしまう
- テストを書くと違和感がはっきりする
- completeOrder というメソッドにはいろいろな機能追加がありそうだし Inventory を操作するのは違うんじゃないかという疑問
- @ModuleTest で疎結合性を検証する
- Moduliths の提供するアノテーション (Spring I/O 2019 Recap - Modulithsで紹介されてた)
- Inventory から直接 Orders にアクセスするのは OK
- Ordersから直接 Inventory にアクセスするのは NG
- Inventory が Order のイベントリスナーを実装する
- イベントで連携する場合はユニットテストじゃなくてインテグレーションテストで検証する
- ビジネスロジックはきれいに分離できたのでイベントストアを外部化するのは簡単だと思うよ (どうかなぁ🤔)
雑感
- プレゼンテーション資料に書いてない詳細をひたすら喋りまくる人ばかりで、再生速度を遅くして英語字幕(自動生成)も付けてようやく追いつける感じだった。
- 巻き舌なインド英語を正確に文字化する YouTube の自動字幕機能はすごい (たとえば purpose をパーパスじゃなくてパルパスと発音するんだよね…)
Public Cloud Provider ごとの Java ランタイム
Java で1つのソースコードファイルに複数のクラスを記述する
実験で書いてみましたが業務で利用するといろんな人がびっくりするのでやめましょう
テストコードならありなのかなぁ
前書き的なやつ
よくライブコーディングで見かけるやつが気になったので整理しました。 Java でも 1 つのソースコードファイルに複数のクラスを定義することができます。
example/app/Application.java
- 定義してるクラス
- Application クラス
- public static void main メソッドを定義してる
example/test/Utility.java
- 定義してるクラス
- Utility クラス
- Item クラス ?
- Option クラス ??
package example.app; import example.test.Utility; public class Application { public static void main(String[] args) { var utility = new Utility(); System.out.println("app: " + utility.doSomething(args[0], args[1])); } }
package example.test; public class Utility { public static void main(String[] args) { var utility = new Utility(); System.out.println("test: " + utility.doSomething(args[0], args[1])); } public Option doSomething(String name, String value) { return new Option(new Item(name, value)); } } class Item { private final String name; private final String value; Item(String name, String value) { this.name = name; this.value = value; } @Override public String toString() { return "Item[name=" + this.name + ",value=" + this.value + "]"; } } class Option { private final Item item; Option(Item item) { this.item = item; } @Override public String toString() { return "Option[item=" + this.item.toString() + "]"; } }
後者の Utility.java
に3つもクラスが定義されてるのは、普段見かけないからすごい不思議な感じがすると思います。
個人的には Application
クラスから example.test.Option
を返り値とする Utility#doSomething
を利用できてるのが不思議です。
理屈
パッケージに関する Java 言語仕様が関係してます。
少なくとも1つはファイル名と同じクラスやインターフェイスが存在すること、それらのクラスやインターフェイスの可視性が public であることがコンパイル可能であることの制約になっています。
結局、 Utility.java
で定義している Item
や Option
はただのパッケージスコープになっているということです。
パッケージがファイルシステム(7.2.1)に格納されるとき,ホストシステムは,次のいずれかが真の場合は,型名及び(.java 又は .jav のような)拡張子から構成される名前でファイル内にその型を見つけられなければ,コンパイル時エラーとする制約を課すことができる。
実践
コンパイルしてみましょう。成功するはずです。
$ ${JAVA_HOME}/bin/javac -version javac 11.0.8 $ ${JAVA_HOME}/bin/javac -verbose -d out example/app/Application.java example/test/Utility.java [/modules/java.transaction.xa/module-info.classを読込み中] [/modules/jdk.internal.jvmstat/module-info.classを読込み中] [/modules/jdk.jartool/module-info.classを読込み中] [/modules/jdk.crypto.ec/module-info.classを読込み中] [/modules/jdk.jshell/module-info.classを読込み中] [/modules/java.datatransfer/module-info.classを読込み中] [/modules/java.desktop/module-info.classを読込み中] [/modules/jdk.naming.rmi/module-info.classを読込み中] [/modules/jdk.jdeps/module-info.classを読込み中] [/modules/jdk.jsobject/module-info.classを読込み中] [/modules/jdk.jfr/module-info.classを読込み中] [/modules/jdk.security.jgss/module-info.classを読込み中] [/modules/java.logging/module-info.classを読込み中] [/modules/java.smartcardio/module-info.classを読込み中] [/modules/jdk.rmic/module-info.classを読込み中] [/modules/java.instrument/module-info.classを読込み中] [/modules/jdk.dynalink/module-info.classを読込み中] [/modules/jdk.pack/module-info.classを読込み中] [/modules/jdk.naming.dns/module-info.classを読込み中] [/modules/java.se/module-info.classを読込み中] [/modules/java.security.sasl/module-info.classを読込み中] [/modules/jdk.charsets/module-info.classを読込み中] [/modules/jdk.internal.vm.compiler.management/module-info.classを読込み中] [/modules/jdk.internal.ed/module-info.classを読込み中] [/modules/java.rmi/module-info.classを読込み中] [/modules/jdk.sctp/module-info.classを読込み中] [/modules/jdk.security.auth/module-info.classを読込み中] [/modules/jdk.internal.vm.ci/module-info.classを読込み中] [/modules/jdk.jlink/module-info.classを読込み中] [/modules/java.base/module-info.classを読込み中] [/modules/java.sql/module-info.classを読込み中] [/modules/jdk.unsupported.desktop/module-info.classを読込み中] [/modules/jdk.compiler/module-info.classを読込み中] [/modules/java.net.http/module-info.classを読込み中] [/modules/jdk.xml.dom/module-info.classを読込み中] [/modules/jdk.scripting.nashorn.shell/module-info.classを読込み中] [/modules/jdk.management.agent/module-info.classを読込み中] [/modules/java.management/module-info.classを読込み中] [/modules/jdk.management/module-info.classを読込み中] [/modules/jdk.internal.le/module-info.classを読込み中] [/modules/jdk.zipfs/module-info.classを読込み中] [/modules/jdk.jstatd/module-info.classを読込み中] [/modules/jdk.internal.vm.compiler/module-info.classを読込み中] [/modules/java.compiler/module-info.classを読込み中] [/modules/java.xml.crypto/module-info.classを読込み中] [/modules/jdk.httpserver/module-info.classを読込み中] [/modules/jdk.aot/module-info.classを読込み中] [/modules/jdk.jcmd/module-info.classを読込み中] [/modules/java.scripting/module-info.classを読込み中] [/modules/jdk.localedata/module-info.classを読込み中] [/modules/jdk.editpad/module-info.classを読込み中] [/modules/java.prefs/module-info.classを読込み中] [/modules/java.xml/module-info.classを読込み中] [/modules/jdk.unsupported/module-info.classを読込み中] [/modules/java.sql.rowset/module-info.classを読込み中] [/modules/jdk.net/module-info.classを読込み中] [/modules/jdk.jdi/module-info.classを読込み中] [/modules/jdk.internal.opt/module-info.classを読込み中] [/modules/java.naming/module-info.classを読込み中] [/modules/jdk.crypto.cryptoki/module-info.classを読込み中] [/modules/jdk.management.jfr/module-info.classを読込み中] [/modules/jdk.hotspot.agent/module-info.classを読込み中] [/modules/jdk.jdwp.agent/module-info.classを読込み中] [/modules/java.security.jgss/module-info.classを読込み中] [/modules/jdk.attach/module-info.classを読込み中] [/modules/jdk.accessibility/module-info.classを読込み中] [/modules/jdk.javadoc/module-info.classを読込み中] [/modules/jdk.scripting.nashorn/module-info.classを読込み中] [/modules/jdk.crypto.mscapi/module-info.classを読込み中] [/modules/jdk.jconsole/module-info.classを読込み中] [/modules/java.management.rmi/module-info.classを読込み中] [ソース・ファイルの検索パス: .] [/modules/java.base/java/lang/Object.classを読込み中] [/modules/java.base/java/lang/String.classを読込み中] [/modules/java.base/java/lang/Deprecated.classを読込み中] [/modules/java.base/java/lang/Override.classを読込み中] [/modules/java.base/java/lang/annotation/Annotation.classを読込み中] [/modules/java.base/java/lang/annotation/Retention.classを読込み中] [/modules/java.base/java/lang/annotation/RetentionPolicy.classを読込み中] [/modules/java.base/java/lang/annotation/Target.classを読込み中] [/modules/java.base/java/lang/annotation/ElementType.classを読込み中] [example.app.Applicationを確認中] [/modules/java.base/java/io/Serializable.classを読込み中] [/modules/java.base/java/lang/AutoCloseable.classを読込み中] [/modules/java.base/java/lang/System.classを読込み中] [/modules/java.base/java/io/PrintStream.classを読込み中] [/modules/java.base/java/lang/Appendable.classを読込み中] [/modules/java.base/java/io/Closeable.classを読込み中] [/modules/java.base/java/io/FilterOutputStream.classを読込み中] [/modules/java.base/java/io/OutputStream.classを読込み中] [/modules/java.base/java/io/Flushable.classを読込み中] [out\example\app\Application.classを書込み完了] [example.test.Utilityを確認中] [out\example\test\Utility.classを書込み完了] [example.test.Optionを確認中] [/modules/java.base/java/lang/Byte.classを読込み中] [/modules/java.base/java/lang/Character.classを読込み中] [/modules/java.base/java/lang/Short.classを読込み中] [/modules/java.base/java/lang/Long.classを読込み中] [/modules/java.base/java/lang/Float.classを読込み中] [/modules/java.base/java/lang/Integer.classを読込み中] [/modules/java.base/java/lang/Double.classを読込み中] [/modules/java.base/java/lang/Boolean.classを読込み中] [/modules/java.base/java/lang/Void.classを読込み中] [/modules/java.base/java/lang/invoke/StringConcatFactory.classを読込み中] [/modules/java.base/java/lang/invoke/MethodHandles.classを読込み中] [/modules/java.base/java/lang/invoke/MethodHandles$Lookup.classを読込み中] [/modules/java.base/java/lang/invoke/MethodType.classを読込み中] [/modules/java.base/java/lang/invoke/CallSite.classを読込み中] [out\example\test\Option.classを書込み完了] [example.test.Itemを確認中] [out\example\test\Item.classを書込み完了] [合計317ミリ秒]
ちゃんと実行できます。
$ ${JAVA_HOME}/bin/java -cp out example.app.Application a 1 app: Option[item=Item[name=a,value=1]] $ ${JAVA_HOME}/bin/java -cp out example.test.Utility b 2 test: Option[item=Item[name=b,value=2]]
banner コマンド 2020
banner コマンドは 1990 年より前に開発されたコマンドで、今は Cedar Solutions という会社がソースコードを保有しているようです。
こんな風に指定した文字列を大きな文字にしてくれるコマンドです。サーバー作業手順書で見かけたことのある人がいるかもしれません。
最近のディストリビューションでも利用できるのか調べてみました。
# banner tech
##### ###### #### # #
# # # # # #
# ##### # ######
# # # # #
# # # # # #
# ###### #### # #
CentOS / RHEL
RHEL 8 の yum リポジトリには残ってませんでした。
$ docker container run --rm -it registry.access.redhat.com/ubi8/ubi:latest
[root@690b60b58764 /]# cat /etc/os-release
NAME="Red Hat Enterprise Linux"
VERSION="8.2 (Ootpa)"
ID="rhel"
ID_LIKE="fedora"
VERSION_ID="8.2"
PLATFORM_ID="platform:el8"
PRETTY_NAME="Red Hat Enterprise Linux 8.2 (Ootpa)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:redhat:enterprise_linux:8.2:GA"
HOME_URL="https://www.redhat.com/"
BUG_REPORT_URL="https://bugzilla.redhat.com/"
REDHAT_BUGZILLA_PRODUCT="Red Hat Enterprise Linux 8"
REDHAT_BUGZILLA_PRODUCT_VERSION=8.2
REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux"
REDHAT_SUPPORT_PRODUCT_VERSION="8.2"
[root@690b60b58764 /]# yum install -q -y epel-release
Error: Unable to find a match: epel-release
[root@690b60b58764 /]# yum install -q -y banner
Error: Unable to find a match: banner
CentOS 8 の yum リポジトリ にも残ってません。
Debian 10 では main ラインに残っています。 System-V banner clone という名称になっていますけど。
Alpine
Alpine 3.9 の apk リポジトリには存在しませんでした。残念。
$ docker container run --rm -it alpine:3.9
/ # cat /etc/os-release
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.9.6
PRETTY_NAME="Alpine Linux v3.9"
HOME_URL="https://alpinelinux.org/"
BUG_REPORT_URL="https://bugs.alpinelinux.org/"
/ # apk add -u -q banner
ERROR: unsatisfiable constraints:
banner (missing):
required by: world[banner]
第6回 Chaos Engieering 読書会@リモートのログ
引き続き Discord によるオンライン開催。
次回は 10/17(土曜日)で、Part III Human Factors の 12 Experiment Selection Problem (and a Solution) から。
対象範囲
- Part III Human Factors(第三部 ヒューマンファクター)
- 10 Humanistic Chaos
- 10.3 Putting the Principles into Practice
- 10.3 Case Study 1 ~ Gaming Your Game Day
- 10.3 Case Study 2 ~ Connecting the Dots
- 10.3 Case Study 3 - Changing a Basic Assumption
- 11 People in the Loop
トピック
- BIT VALLEY 2020 で話してきた
- 転職したら自己紹介のための動画撮影が大変だった
- 東京離脱を検討中
- private で YouTube 活動し始めた
- 新卒採用の会社紹介動画をスタジオ収録してきた(400人以上の視聴者!) *
ディスカッション
10. Humanistic Chaos
弱いシグナルとか定常状態とかいうけど、info レベルのアラートは数が多すぎて重要な情報が埋もれがち
- オオカミ少年問題
他の人とまったく意見が合わなかった
- メトリクスの解釈の仕方は難しい、という話じゃないと思うんだけどな
- メトリクスの大小を判断する話じゃないと思うんだけど
抄訳作るのに deepL 使ってるそうだけど、文章が日本語として成立してないのでまともに読めてる人がどれだけいるんだろうか。
10.3. Putting the Principles into Practice
人間系を部分取り替え可能な系だと考えるところが常に疑問
10.3 Case Study 1 ~ Gaming Your Game Day
- ゲームデイはやったほうがいいんだね
- 「唯一の専門家を休暇扱いにする、あるいは、運営側にする」のは面白いやり方
10.3 Case Study 2 ~ Connecting the Dots
- 組織マネジメントの話だった
- 仮説の記述が甘いよね
- 各社のローテーション話で盛り上がってた
10.3 Case Study 3 - Changing a Basic Assumption
- 文章構成がよく分からない。こういうことかな
- 前提 - 組織の基本的な前提となるシステムを変える手助けをしましょう
- 仮説 - 人々が仕事をしたくなるような場所を作ることで、最終的にはより幸せで生産性の高い従業員が生まれるのです
- 変数 - 私の個人的な経験に基づいて、二つの例を挙げると、エンジニアが行った行動が当社の文化の大きな柱となったことに由来しています
- 成果 - 私がこの種の活動で最も成功しているのは、"何かをやってみたい "と明確に表明しているときです。これは失敗してもいい、それは大丈夫だという期待を明確に設定することで、最終的には成功する可能性が高くなります
11. People in the Loop
- Dark Deployってなんだろう
- フィッツリストからいろいろと話題が派生した
参考情報
Spring Framework のアプリケーションを停止したときの振る舞い
Spring Framework で実装したアプリケーションを停止するときに何が起きているのか整理しました。
具体的な停止方法については Shutdown a Spring Boot Application(Baeldung) にまとまっているので参照してください。
要点
- Spring Framework のアプリケーションを安全に停止する方法が知りたい
- Spring Framework のアプリケーションに停止処理を追加する方法が知りたい
ContextClosedEvent
に対するイベントリスナーを実装するか、@PreDestroy
などで修飾したメソッドに記述する
- コンテナ実行基盤で実行している Spring Framework のアプリケーションに停止処理を追加したい
- 停止処理を実行できる時間には制限があるので気を付けましょう
停止のきっかけと振る舞い
フレームワークと関係ない Java HotSpot VM の話です。
Java HotSpot VM が停止するきっかけには内部要因と外部要因があります。
- 内部要因
daemonize
していない全てのスレッドが終了した場合- 例 -
main
スレッドとdaemonize
した複数のスレッドが存在するプログラムでmain
スレッドが終了した場合
- 例 -
System.exit()
を呼び出した場合
- 外部要因
- OS からシグナルを受信した場合
SIGINT
- Ctrl+C で実行を中断したときに発生するシグナルですSIGTERM
-kill
コマンドが通知するデフォルトのシグナルですSIGHUP
- 接続していた ptty を失った場合などに発生するシグナルです- 参考 - Oracle Solaris、LinuxおよびmacOSで使用されるシグナル
- OS からシグナルを受信した場合
どちらの場合でも、 Runtime#addShutdownHook
で登録されて待機状態になっていたスレッド(シャットダウンフック)が起動するようになっています。
(参考 - Runtime (Java SE 11 & JDK 11))
Spring Framework の登録するシャットダウンフック
ConfigurableApplicationContext を実装した抽象基底クラスの AbstractApplicationContex がシャットダウンフックを登録します。
/** * Register a shutdown hook {@linkplain Thread#getName() named} * {@code SpringContextShutdownHook} with the JVM runtime, closing this * context on JVM shutdown unless it has already been closed at that time. * <p>Delegates to {@code doClose()} for the actual closing procedure. * @see Runtime#addShutdownHook * @see ConfigurableApplicationContext#SHUTDOWN_HOOK_THREAD_NAME * @see #close() * @see #doClose() */ @Override public void registerShutdownHook() { if (this.shutdownHook == null) { // No shutdown hook registered yet. this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) { @Override public void run() { synchronized (startupShutdownMonitor) { doClose(); } } }; Runtime.getRuntime().addShutdownHook(this.shutdownHook); } }
doClose
メソッドはアプリケーションコンテキストイベントの ContextClosedEvent
を通知します。
(つまり、アプリケーションコードに何かしら終了処理を追加したいなら ContextClosedEvent
に対するイベントリスナーを実装すればよい)
ライフサイクル管理イベントも通知する (onClose
) ので、@PreDestroy
などで修飾したメソッドも呼び出されるはずです。
protected void doClose() { // Check whether an actual close attempt is necessary... if (this.active.get() && this.closed.compareAndSet(false, true)) { if (logger.isDebugEnabled()) { logger.debug("Closing " + this); } if (!IN_NATIVE_IMAGE) { LiveBeansView.unregisterApplicationContext(this); } try { // Publish shutdown event. publishEvent(new ContextClosedEvent(this)); } catch (Throwable ex) { logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex); } // Stop all Lifecycle beans, to avoid delays during individual destruction. if (this.lifecycleProcessor != null) { try { this.lifecycleProcessor.onClose(); } catch (Throwable ex) { logger.warn("Exception thrown from LifecycleProcessor on context close", ex); } } // Destroy all cached singletons in the context's BeanFactory. destroyBeans(); // Close the state of this context itself. closeBeanFactory(); // Let subclasses do some final clean-up if they wish... onClose(); // Reset local application listeners to pre-refresh state. if (this.earlyApplicationListeners != null) { this.applicationListeners.clear(); this.applicationListeners.addAll(this.earlyApplicationListeners); } // Switch to inactive. this.active.set(false); } }
コンテナ実行基盤ごとの停止処理と猶予時間
ここでは Docker コンテナを実行できる環境のことを コンテナ実行基盤 と呼んでいます。
停止操作を行うと、コンテナのメインプロセス(PID が 1 のプロセス) に TERM
シグナル (SIGTERM
) を通知します。
その後、所定の猶予時間を経過すると KILL
シグナル (SIGKILL
) を通知します。
KILL
シグナルはプログラムで処理できないシグナルで、対象のプロセスは OS により停止されます。
(参考 - signal(7) - Linux manual page)
つまり、アプリケーションで終了処理を実行できるのは TERM
シグナルを受信してから KILL
シグナルを受信するまでの間だけです。
コンテナ実行基盤ごとに猶予時間の初期値を整理してみました。
種類 | 猶予時間 | 変更できるかどうか |
---|---|---|
Docker Daemon | 10 秒 | コマンド引数で指定できる [1] |
Kubernetes | 30 秒 | マニフェストで設定できる [2] |
Amazon ECS | 30 秒 | コンテナエージェントの環境変数で設定できる [3] [4] |
- [1] - docker stop
- [2] - Pod の終了
- [3] - StopTask - Amazon Elastic Container Service
- [4] - Amazon ECS コンテナエージェントの設定
今のところ設定できる猶予時間に上限値は設けられていないようです。 Too big terminationGracePeriodSeconds value leads to pod hanging forever in Terminating state #84298
あまり長い時間を設定するとコンテナの特徴である軽量な停止と開始を阻害するだけなので、仕組み自体を検討したほうがいいでしょう。