Skip to content

Commit

Permalink
feat (quarkusio#7788) Add knative autoscaling properties
Browse files Browse the repository at this point in the history
  • Loading branch information
iocanel committed Jul 12, 2020
1 parent 48ea64c commit f8caea7
Show file tree
Hide file tree
Showing 16 changed files with 460 additions and 0 deletions.
@@ -0,0 +1,15 @@

package io.quarkus.kubernetes.deployment;

public enum AutoScalerClass {

/**
* Kubernetes Pod Autoscaler
**/
kpa,

/**
* Horizontal Pod Autoscaler
**/
hpa
}
@@ -0,0 +1,14 @@
package io.quarkus.kubernetes.deployment;

public class AutoScalerClassConverter {

public static io.dekorate.knative.config.AutoScalerClass convert(AutoScalerClass clazz) {
if (clazz == AutoScalerClass.hpa) {
return io.dekorate.knative.config.AutoScalerClass.hpa;
} else if (clazz == AutoScalerClass.kpa) {
return io.dekorate.knative.config.AutoScalerClass.kpa;
} else {
throw new IllegalStateException("Failed to map autoscaler class: " + clazz + "!");
}
}
}
@@ -0,0 +1,48 @@

package io.quarkus.kubernetes.deployment;

import java.util.Optional;

import io.quarkus.runtime.annotations.ConfigGroup;

@ConfigGroup
public class AutoScalingConfig {

/**
* The Autoscaler class.
* Knative Serving comes with its own autoscaler, the KPA (Knative Pod Autoscaler) but can also be configured to use
* Kubernetes’ HPA (Horizontal Pod Autoscaler) or even a custom third-party autoscaler.
* Possible values (kpa, hpa, default: kpa).
*
* @return The autoscaler class.
*/
Optional<AutoScalerClass> autoScalerClass;

/**
* The autoscaling metric to use.
* Possible values (concurency, rps, cpu).
*
* @return The cpu metric or NONE if no metric has been selected.
*/
Optional<AutoScalingMetric> metric;

/**
* The autoscaling target.
*
* @return the selected target or zero if no target is selected.
*/
Optional<Integer> target;

/**
* The exact amount of requests allowed to the replica at a time.
* Its default value is “0”, which means an unlimited number of requests are allowed to flow into the replica.
*
* @return the container concurrenct or zero if its not bound.
*/
Optional<Integer> containerConcurrency;

/**
* This value specifies a percentage of the target to actually be targeted by the autoscaler.
*/
Optional<Integer> targetUtilizationPercentage;
}
@@ -0,0 +1,19 @@

package io.quarkus.kubernetes.deployment;

public enum AutoScalingMetric {
/**
* Concurrency
*/
concurrency,

/**
* Requests per second
**/
rps,

/**
* CPU
**/
cpu;
}
@@ -0,0 +1,16 @@
package io.quarkus.kubernetes.deployment;

public class AutoScalingMetricConverter {

public static io.dekorate.knative.config.AutoscalingMetric convert(AutoScalingMetric metric) {
if (metric == AutoScalingMetric.concurrency) {
return io.dekorate.knative.config.AutoscalingMetric.concurrency;
} else if (metric == AutoScalingMetric.rps) {
return io.dekorate.knative.config.AutoscalingMetric.rps;
} else if (metric == AutoScalingMetric.cpu) {
return io.dekorate.knative.config.AutoscalingMetric.cpu;
} else {
throw new IllegalStateException("Failed to map autoscaling metric: " + metric + "!");
}
}
}
@@ -0,0 +1,38 @@
package io.quarkus.kubernetes.deployment;

import java.util.Optional;

import io.quarkus.runtime.annotations.ConfigGroup;

@ConfigGroup
public class GlobalAutoScalingConfig {

/**
* The Autoscaler class.
* Knative Serving comes with its own autoscaler, the KPA (Knative Pod Autoscaler) but can also be configured to use
* Kubernetes’ HPA (Horizontal Pod Autoscaler) or even a custom third-party autoscaler.
* Possible values (kpa, hpa, default: kpa).
*
* @return The autoscaler class.
*/
Optional<AutoScalerClass> autoScalerClass;

/**
* The exact amount of requests allowed to the replica at a time.
* Its default value is “0”, which means an unlimited number of requests are allowed to flow Integer>o the replica.
*
* @return the container concurrenct or zero if its not bound.
*/
Optional<Integer> containerConcurrency;

/**
* This value specifies a percentage of the target to actually be targeted by the autoscaler.
*/
Optional<Integer> targetUtilizationPercentage;

/**
* The requests per second per replica.
*/
Optional<Integer> requestsPerSecond;

}
Expand Up @@ -334,4 +334,34 @@ public EnvVarsConfig getEnv() {
@ConfigItem
public boolean clusterLocal;

/**
* This value controls the minimum number of replicas each revision should have.
* Knative will attempt to never have less than this number of replicas at any one point in time.
*/
@ConfigItem
Optional<Integer> minScale;

/**
* This value controls the maximum number of replicas each revision should have.
* Knative will attempt to never have more than this number of replicas running, or in the process of being created, at any
* one point in time.
**/
@ConfigItem
Optional<Integer> maxScale;

/**
* The scale-to-zero values control whether Knative allows revisions to scale down to zero, or stops at “1”.
*/
@ConfigItem(defaultValue = "true")
boolean scaleToZeroEnabled;

/**
* Revision autoscaling configuration.
*/
AutoScalingConfig revisionAutoScaling;

/**
* Global autoscaling configuration.
*/
GlobalAutoScalingConfig globalAutoScaling;
}
Expand Up @@ -51,6 +51,17 @@
import io.dekorate.Session;
import io.dekorate.SessionReader;
import io.dekorate.SessionWriter;
import io.dekorate.knative.decorator.ApplyGlobalAutoscalingClassDecorator;
import io.dekorate.knative.decorator.ApplyGlobalContainerConcurrencyDecorator;
import io.dekorate.knative.decorator.ApplyGlobalRequestsPerSecondTargetDecorator;
import io.dekorate.knative.decorator.ApplyGlobalTargetUtilizationDecorator;
import io.dekorate.knative.decorator.ApplyLocalAutoscalingClassDecorator;
import io.dekorate.knative.decorator.ApplyLocalAutoscalingMetricDecorator;
import io.dekorate.knative.decorator.ApplyLocalAutoscalingTargetDecorator;
import io.dekorate.knative.decorator.ApplyLocalContainerConcurrencyDecorator;
import io.dekorate.knative.decorator.ApplyLocalTargetUtilizationPercentageDecorator;
import io.dekorate.knative.decorator.ApplyMaxScaleDecorator;
import io.dekorate.knative.decorator.ApplyMinScaleDecorator;
import io.dekorate.kubernetes.annotation.ImagePullPolicy;
import io.dekorate.kubernetes.annotation.ServiceType;
import io.dekorate.kubernetes.config.Annotation;
Expand All @@ -63,6 +74,8 @@
import io.dekorate.kubernetes.decorator.AddAwsElasticBlockStoreVolumeDecorator;
import io.dekorate.kubernetes.decorator.AddAzureDiskVolumeDecorator;
import io.dekorate.kubernetes.decorator.AddAzureFileVolumeDecorator;
import io.dekorate.kubernetes.decorator.AddConfigMapDataDecorator;
import io.dekorate.kubernetes.decorator.AddConfigMapResourceProvidingDecorator;
import io.dekorate.kubernetes.decorator.AddConfigMapVolumeDecorator;
import io.dekorate.kubernetes.decorator.AddEnvVarDecorator;
import io.dekorate.kubernetes.decorator.AddImagePullSecretDecorator;
Expand Down Expand Up @@ -500,6 +513,56 @@ private void applyKnativeConfig(Session session, Project project, String name, K
.withValue("cluster-local")
.build()));
}

config.minScale.ifPresent(min -> session.resources().decorate(KNATIVE, new ApplyMinScaleDecorator(name, min)));

config.maxScale.ifPresent(max -> session.resources().decorate(KNATIVE, new ApplyMaxScaleDecorator(name, max)));

config.revisionAutoScaling.autoScalerClass.map(AutoScalerClassConverter::convert)
.ifPresent(a -> session.resources().decorate(KNATIVE, new ApplyLocalAutoscalingClassDecorator(name, a)));

config.revisionAutoScaling.metric.map(AutoScalingMetricConverter::convert)
.ifPresent(m -> session.resources().decorate(KNATIVE, new ApplyLocalAutoscalingMetricDecorator(name, m)));

config.revisionAutoScaling.containerConcurrency
.ifPresent(c -> session.resources().decorate(KNATIVE, new ApplyLocalContainerConcurrencyDecorator(name, c)));

config.revisionAutoScaling.targetUtilizationPercentage
.ifPresent(t -> session.resources().decorate(KNATIVE,
new ApplyLocalTargetUtilizationPercentageDecorator(name, t)));
config.revisionAutoScaling.target
.ifPresent(t -> session.resources().decorate(KNATIVE, new ApplyLocalAutoscalingTargetDecorator(name, t)));

config.globalAutoScaling.autoScalerClass
.map(AutoScalerClassConverter::convert)
.ifPresent(a -> {
session.resources().decorate(new AddConfigMapResourceProvidingDecorator("config-autoscaler"));
session.resources().decorate(new ApplyGlobalAutoscalingClassDecorator(a));
});

config.globalAutoScaling.containerConcurrency
.ifPresent(c -> {
session.resources().decorate(new AddConfigMapResourceProvidingDecorator("config-defaults"));
session.resources().decorate(new ApplyGlobalContainerConcurrencyDecorator(c));
});

config.globalAutoScaling.requestsPerSecond
.ifPresent(r -> {
session.resources().decorate(new AddConfigMapResourceProvidingDecorator("config-autoscaler"));
session.resources().decorate(new ApplyGlobalRequestsPerSecondTargetDecorator(r));
});

config.globalAutoScaling.targetUtilizationPercentage
.ifPresent(t -> {
session.resources().decorate(new AddConfigMapResourceProvidingDecorator("config-autoscaler"));
session.resources().decorate(new ApplyGlobalTargetUtilizationDecorator(t));
});

if (!config.scaleToZeroEnabled) {
session.resources().decorate(new AddConfigMapResourceProvidingDecorator("config-autoscaler"));
session.resources().decorate(new AddConfigMapDataDecorator("config-autoscaler", "enable-scale-to-zero",
String.valueOf(config.scaleToZeroEnabled)));
}
}

/**
Expand Down
@@ -0,0 +1,51 @@
package io.quarkus.it.kubernetes;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;

import java.io.IOException;
import java.nio.file.Path;
import java.util.List;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.quarkus.test.ProdBuildResults;
import io.quarkus.test.ProdModeTestResults;
import io.quarkus.test.QuarkusProdModeTest;

public class KnativeGlobalContainerConcurrency {

@RegisterExtension
static final QuarkusProdModeTest config = new QuarkusProdModeTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(GreetingResource.class))
.setApplicationName("knative-global-container-concurrency")
.setApplicationVersion("0.1-SNAPSHOT")
.withConfigurationResource("knative-global-container-concurrency.properties");

@ProdBuildResults
private ProdModeTestResults prodModeTestResults;

@Test
public void assertGeneratedResources() throws IOException {
Path kubernetesDir = prodModeTestResults.getBuildDir().resolve("kubernetes");
assertThat(kubernetesDir)
.isDirectoryContaining(p -> p.getFileName().endsWith("knative.json"))
.isDirectoryContaining(p -> p.getFileName().endsWith("knative.yml"))
.satisfies(p -> assertThat(p.toFile().listFiles()).hasSize(2));

List<HasMetadata> kubernetesList = DeserializationUtil
.deserializeAsList(kubernetesDir.resolve("knative.yml"));

assertThat(kubernetesList).filteredOn(i -> "ConfigMap".equals(i.getKind())).hasOnlyOneElementSatisfying(c -> {
assertThat(c.getMetadata()).satisfies(m -> assertThat(m.getName()).isEqualTo("config-autoscaler"));
assertThat(c).isInstanceOfSatisfying(ConfigMap.class, m -> {
assertThat(m.getData()).contains(entry("container-concurrency", "100"));
});
});
}
}
@@ -0,0 +1,51 @@
package io.quarkus.it.kubernetes;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;

import java.io.IOException;
import java.nio.file.Path;
import java.util.List;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.quarkus.test.ProdBuildResults;
import io.quarkus.test.ProdModeTestResults;
import io.quarkus.test.QuarkusProdModeTest;

public class KnativeGlobalRequestsPerSecond {

@RegisterExtension
static final QuarkusProdModeTest config = new QuarkusProdModeTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(GreetingResource.class))
.setApplicationName("knative-global-requests-per-second")
.setApplicationVersion("0.1-SNAPSHOT")
.withConfigurationResource("knative-global-requests-per-second.properties");

@ProdBuildResults
private ProdModeTestResults prodModeTestResults;

@Test
public void assertGeneratedResources() throws IOException {
Path kubernetesDir = prodModeTestResults.getBuildDir().resolve("kubernetes");
assertThat(kubernetesDir)
.isDirectoryContaining(p -> p.getFileName().endsWith("knative.json"))
.isDirectoryContaining(p -> p.getFileName().endsWith("knative.yml"))
.satisfies(p -> assertThat(p.toFile().listFiles()).hasSize(2));

List<HasMetadata> kubernetesList = DeserializationUtil
.deserializeAsList(kubernetesDir.resolve("knative.yml"));

assertThat(kubernetesList).filteredOn(i -> "ConfigMap".equals(i.getKind())).hasOnlyOneElementSatisfying(c -> {
assertThat(c.getMetadata()).satisfies(m -> assertThat(m.getName()).isEqualTo("config-autoscaler"));
assertThat(c).isInstanceOfSatisfying(ConfigMap.class, m -> {
assertThat(m.getData()).contains(entry("requests-per-second-target-default", "150"));
});
});
}
}

0 comments on commit f8caea7

Please sign in to comment.