Files
Etienne PerotandgVisor bot 6218b88020 Reduce max thread count in nginx and wordpress benchmarks.
Now it is still a high number, but one that has a more realistic chance
to run effectively on small machines.

PiperOrigin-RevId: 770887549
2025-06-12 19:25:04 -07:00

380 lines
11 KiB
Go

// Copyright 2024 The gVisor Authors.
//
// 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 wordpress
import (
"context"
"fmt"
"strings"
"testing"
"time"
"gvisor.dev/gvisor/test/kubernetes/benchmarks/httpbench"
"gvisor.dev/gvisor/test/kubernetes/benchmarks/profiling"
"gvisor.dev/gvisor/test/kubernetes/k8sctx"
"gvisor.dev/gvisor/test/kubernetes/testcluster"
v13 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
const (
wordpressImage = "wordpress:6.2.0-php8.2-apache"
mariaDBImage = "mariadb:10.11.3-jammy"
wordpressPort = 80
mariaDBPort = 3306
wordpressBenchmarkDuration = 55 * time.Second
wordpressRequestTimeout = 10 * time.Second
wordpressLoginPage = "/wp-login.php"
mariaDBName = "wpbench"
mariaDBUser = "wpuser"
mariaDBPassword = "wppassword"
mariaDBRootPassword = "hunter2"
mariaDBVolumeName = "wpdata"
mariaDBVolumeDirectory = "/var/lib/mysql"
wordpressServerLabelKey = "app.kubernetes.io/name"
wordpressServerLabelValue = "wordpress"
mariaDBServerLabelKey = "app.kubernetes.io/name"
mariaDBServerLabelValue = "mariadb"
)
var (
threads = []int{1, 8, 256}
targetQPS = []int{1, 64, httpbench.InfiniteQPS}
wantPercentiles = []int{50, 95, 99}
)
// BenchmarkWordpress runs a benchmark of WordPress performance.
func BenchmarkWordpress(ctx context.Context, t *testing.T, k8sCtx k8sctx.KubernetesContext, cluster *testcluster.TestCluster) {
benchmarkNS := cluster.Namespace(testcluster.NamespaceBenchmark)
if err := benchmarkNS.Reset(ctx); err != nil {
t.Fatalf("cannot reset namespace: %v", err)
}
defer benchmarkNS.Cleanup(ctx)
endProfiling, err := profiling.MaybeSetup(ctx, t, k8sCtx, cluster, benchmarkNS)
if err != nil {
t.Fatalf("Failed to setup profiling: %v", err)
}
defer endProfiling()
// Create a persistent volume on which to store the database data.
dbVolume := benchmarkNS.GetPersistentVolume(mariaDBVolumeName, "30Gi")
dbVolume, err = cluster.CreatePersistentVolume(ctx, dbVolume)
if err != nil {
t.Fatalf("failed to create persistent volume: %v", err)
}
defer cluster.DeletePersistentVolume(ctx, dbVolume)
databaseName := "mariadb"
mariaDBImg, err := k8sCtx.ResolveImage(ctx, mariaDBImage)
if err != nil {
t.Fatalf("failed to resolve image: %v", err)
}
database := newMariaDBServer(benchmarkNS, databaseName, mariaDBImg, dbVolume)
database, err = cluster.ConfigurePodForTertiaryNodepool(ctx, database)
if err != nil {
t.Fatalf("Failed to configure pod for tertiary nodepool: %v", err)
}
database, err = cluster.CreatePod(ctx, database)
if err != nil {
t.Fatalf("Failed to create pod: %v", err)
}
defer cluster.DeletePod(ctx, database)
if err := cluster.WaitForPodRunning(ctx, database); err != nil {
t.Fatalf("Failed to wait for pod: %v", err)
}
databaseService := newMariaDBService(benchmarkNS, databaseName)
databaseService, err = cluster.CreateService(ctx, databaseService)
if err != nil {
t.Fatalf("Failed to create database service: %v", err)
}
defer cluster.DeleteService(ctx, databaseService)
mariaDBIP := testcluster.GetIPFromService(databaseService)
name := "wordpress"
wordpressImg, err := k8sCtx.ResolveImage(ctx, wordpressImage)
if err != nil {
t.Fatalf("Failed to resolve image: %v", err)
}
server := newWordpressServer(benchmarkNS, name, wordpressImg, mariaDBIP)
server, err = cluster.ConfigurePodForRuntimeTestNodepool(ctx, server)
if err != nil {
t.Fatalf("Failed to configure pod for runtime nodepool: %v", err)
}
server, err = testcluster.SetContainerResources(server, "", testcluster.ContainerResourcesRequest{})
if err != nil {
t.Fatalf("Failed to set container resources: %v", err)
}
server, err = cluster.CreatePod(ctx, server)
if err != nil {
t.Fatalf("Failed to create pod: %v", err)
}
defer cluster.DeletePod(ctx, server)
if err := cluster.WaitForPodRunning(ctx, server); err != nil {
t.Fatalf("Failed to wait for pod: %v", err)
}
service := newWordpressService(benchmarkNS, name)
service, err = cluster.CreateService(ctx, service)
if err != nil {
t.Fatalf("Failed to create service: %v", err)
}
defer cluster.DeleteService(ctx, service)
wordpressIP := testcluster.GetIPFromService(service)
// Install WordPress.
installWordpressPod := newWordpressInstall(benchmarkNS, "install-wordpress", wordpressIP)
installWordpressPod, err = cluster.ConfigurePodForClientNodepool(ctx, installWordpressPod)
if err != nil {
t.Fatalf("Failed to configure pod for client nodepool: %v", err)
}
installWordpressPod, err = cluster.CreatePod(ctx, installWordpressPod)
if err != nil {
t.Fatalf("Failed to create pod: %v", err)
}
defer cluster.DeletePod(ctx, installWordpressPod)
if err := cluster.WaitForPodCompleted(ctx, installWordpressPod); err != nil {
t.Fatalf("Failed to wait for pod: %v", err)
}
cluster.DeletePod(ctx, installWordpressPod)
var rounds []httpbench.Round
for _, numThreads := range threads {
for _, qps := range targetQPS {
if qps < numThreads {
continue
}
onlyReport := []httpbench.MetricType{httpbench.RequestsPerSecond}
// If we're testing at max QPS, don't report latency,
// because all requests will hit the timeout.
// Otherwise, only report latency, because the throughput
// is exactly determined by the QPS target anyway.
if qps != httpbench.InfiniteQPS {
onlyReport = append(onlyReport, httpbench.Latency)
}
rounds = append(rounds, httpbench.Round{
NumThreads: numThreads,
TargetQPS: qps,
Duration: wordpressBenchmarkDuration,
OnlyReport: onlyReport,
})
}
}
benchmark := &httpbench.HTTPBenchmark{
Name: "wordpress",
Cluster: cluster,
Namespace: benchmarkNS,
Service: service,
Port: wordpressPort,
Path: wordpressLoginPage,
Rounds: rounds,
Timeout: wordpressRequestTimeout,
WantPercentiles: wantPercentiles,
}
benchmark.Run(ctx, t)
}
func newMariaDBServer(namespace *testcluster.Namespace, name, image string, volume *v13.PersistentVolumeClaim) *v13.Pod {
return &v13.Pod{
TypeMeta: v1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: v1.ObjectMeta{
Name: name,
Namespace: namespace.Namespace,
Labels: map[string]string{mariaDBServerLabelKey: mariaDBServerLabelValue},
},
Spec: v13.PodSpec{
Containers: []v13.Container{
{
Name: name,
Image: image,
Ports: []v13.ContainerPort{
{
Name: name,
ContainerPort: mariaDBPort,
},
},
Env: []v13.EnvVar{
{
Name: "MARIADB_ROOT_PASSWORD",
Value: mariaDBRootPassword,
},
{
Name: "MARIADB_DATABASE",
Value: mariaDBName,
},
{
Name: "MARIADB_USER",
Value: mariaDBUser,
},
{
Name: "MARIADB_PASSWORD",
Value: mariaDBPassword,
},
},
VolumeMounts: []v13.VolumeMount{{
Name: volume.GetName(),
MountPath: mariaDBVolumeDirectory,
}},
},
},
Volumes: []v13.Volume{{
Name: volume.GetName(),
VolumeSource: v13.VolumeSource{
PersistentVolumeClaim: &v13.PersistentVolumeClaimVolumeSource{
ClaimName: volume.GetName(),
},
},
}},
RestartPolicy: v13.RestartPolicyNever,
},
}
}
func newMariaDBService(namespace *testcluster.Namespace, name string) *v13.Service {
return namespace.GetService(name, v13.ServiceSpec{
Selector: map[string]string{mariaDBServerLabelKey: mariaDBServerLabelValue},
Ports: []v13.ServicePort{
{
Name: name,
Protocol: v13.ProtocolTCP,
Port: mariaDBPort,
TargetPort: intstr.FromString(name),
},
},
})
}
func newWordpressServer(namespace *testcluster.Namespace, name, image, mariaDBHost string) *v13.Pod {
return &v13.Pod{
TypeMeta: v1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: v1.ObjectMeta{
Name: name,
Namespace: namespace.Namespace,
Labels: map[string]string{wordpressServerLabelKey: wordpressServerLabelValue},
},
Spec: v13.PodSpec{
Containers: []v13.Container{
{
Name: name,
Image: image,
Ports: []v13.ContainerPort{
{
Name: name,
ContainerPort: wordpressPort,
},
},
Env: []v13.EnvVar{
{
Name: "WORDPRESS_DB_HOST",
Value: mariaDBHost,
},
{
Name: "WORDPRESS_DB_USER",
Value: mariaDBUser,
},
{
Name: "WORDPRESS_DB_PASSWORD",
Value: mariaDBPassword,
},
{
Name: "WORDPRESS_DB_NAME",
Value: mariaDBName,
},
{
Name: "WORDPRESS_TABLE_PREFIX",
Value: "wp_",
},
},
},
},
RestartPolicy: v13.RestartPolicyNever,
},
}
}
func newWordpressService(namespace *testcluster.Namespace, name string) *v13.Service {
return namespace.GetService(name, v13.ServiceSpec{
Selector: map[string]string{wordpressServerLabelKey: wordpressServerLabelValue},
Ports: []v13.ServicePort{
{
Name: name,
Protocol: v13.ProtocolTCP,
Port: wordpressPort,
TargetPort: intstr.FromString(name),
},
},
})
}
func newWordpressInstall(namespace *testcluster.Namespace, name, wpHost string) *v13.Pod {
return &v13.Pod{
TypeMeta: v1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: v1.ObjectMeta{
Name: name,
Namespace: namespace.Namespace,
},
Spec: v13.PodSpec{
Containers: []v13.Container{
{
Name: "install-wordpress",
Image: "debian:latest",
// This command installs WordPress through the web UI.
// Source of the parameters:
// https://github.com/GoogleCloudPlatform/click-to-deploy/blob/master/k8s/wordpress/chart/wordpress/templates/wordpress-configmap.yaml
Command: []string{
"sh", "-c",
strings.Join([]string{
"apt-get update -y </dev/null",
"apt-get install -y curl </dev/null",
strings.Join([]string{
"curl",
"--fail",
"--retry", "20",
"--retry-delay", "5",
"--retry-connrefused",
"--data-urlencode", "'weblog_title=WPBench'",
"--data-urlencode", "'user_name=iamroot'",
"--data-urlencode", "'[email protected]'",
"--data-urlencode", fmt.Sprintf("'admin_password=%s'", mariaDBPassword),
"--data-urlencode", fmt.Sprintf("'admin_password2=%s'", mariaDBPassword),
"--data-urlencode", "'pw_weak=1'",
fmt.Sprintf("'http://%s/wp-admin/install.php?step=2'", wpHost),
}, " "),
}, " && "),
},
Env: []v13.EnvVar{
{
Name: "DEBIAN_FRONTEND",
Value: "noninteractive",
},
},
},
},
RestartPolicy: v13.RestartPolicyNever,
},
}
}