# 讓開發自動化: 使用自動化加速部署
_利用自動化加速軟件在不同環境間的遷移_
自動化構建不僅僅適用于開發團隊 —— 在將軟件從開發遷移到生產這一過程中也大有作為。在這一期 _[讓開發自動化](http://www.ibm.com/developerworks/cn/java/j-ap/)_中,自動化專家 Paul Duvall 將介紹如何結合使用 Ant 和 Java?Secure Channel 將軟件遠程部署到多個目標環境中。
## 關于本系列
作為開發人員,我們的工作就是為終端用戶實現過程自動化;然而,很多開發人員卻忽略了將自己的開發過程自動化的機會。為此,我編寫了 [_讓開發自動化_](http://www.ibm.com/developerworks/cn/java/j-ap/)這個系列的文章,專門探討軟件開發過程自動化的實際應用,并教您 _何時_以及 _如何_成功地應用自動化。
您是否曾注意到,很多團隊總是在將軟件從開發環境遷移到生產環境之后才想到改善?我曾經遇到過一些團隊,他們的開發周期長達幾個星期甚至幾個月不等 —我認為這是浪費時間。為什么不像自動化構建一樣,通過自動化大幅度減少花費在部署配置問題上的時間,并因此提升基礎架構的效率?
仔細想一想:軟件部署流程中的低效率意味著將推遲應用程序到用戶的交付。更糟糕的是,一些人認為,對于大多數項目,部署類似于撕掉創可貼(僅會產生暫時的疼痛),然而,部署問題將一再滯留,并且在每次交付時頻繁出現。
除了延遲交付外,低效的部署基礎架構降低了團隊對軟件修改的適應性,這使得他們經常將過多的功能全部填充到一個版本中(因為版本不會經常發布)。這導致了惡性循環:企業希望盡快將軟件交付給用戶,但是這個流程太長,因此所有人都力求設計一個全面的版本(big bang)來最大化業務機遇。
## 實現無憂部署
基本部署流程包括編譯、數據修改集成(例如數據庫表)、向其他計算機遠程部署發行包(如 JAR 和 WAR),以及管理遠程計算機中的資源。盡管如此,仍然有很多工作可以通過自動化完成,比如創建安裝媒體、測試、生成用戶文檔等等。在本文中,我將介紹這些基本內容并演示如何將這些流程納入到自動化構建流程中。具體來說,您將了解以下流程:
* 向遠程機器部署二進制文件
* 外部化(externalize)配置屬性
* 遠程更新 MySQL RDBMS
* 遠程配置 Jakarta 的 Tomcat Servlet 容器
通過自動化實現這些流程,您能夠更迅速更順利地向用戶交付軟件。
* * *
## 必要工具
實現自動化部署的核心工具是一個構建腳本;在本文中,我使用的是 Ant。我的 Ant 腳本將使用屬性文件(特定于目標環境,如演示和生產環境),通過 Ant 的 `sql`任務與 MySQL 數據庫交互,使用 Java Secure Channel (JSch) 將文件復制到遠程機器上(通過 Secure-Copy 協議(SCP))并停止和啟動 Tomcat 服務(通過 SSH)。
圖 1 演示了這一流程的高級架構視圖。關鍵的一點是,所有軟件資源均存儲在一個版本控制庫中,因為理想的構建流程首先將對源代碼(以及配置文件)進行簽出,并在本地構建和打包組件,隨后遠程執行 SQL 語句,最后部署發行包并重新啟動 Tomcat。
##### 圖 1\. 實現遠程部署的高級構建架構

您可以自動執行所有這些流程,這樣,通過執行一條命令或單擊一下鼠標就可觸發部署,甚至不需要人為干涉就可對流程進行調度。是不是很令人興奮?
* * *
## 外部化屬性
對于不同的目標環境,配置值(例如文件位置、主機名、數據庫名和端口號)可能各不相同,因此不能進行硬編碼(例如在源代碼中)。這些屬性在 _.properties_文件中得到了完善的管理。通過外部化屬性,可以使用同一個構建腳本在一個環境中編譯,然后在另一個環境中部署,而不需要修改或重新編譯源代碼。
## 屬性規則
在轉換到不同環境時,如果需要修改某個值,那么將這個值放到外部的 .properties 文件。如果構建腳本中有多處引用該值,那么應將其轉化為構建腳本(即 build.xml)的屬性。如果 _始終_只存在一個引用,就沒有必要將其轉化為屬性(但這僅僅是一個假定條件)。
清單 1 演示了在 Ant 構建腳本中定義屬性的簡單示例,它允許您將一個 .properties 文件作為系統參數傳遞(例如 `test.properties`),其中包含針對特定目標環境的所有值。`property.file.location`可解析為諸如 C:\Documents 和 Settings\patrick.henry\test.properties 這樣的路徑。例如,您可以在命令行輸入:`ant -Dproperty.file.location=C:\Documents and Settings\patrick.henry\test.properties`。
##### 清單 1\. 外部化 property 屬性
```
<property file="${property.file.location}" />
```
清單 2 顯示了一個示例目標環境 .properties 文件。文件中的 _values_屬性在不同目標環境中應該(或者可以)是不同的,但是 _names_屬性則保持不變。
##### 清單 2\. property 文件中的示例屬性和對應的值
```
db.database=brewery
db.username.system=root
db.password.system=sa
db.username=root
db.password=sa
db.hostname=my-hostname.domain.com
db.driver=com.mysql.jdbc.Driver
db.port=3306
db.url.system=jdbc:mysql://${db.hostname}:${db.port}/
db.url=jdbc:mysql://${db.hostname}:${db.port}/${db.database}
```
通過外部化 property 屬性和值,我們可以創建一個更加靈活的構建和部署架構,從而支持多個目標環境。
* * *
## 以簡單性為核心
向另一個環境部署軟件的流程不應該過于繁雜;應盡量保持簡單,就好象輸入 “deploy” 一樣。幸運的是,諸如 Ant 這樣的構建系統使這成為了現實。通過在邏輯上定義一個工作流,按順序執行一系列步驟,您可以創建一個簡單的調用命令。
清單 3 的 `depends`屬性中枚舉的 Ant 目標從較高層次定義了最佳自動部署流程。首先,腳本從本地環境中刪除以前生成的工件(使用 `clean`目標)、編譯源代碼、遠程創建數據庫、應用測試數據、啟動數據庫,最后將 WAR 文件遠程部署到位于目標環境中的 Tomcat 容器中。
##### 清單 3\. 遠程部署中執行的關鍵目標
```
<target name="build"
depends="clean, compile, refresh-database, remote-tomcat-deploy" />
```
刷新數據庫并進行遠程部署并非易事;然而,通過使用一些聰明的腳本,一切都將變得簡單!
* * *
## 自動化 DBA
在設置目標測試環境時,經常需要執行一些手動操作,例如配置數據庫、插入測試數據、刪除舊條目以及其他重復性(并且容易出錯)流程。幸運的是,在部署期間處理數據庫可以變得更加簡單。
Data Definition Language(DDL)語句(如刪除現有數據庫、創建數據庫和創建數據庫用戶)以及 Data Manipulation(DML)語句(如 `insert`語句)可以輕松地腳本化并作為 Ant 構建腳本的一部分運行。而且,還可以遠程執行這些語句。
例如,通過從目標環境 .properties 文件傳遞一個 `db.url.system`屬性(如清單 4 所示),構建環境可以針對一個遠程數據庫執行 SQL 語句:
##### 清單 4\. 創建數據庫和插入數據的腳本
```
<target name="refresh-database" depends="create-database,insert-data" />
<target name="create-database">
<sql driver="${db.driver}"
url="${db.url.system}"
userid="${db.username.system}"
password="${db.password.system}"
src="${database.dir}/create-database.sql">
<classpath>
<pathelement location="${mysql-connector.jar}"/>
</classpath>
</sql>
</target>
...
<target name="insert-data">
<sql driver="${db.driver}"
url="${db.url}"
userid="${db.username}"
password="${db.password}"
src="${database.dir}/insert-data.sql">
<classpath>
<pathelement location="${mysql-connector.jar}"/>
</classpath>
</sql>
</target>
```
清單 5 中 insert-data.sql 文件的內容是從清單 4 的 `insert-data`目標調用的。任何 SQL 語句,DDL 或 DML,都可通過 Ant 的 `sql`任務以類似方式執行。
##### 清單 5\. 執行數據插入的 SQL 語句
```
insert into beer(id, beer_name, date_received) values
(1, 'Samuel Adams Lager','2006-12-09');
insert into beer(id, beer_name, date_received) values
(2, 'Guinness Stout','2006-12-29');
insert into beer(id, beer_name, date_received) values
(3, 'Olde Saratoga Lager','2007-02-14');
insert into beer(id, beer_name, date_received) values
(4, 'Sierra Nevada Pale Ale','2007-05-14');
```
現在我已經更新了遠程數據庫,下一個邏輯步驟是向運行 Tomcat 的遠程環境部署一些資源。
* * *
## 發行和部署
遠程部署和本地部署在實現方面并非完全不同,它僅需要一個不同的通道,從而將資源安全地從一個位置復制到另一個位置(從構建機器復制到目標環境)。在大多數企業中,安全性至關重要,因此僅僅使用 FTP 和 telnet 并不能滿足需求。在這種情況下,SCP 和 SSH 可以輕松完成任務。通過 Ant 可以很方便地使用這些通道;事實上,我經常使用 JSch 中的 `sshexec`和 `scp`任務遠程復制文件并在遠程機器上運行命令。
## 遷移到生產環境
盡管某些軟件應用程序(例如,Software as a service,即 _SaaS_)可以改變部署頻率,但是仍然難于遷移到生產環境中。您需要添加應用程序和數據庫回滾,確保軟件系統能夠回滾到以前的狀態。這一點至關重要,因為有可能帶來數百萬美元的損失或盈利。和測試軟件系統本身一樣,必須對自動化部署流程進行嚴格測試。
### 使用 SCP 安全復制文件
SCP 能夠在兩臺機器之間安全復制資源。很多工具都支持 SCP。在 Ant 中,理論上講,在 Ant 中,JSch 將使用 SCP 復制資源(如 JAR 文件),而 _不需要人為干預_,我喜歡將其稱之為 _自動化_。
在清單 6 中,`scp`任務(JSch 提供)將構建機器中的 WAR 文件復制到遠程機器上。JSch 庫(jsch-0.1.36.jar)必須位于 Ant 的類路徑中,以利用 `scp`任務。
##### 清單 6\. 將一個 WAR 文件從一個機器中安全復制到另一個機器
```
<target name="copy-tomcat-dist">
<scp file="${basedir}/target/brewery.war"
trust="true"
keyfile="${ssh.key.file}"
username="${ssh.username}"
passphrase=""
todir="${ssh.server.username}:${ssh.server.password}@${ssh.server.hostname}
:${tomcat.home}/webapps" />
</target>
```
當調用 `scp`任務時,需要提供進行復制的本地文件的位置,以及本地 SSH 私鑰文件的位置(清單 6 中的 `ssh.key.file`,用于安全身份驗證)。最后,需要在遠程機器上提供一個位置(清單 6 中的 `ssh.server.hostname`),`scp`將把(一個或多個)本地文件放在這個位置。
### 使用 SSH 遠程調用流程
與使用 SCP 一樣,在遠程機器上運行命令通常需要某種安全機制,例如 SSH。在清單 7 中,我使用 JSch `sshexec` Ant 任務停止和重新啟動位于某個遠程機器上的 Tomcat 容器。構建流程剛剛將一系列資源(如 WAR 文件)復制到這臺機器上。
##### 清單 7\. 停止和重新啟動遠程 Tomcat 實例
```
<target name="remote-tomcat-stop>
<sshexec host="${ssh.hostname}"
port="${ssh.port}"
keyfile="${ssh.key.file}"
username="${ssh.username}"
passphrase=""
trust="true"
command="${tomcat.home}/bin/shutdown" />
<sleep seconds="${sleep.time}" />
</target>
...
<target name="remote-tomcat-start">
<sshexec host="${ssh.hostname}"
port="${ssh.port}"
username="${ssh.username}"
passphrase=""
trust="true"
keyfile="${ssh.key.file}"
command="${tomcat.home}/bin/startup" />
<sleep seconds="${sleep.time}" />
</target>
```
在清單 7 中,我提供了托管 Tomcat 的機器的名稱,Tomcat 的端口號(通常為 8080)、私鑰文件(`ssh.key.file`),這樣,構建腳本可以安全地訪問這個機器并執行特定命令。在本例中,可以看到,我依次調用了 `shutdown`和 `startup`命令。
理論上講,完成這一步驟后,我已經完成了下面這些任務:配置遠程數據庫、將一個 Web 應用程序移至遠程計算機、運行一個 Tomcat 實例。至此,人們可以正常測試甚至使用新版本的應用程序。
* * *
## 結束語
希望本文已經向您展示了如何輕松實現自動化部署流程。將軟件從開發環境中交付到用戶手中不能(或不應該)是一個手動流程,也不需要將它從開發團隊的構建流程明確分離出來。實際上,通過本文介紹的方法,軟件發行可以像按下某個按鈕一樣簡單,當然,這必然會顯著提高開發團隊 _頻繁_交付特性的能力。
- 讓開發自動化
- 讓開發自動化: 部署自動化模式,第 2 部分
- 讓開發自動化: 部署自動化模式,第 1 部分
- 讓開發自動化: 使用基于向導的安裝程序
- 讓開發自動化: 針對廣大開發人員的并行開發
- 讓開發自動化: 實現自動化數據庫遷移
- 讓開發自動化: 持續重構
- 讓開發自動化: 文檔化一鍵通
- 讓開發自動化: 利用 Ivy 管理依賴項
- 讓開發自動化: 自動負載測試
- 讓開發自動化: 使用自動化加速部署
- 讓開發自動化: 持續集成反模式
- 讓開發自動化: 斷言架構可靠性
- 讓開發自動化: 持續測試
- 讓開發自動化: 用 Eclipse 插件提高代碼質量
- 讓開發自動化: 除掉構建腳本中的氣味
- 讓開發自動化: 選擇持續集成服務器
- 讓開發自動化: 持續檢查