Enable the heatlh endpoints under a flag

This commit is contained in:
andreaTP 2022-02-22 15:49:39 +00:00 committed by Pedro Igor
parent 6249e34177
commit 59d9e3e3ee
19 changed files with 211 additions and 21 deletions

View file

@ -6,7 +6,7 @@
<@tmpl.guide
title="Running Keycloak in a container"
summary="Learn how to run Keycloak from a container image"
includedOptions="db db-url db-username db-password features hostname https-key-store-file https-key-store-password metrics-enabled">
includedOptions="db db-url db-username db-password features hostname https-key-store-file https-key-store-password health-enabled metrics-enabled">
Keycloak handles containerized environments such as Kubernetes or OpenShift as first-class citizens. This guide describes how to optimize and run the Keycloak container image to provide the best experience running a Keycloak container.
@ -14,13 +14,14 @@ Keycloak handles containerized environments such as Kubernetes or OpenShift as f
For the best start up of your Keycloak container, build an optimized container image by running the `build` step before starting.
=== Building your optimized Keycloak docker image
The following `Dockerfile` creates a pre-configured Keycloak image that enables the metrics endpoint, enables the token exchange feature, and uses a PostgreSQL database.
The following `Dockerfile` creates a pre-configured Keycloak image that enables the health and metrics endpoints, enables the token exchange feature, and uses a PostgreSQL database.
.Dockerfile:
[source, dockerfile]
----
FROM quay.io/keycloak/keycloak:latest as builder
ENV KC_HEALTH_ENABLED=true
ENV KC_METRICS_ENABLED=true
ENV KC_FEATURES=token-exchange
ENV KC_DB=postgres
@ -71,6 +72,8 @@ INFO [org.key.com.Profile] (main) Preview feature enabled: token_exchange
----
This message shows the desired feature is enabled.
Health check endpoints are available at `https://localhost:8443/health`, `https://localhost:8443/health/ready` and `https://localhost:8443/health/live`.
Opening up `https://localhost:8443/metrics` leads to a page containing operational metrics that could be used by your monitoring solution.
== Trying Keycloak in development mode

View file

@ -0,0 +1,39 @@
<#import "/templates/guide.adoc" as tmpl>
<#import "/templates/kc.adoc" as kc>
<#import "/templates/options.adoc" as opts>
<#import "/templates/links.adoc" as links>
<@tmpl.guide
title="Enabling Keycloak Health checks"
summary="Learn how to enable and use Keycloak health checks"
includedOptions="health-enabled">
Keycloak has built in support for health checks. This guide describes how to enable and use the Keycloak health checks.
== Keycloak Health checks
Keycloak exposed health endpoints are three:
* `/health`
* `/health/live`
* `/health/ready`
The result is returned in json format and it looks as follows:
[source, json]
----
{
"status": "UP",
"checks": [
{
"name": "Keycloak database connections health check",
"status": "UP"
}
]
}
----
== Enabling the health checks
Is possible to enable the health checks using the build time option `health-enabled`:
<@kc.build parameters="--health-enabled=true"/>
</@tmpl.guide>

View file

@ -88,6 +88,16 @@ The following table shows the recommended paths to expose.
|Yes
|Search engine rules
|/metrics
|-
|No
|Exposed metrics lead to an unnecessary attack vector.
|/health
|-
|No
|Exposed health checks lead to an unnecessary attack vector.
|===
We assume you run Keycloak on the root path `/` on your reverse proxy/gateway's public API.
If not, prefix the path with your desired one.

View file

@ -33,6 +33,6 @@ public final class Constants {
);
public static final Map<String, String> DEFAULT_DIST_CONFIG = Map.of(
"KC_METRICS_ENABLED", "true"
"KC_HEALTH_ENABLED", "true"
);
}

View file

@ -400,15 +400,13 @@ class KeycloakProcessor {
@Record(ExecutionTime.STATIC_INIT)
@BuildStep
void initializeMetrics(KeycloakRecorder recorder, BuildProducer<RouteBuildItem> routes, NonApplicationRootPathBuildItem nonAppRootPath) {
Handler<RoutingContext> healthHandler;
final Handler<RoutingContext> healthHandler = (isHealthEnabled()) ? new SmallRyeHealthHandler() : new NotFoundHandler();
Handler<RoutingContext> metricsHandler;
if (isMetricsEnabled()) {
healthHandler = new SmallRyeHealthHandler();
String rootPath = nonAppRootPath.getNormalizedHttpRootPath();
metricsHandler = recorder.createMetricsHandler(rootPath.concat(DEFAULT_METRICS_ENDPOINT).replace("//", "/"));
} else {
healthHandler = new NotFoundHandler();
metricsHandler = new NotFoundHandler();
}
@ -620,4 +618,8 @@ class KeycloakProcessor {
private boolean isMetricsEnabled() {
return Configuration.getOptionalBooleanValue(MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX.concat("metrics-enabled")).orElse(false);
}
private boolean isHealthEnabled() {
return Configuration.getOptionalBooleanValue(MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX.concat("health-enabled")).orElse(false);
}
}

View file

@ -5,4 +5,4 @@ hostname-strict-https=false
db=dev-mem
db-username = sa
db-password = keycloak
metrics-enabled=true
health-enabled=true

View file

@ -16,7 +16,10 @@
# Observability
# If the server should expose metrics and healthcheck endpoints.
# If the server should expose healthcheck endpoints.
#health-enabled=true
# If the server should expose metrics endpoints.
#metrics-enabled=true
# HTTP

View file

@ -51,7 +51,9 @@ import picocli.CommandLine.Command;
+ " $ ${PARENT-COMMAND-FULL-NAME:-$PARENTCOMMAND} ${COMMAND-NAME} --features=<feature_name>%n%n"
+ " Or alternatively, enable all tech preview features:%n%n"
+ " $ ${PARENT-COMMAND-FULL-NAME:-$PARENTCOMMAND} ${COMMAND-NAME} --features=preview%n%n"
+ " Enable metrics:%n%n"
+ " Enable health endpoints:%n%n"
+ " $ ${PARENT-COMMAND-FULL-NAME:-$PARENTCOMMAND} ${COMMAND-NAME} --health-enabled=true%n%n"
+ " Enable metrics endpoints:%n%n"
+ " $ ${PARENT-COMMAND-FULL-NAME:-$PARENTCOMMAND} ${COMMAND-NAME} --metrics-enabled=true%n%n"
+ " Change the relative path:%n%n"
+ " $ ${PARENT-COMMAND-FULL-NAME:-$PARENTCOMMAND} ${COMMAND-NAME} --http-relative-path=/auth%n%n"

View file

@ -8,10 +8,11 @@ public enum ConfigCategory {
FEATURE("Feature", 40),
HOSTNAME("Hostname", 50),
HTTP("HTTP/TLS", 60),
METRICS("Metrics", 70),
PROXY("Proxy", 80),
VAULT("Vault", 90),
LOGGING("Logging", 100),
HEALTH("Health", 70),
METRICS("Metrics", 80),
PROXY("Proxy", 90),
VAULT("Vault", 100),
LOGGING("Logging", 110),
GENERAL("General", 999);
private final String heading;

View file

@ -0,0 +1,26 @@
package org.keycloak.quarkus.runtime.configuration.mappers;
import java.util.Arrays;
final class HealthPropertyMappers {
private HealthPropertyMappers(){}
public static PropertyMapper[] getHealthPropertyMappers() {
return new PropertyMapper[] {
builder().from("health-enabled")
.to("quarkus.datasource.health.enabled")
.isBuildTimeProperty(true)
.defaultValue(Boolean.FALSE.toString())
.description("If the server should expose health check endpoints. If enabled, health checks are available at the '/health', '/health/ready' and '/health/live' endpoints.")
.paramLabel(Boolean.TRUE + "|" + Boolean.FALSE)
.expectedValues(Arrays.asList(Boolean.TRUE.toString(), Boolean.FALSE.toString()))
.build()
};
}
private static PropertyMapper.Builder builder() {
return PropertyMapper.builder(ConfigCategory.HEALTH);
}
}

View file

@ -13,7 +13,7 @@ final class MetricsPropertyMappers {
.to("quarkus.datasource.metrics.enabled")
.isBuildTimeProperty(true)
.defaultValue(Boolean.FALSE.toString())
.description("If the server should expose metrics and healthcheck. If enabled, metrics are available at the '/metrics' endpoint and healthcheck at the '/health' endpoint.")
.description("If the server should expose metrics. If enabled, metrics are available at the '/metrics' endpoint.")
.paramLabel(Boolean.TRUE + "|" + Boolean.FALSE)
.expectedValues(Arrays.asList(Boolean.TRUE.toString(), Boolean.FALSE.toString()))
.build()

View file

@ -26,6 +26,7 @@ public final class PropertyMappers {
MAPPERS.addAll(DatabasePropertyMappers.getDatabasePropertyMappers());
MAPPERS.addAll(HostnamePropertyMappers.getHostnamePropertyMappers());
MAPPERS.addAll(HttpPropertyMappers.getHttpPropertyMappers());
MAPPERS.addAll(HealthPropertyMappers.getHealthPropertyMappers());
MAPPERS.addAll(MetricsPropertyMappers.getMetricsPropertyMappers());
MAPPERS.addAll(ProxyPropertyMappers.getProxyPropertyMappers());
MAPPERS.addAll(VaultPropertyMappers.getVaultPropertyMappers());

View file

@ -5,6 +5,7 @@ db=dev-file
http-enabled=false
# Metrics and healthcheck are disabled by default
health-enabled=false
metrics-enabled=false
# Default, and insecure, and non-production grade configuration for the development profile

View file

@ -405,6 +405,12 @@ public class ConfigurationTest {
assertEquals("com.microsoft.sqlserver.jdbc.SQLServerXADataSource", config2.getConfigValue("quarkus.datasource.jdbc.driver").getValue());
assertEquals("xa", config2.getConfigValue("quarkus.datasource.jdbc.transactions").getValue());
}
public void testResolveHealthOption() {
System.setProperty(CLI_ARGS, "--health-enabled=true");
SmallRyeConfig config = createConfig();
assertEquals("true", config.getConfigValue("quarkus.datasource.health.enabled").getValue());
}
@Test
public void testResolveMetricsOption() {

View file

@ -23,6 +23,7 @@ import static org.keycloak.quarkus.runtime.Environment.forceTestLaunchMode;
import static org.keycloak.quarkus.runtime.cli.command.Main.CONFIG_FILE_LONG_NAME;
import static org.keycloak.quarkus.runtime.cli.command.Main.CONFIG_FILE_SHORT_NAME;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
@ -45,6 +46,7 @@ import io.quarkus.test.junit.main.Launch;
import io.quarkus.test.junit.main.LaunchResult;
import org.keycloak.quarkus.runtime.configuration.KeycloakPropertiesConfigSource;
import org.keycloak.quarkus.runtime.configuration.test.TestConfigArgsConfigSource;
import org.keycloak.quarkus.runtime.integration.QuarkusPlatform;
public class CLITestExtension extends QuarkusMainTestExtension {
@ -217,6 +219,9 @@ public class CLITestExtension extends QuarkusMainTestExtension {
setProperty("kc.db", database.alias());
// databases like mssql are very strict about password policy
setProperty("kc.db-password", "Password1!");
} else {
// This is for re-creating the H2 database instead of using the default in home
setProperty("kc.db-url-path", new QuarkusPlatform().getTmpDirectory().getAbsolutePath());
}
}

View file

@ -0,0 +1,61 @@
/*
* Copyright 2021 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.it.cli.dist;
import io.quarkus.test.junit.main.Launch;
import org.junit.jupiter.api.Test;
import org.keycloak.it.junit5.extension.DistributionTest;
import static io.restassured.RestAssured.when;
import static org.hamcrest.Matchers.containsString;
@DistributionTest(keepAlive =true)
public class HealthDistTest {
@Test
@Launch({ "start-dev" })
void testHealthEndpointNotEnabled() {
when().get("/health").then()
.statusCode(404);
when().get("/health/live").then()
.statusCode(404);
when().get("/health/ready").then()
.statusCode(404);
}
@Test
@Launch({ "start-dev", "--health-enabled=true" })
void testHealthEndpoint() {
when().get("/health").then()
.statusCode(200);
when().get("/health/live").then()
.statusCode(200);
when().get("/health/ready").then()
.statusCode(200);
// Metrics is endpoint independent
when().get("/metrics").then()
.statusCode(404);
}
@Test
@Launch({ "start-dev", "--health-enabled=true" })
void testHealthEndpointDoesNotEnableMetrics() {
when().get("/metrics").then()
.statusCode(404);
}
}

View file

@ -28,6 +28,13 @@ import io.quarkus.test.junit.main.Launch;
@DistributionTest(keepAlive =true)
public class MetricsDistTest {
@Test
@Launch({ "start-dev" })
void testMetricsEndpointNotEnabled() {
when().get("/metrics").then()
.statusCode(404);
}
@Test
@Launch({ "start-dev", "--metrics-enabled=true" })
void testMetricsEndpoint() {
@ -43,4 +50,11 @@ public class MetricsDistTest {
.statusCode(200)
.body(containsString("base_gc_total"));
}
@Test
@Launch({ "start-dev", "--metrics-enabled=true" })
void testMetricsEndpointDoesNotEnableHealth() {
when().get("/health").then()
.statusCode(404);
}
}

View file

@ -56,12 +56,18 @@ HTTP/TLS:
--http-relative-path <path>
Set the path relative to '/' for serving resources. Default: /.
Health:
--health-enabled <true|false>
If the server should expose health check endpoints. If enabled, health checks
are available at the '/health', '/health/ready' and '/health/live'
endpoints. Default: false.
Metrics:
--metrics-enabled <true|false>
If the server should expose metrics and healthcheck. If enabled, metrics are
available at the '/metrics' endpoint and healthcheck at the '/health'
endpoint. Default: false.
If the server should expose metrics. If enabled, metrics are available at the
'/metrics' endpoint. Default: false.
Vault:
@ -81,7 +87,11 @@ Examples:
$ kc.sh build --features=preview
Enable metrics:
Enable health endpoints:
$ kc.sh build --health-enabled=true
Enable metrics endpoints:
$ kc.sh build --metrics-enabled=true

View file

@ -118,12 +118,18 @@ HTTP/TLS:
The type of the trust store file. If not given, the type is automatically
detected based on the file name.
Health:
--health-enabled <true|false>
If the server should expose health check endpoints. If enabled, health checks
are available at the '/health', '/health/ready' and '/health/live'
endpoints. Default: false.
Metrics:
--metrics-enabled <true|false>
If the server should expose metrics and healthcheck. If enabled, metrics are
available at the '/metrics' endpoint and healthcheck at the '/health'
endpoint. Default: false.
If the server should expose metrics. If enabled, metrics are available at the
'/metrics' endpoint. Default: false.
Proxy: