Docker 20.10.0についてまとめる
はじめに
だいぶ前ですがDokcer 20.10.0
がリリースされています。かねてからやろうと思ってやってなかったのですが、リリースノートを眺めて、気になったところをかいつまんでまとめてみようと思います。
ものによっては動かしてみようかと。
やっていく
環境
$ uname -srvmpio Linux 5.4.0-70-generic #78-Ubuntu SMP Fri Mar 19 13:29:52 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.2 LTS Release: 20.04 Codename: focal $ docker version Client: Docker Engine - Community Version: 20.10.5 API version: 1.41 Go version: go1.13.15 Git commit: 55c4c88 Built: Tue Mar 2 20:18:20 2021 OS/Arch: linux/amd64 Context: default Experimental: true Server: Docker Engine - Community Engine: Version: 20.10.5 API version: 1.41 (minimum version 1.12) Go version: go1.13.15 Git commit: 363e9a8 Built: Tue Mar 2 20:16:15 2021 OS/Arch: linux/amd64 Experimental: true containerd: Version: 1.4.4 GitCommit: 05f951a3781f4f2c1911b05e61c160e9c30eaa8e runc: Version: 1.0.0-rc93 GitCommit: 12644e614e25b05da6fd08a38ffa0cfe1903fdec docker-init: Version: 0.19.0 GitCommit: de40ad0
今回、使用するDockerは20.10.5
ですが、20.10.0
の変更点のみをまとめていこうと思います。
API
GET /events
でprune
のイベントを取得できる
例えば、以下のようにcurl --unix-socket /var/run/docker.sock http:/v1.41/events
にアクセスした状態で、docker system prune
コマンドを実行すると、以下のようにイベントを取得することができます。
$ curl --unix-socket /var/run/docker.sock http:/v1.41/events {"status":"prune","Type":"container","Action":"prune","Actor":{"ID":"","Attributes":{"reclaimed":"0"}},"scope":"local","time":1618107008,"timeNano":1618107008756813704} {"Type":"network","Action":"prune","Actor":{"ID":"","Attributes":{"reclaimed":"0"}},"scope":"local","time":1618107008,"timeNano":1618107008758330171} {"status":"prune","Type":"image","Action":"prune","Actor":{"ID":"","Attributes":{"reclaimed":"0"}},"scope":"local","time":1618107008,"timeNano":1618107008798691967} {"Type":"builder","Action":"prune","Actor":{"ID":"","Attributes":{"reclaimed":"0"}},"scope":"local","time":1618107008,"timeNano":1618107008866211129}
このエンドポイントはcontainer
、network
、 volume
、 image
それぞれに対してreclaimed
(回収したバイト数)を取得することができます。
GET /info
に対する変更点
以下のような変更点があります。
- OSのVersionを取得できるようになった
- DefaultAddressPoolsを取得できるようになった
Builder
#syntax
なしで、RUN --mount
オプションを利用できるようになった
もともとBuildKitのRUN --mount
拡張機能を利用したい場合は``のようなコメント行をDockerfileの先頭に追加する必要がありましたが不要になりました。
ARG
がENV
のように複数の値を受け付けるように変更
ARG
コマンドは変数を定義してビルド時に--build-arg <varname>=<value>
のフラグを利用することによって引数を渡すことができます。このARG
コマンドが今回の変更でENV
と同じように複数定義を一行でできるように修正さています。
ADD
コマンドに--chown
パラメーター拡張を渡せるようになった
もともとADD
/COPY
コマンドでは--chown
フラグを指定できファイルオーナーを設定できますしかし、ADDコマンドではARG
やENV
で定義された変数からの指定ができないようになってました。しかし今回の修正でそれが行えるようになったようです。
Clientクライアントでは以下のような修正が行われています
- -a/--all-tagsフラグですべてのタグをpushできるようになった
- Kubernetesの
username/password
認証をサポート run
コマンドとcreate
コマンドに--pull=missing|always|never
フラグを追加docker exec
に--env-file
フラグがついかされ環境変数を渡せるようになったlog-driver
が追加されPrettyオプションが利用可能になった--cgroupns
フラグでネームスペースを指定できようになったdocker manifest rm
ローカルストレージのマニフェストリストドラフト(設定やレイヤーの情報を保持)を削除docker info
とdocker version
にコンテキストの情報を出力するように変更
Runtime
ランタイムでは以下のような修正が行われています。
- cgroup2のサポート
- cgroup2がデフォルトで
systemd
のcgroupを利用するようになった - 新しいストレージドライバ
fuse-overlayfs
- containerdのバイナリを1.4.3にアップデート
docker push
のデフォルトがすべてのタグのPushからlatest
に変更- イメージをPullする際のコネクションロスでリコネクトする回数を指定できるように変更
- コンテナスタートアップのために、最低のメモリリミットを6Mへ変更。
Networking
host.docker.internal
のLinuxでのサポート
最後の方力尽きましたが、すべての変更に関してはこちらをご確認ください
QuarkusのDevServicesを試す
はじめに
先日、Quarkusの1.13がリリースされので、リリースブログを眺めていたのですがDevServicesという便利そうな機能が追加されていたので試してみようかと思います。
DevServicesとは
DevモードでQuarkusを起動した場合、追加の設定無しでDBを起動してくれてDevモードのコンフィグとバインドしてくれるようです。
例えば、PostgresSQL JDBCの拡張がPomの依存に追加されている場合Testcontainersを使って(もしくはJavaのプロセス内で)自動的にDBを立ち上げてくれます。(今回は試しませんがReactiveクライアントにも対応しているようです)
現在DevSercicesは以下のDBに対応しているようです。
- Postgresql (コンテナ)
- MySQL (コンテナ)
- MariaDB (コンテナ)
- H2 (プロセス内)
- Apache Derby (プロセス内)
- DB2 (コンテナ)
- MSSQL (コンテナ)
また、DB2とMSSQLに関してはライセンスへの同意が必要です。src/main/resources/container-license-acceptance.txt
を作成して以下のようなテキストを記述する必要があります。
ibmcom/db2:11.5.0.0a mcr.microsoft.com/mssql/server:2017-CU12
使ってみる
環境
今回は以下の環境でアプリを動かします。
$ java --version openjdk 16 2021-03-16 OpenJDK Runtime Environment (build 16+36-2231) OpenJDK 64-Bit Server VM (build 16+36-2231, mixed mode, sharing) $ mvn --version Apache Maven 3.6.3 Maven home: /usr/share/maven Java version: 16, vendor: Oracle Corporation, runtime: /home/username/.sdkman/candidates/java/16-open Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "5.4.0-70-generic", arch: "amd64", family: "unix" $ docker version Client: Docker Engine - Community Version: 20.10.5 API version: 1.41 Go version: go1.13.15 Git commit: 55c4c88 Built: Tue Mar 2 20:18:20 2021 OS/Arch: linux/amd64 Context: default Experimental: true Server: Docker Engine - Community Engine: Version: 20.10.5 API version: 1.41 (minimum version 1.12) Go version: go1.13.15 Git commit: 363e9a8 Built: Tue Mar 2 20:16:15 2021 OS/Arch: linux/amd64 Experimental: true containerd: Version: 1.4.4 GitCommit: 05f951a3781f4f2c1911b05e61c160e9c30eaa8e runc: Version: 1.0.0-rc93 GitCommit: 12644e614e25b05da6fd08a38ffa0cfe1903fdec docker-init: Version: 0.19.0 GitCommit: de40ad0
プロジェクトを作成する
今回はPosgresSQLを利用して、DBに保存されたデータを返す簡単なAPIを記述しようと思います。
以下の構成でプロジェクトを作成します。
Devモードでアプリケーションを起動する
DevServcesはデフォルトでオンになっており、DB URLやパスワード、ユーザ名が設定されていなければTestcontainersを使ってDBを立ち上げてくれます。
先程、作成したプロジェクトをunzipしてなにも変更せずにDevモードで起動すると以下のようなログを出力してDBも立ち上げてくれます。
$ ./mvnw compile quarkus:dev [INFO] Scanning for projects... [INFO] [INFO] ----------------------< dev.hirooka:devservices >----------------------- [INFO] Building devservices 1.0.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- quarkus-maven-plugin:1.13.0.Final:generate-code (default) @ devservices --- [INFO] [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ devservices --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] Copying 2 resources [INFO] [INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ devservices --- [INFO] Nothing to compile - all classes are up to date [INFO] [INFO] --- quarkus-maven-plugin:1.13.0.Final:dev (default-cli) @ devservices --- Listening for transport dt_socket at address: 5005 2021-04-05 12:13:24,948 INFO [org.tes.doc.DockerClientProviderStrategy] (build-28) Loaded org.testcontainers.dockerclient.EnvironmentAndSystemPropertyClientProviderStrategy from ~/.testcontainers.properties, will try it first 2021-04-05 12:13:25,299 INFO [org.tes.doc.DockerClientProviderStrategy] (build-28) Found Docker environment with Environment variables, system properties and defaults. Resolved dockerHost=unix:///var/run/docker.sock 2021-04-05 12:13:25,300 INFO [org.tes.DockerClientFactory] (build-28) Docker host IP address is localhost 2021-04-05 12:13:25,324 INFO [org.tes.DockerClientFactory] (build-28) Connected to docker: Server Version: 20.10.5 API Version: 1.41 Operating System: Ubuntu 20.04.2 LTS Total Memory: 31741 MB 2021-04-05 12:13:25,326 INFO [org.tes.uti.ImageNameSubstitutor] (build-28) Image name substitution will be performed by: DefaultImageNameSubstitutor (composite of 'ConfigurationFileImageNameSubstitutor' and 'PrefixingImageNameSubstitutor') 2021-04-05 12:13:25,352 INFO [org.tes.uti.RegistryAuthLocator] (build-28) Failure when attempting to lookup auth config. Please ignore if you don't have images in an authenticated registry. Details: (dockerImageName: testcontainers/ryuk:0.3.1, configFile: /home/yuya-hirooka/.docker/config.json. Falling back to docker-java default behaviour. Exception message: /home/yuya-hirooka/.docker/config.json (そのようなファイルやディレクトリはありません) 2021-04-05 12:13:26,102 INFO [org.tes.DockerClientFactory] (build-28) Ryuk started - will monitor and terminate Testcontainers containers on JVM exit 2021-04-05 12:13:26,103 INFO [org.tes.DockerClientFactory] (build-28) Checking the system... 2021-04-05 12:13:26,103 INFO [org.tes.DockerClientFactory] (build-28) ✔︎ Docker server version should be at least 1.6.0 2021-04-05 12:13:26,185 INFO [org.tes.DockerClientFactory] (build-28) ✔︎ Docker environment should have more than 2GB free disk space 2021-04-05 12:13:26,295 INFO [🐳 .6.12]] (build-28) Creating container for image: postgres:9.6.12 2021-04-05 12:13:26,337 INFO [🐳 .6.12]] (build-28) Starting container with ID: 33b42f67a20ed4d4ff1b2f0135ec06b13c3444e5782567e4d30395e0c5741fd3 2021-04-05 12:13:26,710 INFO [🐳 .6.12]] (build-28) Container postgres:9.6.12 is starting: 33b42f67a20ed4d4ff1b2f0135ec06b13c3444e5782567e4d30395e0c5741fd3 2021-04-05 12:13:30,167 INFO [🐳 .6.12]] (build-28) Container postgres:9.6.12 started in PT3.98152593S __ ____ __ _____ ___ __ ____ ______ --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ 2021-04-05 12:13:30,588 INFO [io.quarkus] (Quarkus Main Thread) devservices 1.0.0-SNAPSHOT on JVM (powered by Quarkus 1.13.0.Final) started in 6.064s. Listening on: http://localhost:8080 2021-04-05 12:13:30,589 INFO [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated. 2021-04-05 12:13:30,589 INFO [io.quarkus] (Quarkus Main Thread) Installed features: [agroal, cdi, hibernate-orm, jdbc-postgresql, mutiny, narayana-jta, resteasy, smallrye-context-propagation]
docker psコマンドで確認してみるとPostgresとTestcontainersのコンテナがきどうしているのが確認できます。
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 74541a57e8cd postgres:9.6.12 "docker-entrypoint.s…" 3 minutes ago Up 3 minutes 0.0.0.0:49164->5432/tcp great_blackburn 72f55524eecf testcontainers/ryuk:0.3.1 "/app" 3 minutes ago Up 3 minutes 0.0.0.0:49163->8080/tcp testcontainers-ryuk-42c2bcf5-1ddc-415d-b694-3f7f311d885e
データを取得してみる
それでは、実際にこの起動したDB利用してみようと思います。
まずは初期データを投入します。
まずはapplication.properties
を以下のように修正します。
%dev.quarkus.hibernate-orm.log.sql=true %dev.quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.database.generation=drop-and-create
を指定することで、Quarkusのアプリは起動時にsqlファイルを読み取ってテーブルを作成してくれるようになります。
また、デフォルトではresources
のimport.sql
という名前のファイルを読み取るようになっているのでそちらも用意しておきます。
drop table if exists message; create table message( id int, content varchar(100) ); insert into message values (1, 'hello, dev service1'), (2, 'hello, dev service2'), (3, 'hello, dev service3');
quarkus.hibernate-orm.log.sql
はSQLの実行がわかりやすいように設定しています。また%dev
はDevモードで起動時に有効になる設定を示します。
この状態でアプリケーションを再起動します。
すると、ここには記述しませんが実行されたSQLのログが出力されるはずです。
ここまでで、起動したDBを利用できていることががある程度確認できたかもしれませんが。
EntityとControllerを作成してmessage
を取得してみようと思います。
MessageEntity.java
@Entity public class MessageEntity { @Id private int id; private String content; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } }
GreetingResource .java
@Path("/messages") public class GreetingResource { private EntityManager em; public GreetingResource(EntityManager em) { this.em = em; } @GET @Produces(MediaType.APPLICATION_JSON) public List<MessageEntity> hello() { return em.createNativeQuery("select id, content from message", MessageEntity.class).getResultList(); } }
コントローラーはデフォルトで作成されたGreetingResource.java
を少し改造して作っています。また、少し筋は悪いかもしれませんがEntityをそのままレスポンスとして返すようにしています。
アプリケーションがリロードされたらcURLを叩いてみます。
$ curl localhost:8080/messages [{"id":1,"content":"hello, dev service1"},{"id":2,"content":"hello, dev service2"},{"id":3,"content":"hello, dev service3"}]
import.sql
でセットしていた値が帰ってきていることが確認できました。
本番用の設定を記述する
前述したとおりDevServicesはDB URLなどがセットされている場合においてDBを起動しないようになっています。
例えば、以下のように設定を記述してアプリケーションを起動すると、DBが起動されません。
quarkus.datasource.db-kind=postgresql quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/hibernate_orm_test quarkus.datasource.username=username quarkus.datasource.password=password
起動すると、import.sql
の実行に失敗したエラーログが出力されたと思います。
application.properites
に本番用の設定値を書きたいが、ローカルでの開発ではDevServicesを利用したい場合は設定のプリフィクスとして%prod
を以下のように付与します。
%prod.quarkus.datasource.db-kind=postgresql %prod.quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/hibernate_orm_test %prod.quarkus.datasource.username=username %prod.quarkus.datasource.password=password
これで上記の設定はDevモードでは用いられることはなくDevServicesはDBを立ち上げてくれます。
その他の設定値
最後にDevServicesの設定をいくつかまとめておこうと思います。完全なリストはこちら をご覧ください。
設定値 | 説明 | デフォルト値(型) |
---|---|---|
quarkus.datasource.devservices | DevServicesを明示的に有効化するかのフラグ | true(boolean) |
quarkus.datasource.devservices.image-name | 利用するコンテナイメージ名を指定する。もしH2などコンテナを起動しないサービスの場合は影響を及ぼさない | (string) |
また、ここにはまとめてませんが、名前付きデータソースごとにそれぞれの設定を行なうことも可能なようです。
k8s上のSpring Bootアプリからメトリクスを取得する(Actuator + Prometheus Operator)
はじめに
Kubernetes上でのSpring Bootのアプリのデバックを行なう際にPodのコンテナ内に応じて必要なコマンドとってきて、スレッドダンプやヒープダンプを取得してホストにコピーみたいなことをやっていたのですが、流石に面倒に感じはじめました。
そこでふとActuator入れりゃええやんと思いたち、どうせだったら、Prometeheusでメトリクスの収集までできればいろいろええんじゃないかと思いいろいろ動かしてみようと思います。
Spring Boot Actuatorとは
(まず、前提としてこのブログはBoot 2.4系のドキュメントを確認してます。貼られているリンクもそうなってると思います。)
Spring Boot Actuatorはアプリケーションをモニター、マネージするための追加機能を提供してくれています。HTTPやJMXのエンドポイントを通して操作を選択することができます(JMXを利用する場合はspring.jmx.enabled
をtrue
で設定する必要があります)。HTTPを利用する場合はデフォルトでは/actuator/{id}
の形式で情報を取得することが可能で、たとえばhealth
の情報を取得したい場合は/actuator/health
のエンドポイントで利用できます。カスタムエンドポイントを公開することもできますが、基本としては以下のようなエンドポイントが用意されています。
ID | 説明 |
---|---|
health | アプリケーションの正常性情報を取得でる。このエンドポイントを通して、実行中のアプリケーション(本体と関連するアプリケーション)ステータスを確認することができる。関連するアプリケーションに関してはHealthContributorRegistry に定義されているすべてのHealthContributor から収集される。(詳細はこちら) |
httptrace | HTTPのトレース情報(リクエスト/レスポンス)を取得できる。デフォルトで最新100個をインメモリに保存。HTTP トレースを有効にするには、アプリケーションの構成で HttpTraceRepository 型をBeanとして提供する必要がある |
beans | アプリケーション内のSpring Beanのリストを取得できる |
mappings | @RequestMapping のパスのリストを表示します |
shutdown | アップリケーションをシャットダウンする |
threaddump | スレッドダンプを取得できる |
heapdump | hprofヒープダンプを取得できる。アプリケーションがSpring MVCやWebFluxである場合に有効 |
logfile | ログの内容を返す。logging.file.name かlogging.file.path プロパティが設定されている必要がある。アプリケーションがアプリケーションがSpring MVCやWebFluxである場合に有効 |
詳細なリストに関してはこちらをご覧ください。
また、Prometheusフォーマットで出力してくれるエンドポイントも提供されており今回はこちらを利用して、メトリクスを収集しようと思います。
エンドポイントの公開に関するセキュリティ的な考慮点に付いて
Actuatorでアプリケーションのインフラレイヤーやアプリケーションレイヤーで保護しておく必要があります。アプリケーションレイヤーでは「必要なエンドポイントのみ公開する」のが基本的な戦略となると思います。ActuatorではデフォルトでHTTPのエンドポイントはhealth
とinfo
以外は非公開になっているようです。
これらのエンドポイントを公開非公開を制御する場合は以下のようなinclude
もしくはexcluede
プロパティを使用します。例えば、info, health, threaddump
のエンドポイントを公開したい場合は、アプリケーションプロパティで以下のように設定します。
management.endpoints.web.exposure.include=info, health, threaddump
その他のプロパティに関してはこちらをご覧ください。
単純な公開非公開とは別に、Spring Securityを利用している場合、そのコンテンツネゴシエーションの仕組みを利用してエンドポイントを保護することができます。例えば、特定のロールを持つユーザのみアクセス可能と言ったような制御を行なうことができます。
やっていく
環境
今回KubernetesのクラスタはMinikubeを用いてローカルに構成します。そのVMはデフォルトのDockerを利用します。
各ミドルウェア、OSのバージョンは以下のとおりです。
$ uname -srvmpio Linux 5.4.0-70-generic #78-Ubuntu SMP Fri Mar 19 13:29:52 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.2 LTS Release: 20.04 Codename: focal (Minikubeが動いているDocker) $ docker version (クライアントは省略) Server: Docker Engine - Community Engine: Version: 20.10.5 API version: 1.41 (minimum version 1.12) Go version: go1.13.15 Git commit: 363e9a8 Built: Tue Mar 2 20:16:15 2021 OS/Arch: linux/amd64 Experimental: true containerd: Version: 1.4.4 GitCommit: 05f951a3781f4f2c1911b05e61c160e9c30eaa8e runc: Version: 1.0.0-rc93 GitCommit: 12644e614e25b05da6fd08a38ffa0cfe1903fdec docker-init: Version: 0.19.0 GitCommit: de40ad0 $ minikube version minikube version: v1.16.0 commit: 9f1e482427589ff8451c4723b6ba53bb9742fbb1 $ kubectl version -o yaml clientVersion: buildDate: "2021-03-18T01:10:43Z" compiler: gc gitCommit: 6b1d87acf3c8253c123756b9e61dac642678305f gitTreeState: clean gitVersion: v1.20.5 goVersion: go1.15.8 major: "1" minor: "20" platform: linux/amd64 serverVersion: buildDate: "2020-12-08T17:51:19Z" compiler: gc gitCommit: af46c47ce925f4c4ad5cc8d1fca46c7b77d13b38 gitTreeState: clean gitVersion: v1.20.0 goVersion: go1.15.5 major: "1" minor: "20" platform: linux/amd64
環境構築
プロジェクトの作成 & Ptometeusのメトリクスを取得できるように設定
まずは、プロジェクトを作成します。
プロジェクトはSpring Initializrを利用して以下のような構成で作成します。
Webの他にActuatorとPrometheusの選択肢を選択肢てます。
出来上がったプロジェクトのPomの依存を確認すると以下の2つが存在することが確認できます。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> <scope>runtime</scope> </dependency>
Actuatorの出力をPrometheusの形式で出力する場合micrometer-registry-prometheus
の依存を追加する必要があります。今回は初期Initializrで追加しましたが、既存のBootプロジェクトにActuatorとPromtheusを使ってメトリクスの収集をする場合は上記の2つの依存を追加してやれば大丈夫だと思います。
コントローラーを作っておく
GETでリクエストを受けると文字列を返すコントローラーを作っておきます。
コンポーネントスキャン対象のパッケージ配下にいかのようなGreetingController
を作成します。
@RestController public class GreetingController { @GetMapping("/hello") public String hello(){ return "hello, hello"; } }
起動してcURLで作ったコントローラーとActuatorのエンドポイントにアクセスしてみます。
$ curl localhost:8080/hello hello, hello $ curl localhost:8080/actuator/health {"status":"UP"}
Prometheusのエンドポイントの公開設定を行なう
前述の通りActuatorのエンドポイントはhealth
とinfo
以外は非公開になっています。/actuator
にアクセスすると、公開されているエンドポイントの情報を取得することができます。
$ curl localhost:8080/actuator/ | jq { "_links": { "self": { "href": "http://localhost:8080/actuator", "templated": false }, "health": { "href": "http://localhost:8080/actuator/health", "templated": false }, "health-path": { "href": "http://localhost:8080/actuator/health/{*path}", "templated": true }, "info": { "href": "http://localhost:8080/actuator/info", "templated": false } }
Prometheus用のエンドポイントを公開するには、設定を追加する必要があります。
また、もともとの困りごとだったダンプの取得を行なうエンドポイントも公開しておきます。
アプリケーションプロパティに以下のように設定を行います。
# ついでにPort番号も変えておきます。 server.port=8888 management.endpoints.web.exposure.include=info, health, threaddump, prometheus, heapdump
再度/actuator
にアクセスしてみます。
$ curl localhost:8888/actuator | jq { "_links": { "self": { "href": "http://localhost:8888/actuator", "templated": false }, "health": { "href": "http://localhost:8888/actuator/health", "templated": false }, "health-path": { "href": "http://localhost:8888/actuator/health/{*path}", "templated": true }, "info": { "href": "http://localhost:8888/actuator/info", "templated": false }, "heapdump": { "href": "http://localhost:8888/actuator/heapdump", "templated": false }, "threaddump": { "href": "http://localhost:8888/actuator/threaddump", "templated": false }, "prometheus": { "href": "http://localhost:8888/actuator/prometheus", "templated": false } } } yuya-hirooka@yuya-hirooka:~/source/java/actu
先程と比べて、設定したしたエンドポイントの情報が出力されているのがわかります。
Prometheus形式のメトリクスの取得をしてみます。
$ curl 'http://localhost:8888/actuator/prometheus' # HELP jvm_memory_committed_bytes The amount of memory in bytes that is committed for the Java virtual machine to use # TYPE jvm_memory_committed_bytes gauge jvm_memory_committed_bytes{area="heap",id="G1 Survivor Space",} 0.0 jvm_memory_committed_bytes{area="heap",id="G1 Old Gen",} 5.4525952E7 jvm_memory_committed_bytes{area="nonheap",id="Metaspace",} 3.0539776E7 jvm_memory_committed_bytes{area="nonheap",id="CodeHeap 'non-nmethods'",} 2555904.0 jvm_memory_committed_bytes{area="heap",id="G1 Eden Space",} 4.6137344E7 jvm_memory_committed_bytes{area="nonheap",id="Compressed Class Space",} 4325376.0 jvm_memory_committed_bytes{area="nonheap",id="CodeHeap 'non-profiled nmethods'",} 6815744.0 # HELP tomcat_sessions_alive_max_seconds # TYPE tomcat_sessions_alive_max_seconds gauge tomcat_sessions_alive_max_seconds 0.0 # HELP jvm_threads_states_threads The current number of threads having NEW state # TYPE jvm_threads_states_threads gauge jvm_threads_states_threads{state="runnable",} 11.0 jvm_threads_states_threads{state="blocked",} 0.0 jvm_threads_states_threads{state="waiting",} 11.0 jvm_threads_states_threads{state="timed-waiting",} 4.0 jvm_threads_states_threads{state="new",} 0.0 jvm_threads_states_threads{state="terminated",} 0.0 # HELP system_cpu_usage The "recent cpu usage" for the whole system # TYPE system_cpu_usage gauge system_cpu_usage 1.0 # HELP jvm_gc_memory_promoted_bytes_total Count of positive increases in the size of the old generation memory pool before GC to after GC # TYPE jvm_gc_memory_promoted_bytes_total counter jvm_gc_memory_promoted_bytes_total 9284952.0 # HELP jvm_threads_peak_threads The peak live thread count since the Java virtual machine started or peak was reset # TYPE jvm_threads_peak_threads gauge jvm_threads_peak_threads 30.0 # HELP jvm_threads_daemon_threads The current number of live daemon threads # TYPE jvm_threads_daemon_threads gauge jvm_threads_daemon_threads 22.0 # HELP tomcat_sessions_active_current_sessions # TYPE tomcat_sessions_active_current_sessions gauge tomcat_sessions_active_current_sessions 0.0 # HELP jvm_buffer_total_capacity_bytes An estimate of the total capacity of the buffers in this pool # TYPE jvm_buffer_total_capacity_bytes gauge jvm_buffer_total_capacity_bytes{id="mapped - 'non-volatile memory'",} 0.0 jvm_buffer_total_capacity_bytes{id="mapped",} 0.0 jvm_buffer_total_capacity_bytes{id="direct",} 49152.0 # HELP system_load_average_1m The sum of the number of runnable entities queued to available processors and the number of runnable entities running on the available processors averaged over a period of time # TYPE system_load_average_1m gauge system_load_average_1m 2.04 # HELP jvm_memory_used_bytes The amount of used memory # TYPE jvm_memory_used_bytes gauge jvm_memory_used_bytes{area="heap",id="G1 Survivor Space",} 0.0 jvm_memory_used_bytes{area="heap",id="G1 Old Gen",} 1.8475864E7 jvm_memory_used_bytes{area="nonheap",id="Metaspace",} 3.016488E7 jvm_memory_used_bytes{area="nonheap",id="CodeHeap 'non-nmethods'",} 1257088.0 jvm_memory_used_bytes{area="heap",id="G1 Eden Space",} 0.0 jvm_memory_used_bytes{area="nonheap",id="Compressed Class Space",} 4117488.0 jvm_memory_used_bytes{area="nonheap",id="CodeHeap 'non-profiled nmethods'",} 6814208.0 # HELP system_cpu_count The number of processors available to the Java virtual machine # TYPE system_cpu_count gauge system_cpu_count 8.0 # HELP jvm_buffer_memory_used_bytes An estimate of the memory that the Java virtual machine is using for this buffer pool # TYPE jvm_buffer_memory_used_bytes gauge jvm_buffer_memory_used_bytes{id="mapped - 'non-volatile memory'",} 0.0 jvm_buffer_memory_used_bytes{id="mapped",} 0.0 jvm_buffer_memory_used_bytes{id="direct",} 49152.0 # HELP process_uptime_seconds The uptime of the Java virtual machine # TYPE process_uptime_seconds gauge process_uptime_seconds 231.285 # HELP tomcat_sessions_rejected_sessions_total # TYPE tomcat_sessions_rejected_sessions_total counter tomcat_sessions_rejected_sessions_total 0.0 # HELP tomcat_sessions_created_sessions_total # TYPE tomcat_sessions_created_sessions_total counter tomcat_sessions_created_sessions_total 0.0 # HELP logback_events_total Number of error level events that made it to the logs # TYPE logback_events_total counter logback_events_total{level="warn",} 0.0 logback_events_total{level="debug",} 0.0 logback_events_total{level="error",} 0.0 logback_events_total{level="trace",} 0.0 logback_events_total{level="info",} 7.0 # HELP jvm_classes_unloaded_classes_total The total number of classes unloaded since the Java virtual machine has started execution # TYPE jvm_classes_unloaded_classes_total counter jvm_classes_unloaded_classes_total 0.0 # HELP jvm_buffer_count_buffers An estimate of the number of buffers in the pool # TYPE jvm_buffer_count_buffers gauge jvm_buffer_count_buffers{id="mapped - 'non-volatile memory'",} 0.0 jvm_buffer_count_buffers{id="mapped",} 0.0 jvm_buffer_count_buffers{id="direct",} 6.0 # HELP process_cpu_usage The "recent cpu usage" for the Java Virtual Machine process # TYPE process_cpu_usage gauge process_cpu_usage 0.0 # HELP jvm_gc_live_data_size_bytes Size of long-lived heap memory pool after reclamation # TYPE jvm_gc_live_data_size_bytes gauge jvm_gc_live_data_size_bytes 1.8475864E7 # HELP jvm_gc_memory_allocated_bytes_total Incremented for an increase in the size of the (young) heap memory pool after one GC to before the next # TYPE jvm_gc_memory_allocated_bytes_total counter jvm_gc_memory_allocated_bytes_total 6.291456E7 # HELP tomcat_sessions_expired_sessions_total # TYPE tomcat_sessions_expired_sessions_total counter tomcat_sessions_expired_sessions_total 0.0 # HELP jvm_threads_live_threads The current number of live threads including both daemon and non-daemon threads # TYPE jvm_threads_live_threads gauge jvm_threads_live_threads 26.0 # HELP jvm_gc_pause_seconds Time spent in GC pause # TYPE jvm_gc_pause_seconds summary jvm_gc_pause_seconds_count{action="end of major GC",cause="Heap Dump Initiated GC",} 2.0 jvm_gc_pause_seconds_sum{action="end of major GC",cause="Heap Dump Initiated GC",} 0.077 jvm_gc_pause_seconds_count{action="end of minor GC",cause="G1 Evacuation Pause",} 1.0 jvm_gc_pause_seconds_sum{action="end of minor GC",cause="G1 Evacuation Pause",} 0.005 # HELP jvm_gc_pause_seconds_max Time spent in GC pause # TYPE jvm_gc_pause_seconds_max gauge jvm_gc_pause_seconds_max{action="end of major GC",cause="Heap Dump Initiated GC",} 0.0 jvm_gc_pause_seconds_max{action="end of minor GC",cause="G1 Evacuation Pause",} 0.0 # HELP http_server_requests_seconds # TYPE http_server_requests_seconds summary http_server_requests_seconds_count{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/actuator/heapdump",} 2.0 http_server_requests_seconds_sum{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/actuator/heapdump",} 0.425517121 http_server_requests_seconds_count{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/actuator",} 1.0 http_server_requests_seconds_sum{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/actuator",} 0.024393608 # HELP http_server_requests_seconds_max # TYPE http_server_requests_seconds_max gauge http_server_requests_seconds_max{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/actuator/heapdump",} 0.0 http_server_requests_seconds_max{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/actuator",} 0.0 # HELP process_start_time_seconds Start time of the process since unix epoch. # TYPE process_start_time_seconds gauge process_start_time_seconds 1.616906295082E9 # HELP tomcat_sessions_active_max_sessions # TYPE tomcat_sessions_active_max_sessions gauge tomcat_sessions_active_max_sessions 0.0 # HELP jvm_memory_max_bytes The maximum amount of memory in bytes that can be used for memory management # TYPE jvm_memory_max_bytes gauge jvm_memory_max_bytes{area="heap",id="G1 Survivor Space",} -1.0 jvm_memory_max_bytes{area="heap",id="G1 Old Gen",} 8.321499136E9 jvm_memory_max_bytes{area="nonheap",id="Metaspace",} -1.0 jvm_memory_max_bytes{area="nonheap",id="CodeHeap 'non-nmethods'",} 7553024.0 jvm_memory_max_bytes{area="heap",id="G1 Eden Space",} -1.0 jvm_memory_max_bytes{area="nonheap",id="Compressed Class Space",} 1.073741824E9 jvm_memory_max_bytes{area="nonheap",id="CodeHeap 'non-profiled nmethods'",} 2.44105216E8 # HELP jvm_classes_loaded_classes The number of classes that are currently loaded in the Java virtual machine # TYPE jvm_classes_loaded_classes gauge jvm_classes_loaded_classes 7304.0 # HELP process_files_open_files The open file descriptor count # TYPE process_files_open_files gauge process_files_open_files 60.0 # HELP jvm_gc_max_data_size_bytes Max size of long-lived heap memory pool # TYPE jvm_gc_max_data_size_bytes gauge jvm_gc_max_data_size_bytes 8.321499136E9 # HELP process_files_max_files The maximum file descriptor count # TYPE process_files_max_files gauge process_files_max_files 1048576.0
JVMの情報とか諸々が出力されてるが見て取れます。
threddump、heapdumpを取得するときは以下のようにします。
$ curl localhost:8888/actuator/threaddump -H 'Accept: text/plain' > threddump.txt % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 30125 100 30125 0 0 668k 0 --:--:-- --:--:-- --:--:-- 684k $ curl 'http://localhost:8888/actuator/heapdump' -O % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 27.6M 100 27.6M 0 0 124M 0 --:--:-- --:--:-- --:--:-- 124M
Docker Imageの作成
アプリケーションの準備として最後に作成したアプリをDockerイメージ化しておきます。
ここで、今回はDockerのリポジトリを個別に用意したりDocker HubにイメージをPushのは面倒なので、Minikubeのコンテキストでイメージをビルドします。
具体的には次のコマンドを実行します。
$ eval $(minikube docker-env) f $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE kubernetesui/dashboard v2.1.0 9a07b5b4bfac 3 months ago 226MB k8s.gcr.io/kube-proxy v1.20.0 10cc881966cf 3 months ago 118MB k8s.gcr.io/kube-apiserver v1.20.0 ca9843d3b545 3 months ago 122MB k8s.gcr.io/kube-scheduler v1.20.0 3138b6e3d471 3 months ago 46.4MB k8s.gcr.io/kube-controller-manager v1.20.0 b9fa1895dcaa 3 months ago 116MB gcr.io/k8s-minikube/storage-provisioner v4 85069258b98a 3 months ago 29.7MB k8s.gcr.io/etcd 3.4.13-0 0369cf4303ff 7 months ago 253MB k8s.gcr.io/coredns 1.7.0 bfe3a36ebd25 9 months ago 45.2MB kubernetesui/metrics-scraper v1.0.4 86262685d9ab 12 months ago 36.9MB k8s.gcr.io/pause 3.2 80d28bedfe5d 13 months ago 683kB
これで一時的にMinikubeのコンテキストでDocker ビルドできるようになりました。
minikube docker-env
コマンドはMinikube組み込みのDockerデーモンをホストマシンから操作できるようにするためのコマンドです。
この状態でイメージをビルドします。
Spring Bootは2.3からBuildpackを利用したイメージのビルドをサポートしています。今回はその機能を利用してイメージを作成したいと思います。
具体的にはプロジェクトルートで以下のコマンドを実行します。
$ ./mvnw spring-boot:build-image -Dspring-boot.build-image.imageName=spring/actuator-prometheus $ docker images | grep spring $ docker images | grep spring/actuator-prometheus spring/actuator-prometheus latest 2b5438489b00 41 years ago 278MB
なんかCreatedがバグってる気がしますが、一旦はこれでOKです。
クラスターの設定 & 諸々のリソース作成
今回クラスターは以前のブログで作成したものを再利用しようと思います。構成としてはMinikubeで作ったクラスタにkube-promtheusを使ってPrometheusとGrafanaをデプロイ&設定を行っています。
PodMonitorを作成
通常、Prohetheusがk8sクラスタ内のサービスを見つける際にはkubernetes_sd_configをう場合が多いみたいですが、今回はkube-prometheusを利用しており、Kubernetes Operatorが利用できます。Kubernetes OperatorではPodMonitor
とPodMetricsEndpoints
というCRDが用意されており、そっちを利用してやってみようと思います。
以下のようなマニフェストファイルを用意します。
apiVersion: monitoring.coreos.com/v1 kind: PodMonitor metadata: name: spring-app labels: backend: spring-app spec: selector: matchLabels: app: spring-app podMetricsEndpoints: - port: app-port - path: /actuator/prometheus
ポイントはpodMetricsEndpoints
のところで、ここにはPodで指定するポートの名前と、メトリクスを収集するPathを指定します。Actuatorはデフォルトでは/actuator/prometheus
でメトリクスを公開しているので、そのパスを指定します。
作ったマニフェストをApplyしておきます。
$ kubectl apply -f pod-monitor.yaml podmonitor.monitoring.coreos.com/spring-app created $ kubectl get podmonitor NAME AGE spring-app 19s
Doploymentを作成
それでは、Deploymentを作成して、作ったSpringのアプリをKubernetesにデプロイします。
まずは、--dry-run
オプションを使ってベースとなるDeploymentを作成します。
$ kubectl create deployment spring-app --image=spring/actuator-prometheus --dry-run=client -o yaml
できたマニフェストを以下のように修正します。
apiVersion: apps/v1 kind: Deployment metadata: creationTimestamp: null labels: app: spring-app name: spring-app spec: replicas: 1 selector: matchLabels: app: spring-app strategy: {} template: metadata: creationTimestamp: null labels: app: spring-app spec: containers: - image: spring/actuator-prometheus name: actuator-prometheus resources: {} status: {}
このマニフェストを以下のように修正します。
apiVersion: apps/v1 kind: Deployment metadata: creationTimestamp: null labels: app: spring-app name: spring-app spec: replicas: 1 selector: matchLabels: app: spring-app strategy: {} template: metadata: creationTimestamp: null labels: app: spring-app spec: containers: - image: spring/actuator-prometheus name: actuator-prometheus imagePullPolicy: IfNotPresent ports: - name: spring-app containerPort: 8888 resources: {} status: {}
具体的には
imagePullPolicy: IfNotPresent ports: - name: spring-app containerPort: 8888
のところを追記しました。
imagePullPolicy: IfNotPresent
はローカルにイメージがある場合はPullしないようの設定と、PodMonitorが利用するようにportに名前を付けています。
それではこのDeploymentをApplyします。
$ kubectl apply -f deployment.yaml deployment.apps/spring-app created
Prometheusからメトリクスを確認する
実はここまでで大体設定が終わっていて、すでにPrometheusでのメトリクスの収集が始まってます。
Prometheusのダッシュボードでメトリクスを確認してみます。
まずは、デプロイされているプロメテウスにlocalhostでアクセスするためにポートフォワードを行います。
$ kubectl --namespace monitoring port-forward svc/prometheus-k8s 9090[f:id:yuya_hirooka:20210403140913p:plain] Forwarding from 127.0.0.1:9090 -> 9090 Forwarding from [::1]:9090 -> 9090
http://localhost:9090にアクセスしてUIから確認すると以下のようにJVMのメトリクスを取得できるようになっています。
試しにjvm_memory_used_bytes
をグラフ化してみると以下のようにヒープの領域ごとにいい感じにグラフ化できることが確認できました。
Minikubeにkube-prometheusを使ってPrometheusとGrafanaをデプロイする
はじめに
別のことがしたくて、Kubernetesクラスタの監視をPrometheus + Grafanaで行っている環境が欲しくて、いろいろ調べてたらkube-prometheusというのを見つけて、スターも多そうだったのでちょっと触ってみようと思います。
なおこのブログで特に指定がない場合はコンテキストはminikube
が指定されているものとします。
kube-prometheusとは
kube-prometheusはKubernetesのクラスターをPrometheusで監視を行ない簡単に運用していくくための諸々が用意されたアセットです。具体的には、以下のようなものが含まれます。
- Grafana
- Prometheus
- Prometheus Operator
- Kubernetesに対するネイティブデプロイメントとそのマネジメント機能を提供するKubernetes Operator
- Alertmanager
- Prometheusのようなクライアントアプリからのアラートをハンドルするコンポーネント
- Prometheus node-exporter
- *NIX kernelsによって公開されるOSのメトリクスのPrometheusエクスポーター
- Prometheus Adapter for Kubernetes Metrics APIs
- kube-state-metrics
- apiserverに問い合わせを行って、Kubernetes Objectsに関するメトリクスを行ってくれるコンポーネント
インストールしてみる
環境
動作環境は以下の通り
今回KubernetesのクラスタはMinikubeを用いてローカルに構成します。そのVMはデフォルトのDockerを利用します。
各ミドルウェア、OSのバージョンは以下のとおりです。
$ uname -srvmpio Linux 5.4.0-70-generic #78-Ubuntu SMP Fri Mar 19 13:29:52 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.2 LTS Release: 20.04 Codename: focal (Minikubeが動いているDocker) $ docker version (クライアントは省略) Server: Docker Engine - Community Engine: Version: 20.10.5 API version: 1.41 (minimum version 1.12) Go version: go1.13.15 Git commit: 363e9a8 Built: Tue Mar 2 20:16:15 2021 OS/Arch: linux/amd64 Experimental: true containerd: Version: 1.4.4 GitCommit: 05f951a3781f4f2c1911b05e61c160e9c30eaa8e runc: Version: 1.0.0-rc93 GitCommit: 12644e614e25b05da6fd08a38ffa0cfe1903fdec docker-init: Version: 0.19.0 GitCommit: de40ad0 $ minikube version minikube version: v1.18.1 commit: 09ee84d530de4a92f00f1c5dbc34cead092b95bc $ kubectl version -o yaml clientVersion: buildDate: "2021-03-18T01:10:43Z" compiler: gc gitCommit: 6b1d87acf3c8253c123756b9e61dac642678305f gitTreeState: clean gitVersion: v1.20.5 goVersion: go1.15.8 major: "1" minor: "20" platform: linux/amd64 serverVersion: buildDate: "2020-12-08T17:51:19Z" compiler: gc gitCommit: af46c47ce925f4c4ad5cc8d1fca46c7b77d13b38 gitTreeState: clean gitVersion: v1.20.0 goVersion: go1.15.5 major: "1" minor: "20" platform: linux/amd64
クラスターを構築する
公式のやり方通りにMinikubeを用いてクラスタを作ってみたいと思います。
以下のコマンドを実行します。
minikube start --kubernetes-version=v1.20.0 --memory=6g --bootstrapper=kubeadm --extra-config=kubelet.authentication-token-webhook=true --extra-config=kubelet.authorization-mode=Webhook --extra-config=scheduler.address=0.0.0.0 --extra-config=controller-manager.address=0.0.0.0
ここで、それぞれのオプションの意味は以下のようになっています。
bootstrapper
:Bootstrapper(インストール前の環境をチェックするプログラム)の名前。デフォルトはkubeadm
(今回明示的にやる必要は無かったかも)--extra-config
:key=value
のペアで各コンポーネントに渡される設定。valueは.
で区切られており最初のパートは設定を追加するコンポーネント(kubelet, kubeadm, apiserver, controller-manager, etcd, proxy, schedulerなどが選択可能)、後のパートは渡す設定値を示すようです。具体的にはそれぞれのコンポーネントに対して以下のようなオプションを渡しています。kubelet
に対して渡すオプションとして--authentication-token-webhook=ture
とauthorization-mode=Webhook
を渡しています(オプションの意味はこちら)。- schedulerに対して
0.0.0.0
にバインドするように設定 - controller-managerに対して
0.0.0.0
にバインドするように設定
全体を通して、すごく重要そうなのはなさそうな印象ですね。ものによっては省略しても行けそうな気がしますが、変なところでつまりたくないので一旦公式に従っておきます。 ちなみに、それぞれのオプションの意味の詳細はここをご確認ください。 また、Kubernetesに関係するコンポーネントに関してはかこちらがよくまとまっています。
kube-prometheusはmetrics APIサーバーを含むたminikubeが持つmetrics-serverは不要となります。
以下のコマンドで、無効化されているか確認します。
$ minikube addons disable metrics-server 🌑 「metrics-server」アドオンは無効化されています
minikubeにkube-prometeusをインストールする
まず、動かしているクラスターのバージョンによって提供されているブランチを選択する必要があるようです。今回クラスターは1.20系で動かしているので、現状ではブランチはrelease-0.7
かHEAD
を使う必要がありそうです。その他詳細な対応表に関してはこちらを確認してください。
今回はrelease-0.7
を利用したいと思います。
以下のようにソースコードをクローンしてきて、release-07
ブランチをチェックアウトします。
$ git clone https://github.com/prometheus-operator/kube-prometheus.git $ cd kube-prometheus/ $ git checkout -b release-0.7 origin/release-0.7 $ git branch main * release-0.7
チェックアウトまでできたら、必要なコンポーネントをクラスタにデプロイします。
kube-prometeusのセットアップは2段階で行なう必要があります。
* manifests/setup
ディレクトリに用意されているマニフェストでCRDs(Custom Resource Definition)とネームスペースを作成する
* CDRとネームスペースができたらmanifests
配下にあるリソースをデプロイする
具体的には以下のコマンドを実行します。
$ kubectl create -f manifests/setup $ kubectl create -f manifests/
ちなみにkube-prometeusを削除したい場合は以下のコマンドをを実行します。
$ kubectl delete --ignore-not-found=true -f manifests/ -f manifests/setup
Grafanaのダッシュボードを確認する
デプロイされたGrafanaとPrometheusのダッシュボードにアクセスするためには以下のコマンドでポートフォワードを行います。
$ kubectl --namespace monitoring port-forward svc/prometheus-k8s 9090 & \ kubectl --namespace monitoring port-forward svc/grafana 3000 Forwarding from 127.0.0.1:9090 -> 9090 Forwarding from [::1]:9090 -> 9090 Forwarding from 127.0.0.1:3000 -> 3000 Forwarding from [::1]:3000 -> 3000
Prometheusはhttp://localhost:9090、Grafanaはhttp://localhost:3000でそれぞれアクセスできます。
Grafanaの画面にアクセスするとデフォルトで諸々の設定を行ってくれたダッシュボードを確認することができます。
NginxのPodをデプロイしてメトリクスを見てみる
最後に1つネームスペースを作ってNginxのPodをデプロイしてみて、可視化されたメトリクスを確認してみたいと思います。
こちらのマニフェストを使ってNginxのPodを1つのレプリケイトで作るDeploymentを作成します。
まずは、ネームスペースを作成します。
$ kubectl create ns nginx-ns
次にDeoloymentを作成します。
$ kubectl -n nginx-ns apply -f https://raw.githubusercontent.com/samuraiball/settings/master/kubernetes/double-nginx-sample/deployment.yaml deployment.apps/nginx-first created deployment.apps/nginx-second created $ kubectl -n nginx-ns get po NAME READY STATUS RESTARTS AGE nginx-first-d6db6c668-mpbk7 1/1 Running 0 42s nginx-second-6b8d5c9696-wdt75 1/1 Running 0 42s
この状態でGrafanaの左サイドメニューからSearch
を選択し、「pod
」で検索します。
検索結果一覧のなかからKubernetes / Compute Resources / Namespace (Pods)
を選択します。
画面上部のnamespaceでnginx-ns
を選択するとデプロイしたNginxのPodのメトリクスがきちんと
MandrelでQuarkusのアプリをネイティブイメージ化する
はじめに
Quarkusのネイティブイメージ化したアプリを作ったことなかったのと、Mandrelという名前自体は聞いていたのですがQuarkusに関係するGraalVMぐらいの理解でしか無かったので、ちょっとまとめて動かしてみようかと思います。
基本的にはQuarkusのガイド(BUILDING A NATIVE EXECUTABLE)に従いつつやりますが、自分が気なったところを少しだけ深ぼってまとめるようにしようと思います。
Mandrelってなんぞ?
Oracle GraalVM Community Editionのダウンストリームに当たるGraalVMのディストリビューションの1つです。そのメインの目的としては、Quarkusのためにデザインされたネイティブイメージ化の方法を提供することにあります。基本的にアップストリームからの大きな変更はないようですが、Quakusのアプリに不要なものが取り除かれているようです。MandrelはOracle GraalVM CEと同じネイティブイメージ化の能力を提供しますが、polyglotなどのサポートが取り除かれているようです。Mandrelは現在、Linuxコンテナの環境におけるネイティブイメージのビルドだけの利用をが推奨されており、WindowsやMacOSに対するネイティブイメージを作成する場合は、 Oracle GraalVMを利用することが推奨されるようです。
理解が足りてない部分があるかもですが、おそらくCI上でのビルドやDockerのマルチステージビルドなどでMandrelを使えば諸々のコストの削減になるのでは無いかと思われます。
ネイティブイメージ化してみる
環境
今回の動作環境は以下のとおりです。
$ java --version openjdk 11.0.10 2021-01-19 OpenJDK Runtime Environment 18.9 (build 11.0.10+9) OpenJDK 64-Bit Server VM 18.9 (build 11.0.10+9, mixed mode) $ uname -srvmpio Linux 5.4.0-66-generic #74-Ubuntu SMP Wed Jan 27 22:54:38 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.2 LTS Release: 20.04 Codename: focal
Mandrelに関してはこのブログ内でインストールします。
Mandrelをインストールする
ダウンロードはGitHubのここのページからおこなうことが可能です。
具体的には以下の手順でセットアップをおこなします。
$ cd ${YOUR_GRAALVM_INSTALL_DIR} $ wget https://github.com/graalvm/mandrel/releases/download/mandrel-21.0.0.0-Final/mandrel-java11-linux-amd64-21.0.0.0-Final.tar.gz $ tar -xf mandrel-java11-linux-amd64-21.0.0.0-Final.tar.gz $ export JAVA_HOME="$( pwd )/mandrel-java11-21.0.0.0-Final" $ export GRAALVM_HOME="${JAVA_HOME}" $ export PATH="${JAVA_HOME}/bin:${PATH}"
QuarkusアプリのネイティブイメージのビルドはQuarkusのMavenラッパーを使って行いますが、その際に自身で指定するGraalVMでのビルドを行いたい場合はPathがとおっているGraalVMに対してnative-image
コマンドがインストールされている必要があります。
通常、native-image
コマンドはgu
等を使ってインストールしないと行けなかった気がしますが、Mandrelの場合最初から内包されているようです。
Nativeイメージ化するプロジェクトの作成
Quarkus - Start coding with code.quarkus.ioを使って、
こんな感じの設定でプロジェクトを作成します。
この際にExample CodeはYes, Please
を選択肢します(自分でコード書いても良いのですが、ちょいめんどくいさいので)。
Exampleのコードを生成を有効にしたので、GreetingResource.java
というハンドラーのコードが生成されているはずです。起動して、/hello-resteasy
のパスにアクセスするとHello RESTEasy
という文字列が返ってきます。
$ ./mvnw compile quarkus:dev # 別ターミナルで $ curl localhost:8080/hello-resteasy Hello RESTEasy
作成されたプロジェクトのPomをちょっと見てみる
作成されたプロジェクトのPomを見てみると以下のようなプロファイルの設定が記述されているのが確認できます。
<profile> <id>native</id> <activation> <property> <name>native</name> </property> </activation> <build> <plugins> <plugin> <artifactId>maven-failsafe-plugin</artifactId> <version>${surefire-plugin.version}</version> <executions> <execution> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> <configuration> <systemPropertyVariables> <native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path> <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager> <maven.home>${maven.home}</maven.home> </systemPropertyVariables> </configuration> </execution> </executions> </plugin> </plugins> </build> <properties> <quarkus.package.type>native</quarkus.package.type> </properties> </profile>
Maven Failsafe Pluginを用いたインテグレーションテストの設定が記述されています(詳しくは後述)。
ネイティブイメージをビルドする
Quarkusアプリをネイティブイメージでビルドする場合は以下のコマンドを用いて行います。
$ cd ${YOUR_PROJECT_DIR} $ ./mvnw package -Pnative
この際にPathがとおっているGraalVMにnative-image
コマンドが無かった場合は、以下のようなログを出力し、Dockerイメージをプルしてきてビルドを行ってくれます。
[WARNING] [io.quarkus.deployment.pkg.steps.NativeImageBuildStep] Cannot find the `native-image` in the GRAALVM_HOME, JAVA_HOME and System PATH. Install it using `gu install native-image` Attempting to fall back to container build.
ビルドが完了するとデフォルトではtarget
配下に実行可能なバイナリが${project.artifactId}-${project.version}-runner
の名前でできています。
今回作成されたバイナリは以下のように実行することができます。
$ ./target/native-image-1.0.0-SNAPSHOT-runner __ ____ __ _____ ___ __ ____ ______ --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ 2021-03-10 21:12:31,788 INFO [io.quarkus] (main) native-image 1.0.0-SNAPSHOT native (powered by Quarkus 1.12.1.Final) started in 0.030s. Listening on: http://0.0.0.0:8080 2021-03-10 21:12:31,789 INFO [io.quarkus] (main) Profile prod activated. 2021-03-10 21:12:31,789 INFO [io.quarkus] (main) Installed features: [cdi, resteasy]
ネイティブイメージを使ったテストを実行する
ネイティブイメージ化すると、jarでビルドする際と比較して予想しない問題が起こることがありえます。そのため、ネイティブイメージで動くアプリのインテグレーションテストを行なって置くことが推奨されます。
今回は、Exampleコードの生成を有効化しているためデフォルトで以下のようなNativeGreetingResourceIT
というクラスが生成されていると思います。
import io.quarkus.test.junit.NativeImageTest; @NativeImageTest public class NativeGreetingResourceIT extends { // Execute the same tests but in native mode. }
@NativeImageTest
を付与されたテストクラスにインテグレーションテストを記述しておくと、前述した、PomのMaven Failsafe Plugin
の設定で指定されるネイティブイメージを利用したテストを実施することが可能です。
ExampleではGreetingResourceTest
を拡張しており、このクラスはGreetingResourceをテストがRest Assuredで行われています。
このテストを実行されるには以下のコマンドを実行します。
$ ./mvnw verify -Pnative
このコマンドを実行すると、ネイティブイメージのビルドが行われ、その後、そのイメージを使ったテストが実施されます。
Goのembedを使う
はじめに
Goの1.16からembed packageがcoreライブラリに追加されています。結構面白い感じの機能だったのでちょっと試してみようかと思います。
go enbedとは
embedを利用すると静的ファイルをGoのプログラムに埋め込み、そこに対するアクセスを提供してくれます。embedでは以下の3つの形式でファイルを読み込むことができます
- string
- byte[]
- FS
ファイルの読み込みはパッケージのディレクトリかもしくはそのサブディレクトリから読み込むことができます。
使ってみる。
環境
動作環境は以下です。
$ go version go version go1.16 linux/amd64 $ uname -srvmpio Linux 5.4.0-66-generic #74-Ubuntu SMP Wed Jan 27 22:54:38 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.2 LTS Release: 20.04 Codename: focal
単体のテキストファイルをstringやbyte[]として読み込む
まずは基本的な使い方として単体のテキストファイルの読み込みをしてみます。
//go:embed
ディレクティブをコメントとして記述することで利用することができます。
まずは以下のようなテキストファイルをembed
ディレクティブを記述するプログラムと同じファイル階層に置いておきます。
hello.txt
hello, world. this is embedded.
このテキストファイルをembed
でstringとして、読み込むためには以下のようにします。
package main import ( _ "embed" "fmt" ) //go:embed hello.txt var hello string func main() { fmt.Println(hello) }
実行してみます。
$ go build -o hello_embed $ ./hello_embed hello, world. this is embedded.
注目ポイントはふたつで、まずはembed
を利用するためにパッケージのブランクインポートが必要です。
もう一つのポイントはembed
ディレクティブは関数の外側でパッケージグローバルに定義しておく必要があるということで例えばプログラムを以下のように変更するとエラーで落ちます。
func main() { //go:embed hello.txt var hello string fmt.Println(hello) }
$ go build -o hello_embed # github.com/samuraiball/go-sandbox ./main.go:10:4: go:embed cannot apply to var inside func
読み込んだファイルを[]byte
で受け取りたい場合は単に以下のように書き換えるだけで大丈夫です。
//go:embed hello.txt var hello []byte func main() { fmt.Println(hello) fmt.Println(string(hello)) }
実行結果
[104 101 108 108 111 44 32 119 111 114 108 100 46 10 116 104 105 115 32 105 115 32 101 109 98 101 100 100 101 100 46] hello, world. this is embedded.
複数ファイルの読み込み
1つ以上の複数ファイルを読み込みたい場合はembed.FS
を受け取りの型として利用することができます。
例えばhtml
ディレクトリをembed
を行なうプログラムファイルと同じ階層に作り以下の2つを用意します。
index1.html
<!DOCTYPE html> <html lang="en"> <body> <h1>Hello, Embed files 1</h1> </body> </html>
index2.thml
<!DOCTYPE html> <html lang="en"> <body> <h1>Hello, Embed files 2</h1> </body> </html>
まずindex1.html
と、index2.html
を読み込むためには以下のようにします。
//go:embed html/* var html embed.FS func main() { htmlBytes, err := html.ReadFile("html/index1.html") if err != nil { log.Fatal(err) } fmt.Printf(string(htmlBytes)) }
前述している通り、embed.FS
で受け取ります。そして、個別のファイルを受け取る場合は[func (f FS) ReadFile(name string) ([]byte, error)](https://golang.org/pkg/embed/#FS.ReadFile)
を利用し、読み込みたいディレクトリ/ファイル名
を文字列で渡すと利用することができます。この戻り値は []byte
になります。
実行結果は以下の通りになります。
<!DOCTYPE html> <html lang="en"> <body> <h1>Hello, Embed files 1</h1> </body> </html> ---------------------------------------------------- <!DOCTYPE html> <html lang="en"> <body> <h1>Hello, Embed files 2</h1> </body> </html>
ここで、ファイルの読み込みはpath.Matchパターンでファイルを読み込むことができます例えば//go:embed html/*1.html
に書き換えて先程のコードを実行すると、index1.html
が読み込まれるようになるため、index2, err := html.ReadFile("html/index2.html")
のところで落ちるようになります。
2021/03/05 19:12:12 open html/index2.html: file does not exist
また、embed.FS
には他にも、func (f FS) Open(name string) (fs.File, error)とfunc (f FS) ReadDir(name string) ([]fs.DirEntry, error)も定義されており、それぞれfs.File
でファイルを読み込んだり、[]fs.DirEntry
を読み込んだりすることができます。
複数ディレクトリからまとめてファイルを読み込む
embed
は複数のディレクトリからファイルを読み込むことも可能です。
先程のindex1.html
とindex2.html
をそれぞれhtml1
とhtml2
ディレクトリを作成して格納し、それらを読み込みたい場合は以下のようにします。
//go:embed html1/* html2/* var html embed.FS func main() { index1, err := html.ReadFile("html1/index1.html") // エラーハンドリング省略 index2, err := html.ReadFile("html2/index2.html") // エラーハンドリング省略 fmt.Print(string(index1)) fmt.Println() fmt.Printf("----------------------------------------------------") fmt.Println() fmt.Print(string(index2)) }
実行結果は先ほどと同じです。
<!DOCTYPE html> <html lang="en"> <body> <h1>Hello, Embed files 1</h1> </body> </html> ---------------------------------------------------- <!DOCTYPE html> <html lang="en"> <body> <h1>Hello, Embed files 2</h1> </body> </html>
gockでHTTPリクエストをMockする
はじめに
最近はGo言語でWebアプリを書く際に使う諸々のライブラリを試してみているのですが、今はMockサーバをいろいろ見ていました。
Goの場合は標準ライブラリでもhttptestでいろいろ用意されていているみたいです。ただちょっと、そのまま使うには手間が多そうに感じ、ほかを探してるとgockというのが良さげだったのでちょっと試してみようと思います。
gockとは
Go製のHTTPのMockライブラリーです。
以下のような特徴があります。
gockはhttp.Client
で利用されるhttp.DefaultTransport
かもしくはカスタムhttp.Transport
を経由して、HTTPのアウトバウンドのリクエストをモックします。
登録されたMockがFIFOでリクエストにマッチするかの検証が行われ、マッチした場合MockのHTTPレスポンスを返します。
そして、どのMockにもマッチしなかった場合は基本的にはエラーが起こるようです。ただし、リアルネットワークモードが有効化されている場合は実際のリクエストが代わりに実行されます。
このブログではあまり複雑なことはせずにひとまず動かしてみるところまでやってみようかと思います。
使ってみる
環境
プログラムを動かす環境は以下の通り
$ go version go version go1.16 linux/amd64 $ uname -srvmpio Linux 5.4.0-65-generic #73-Ubuntu SMP Mon Jan 18 17:25:17 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.2 LTS Release: 20.04 Codename: focal
プロジェクトを作る
プロジェクトを作成して、TestifyとGockをインストトールします。
$ go get github.com/stretchr/testify go get: added github.com/stretchr/testify v1.7.0 $ go get -u gopkg.in/h2non/gock.v1 go get: added gopkg.in/h2non/gock.v1 v1.0.16
これで下準備までは環境です。
Pongの文字列を返すMockを作る
準備ができたので、早速使っていこうと思います。
localhost:8081/ping
に対して”pong”
の文字列のレスポンス返す場合は以下のようにします。
import ( "github.com/stretchr/testify/assert" "gopkg.in/h2non/gock.v1" "io" "log" "net/http" "testing" ) const ( MOCK_URL = "localhost:8082" PING_PATH = "/ping" ) func TestName(t *testing.T) { defer gock.Off() gock.New(MOCK_URL). Get(PING_PATH). Reply(200). BodyString("pong") res, err := http.Get("http://" + MOCK_URL + PING_PATH) handleError(err) bodyByte, err := io.ReadAll(res.Body) handleError(err) assert.Equal(t, 200, res.StatusCode) assert.Equal(t, "pong", string(bodyByte)) assert.Equal(t, gock.IsDone(), true) } func handleError(err error) { if err != nil { log.Fatal(err) } }
gock
でMockをたてる場合はgock.New(MOCK_URL)
のように宣言的にMockを定義することができます。また、リクエストのヘッダーやボディ、リクエストパラムでマッピングを作成したい場合はReply(200).
より前のリクエストのパターンを構成するビルダーのメソットチェインの中で、それぞれMatchHeader
、MatchBody
、JSON
(Jsonのリクエストボディを受け取る場合)、MatchHeader
などの関数を呼び出すことで行えます。
例えば、MatchHeader("x-api-version", "1.[0-9]")
のように記述することができ、この記述の場合、ヘッダーにキーがx-api-version
で値が1.(0から9までの数字)
を含むリクエストに対してマッチングします。
レスポンスを作成する場合はReply()
関数を呼び出し、レスポンスボディは今回は文字列を返すのでBodyString
を呼び出しています。JSONの値を返したい場合はJSONメソッドを呼び出して、JSON(map[string]string{"foo": "bar"})
のように記述します。
アサーション部分では、最後のところがポイントでgock.IsDone()
を呼び出すことですべての設定したモックが呼び出されているかの検証を行なうことができます。