Microservices Benchmarking with DeathStarBench
Experiment replication guide¶
In this document we present the conditions and steps required to replicate the experiment setup for
DeathStarBench Social Network workload and run experiments on a 3-worker-node Kubernetes Cluster using Home-timeline-query.
DeathStarBench Social Network workload¶
The workload is a social network with unidirectional follow
relationships, implemented with loosely coupled microservices,
communicating with each other via Thrift RPCs. It is open source and can
be referenced here:
https://github.com/delimitrou/DeathStarBench/tree/master/socialNetwork
The Social Network application is part of the DeathStarBench benchmark
suite for cloud microservices, that includes five end-to-end services,
four for cloud systems, and one for cloud-edge systems running on drone
swarms.
https://github.com/delimitrou/DeathStarBench
More details on the applications and a characterization of their behavior can be found at "An Open-Source Benchmark Suite for Microservices and Their Hardware-Software Implications for Cloud and Edge Systems", Y. Gan et al., ASPLOS 2019.
Conditions¶
Layers of the cluster (bottom up):
- Operating System: Linux Centos 8.4.2105 - kernel stable v6.0.6
- Container Runtime: Containerd v1.4.12
- Container Orchestrator: Kubernetes v1.21.14
- Container Network Interface: Cilium v1.11.4
- Benchmark Application: DeathStarBench Social Network v0.0.8
They come on top of hardware infrastructure specifications presented in detail in the README enclosed with the data sets.
Experiment steps¶
Bellow we relate the steps took to:
- create the kubernetes cluster
- deploy, initialize and scale the social network
- collect the baseline data
Step_1: create the Kubernetes Cluster¶
Master node kubeadm init command with --skip-phases option:
Master node init config.yaml:
apiVersion: kubeadm.k8s.io/v1beta2
kind: InitConfiguration
nodeRegistration:
taints: []
criSocket: /run/containerd/containerd.sock
kubeletExtraArgs:
node-ip: <MASTER_NODE_IP>
localAPIEndpoint:
advertiseAddress: <MASTER_NODE_IP>
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
cpuManagerPolicy: static
systemReserved:
cpu: 500m
memory: 256M
kubeReserved:
cpu: 500m
memory: 256M
topologyManagerPolicy: best-effort
maxPods: 4000
---
apiVersion: kubeadm.k8s.io/v1beta2
kind: ClusterConfiguration
networking:
dnsDomain: cluster.local
podSubnet: 10.244.0.0/16
serviceSubnet: 10.96.0.0/16
controlPlaneEndpoint: "<MASTER_NODE_IP>:6443"
apiServer:
extraArgs:
advertise-address: <MASTER_NODE_IP>
Worker nodes kubeadm join command:
Worker nodes join config.yaml:
apiVersion: kubeadm.k8s.io/v1beta2
kind: JoinConfiguration
nodeRegistration:
taints: []
kubeletExtraArgs:
node-ip: <WORKER_NODE_IP>
discovery:
bootstrapToken:
apiServerEndpoint: <MASTER_NODE_IP>:6443
# REPLACE WITH OUTPUT FROM MASTER NODE KUBEADM INIT
token: <TOKEN>
caCertHashes:
- sha256:<SHA KEY>
Step_2: install Kubernetes Cluster Network Plugin¶
Cilium repo:
Cilium install options:
helm install cilium cilium/cilium --version 1.11.4 \
--namespace kube-system \
--set kubeProxyReplacement=strict \
--set k8sServiceHost=<MASTER_NODE_IP> \
--set k8sServicePort=6443 \
--set nodePort.directRoutingDevice=<NIC_NAME> \
--set enableEndpointRoutes=true \
--set nativeRoutingCIDR=10.0.0.0/16 \
--set tunnel=disabled \
--set autoDirectNodeRoutes=true
Step_3: deploy DeathStarBench Social Network¶
DeathStarBench github repo and commit id used:
git clone https://github.com/delimitrou/DeathStarBench
git checkout 25f60f07edc8031cec7d7fd582e6732ace34d6a3
Install Social Network helm command:
helm install dsb DeathStarBench/socialNetwork/helm-chart/socialnetwork -n deathstarbench-social-network
--create-namespace
--set global.dockerRegistry=<docker private registry if available>
--set-string global.topologySpreadConstraints=
"- maxSkew: 1
topologyKey:
kubernetes.io/hostname
whenUnsatisfiable:
DoNotSchedule
labelSelector:
matchLabels:
service: {{ .Values.name }}"
Checks, info and best practices:
* Jaeger pod should be scheduled on master node, please move pod on master node if deployed on one of the worker nodes.
The reason is that jaeger service registers high values of cpu utilization and will interfere with performance of services
scheduled on the respective node.
* Topology spread constraints ensure that when scaled service replicas are evenly distributed among the 3 worker nodes.
* GitHub repo commit id used assures versions:
* socialNetwork-0.0.8
* nginx-thrift docker image: yg397/openresty-thrift:xenial
Step_4: deploy Bitnami Redis Cluster¶
Bitnami charts github repo:
Install Bitnami Redis chart helm command:
helm install redis-ha charts/bitnami/redis
--set master.persistence.enabled=false
--set replica.persistence.enabled=false
--set auth.enabled=false
--set replica.replicaCount=6
--set-string global.topologySpreadConstraints=
"- maxSkew: 1
topologyKey:
kubernetes.io/hostname
whenUnsatisfiable:
DoNotSchedule
labelSelector:
matchLabels:
service: {{ .Values.name }}"
Checks, info and best practices: * Redis pods should all be scheduled on the worker nodes, to ensure that please taint master node as unschedulable before installing redis chart * Topology spread constraints ensure that redis replicas are evenly distributed among the 3 worker nodes.
Step_5: delete default home-timeline-redis service and point home-timeline-service to redis-ha-master¶
Delete default home-timeline-redis service:
Edit home-timeline-service config map and change home-timeline-redis addr to redis-ha-master:
kubectl edit cm home-timeline-service -n deathstarbench-social-network
..
"home-timeline-redis": {
"addr": "redis-ha-master",
"port": 6379,
...
Redeploy the home-timeline-service pod:
Step_6: initialize the Social Graph with sample users, followers and posts¶
Initialization script is located at DeathStarBench/socialNetwork/scripts/init_social_graph.py.
DeathStarBench//socialNetwork/datasets/social-graph/socfb-Reed98 dataset contains 962 users with 15 followers each (14430 edges):
For each user add 110 sample posts:
Step_7: point home-timeline-service to redis-ha-replicas service¶
Edit home-timeline-service config map and change home-timeline-redis addr to redis-ha-replicas:
kubectl edit cm home-timeline-service -n deathstarbench-social-network
..
"home-timeline-redis": {
"addr": "redis-ha-replicas",
"port": 6379,
...
Redeploy the home-timeline-service pod:
Step_8: set nginx-thrift worker_processes and scale services according to BKC¶
Set nginx-thrift worker_processes to 110/200 according to the BKC:
kubectl edit cm nginx-thrift -n deathstarbench-social-network
...
# Checklist: Make sure that worker_processes == #cores you gave to
# nginx process
worker_processes 110;
...
Redeploy nginx-thrift pod:
Scale services according to Best Known Configuration (BKC) to provide response latency time withing 100ms SLA for a Maximum Number of Requests per Second:
kubectl scale deployment home-timeline-service -n deathstarbench-social-network --replicas 18
kubectl scale deployment nginx-thrift -n deathstarbench-social-network --replicas 3
kubectl scale deployment post-storage-memcached -n deathstarbench-social-network --replicas 3
kubectl scale deployment post-storage-service -n deathstarbench-social-network --replicas 18
Step_9: run wrk2 workloads¶
wrk2 close-loop load generator opens -c number of connections to nginx-thrift service endpoint and makes -R number requests
per second for the duration of -d seconds. Each request triggers the action of reading the home-timeline for a random chose
social-netowrk user. On client side the requests are parallelized using -t number of threads.
wrk -t100 -c9000 -d 60s -L -s "scripts/social-network/read-home-timeline.lua" http://nginx-thrift.deathstarbench-social-network.svc.cluster.local:8080 -R 90000
Snipped from the output of the wrk2 load generator:
Running 1m test @ http://nginx-thrift.deathstarbench-social-network.svc.cluster.local:8080
100 threads and 9000 connections
...
Thread Stats Avg Stdev 99% +/- Stdev
Latency 29.40ms 378.32ms 26.90ms 99.70%
Req/Sec 0.91k 215.55 1.38k 68.73%
Latency Distribution (HdrHistogram - Recorded Latency)
50.000% 9.76ms
75.000% 12.92ms
90.000% 16.70ms
99.000% 26.90ms
99.900% 7.65s
99.990% 11.66s
99.999% 12.51s
100.000% 13.02s
----------------------------------------------------------
5369637 requests in 1.00m, 59.82GB read
Requests/sec: 89918.38
Transfer/sec: 1.00GB
wrk2 is built and installed from DeathStarBench/socialNetwork/wrk2:
The lua scripts employed by wrk2 to trigger different actions are located at DeathStarBench/socialNetwork/wrk2/scripts/social-network:
This experiment only uses read-home-timeline requests.
The wrk2 was build and run inside a container deployed in the same namespace as the social network app, this allowed us to send
the wrk2 requests directly to the nginx-thrift FQDN nginx-thrift.deathstarbench-social-network.svc.cluster.local and also to partially qualified one:
nginx-thrift on port 8080:
kubectl exec -it deathstarclient-wrk2-rebase -c server -n deathstarbench-social-network -- /bin/bash -c './wrk2/wrk -t100 -c9000 -d 6s -L -s "socialnet/wrk-scripts/read-home-timeline.lua" http://nginx-thrift:8080 -R 90000'
The Docker file used to build wrk2 and execute workloads inside a container:
FROM ubuntu:xenial
WORKDIR /workspace
RUN apt-get update && apt-get install -y software-properties-common && add-apt-repository ppa:deadsnakes/ppa
RUN apt-get update
RUN apt-get install -y python3.6 python3.6-dev python3-pip
RUN ln -sfn /usr/bin/python3.6 /usr/bin/python3 && ln -sfn /usr/bin/python3 /usr/bin/python && ln -sfn /usr/bin/pip3 /usr/bin/pip
RUN apt-get install -y curl vim dnsutils
RUN apt-get install -y libssl-dev
RUN apt-get install -y libz-dev
RUN apt-get install -y luarocks
RUN luarocks install luasocket
COPY . /workspace
RUN pip3 install --upgrade pip
RUN pip3 install --no-cache-dir -r requirements.txt
# Compile and install wrk2 to /usr/local/bin
COPY wrk2/ /workspace/wrk2
WORKDIR /workspace/wrk2
RUN make -j8 && make install
# Copy scripts to container.
COPY scripts/ /workspace/scripts
WORKDIR /workspace
CMD [ "python3", "./sleep.py" ]
Folder wrk2 would be folder DeathStarBench/socialNetwork/wrk2/.
Folder scripts would be folder DeathStarBench/socialNetwork/wrk2/scripts.
Step_10: collect baseline data¶
Collecting the baseline for a range of RPS (requests per second) involves running consecutive
wrk2 workloads starting from a specific RPS value and increasing it with a step of 1000 RPS.
wrk -t100 -c7100 -d 60s -L -s "<rht lua script>" http://<nginx-thrift-service:port> -R 71000
wrk -t100 -c7200 -d 60s -L -s "<rht lua script>" http://<nginx-thrift-service:port> -R 72000
wrk -t100 -c7300 -d 60s -L -s "<rht lua script>" http://<nginx-thrift-service:port> -R 73000
wrk -t100 -c7400 -d 60s -L -s "<rht lua script>" http://<nginx-thrift-service:port> -R 74000
...
wrk -t100 -c9500 -d 60s -L -s "<rht lua script>" http://<nginx-thrift-service:port> -R 95000
wrk -t100 -c9600 -d 60s -L -s "<rht lua script>" http://<nginx-thrift-service:port> -R 96000
wrk -t100 -c9700 -d 60s -L -s "<rht lua script>" http://<nginx-thrift-service:port> -R 97000
wrk -t100 -c9800 -d 60s -L -s "<rht lua script>" http://<nginx-thrift-service:port> -R 98000
wrk -t100 -c9900 -d 60s -L -s "<rht lua script>" http://<nginx-thrift-service:port> -R 99000
wrk -t100 -c1000 -d 60s -L -s "<rht lua script>" http://<nginx-thrift-service:port> -R 100000
The Connections / RPS ratio (c/R) is always kept at 1/10 and the -t is fixed at 100 threads.
The "c/R" ratio translates in 1 connection executes a number of 10 requests per second.
To plot the data we parse the out or the each wrk2 command and extract the P99 value from Latency Distribution (HdrHistogram - Recorded Latency) section.
The RPS number is X axis and the P99 response latency is the Y axis.
We call the previous value of RPS before the SLA is broken MAX RPS. The SLA is P99 = 100ms.