Apache Ant 實現自動化部署

http://www.netkiller.cn/journal/java.ant.html

Mr. Neo Chen (陳景峯), netkiller, BG7NYT


中國廣東省深圳市龍華新區民治街道溪山美地
518131
+86 13113668890


版權聲明

轉載請與作者聯繫,轉載時請務必標明文章原始出處和作者信息及本聲明。

文檔出處:
http://netkiller.github.io
http://netkiller.sourceforge.net

微信掃瞄二維碼進入 Netkiller 微信訂閲號

QQ群:128659835 請註明“讀者”

2015-11-10

這篇文章幫你解決下列問題:

源碼獲取,源碼編譯,處理配置檔案,應用部署,遠程備份,部署回撤,啟動,伺服器狀態,停止


目錄

1. 背景

在你的企業中是怎樣完成從開發,測試到運維的?

很多企業的升級是這樣做的,寫完代碼後編譯打包,放到FTP上,同時發送一個升級郵件。然後讓運維按照升級文檔,一步一步操作。

這樣的流程有很多問題

  1. 開發者通常是在Windows系統上完成開發與編譯,而伺服器通常是Linux操作系統,操作系統的差異可能導致編譯後的程序運行不了。

  2. 安全形度,源碼可以審查,但編譯檔案無法審查,打包過程可能被植入惡意代碼

  3. 經常出現生產環境與本地開發環境不一致,運行有差異

  4. 浪費人力,理論上代碼寫完,就跟開發人員一點關係都沒有了,但實際上每次升級過程開發與測試都需要在場

稍先進一點做法是使用Subversion/Git,開發將代碼放到版本庫中,運維直接使用 svn up / git pull 升級,這樣做法也有很多問題存在

  1. 首次升級非常慢,svn 還好些,svn只取最後一次提交的版本;git 將所有的版本克隆到本地。
  2. 如果修改了本地檔案,更新會產生衝突
  3. 配置檔案無法個性化配置

2. 我們需要什麼樣的流程

我們需要什麼樣的流程或者什麼樣的流程才是最理想流程?

我認為:
  1. 開發人員不要做與開發無關的事情,代碼寫完就與開發沒有半點關係了。通知測試人員,代碼已經完成。

  2. 測試人員自己部署測試環境,不依賴開發人員,測試完成,通知運維人員可能升級了

  3. 運維人員不接受任何部門提供的打包或補丁程序,代碼只能在配置管理伺服器上完成編譯打包以及部署。

  4. 升級應該由自動化工具完成,而不是人工操作。

開發,測試,運維各司其職,這就是DevOps。

3. 怎樣實現自動部署

實現自動化部署有很多方法,很多年前筆者就開始研究總結,下面是一些經驗分享。

3.1. 操作系統

開發,測試,生產三個環境的配置如果出自同一個模板會減少很多由於環境差異帶來的困擾。

過程 1. 操作系統部署
  1. 無人值守安裝

    通過無人值守腳本安裝操作系統,減少人為安裝造成的差異

  2. 運行環境

    統一配置運行環境,開發庫以及版本統一

  3. 應用伺服器統一

    應用伺服器版本,安裝標準,配置檔案都需要統一,減少差異

3.2. 程序部署

實現應用程序自動部署,首先你要清楚自動部署所需要的流程,部署一個流程通常是這樣的:

過程 2. 自動部署步驟
  1. 初始化

    建立工作環境,例如目錄,檢查所需環境

  2. 獲取

    從版本庫指定分支中獲取代碼並保存到本地

  3. 編譯

    編譯可執行代碼

  4. 配置

    處理配置檔案

  5. 備份

    備份應用程序

  6. 停止

    服務服務

  7. 部署

    部署應用程序到目的主機,如果已存在需要覆蓋原來的程序

  8. 啟動

    啟動服務

3.3. 自動部署程序

自動部署程序完成上面的部署,還需要做下面一些事情。

日誌功能
  1. 記錄什麼時間點做過部署
  2. 部署了那些檔案

4. Apache Ant 實現自動化部署

4.1. 運行環境

準備一個全新的的伺服器,最小化安裝CentOS 7操作系統,然後運行下面腳本初始化

			
curl -s https://raw.githubusercontent.com/oscm/shell/master/os/centos7.sh | bash
curl -s https://raw.githubusercontent.com/oscm/shell/master/os/selinux.sh | bash
curl -s https://raw.githubusercontent.com/oscm/shell/master/os/iptables/iptables.sh | bash
curl -s	https://raw.githubusercontent.com/oscm/shell/master/os/ntpd/ntp.sh |	bash
curl -s	https://raw.githubusercontent.com/oscm/shell/master/os/ssh/sshd_config.sh	| bash
curl -s	https://raw.githubusercontent.com/oscm/shell/master/os/user/www.sh |	bash
curl -s	https://raw.githubusercontent.com/oscm/shell/master/lang/gcc/gcc.sh	| bash
			
			

安裝 server-jre 與 apache-tomcat

			
curl -s	https://raw.githubusercontent.com/oscm/shell/master/lang/java/server-jre-8u40.sh	| bash
curl -s	https://raw.githubusercontent.com/oscm/shell/master/web/tomcat/apache-tomcat-8.0.26.sh	| bash
curl -s	https://raw.githubusercontent.com/oscm/shell/master/web/tomcat/systemctl.sh	| bash
			
			

請使用systemctl 啟動與停止 Tomcat

			
systemctl start tomcat
systemctl stop tomcat
			
			

Infrastructure Management Shell https://github.com/oscm/shell

4.2. 部署機

安裝Ant

			
curl -s	https://raw.githubusercontent.com/oscm/shell/master/lang/java/ant.sh | bash
			
			

下載build.xml檔案 https://github.com/oscm/build/tree/master/Ant

			
wget https://raw.githubusercontent.com/oscm/build/master/Ant/build.xml			
			
			

打開 build.xml 檔案

			
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Homepage: http://www.netkiller.cn
Author: neo <netkiller@msn.com>
Date: 2015-12-07
-->
<project name="admin.example.com" default="compile" basedir=".">

	<property name="repository" value="git@58.96.11.18:example.com/admin.example.com.git" />
	<property name="branch" value="master" />

	<property name="remote" value="www@23.25.22.72" />
	<property name="destination" value="/www/example.com/admin.example.com" />

	<property name="project.dir" value="repository" />
	<property name="project.src" value="${project.dir}/src" />
	<property name="project.build" value="build" />
	<property name="project.config" value="config" />
	<property name="project.log" value="log" />

	<property name="pkg" value="example-1.0.0.jar" />

	<property name="backup.dir" value="backup" />
	<property name="receive.timepoint" value="2015-12-04.17:46:35" />

	<property name="build.sysclasspath" value="last" />
	<property environment="env" />
	<echo message="JAVA_HOME is set to = ${env.JAVA_HOME}" />
	<echo message="CATALINA_HOME is set to = ${env.CATALINA_HOME}" />

	<path id="classpath">
		<fileset dir="${env.JAVA_HOME}/lib">
			<include name="*.jar" />
		</fileset>
		<fileset dir="${env.CATALINA_HOME}/lib">
			<include name="*.jar" />
		</fileset>
		<fileset dir="${project.dir}/WebRoot/WEB-INF/lib" includes="*.jar" />
	</path>

	<macrodef name="git">
		<attribute name="command" />
		<attribute name="dir" default="" />
		<element name="args" optional="true" />
		<sequential>
			<!-- echo message="git @{command}" / -->
			<exec executable="git" dir="@{dir}">
				<arg value="@{command}" />
				<args />
			</exec>
		</sequential>
	</macrodef>

	<macrodef name="rsync">
		<attribute name="option" default="auzv" />
		<attribute name="src" default="" />
		<attribute name="dest" default="" />
		<element name="args" optional="true" />
		<sequential>
			<!-- echo message="rsync @{option} ${src} ${dest}" / -->
			<exec executable="rsync">
				<arg value="@{option}" />
				<args />
				<arg value="@{src}" />
				<arg value="@{dest}" />
			</exec>
		</sequential>
	</macrodef>

	<macrodef name="ssh">
		<attribute name="host" />
		<attribute name="command" />
		<attribute name="keyfile" default="~/.ssh/id_rsa" />
		<element name="args" optional="true" />
		<sequential>
			<exec executable="ssh">
				<arg value="@{host}" />
				<!-- arg value="-i @{keyfile}" / -->
				<args />
				<arg value="@{command}" />
			</exec>
		</sequential>
	</macrodef>

	<target name="dir.check">
		<condition property="dir.exists">
			<available file="${project.dir}" type="dir" />
		</condition>
	</target>

	<target name="clone" depends="dir.check" unless="dir.exists">
		<echo>clone</echo>
		<git command="clone">
			<args>
				<arg value="${repository}" />
				<arg value="${project.dir}" />
			</args>
		</git>
	</target>

	<target name="pull" depends="clone" if="dir.exists">
		<echo>${project.dir} exists</echo>
		<git command="pull" dir="${project.dir}" />
		<git command="clean" dir="${project.dir}">
			<args>
				<arg value="-df" />
			</args>
		</git>

		<git command="reset" dir="${project.dir}">
			<args>
				<arg value="HEAD" />
				<arg value="--hard" />
			</args>
		</git>
	</target>

	<target name="branch" depends="pull" if="dir.exists">
		<echo>${project.dir} exists</echo>
		<git command="checkout" dir="${project.dir}">
			<args>
				<arg value="-f" />
				<arg value="${branch}" />
			</args>
		</git>
	</target>

	<target name="init" depends="branch">

		<mkdir dir="${project.build}" />
		<mkdir dir="${project.log}" />

		<copy todir="${project.build}">
			<fileset dir="${project.dir}/WebRoot" includes="**/*" />
		</copy>

		<copy todir="${project.build}/WEB-INF/classes">
			<fileset dir="${project.src}">
				<include name="**/*.xml" />
				<include name="**/*.properties" />
			</fileset>
		</copy>

	</target>
	<target name="compile" depends="init">
		<javac srcdir="${project.src}" destdir="${project.build}/WEB-INF/classes">
			<classpath refid="classpath" />
		</javac>
	</target>

	<target name="config" depends="compile">
		<copy todir="${project.build}" overwrite="true">
			<fileset dir="${project.config}" includes="**/*" />
		</copy>
	</target>

	<target name="deploy" depends="config">
		<tstamp>
			<format property="timepoint" pattern="yyyy-MM-dd.HH:mm:ss" locale="cn,CN" />
		</tstamp>
		<rsync option="-auzv" src="${project.build}/" dest="${remote}:${destination}">
			<args>
				<arg value="--exclude=.git" />
				<arg value="--exclude=.svn" />
				<arg value="--exclude=.gitignore" />
				<arg value="--backup" />
				<arg value="--backup-dir=~/${backup.dir}/${timepoint}" />
				<arg value="--log-file=log/${ant.project.name}.${timepoint}.log" />
			</args>
		</rsync>
	</target>

	<target name="pkg" depends="compile">
		<jar jarfile="${pkg}" basedir="${project.build}" />
	</target>

	<target name="backup" depends="">
		<tstamp>
			<format property="TIMEPOINT" pattern="yyyy-MM-dd.HH:mm:ss" locale="cn,CN" />
		</tstamp>
		<echo>the backup directory is ${TIMEPOINT}.</echo>
		<mkdir dir="${backup.dir}/${TIMEPOINT}" />
		<rsync option="-auzv" src="${remote}:${destination}" dest="${backup.dir}/${TIMEPOINT}">
		</rsync>
	</target>

	<target name="receive" depends="">
		<echo>the receive directory is ${receive.timepoint}.</echo>
		<rsync option="-auzv" src="${backup.dir}/${receive.timepoint}" dest="${remote}:${destination}" />
	</target>

	<target name="fetch">
		<ant target="pull" />
		<ant target="branch" />
	</target>

	<target name="stop" depends="">
		<!-- ssh host="${remote}" command="/srv/apache-tomcat/bin/catalina.sh stop -force" keyfile="~/.ssh/id_rsa" / -->
		<ssh host="${remote}" command="/srv/apache-tomcat/bin/shutdown.sh" />
		<ant target="status" />
	</target>
	<target name="start" depends="">
		<ssh host="${remote}" command="/srv/apache-tomcat/bin/startup.sh" keyfile="~/.ssh/id_rsa" />
		<ant target="status" />
	</target>
	<target name="status" depends="">
		<ssh host="${remote}" command="ps ax | grep tomcat | grep -v grep" />
	</target>
	<target name="kill" depends="">
		<ssh host="${remote}" command="pkill -9 -f tomcat" />
		<ant target="status" />
	</target>
	<target name="run" depends="">
		<java classname="test.ant.HelloWorld" classpath="${hello}" />
	</target>
	<target name="clean">
		<delete dir="${project.build}" />
		<delete file="${hello}" />
	</target>
</project>

			
			

修改下面幾處定義

			
<property name="repository" value="版本庫地址" />
<property name="branch" value="部署分支" />
<property name="remote" value="遠程伺服器" />
<property name="destination" value="遠程目錄" />		
			
			

開始部署代碼

			
ant backup
ant stop
ant deploy
ant start
			
			

5. 延伸閲讀

如果你想學習製作部署工具,還可以看看筆者早期的作品https://github.com/oscm/deployment這個工具使用Bash開發,寫這個工具僅僅半天時間,後面小改過幾次,這個工具伴隨筆者很多年。

第一個版本因為很多缺陷存在,筆者使用Python重新開發 https://github.com/oscm/devops 這個工具更適合PHP項目部署

筆者微信公眾號,QQ群:128659835 請註明“讀者”