讀古今文學網 > Spring Boot實戰 > 8.3 推上雲端 >

8.3 推上雲端

服務器硬件的購買和維護成本很高。大流量很難通過適當擴展服務器去處理,這種做法在某些組織中甚至是禁忌。現如今,相比在自己的數據中心運行應用程序,把它們部署到雲上是更引人注目,而且划算的做法。

目前有多個雲平台可供選擇,而那些提供Platform as a Service(PaaS)能力的平台無疑是最有吸引力的。PaaS提供了現成的應用程序部署平台,帶有附加服務(比如數據庫和消息代理),可以綁定到應用程序上。除此之外,當你的應用程序要求提供更大的馬力時,雲平台能輕鬆實現應用程序在運行時向上(或向下)伸縮,只需添加或刪除實例即可。

之前我們已經把閱讀列表應用程序部署到了傳統的應用服務器上,現在再試試將其部署到雲上。我們將把應用程序部署到Cloud Foundry和Heroku這兩個著名的PaaS平台上。

8.3.1 部署到Cloud Foundry

Cloud Foundry是Pivotal的PaaS平台。這家公司也贊助了Spring Framework和Spring平台裡的其他庫。Cloud Foundry裡最吸引人的特點之一就是它既有開源版本,也有多個商業版本。你可以選擇在何處運行Cloud Foundry。它甚至還可以在公司數據中心的防火牆後運行,提供私有雲。

我打算將閱讀列表應用程序部署到Pivotal Web Services(PWS)上。這是一個由Pivotal托管的公共Cloud Foundry,地址是http://run.pivotal.io。如果想使用PWS,你可以註冊一個賬號。PWS提供為期60天的免費試用,在試用期間無需提交任何信用卡信息。

在註冊了PWS後,可以從https://console.run.pivotal.io/tools下載並安裝cf命令行工具。你可以通過這個工具將應用程序推上Cloud Foundry。但你要先用這個工具登錄自己的PWS賬號。

$ cf login -a https://api.run.pivotal.io
API endpoint: https://api.run.pivotal.io

Email> {your email}

Password> {your password}
Authenticating...
OK

  

現在我們已經可以把閱讀列表應用程序傳到雲上了。實際上,我們的項目已經做好了部署到Cloud Foundry的準備,只需使用cf push命令把它推上去就好。

$ cf push sbia-readinglist -p build/libs/readinglist.war

  

cf push命令的第一個參數指定了應用程序在Cloud Foundry裡的名稱。這個名稱將被用作托管應用程序的子域名。本例中,應用程序的完整域名將是http://sbia-readinglist.cfapps.io。因此,應用程序的命名很重要。名字必須獨一無二,這樣才不會和Cloud Foundry裡部署的其他應用程序(包括其他用戶部署的應用程序)發生衝突。

因為空想一個獨一無二的名稱有點困難,所以cf push命令提供了一個--random-route選項,可以為你隨機產生一個子域名。下面的例子演示了如何上傳閱讀列表應用程序,生成一個隨機的子域名。

$ cf push sbia-readinglist -p build/libs/readinglist.war --random-route

  

在使用了--random-route後,還是要設定應用程序名稱。會有兩個隨機選擇的單詞添加到後面,組成子域名。(在我自己嘗試的時候,生成的子域名是sbia-readinglist-gastroenterologicalstethoscope。)

不僅僅是WAR文件 雖然我們部署的應用程序是一個WAR文件,但Cloud Foundry也可以部署其他格式的Spring Boot應用程序,包括可執行的JAR文件,甚至Spring Boot CLI開發的未經編譯的Groovy腳本。

如果一切順利,我們部署的應用程序應該可以處理請求了。假設子域名是sbia-readinglist,你可以用瀏覽器訪問http://sbia-readinglist.cfapps.io,看看效果。你應該會被引導到登錄頁。回想一下,數據庫遷移腳本中插入了一個名為craig的用戶,密碼是password,可以以此登錄應用程序。

你可以在應用程序裡隨便點點,加幾本書。所有的東西都可以運行,但還是有點不對勁。如果重啟應用程序(通過cf restart命令),重新登錄,你會發現閱讀列表清空了。你在重啟前添加的書都不見了。

應用程序重啟後數據消失,原因在於我們還在使用內嵌的H2數據庫。我們可以通過Actuator的/health端點驗證推測。它返回的信息大約是這樣的:

{
  \"status\": \"UP\",
  \"diskSpace\": {
    \"status\": \"UP\",
    \"free\": 834236510208,
    \"threshold\": 10485760
  },
  \"db\": {
    \"status\": \"UP\",
    \"database\": \"H2\",
    \"hello\": 1
  }
}

  

請注意db.database屬性的值。它證實了我們之前的懷疑——果然用的是內嵌的H2數據庫。我們需要修復這個問題。

實際上,Cloud Foundry以市集服務(marketplace services)的形式提供了一些數據庫以供選擇,包括MySQL和PostgreSQL。因為我們已經在項目裡放了PostgreSQL的JDBC驅動,所以就使用市集裡的PostgreSQL服務,名字是elephantsql。

elephantsql服務也有不少計劃可選,小到開發用的小型數據庫,大到工業級生產數據庫。elephantsql的完整計劃列表可以通過cf marketplace命令獲得。

$ cf marketplace -s elephantsql
Getting service plan information for service elephantsql as [email protected]...
OK

service plan           description                   free or paid
turtle                 Tiny Turtle                   free
panda                  Pretty Panda                  paid
hippo                  Happy Hippo                   paid
elephant               Enormous Elephant             paid

  

如你所見,比較嚴謹的生產級數據庫計劃都是要付費的。你可以選擇你所期望的計劃。我先假設你會選擇免費的turtle。

創建數據庫服務的實例,需要使用cf create-service命令,指定服務名、計劃名和實例名。

$ cf create-service elephantsql turtle readinglistdb
Creating service readinglistdb in org habuma /
      space development as [email protected]...
OK

  

服務創建後,需要通過cf bind-service命令將它綁定到我們的應用程序上。

$ cf bind-service sbia-readinglist readinglistdb

  

將一個服務綁定到應用程序上不過就是為應用程序提供了連接服務的細節,這裡用的是名為VCAP_SERVICES的環境變量。它不會通過修改應用程序來使用服務。

我們可以改寫閱讀列表應用程序,讀取VCAP_SERVICES,使用其中提供的信息來連接數據庫服務。但其實完全不用這麼做。實際上,我們只需用cf restage命令重啟應用程序就可以了:

$ cf restage sbia-readinglist

  

cf restage命令會讓Cloud Foundry重新部署應用程序,並重新計算VCAP_SERVICES的值。如此一來,我們的應用程序會在Spring應用程序上下文裡聲明一個引用了綁定數據庫服務的DataSource Bean,用它來替換原來的DataSource Bean。這樣我們就能拋開內嵌的H2數據庫,使用elephantsql提供的PostgreSQL服務了。

現在來試一下。登錄應用程序,添加幾本書,然後重啟。重啟之後你所添加的書應該還在列表裡,因為它們已經被持久化在綁定的數據庫服務裡,而非內嵌的H2數據庫裡。再訪問一下Actuator的/health端點,返回的內容能證明我們在使用PostgreSQL:

{
  \"status\": \"UP\",
  \"diskSpace\": {
    \"status\": \"UP\",
    \"free\": 834331525120,
    \"threshold\": 10485760
  },
  \"db\": {
    \"status\": \"UP\",
    \"database\": \"PostgreSQL\",
    \"hello\": 1
  }
}

  

Cloud Foundry對Spring Boot應用程序部署而言是極佳的PaaS,Cloud Foundry與Spring項目搭配可謂如虎添翼。但Cloud Foundry並非Spring Boot應用程序在PaaS方面的唯一選擇。讓我們來看看如何將閱讀列表應用程序部署到另一個流行的Paas平台:Heroku。

8.3.2 部署到Heroku

Heroku在應用程序部署上有一套獨特的方法,不用部署完整的部署產物。Heroku為你的應用程序安排了一個Git倉庫。每當你向倉庫裡提交代碼時,它都會自動為你構建並部署應用程序。

如果還是解決不了問題,則需要先將項目目錄初始化為Git倉庫。

$ git init

  

這樣Heroku的命令行工具就能自動把遠程Heroku Git倉庫添加到項目裡。

現在可以通過Heroku的命令行工具在Heroku中設置應用程序了。這裡使用apps:create命令。

$ heroku apps:create sbia-readinglist

  

這裡我要求Heroku將應用程序命名為sbia-readinglist。這將成為Git倉庫的名字,同時也是應用程序在herokuapps.com的子域名。你需要確定這個名字唯一,因為不能有同名應用程序。此外,也可以讓Heroku替你生成一個獨特的名字(比如fierce-river-8120或serene-anchorage-6223)。

apps:create命令會在https://git.heroku.com/sbia-readinglist.git創建一個遠程Git倉庫,並在本地項目的Git配置裡添加一個名為heroku的遠程倉庫引用。這樣就能通過git命令將項目推送到Heroku了。

Heroku裡的項目已經設置完畢,但我們現在還不能進行推送。Heroku需要你提供一個名為Procfile的文件,告訴Heroku應用程序構建後該如何運行。對於閱讀列表應用程序而言,我們需要告訴Heroku,構建生成的WAR文件要當作可執行JAR文件來運行,這裡使用java命令。1假設應用程序是用Gradle來構建的,只需要如下一行內容的Procfile:

1當前使用的項目會實際生成一個可執行的WAR文件。但對Heroku來說,它和可執行的JAR文件沒什麼區別。

web: java -Dserver.port=$PORT -jar build/libs/readinglist.war

 

另一方面,如果你使用Maven來構建項目,JAR文件的路徑就會有所不同。Heroku需要到target目錄,而不是build/libs目錄裡尋找可執行WAR文件。具體如下:

web: java -Dserver.port=$PORT -jar target/readinglist.war

  

不管何種情況,你都需要像例子中那樣設置server.port屬性。這樣內嵌的Tomcat服務器才能在Heroku分配的端口上(通過$PORT變量指定)啟動。

我們差不多可以把應用程序推上Heroku了,但Gradle構建說明還要稍作調整。Heroku構建應用程序時,會執行一個名為stage的任務,因此需要在build.gradle裡添加這個stage任務。

task stage(dependsOn: [\'build\']) {
}

  

如你所見,這個stage任務什麼也沒做,但依賴了build任務。於是,在Heroku使用stage任務構建應用程序會觸發build任務,生成的JAR文件會放在build/libs目錄裡。

你還需要告訴Heroku用什麼Java版本來構建並運行應用程序。這樣Heroku才能用合適的版本來運行它。最簡單的方法是在項目根目錄裡創一個名為system.properties的文件,在其中設置java.runtime.version屬性:

java.runtime.version=1.7

  

現在就可以將項目推上Heroku了。和前面說一樣,只需將代碼推到遠程Git倉庫,Heroku會幫我們搞定其他事情。

$ git commit -am \"Initial commit\"
$ git push heroku master

  

然後,Heroku會根據找到的構建說明文件,使用Maven或Gradle進行構建,再用Procfile裡的指令來運行應用程序。就緒後,你可以用瀏覽器打開http://{app name}.herokuapp.com,這裡的{app name}就是你在apps:create裡給應用程序起的名字。例如,我在部署時將應用程序命名為sbia-readinglist,所以它的URL就是http://sbia-readinglist.herokuapps.com。

你可以在應用程序裡隨便點點,但要訪問一下/health端點。db.database屬性會告訴你應用程序正在使用內嵌的H2數據庫。我們應該把它換成PostgreSQL服務。

我們可以通過Heroku命令行工具的addons:add命令創建並綁定一個PostgreSQL服務。

$ heroku addons:add heroku-postgresql:hobby-dev

  

這裡我們要使用名為heroku-postgresql的附加服務。這是Heroku提供的PostgreSQL服務。我們還要求使用該服務的hobby-dev計劃,這是免費的。

在PostgreSQL服務創建並綁定到應用程序後,Heroku會自動重啟應用程序以保證綁定生效。但即便如此,我們在訪問/health端點時仍然會看到應用程序還在使用內嵌的H2數據庫。那是因為H2的自動配置仍然有效,誰也沒告訴Spring Boot要用PostgreSQL代替H2。

一個辦法是設置spring.datasource.*屬性,做法和我們將應用程序部署到應用服務器上時一樣。我們所需要的信息能在數據庫服務的儀表板上找到,可以用addons:open命令打開儀表板。

$ heroku addons:open waking-carefully-3728

  

在這個例子裡,數據庫實例的名字是waking-carefully-3728。該命令會在Web瀏覽器裡打開儀表板頁面,其中包含了你所需要的全部連接信息,包括主機名、數據庫名和賬戶信息。總之,設置spring.datasource.*屬性所需的一切信息都在這裡了。

還有一個更簡單的辦法,與其自己查找那些信息,再設置到屬性裡,為什麼不讓Spring替我們查找信息呢?實際上,這就是Spring Cloud Connectors的用途。它可以用在Cloud Foundry和Heroku上,查找綁定到應用程序上的所有服務,並自動配置應用程序,以便使用那些服務。

我們只需在項目中加入Spring Cloud Connectors依賴即可。在Gradle項目裡,在build.gradle中添加如下內容:

compile(
      \"org.springframework.boot:spring-boot-starter-cloud-connectors\")

  

如果你用的是Maven,則添加如下Spring Cloud Connectors<dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-cloud-connectors</artifactId>
</dependency>

  

只有激活cloud Profile,Spring Cloud Connectors才會工作。要在Heroku裡激活cloud Profile,可以使用config:set命令:

$ heroku config:set SPRING_PROFILES_ACTIVE=\"cloud\"

  

現在項目裡有了Spring Cloud Connectors依賴,cloud Profile也激活了。我們可以再推一次應用程序。

$ git commit -am \"Add cloud connector\"
$ git push heroku master

  

應用程序啟動後,登入應用程序,查看/health端點。它應該顯示應用程序已經連接到了PostgreSQL數據庫:

\"db\": {
  \"status\": \"UP\",
  \"database\": \"PostgreSQL\",
  \"hello\": 1
}

  

現在我們的應用程序已經部署到雲上,可以接受世界各地的請求了!