From 11c8260063aa114c6cb06713d288ef8b343d0ab9 Mon Sep 17 00:00:00 2001
From: Sina Siadat ServiceName is the name of the Service for this cell’s vtgate. LabelSelector is required by the Scale subresource, which is used by
+HorizontalPodAutoscaler when reading pod metrics. Replicas is required by the Scale subresource, which is used by
+HorizontalPodAutoscaler to determine the current number of replicas.
+(Appears on:
+VitessCellGatewaySpec)
+
+ AutoscalerSpec defines the vtgate’s pod autoscaling specification.VitessCellGatewayStatus
+
+
+
+labelSelector
+
+string
+
+
+
+
+
+
+replicas
+
+int32
+
+
+
+VitessCellImages
diff --git a/pkg/apis/planetscale/v2/vitesscell_types.go b/pkg/apis/planetscale/v2/vitesscell_types.go
index d65645a6..eb2458ce 100644
--- a/pkg/apis/planetscale/v2/vitesscell_types.go
+++ b/pkg/apis/planetscale/v2/vitesscell_types.go
@@ -39,6 +39,7 @@ import (
// just like a Deployment can manage Pods that run on multiple Nodes.
// +kubebuilder:resource:path=vitesscells,shortName=vtc
// +kubebuilder:subresource:status
+// +kubebuilder:subresource:scale:specpath=.spec.gateway.replicas,statuspath=.status.gateway.replicas,selectorpath=.status.gateway.labelSelector
type VitessCell struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
@@ -252,6 +253,13 @@ type VitessCellGatewayStatus struct {
Available corev1.ConditionStatus `json:"available,omitempty"`
// ServiceName is the name of the Service for this cell's vtgate.
ServiceName string `json:"serviceName,omitempty"`
+ // LabelSelector is required by the Scale subresource, which is used by
+ // HorizontalPodAutoscaler when reading pod metrics.
+ LabelSelector string `json:"labelSelector,omitempty"`
+ // Replicas is required by the Scale subresource, which is used by
+ // HorizontalPodAutoscaler to determine the current number of replicas.
+ // +kubebuilder:validation:Minimum=0
+ Replicas int32 `json:"replicas,omitempty"`
}
// VitessCellStatus defines the observed state of VitessCell
diff --git a/pkg/controller/vitesscell/reconcile_vtgate.go b/pkg/controller/vitesscell/reconcile_vtgate.go
index 136e5055..40e54ee4 100644
--- a/pkg/controller/vitesscell/reconcile_vtgate.go
+++ b/pkg/controller/vitesscell/reconcile_vtgate.go
@@ -161,6 +161,10 @@ func (r *ReconcileVitessCell) reconcileVtgate(ctx context.Context, vtc *planetsc
curObj := obj.(*appsv1.Deployment)
status := &vtc.Status.Gateway
+ if replicas := curObj.Spec.Replicas; replicas != nil {
+ status.Replicas = *replicas
+ }
+ status.LabelSelector = curObj.Spec.Selector.String()
if available := conditions.Deployment(curObj.Status.Conditions, appsv1.DeploymentAvailable); available != nil {
status.Available = available.Status
}
From aa5c2047a2dfb723d83b79e39a86857bfe460858 Mon Sep 17 00:00:00 2001
From: Sina Siadat
VitessCluster
+
AutoscalerSpec
+
+
Field | +Description | +
---|---|
+minReplicas
+
+int32
+
+ |
+
+ MinReplicas is the minimum number of instances of vtgate to run in +this cell when autoscaling is enabled. + |
+
+maxReplicas
+
+int32
+
+ |
+
+ MaxReplicas is the maximum number of instances of vtgate to run in +this cell when autoscaling is enabled. + |
+
+behavior
+
+
+Kubernetes autoscaling/v2.HorizontalPodAutoscalerBehavior
+
+
+ |
++(Optional) + | +
+metrics
+
+
+[]Kubernetes autoscaling/v2.MetricSpec
+
+
+ |
+
+(Optional)
+ Metrics is meant to provide a customizable way to configure HPA metrics. +currently the only supported custom metrics is type=Pod. +Use TargetCPUUtilization or TargetMemoryUtilization instead if scaling on these common resource metrics. + |
+
@@ -3337,6 +3409,21 @@
autoscaler
+
+
+AutoscalerSpec
+
+
+Autoscaler specifies the pod autoscaling configuration to use +for the vtgate workload.
+resources
diff --git a/pkg/apis/planetscale/v2/vitesscell_types.go b/pkg/apis/planetscale/v2/vitesscell_types.go
index eb2458ce..0e911e77 100644
--- a/pkg/apis/planetscale/v2/vitesscell_types.go
+++ b/pkg/apis/planetscale/v2/vitesscell_types.go
@@ -17,6 +17,7 @@ limitations under the License.
package v2
import (
+ autoscalingv2 "k8s.io/api/autoscaling/v2"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -118,12 +119,39 @@ type VitessCellImages struct {
Vtgate string `json:"vtgate,omitempty"`
}
+// AutoscalerSpec defines the vtgate's pod autoscaling specification.
+type AutoscalerSpec struct {
+ // MinReplicas is the minimum number of instances of vtgate to run in
+ // this cell when autoscaling is enabled.
+ // +kubebuilder:validation:Minimum=0
+ MinReplicas *int32 `json:"minReplicas,omitempty"`
+
+ // MaxReplicas is the maximum number of instances of vtgate to run in
+ // this cell when autoscaling is enabled.
+ // +kubebuilder:validation:Minimum=0
+ MaxReplicas *int32 `json:"maxReplicas,omitempty"`
+
+ // +optional
+ Behavior *autoscalingv2.HorizontalPodAutoscalerBehavior `json:"behavior,omitempty"`
+
+ // Metrics is meant to provide a customizable way to configure HPA metrics.
+ // currently the only supported custom metrics is type=Pod.
+ // Use TargetCPUUtilization or TargetMemoryUtilization instead if scaling on these common resource metrics.
+ // +optional
+ Metrics []autoscalingv2.MetricSpec `json:"metrics,omitempty"`
+}
+
// VitessCellGatewaySpec specifies the per-cell deployment parameters for vtgate.
type VitessCellGatewaySpec struct {
// Replicas is the number of vtgate instances to deploy in this cell.
// +kubebuilder:validation:Minimum=0
Replicas *int32 `json:"replicas,omitempty"`
+ // Autoscaler specifies the pod autoscaling configuration to use
+ // for the vtgate workload.
+ // +optional
+ Autoscaler *AutoscalerSpec `json:"autoscaler,omitempty"`
+
// Resources determines the compute resources reserved for each vtgate replica.
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
diff --git a/pkg/apis/planetscale/v2/zz_generated.deepcopy.go b/pkg/apis/planetscale/v2/zz_generated.deepcopy.go
index 4db30177..a6636bd4 100644
--- a/pkg/apis/planetscale/v2/zz_generated.deepcopy.go
+++ b/pkg/apis/planetscale/v2/zz_generated.deepcopy.go
@@ -6,10 +6,48 @@
package v2
import (
+ autoscalingv2 "k8s.io/api/autoscaling/v2"
"k8s.io/api/core/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AutoscalerSpec) DeepCopyInto(out *AutoscalerSpec) {
+ *out = *in
+ if in.MinReplicas != nil {
+ in, out := &in.MinReplicas, &out.MinReplicas
+ *out = new(int32)
+ **out = **in
+ }
+ if in.MaxReplicas != nil {
+ in, out := &in.MaxReplicas, &out.MaxReplicas
+ *out = new(int32)
+ **out = **in
+ }
+ if in.Behavior != nil {
+ in, out := &in.Behavior, &out.Behavior
+ *out = new(autoscalingv2.HorizontalPodAutoscalerBehavior)
+ (*in).DeepCopyInto(*out)
+ }
+ if in.Metrics != nil {
+ in, out := &in.Metrics, &out.Metrics
+ *out = make([]autoscalingv2.MetricSpec, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AutoscalerSpec.
+func (in *AutoscalerSpec) DeepCopy() *AutoscalerSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(AutoscalerSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AzblobBackupLocation) DeepCopyInto(out *AzblobBackupLocation) {
*out = *in
@@ -1097,6 +1135,11 @@ func (in *VitessCellGatewaySpec) DeepCopyInto(out *VitessCellGatewaySpec) {
*out = new(int32)
**out = **in
}
+ if in.Autoscaler != nil {
+ in, out := &in.Autoscaler, &out.Autoscaler
+ *out = new(AutoscalerSpec)
+ (*in).DeepCopyInto(*out)
+ }
in.Resources.DeepCopyInto(&out.Resources)
in.Authentication.DeepCopyInto(&out.Authentication)
if in.SecureTransport != nil {
diff --git a/pkg/controller/vitesscell/reconcile_vtgate.go b/pkg/controller/vitesscell/reconcile_vtgate.go
index 40e54ee4..be18da44 100644
--- a/pkg/controller/vitesscell/reconcile_vtgate.go
+++ b/pkg/controller/vitesscell/reconcile_vtgate.go
@@ -20,6 +20,7 @@ import (
"context"
appsv1 "k8s.io/api/apps/v1"
+ autoscalingv2 "k8s.io/api/autoscaling/v2"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
apitypes "k8s.io/apimachinery/pkg/types"
@@ -174,5 +175,35 @@ func (r *ReconcileVitessCell) reconcileVtgate(ctx context.Context, vtc *planetsc
resultBuilder.Error(err)
}
+ var wantHpa bool
+ var hpaSpec *vtgate.HpaSpec
+
+ if vtc.Spec.Gateway.Autoscaler != nil {
+ wantHpa = vtc.Spec.Gateway.Autoscaler.MaxReplicas != nil
+ hpaSpec = &vtgate.HpaSpec{
+ Labels: labels,
+ MinReplicas: vtc.Spec.Gateway.Autoscaler.MinReplicas,
+ MaxReplicas: vtc.Spec.Gateway.Autoscaler.MaxReplicas,
+ Behavior: vtc.Spec.Gateway.Autoscaler.Behavior,
+ Metrics: vtc.Spec.Gateway.Autoscaler.Metrics,
+ }
+ }
+
+ // Reconcile vtgate HorizontalPodAutoscaler.
+ err = r.reconciler.ReconcileObject(ctx, vtc, key, labels, wantHpa, reconciler.Strategy{
+ Kind: &autoscalingv2.HorizontalPodAutoscaler{},
+
+ New: func(key client.ObjectKey) runtime.Object {
+ return vtgate.NewHorizontalPodAutoscaler(key, hpaSpec)
+ },
+ UpdateInPlace: func(key client.ObjectKey, obj runtime.Object) {
+ newObj := obj.(*autoscalingv2.HorizontalPodAutoscaler)
+ vtgate.UpdateHorizontalPodAutoscaler(newObj, hpaSpec)
+ },
+ })
+ if err != nil {
+ resultBuilder.Error(err)
+ }
+
return resultBuilder.Result()
}
diff --git a/pkg/operator/vtgate/horizontal_pod_autoscaler.go b/pkg/operator/vtgate/horizontal_pod_autoscaler.go
new file mode 100644
index 00000000..4b7abb3e
--- /dev/null
+++ b/pkg/operator/vtgate/horizontal_pod_autoscaler.go
@@ -0,0 +1,68 @@
+/*
+Copyright 2019 PlanetScale Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package vtgate
+
+import (
+ autoscalingv2 "k8s.io/api/autoscaling/v2"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+
+ "planetscale.dev/vitess-operator/pkg/operator/update"
+)
+
+// Spec specifies all the internal parameters needed to deploy vtgate,
+// as opposed to the API type planetscalev2.VitessCellGatewaySpec, which is the public API.
+type HpaSpec struct {
+ Labels map[string]string
+ MinReplicas *int32
+ MaxReplicas *int32
+ Behavior *autoscalingv2.HorizontalPodAutoscalerBehavior `json:"behavior,omitempty"`
+ Metrics []autoscalingv2.MetricSpec `json:"metrics,omitempty"`
+}
+
+// NewHorizontalPodAutoscaler creates a new HorizontalPodAutoscaler object for vtgate.
+func NewHorizontalPodAutoscaler(key client.ObjectKey, spec *HpaSpec) *autoscalingv2.HorizontalPodAutoscaler {
+ // Fill in the immutable parts.
+ obj := &autoscalingv2.HorizontalPodAutoscaler{
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: key.Namespace,
+ Name: key.Name,
+ },
+ Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
+ ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
+ APIVersion: "planetscale.com/v2",
+ Kind: "VitessCell",
+ Name: key.Name,
+ },
+ },
+ }
+ // Set everything else.
+ UpdateHorizontalPodAutoscaler(obj, spec)
+ return obj
+}
+
+// UpdateHorizontalPodAutoscaler updates the mutable parts of the vtgate HorizontalPodAutoscaler.
+func UpdateHorizontalPodAutoscaler(obj *autoscalingv2.HorizontalPodAutoscaler, spec *HpaSpec) {
+ // Set labels on the HorizontalPodAutoscaler object.
+ update.Labels(&obj.Labels, spec.Labels)
+
+ // Set the specs for the HorizontalPodAutoscaler object.
+ obj.Spec.MinReplicas = spec.MinReplicas
+ obj.Spec.MaxReplicas = *spec.MaxReplicas
+ obj.Spec.Metrics = spec.Metrics
+ obj.Spec.Behavior = spec.Behavior
+}
diff --git a/test/integration/vitesscluster/vitesscluster_test.go b/test/integration/vitesscluster/vitesscluster_test.go
index b7f2440d..41390acd 100644
--- a/test/integration/vitesscluster/vitesscluster_test.go
+++ b/test/integration/vitesscluster/vitesscluster_test.go
@@ -21,6 +21,7 @@ import (
"testing"
appsv1 "k8s.io/api/apps/v1"
+ autoscalingv2 "k8s.io/api/autoscaling/v2"
corev1 "k8s.io/api/core/v1"
apilabels "k8s.io/apimachinery/pkg/labels"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -38,6 +39,17 @@ spec:
- name: cell1
- name: cell2
- name: cell3
+ gateway:
+ autoscaler:
+ minReplicas: 1
+ maxReplicas: 2
+ metrics:
+ - type: Resource
+ resource:
+ name: cpu
+ target:
+ type: Utilization
+ averageUtilization: 80
keyspaces:
- name: keyspace1
partitionings:
@@ -134,6 +146,7 @@ func verifyBasicVitessCluster(f *framework.Fixture, ns, cluster string) {
verifyBasicVitessCell(f, ns, cluster, "cell1")
verifyBasicVitessCell(f, ns, cluster, "cell2")
verifyBasicVitessCell(f, ns, cluster, "cell3")
+ f.MustGet(ns, names.JoinWithConstraints(names.DefaultConstraints, cluster, "cell3", "vtgate"), &autoscalingv2.HorizontalPodAutoscaler{})
// VitessCluster creates VitessKeyspaces.
verifyBasicVitessKeyspace(f, ns, cluster, "keyspace1")
From 6816227fba647a4c2e350b262092e7f2054b6274 Mon Sep 17 00:00:00 2001
From: Sina Siadat