Spring Boot 2.7.0についてメモ
はじめに
先日、Bootの2.7が出ましたね。今回もまとめておこうと思います。ここでは私が気になるところについてまとめて、ものによっては簡単に動かしてみたいと思います。すべての変更点に関しては
そういえば昨年のSpring Oneの記憶なんですが、2.7.xは2系の最後のリリースになるんですかね。
2.5系のOSSサポートも終わったみたいですししっかり最新のバージョンについていって3系に挙げれるようにしたいですね。
諸々みてみる
環境
今回の動作環境
$ uname -srvmpio Linux 5.14.0-1038-oem #42-Ubuntu SMP Thu May 19 05:03:08 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux $ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 20.04.4 LTS Release: 20.04 Codename: focal $ java --version openjdk 18 2022-03-22 OpenJDK Runtime Environment (build 18+36-2087) OpenJDK 64-Bit Server VM (build 18+36-2087, mixed mode, sharing) $ mvn -v Apache Maven 3.8.5 (3599d3414f046de2324203b78ddcf9b5e4388aa0) Maven home: /home/hogehoge/.sdkman/candidates/maven/current Java version: 18, vendor: Oracle Corporation, runtime: /home/yuyahirooka/.sdkman/candidates/java/18-open Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "5.14.0-1038-oem", arch: "amd64", family: "unix"
おおまかな変更点
- InfoエンドポイントでJava Vendorの情報を取得可能に
- @SpringBootTestのプロパティ優先度の変更
- OkHttp4へ変更
- Spring for GraphQL 1.0のオートコンフィグとメトリクスのサポート
- Jackson Mixinsのサポート
- PEM-encoded certsを利用したWeb server SSLの設定
- Cloud Native Buildpacks利用時にPodmanが利用可能に
- Cache2kのサポート
- 依存のアップグレード
プロジェクトの作成
今回のプロジェクトはInitializrから以下の設定で作成しました。
各変更点のざっくりまとめ
それでは各変更点についてざっくりまとめていきます。
InfoエンドポイントでJava Vendorの情報を取得可能に
JavaInfoContributor
が改善されて、Java Vendorの情報を取れるようになりました。
まずは/info
エンドポイントの公開とInfoContributor
の有効化を行いましょう。
application.properties
に以下の設定を追加します。
management.endpoints.web.exposure.include=info management.info.java.enabled=true
cURLで/info
えんどポイントにアクセスすると、以下のように情報はJasonのvendor
オブジェクトのname
プロパティに含まれます。
$ cur -sl localhost:8080/actuator/info | jq . { "java": { "version": "18", "vendor": { "name": "Oracle Corporation", "version": null }, "runtime": { "name": "OpenJDK Runtime Environment", "version": "18+36-2087" }, "jvm": { "name": "OpenJDK 64-Bit Server VM", "vendor": "Oracle Corporation", "version": "18+36-2087" } } }
@SpringBootTestのプロパティ優先度の変更
@SpringBootTest
もしくは、@TestPropertySource
で追加できるプロパティソースがコマンドラインの入力で追加できるプロパティソースの優先度より上になりました。
これらの両方を利用して設定を行っている場合は変更が必要かもしれません。
OkHttp4へ変更
OkHttp3のサポート終了により、2.7からはOkHTTP 4が利用されるようになりました。このアップグレードによりOkHttpの設定を行うプロパティもokhttp3.version
からokhttp.version
に変更されています。
Spring for GraphQL 1.0のオートコンフィグとメトリクスのサポート
2.7からSpring for GraphQLのstarterが用意されています。starterはspring-boot-starter-graphql
から利用できます。
ドキュメントによるとこのstarterを利用してWebで公開する場合以下のstarterのいずれかを追加で依存に入れてやる必要があります。
spring-boot-starter-web
spring-boot-starter-websocket
spring-boot-starter-webflux
spring-boot-starter-rsocket
より詳細な情報は各種設定に関してはこちらをご覧ください。
Jackson Mixinsのサポート
2.7からはJacsonMixinsがサポートされます。Jackson Mixinsを利用することでオブジェクトがJacksonに直接依存することなくシリアライズ/デシリアライズすることができます。Mixinの登録は@JsonMixin
アノテーションが付与されたMixinの抽象クラスが自動的に発見され行われます。
例えば以下のようなレコードクラスを用意します。
User.java
public record User(String userId, String password) {}
このレコードクラスに対してMixinするための抽象クラスを用意します。
このクラスには@JsonMixin
アノテーションを付与しタイプにシリアライズ/デシリアライズ対象のPOJOを指定します。
更にポイントはpassword
フィールドには@JsonIgnore
アノテーションを付与しプロパティを無視するようにします。
UserJacksonMixin.java
import com.fasterxml.jackson.annotation.JsonIgnore; import org.springframework.boot.jackson.JsonMixin; @JsonMixin(type = User.class) abstract class UserJacksonMixin { String userId; @JsonIgnore String password; }
このUserレコードクラスを/user
のGETアクセスで取得できるようにハンドラーを追加します。
@SpringBootApplication @RestController public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @GetMapping("/user") public User getUser(){ return new User("henoheno", "password"); } }
cURLでリクエストを送ると以下のようにpassword
フィールドが無視されたJsonが取得できます。
$ curl -s localhost:8080/user | jq . { "userId": "henoheno" }
PEM-encoded certsを利用したWeb server SSLの設定
server.ssl.certificate
やserver.ssl.certificate-private-key
プロパティを設定することでPEM-eoncordedのSSLを設定できるようになりました。
設定方法やより詳細な情報はこちらをご確認ください。
Cloud Native Buildpacks利用時にPodmanが利用可能に
Mavenに以下の設定を追加することで、Cloud Native Buildpacks利用時にDocker Engineの代わりにPodmanをつかったビルドを行えるようになりました。
<project> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <docker> <host>unix:///run/user/1000/podman/podman.sock</host> <bindHostToBuilder>true</bindHostToBuilder> </docker> </configuration> </plugin> </plugins> </build> </project>
Gradleでの設定はこちらをご確認ください。
Cache2kのサポート
Cache2kのオートコンフィグなどのサポートが入りました。設定はCache2kBuilderCustomizer beenを通して行うことができます。設定の詳細はこちら をご確認ください。
CI/CDツールのDaggerを動かす
はじめに
CICDのパイプラインを記述する際はツールごとに独自のフォーマットや記述方法で記述する必要があるため移植性が低かったり、ローカルで試しに実行するのがなかなか難しかったりすることが多いと思います。DaggerはCICDの開発キッドで上記のような課題を削減することができます。 今回はこのDaggerをつかって簡単なパイプラインを作成し、ローカルで実行してみたいと思います。
Daggerとは
上記のようにDaggerはCICDのための開発キッドでCICDのパイプラインをCUEと呼ばれるようなGoogleで開発されて宣言的な言語で記述します。また特徴的なのが一度パイプラインを記述するとおおよそメジャーなCI環境で動かすことができます。そのため、CIのロックインを減らすことができます。また、ローカルマシーンで記述したパイプラインのテストやデバックを行うこともできます。
今回は他CIとのインテグレーションの機能は試しません。そちらについて知りたい人はこちらをご確認ください。
動かしてみる
動作環境
$ uname -srvmpio Linux 5.14.0-1033-oem #36-Ubuntu SMP Mon Apr 4 15:15:49 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux $ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 20.04.4 LTS Release: 20.04 Codename: focal $ docker version Client: Docker Engine - Community Version: 20.10.14 API version: 1.41 Go version: go1.16.15 Git commit: a224086 Built: Thu Mar 24 01:48:02 2022 OS/Arch: linux/amd64 Context: default Experimental: true Server: Docker Engine - Community Engine: Version: 20.10.14 API version: 1.41 (minimum version 1.12) Go version: go1.16.15 Git commit: 87a90dc Built: Thu Mar 24 01:45:53 2022 OS/Arch: linux/amd64 Experimental: false containerd: Version: 1.5.11 GitCommit: 3df54a852345ae127d1fa3092b95168e4a88e2f8 runc: Version: 1.0.3 GitCommit: v1.0.3-0-gf46b6ba docker-init: Version: 0.19.0 GitCommit: de40ad0
インストール
インストールは以下のコマンドで行います。
$ cd /usr/local $ curl -L https://dl.dagger.io/dagger/install.sh | sudo sh $ dagger version dagger 0.2.11 (0088c621) linux/amd64
これでDaggerのインストールは完了しました。
もし、Daggerを/user/local
以外の場所にインストールしたい場合は、単純に自分がインストールしたいロケーションにcd
してください。
また、特定のバージョンをインストールしたい場合やLinux以外でのインストールを行いたい場合はこちらを確認してください。
コアコンセプト
Daggerのパイプラインははアクションと呼ばれるベーシックなブロックから構成されます。アクションは複雑な自動化の部分をカプセル化し抽象化することでシンプルなソフトウェアコンポーネントとして動作し、安全にシェアすることができます。
Actionはdagger do
で実行することもできますし、もっと複雑なコンポーネントから実行することともできます。
Actionにはcore action
とcomposite action
と呼ばれる2種類のActionが存在します。
core action
はプリミティブにDaggerエンジンに実装されているActionです。よりハイレベルなActionから利用されます。このActionを利用する場合はdagger.io/dagger/core
パッケージをインストールする必要があります。core action
のリファレンスはこちらを参照ください。
composite action
は他のActionから構成されるActionです。このActionはcore
、composite
両方のActionから構成されることがあります。
Actionのライフサイクル
composite action
の以下のような4つのライフサイクルで実行されます。
- Definition
- Integration
- Discovery
- Execution
Definition
ActionはCUE Definitionと呼ばれるテンプレートで記述されます。
DefinitionはActionのインプットとアウトプット、サブアクションを定義します。
package main import ( "dagger.io/dagger" "dagger.io/dagger/core" ) // Write a greeting to a file, and add it to a directory #AddHello: { // The input directory dir: dagger.#FS // The name of the person to greet name: string | *"world" write: core.#WriteFile & { input: dir path: "hello-\(name).txt" contents: "hello, \(name)!" } // The directory with greeting message added result: write.output }
上記のサンプルはcore.#WriteFile
と呼ばれるサブアクションを含んでいます。1つのActionは複数のサブアクションを組み込むことができます。
input
はその名と通りインフットで、Integration
のタイミングで値が決定します。外部からの入力を受け取ることができます。上記の例ではdir
やname
がインプットにあたります。
output
は逆に値を生成します。その値はIntegration
のタイミングで他のActionなどから参照することができます。上記の例ではresult
がアウトプットにあたります。
dir
やresult
などのフィールドの名前に成約はありません。
Integration
Action definitionは直接実行できず、plan
に統合される必要があります。
plan
は実行コンテキストで、いかのようなことが定義します。
- エンドユーザに提供されるAction
- それらのタスクの依存関係
- タスクと client システム(ローカルマシンとのインテグレーション)の相互作用
Actionの各CUEファイルはplan
の CUR definitionにマージされ統合されます。
以下にplan
の definition例を示します。
package main import ( "dagger.io/dagger" ) dagger.#Plan & { // Say hello by writing to a file actions: hello: #AddHello & { dir: client.filesystem.".".read.contents } client: filesystem: ".": { read: contents: dagger.#FS write: contents: actions.hello.result } }
上記の例では@AddHello
が直接plan
に統合されており、そして、core.#WriteFile
は間接的に統合されています。
Planの詳細に関しては、後述します。
Discovery
plan
に統合されたActionはエンドユーザから利用することができます。
(ここではまだ下記のコマンドは実行できませんが、後ほど実行してみます)
$ dagger do --help Usage: dagger do [flags] Options Available Actions: hello Say hello by writing to a file (省略)
plan
にとうごうされたhello action
がDagger側から認識されます。
Execution
daggerから認識されたactionは、以下のように実行できます。
(ここではまだ下記のコマンドは実行できませんが、後ほど実行してみます)
$ dagger do hello
Hello WorldのActionを動かしてみる
コアコンセプトで利用したHello Worldのdefinitionを実行してみたいと思います。 まずはプロジェクトを初期化する必要があります。
$ mkdir helloworld && cd helloworld $ dagger project init Project initialized! To install dagger packages, run `dagger project update`
次にdagger packageをインストールするためにdagger project update
コマンドを実行します。
$ dagger project update 10:21PM INF system | installing all packages... 10:21PM INF system | installed/updated package dagger.io@0.2.11 10:21PM INF system | installed/updated package universe.dagger.io@0.2.11
ここまでで準備が完了です。
先程のコアコンセプトで利用したActionとplan
のDefinitionをそれぞれhello.cue
とplan.cue
という名前で保存します。
するとDagger側からAction
を認識できるようになります。
d$ dagger do --help Usage: dagger do [flags] Options Available Actions: hello Say hello by writing to a file (省略)
実際にActionを実行してみます。
$ dagger do hello 10:35PM INF upgrading buildkit have host network=true version=v0.10.3 [✔] client.filesystem.".".read 0.1s [✔] actions.hello.write 0.0s [✔] client.filesystem.".".write 0.0s
すると、hello-world.txt
というファイルが作成されます。
$ ls cue.mod hello-world.txt hello.cue plan.cue $ cat hello-world.txt hello, world!
次はplan
を少しだけ書き換えて、greetする人を変えてみます。
plan.cue
を以下のように変更します。
package main import ( "dagger.io/dagger" ) dagger.#Plan & { // Say hello by writing to a file actions: hello: #AddHello & { dir: client.filesystem.".".read.contents //ここを追加 name: "henoheno" } client: filesystem: ".": { read: contents: dagger.#FS write: contents: actions.hello.result } }
再度Actionを実行してみます。
$ dagger do hello [✔] client.filesystem.".".read 0.1s [✔] actions.hello.write 0.0s [✔] client.filesystem.".".write 0.0s $ ls cue.mod hello-henoheno.txt hello-world.txt hello.cue plan.cue $ cat hello-henoheno.txt hello, henoheno!
Kong Admin API GUIのKongaを試す
はじめに
Kongの諸々の管理する場合、Admin APIを使うのが1つの手かと思うのですが、いかんせんヒューマンフレンドリーでは無いと言うような課題を感じることがあります。
Technology RadarでKongaというKongのAdmin API GUIを見つけたのでちょっと試して見ようかと思います。
ローカルで動かしてみて(Dockerで動かす想定)、GUIからためせそうな機能をひと通り見てみるぐらいをやってみようかと思います。Dockerなどの設定値はとりあえず動かすことを目的として設定しています。本番などで利用する際は値を適切なものに変えてください。SSLを使ってなかったりするのでセキュリティ的に問題が起こることがありえます。
そもそもKongに付いては過去にまとめてるのでよかったらこちら(※情報がちょっと古いかも知れませんが、基本はおそらくそこまで変わってないと思います) も見てみてください。
Kongaとは
前述していますが、KongaはKongを管理するためのGUIです。
GitHubによると以下のような機能を提供しているようです。
- すべてのAdmin APIオブジェクトの管理
- Consumer設定のリモートデータソースからのインポート(Database, files, APIsなど)
- Kongの複数ノードの管理
- スナップショット機能を使ったKong Nodeのバックアップとリストア、マイグレート機能
- ノードのヘルスチェック、モニター機能
- Email、Slackの通知機能
- 複数ユーザの管理
- データベースとの連携機能(MySQL, postgresSQL, MongoDB)
GitHubによるとKongaは公式のアプリではないようです。
動かしてみる
環境
動作環境は以下の通り
$ uname -srvmpio Linux 5.4.0-100-generic #113-Ubuntu SMP Thu Feb 3 18:43:29 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux $ lsb_release -a LSB Version: core-11.1.0ubuntu2-noarch:security-11.1.0ubuntu2-noarch Distributor ID: Ubuntu Description: Ubuntu 20.04.4 LTS Release: 20.04 Codename: focal $ docker version Client: Docker Engine - Community Version: 20.10.12 API version: 1.41 Go version: go1.16.12 Git commit: e91ed57 Built: Mon Dec 13 11:45:33 2021 OS/Arch: linux/amd64 Context: default Experimental: true Server: Docker Engine - Community Engine: Version: 20.10.12 API version: 1.41 (minimum version 1.12) Go version: go1.16.12 Git commit: 459d0df Built: Mon Dec 13 11:43:42 2021 OS/Arch: linux/amd64 Experimental: true containerd: Version: 1.4.12 GitCommit: 7b11cfaabd73bb80907dd23182b9347b4245eb5d runc: Version: 1.0.2 GitCommit: v1.0.2-0-g52b36a2 docker-init: Version: 0.19.0 GitCommit: de40ad0
諸々動かしてみる
Kongの起動
先ずはKongを動かしてみます。前述していますが、今回、KongとKongaはDokcerで動かそうと思います。また、DB Lessモードで動かそうと思います。Kongの動かし方は以前書いたブログをそのままやりますので深くは触れません。
まず、以下のようなKongの設定ファイルを用意します。
kong.yml
_format_version: "1.1" services: - name: my-service url: https://example.com plugins: - name: key-auth routes: - name: my-route paths: - / consumers: - username: my-user keyauth_credentials: - key: my-key
次にKongをDockerで起動します。
docker run -d --name kong \ --network=host \ -v "/$PATH_TO_WORK_DIR/kong.yml:/usr/local/kong/declarative/kong.yml" \ -e "KONG_DATABASE=off" \ -e "KONG_DECLARATIVE_CONFIG=/usr/local/kong/declarative/kong.yml" \ -e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \ -e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \ -e "KONG_PROXY_ERROR_LOG=/dev/stderr" \ -e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \ -e "KONG_ADMIN_LISTEN=0.0.0.0:8001" \ kong:2.7.1-alpine $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 495804f277d4 kong:2.7.1-alpine "/docker-entrypoint.…" 2 seconds ago Up 1 second (health: starting) kong
Kongaの起動
こちらもDockerを使います。
基本的にはGitHubのREADMEを参考にしながら動かしています。
docker run -d --network=host \ --name konga \ -e "TOKEN_SECRET=$RANDOM_STRINGS" \ -e "NO_AUTH=true" \ pantsel/konga:0.14.9
設定の環境変数は最小限にしています。 上記のものと他に環境変数は以下のようなものがあります。 ここでは主要なものをまとめますSSLに関するものもあったりするので全量はこちらをご確認ください。
環境変数名 | 説明 | デフォルト値 |
---|---|---|
HOST | kongaにバインドするホスト名 | '0.0.0.0' |
PORT | kongaにバインドするポート番号 | 1337 |
NODE_ENV | ノードの環境。production or development |
development |
KONGA_HOOK_TIMEOUT | Kongaがタスクをスタートアップする際のタイムアウト値(ms) | 60000ms |
DB_ADAPTER | Kongaが使うDBの種類。mongo 、mysql 、postgres 。設定されていない場合はローカルディスクのDBが利用される |
- |
DB_URI | DBのフルURIの設定。DB_ADAPTERによって変わる | - |
DB_HOST | DB_URIが設定されていない場合。DBのホスト名を設定する | localhost |
DB_PORT | DB_URIが設定されていない場合。DBのポート番号 | - |
DB_USER | DB_URIが設定されていない場合。DBのユーザ名 | - |
DB_PASSWORD | DB_URIが設定されていない場合。DBのパスワード | |
DB_PG_SCHEMA | Postgresを利用している場合のスキーマ | public |
KONGA_LOG_LEVEL | Kongaのログレベル。silly 、debug 、info 、warn 、error |
development : debug 、production : warn |
TOKEN_SECRET | KongaがJWTをサインする際に利用される文字列。ランダムな文字列を設定する | - |
NO_AUTH | Kongaの認証を行うか否か。true or false |
false |
BASE_URL | Kongaの相対パス | - |
KONGA_SEED_USER_DATA_SOURCE_FILE | 初回起動時のデフォルトユーザ設定。詳細はこちらを確認ください | - |
KONGA_SEED_KONG_NODE_DATA_SOURCE_FILE | KongのAdminAPIに | |
KONGA_SEED_KONG_NODE_DATA_SOURCE_FILE | 初回起動時に複数のKongノードにつなぎたい場合のAdmin APIの設定。詳細はこちらをご確認ください | - |
今回はなるべく複雑度を下げるため、NO_AUTH
をtrue
にしてTOKEN_SECRET
だけ設定しています。
Kongaにアクセスして初回設定を行う
今回は、Dockerのネットワーク設定をホストにしていてかつデフォルトで起動しているのでhttp://localhost:1337/にブラウザーからアクセスすることで以下のようなKongaの画面を確認できます。
まずは、Kong Admin APIの設定を行う必要があるみたいですね。
以下の設定値を設定してします。
- Name: konga-demo
- Kong Admin URL: http://localhost:8001
設定が正しく完了すると下のようなダッシュボードが開きます。
起動したKongaで何ができそうかざっくり見ていく
Kongaが立ち上がったところで、GUIからどういったことができそうか簡単に見ていきます。
先ずは先程の設定後に表示されたダッシュボードですが、こちらではノードの情報やKongに来ているリクエストの数等を確認することができます。
次にInfo
タブではより詳細なノードの情報を確認することができそうです。
SERVICE
タブではKongのService Entityの管理ができそうです。
同様にROUTES
タブではRoutes Entityの管理、CONSUMERS
タブではConsumer Entityの管理が行えます。
PLUGINS
タブではPluginの追加や削除が行えるようです。
ざっとみ、プラグインはKong Plugin Hubからいい感じに取得してきてくれてそうですね。
ざっくりとはこんな感じでしょうか、今回はとりあえず動かすことができたのでスナップショットなどは一旦触らずにおきます。
GoのロギングライブラリZapを試す
はじめに
GoのLogライブラリは標準のものを使うことが多かったのですが、構造化したりするのがちょっと大変だったので、より高機能で使い勝手がよいものを試してみたくなってZapというのをどこかで見かけたのを思い出して、ちょっと試してみようと思います。
ここでは、コアな設定などは行わず先ずはプリセットの設定などを利用して、Quick Startを参考にしたコードを動かし足がかりにしながら、少しだけ深堀りして学んで行きたいと思います。
Zapとは
タイトルの通り、Goのロギングライブラリです。GitHubを見ているかぎり速さを売りにしているみたいです。1.x系では破壊的変更が行われないことが明記されています(おそらく普通にセマンティクスバージョニングだと思います)。また、サポートに関しては過去2つ分のマイナーバージョンまでサポートがされているみたいです。
ログのスタイルとしては構造化されているスタイルと単にprintfスタイルのAPIが提供されているみたいです。
動かしてみる
環境
今回の動作環境はこちら
$ uname -srvmpio Linux 5.4.0-100-generic #113-Ubuntu SMP Thu Feb 3 18:43:29 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux $ lsb_release -a LSB Version: core-11.1.0ubuntu2-noarch:security-11.1.0ubuntu2-noarch Distributor ID: Ubuntu Description: Ubuntu 20.04.3 LTS Release: 20.04 Codename: focal $ go version go version go1.17.7 linux/amd64
プロジェクトの初期化&Zapのインストール
先ずはプロジェクトの初期化からします。
$ mkdir zap-example && cd zap-example $ go mod init zap-exampe
次にプロジェクトにZapのインストールを行います。
$ go get -u go.uber.org/zap go get: added go.uber.org/atomic v1.9.0 go get: added go.uber.org/multierr v1.7.0 go get: added go.uber.org/zap v1.21.0
これで下準備は完了です。
何はともあれログを出力してみる
ひとまずGitHubのQuick Startを参考にログを出力してみます。
以下のコードを記述します。
package main import ( "go.uber.org/zap" "time" ) func main() { logger, _ := zap.NewProduction() defer logger.Sync() sugar := logger.Sugar() sugar.Infow("This is my first Log with Zap", "int num", 3, "Time", time.Now(), "String", "Hello, Zap", ) }
もろもろ気になることはありますが、実行してみます。
$ go run main.go {"level":"info","ts":1645328356.9622054,"caller":"zap-example/main.go:14","msg":"This is my first Log with Zap","int num":3,"Time":1645328356.9622047,"String":"Hello, Zap"}
いい感じに出力されました。
ちょっとコードを詳細に見ていきます。
ZapのLoggerプリセット
まず、Zapを使うためプリセットの設定がいくつ用意されています。上記のコードではzap.NewProduction()
などで設定済みのロガーを取得することができます。
このプリセットはfunc NewDevelopment、func NewProduction、func NewExample、func NewNopが用意されておりそれぞれログの出力レベルと出力の形式が異なっています。
すでに出力していますがNewProductionを利用した場合は、ログがJson形式で出力されよりツールで利用されることを想定したログが出力されます。また、ここでのログレベルはInfoレベルになり標準エラーに出力されるようです。
これは、NewProductionConfig().Build(...Option)
のショートカットとなっているようです。
次にNewDevelopmentを利用した場合以下のような出力になります。
$ go run main.go 2022-02-20T12:49:50.559+0900 INFO zap-example/main.go:14 This is my first Log with Zap {"int num": 3, "Time": "2022-02-20T12:49:50.559+0900", "String": "Hello, Zap"}
ログ出力はよりヒューマンリーダブルな形で出力され、更にDebugレベルでのログ出力になります。 こちらも標準エラーにログが出力されます。
NewExampleを利用した場合は以下のような出力になります。
$ go run main.go {"level":"info","msg":"This is my first Log with Zap","int num":3,"Time":"2022-02-20T13:05:35.718+0900","String":"Hello, Zap"}
こちらはZapをちょっと試したい時ときに利用されるもののようです。諸々が事前にわかりきっている(ドキュメントには決定論的?と書かれていました)環境で利用されるもので、時間などが省略されたより短いログ出力になります。こちらも出力レベルはDebugになるようです。
最後に、NewNopを利用した場合の出力です。
$ go run main.go
こちらはログ出力を全く行わないロガーで、ユーザが定義したHooksなども実行されません。
こちらは、WithOptions
関数などで、Coreやエラーアウトプットを置き換えることでログ出力を行うことが可能になるようです。
(おそらく、性能などの関心で全くログ出力を行わない場合やマニュアルでログを設定したい場合に利用されるものだと個人的に理解しています)
defer logger.Sync()
ロガーを取得した後のdefer logger.Sync()
ですが、これはアプリケーション終了時にすべてのログを出力してくれるようです。
Sugar
そして、個人的に最も気になったsugar := logger.Sugar()
このSugarはZapのLoggerラッパーでLoggerを生で使うよりはほんの少し遅いもののより人間が使いやすい形に設計されているようです。後述しますが、suger.Infow()
などの関数はSugarの方で提供されているみたいです。
このブログでは先ずはSugarを使って、ログ出力するところにフォーカスして進めます。
ログ出力
ログ出力の部分です。
sugar.Infow("This is my first Log with Zap", "int num", 3, "Time", time.Now(), "String", "Hello, Zap", )
前述の通りZapは構造化されたログの形式とprintfスタイルのログ出力両方をサポートしています。
上記の用に〇〇w()
関数(func (*SugaredLogger) Infow 、func (*SugaredLogger) Debugw、func (*SugaredLogger) Errorw、func (*SugaredLogger) Fatalw等)というようなw
が着く関数は第一引数にメッセージを受け取り第二引数以降はkey-valueの形で構造化されるログを受け取ります。
printfスタイルのログを出力する場合は関数の後にf
が着く以下のような関数を利用することで行えます。
sugar.Infof("Hello, %s", "World")
出力結果
{"level":"info","ts":1645332593.3569844,"caller":"zap-example/main.go:20","msg":"Hello, World"}
構造化されたスタイルのログ出力を行う場合でマイクロ秒レベルのパフォーマンスが求められる場合は以下のように事前に型を指定してkey-valueでわたしてやることでより低コストでログ出力を行えるようです。
sugar.Infow("This is my first Log with Zap", zap.Int("int num", 3), zap.Time("Time", time.Now()), zap.String("String", "Hello, Zap"), )
Sealed Secretsでk8s上で利用する機密情報をGitなどでセキュアに管理する
はじめに
k8sには、Secretという機密情報を扱うリソースがありますが、こいつは基本情報をBase64エンコードしたもので扱われます。例えばSecretのリソース定義ファイルをGitで管理したいとなった場合定義ファイルに書かれる機密情報はただBase64されてるだけなのでそのままでは管理できない(やりにくい)という問題があります。
こういった問題にたいして、様々な対象方法はあるかと思いますが、その中の1つであるSealed Secretsを試してみたいと思います。
Sealed Secretsとは
Sealed SecretsのGitHubには以下のように書かれています。
Problem: "I can manage all my K8s config in git, except Secrets."
Solution: Encrypt your Secret into a SealedSecret, which is safe to store - even to a public repository. The SealedSecret can be decrypted only by the controller running in the target cluster and nobody else (not even the original author) is able to obtain the original Secret from the SealedSecret.
わかりやすいProblemですね。前述の通りSecretではBase64しただけの機密情報を扱うことになるので、Gitでの管理がなかなか難しくなってきます。それに対して、Sealed Secretでは機密情報を暗号化し、その複合ができるのはクラスターで動くコントローラーのみとなります。
機密情報は暗号化されるため、パブリックのGitリポジトリにPushすることも可能になります。
Sealed Secretsは以下の2つのパートから成り立ちます。
- クラスターサイド: controller / operator
- クライアントサイドユーティリティ: kubeseal
kubeseal
は公開鍵暗号方式で情報を暗号化し、その複合を行えるのはコントローラーだけとなります。
SealedSecretとSecretは、完全に同じではありませんがDeploymentとPodの関係に似ていると説明されています。
kubeseal
は公開鍵をk8sのAPIサーバーから取得し、情報を暗号化します。kubeseal --cert mycert.pem
のようにして、Pemファイルを直接指定してオフラインでの暗号化も可能なようです。この場合は、kubeseal --fetch-cert >mycert.pem
でPemファイルを取得できるようです。鍵はコントローラーの起動時にログにも出力されるようです。
動かしてみる
環境
今回のk8sクラスターはminikubeを用いて作成します。
$ uname -srvmpio Linux 5.4.0-99-generic #112-Ubuntu SMP Thu Feb 3 13:50:55 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux $ lsb_release -a LSB Version: core-11.1.0ubuntu2-noarch:security-11.1.0ubuntu2-noarch Distributor ID: Ubuntu Description: Ubuntu 20.04.3 LTS Release: 20.04 Codename: focal $ docker version Client: Docker Engine - Community Version: 20.10.12 API version: 1.41 Go version: go1.16.12 Git commit: e91ed57 Built: Mon Dec 13 11:45:33 2021 OS/Arch: linux/amd64 Context: default Experimental: true Server: Docker Engine - Community Engine: Version: 20.10.12 API version: 1.41 (minimum version 1.12) Go version: go1.16.12 Git commit: 459d0df Built: Mon Dec 13 11:43:42 2021 OS/Arch: linux/amd64 Experimental: true containerd: Version: 1.4.12 GitCommit: 7b11cfaabd73bb80907dd23182b9347b4245eb5d runc: Version: 1.0.2 GitCommit: v1.0.2-0-g52b36a2 docker-init: Version: 0.19.0 GitCommit: de40ad0 $ go version go version go1.17.6 linux/amd64 $ minikube version minikube version: v1.25.1 commit: 3e64b11ed75e56e4898ea85f96b2e4af0301f43d $ helm version version.BuildInfo{Version:"v3.8.0", GitCommit:"d14138609b01886f544b2025f5000351c9eb092e", GitTreeState:"clean", GoVersion:"go1.17.5"} $ kubectl version -o yaml clientVersion: buildDate: "2021-12-16T11:41:01Z" compiler: gc gitCommit: 86ec240af8cbd1b60bcc4c03c20da9b98005b92e gitTreeState: clean gitVersion: v1.23.1 goVersion: go1.17.5 major: "1" minor: "23" platform: linux/amd64 serverVersion: buildDate: "2021-12-16T11:34:54Z" compiler: gc gitCommit: 86ec240af8cbd1b60bcc4c03c20da9b98005b92e gitTreeState: clean gitVersion: v1.23.1 goVersion: go1.17.5 major: "1" minor: "23" platform: linux/amd64
今後実行するコマンドは、特に指定がない場合はminikubeのコンテキストを指しています。
インストール
インストールはおおきく以下の2つのことを行う必要があります。
- Sealed Secretsのクラスターへのデプロイ
- kubesealedのインストール
先ずは、Sealed Secretsのクラスターへのデプロイを行います。
READMEのInstallationによるとこれを行うには以下の3つの方法が提供されているようです。
- Kustomize
- Helm Chart
- Operator Framework
GKEなどでセットアップする場合はこちらを確認してください。
今回はHelm Chartを使ったデプロイの方法を試してみたいと思います。Sealed SecretsのHelm ChartはGitHubの公式レポジトリでホスティングされています。以下のコマンドを実行し、リポジトリの追加を行います。
$ helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets "sealed-secrets" has been added to your repositories
ここで少し注意が必要なのは、バージョニングのスキーマで、このHelm Chartはもともとはコミュニティで作成されていたもので、メジャーバージョンが1.x.y
で始まっています。しかし、Sealed SecretsのVersionhaまた0
なので、以下のような対応関係を持ちます。
- Sealed SecretsのコントローラーのVersion: 0.X.Y
- Helm ChatのVersion: 1.X.Y-rZ
と、思いましたが....どうもChartの方は2系がすでに出ているみたいですね...しかもバージョニングスキーマも説明されるものとは少し違うようです。
$ helm search hub sealed-secrets URL CHART VERSION APP VERSION DESCRIPTION https://artifacthub.io/packages/helm/bitnami-la... 2.1.2 v0.17.3 Helm chart for the sealed-secrets controller. https://artifacthub.io/packages/helm/wenerme/se... 2.1.2 v0.17.3 Helm chart for the sealed-secrets controller. https://artifacthub.io/packages/helm/openinfrad... 1.16.1 v0.16.0 Helm chart for the sealed-secrets controller. https://artifacthub.io/packages/helm/cloudnativ... 1.0.2 0.7.0 A Helm chart for Sealed Secrets https://artifacthub.io/packages/helm/redhat-cop... 1.10.2 0.12.1 A Helm chart for Sealed Secrets
この辺は、もしかしたらドキュメントが少し古くなってるのかも知れません。ひとまずSealedSecretsのバージョンとHelm Chartのバージョンは完璧には対応づかないようなので注意が必要です。
今回は最新のChartを使ってデプロイを行いたいと思います。デプロイはこちらを参考に以下のコマンドを実行します。
$ helm install --namespace kube-system my-release sealed-secrets/sealed-secrets NAME: my-release LAST DEPLOYED: Sat Feb 12 10:26:21 2022 NAMESPACE: kube-system STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: (省略)
これでデプロイは完了です。my-release
のところは任意のものを入れます。もしクリーンアップしたい場合はhelm --namespace kube-system delete my-release
のコマンドで実行できます。
kubectl
コマンドで諸々がデプロイされていることを確認します。
$ kubectl -n kube-system get all NAME READY STATUS RESTARTS AGE pod/coredns-64897985d-qfl8f 1/1 Running 0 34m pod/etcd-minikube 1/1 Running 1 34m pod/kube-apiserver-minikube 1/1 Running 1 34m pod/kube-controller-manager-minikube 1/1 Running 1 34m pod/kube-proxy-vlq5q 1/1 Running 0 34m pod/kube-scheduler-minikube 1/1 Running 1 34m pod/my-release-sealed-secrets-559446f98f-52szw 1/1 Running 0 83s pod/storage-provisioner 1/1 Running 1 (33m ago) 34m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 34m service/my-release-sealed-secrets ClusterIP 10.101.162.188 <none> 8080/TCP 83s NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE daemonset.apps/kube-proxy 1 1 1 1 1 kubernetes.io/os=linux 34m NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/coredns 1/1 1 1 34m deployment.apps/my-release-sealed-secrets 1/1 1 1 83s NAME DESIRED CURRENT READY AGE replicaset.apps/coredns-64897985d 1 1 1 34m replicaset.apps/my-release-sealed-secrets-559446f98f 1 1 1 83s
これでデプロイまでは完了しました。
次にクライアント側のインストールです。
READMEにとると、kubeseal
をインストールするには以下のようなやり方が用意されているようです。
- Homebrew
- MacPorts
- Installation from source
私の環境はLinuxなので最後のInstallation from source
の方法を試そうかと思ったのですが、ちょっとうまく行かなかったのでバイナリを直接ダウンロードしてパスを通そうと思います。
現状の最新である0.17.3のリリースページを確認し、以下のコマンドでインストールします。
$ wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.17.3/kubeseal-0.17.3-linux-amd64.tar.gz $ sudo mkdir /opt/kubeseal $ sudo tar -zxvf kubeseal-0.17.3-linux-amd64.tar.gz -C /opt/kubeseal
あとはええ感じにパスを通して、OKです。
$ kubeseal --version kubeseal version: 0.17.3
kubesealで作ったSecretsを暗号化する
kubesealでSecretsを暗号化します。
今回はAPIサーバ経由で鍵を取得して、暗号化する方法を試したいと思います。もし、この方法が使えない場合は以下のようなコマンドを実行してPemファイルを取得します。
$ kubeseal --fetch-cert \ --controller-name=my-release-sealed-secrets \ --controller-namespace=kube-system \ > pub-cert.pem
少し脇道にそれましたが、話を戻します。
適当なSecretsを作って、そのJsonファイルを出力します。今回はfoo=bar
というSecretsを作成します。
$ echo -n bar | kubectl create secret generic mysecret --dry-run=client --from-file=foo=/dev/stdin -o json >mysecret.json
次にそのSecretsのJsonを暗号化します。
$ kubeseal --controller-name=my-release-sealed-secrets --controller-namespace=kube-system <mysecret.json >mysealedsecret.json
ここでのポイントはコントローラーの名前(今回の場合はDeploymentの名前で行けました)とネームスペースをきちんと指定してやることです。
出力されたファイルは暗号化されたデータとなっているので、Gitでも管理できます(Twitterに上げることもできるよ!って書いてあった)。
$ cat mysealedsecret.json { "kind": "SealedSecret", "apiVersion": "bitnami.com/v1alpha1", "metadata": { "name": "mysecret", "namespace": "default", "creationTimestamp": null }, "spec": { "template": { "metadata": { "name": "mysecret", "namespace": "default", "creationTimestamp": null }, "data": null }, "encryptedData": { "foo": "AgCHg3oahao+sLw2gPlH+9SaxWlYdG06/M5CZAOZ2hOHXZ9deWFF/bMpt+YRlThK5c1mNIj0rf/NsVxWzWL3N4/LFeNFYmJ/orjSYln3Qu4+2F03kKH30kcz23X8CeTQjpRoIYYsy6S0bLMn+Svs5EB669K/n+nEWNjXb5BmO3438GamQ5jodLlcv5zjZLjEpwrqb31HTs44r3NKhzp7sJZ5DaU5Q28r9IkGQneDDi6Y4dRwF/Kp80uiA9DGXRPcG39l0xfsljdEdwTF9NucazQEZ14eI3VUQD/ofQ2gdpkAUaOKW4nr9pHsQk+KErifBGZQOtvaCxqp2NoFwAG9lXcW0PntzR3m0VY4bqYCslr+Ma5D0kyuYojsOZHWJEmVPnrBu18sSzgdQSHgK5lj85hHgWzMWTB9LCifzyjUMzpHpOkVqvf0RmWAVTIGC5KrT4lbN/w0rCMW6mKfcszYUtUshVZYgaTcTMi+MPNAmSknDUVU2owOIzYTMzyz7iGXaM8zhS3q95h98rJiXiVzJIxLKxxbbmSI6bNuTYalstwXLqx3V5kZqnfLNAfWIS5+Hz2FSjrRlXyZCrCnTKrbij4Q+G97URlKTgekjOKyMfT7XVUpIr6R7dxMwTVDBXveSuvXwqPDsNsyHdF6aSPdUHpkCpAdzCxJLN0F6rzbNbZO4RJmA6kP2PVG90vySur/5sBuMIc=" } } }
できたSealed Secretsをクラスターにデプロイします。
$ kubectl create -f mysealedsecret.json sealedsecret.bitnami.com/mysecret created
これで完了です。デプロイされたSealed Secretsはコントローラーによって複合されてSecretsが作成されます。
$ kubectl describe secret mysecret Name: mysecret Namespace: default Labels: <none> Annotations: <none> Type: Opaque Data ==== foo: 3 bytes
後は、普通にSecretsを使う要領で利用できます。
Flixをインストールしてテストを実行する
はじめに
最近所属する会社内でFlixというJVM系の言語が新しく出たみたいなのを見かけて面白そうだったので少し触ってみたいと思います。 色々深堀りをできそうなポイントはあるのですが一旦深く突っ込まず個々ではインストールして動かすことと、簡単に概要を把握することに努めます。
Flixとは
オーフス大学とウォータールー大学が主体となって開発されている言語で、様々な言語からインスパイヤーされています。
公式サイトには以下のように記述されています。
Flix is inspired by OCaml and Haskell with ideas from Rust and Scala. Flix looks like Scala, but its type system is based on Hindley-Milner. Two unique features of Flix are its polymorphic effect system and its support for first-class Datalog constraints. Flix compiles to efficient JVM bytecode, runs on the Java Virtual Machine, and supports full tail call elimination.
FlixはOCamlやHaskellの影響を受けており、RustやSlacaのアイディアなども取り入れているみたいですね。見た目はScalaですが、型システムはHindley-Milner
型らしいです。
Hindley-Milnerは型推論のアルゴリズムの一種であるようですが、ここではあまり主題ではないため深堀はしません。
その他にも公式サイトのWhy
によればGoのチャンネル通信ベースの非同期やElmのようなextensible records、あとはユニークな機能としてpolymorphic effect system、purity polymorphic functions、first-class Datalog constraints(こいつらについても別のブログでまとめようかとは思いますが、ここでは深くふれません)というものがあるらしいです。
JVM系の言語なのでJavaのバイトコードにコンパイルされJVM上で動きます。
動かしてみる
動作環境
動作環境は以下とおりです。
$ uname -srvmpio Linux 5.4.0-91-generic #102-Ubuntu SMP Fri Nov 5 16:31:28 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux $ lsb_release -a LSB Version: core-11.1.0ubuntu2-noarch:security-11.1.0ubuntu2-noarch Distributor ID: Ubuntu Description: Ubuntu 20.04.3 LTS Release: 20.04 Codename: focal $ java --version openjdk 17 2021-09-14 OpenJDK Runtime Environment (build 17+35-2724) OpenJDK 64-Bit Server VM (build 17+35-2724, mixed mode, sharing)
Flixのバージョンなどはインストール時に明記します。
インストール
とりあえずFlixを気軽に動かしてみるには、プレイグラウンドを使って見る方法が一番簡単にできると思います。
また、VS Codoのプラグインも用意されているようでそちらをインストールするのが現段階ではコーディングをゴリゴリやっていく上では良い方法かと思います。
ただ、このブログでは、Flixのjarをダウンロードして実際に手動でテストの実行やコンパイルを行います。
flix.jarはGitHubのリリースページのところから入手できます。
今回は現状の最新である0.25.0をダウンロードしてきて、適当なディレクトリに配置します。
自分の場合は/opt/flix
ディレクトリを作成しそこにおきました。
ビルド&パッケージマネージャー
公式のBuild and Package Managementによると現状(2022/01/02)ではまだ、依存解決などの方法は提供されていないようですが、ビルドしてパッケージをシェアすること自体は可能なようです。
セントラルなパッケージのリポジトリなどはまだなくバージョンの管理や配布などはマニュアルで行わないと行けないようです。
バージョン管理などの方法については将来的には提供される予定のようです(foo-1.2.1.fpkgという名前のアーティファクト?に含まれる予定のようです)。
現状Flixは、/path/to/flix.jar <command>
のような実行方法でコンパイルやテストの実行
ここで、コマンドには以下のようなものがあります。
コマンド | 説明 |
---|---|
init | カレントディレクトリに新しいプロジェクトの作成 |
check | カレントプロジェクトにエラーがないかチェック |
build | カレントプロジェクトのコンパイル |
build-jar | カレントプロジェクトのjarファイルの作成 |
build-pkg | カレントプロジェクトのfpkg-fileの作成 |
run | カレントプロジェクトのメイン関数の実行 |
test | カレントプロジェクトのテストの実行 |
このブログでは一通りのコマンドは試そうと思います。
標準APIの一覧
Hello World
インストールと簡単な概要をまとめたところで、先ずはHello Worldを記述して実行してみます。
先ずは、プロジェクトを作成します。
$ mkdir hello-world && cd hello-world $ java -jar /opt/flix/flix.jar init $ tree . ├── HISTORY.md ├── LICENSE.md ├── README.md ├── build ├── lib ├── src │ └── Main.flix └── test └── TestMain.flix
必要なものができたみたいですね。
見た感じ、src/
にflixのコードを追加していって、test/
にテストコードを記述するみたいです。
すでにMain.flix
と言う名前のファイルが作成されHello Worldのプログラムが作成されています。
// The main entry point. def main(_args: Array[String]): Int32 & Impure = Console.printLine("Hello World!"); 0 // exit code
このPJを実行するとHello World
の文字列が標準出力に出力されます。
$ java -jar /opt/flix/flix.jar run Hello World! Main exited with status code 0.
テストの記述
プロジェクトを初期化するとすでに以下のようなテストがtest/TestMain.flix
に記述されています。
テストに関してはprinciplesのBuilt-in unit testsの項目で軽くふれられています。
Built-in unit tests
Flix supports unit tests as part of the language. We believe such integration avoids fragmentation of the ecosystem and ultimately leads to better tool support.
ライブラリーなどを使うのでは無くビルトインでテストの方法がサポートされているようですね。
@test def test01(): Bool = 1 + 1 == 2
1+1の実行結果をアサーションしているテストですね。
@test
を付けてBool型を返す関数を記述すれば良いようです。
テストに関して深く説明されたドキュメンテーションが見つからなかったのですが、おそらくMockなどの方法は現時点ではまだサポートされていないのだと思います。
上記のテストを実行すると以下のような結果が得られます。
$ java -jar /opt/flix/flix.jar test -- Tests -------------------------------------------------- root ✓ test01 Tests Passed! (Passed: 1 / 1)
テストを以下のように書き換えて失敗するようにしてみます。
@test def test01(): Bool = false
実行すると以下のような結果を得られます。
$ java -jar /opt/flix/flix.jar test -- Tests -------------------------------------------------- root ✗ test01: Returned false. (test/TestMain.flix:2:5) Tests Failed! (Passed: 0 / 1)
当たり前ですがテストは失敗しました。
それでは、独自の関数を1つ書いて、それをテストするコードを書いてみます。
関数は、人物名の文字列を受け取って、その文字列を返すだけの簡単なものを想定して作成します。
先ずはテストをTestMain.flix
以下のように追記します。
@test def testGreeting(): Bool = greeting("moheji") == "Hello!! moheji!!"
そして、空の実装の方もMain.flix
に作っておきます。
// The main entry point. def main(_args: Array[String]): Int32 & Impure = Console.printLine(greeting("moheji")); 0 // exit code def greeting(name: String): String = ???
この状態でテストを実行するとエラーになります。
$ java -jar /opt/flix/flix.jar test -- Tests -------------------------------------------------- root ✓ test01 ✗ testGreeting: Hole '?h26182' at src/Main.flix:6:38 (test/TestMain.flix:5:5) Tests Failed! (Passed: 1 / 2)
それでは実装の方を修正します。
// The main entry point. def main(_args: Array[String]): Int32 & Impure = Console.printLine(greeting("moheji")); 0 // exit code def greeting(name: String): String = "Hello!!" + " " + name + "!!"
この状態で、テストを実行すると今度は成功します。
$ java -jar /opt/flix/flix.jar test -- Tests -------------------------------------------------- root ✓ test01 ✓ testGreeting Tests Passed! (Passed: 2 / 2)
jarを作成する
flixコマンドを使えばプロジェクトをJarに固めることができます。
実行は以下の用にbuild-jar
コマンドで行います。
$ java -jar /opt/flix/flix.jar build-jar $ tree -L 1 . ├── HISTORY.md ├── LICENSE.md ├── README.md ├── build ├── hello-world.jar ├── lib ├── src ├── target └── test
コマンドを実行すると特にログ出力も無くプロジェクトルートにhello-world.jar
が作成されているのがわかります。
このjarを実行してみます。
$ java -jar hello-world.jar Hello!! moheji!!
正しく実行を行えているようですね。
Spring Boot 2.6.0についてメモ
はじめに
もうしばらく前ですがSpring Bootが11月9日に出てますね。
ちょっと仕事がバタバタしててモチベーションが出ず更新ができていなかったのですがまたゆっくりでも再開していきたいと思います。
このブログではすべての変更点を網羅するわけでは無く、個人的に気になったところをまとめてみたり使ってみたりしようと思います。
2.6.0のすべての変更に付いてはRelease Noteを確認してください。
ちなみに話題になっているLog4J2やLogbackなどの脆弱性の対応は12月23日に出る2.6.2(2.5系なら2.5.8)でライブラリのアップデートが入るみたいですね。
動かして確認してみる
環境
$ lsb_release -a LSB Version: core-11.1.0ubuntu2-noarch:security-11.1.0ubuntu2-noarch Distributor ID: Ubuntu Description: Ubuntu 20.04.3 LTS Release: 20.04 Codename: focal $ uname -srvmpio Linux 5.4.0-91-generic #102-Ubuntu SMP Fri Nov 5 16:31:28 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux $ ./mvnw -v Apache Maven 3.8.3 (ff8e977a158738155dc465c6a97ffaf31982d739) Maven home: /home/yuya-hirooka/.m2/wrapper/dists/apache-maven-3.8.3-bin/5a6n1u8or3307vo2u2jgmkhm0t/apache-maven-3.8.3 Java version: 17, vendor: Oracle Corporation, runtime: /home/yuya-hirooka/.sdkman/candidates/java/17-open Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "5.4.0-91-generic", arch: "amd64", family: "unix" $ java --version openjdk 17 2021-09-14 OpenJDK Runtime Environment (build 17+35-2724) OpenJDK 64-Bit Server VM (build 17+35-2724, mixed mode, sharing)
今回動作確認を行うアプリケーションはSpringを使って設定は以下のようにして作ります。
Bootのバージョンはこのブログを書いている段階での最新である2.6.1をを使おうと思います。
動作確認の関係でWebとActuatorの依存だけ入れてます。
大まかな変更点
2.6.0では大きく以下のような変更が行われいます。
- サーブレットアプリでのSameSite Cookie Attributeのサポート
- Actuator周りの変更
- WebTestClientを用いたMVCアプリケーションのテストサポート
- Recordクラスで@ConfigurationPropertiesを使う際に@ConstructorBindingをつける必要がなくなった
- Docker Imageビルド周りのサポートの追加
EOL
Spring Boot のEOLはこちらの公式のサイトにまとめられています。
それに寄ると2.6.x系のサポートは2023年5月18日に完全にサポートが終わるようですね。
まとめていく
サーブレットアプリでのSameSite Cookie Attributeのサポート
Servletのアプリケーションに置いて、SameSiteの設定をserver.servlet.session.cookie.same-site
プロパティを通して設定することが可能になったようです。
これはTomcat、Jetty、Undertowなどのサーバーで利用可能です。
Actuator周りの変更
メインポートや管理ポートに追加のパスを指定することができるようになった
ActuatorのHealth Groupの機能を使えば複数のヘルスインディケーターをグルーピングして公開したりすることができます。別のポートヘルスチェックのエンドポイントを公開するとその信頼性が下がってしまう場合があります。そのような場合に置いて追加パスを指定して管理用のポートではなくサーバのポートでヘルスチェックのエンドポイントを公開することができるようになりました。
例えば以下のような設定でグループを作ります。
application.properties
management.server.port=9090 management.endpoint.health.group.moheji.include=ping
ここでmeheji
の部分は任意の文字列でつけるグループ名になります。
このヘルスグループにアクセスする際には以下のようにリクエストを送ります。
$ curl http://localhost:9090/actuator/health/moheji {"status":"UP"}
これに対して/actuator/health/moheji
などのPathでは無く追加のアクセス用のPathを作ることができます。
メインポートと管理用ポートのどちらかで公開することができます。
例えば下記のような設定を記述するとmainのポートの/henoheno
のパスで moheji
グループのヘルスチェックのエンドポイントが公開できるようです。
management.endpoint.health.group.moheji.additional-path="server:/henoheno"
メインのポートでは無く管理用のポートを使いたい場合は server:
の代わりにmanagement:
を使うようにします。
infoのエンドポイントでJavaの実行環境の情報を取得できるようになった
management.info.java.enabled=true
と設定することでJavaの実行環境の情報を/actuator/info
から取得できるようになります。
以下のように設定を追加します。
management.endpoints.web.exposure.include=info management.info.java.enabled=true
$ curl localhost:9090/actuator/info | jq % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 222 0 222 0 0 44400 0 --:--:-- --:--:-- --:--:-- 44400 { "java": { "vendor": "Oracle Corporation", "version": "17", "runtime": { "name": "OpenJDK Runtime Environment", "version": "17+35-2724" }, "jvm": { "name": "OpenJDK 64-Bit Server VM", "vendor": "Oracle Corporation", "version": "17+35-2724" } } }
WebTestClientを用いたMVCアプリケーションのテストサポート
SpringMVCをモック環境で利用することが可能になりました。
今までは、Mock環境のWebFluexか実際に立ち上がっているサーバに対するサポートしか行われていませんでしたが、今回のアップデートで
@AutoConfigureMockMvc
を付けたクラスはWebTestClient
をDIで受け取ることができる用になり、テストを実行できます。
注意点としてはWebTestClientを使うためにはWebfluxが依存として追加されている必要があるので今回は以下の用に依存をPomに追加しておきます。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
依存の追加ができたら実際にテストを記述していきます。
例えば、以下のようなコントローラをテストしたい場合。
@RestController public class HelloController { @GetMapping("/hello") public String hello() { return "hello"; } }
次のようにテストを記述することができます。
@SpringBootTest @AutoConfigureMockMvc class HelloControllerTest { @Autowired WebTestClient client; @Test public void testHelloController() { client.get() .uri("/hello") .exchange() .expectStatus().isOk() .expectBody(String.class).isEqualTo("hello"); } }
Recordクラスで@ConfigurationPropertiesを使う際に@ConstructorBindingをつける必要がなくなった
Recordsで@ConfigurationPropertiesを使う場合もしコンストラクターが1つである場合は@ConstructorBindingをつける必要がなくなりました。
例えば今までだとRecordクラスで@ConfigurationPropertiesを使う場合は以下のように記述する必要がありました。
@ConfigurationProperties("greeting") @ConstructorBinding public record MyProperties(String word) { }
これが2.6からはコンストラクターが1つしかない場合は以下のように@ConstructorBindingを省略することができます。
@ConfigurationProperties("greeting") public record MyProperties(String word) {}
先程のWebTestClientでテストしたControllerをプロパティーを読み込んだ文字列を返すように変更してみます。
MyProperties.java
@ConfigurationProperties("greeting") public record MyProperties(String word) {}
Config.java
@Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(MyProperties.class) public class Config { }
HelloController.java
@RestController public class HelloController { final private MyProperties properties; public HelloController(MyProperties properties) { this.properties = properties; } @GetMapping("/hello") public String hello() { return properties.word(); } }
application.properties
にプロパティーを追加します。
greeting.word=Hello
アプリを起動してcURLでアクセスします。
$ curl localhost:8080/hello Hello
Docker Imageビルド周りのサポートの追加
追加のイメージタグ
Docker Imageビルドの際にMavenやGradleに設定を記述すれば追加のタグを指定できるようになり、同じイメージを複数の名前でビルドできるようになりました。
例えばMavenの場合以下のようにPomに設定を記述することで複数のイメージをビルドすることができます。
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <image> <name>greeting-app</name> <tags> <tag>hoge</tag> <tag>fuga</tag> </tags> </image> </configuration> </plugin> </plugins> </build>
そして、./mvnw spring-boot:build-image
を実行すると以下のように3つのイメージをビルドしてくれます。
$ ./mvnw spring-boot:build-image (省略) [INFO] Successfully built image 'docker.io/library/greeting-app:latest' [INFO] [INFO] Successfully created image tag 'docker.io/library/hoge' [INFO] [INFO] Successfully created image tag 'docker.io/library/fuga' (省略)
キャッシュの設定
The CNB builder caches layers that are used when building and launching an image. By default, these caches are stored as named volumes in the Docker daemon with names that are derived from the full name of the target image. If the image name changes frequently, for example when the project version is used as a tag in the image name, then the caches can be invalidated frequently.
Cloud Native Buildpacksはイメージをビルドするサイト起動する際にそれぞれキャッシュを行いますが、デフォルトではフルのイメージ名を利用してそのキャシュを管理します。例えばイメージの名前がよく変わるなどする場合は(tagでバージョニングを行っている際など)このキャッシュが同様の頻度で利用できなくなってしまいまいます。
そういった問題を解決するためキャッシュの変わりの名前を設定できるようになりました。
この機能を利用するためには次のような設定をPomに記述します。
<project> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <image> <buildCache> <volume> <name>cache-${project.artifactId}.build</name> </volume> </buildCache> <launchCache> <volume> <name>cache-${project.artifactId}.launch</name> </volume> </launchCache> </image> </configuration> </plugin> </plugins> </build> </project>