前言
自動伸縮是每個人都想要的,尤其是在微服務領域。讓我們看看如何在基于spring boot的應用程序中實現。
我們決定使用kubernetes、pivotal cloud foundry或hashicorp's nomad等工具的一個更重要的原因是為了讓系統可以自動伸縮。當然,這些工具也提供了許多其他有用的功能,在這里,我們只是用它們來實現系統的自動伸縮。乍一看,這似乎很困難,但是,如果我們使用spring boot來構建應用程序,并使用jenkins來實現ci,那么就用不了太多工作。
今天,我將向您展示如何使用以下框架/工具實現這樣的解決方案:
- spring boot
- spring boot actuator
- spring cloud netflix eureka
- jenkins ci
它是如何工作的
每一個包含spring boot actuator庫的spring boot應用程序都可以在/actuator/metrics端點下公開metric。許多有價值的metric都可以提供應用程序運行狀態的詳細信息。在討論自動伸縮時,其中一些metric可能特別重要:jvm、cpu metric、正在運行的線程數和http請求數。有專門的jenkins流水線通過按一定頻率輪詢/actuator/metrics 端點來獲取應用程序的指標。如果監控的任何metric【指標】低于或高于目標范圍,則它會啟動新實例或使用另一個actuator端點/actuator/shutdown來關閉一些正在運行的實例。在此之前,我們需要知道當前有那些實踐在提供服務,只有這樣我們才能在需要的時候關閉空閑的實例或啟動新的新例。
在討論了系統架構之后,我們就可以繼續開發了。這個應用程序需要滿足以下要求:它必須有公開的可以優雅地關閉應用程序和用來獲取應用程序運行狀態metric【指標】的端點,它需要在啟動完成的同時就完成在eureka的注冊,在關閉時取消注冊,最后,它還應該能夠從空閑端口池中隨機獲取一個可用的端口。感謝spring boot,只需要約五分鐘,我們可以輕松地實現所有這些機制。
動態端口分配
由于可以在一臺機器上運行多個應用程序實例,所以我們必須保證端口號不沖突。幸運的是,spring boot為應用程序提供了這樣的機制。我們只需要將application.yml中的server.port屬性設置為0。因為我們的應用程序會在eureka中注冊,并且發送唯一的標識instanceid,默認情況下這個唯一標識是將字段spring.cloud.client.hostname, spring.application.name和server.port拼接而成的。
示例應用程序的當前配置如下所示。
可以看到,我通過將端口號替換為隨機生成的數字來改變了生成instanceid字段值的模板。
1
2
3
4
5
6
7
8
|
spring: application: name: example-service server: port: ${port: 0 } eureka: instance: instanceid: ${spring.cloud.client.hostname}:${spring.application.name}:${random. int [ 1 , 999999 ]} |
啟用actuator的metric
為了啟用spring boot actuator,我們需要將下面的依賴添加到pom.xml。
1
2
3
4
|
<dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-actuator</artifactid> </dependency> |
我們還必須通過http api將屬性management.endpoints.web.exposure.include設置為'*'來暴露actuator的端點?,F在,所有可用的指標名稱列表都可以在/actuator/metrics端點中找到,每個指標的詳細信息可以通過/actuator/metrics/{metricname}端點查看。
優雅地停止應用程序
除了查看metric端點外,spring boot actuator還提供了停止應用程序的端點。然而,與其他端點不同的是,缺省情況下,此端點是不可用的。我們必須把management.endpoint.shutdown.enabled設為true。在那之后,我們就可以通過發送一個post請求到/actuator/shutdown端點來停止應用程序了。
這種停止應用程序的方法保證了服務在停止之前從eureka服務器注銷。
啟用eureka自動發現
eureka是最受歡迎的發現服務器,特別是使用spring cloud來構建微服務的架構。所以,如果你已經有了微服務,并且想要為他們提供自動伸縮機制,那么eureka將是一個自然的選擇。它包含每個應用程序注冊實例的ip地址和端口號。為了啟用eureka客戶端,您只需要將下面的依賴項添加到pom.xml中。
1
2
3
4
|
<dependency> <groupid>org.springframework.cloud</groupid> <artifactid>spring-cloud-starter-netflix-eureka-client</artifactid> </dependency> |
正如之前提到的,我們還必須保證通過客戶端應用程序發送到eureka服務器的instanceid的唯一性。在“動態端口分配”中已經描述了它。
下一步需要創建一個包含內嵌eureka服務器的應用程序。為了實現這個功能,首先我們需要在pom.xml中添加下面這個依賴:
1
2
3
4
|
<dependency> <groupid>org.springframework.cloud</groupid> <artifactid>spring-cloud-starter-netflix-eureka-server</artifactid> </dependency> |
這個main類需要添加@enableeurekaserver注解。
1
2
3
4
5
6
7
|
@springbootapplication @enableeurekaserver public class discoveryapp { public static void main(string[] args) { new springapplicationbuilder(discoveryapp. class ).run(args); } } |
默認情況下,客戶端應用程序嘗試使用8761端口連接eureka服務器。我們只需要單獨的、獨立的eureka節點,因此我們將禁用注冊,并嘗試從另一個eureka服務器實例中獲取服務列表。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
spring: application: name: discovery-service server: port: ${port: 8761 } eureka: instance: hostname: localhost client: registerwitheureka: false fetchregistry: false serviceurl: defaultzone: http: //localhost:8761/eureka/ |
我們將使用docker容器來測試上面的自動伸縮系統,因此需要使用eureka服務器來準備和構建image。
dockerfile和image的定義如下所示。
我們可以使用命令docker build -t piomin/discovery-server:2.0來進行構建。
1
2
3
4
5
6
7
8
|
from openjdk: 8 -jre-alpine env app_file discovery-service- 1.0 -snapshot.jar env app_home /usr/apps expose 8761 copy target/$app_file $app_home/ workdir $app_home entrypoint [ "sh" , "-c" ] cmd [ "exec java -jar $app_file" ] |
為彈性伸縮構建一個jenkins流水線
第一步是準備jenkins流水線,負責自動伸縮。我們將創建jenkins聲明式流水線,它每分鐘運行一次??梢允褂胻riggers指令配置執行周期,它定義了自動化觸發流水線的方法。我們的流水線將與eureka服務器和每個使用spring boot actuator的微服務中公開的metric端點進行通信。
測試服務的名稱是example-service,它和定義在application.yml文件spring.application.name的屬性值(大寫字母)相同。被監控的metric是運行在tomcat容器中的http listener線程數。這些線程負責處理客戶端的http請求。
1
2
3
4
5
6
7
8
9
10
11
12
|
pipeline { agent any triggers { cron( '* * * * *' ) } environment { service_name = "example-service" metrics_endpoint = "/actuator/metrics/tomcat.threads.busy?tag=name:http-nio-auto-1" shutdown_endpoint = "/actuator/shutdown" } stages { ... } } |
使用eureka整合jenkins流水線
流水線的第一個階段負責獲取在discovery服務器上注冊的服務列表。eureka發現了幾個http api端點。其中一個是get /eureka/apps/{servicename},它返回一個給定服務名稱的所有活動實例列表。我們正在保存運行實例的數量和每個實例metric端點的url。這些值將在流水線的下一個階段中被訪問。
下面的流水線片段可以用來獲取活動應用程序實例列表。stage名稱是calculate。我們使用http請求插件 來發起http連接。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
stage( 'calculate' ) { steps { script { def response = httprequest "http://192.168.99.100:8761/eureka/apps/${env.service_name}" def app = printxml(response.content) def index = 0 env[ "instance_count" ] = app.instance.size() app.instance.each { if (it.status == 'up' ) { def address = "http://${it.ipaddr}:${it.port}" env[ "instance_${index++}" ] = address } } } } } @noncps def printxml(string text) { return new xmlslurper( false , false ).parsetext(text) } |
下面是eureka api對我們的微服務的示例響應。響應content-type是xml。
使用spring boot actuator整合jenkins流水線
spring boot actuator使用metric來公開端點,這使得我們可以通過名稱和選擇性地使用標簽找到metric。在下面可見的流水線片段中,我試圖找到metric低于或高于閾值的實例。如果有這樣的實例,我們就停止循環,以便進入下一個階段,它執行向下或向上的伸縮。應用程序的ip地址是從帶有instance_前綴的流水線環境變量獲取的,這是在前一階段中被保存了下來的。
1
2
3
4
5
6
7
8
9
10
11
|
stage( 'metrics' ) { steps { script { def count = env.instance_count for (def i= 0 ;i 100 ) return "up" else if (value.tointeger() < 20 ) return "down" else return "none" } |
關閉應用程序實例
在流水線的最后一個階段,我們將關閉運行的實例,或者根據在前一階段保存的結果啟動新的實例。通過調用spring boot actuator端點可以很容易執行停止操作。在接下來的流水線片段中,首先選擇了eureka實例。然后我們將發送post請求到那個ip地址。
如果需要擴展應用程序,我們將調用另一個流水線,它負責構建fat jar并讓這個應用程序在機器上跑起來。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
stage( 'scaling' ) { steps { script { if (env.scale_type == 'down' ) { def ip = env[ "instance_0" ] + env.shutdown_endpoint httprequest url: ip, contenttype: 'application_json' , httpmode: 'post' } else if (env.scale_type == 'up' ) { build job: 'spring-boot-run-pipeline' } currentbuild.description = env.scale_type } } } |
下面是spring-boot-run-pipeline流水線的完整定義,它負責啟動應用程序的新實例。它先從git倉庫中拉取源代碼,然后使用maven命令編譯并構建二進制的jar文件,最后通過在java -jar命令中添加eureka服務器地址來運行應用程序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
pipeline { agent any tools { maven 'm3' } stages { stage( 'checkout' ) { steps { git url: 'https://github.com/piomin/sample-spring-boot-autoscaler.git' , credentialsid: 'github-piomin' , branch: 'master' } } stage( 'build' ) { steps { dir( 'example-service' ) { sh 'mvn clean package' } } } stage( 'run' ) { steps { dir( 'example-service' ) { sh 'nohup java -jar -deureka_url=http://192.168.99.100:8761/eureka target/example-service-1.0-snapshot.jar 1>/dev/null 2>logs/runlog &' } } } } } |
擴展到多個機器
在前幾節中討論的算法只適用于在單個機器上啟動的微服務。如果希望將它擴展到更多的機器上,我們將不得不修改我們的架構,如下所示。每臺機器都有jenkins代理運行并與jenkins master通信。如果想在選定的機器上啟動一個微服務的新實例,我們就必須使用運行在該機器上的代理來運行流水線。此代理僅負責從源代碼構建應用程序并將其啟動到目標機器上。這個實例的關閉仍然是通過調用http端點來完成。
假設我們已經成功地在目標機器上啟動了一些代理,我們需要對流水線進行參數化,以便能夠動態地選擇代理(以及目標機器)。
當擴容應用程序時,我們必須將代理標簽傳遞給下游流水線。
1
|
build job: 'spring-boot-run-pipeline' , parameters:[string(name: 'agent' , value: "slave-1" )] |
調用流水線具體由那個標簽下的代理運行,是由"${params.agent}"決定的。
1
2
3
4
5
6
|
pipeline { agent { label "${params.agent}" } stages { ... } } |
如果有一個以上的代理連接到主節點,我們就可以將它們的地址映射到標簽中。由于這一點,我們能夠將從eureka服務器獲取的微服務實例的ip地址映射到與jenkins代理的目標機器上。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
pipeline { agent any triggers { cron( '* * * * *' ) } environment { service_name = "example-service" metrics_endpoint = "/actuator/metrics/tomcat.threads.busy?tag=name:http-nio-auto-1" shutdown_endpoint = "/actuator/shutdown" agent_192. 168.99 . 102 = "slave-1" agent_192. 168.99 . 103 = "slave-2" } stages { ... } } |
總結
在本文中,我演示了如何使用spring boot actuato metric來自動伸縮spring boot應用程序。使用spring boot提供的特性以及spring cloud netflix eureka和jenkins,您就可以實現系統的自動伸縮,而無需借助于任何其他第三方工具。本文也假設遠程服務器上也是使用jenkins代理來啟動新的實例,但是您也可以使用ansible這樣的工具來啟動。如果您決定從jenkins運行ansible腳本,那么將不需要在遠程機器上啟動jenkins代理。
好了,以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。
原文鏈接:http://blog.51cto.com/14010829/2299257