diff --git a/docs/guides/operator/advanced-configuration.adoc b/docs/guides/operator/advanced-configuration.adoc index e0572e5ef01f..73154125333e 100644 --- a/docs/guides/operator/advanced-configuration.adoc +++ b/docs/guides/operator/advanced-configuration.adoc @@ -118,7 +118,7 @@ The `unsupported` field of the CR contains highly experimental configuration opt ==== Pod Template The Pod Template is a raw API representation that is used for the Deployment Template. -This field is a temporary workaround in case no supported field exists at the top level of the CR for your use case. +This field is a temporary workaround in case no supported field exists at the top level of the CR for your use case. The Operator merges the fields of the provided template with the values generated by the Operator for the specific Deployment. With this feature, you have access to a high level of customizations. However, no guarantee exists that the Deployment will work as expected. @@ -175,7 +175,7 @@ spec: The Keycloak CR allows specifying the `resources` options for managing compute resources for the {project_name} container. It provides the ability to request and limit resources independently for the main Keycloak deployment via the Keycloak CR, and for the realm import Job via the Realm Import CR. -When no values are specified, the default `requests` memory is set to `1700MiB`, and the `limits` memory is set to `2GiB`. +When no values are specified, the default `requests` memory is set to `1700MiB`, and the `limits` memory is set to `2GiB`. Similarly, the default `requests` CPU is set to `500m`, and the `limits` CPU is set to `1`. These values were chosen based on a deeper analysis of {project_name} memory management. If no values are specified in the Realm Import CR, it falls back to the values specified in the Keycloak CR, or to the defaults as defined above. @@ -259,7 +259,7 @@ stringData: ... ------ -When running on a Kubernetes or OpenShift environment well-known locations of trusted certificates are included automatically. +When running on a Kubernetes or OpenShift environment well-known locations of trusted certificates are included automatically. This includes /var/run/secrets/kubernetes.io/serviceaccount/ca.crt and the /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt when present. diff --git a/operator/src/main/java/org/keycloak/operator/Config.java b/operator/src/main/java/org/keycloak/operator/Config.java index 4f54d14951e5..cdbd28a396b3 100644 --- a/operator/src/main/java/org/keycloak/operator/Config.java +++ b/operator/src/main/java/org/keycloak/operator/Config.java @@ -45,6 +45,7 @@ interface ResourceRequirements { interface Resources { Quantity memory(); + Quantity cpu(); } } } diff --git a/operator/src/main/java/org/keycloak/operator/Utils.java b/operator/src/main/java/org/keycloak/operator/Utils.java index 430ca6e5424f..042c7c2b8336 100644 --- a/operator/src/main/java/org/keycloak/operator/Utils.java +++ b/operator/src/main/java/org/keycloak/operator/Utils.java @@ -76,7 +76,7 @@ public static Map allInstanceLabels(HasMetadata primary) { public static Optional getByName(Class clazz, Function nameFunction, Keycloak primary, Context context) { InformerEventSource ies = (InformerEventSource) context .eventSourceRetriever().getResourceEventSourceFor(clazz); - + return ies.get(new ResourceID(nameFunction.apply(primary), primary.getMetadata().getNamespace())); } @@ -92,7 +92,9 @@ public static void addResources(ResourceRequirements resource, Config config, Co final var requests = Optional.ofNullable(resourcesSpec.getRequests()).orElseGet(HashMap::new); final var requestsMemory = requests.get("memory"); + final var requestsCpu = requests.get("cpu"); final var defaultRequestsMemory = config.keycloak().resources().requests().memory(); + final var defaultRequestsCpu = config.keycloak().resources().requests().cpu(); // Validate 'requests' memory if (requestsMemory != null) { @@ -104,9 +106,20 @@ public static void addResources(ResourceRequirements resource, Config config, Co requests.put("memory", defaultRequestsMemory); } + // Validate 'requests' cpu + if (requestsCpu != null) { + var specifiedCpuIsLessThanDefault = requestsCpu.getNumericalAmount().intValue() < defaultRequestsCpu.getNumericalAmount().intValue(); + if (specifiedCpuIsLessThanDefault) { + Log.debugf("Provided 'requests' cpu ('%s') is less than used default value ('%s'). Use it in your risk, as Keycloak performance might be degraded.", requestsCpu, defaultRequestsCpu); + } + } else { + requests.put("cpu", defaultRequestsCpu); + } + // sets the max boundary when the spec is not present final var limits = Optional.ofNullable(resourcesSpec.getLimits()).orElseGet(HashMap::new); limits.putIfAbsent("memory", config.keycloak().resources().limits().memory()); + limits.putIfAbsent("cpu", config.keycloak().resources().limits().cpu()); kcContainer.setResources(resourcesSpec); } diff --git a/operator/src/main/resources/application.properties b/operator/src/main/resources/application.properties index 0abbcaa7650d..b5a2d437f0ad 100644 --- a/operator/src/main/resources/application.properties +++ b/operator/src/main/resources/application.properties @@ -11,7 +11,9 @@ kc.operator.keycloak.start-optimized=false kc.operator.keycloak.poll-interval-seconds=60 # Keycloak container default requests/limits resources kc.operator.keycloak.resources.requests.memory=1700Mi +kc.operator.keycloak.resources.requests.cpu=500m kc.operator.keycloak.resources.limits.memory=2Gi +kc.operator.keycloak.resources.limits.cpu=1 # https://quarkus.io/guides/deploying-to-kubernetes#environment-variables-from-keyvalue-pairs quarkus.kubernetes.env.vars.related-image-keycloak=${kc.operator.keycloak.image} diff --git a/operator/src/test/java/org/keycloak/operator/testsuite/integration/KeycloakDeploymentTest.java b/operator/src/test/java/org/keycloak/operator/testsuite/integration/KeycloakDeploymentTest.java index afbee6f8c10f..7353263ed80f 100644 --- a/operator/src/test/java/org/keycloak/operator/testsuite/integration/KeycloakDeploymentTest.java +++ b/operator/src/test/java/org/keycloak/operator/testsuite/integration/KeycloakDeploymentTest.java @@ -705,10 +705,12 @@ public void testApplyingResourcesDefaultValues() { var requests = resources.getRequests(); assertThat(requests).isNotNull(); assertThat(requests.get("memory")).isEqualTo(config.keycloak().resources().requests().memory()); + assertThat(requests.get("cpu")).isEqualTo(config.keycloak().resources().requests().cpu()); var limits = resources.getLimits(); assertThat(limits).isNotNull(); assertThat(limits.get("memory")).isEqualTo(config.keycloak().resources().limits().memory()); + assertThat(limits.get("cpu")).isEqualTo(config.keycloak().resources().limits().cpu()); } private void handleFakeImagePullSecretCreation(Keycloak keycloakCR, diff --git a/operator/src/test/java/org/keycloak/operator/testsuite/integration/RealmImportTest.java b/operator/src/test/java/org/keycloak/operator/testsuite/integration/RealmImportTest.java index 62acab55ec7a..c285faa1db78 100644 --- a/operator/src/test/java/org/keycloak/operator/testsuite/integration/RealmImportTest.java +++ b/operator/src/test/java/org/keycloak/operator/testsuite/integration/RealmImportTest.java @@ -135,6 +135,7 @@ public void testWorkingRealmImport() { assertThat(job.getSpec().getTemplate().getSpec().getImagePullSecrets().get(0).getName()).isEqualTo("my-empty-secret"); assertResources(container, config.keycloak().resources().requests().memory(), config.keycloak().resources().limits().memory()); + assertResources(container, config.keycloak().resources().requests().cpu(), config.keycloak().resources().limits().cpu()); String url = "https://" + KeycloakServiceDependentResource.getServiceName(kc) + "." + namespace + ":" + KEYCLOAK_HTTPS_PORT + "/realms/count0";