Skip to content

Commit

Permalink
Add cpu limit to resource requests and refactor knative service struct
Browse files Browse the repository at this point in the history
  • Loading branch information
deadlycoconuts committed May 23, 2024
1 parent 70da2bc commit 2861728
Show file tree
Hide file tree
Showing 14 changed files with 444 additions and 312 deletions.
12 changes: 2 additions & 10 deletions api/turing/cluster/knative_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,7 @@ type KnativeService struct {
TopologySpreadConstraints []corev1.TopologySpreadConstraint `json:"topologySpreadConstraints"`

// Resource properties
QueueProxyResourcePercentage int `json:"queueProxyResourcePercentage"`
UserContainerCPULimitRequestFactor float64 `json:"userContainerLimitCPURequestFactor"`
UserContainerMemoryLimitRequestFactor float64 `json:"userContainerLimitMemoryRequestFactor"`
QueueProxyResourcePercentage int `json:"queueProxyResourcePercentage"`
}

// Creates a new config object compatible with the knative serving API, from
Expand Down Expand Up @@ -131,12 +129,6 @@ func (cfg *KnativeService) buildSvcSpec(
// Revision name
revisionName := getDefaultRevisionName(cfg.Name)

// Build resource requirements for the user container
resourceReqs := cfg.buildResourceReqs(
cfg.UserContainerCPULimitRequestFactor,
cfg.UserContainerMemoryLimitRequestFactor,
)

// Build container spec
var portName string
// If protocol is using GRPC, add "h2c" which is required for grpc knative
Expand All @@ -151,7 +143,7 @@ func (cfg *KnativeService) buildSvcSpec(
ContainerPort: cfg.ContainerPort,
},
},
Resources: resourceReqs,
Resources: cfg.buildResourceReqs(),
VolumeMounts: cfg.VolumeMounts,
Env: cfg.Envs,
}
Expand Down
216 changes: 135 additions & 81 deletions api/turing/cluster/knative_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ import (
)

func TestBuildKnativeServiceConfig(t *testing.T) {
cpuRequest := resource.MustParse("400m")
memoryRequest := resource.MustParse("512Mi")

// Test configuration
baseSvc := &BaseService{
baseSvc := BaseService{
Name: "test-svc",
Namespace: "test-namespace",
Image: "asia.gcr.io/gcp-project-id/turing-router:latest",
CPURequests: resource.MustParse("400m"),
MemoryRequests: resource.MustParse("512Mi"),
CPURequests: cpuRequest,
MemoryRequests: memoryRequest,
ProbePort: 8080,
LivenessHTTPGetPath: "/v1/internal/live",
ReadinessHTTPGetPath: "/v1/internal/ready",
Expand Down Expand Up @@ -87,6 +90,10 @@ func TestBuildKnativeServiceConfig(t *testing.T) {
},
}

baseSvcWithResourceLimits := baseSvc
baseSvcWithResourceLimits.CPULimit = &cpuRequest
baseSvcWithResourceLimits.MemoryLimit = &memoryRequest

// Expected specs
var defaultConcurrency, defaultTrafficPercent int64 = 0, 100
var defaultLatestRevision = true
Expand All @@ -100,13 +107,10 @@ func TestBuildKnativeServiceConfig(t *testing.T) {
},
}
resources := corev1.ResourceRequirements{
Limits: map[corev1.ResourceName]resource.Quantity{
corev1.ResourceCPU: resource.MustParse("600m"),
corev1.ResourceMemory: resource.MustParse("768Mi"),
},
Limits: map[corev1.ResourceName]resource.Quantity{},
Requests: map[corev1.ResourceName]resource.Quantity{
corev1.ResourceCPU: resource.MustParse("400m"),
corev1.ResourceMemory: resource.MustParse("512Mi"),
corev1.ResourceCPU: cpuRequest,
corev1.ResourceMemory: memoryRequest,
},
}
envs := []corev1.EnvVar{
Expand All @@ -123,47 +127,57 @@ func TestBuildKnativeServiceConfig(t *testing.T) {
{Name: "APP_BQ_TABLE", Value: "turing_log_test"},
{Name: "APP_BQ_BATCH_LOAD", Value: "false"},
}
podSpec := corev1.PodSpec{
Containers: []corev1.Container{

containerSpec := corev1.Container{
Name: "user-container",
Image: "asia.gcr.io/gcp-project-id/turing-router:latest",
Ports: []corev1.ContainerPort{
{
Name: "user-container",
Image: "asia.gcr.io/gcp-project-id/turing-router:latest",
Ports: []corev1.ContainerPort{
{
ContainerPort: 8080,
},
},
Resources: resources,
LivenessProbe: &corev1.Probe{
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Port: intstr.FromInt(8080),
Path: "/v1/internal/live",
},
},
InitialDelaySeconds: 20,
PeriodSeconds: 10,
TimeoutSeconds: 5,
FailureThreshold: 5,
ContainerPort: 8080,
},
},
Resources: resources,
LivenessProbe: &corev1.Probe{
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Port: intstr.FromInt(8080),
Path: "/v1/internal/live",
},
ReadinessProbe: &corev1.Probe{
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Port: intstr.FromInt(8080),
Path: "/v1/internal/ready",
},
},
InitialDelaySeconds: 20,
PeriodSeconds: 10,
SuccessThreshold: 1,
TimeoutSeconds: 5,
FailureThreshold: 5,
},
InitialDelaySeconds: 20,
PeriodSeconds: 10,
TimeoutSeconds: 5,
FailureThreshold: 5,
},
ReadinessProbe: &corev1.Probe{
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Port: intstr.FromInt(8080),
Path: "/v1/internal/ready",
},
VolumeMounts: baseSvc.VolumeMounts,
Env: envs,
},
InitialDelaySeconds: 20,
PeriodSeconds: 10,
SuccessThreshold: 1,
TimeoutSeconds: 5,
FailureThreshold: 5,
},
Volumes: baseSvc.Volumes,
VolumeMounts: baseSvc.VolumeMounts,
Env: envs,
}
podSpec := corev1.PodSpec{
Containers: []corev1.Container{containerSpec},
Volumes: baseSvc.Volumes,
}

containerSpecWithResourceLimits := containerSpec
containerSpecWithResourceLimits.Resources.Limits = map[corev1.ResourceName]resource.Quantity{
corev1.ResourceCPU: cpuRequest,
corev1.ResourceMemory: memoryRequest,
}
podSpecWithResourceLimits := corev1.PodSpec{
Containers: []corev1.Container{containerSpecWithResourceLimits},
Volumes: baseSvc.Volumes,
}

tests := map[string]struct {
Expand All @@ -172,16 +186,14 @@ func TestBuildKnativeServiceConfig(t *testing.T) {
}{
"basic": {
serviceCfg: KnativeService{
BaseService: baseSvc,
ContainerPort: 8080,
MinReplicas: 1,
MaxReplicas: 2,
AutoscalingMetric: "concurrency",
AutoscalingTarget: "1",
IsClusterLocal: true,
QueueProxyResourcePercentage: 30,
UserContainerCPULimitRequestFactor: 1.5,
UserContainerMemoryLimitRequestFactor: 1.5,
BaseService: &baseSvc,
ContainerPort: 8080,
MinReplicas: 1,
MaxReplicas: 2,
AutoscalingMetric: "concurrency",
AutoscalingTarget: "1",
IsClusterLocal: true,
QueueProxyResourcePercentage: 30,
},
expectedSpec: knservingv1.Service{
ObjectMeta: metav1.ObjectMeta{
Expand Down Expand Up @@ -220,20 +232,66 @@ func TestBuildKnativeServiceConfig(t *testing.T) {
},
},
},
"basic with limits": {
serviceCfg: KnativeService{
BaseService: &baseSvcWithResourceLimits,
ContainerPort: 8080,
MinReplicas: 1,
MaxReplicas: 2,
AutoscalingMetric: "concurrency",
AutoscalingTarget: "1",
IsClusterLocal: true,
QueueProxyResourcePercentage: 30,
},
expectedSpec: knservingv1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test-svc",
Namespace: "test-namespace",
Labels: map[string]string{
"labelKey": "labelVal",
"networking.knative.dev/visibility": "cluster-local",
},
},
Spec: knservingv1.ServiceSpec{
ConfigurationSpec: knservingv1.ConfigurationSpec{
Template: knservingv1.RevisionTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Name: "test-svc-0",
Labels: map[string]string{
"labelKey": "labelVal",
},
Annotations: map[string]string{
"autoscaling.knative.dev/minScale": "1",
"autoscaling.knative.dev/maxScale": "2",
"autoscaling.knative.dev/metric": "concurrency",
"autoscaling.knative.dev/target": "1.00",
"autoscaling.knative.dev/class": "kpa.autoscaling.knative.dev",
"queue.sidecar.serving.knative.dev/resourcePercentage": "30",
},
},
Spec: knservingv1.RevisionSpec{
PodSpec: podSpecWithResourceLimits,
TimeoutSeconds: &timeout,
ContainerConcurrency: &defaultConcurrency,
},
},
},
RouteSpec: defaultRouteSpec,
},
},
},
// upi has no liveness probe in pod spec and user-container is using h2c
"upi": {
serviceCfg: KnativeService{
BaseService: baseSvc,
ContainerPort: 8080,
MinReplicas: 1,
MaxReplicas: 2,
AutoscalingMetric: "concurrency",
AutoscalingTarget: "1",
IsClusterLocal: true,
QueueProxyResourcePercentage: 30,
UserContainerCPULimitRequestFactor: 1.5,
UserContainerMemoryLimitRequestFactor: 1.5,
Protocol: routerConfig.UPI,
BaseService: &baseSvc,
ContainerPort: 8080,
MinReplicas: 1,
MaxReplicas: 2,
AutoscalingMetric: "concurrency",
AutoscalingTarget: "1",
IsClusterLocal: true,
QueueProxyResourcePercentage: 30,
Protocol: routerConfig.UPI,
},
expectedSpec: knservingv1.Service{
ObjectMeta: metav1.ObjectMeta{
Expand Down Expand Up @@ -293,15 +351,13 @@ func TestBuildKnativeServiceConfig(t *testing.T) {
},
"annotations": {
serviceCfg: KnativeService{
BaseService: baseSvc,
ContainerPort: 8080,
MinReplicas: 5,
MaxReplicas: 6,
AutoscalingMetric: "memory",
AutoscalingTarget: "70",
IsClusterLocal: false,
UserContainerCPULimitRequestFactor: 1.5,
UserContainerMemoryLimitRequestFactor: 1.5,
BaseService: &baseSvc,
ContainerPort: 8080,
MinReplicas: 5,
MaxReplicas: 6,
AutoscalingMetric: "memory",
AutoscalingTarget: "70",
IsClusterLocal: false,
},
expectedSpec: knservingv1.Service{
ObjectMeta: metav1.ObjectMeta{
Expand Down Expand Up @@ -340,7 +396,7 @@ func TestBuildKnativeServiceConfig(t *testing.T) {
},
"topology spread constraints": {
serviceCfg: KnativeService{
BaseService: baseSvc,
BaseService: &baseSvc,
ContainerPort: 8080,
MinReplicas: 1,
MaxReplicas: 2,
Expand Down Expand Up @@ -384,11 +440,9 @@ func TestBuildKnativeServiceConfig(t *testing.T) {
},
},
},
IsClusterLocal: true,
QueueProxyResourcePercentage: 30,
UserContainerCPULimitRequestFactor: 1.5,
UserContainerMemoryLimitRequestFactor: 1.5,
Protocol: routerConfig.UPI,
IsClusterLocal: true,
QueueProxyResourcePercentage: 30,
Protocol: routerConfig.UPI,
},
expectedSpec: knservingv1.Service{
ObjectMeta: metav1.ObjectMeta{
Expand Down
2 changes: 1 addition & 1 deletion api/turing/cluster/kubernetes_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func (cfg *KubernetesService) buildStatefulSet(labels map[string]string) *appsv1
Args: cfg.Command,
Ports: cfg.buildContainerPorts(),
Env: cfg.Envs,
Resources: cfg.buildResourceReqs(defaultCPULimitRequestFactor, defaultMemoryLimitRequestFactor),
Resources: cfg.buildResourceReqs(),
VolumeMounts: cfg.VolumeMounts,
LivenessProbe: cfg.buildContainerProbe(livenessProbeType, int(cfg.ProbePort)),
ReadinessProbe: cfg.buildContainerProbe(readinessProbeType, int(cfg.ProbePort)),
Expand Down
19 changes: 9 additions & 10 deletions api/turing/cluster/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ type BaseService struct {
Image string `json:"image"`

// Resources
CPURequests resource.Quantity `json:"cpu_requests"`
MemoryRequests resource.Quantity `json:"memory_requests"`
CPURequests resource.Quantity `json:"cpu_requests"`
CPULimit *resource.Quantity `json:"cpu_limit"`
MemoryRequests resource.Quantity `json:"memory_requests"`
MemoryLimit *resource.Quantity `json:"memory_limit"`

// Health Checks
ProbePort int32 `json:"probe_port"`
Expand All @@ -62,22 +64,19 @@ type BaseService struct {
InitContainers []Container `json:"init_containers"`
}

func (cfg *BaseService) buildResourceReqs(
UserContainerCPULimitRequestFactor float64,
UserContainerMemoryLimitRequestFactor float64,
) corev1.ResourceRequirements {
func (cfg *BaseService) buildResourceReqs() corev1.ResourceRequirements {
reqs := map[corev1.ResourceName]resource.Quantity{
corev1.ResourceCPU: cfg.CPURequests,
corev1.ResourceMemory: cfg.MemoryRequests,
}

// Set resource limits to request * userContainerCPULimitRequestFactor or UserContainerMemoryLimitRequestFactor
limits := map[corev1.ResourceName]resource.Quantity{}
if UserContainerCPULimitRequestFactor != 0 {
limits[corev1.ResourceCPU] = ComputeResource(cfg.CPURequests, UserContainerCPULimitRequestFactor)
if cfg.CPULimit != nil {
limits[corev1.ResourceCPU] = *cfg.CPULimit
}
if UserContainerMemoryLimitRequestFactor != 0 {
limits[corev1.ResourceMemory] = ComputeResource(cfg.MemoryRequests, UserContainerMemoryLimitRequestFactor)
if cfg.MemoryLimit != nil {
limits[corev1.ResourceMemory] = *cfg.MemoryLimit
}

return corev1.ResourceRequirements{
Expand Down
Loading

0 comments on commit 2861728

Please sign in to comment.