1. 程式人生 > >AWS EC2+Docker+JMeter構建分散式負載測試基礎架構

AWS EC2+Docker+JMeter構建分散式負載測試基礎架構

[原文連結](https://medium.com/@DragosCampean/how-to-build-a-distributed-load-testing-infrastructure-with-aws-docker-and-jmeter-accf3c2aa3a3) @[Toc] ### 概述及範圍 本文介紹有關如何使用AWS EC2+Docker+JMeter建立分散式負載測試基礎架構。 完成所有步驟後,得到的基礎結構如下: ![AWS EC2+Docker+JMeter基礎架構](https://img-blog.csdnimg.cn/2020022917094142.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3piajE4MzE0NDY5Mzk1,size_1,color_FFFFFF,t_70) 在**Part 1**中,我們將按照所需的步驟進行操作,以建立適合你需求的自定義JMeter Dockerfiles和映像。 然後,在**Part 2**中,我們將在AWS EC2設定中使用這些元素。 接下來開始第一步: ### 前提條件 為了能夠順利的逐步進行配置和操作,你需要上述每個系統(EC2,Docker和JMeter)的一些基本知識。 此外,還需要一個活動的AWS賬戶才能執行所有步驟。 ### Part 1: Local setup—本地配置 #### Step 1: 從Dockerfile建立映像 [dockerfile](https://conetix.com.au/blog/what-is-a-dockerfile/)是開始使用docker所需的基本元素或“ cookbook”,因此我們將從此開始。 我們需要建立2層: 1、一是基礎層,該層建立執行JMeter例項所需的基本設定; 2、二是邏輯層,它是一個JMeter例項,可以是主節點或從節點; JMeter base映像的Dockerfile和entrypoint.sh指令碼如下所示: **Dockerfile:** ```bash # Use Java 11 JDK Oracle Linux FROM openjdk:11-jdk-oracle MAINTAINER Dragos # Set the JMeter version you want to use ARG JMETER_VERSION="5.1.1" # Set JMeter related environment variables ENV JMETER_HOME /opt/apache-jmeter-${JMETER_VERSION} ENV JMETER_BIN ${JMETER_HOME}/bin ENV JMETER_DOWNLOAD_URL https://archive.apache.org/dist/jmeter/binaries/apache-jmeter-${JMETER_VERSION}.tgz # Set default values for allocation of system resources (memory) which will be used by JMeter ENV Xms 256m ENV Xmx 512m ENV MaxMetaspaceSize 1024m # Change timezone to local time ENV TZ="Europe/Bucharest" RUN export TZ=$TZ # Install jmeter RUN yum -y install curl \ && mkdir -p /tmp/dependencies \ && curl -L --silent ${JMETER_DOWNLOAD_URL} > /tmp/dependencies/apache-jmeter-${JMETER_VERSION}.tgz \ && mkdir -p /opt \ && tar -xzf /tmp/dependencies/apache-jmeter-${JMETER_VERSION}.tgz -C /opt \ && rm -rf /tmp/dependencies # Set JMeter home ENV PATH $PATH:$JMETER_BIN # copy our entrypoint COPY entrypoint.sh / RUN chmod +x ./entrypoint.sh # Run command to allocate the default system resources to JMeter at 'docker run' ENTRYPOINT ["/entrypoint.sh" ``` **Entrypoint.sh:** ```bash #!/bin/bash # Run command to allocate the default or custom system resources (memory) to JMeter at 'docker run' sed -i 's/\("${HEAP:="\)\(.*\)\("}"\)/\1-Xms'${Xms}' -Xmx'${Xmx}' -XX:MaxMetaspaceSize='${MaxMetaspaceSize}'\3/' ${JMETER_BIN}/jmeter exec "$@" ``` 在基礎層之上,可以建立一個Master層和一個Slave層。這些Dockerfile可以根據你的特定要求進行自定義。 現在讓我們看一下我們的邏輯層: **Master層的Dockerfile:** ```bash # Use my custom base image defined above FROM dragoscampean/testrepo:jmetrubase MAINTAINER Dragos # Expose port for JMeter Master EXPOSE 60000 ``` **Slave層的Dockerfile:** ```bash # Use my custom base image defined above FROM dragoscampean/testrepo:jmetrubase MAINTAINER Dragos # Expose ports for JMeter Slave EXPOSE 1099 50000 COPY entrypoint.sh / RUN chmod +x ./entrypoint.sh # Run command to allocate the default system resources to JMeter at 'docker run' and start jmeter-server with all required parameters ENTRYPOINT ["/entrypoint.sh"] ``` **Slave層的Entrypoint.sh:** ```bash #!/bin/bash # Run command to allocate the default system resources to JMeter at 'docker run' sed -i 's/\("${HEAP:="\)\(.*\)\("}"\)/\1-Xms'${Xms}' -Xmx'${Xmx}' -XX:MaxMetaspaceSize='${MaxMetaspaceSize}'\3/' ${JMETER_BIN}/jmeter && $JMETER_HOME/bin/jmeter-server \ -Dserver.rmi.localport=50000 \ -Dserver_port=1099 \ -Dserver.rmi.ssl.disable=true \ -Djava.rmi.server.hostname=$HostIP exec "$@" ``` 我們不會詳細討論dockerfiles中的所有內容的含義,在網上有很多這樣的文件。不過值得一提的是與Dockerfiles繫結在一起的**entrypoint shell**指令碼。 docker entrypoints的作用是在執行時將資料初始化或者配置到容器中。在我們的例子中,我們需要它們來指定JMeter允許使用多少記憶體,並使用一些自定義配置來啟動JMeter伺服器,這些配置是基礎設施工作所必需的。這將在“**Step 2**”部分中舉例說明。 現在,讓我們看一下建立Docker映像所需的命令。順便說一下,Docker影象表示一組很好地整合在一起的層,是我們需要的環境的穩定快照。 從這樣一個映像開始,我們可以生成N個容器,這正是我們在這個特定場景中所需要的,這取決於我們想要模擬的負載。 **建立一個簡單的docker映像的命令:** `docker build /path/to/dockerfile` **為docker映像建立一個標籤:** `docker tag imageId username/reponame:imageTag` **同時建立docker映像和標籤:** `docker build -t username/reponame:imageTag /path/to/dockerfile ` #### Step 2: 從一個映像建立一個容器 現在我們已經準備好映像,可以開始從中建立容器,在其中可以實際執行效能測試指令碼。 **建立一個新的容器:** `sudo docker run -dit --name containername repository:tag or imageId /bin/bash` **啟動/停止容器:** `docker start containerId` `docker stop containerId` **訪問正在執行的容器:** `docker exec -it containerId or containerName /bin/bash` 到目前為止,如果你一直使用類似於**Step 1**中提供的Dockerfile,那麼您應該擁有一個完全可用的Java + JMeter容器。 你可以通過檢查工具版本來測試它,看看是否有任何錯誤,甚至可以嘗試執行你計劃在AWS中擴充套件的指令碼(所有這些都應該在執行的容器中完成): `Jmeter -v` `Java -version` `Jmeter -n -t -J numberOfThreads=1 /path/to/script.jmx -l /path/to/logfile.jtl` #### Step 3: 將映像Push/Pull到Dockerhub或任何私有的Docker倉庫(docker登入CLI後) 測試建立的影象是否符合要求的標準(容器內的所有內容),通常,最好將此影象儲存到儲存庫中。然後,你可以在後續隨時從那裡提取它,而不必每次都從Dockerfile構建它。 **`Push`映像到dockerhub:** `docker push username/reponame:imageTag` **從dockerhub中`Pull`已存在的映像(例如jdk映像):** `docker pull openjdk:version` 到此為止,這意味著您已經為cloud setup準備好了一組功能強大的JMeter從屬映像和主映像。 ### Part 2: Cloud端基礎架構——Infrastructure 可以使用[**EC2免費層**](https://aws.amazon.com/cn/ec2/)例項,最多750小時/月,持續1年,因此有很多時間進行試驗。 **`注意:`**`對於下面提供的示例,我使用了`**`Ubuntu Server 18.04 LTS`**`例項,因此提供的命令可能無法在其他Linux發行版上使用。` #### Step 4: 建立安全組——Security Group 使容器內的JMeter例項(master例項或slave例項)能夠通訊,[**自定義安全組**](https://docs.aws.amazon.com/zh_cn/AWSEC2/latest/UserGuide/ec2-security-groups.html)已定義並將其附加到每個主機: 入站規則(Inbound rules): ![Security group inbound rules](https://img-blog.csdnimg.cn/20200229180536299.png) 出站規則(Outbound rules): ![Security group outbound rules](https://img-blog.csdnimg.cn/20200229181109466.png) **注意**:確保將要成為負載測試基礎結構部分的所有例項分配給此安全組,否則它們可能無法相互通訊。 #### Step 5: 建立一個[IAM策略](https://docs.aws.amazon.com/zh_cn/IAM/latest/UserGuide/introduction.html)(可選) 假設您只需要一個由1個JMeter主節點和2個從節點組成的基礎架構。在這種情況下,訪問每個例項並對其進行配置(安裝docker +啟動容器)相對容易。 **`但是,如果需要處理的例項超過3個,會發生什麼情況呢?`** 手動逐個配置變得極其乏味,手動並不是一個好主意。 這時,你將需要一個系統,能夠管理你正在使用的大量容器。一些著名的工具,如谷歌的[**`Kubernetes`**](https://www.infoworld.com/article/3268073/what-is-kubernetes-your-next-application-platform.html),或者[**`Rancher`**](http://www.testautomationguru.com/jmeter-distributed-load-testing-using-docker-rancheros-in-cloud/)等工具。 由於當前使用的是AWS,因此這兩種解決方案似乎過於龐大了,因為亞馬遜針對這一點提供了一個開箱即用的解決方案: “[**`Run Command`**](https://aws.amazon.com/cn/blogs/aws/new-ec2-run-command-remote-instance-management-at-scale/)”功能使我們可以同時在多個EC2例項上執行Shell指令碼。因此,我們不必訪問每個例項,安裝docker並一次一個例項地啟動容器。 能夠通過“**`Run Command`**”功能在EC2例項上執行命令的唯一要求是,適當的IAM角色已與該例項相關聯。我將IAM策略命名為“ **EC2Command**”,併為每個新建立的例項選擇了該策略(但是稍後可以通過“attach/replace role”功能將該角色分配給該例項): ![為現有例項設定IAM策略](https://img-blog.csdnimg.cn/20200229182906271.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3piajE4MzE0NDY5Mzk1,size_1,color_FFFFFF,t_70) ![在例項建立時關聯IAM策略](https://img-blog.csdnimg.cn/2020022919090035.png) 當您建立角色時,請確保將“**AmazonEC2RoleforSSM**”策略附加到您的角色上,這樣就可以了。 ![將許可權關聯到IAM角色](https://img-blog.csdnimg.cn/2020022919094119.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3piajE4MzE0NDY5Mzk1,size_1,color_FFFFFF,t_70) 現在您可以使用“Run command”功能對多個例項批量執行指令碼。 這將我們帶入流程的下一步。 #### Step 6: 在測試機器上安裝Docker 現在,你需要在EC2主機上安裝docker,以便可以啟動容器並將它們連線在一起以進行分散式負載測試。 **直接使用命令(直接在Ubuntu上的例項終端中執行):** ```bash sudo apt-get install curl apt-transport-https ca-certificates software-properties-common \ && curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add \ && sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" \ && sudo apt-get update \ && sudo apt-get install -y docker-ce \ && sudo usermod -aG docker $USER \ && sudo curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose \ && sudo chmod +x /usr/local/bin/docker-compose \ && sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose ``` **通過“Run command”執行的Shell指令碼:** ```bash #!/bin/bash sudo apt-get install curl apt-transport-https ca-certificates software-properties-common curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" sudo apt-get update sudo apt-get install -y docker-ce USER_DOCKER=$(getent passwd {1000..60000} | grep "/bin/bash" | awk -F: '{ print $1}') sudo usermod -aG docker $USER_DOCKER ``` 理想情況下,您將在多個EC2例項上執行第二個指令碼,之後它們都將具有可用的Docker版本。 下一步是配置主節點和從屬節點: #### Step 7: 配置主節點——Master Node 在某些情況下,你甚至不需要多個從屬節點來分散式執行測試,比如,當你有一臺功能強大的主機並且該計算機能夠生成目標的負載量時,對於這種特定情況,不需要Step 8和Step 9。對於這種情況,你甚至不想使用容器並直接在主機上安裝JMeter。 但是,假設你確實需要一個Master + Slaves系統,然後繼續啟動Master容器: **直接使用命令(直接在Ubuntu上的例項終端中執行):** ```bash HostIP=$(ip route show | awk '/default/ {print $9}') \ && docker pull dragoscampean/testrepo:jmetrumaster \ && docker run -dit --name master --network host -e HostIP=$HostIP -e Xms=256m -e Xmx=512m -e MaxMetaspaceSize=512m -v /opt/Sharedvolume:/opt/Sharedvolume dragoscampean/testrepo:jmetrumaster /bin/bash ``` **通過“Run command”執行的Shell指令碼:** ```bash #!/bin/bash HostIP=$(ip route show | awk '/default/ {print $9}') docker pull dragoscampean/testrepo:jmetrumaster docker run -dit --name master --network host -e HostIP=$HostIP -e Xms=256m -e Xmx=512m -e MaxMetaspaceSize=512m -v /opt/Sharedvolume:/opt/Sharedvolume dragoscampean/testrepo:jmetrumaster /bin/bash ``` 指令碼的第一行將機器的私有IP儲存在變數“**HostIP**”中。主的HostIP不用於任何目的,僅使用從屬節點的HostIP。我們將在Step 9看到具體要做什麼。現在,請記住,你可以快速訪問每個容器中主機的專用IP地址。 第二行很簡單,只是從適當的倉庫中獲取影象。 最後一行建立我們將要使用的容器。此命令中有一些要點: 1、'**--network host** '命令啟用主機連網,這意味著容器內的應用程式(JMeter),將在‘entrypoint.sh’指令碼公開的埠上可用。如果沒有它,我就無法進行設定。問題是,即使指令碼是在從節點上執行的,由於錯誤(**java.rmi.ConnectException: Connection refused to host:masterPrivateIP**),主節點上也沒有聚集任何結果。注意,我在較老版本的JMeter(如3.x.x)中沒有遇到這個問題 2、**‘- e Xms=256m -e Xmx=512m -e MaxMetaspaceSize=512m’** 是Xms和Xmx的引數化,**MaxMetaspaceSize**決定了允許使用JMeter的記憶體量。這是通過首先在容器內設定一些環境變數來完成的。然後,在“ entrypoint.sh”指令碼中執行命令,將更改JMeter的“ / bin”資料夾中的“JMeter”檔案。如果未指定這些值,則使用預設值。 要進一步瞭解這些變數代表什麼以及如何設定它們,請閱讀以下內容: Xmx計算如下:**系統總記憶體-(OS使用的記憶體+ JVM使用的記憶體+在計算機上執行所需的任何其他指令碼)** 如果您有一臺專用的測試機器,為避免在測試執行時重新分配Xms,請從一開始就設定**Xms = Xmx**。 **MaxMetaspaceSize**跟蹤所有載入的類元資料和靜態內容(靜態方法,原始變數和物件引用) 例如: 一臺專用機器上64 GB RAM **Xmx** = 56G **Xms** = 56G **MaxMetaspaceSize** = 4096 MB 這為作業系統和其他程序留下了將近4GB的空間,這綽綽有餘。 3、**-v /opt/Sharedvolume:/opt/Sharedvolume userName/repoName:imageTag** 該命令只是將主機上的資料夾對映到容器內的資料夾,你將在其中儲存指令碼檔案和生成的日誌。我們將不做進一步詳細介紹,但是如果您想了解有關卷對映的更多資訊,請參閱[本文和迷你教程](https://www.digitalocean.com/community/tutorials/how-to-share-data-between-the-docker-container-and-the-host)。 #### Step 8: 配置從節點——Slave Nodes “ **HostIP**”變數僅在“**entrypoint.sh**”指令碼中用於此處,以啟用從master伺服器到slave伺服器的遠端訪問(“**-Djava.rmi。server.hostname = $ HostIP**”)。 **直接使用命令(直接在Ubuntu上的例項終端中執行):** ```bash HostIP=$(ip route show | awk '/default/ {print $9}') \ && docker pull dragoscampean/testrepo:jmetruslave \ && docker run -dit --name slave --network host -e HostIP=$HostIP -e HostIP=$HostIP -e Xms=256m -e Xmx=512m -e MaxMetaspaceSize=512m dragoscampean/testrepo:jmetruslave /bin/bash ``` **通過“Run command”執行的Shell指令碼:** ```bash #!/bin/bash HostIP=$(ip route show | awk '/default/ {print $9}') docker pull dragoscampean/testrepo:jmetruslave docker run -dit --name slave --network host -e HostIP=$HostIP -e Xms=256m -e Xmx=512m -e MaxMetaspaceSize=512m dragoscampean/testrepo:jmetruslave /bin/bash ``` #### Step 9: 分散式模式下執行指令碼 到此,準備就緒,可以開始執行測試了。這是我們需要在master主節點上執行以開始執行分散式測試的命令: ```bash jmeter -n -t /path/to/scriptFile.jmx -Dserver.rmi.ssl.disable=true -R host1PrivateIP, host2PrivateIP,..., hostNPrivateIP -l /path/to/logfile.jtl ``` ### 總結: 按照上面的操作步驟,可以實現AWS EC2+Jmeter+Docker的分散式效能測試,可能會遇到一些問題,完全沒問題那是不可能的。 比如: [該文](https://dzone.com/articles/stop-hoping-for-the-best-and-load-performing-tests)提到了一個EC2例項中有太多Websocket連線時可能遇到的問題。 另一個例子是我的一位同事在對Apa​​che伺服器進行負載測試時遇到的情況,他會在JMeter中遇到各種連線錯誤,我們最初認為這是來自被測試的伺服器。解決這個問題的方法來自[這篇簡短的文章。](http://www.linuxbrigade.com/reduce-time_wait-socket-connections/) 我在一個專案中偶然發現的一個問題是,在嘗試從一臺計算機執行大約20000個執行緒時,進行了一些資料驅動的測試。如果在Linux / MacOS終端中鍵入“ **ulimit -a**”,則會看到名為“ open files”的行。問題在於該屬性在測試計算機上設定為1024。使用JMeter執行資料驅動的測試時,此工具將為每個啟動的執行緒開啟.csv檔案或描述符,一旦並行執行緒數超過1024,我將收到錯誤訊息。 **解決方案:** 是從'**/etc/security/limits**'檔案中編輯'**open files**'的最大值,並設定為'**unlimite