mirror of
https://github.com/keycloak/keycloak.git
synced 2026-05-07 23:50:03 -05:00
Supported option to specify resource management for pods in Keycloak CR (#26661)
Closes #26456 Signed-off-by: Martin Bartoš <mabartos@redhat.com>
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.keycloak.operator;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Quantity;
|
||||
import io.smallrye.config.ConfigMapping;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -34,6 +35,16 @@ public interface Config {
|
||||
boolean startOptimized();
|
||||
int pollIntervalSeconds();
|
||||
|
||||
ResourceRequirements resources();
|
||||
Map<String, String> podLabels();
|
||||
}
|
||||
|
||||
interface ResourceRequirements {
|
||||
Resources requests();
|
||||
Resources limits();
|
||||
|
||||
interface Resources {
|
||||
Quantity memory();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,12 +17,14 @@
|
||||
|
||||
package org.keycloak.operator;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Container;
|
||||
import io.fabric8.kubernetes.api.model.HasMetadata;
|
||||
import io.fabric8.kubernetes.api.model.ResourceRequirements;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.javaoperatorsdk.operator.api.reconciler.Context;
|
||||
import io.javaoperatorsdk.operator.processing.event.ResourceID;
|
||||
import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource;
|
||||
|
||||
import io.quarkus.logging.Log;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -30,6 +32,7 @@ import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -77,4 +80,35 @@ public final class Utils {
|
||||
return ies.get(new ResourceID(nameFunction.apply(primary), primary.getMetadata().getNamespace()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set resources requests/limits for Keycloak container
|
||||
* </p>
|
||||
* If not specified in the Keycloak CR, set default values from operator config
|
||||
*/
|
||||
public static void addResources(ResourceRequirements resource, Config config, Container kcContainer) {
|
||||
final ResourceRequirements resourcesSpec = Optional.ofNullable(resource).orElseGet(ResourceRequirements::new);
|
||||
|
||||
// sets the min boundary when the spec is not present
|
||||
final var requests = Optional.ofNullable(resourcesSpec.getRequests()).orElseGet(HashMap::new);
|
||||
|
||||
final var requestsMemory = requests.get("memory");
|
||||
final var defaultRequestsMemory = config.keycloak().resources().requests().memory();
|
||||
|
||||
// Validate 'requests' memory
|
||||
if (requestsMemory != null) {
|
||||
var specifiedMemoryIsLessThanDefault = requestsMemory.getNumericalAmount().intValue() < defaultRequestsMemory.getNumericalAmount().intValue();
|
||||
if (specifiedMemoryIsLessThanDefault) {
|
||||
Log.debugf("Provided 'requests' memory ('%s') is less than used default value ('%s'). Use it in your risk, as Keycloak performance might be degraded.", requestsMemory, defaultRequestsMemory);
|
||||
}
|
||||
} else {
|
||||
requests.put("memory", defaultRequestsMemory);
|
||||
}
|
||||
|
||||
// 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());
|
||||
|
||||
kcContainer.setResources(resourcesSpec);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -234,6 +234,9 @@ public class KeycloakController implements Reconciler<Keycloak>, EventSourceInit
|
||||
status.addWarningMessage(
|
||||
"The image of the keycloak container cannot be modified using podTemplate");
|
||||
}
|
||||
if (container.getResources() != null) {
|
||||
status.addWarningMessage("Resources requirements of the Keycloak container cannot be modified using podTemplate");
|
||||
}
|
||||
});
|
||||
|
||||
if (overlayTemplate.getSpec() != null &&
|
||||
|
||||
+3
@@ -22,6 +22,7 @@ import io.fabric8.kubernetes.api.model.EnvVar;
|
||||
import io.fabric8.kubernetes.api.model.EnvVarBuilder;
|
||||
import io.fabric8.kubernetes.api.model.EnvVarSource;
|
||||
import io.fabric8.kubernetes.api.model.EnvVarSourceBuilder;
|
||||
import io.fabric8.kubernetes.api.model.PodResourceClaim;
|
||||
import io.fabric8.kubernetes.api.model.PodSpec;
|
||||
import io.fabric8.kubernetes.api.model.PodTemplateSpec;
|
||||
import io.fabric8.kubernetes.api.model.Secret;
|
||||
@@ -66,6 +67,7 @@ import java.util.stream.Stream;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import static org.keycloak.operator.Utils.addResources;
|
||||
import static org.keycloak.operator.crds.v2alpha1.CRDUtils.isTlsConfigured;
|
||||
|
||||
@KubernetesDependent(labelSelector = Constants.DEFAULT_LABELS_AS_STRING)
|
||||
@@ -112,6 +114,7 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
|
||||
Container kcContainer = baseDeployment.getSpec().getTemplate().getSpec().getContainers().get(0);
|
||||
addTruststores(primary, baseDeployment, kcContainer, allSecrets);
|
||||
addEnvVars(baseDeployment, primary, allSecrets);
|
||||
addResources(primary.getSpec().getResourceRequirements(), operatorConfig, kcContainer);
|
||||
Optional.ofNullable(primary.getSpec().getCacheSpec())
|
||||
.ifPresent(c -> configureCache(primary, baseDeployment, kcContainer, c, context.getClient()));
|
||||
|
||||
|
||||
+5
-1
@@ -32,6 +32,7 @@ import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
|
||||
import io.javaoperatorsdk.operator.processing.event.source.EventSource;
|
||||
import io.quarkus.logging.Log;
|
||||
|
||||
import org.keycloak.operator.Config;
|
||||
import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImport;
|
||||
import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImportStatus;
|
||||
import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImportStatusBuilder;
|
||||
@@ -49,6 +50,9 @@ dependents = {
|
||||
})
|
||||
public class KeycloakRealmImportController implements Reconciler<KeycloakRealmImport>, ErrorStatusHandler<KeycloakRealmImport>, EventSourceInitializer<KeycloakRealmImport> {
|
||||
|
||||
@Inject
|
||||
Config config;
|
||||
|
||||
@Inject
|
||||
KubernetesClient client;
|
||||
|
||||
@@ -56,7 +60,7 @@ public class KeycloakRealmImportController implements Reconciler<KeycloakRealmIm
|
||||
|
||||
@Override
|
||||
public Map<String, EventSource> prepareEventSources(EventSourceContext<KeycloakRealmImport> context) {
|
||||
this.jobDependentResource = new KeycloakRealmImportJobDependentResource();
|
||||
this.jobDependentResource = new KeycloakRealmImportJobDependentResource(config);
|
||||
return EventSourceInitializer.nameEventSourcesFromDependentResource(context, jobDependentResource);
|
||||
}
|
||||
|
||||
|
||||
+16
-7
@@ -31,8 +31,10 @@ import io.javaoperatorsdk.operator.api.reconciler.Context;
|
||||
import io.javaoperatorsdk.operator.api.reconciler.dependent.GarbageCollected;
|
||||
import io.javaoperatorsdk.operator.processing.dependent.Creator;
|
||||
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource;
|
||||
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig;
|
||||
|
||||
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfigBuilder;
|
||||
import jakarta.inject.Inject;
|
||||
import org.keycloak.operator.Config;
|
||||
import org.keycloak.operator.Constants;
|
||||
import org.keycloak.operator.Utils;
|
||||
import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImport;
|
||||
@@ -40,14 +42,19 @@ import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImport;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.keycloak.operator.Utils.addResources;
|
||||
import static org.keycloak.operator.controllers.KeycloakDistConfigurator.getKeycloakOptionEnvVarName;
|
||||
|
||||
public class KeycloakRealmImportJobDependentResource extends KubernetesDependentResource<Job, KeycloakRealmImport> implements Creator<Job, KeycloakRealmImport>, GarbageCollected<KeycloakRealmImport> {
|
||||
|
||||
KeycloakRealmImportJobDependentResource() {
|
||||
private final Config config;
|
||||
|
||||
KeycloakRealmImportJobDependentResource(Config config) {
|
||||
super(Job.class);
|
||||
this.configureWith(new KubernetesDependentResourceConfig<Job>()
|
||||
.setLabelSelector(Constants.DEFAULT_LABELS_AS_STRING));
|
||||
this.config = config;
|
||||
this.configureWith(new KubernetesDependentResourceConfigBuilder<Job>()
|
||||
.withLabelSelector(Constants.DEFAULT_LABELS_AS_STRING)
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -61,7 +68,7 @@ public class KeycloakRealmImportJobDependentResource extends KubernetesDependent
|
||||
String secretName = KeycloakRealmImportSecretDependentResource.getSecretName(primary);
|
||||
String volumeName = KubernetesResourceUtil.sanitizeName(secretName + "-volume");
|
||||
|
||||
buildKeycloakJobContainer(keycloakPodTemplate.getSpec().getContainers().get(0), volumeName, primary.getRealmName());
|
||||
buildKeycloakJobContainer(keycloakPodTemplate.getSpec().getContainers().get(0), primary, volumeName);
|
||||
keycloakPodTemplate.getSpec().getVolumes().add(buildSecretVolume(volumeName, secretName));
|
||||
|
||||
var labels = keycloakPodTemplate.getMetadata().getLabels();
|
||||
@@ -114,7 +121,7 @@ public class KeycloakRealmImportJobDependentResource extends KubernetesDependent
|
||||
.build();
|
||||
}
|
||||
|
||||
private void buildKeycloakJobContainer(Container keycloakContainer, String volumeName, String realmName) {
|
||||
private void buildKeycloakJobContainer(Container keycloakContainer, KeycloakRealmImport keycloakRealmImport, String volumeName) {
|
||||
var importMntPath = "/mnt/realm-import/";
|
||||
|
||||
var command = List.of("/bin/bash");
|
||||
@@ -124,7 +131,7 @@ public class KeycloakRealmImportJobDependentResource extends KubernetesDependent
|
||||
var runBuild = !keycloakContainer.getArgs().contains(KeycloakDeploymentDependentResource.OPTIMIZED_ARG) ? "/opt/keycloak/bin/kc.sh --verbose build && " : "";
|
||||
|
||||
var commandArgs = List.of("-c",
|
||||
runBuild + "/opt/keycloak/bin/kc.sh --verbose import --optimized --file='" + importMntPath + realmName + "-realm.json' " + override);
|
||||
runBuild + "/opt/keycloak/bin/kc.sh --verbose import --optimized --file='" + importMntPath + keycloakRealmImport.getRealmName() + "-realm.json' " + override);
|
||||
|
||||
keycloakContainer.setCommand(command);
|
||||
keycloakContainer.setArgs(commandArgs);
|
||||
@@ -139,5 +146,7 @@ public class KeycloakRealmImportJobDependentResource extends KubernetesDependent
|
||||
// Disable probes since we are not really starting the server
|
||||
keycloakContainer.setReadinessProbe(null);
|
||||
keycloakContainer.setLivenessProbe(null);
|
||||
|
||||
addResources(keycloakRealmImport.getSpec().getResourceRequirements(), config, keycloakContainer);
|
||||
}
|
||||
}
|
||||
|
||||
+13
@@ -17,6 +17,7 @@
|
||||
package org.keycloak.operator.crds.v2alpha1.deployment;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.LocalObjectReference;
|
||||
import io.fabric8.kubernetes.api.model.ResourceRequirements;
|
||||
import io.fabric8.kubernetes.model.annotation.SpecReplicas;
|
||||
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.CacheSpec;
|
||||
@@ -95,6 +96,10 @@ public class KeycloakSpec {
|
||||
@JsonPropertyDescription("In this section you can configure Keycloak's cache")
|
||||
private CacheSpec cacheSpec;
|
||||
|
||||
@JsonProperty("resources")
|
||||
@JsonPropertyDescription("Compute Resources required by Keycloak container")
|
||||
private ResourceRequirements resourceRequirements;
|
||||
|
||||
public HttpSpec getHttpSpec() {
|
||||
return httpSpec;
|
||||
}
|
||||
@@ -213,4 +218,12 @@ public class KeycloakSpec {
|
||||
this.cacheSpec = cache;
|
||||
}
|
||||
|
||||
public ResourceRequirements getResourceRequirements() {
|
||||
return resourceRequirements;
|
||||
}
|
||||
|
||||
public void setResourceRequirements(ResourceRequirements resourceRequirements) {
|
||||
this.resourceRequirements = resourceRequirements;
|
||||
}
|
||||
|
||||
}
|
||||
+13
@@ -16,8 +16,10 @@
|
||||
*/
|
||||
package org.keycloak.operator.crds.v2alpha1.realmimport;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
||||
import io.fabric8.generator.annotation.Required;
|
||||
import io.fabric8.kubernetes.api.model.ResourceRequirements;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
|
||||
public class KeycloakRealmImportSpec {
|
||||
@@ -29,6 +31,10 @@ public class KeycloakRealmImportSpec {
|
||||
@JsonPropertyDescription("The RealmRepresentation to import into Keycloak.")
|
||||
private RealmRepresentation realm;
|
||||
|
||||
@JsonProperty("resources")
|
||||
@JsonPropertyDescription("Compute Resources required by Keycloak container. If not specified, the value is inherited from the Keycloak CR.")
|
||||
private ResourceRequirements resourceRequirements;
|
||||
|
||||
public String getKeycloakCRName() {
|
||||
return keycloakCRName;
|
||||
}
|
||||
@@ -45,4 +51,11 @@ public class KeycloakRealmImportSpec {
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
public ResourceRequirements getResourceRequirements() {
|
||||
return resourceRequirements;
|
||||
}
|
||||
|
||||
public void setResourceRequirements(ResourceRequirements resourceRequirements) {
|
||||
this.resourceRequirements = resourceRequirements;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ kc.operator.keycloak.image=${RELATED_IMAGE_KEYCLOAK:quay.io/keycloak/keycloak:ni
|
||||
kc.operator.keycloak.image-pull-policy=Always
|
||||
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=768Mi
|
||||
kc.operator.keycloak.resources.limits.memory=4Gi
|
||||
|
||||
# https://quarkus.io/guides/deploying-to-kubernetes#environment-variables-from-keyvalue-pairs
|
||||
quarkus.kubernetes.env.vars.related-image-keycloak=${kc.operator.keycloak.image}
|
||||
|
||||
Reference in New Issue
Block a user