feat(service create/update): Add support of minScale/maxScale/concurrency-target/concurrency-limit (#157)

Autoscaler concurrency annotations are added to the revision template if
--min-scale / --max-scale / --concurrency-target/--concurrency-limit
are provided to `kn service create` and `kn service update`

Fixes #151
This commit is contained in:
Roland Huß 2019-06-08 17:27:40 +02:00 committed by Knative Prow Robot
parent 8a1adf0cb0
commit b885e41974
8 changed files with 168 additions and 23 deletions

View File

@ -36,12 +36,16 @@ kn service create NAME --image IMAGE [flags]
### Options
```
--concurrency-limit int Hard Limit of concurrent requests to be processed by a single replica.
--concurrency-target int Recommendation for when to scale up based on the concurrent number of incoming request. Defaults to --concurrency-limit when given.
-e, --env stringArray Environment variable to set. NAME=value; you may provide this flag any number of times to set multiple environment variables.
--force Create service forcefully, replaces existing service if any.
-h, --help help for create
--image string Image to run.
--limits-cpu string The limits on the requested CPU (e.g., 1000m).
--limits-memory string The limits on the requested CPU (e.g., 1024Mi).
--max-scale int Maximal number of replicas.
--min-scale int Minimal number of replicas.
-n, --namespace string List the requested object(s) in given namespace.
--requests-cpu string The requested CPU (e.g., 250m).
--requests-memory string The requested CPU (e.g., 64Mi).

View File

@ -24,11 +24,15 @@ kn service update NAME [flags]
### Options
```
--concurrency-limit int Hard Limit of concurrent requests to be processed by a single replica.
--concurrency-target int Recommendation for when to scale up based on the concurrent number of incoming request. Defaults to --concurrency-limit when given.
-e, --env stringArray Environment variable to set. NAME=value; you may provide this flag any number of times to set multiple environment variables.
-h, --help help for update
--image string Image to run.
--limits-cpu string The limits on the requested CPU (e.g., 1000m).
--limits-memory string The limits on the requested CPU (e.g., 1024Mi).
--max-scale int Maximal number of replicas.
--min-scale int Minimal number of replicas.
-n, --namespace string List the requested object(s) in given namespace.
--requests-cpu string The requested CPU (e.g., 250m).
--requests-memory string The requested CPU (e.g., 64Mi).

View File

@ -30,6 +30,10 @@ type ConfigurationEditFlags struct {
Env []string
RequestsFlags, LimitsFlags ResourceFlags
ForceCreate bool
MinScale int
MaxScale int
ConcurrencyTarget int
ConcurrencyLimit int
}
type ResourceFlags struct {
@ -46,6 +50,10 @@ func (p *ConfigurationEditFlags) AddUpdateFlags(command *cobra.Command) {
command.Flags().StringVar(&p.RequestsFlags.Memory, "requests-memory", "", "The requested CPU (e.g., 64Mi).")
command.Flags().StringVar(&p.LimitsFlags.CPU, "limits-cpu", "", "The limits on the requested CPU (e.g., 1000m).")
command.Flags().StringVar(&p.LimitsFlags.Memory, "limits-memory", "", "The limits on the requested CPU (e.g., 1024Mi).")
command.Flags().IntVar(&p.MinScale, "min-scale", 0, "Minimal number of replicas.")
command.Flags().IntVar(&p.MaxScale, "max-scale", 0, "Maximal number of replicas.")
command.Flags().IntVar(&p.ConcurrencyTarget, "concurrency-target", 0, "Recommendation for when to scale up based on the concurrent number of incoming request. Defaults to --concurrency-limit when given.")
command.Flags().IntVar(&p.ConcurrencyLimit, "concurrency-limit", 0, "Hard Limit of concurrent requests to be processed by a single replica.")
}
func (p *ConfigurationEditFlags) AddCreateFlags(command *cobra.Command) {
@ -93,6 +101,9 @@ func (p *ConfigurationEditFlags) Apply(service *servingv1alpha1.Service, cmd *co
if err != nil {
return err
}
servinglib.UpdateConcurrencyConfiguration(template, p.MinScale, p.MaxScale, p.ConcurrencyTarget, p.ConcurrencyLimit)
return nil
}

View File

@ -235,6 +235,42 @@ func TestServiceCreateRequestsLimitsMemory(t *testing.T) {
}
}
func TestServiceCreateMaxMinScale(t *testing.T) {
action, created, _, err := fakeServiceCreate([]string{
"service", "create", "foo", "--image", "gcr.io/foo/bar:baz",
"--min-scale", "1", "--max-scale", "5", "--concurrency-target", "10", "--concurrency-limit", "100"})
if err != nil {
t.Fatal(err)
} else if !action.Matches("create", "services") {
t.Fatalf("Bad action %v", action)
}
template, err := servinglib.GetRevisionTemplate(created)
if err != nil {
t.Fatal(err)
}
actualAnnos := template.Annotations
expectedAnnos := []string{
"autoscaling.knative.dev/minScale", "1",
"autoscaling.knative.dev/maxScale", "5",
"autoscaling.knative.dev/target", "10",
}
for i := 0; i < len(expectedAnnos); i += 2 {
anno := expectedAnnos[i]
if actualAnnos[anno] != expectedAnnos[i+1] {
t.Fatalf("Unexpected annotation value for %s : %s (actual) != %s (expected)",
anno, actualAnnos[anno], expectedAnnos[i+1])
}
}
if template.Spec.ContainerConcurrency != 100 {
t.Fatalf("container concurrency not set to given value 1000")
}
}
func TestServiceCreateRequestsLimitsCPUMemory(t *testing.T) {
action, created, _, err := fakeServiceCreate([]string{
"service", "create", "foo", "--image", "gcr.io/foo/bar:baz",

View File

@ -64,27 +64,7 @@ func fakeServiceUpdate(original *v1alpha1.Service, args []string) (
}
func TestServiceUpdateImage(t *testing.T) {
orig := &v1alpha1.Service{
TypeMeta: metav1.TypeMeta{
Kind: "Service",
APIVersion: "knative.dev/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "default",
},
Spec: v1alpha1.ServiceSpec{
DeprecatedRunLatest: &v1alpha1.RunLatestType{
Configuration: v1alpha1.ConfigurationSpec{
DeprecatedRevisionTemplate: &v1alpha1.RevisionTemplateSpec{
Spec: v1alpha1.RevisionSpec{
DeprecatedContainer: &corev1.Container{},
},
},
},
},
},
}
orig := newEmptyService()
template, err := servinglib.GetRevisionTemplate(orig)
if err != nil {
@ -110,6 +90,45 @@ func TestServiceUpdateImage(t *testing.T) {
}
}
func TestServiceUpdateMaxMinScale(t *testing.T) {
original := newEmptyService()
action, updated, _, err := fakeServiceUpdate(original, []string{
"service", "update", "foo",
"--min-scale", "1", "--max-scale", "5", "--concurrency-target", "10", "--concurrency-limit", "100"})
if err != nil {
t.Fatal(err)
} else if !action.Matches("update", "services") {
t.Fatalf("Bad action %v", action)
}
template, err := servinglib.GetRevisionTemplate(updated)
if err != nil {
t.Fatal(err)
}
actualAnnos := template.Annotations
expectedAnnos := []string{
"autoscaling.knative.dev/minScale", "1",
"autoscaling.knative.dev/maxScale", "5",
"autoscaling.knative.dev/target", "10",
}
for i := 0; i < len(expectedAnnos); i += 2 {
anno := expectedAnnos[i]
if actualAnnos[anno] != expectedAnnos[i+1] {
t.Fatalf("Unexpected annotation value for %s : %s (actual) != %s (expected)",
anno, actualAnnos[anno], expectedAnnos[i+1])
}
}
if template.Spec.ContainerConcurrency != 100 {
t.Fatalf("container concurrency not set to given value 1000")
}
}
func TestServiceUpdateEnv(t *testing.T) {
orig := &v1alpha1.Service{
TypeMeta: metav1.TypeMeta{
@ -279,6 +298,30 @@ func TestServiceUpdateRequestsLimitsCPU_and_Memory(t *testing.T) {
}
}
func newEmptyService() *v1alpha1.Service {
return &v1alpha1.Service{
TypeMeta: metav1.TypeMeta{
Kind: "Service",
APIVersion: "knative.dev/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "default",
},
Spec: v1alpha1.ServiceSpec{
DeprecatedRunLatest: &v1alpha1.RunLatestType{
Configuration: v1alpha1.ConfigurationSpec{
DeprecatedRevisionTemplate: &v1alpha1.RevisionTemplateSpec{
Spec: v1alpha1.RevisionSpec{
DeprecatedContainer: &corev1.Container{},
},
},
},
},
},
}
}
func createMockServiceWithResources(t *testing.T, requestCPU, requestMemory, limitsCPU, limitsMemory string) *v1alpha1.Service {
service := &v1alpha1.Service{
TypeMeta: metav1.TypeMeta{

View File

@ -16,8 +16,10 @@ package serving
import (
"fmt"
"strconv"
servingv1alpha1 "github.com/knative/serving/pkg/apis/serving/v1alpha1"
servingv1beta1 "github.com/knative/serving/pkg/apis/serving/v1beta1"
corev1 "k8s.io/api/core/v1"
)
@ -33,6 +35,33 @@ func UpdateEnvVars(template *servingv1alpha1.RevisionTemplateSpec, vars map[stri
return nil
}
// Update min and max scale annotation if larger than 0
func UpdateConcurrencyConfiguration(template *servingv1alpha1.RevisionTemplateSpec, minScale int, maxScale int, target int, limit int) {
if minScale != 0 {
UpdateAnnotation(template, "autoscaling.knative.dev/minScale", strconv.Itoa(minScale))
}
if maxScale != 0 {
UpdateAnnotation(template, "autoscaling.knative.dev/maxScale", strconv.Itoa(maxScale))
}
if target != 0 {
UpdateAnnotation(template, "autoscaling.knative.dev/target", strconv.Itoa(target))
}
if limit != 0 {
template.Spec.ContainerConcurrency = servingv1beta1.RevisionContainerConcurrencyType(limit)
}
}
// Updater (or add) an annotation to the given service
func UpdateAnnotation(template *servingv1alpha1.RevisionTemplateSpec, annotation string, value string) {
annoMap := template.Annotations
if annoMap == nil {
annoMap = make(map[string]string)
template.Annotations = annoMap
}
annoMap[annotation] = value
}
// Utility function to translate between the API list form of env vars, and the
// more convenient map form.
func EnvToMap(vars []corev1.EnvVar) (map[string]string, error) {

View File

@ -23,6 +23,24 @@ import (
corev1 "k8s.io/api/core/v1"
)
func TestUpdateAutoscalingAnnotations(t *testing.T) {
template := &servingv1alpha1.RevisionTemplateSpec{}
UpdateConcurrencyConfiguration(template, 10, 100, 1000, 1000)
annos := template.Annotations
if annos["autoscaling.knative.dev/minScale"] != "10" {
t.Error("minScale failed")
}
if annos["autoscaling.knative.dev/maxScale"] != "100" {
t.Error("maxScale failed")
}
if annos["autoscaling.knative.dev/target"] != "1000" {
t.Error("target failed")
}
if template.Spec.ContainerConcurrency != 1000 {
t.Error("limit failed")
}
}
func TestUpdateEnvVarsNew(t *testing.T) {
template, container := getV1alpha1RevisionTemplateWithOldFields()
testUpdateEnvVarsNew(t, template, container)

4
vendor/modules.txt vendored
View File

@ -77,13 +77,13 @@ github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1
github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/fake
github.com/knative/serving/pkg/apis/serving
github.com/knative/serving/pkg/apis/serving/v1alpha1
github.com/knative/serving/pkg/apis/serving/v1beta1
github.com/knative/serving/pkg/client/clientset/versioned/scheme
github.com/knative/serving/pkg/apis/autoscaling
github.com/knative/serving/pkg/apis/networking
github.com/knative/serving/pkg/apis/networking/v1alpha1
github.com/knative/serving/pkg/apis/serving/v1beta1
github.com/knative/serving/pkg/apis/autoscaling/v1alpha1
github.com/knative/serving/pkg/apis/config
github.com/knative/serving/pkg/apis/autoscaling/v1alpha1
# github.com/knative/test-infra v0.0.0-20190531180034-a3c073a2fea1
github.com/knative/test-infra/scripts
# github.com/magiconair/properties v1.8.0