scrapes pod container statuses when waiting for more to become available (#21257)

Closes #10285
This commit is contained in:
Steven Hawkins 2023-06-30 04:49:48 -04:00 committed by GitHub
parent c0b0a25f71
commit 5ee21ab6d1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 60 additions and 1 deletions

View file

@ -17,17 +17,22 @@
package org.keycloak.operator.controllers;
import io.fabric8.kubernetes.api.model.Container;
import io.fabric8.kubernetes.api.model.ContainerState;
import io.fabric8.kubernetes.api.model.ContainerStateWaiting;
import io.fabric8.kubernetes.api.model.EnvVar;
import io.fabric8.kubernetes.api.model.EnvVarBuilder;
import io.fabric8.kubernetes.api.model.EnvVarSourceBuilder;
import io.fabric8.kubernetes.api.model.HTTPGetActionBuilder;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.IntOrString;
import io.fabric8.kubernetes.api.model.PodStatus;
import io.fabric8.kubernetes.api.model.PodTemplateSpec;
import io.fabric8.kubernetes.api.model.ResourceRequirements;
import io.fabric8.kubernetes.api.model.apps.StatefulSet;
import io.fabric8.kubernetes.api.model.apps.StatefulSetBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.readiness.Readiness;
import io.fabric8.kubernetes.client.utils.Serialization;
import io.quarkus.logging.Log;
import org.keycloak.common.util.CollectionUtil;
@ -487,6 +492,7 @@ public class KeycloakDeployment extends OperatorManagedResource implements Statu
} else {
status.apply(b -> b.withInstances(existingDeployment.getStatus().getReadyReplicas()));
if (Optional.ofNullable(existingDeployment.getStatus().getReadyReplicas()).orElse(0) < keycloakCR.getSpec().getInstances()) {
checkForPodErrors(status);
status.addNotReadyMessage("Waiting for more replicas");
}
}
@ -503,6 +509,33 @@ public class KeycloakDeployment extends OperatorManagedResource implements Statu
distConfigurator.validateOptions(status);
}
private void checkForPodErrors(KeycloakStatusAggregator status) {
client.pods().inNamespace(existingDeployment.getMetadata().getNamespace())
.withLabel("controller-revision-hash", existingDeployment.getStatus().getUpdateRevision())
.withLabels(getInstanceLabels())
.list().getItems().stream()
.filter(p -> !Readiness.isPodReady(p)
&& Optional.ofNullable(p.getStatus()).map(PodStatus::getContainerStatuses).isPresent())
.sorted((p1, p2) -> p1.getMetadata().getName().compareTo(p2.getMetadata().getName()))
.forEachOrdered(p -> {
Optional.of(p.getStatus()).map(s -> s.getContainerStatuses()).stream().flatMap(List::stream)
.filter(cs -> !Boolean.TRUE.equals(cs.getReady()))
.sorted((cs1, cs2) -> cs1.getName().compareTo(cs2.getName())).forEachOrdered(cs -> {
if (Optional.ofNullable(cs.getState()).map(ContainerState::getWaiting)
.map(ContainerStateWaiting::getReason).map(String::toLowerCase)
.filter(s -> s.contains("err") || s.equals("crashloopbackoff")).isPresent()) {
Log.infof("Found unhealthy container on pod %s/%s: %s",
p.getMetadata().getNamespace(), p.getMetadata().getName(),
Serialization.asYaml(cs));
status.addErrorMessage(
String.format("Waiting for %s/%s due to %s: %s", p.getMetadata().getNamespace(),
p.getMetadata().getName(), cs.getState().getWaiting().getReason(),
cs.getState().getWaiting().getMessage()));
}
});
});
}
public Set<String> getConfigSecretsNames() {
Set<String> ret = new HashSet<>(serverConfigSecretsNames);
ret.addAll(distConfigurator.getSecretNames());
@ -539,7 +572,7 @@ public class KeycloakDeployment extends OperatorManagedResource implements Statu
&& previousDeployment.getStatus().getReplicas() > 1) {
// TODO Check if migration is really needed (e.g. based on actual KC version); https://github.com/keycloak/keycloak/issues/10441
Log.info("Detected changed Keycloak image, assuming Keycloak upgrade. Scaling down the deployment to one instance to perform a safe database migration");
Log.infof("original image: %s; new image: %s");
Log.infof("original image: %s; new image: %s", previousContainer.getImage(), reconciledContainer.getImage());
reconciledContainer.setImage(previousContainer.getImage());
reconciledDeployment.getSpec().setReplicas(1);

View file

@ -41,6 +41,7 @@ import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatusCondition;
import org.keycloak.operator.crds.v2alpha1.deployment.ValueOrSecret;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HostnameSpecBuilder;
import org.keycloak.operator.testsuite.utils.CRAssert;
import org.keycloak.operator.testsuite.utils.K8sUtils;
import java.nio.charset.StandardCharsets;
@ -555,6 +556,31 @@ public class KeycloakDeploymentTest extends BaseOperatorTest {
}
}
@Test
public void testInvalidCustomImageHasErrorMessage() {
try {
var kc = getDefaultKeycloakDeployment();
kc.getSpec().setImage("does-not-exist");
deployKeycloak(k8sclient, kc, false);
var crSelector = k8sclient.resource(kc);
Awaitility.await().atMost(3, MINUTES).pollDelay(1, SECONDS).ignoreExceptions().untilAsserted(() -> {
Keycloak current = crSelector.get();
CRAssert.assertKeycloakStatusCondition(current, KeycloakStatusCondition.READY, false);
CRAssert.assertKeycloakStatusCondition(current, KeycloakStatusCondition.HAS_ERRORS, true,
String.format("Waiting for %s/%s-0 due to ErrImage", k8sclient.getNamespace(),
kc.getMetadata().getName()));
});
} catch (Exception e) {
savePodLogs();
throw e;
}
}
@Test
public void testHttpRelativePathWithPlainValue() {
try {