Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No performance improvement with Merbridge measurable #291

Open
MerzMax opened this issue Apr 3, 2023 · 22 comments
Open

No performance improvement with Merbridge measurable #291

MerzMax opened this issue Apr 3, 2023 · 22 comments

Comments

@MerzMax
Copy link

MerzMax commented Apr 3, 2023

Hi,

i am currently evaluating Istio together with Merbridge. Apparently I don’t see any improvements when using Merbridge compared to using plain Calico…

Except to the quite loud cloud noise, Merbridge seems to perform worse than Calico. If I understand it correctly, Merbridge should be minimum as fast as Calico and with higher loads even more performant.

Can you explain this behavior?

calico

merbridge

Here are some Versions, that might help explaining the problem:

Istio: 1.16.2 (Installed with helm)
Calico: 3.24.5 (Installed with the Manifest yaml)
Merbridge: installed with kubectl apply -f https://raw.githubusercontent.com/merbridge/merbridge/main/deploy/all-in-one.yaml

@kebe7jun
Copy link
Member

kebe7jun commented Apr 3, 2023

What tool did you use to test it?
Also, default profile does not enable cni mode by default, which may affect performance.

@MerzMax
Copy link
Author

MerzMax commented Apr 3, 2023

@kebe7jun Thanks for your reply!

I use k6 as load test tool and httpbin as the serverside counter part.

I also tried it with the CNI mode, wich did not show any improvements... The averages of the three tests i did were 26.87ms, avg=35.81ms, and avg=16.85ms.

@kebe7jun
Copy link
Member

kebe7jun commented Apr 3, 2023

Can you provide more information? For example, your k6 configuration information, test case information, deployment architecture, etc. Help us to review and troubleshoot.

Theoretically this must be the wrong result and I need to figure out why ...... It would be best if the information you provide can help me reproduce the environment.

@MerzMax
Copy link
Author

MerzMax commented Apr 3, 2023

@kebe7jun Okay, I hope this comment will help you reproducing everything you need:

General

The aim of my loadtests is to verify weather eBPF can improve service mesh networking when using Istio or not. Therefore I test east-west traffic between two pods. In order to "flood" the iptables rules, and slow down the general Istio deployment, I apply pods and services.

Here is a chart which might help:
Bildschirm­foto 2023-04-03 um 11 25 50

Cluster Setup and Installation

The cluster consists of 3 VMs with 4vCPUs, 10GB Disk and 8GB RAM each. As OS they all run Ubuntu 22.04.2 LTS. The nodes I use for the following:

  • main node: runs the Kubernetes control plane
  • worker0: runs the monitoring stack (kube-prom stack)
  • worker1: runs the actual tests
    (this is done by applying a node affinity)

On each VM I install:

  • containerd, 1.6.14
  • runc, 1.1.4
  • cniplugin, 1.1.1
  • kubeadm, 1.26.0-00
  • kubectl, 1.26.0-00
  • kubelet, 1.26.0-00

In order to init kubernetes I execute the following commands:

echo "overlay" >> /etc/modules-load.d/k8s.conf
echo "br_netfilter" >> /etc/modules-load.d/k8s.conf
modprobe br_netfilter
modprobe overlay  
echo "net.bridge.bridge-nf-call-iptables  = 1" >> /etc/sysctl.d/k8s.conf
echo "net.bridge.bridge-nf-call-ip6tables = 1" >> /etc/sysctl.d/k8s.conf
echo "net.ipv4.ip_forward                 = 1" >> /etc/sysctl.d/k8s.conf
sudo sysctl --system

The kubernetes cluster will be initialized with the help of ´kubeadm´ which receives the following configuration:

apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
controlPlaneEndpoint: "mydomain.example.com"
networking:
  podSubnet: 10.244.0.0/16
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
maxPods: 1000

When these installation steps on the VMs are completed I join the worker nodes and install callico:

curl https://raw.githubusercontent.com/projectcalico/calico/v3.24.5/manifests/calico.yaml | kubectl apply -f -
kubectl set env daemonset/calico-node -n kube-system IP_AUTODETECTION_METHOD=kubernetes-internal-ip

In the next step I install Istio:

helm repo add istio https://istio-release.storage.googleapis.com/charts
helm repo update
kubectl create namespace istio-system
helm install istio-base istio/base -n istio-system --version 1.16.2
helm install istiod istio/istiod -n istio-system --wait --version 1.16.2 -f ./istiod_helm_values.yaml

Here is the configuration file I use:

global:
  proxy:
    resources: null

Afterwards the Istio mTLS mode is set to strict by applying this configuration:

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: "mtls-mode-strict"
  namespace: "istio-system"
spec:
  mtls:
    mode: STRICT

Then I install Merbridge (In the tests without Merbridge, this step is, of course, skipped. ;) ):

kubectl apply -f https://raw.githubusercontent.com/merbridge/merbridge/main/deploy/all-in-one.yaml

Afterwards the monitoring stack is depyed, but I don't think that this has to do anything with the described problem. Therefore I will skip them.

In the end I apply an ingress and configure it accordingly:

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.5.1/deploy/static/provider/baremetal/deploy.yaml
MASTER_INTERNAL_IP=$(kubectl get node main-node -o jsonpath="{.status.addresses[?(@.type=='InternalIP')].address}")
kubectl -n ingress-nginx patch svc ingress-nginx-controller -p $(echo \"$MASTER_INTERNAL_IP\" | jq -c -n '.spec.externalIPs |= [inputs]')

Load Test Setup

Cluster flooder

As mentioned before, I try to fill the iptable rules. This is acomplished by a simple Helm setup. Dependent on the configuration, X pods and Y services will be created, which will run on the worder1 node. The services all point to all pods of this cluster flooder component. In my tests I created 250 pods and 250 services.

I uploaded the helm files here: https://github.com/MerzMax/cluster-flooder

Server (httpbin)

This is the configuration and setup I use for the httpbin:

apiVersion: v1
kind: Namespace
metadata:
  name: server
  labels:
    istio-injection: enabled
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpbin
  namespace: server
spec:
  replicas: 1
  selector:
    matchLabels:
      app: httpbin
      version: v1
  template:
    metadata:
      labels:
        app: httpbin
        version: v1
    spec:
      containers:
      - image: docker.io/kennethreitz/httpbin
        name: httpbin
        ports:
        - containerPort: 80
      nodeSelector:
        server: "true"
---
apiVersion: v1
kind: Service
metadata:
  namespace: server
  name: httpbin
  labels:
    app: httpbin
    service: httpbin
spec:
  ports:
  - name: http
    port: 8000
    targetPort: 80
  selector:
    app: httpbin

In order to have some Istio configuration for the servce, I apply the following rules:

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: allow-nothing
  namespace: server
spec:
  {}
---
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: httpbin-allow-get
  namespace: server
spec:
  action: ALLOW
  selector:
    matchLabels:
      app: httpbin
  rules:
  - from:
    - source:
        namespaces: ["client"]
    to:
    - operation:
        methods: ["GET"]
        paths: ["/get"]
        ports: ["80"]
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: httpbin
  namespace: server
spec:
  host: httpbin
  subsets:
  - name: v1
    labels:
      version: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: httpbin
  namespace: server
spec:
  hosts:
  - httpbin
  http:
  - route:
    - destination:
        host: httpbin
        subset: v1
    timeout: 2s

Client (k6)

K6 is deployed the following way:

apiVersion: v1
kind: Namespace
metadata:
  labels:
    istio-injection: enabled
  name: client
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: k6-script
  namespace: client
data:
  script.js: |-
    import http from 'k6/http';

    export const options = {
      scenarios: {
        szenario_load_test: {
          executor: "ramping-arrival-rate",
          
          // It should preallocate 2 VUs before starting the test.
          preAllocatedVUs: 3,
          
          // It is allowed to spin up to `maxVUs` VUs in order to sustain the constant arrival rate
          maxVUs: 100,

          // It should start `startRate` iterations per `timeUnit`
          timeUnit: "1s",

          stages: [

            // Number of `target` iterations per `timeUnit` for `duration`
            { target: 200, duration: '5m' },
          ],
        },
      },
    };

    export default function () {
      http.get('http://httpbin.server:8000/get');
    }
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-nfs-k6
  namespace: client
  labels:
    type: nfs
spec:
  storageClassName: nfs
  capacity:
    storage: 30Gi
  accessModes:
    - ReadWriteOnce
  nfs:
    server: mypvdomain.example.com
    path: "/nfs/k6"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: k6-results-persistent-volume-claim
  namespace: client
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 30Gi
  storageClassName: nfs
---
apiVersion: batch/v1
kind: Job
metadata:
  name: k6
  namespace: client
spec:
  template:
    spec:
      containers:
      - name: k6
        image: grafana/k6:0.43.1
        env:
        - name: K6_PROMETHEUS_RW_SERVER_URL
          value: "http://prometheus-kube-prometheus-prometheus.monitoring:9090/api/v1/write"
        command: [ "/bin/sh", "-c", "--" ]
        args: [ "sleep 20 && k6 run /scripts/script.js -o csv=/k6-results/$(date +%Y-%m-%d_%H-%M-%S)_test_results.csv -o experimental-prometheus-rw --summary-export /k6-results/$(date +%Y-%m-%d_%H-%M-%S)_test_results_summary.json | tee /k6-results/$(date +%Y-%m-%d_%H-%M-%S)_test_results_summary.txt && wget --post-data '' http://127.0.0.1:15020/quitquitquit" ]
        volumeMounts:
        - name: k6-script
          mountPath: /scripts/script.js
          subPath: script.js
        - name: k6-results
          mountPath: /k6-results
      restartPolicy: Never
      volumes:
      - name: k6-script
        configMap:
          name: k6-script
          items:
          - key: script.js
            path: script.js
      - name: k6-results
        persistentVolumeClaim:
          claimName: k6-results-persistent-volume-claim
      nodeSelector:
        client: "true"

@kebe7jun
Copy link
Member

kebe7jun commented Apr 18, 2023

After my testing, it is true that the max duration does increase in CNI mode, which may require further analysis to address. However, overall latency has been optimized to some extent.

I also tested the ambient mode, which further optimizes latency in this mode. Of course, the optimization effect of Merbridge for the same node is more pronounced at present.

without-merbridge-diff-node-test_results_summary.txt:921:     http_req_duration..............: avg=2.1ms   min=936.74µs med=1.97ms  max=31.77ms  p(90)=2.86ms  p(95)=3.15ms
2023-04-18_03-02-38_merbridge-ambient-diff-node-test_results_summary.txt:921:     http_req_duration..............: avg=2.05ms  min=890.7µs  med=1.9ms   max=15.43ms  p(90)=2.84ms  p(95)=3.18ms
2023-04-18_03-11-12_merbridge-main-cni-diff-node-test_results_summary.txt:921:     http_req_duration..............: avg=2.09ms  min=946.97µs med=1.96ms  max=34.72ms  p(90)=2.88ms  p(95)=3.15ms
2023-04-18_03-20-45_merbridge-main-cni-same-node-test_results_summary.txt:920:     http_req_duration..............: avg=1.9ms   min=842.19µs med=1.78ms  max=24.29ms  p(90)=2.58ms  p(95)=2.84ms
2023-04-18_03-30-53_without-merbridge-same-node-test_results_summary.txt:920:     http_req_duration..............: avg=1.98ms  min=856.5µs  med=1.88ms  max=17.62ms  p(90)=2.68ms  p(95)=2.92ms
2023-04-18_03-38-19_merbridge-ambient-same-node-test_results_summary.txt:921:     http_req_duration..............: avg=1.86ms  min=787.21µs med=1.74ms  max=19.97ms  p(90)=2.52ms  p(95)=2.78ms

ambient mode:

kubectl apply -f https://raw.githubusercontent.com/merbridge/merbridge/ambient/deploy/all-in-one.yaml

Note that, ambient profile enable debug mode by default, you should manually disable debug mode by modifing merbridge DaemonSet.

@MerzMax
Copy link
Author

MerzMax commented Apr 18, 2023

Hey @kebe7jun,
first, thanks for investigating the issue! :)

However, two questions have arisen for me:

  1. In the standard deployment file in line 72 the CNI mode is disabled. Therefore, if I understand it correctly, the tests my results are based on do not use this mode. As a result your explaination does not address the problems I determined.
  2. What is the ambeint mode doing differently? Does this have anything to do with the Istio Ambient Mesh? In my setup I am using the standard Istio deployment with the sidecar proxies.

@kebe7jun
Copy link
Member

The ambient mode is the addition of support for Istio ambient mode, which I have not yet merged into the main branch because it is an experimental feature.
In this mode, I did some refactoring of the underlying layer, which makes a big difference in handling the traffic forwarding logic, which can cause some performance impact.
Also, cni mode is no longer needed in ambient mode.

@kebe7jun
Copy link
Member

In the standard deployment file in line 72 the CNI mode is disabled. Therefore, if I understand it correctly, the tests my results are based on do not use this mode. As a result your explaination does not address the problems I determined.

CNI mode optimizes connection performance.

@MerzMax
Copy link
Author

MerzMax commented Apr 18, 2023

I used the CNI mode in the past as well but the results did not show any improvement, compared to the "standard" Merbridge mode.

Test1:

http_req_duration..............: avg=26.87ms min=2.54ms med=6.82ms max=1.03s p(90)=38.01ms p(95)=111.13ms

Test2:

http_req_duration..............: avg=35.81ms min=2.61ms med=6.31ms max=1.46s p(90)=49.01ms p(95)=167.29ms

Test3:

http_req_duration..............: avg=16.85ms min=2.55ms med=5.83ms max=1.25s p(90)=26.12ms p(95)=53.79ms

I will check, wheather I will see improvements with the experimental modeor not..

@MerzMax
Copy link
Author

MerzMax commented Apr 18, 2023

@kebe7jun I tested the ambient implementation and these are my results:

Test1:

http_req_duration..............: avg=17.65ms min=2.33ms med=4.71ms max=800.27ms p(90)=24.61ms p(95)=61.99ms

Test2:

http_req_duration..............: avg=16.4ms min=2.35ms med=4.7ms max=733.69ms p(90)=29.11ms p(95)=76.3ms

Test 2:

http_req_duration..............: avg=23.55ms min=2.27ms med=4.22ms max=2s p(90)=20.16ms p(95)=63.01ms

From my point of view this does not look like an improvement either :/

@kebe7jun
Copy link
Member

what does Test* stand for?

@MerzMax
Copy link
Author

MerzMax commented Apr 19, 2023

@kebe7jun I just executed k6 3 times to get several test results.

@kebe7jun
Copy link
Member

It looks like there is a large error in your results. I am not sure about the reliability of this test, as I conducted it in an environment with limited application. Perhaps other running applications could affect the performance?

Alternatively, could you provide a comparison between the results with and without Merbridge in an ambient mode? Could you please run the test three times?

@MerzMax
Copy link
Author

MerzMax commented Apr 19, 2023

@kebe7jun

It could be that other applications affect it, but I have no idea which ones. On the other side, the results measured with "plain" calico and without Merbridge display high latency as well. If I understand eBPF and Merbridge correctly, the latency and CPU usage should be decrease when using Merbridge. As a result the request duration should be lower when using eBPF, which my tests with Merbridge can not verify. This is the case for both the standard and the ambient configuration.

The test results from the standard Merbridge configuration can be found in the second chart of the initial issue.

The results from using merbridge not in the am

@kebe7jun
Copy link
Member

Yes, Merbridge does optimize network latency, but this effect is likely to be around 5-12% in a typical test scenario, which is likely to be submerged in error because the percentage difference is not significant.
The same test case, in my environment, the average latency is around 2ms, but in your environment it reaches 10ms+, which seems a bit abnormal, maybe related to the load of the nodes or other issues? (I deployed the environment exactly as you provided yaml).
I think the test results may be inaccurate if the test system is not providing stable service.

@MerzMax
Copy link
Author

MerzMax commented Apr 19, 2023

@kebe7jun You are right that the underlying infrastructure is not the best for load tests. Additionally I am surprised, that your setup's latency is less then 20% of my measured latency.

Did you deploy the "cluster-flooder" to your setup and how did you setup your calico / other CNI beneath Merbridge?

@kebe7jun
Copy link
Member

No cluster-flooder installed...

@MerzMax
Copy link
Author

MerzMax commented Apr 19, 2023

@kebe7jun This should make a hudge difference..
In my configurationt I run 250 Pods which are all registered as endpoints in 250 Services.

@kebe7jun
Copy link
Member

kebe7jun commented Apr 20, 2023

My point is that the improvement in Merbridge is not significant(only ~10%), and when we really need to test performance metrics, there is no need to introduce other variables that will increase the error, which will prevent us from achieving the desired results of performance testing. Therefore, I did not deploy the workload.

@tanjunchen
Copy link
Contributor

refer to #172

@tanjunchen
Copy link
Contributor

@MerzMax you can try https://github.com/istio/tools/tree/master/perf/benchmark to test merbrdige.

@MerzMax
Copy link
Author

MerzMax commented Apr 21, 2023

@kebe7jun
I want to test more than just plain eBPF versus iptables. In order to slow down the iptables check, a bunch of Services and Pods are needed to "spam" the rules. The cluster-flooder application accomplishes this. The cluster-flooder Pods are just Pods, including a container doing nothing (kubernetes/pause container). Therefore the applications should not increase the error. If they do so, they at least are increasing the error for the iptables and the eBPF test, which still allows comparability.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants