Observability Dev Services with Grafana OTel LGTM
この Dev Service は、 Grafana OTel-LGTM を提供します。これは、テレメトリーデータを受信して Prometheus (メトリクス)、Tempo (トレース)、および Loki (ログ) に転送する OpenTelemetry Collector を含む all-in-one Docker イメージです。
このデータは、 Grafana で視覚化できます。LGTM という略語は次の意味です。
プロジェクトの設定
If you are using quarkus-opentelemetry or quarkus-micrometer-opentelemetry this Observability extension is already bundled and the container will start automatically in dev mode.
If the mentioned extensions are not present, you can manually add the Quarkus Grafana OTel LGTM sink (where data goes) extension to your build file:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-observability-devservices-lgtm</artifactId>
<scope>provided</scope>
</dependency>
implementation("io.quarkus:quarkus-observability-devservices-lgtm")
Micrometer
Micrometer Quarkus エクステンション は、Quarkus とそのエクステンションに実装された自動計装からのメトリクスを提供します。
Micrometer メトリクスを出力する方法は複数あります。次にいくつか例を示します。
Micrometer Prometheus レジストリーの使用
これは、Micrometer からメトリクスを出力する最も一般的な方法であり、Quarkus のデフォルトの方法です。Micrometer Prometheus レジストリーは、 /q/metrics エンドポイントでデータを公開し、Grafana LGTM Dev Service 内のスクレイパーがデータを取得します (サービスからデータを プル します)。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-micrometer-registry-prometheus</artifactId>
</dependency>
implementation("io.quarkus:quarkus-micrometer-registry-prometheus")
Micrometer OTLP レジストリーの使用
Quarkiverse Micrometer OTLP レジストリー は、OpenTelemetry OTLP プロトコルを使用してデータを Grafana LGTM Dev Service に出力します。これにより、データがサービスから プッシュ されます。
<dependency>
<groupId>io.quarkiverse.micrometer.registry</groupId>
<artifactId>quarkus-micrometer-registry-otlp</artifactId>
</dependency>
implementation("io.quarkiverse.micrometer.registry:quarkus-micrometer-registry-otlp")
Micrometer の Quarkiverse OTLP レジストリーを使用してメトリクスを Grafana OTel LGTM にプッシュする場合、Docker コンテナーの外部から見える OTel コレクターエンドポイントに quarkus.micrometer.export.otlp.url プロパティーが自動的に設定されます。
OpenTelemetry
OpenTelemetry を使用すると、メトリクス、トレース、ログを作成し、Grafana LGTM Dev Service に送信できます。
デフォルトでは、 OpenTelemetry エクステンション は トレース を生成します。 メトリクス と ログ は個別に有効にする必要があります。
quarkus-opentelemetry エクステンションは、次のようにビルドファイルに追加できます。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-opentelemetry</artifactId>
</dependency>
implementation("io.quarkus:quarkus-opentelemetry")
quarkus.otel.exporter.otlp.endpoint プロパティーは、Docker コンテナーの外部から見える OTel コレクターエンドポイントに自動的に設定されます。
quarkus.otel.exporter.otlp.protocol は http/protobuf に設定されています。
Micrometer から OpenTelemetry へのブリッジ
このエクステンションは、Micrometer メトリクスと OpenTelemetry メトリクス、トレース、およびログを提供します。データはすべて OpenTelemetry エクステンションによって管理され、送信されます。
すべてのシグナルがデフォルトで有効になります。
エクステンションは次のようにビルドファイルに追加できます。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-micrometer-opentelemetry</artifactId>
</dependency>
implementation("io.quarkus:quarkus-micrometer-opentelemetry")
Grafana
Grafana UI へのアクセス
アプリケーションを開発モードで起動します。
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
次のようなログエントリーが表示されます。
[io.qu.ob.de.ObservabilityDevServiceProcessor] (build-35) Dev Service Lgtm started, config: {grafana.endpoint=http://localhost:42797, quarkus.otel.exporter.otlp.endpoint=http://localhost:34711, otel-collector.url=localhost:34711, quarkus.micrometer.export.otlp.url=http://localhost:34711/v1/metrics, quarkus.otel.exporter.otlp.protocol=http/protobuf}
Grafana はエフェメラルポートでアクセス可能であるため、どのポートが使用されているかを確認するにはログを確認する必要があります。この例では、Grafana エンドポイントは grafana.endpoint=http://localhost:42797 です。
もう 1 つの方法は、Dev UI (http://localhost:8080/q/dev-ui/extensions) を使用することです。Grafana URL リンクが使用可能になり、選択すると、実行中の Grafana インスタンスに直接新しいブラウザータブが開きます。
探索
Explore セクションでは、すべてのデータソースのデータをクエリーできます。
トレースを表示するには、 tempo データソースを選択し、データをクエリーします。
ログの場合は、 loki データソースを選択し、データをクエリーします。
ダッシュボード
Dev Service にはダッシュボードのセットが含まれています。
各ダッシュボードは、特定のアプリケーション設定に合わせて調整されています。使用可能なダッシュボードは次のとおりです。
-
Quarkus Micrometer OpenTelemetry: Micrometer および OpenTelemetry エクステンションで使用されます。
-
Quarkus Micrometer OTLP registry: Micrometer OTLP レジストリーエクステンションで使用されます。
-
Quarkus Micrometer Prometheus registry: Micrometer Prometheus レジストリーエクステンションで使用されます。
-
Quarkus OpenTelemetry Logging: OpenTelemetry エクステンションからのログを表示します。
|
ダッシュボードの一部のパネルでは、値がスライディングタイムウィンドウにわたって計算される場合、正確なデータが表示されるまでに数分かかることがあります。 |
カスタムダッシュボード
ユーザーは独自のGrafanaダッシュボードと独自の構成を追加できます。grafana-dashboard-[your name].json ファイルをアプリケーションの META-INF/grafana ディレクトリーに配置すると、LGTM Dev Services が自動的にそれを検出し、このダッシュボード構成を全体のGrafanaダッシュボード YAML 構成に含めます。
例えば、/META-INF/grafana/grafana-dashboard-my-simple-prometheus-dashboard.json 構成ファイルは、My Simple Prometheus Dashboard という名前のダッシュボードを作成します。
追加設定
このエクステンションは、Grafana OTel LGTM イメージにバンドルされている OTel Collector にデータを送信するために、 quarkus-opentelemetry および quarkus-micrometer-registry-otlp エクステンションを設定します。
Dev Services に関する面倒な作業 (既存の実行中のコンテナーの検索や再利用など) を避ける場合は、Dev Services を無効にして、Dev Resource の使用のみを有効にすることができます。
quarkus.observability.enabled=false
quarkus.observability.dev-resources=true
デフォルトでは、Quarkus は Testcontainers に Grafana と OTel のランダムなポートを公開させますが、ポートを自分で設定することで、ユーザー側で固定ポートを使用するように強制できます。ただし、その場合はポート競合に注意してください。
quarkus.observability.lgtm.grafana-port=3001
quarkus.observability.lgtm.otel-grpc-port=5317
quarkus.observability.lgtm.otel-http-port=5318
|
内部コンテナーポートは同じままです。例: Grafana は 3000、OTel gRPC は 4317、OTel HTTP は 4318 など。 |
Disabling observability Dev Services
Observability Dev Services are enabled by default in dev mode. To disable them entirely (both in dev and test mode), preventing the Grafana LGTM container from starting automatically, set:
quarkus.observability.enabled=false
This also disables the Dev UI card, the shared container discovery, compose support and the OpenTelemetry override properties.
Enabling observability Dev Services in tests
By default, observability Dev Services do not start in test mode. To enable the Grafana LGTM container during tests, set:
quarkus.observability.enabled-in-tests=true
This property has no effect in dev mode, where Dev Services are controlled solely by quarkus.observability.enabled.
For the least 'auto-magical' usage in the tests, you can explicitly list LGTM Dev Resource in the test as a @QuarkusTestResource resource:
@QuarkusTest
@QuarkusTestResource(value = LgtmResource.class, restrictToAnnotatedClass = true)
@TestProfile(QuarkusTestResourceTestProfile.class)
public class LgtmLifecycleTest extends LgtmTestBase {
}
Dev Resources mode
As an alternative to full Dev Services, you can enable a simplified Dev Resources mode. In this mode, containers are started lazily at runtime via a MicroProfile ConfigSource instead of during the build phase. This means there is no shared container discovery, no compose support, no Dev UI card and no OpenTelemetry override properties injected.
quarkus.observability.enabled=false
quarkus.observability.dev-resources=true
|
|
完全な Grafana OTel LGTM スタックのテスト - 例
既存の Quarkus MicroMeter OTLP レジストリーの使用
<dependency>
<groupId>io.quarkiverse.micrometer.registry</groupId>
<artifactId>quarkus-micrometer-registry-otlp</artifactId>
</dependency>
implementation("io.quarkiverse.micrometer.registry:quarkus-micrometer-registry-otlp")
Meter レジストリーをコードに注入するだけで、メトリクスが Grafana LGTM の OTLP HTTP エンドポイントに定期的にプッシュされるようになります。
@Path("/api")
public class SimpleEndpoint {
private static final Logger log = Logger.getLogger(SimpleEndpoint.class);
@Inject
MeterRegistry registry;
@PostConstruct
public void start() {
Gauge.builder("xvalue", arr, a -> arr[0])
.baseUnit("X")
.description("Some random x")
.tag("my_key", "x")
.register(registry);
}
// ...
}
その後、Grafana のデータソース API で既存のメトリクスデータを確認できます。
public class LgtmTestBase {
@ConfigProperty(name = "grafana.endpoint")
String endpoint; // NOTE -- injected Grafana endpoint!
@Test
public void testTracing() {
String response = RestAssured.get("/api/poke?f=100").body().asString();
System.out.println(response);
GrafanaClient client = new GrafanaClient(endpoint, "admin", "admin");
Awaitility.await().atMost(61, TimeUnit.SECONDS).until(
client::user,
u -> "admin".equals(u.login));
Awaitility.await().atMost(61, TimeUnit.SECONDS).until(
() -> client.query("xvalue_X"),
result -> !result.data.result.isEmpty());
}
}
// simple Grafana HTTP client
public class GrafanaClient {
private static final ObjectMapper MAPPER = new ObjectMapper();
private final String url;
private final String username;
private final String password;
public GrafanaClient(String url, String username, String password) {
this.url = url;
this.username = username;
this.password = password;
}
private <T> void handle(
String path,
Function<HttpRequest.Builder, HttpRequest.Builder> method,
HttpResponse.BodyHandler<T> bodyHandler,
BiConsumer<HttpResponse<T>, T> consumer) {
try {
String credentials = username + ":" + password;
String encodedCredentials = Base64.getEncoder().encodeToString(credentials.getBytes());
HttpClient httpClient = HttpClient.newHttpClient();
HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(URI.create(url + path))
.header("Authorization", "Basic " + encodedCredentials);
HttpRequest request = method.apply(builder).build();
HttpResponse<T> response = httpClient.send(request, bodyHandler);
int code = response.statusCode();
if (code < 200 || code > 299) {
throw new IllegalStateException("Bad response: " + code + " >> " + response.body());
}
consumer.accept(response, response.body());
} catch (IOException e) {
throw new UncheckedIOException(e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}
public User user() {
AtomicReference<User> ref = new AtomicReference<>();
handle(
"/api/user",
HttpRequest.Builder::GET,
HttpResponse.BodyHandlers.ofString(),
(r, b) -> {
try {
User user = MAPPER.readValue(b, User.class);
ref.set(user);
} catch (JsonProcessingException e) {
throw new UncheckedIOException(e);
}
});
return ref.get();
}
public QueryResult query(String query) {
AtomicReference<QueryResult> ref = new AtomicReference<>();
handle(
"/api/datasources/proxy/1/api/v1/query?query=" + query,
HttpRequest.Builder::GET,
HttpResponse.BodyHandlers.ofString(),
(r, b) -> {
try {
QueryResult result = MAPPER.readValue(b, QueryResult.class);
ref.set(result);
} catch (JsonProcessingException e) {
throw new UncheckedIOException(e);
}
});
return ref.get();
}
}