一、問題概述
在一個多月前,組長讓我研究下持續集成。我很自然地選擇了jenkins。當時,(包括現在也是),部分服務器用的是windows主機。
我當時想了想,如果我把jenkins裝在windows上,在windows上打好包后,要怎么把war包或jar包(針對spring boot項目)傳到remote windows主機上呢?
傳過去之后,要怎么把這個包運行起來呢(比如war包丟tomcat,重啟tomcat;比如怎么用java運行spring boot項目),運行肯定是用腳本(bat),但是我怎么
調用這個腳本呢?
想了想,很頭疼。但是我知道linux主機之間,是可以ssh登錄的,並且可以ssh遠程登錄后執行shell的。
所以,最終選擇了centos作為持續集成服務jenkins的操作系統。
研究jenkins以來,作為一個小菜鳥,找了相關的jenkins QQ群加入,群里氣氛很好,大家提問題很多,回答問題的也很多。我就把上述問題在群里問了一下,得到群管理的回復,
針對數據傳輸部分,回復如下,共4種方案。
1。1 在win上裝sshd,在linux上用scp,sftp傳文件到win。
1。2在linux上裝samba客戶端,映射win共享磁盤符到linux。然后cp。
1。3 在linux上裝powershell,然后裝一個第三方模塊。然后在win上開啟。winrm,數據走的是winrm協議端口,即powershell服務器的端口。
1。4 在win上裝ftpd,和道理1。3一樣。數據走的是ftp協議。
今天我就找時間,自己試了下第一種。整整折騰了一天,本來以為搞不出來了,不過還是勉強解決了。
這邊做下記錄。
二(更新)、折騰記錄之安裝 freesshd
update:
當時寫的時候用的是openssh,后來實際情況下,發現部分系統還是執行一些命令會失敗。后來轉到了freesshd這個軟件。
該軟件我個人覺得還是挺好用的。
這里記錄下。
官網:http://www.freesshd.com/?ctt=download
下面是傻瓜式安裝。唯一要注意的是:
1、如果22端口已經被占用,那么只能換端口了,我這邊換成了29
2、要添加用戶名密碼,我這里配了Administrator/1qaz@WSX
jenkins上配置ssh連接:
二、折騰記錄之安裝openssh
參考資料:
https://winscp.net/eng/docs/guide_windows_openssh_server
1.下載openssh
https://github.com/PowerShell/Win32-OpenSSH/releases
這邊按照自己的機器,選一個版本吧,我看了下,貌似都是beta版,但我感覺還是很穩定的。我選擇的是下圖箭頭所示的64位版本。
2.安裝
先解壓縮,不一定非要解壓縮到下圖的地方,自己開心就好。
在解壓的目錄下,按住shift,同時鼠標右鍵,打開cmd或者powershell。
執行:
powershell.exe -ExecutionPolicy Bypass -File install-sshd.ps1
繼續右鍵打開powershell(在我這,cmd不行,但是在powershell窗口可以成功),
執行:(這句意思是加防火牆規則,放行22端口)
New-NetFirewallRule -Name sshd -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
如果提示New-NetFirewallRule不被認識,找個powershell吧,或者查一下,或者手動添加入站規則。
接下來,去到計算機管理-服務里面,找到sshd服務,配置為開機啟動,然后點下面的啟動,將服務啟動起來。
3、測試一下
1.在自己機器上telnet一下,看看ssh服務的22端口是否是通的。
cmd執行:telnet ip 22
2.shell遠程工具測試下,是否可以登錄。
我這邊用的是secureCRT,XShell或者putty都行。
用戶名密碼就是:遠程桌面用的用戶名密碼。
4、注意點
ssh進去后,主工作目錄在C:\Users\Administrator:
三、Jenkins上配置ssh客戶端
1、jenkins配置ssh主機
在jenkins上,在我觀察,Publish Over SSH算是一個比較流行的ssh工具,可以傳輸文件,也可以執行命令(針對linux,可以執行命令和shel;針對windows,cmd里能夠進行的操作都可以)。
在Jenkins管理界面中,依次打開:系統管理--》系統設置--》Publish over SSH部分,按照下圖進行配置:
填寫完成后,可以點擊下面的按鈕,測試下連接是否成功。
另外,這里有個填ssh端口的輸入框,如果修改了服務端的監聽端口的話,記得對應修改。
2、填寫注意點
Name隨便填,Hostname是ip或者主機名,用戶名同遠程桌面 用的用戶名,點擊高級后,填寫密碼部分。
其中的Remote Directory需要重點關注,該參數表示的是:ssh文件上傳后,文件在遠端服務器上的保存路徑,路徑需要預先建立好。
同時,如果上傳了文件后,需要執行命令的話,這也是bat、shell等命令的工作目錄。
針對windows類型的ssh服務器,這邊的填寫只能寫相對路徑,(base 路徑為C:\Users\Administrator,如果作為非管理員登錄,可能會稍微不一樣,可以自己用SecureCRT之類的登進去試試,看看在哪個路徑下)
如果不填,就是在base路徑;
如果填寫內容為“\”,(不含雙引號),則路徑會是c盤根路徑;
如果填"target",則路徑會是C:\Users\Administrator\target。
我這邊簡單起見,先不填。因為其作為后續命令執行的工作路徑的原因,會有一些坑。后邊我再補充。
四、配置job
1、job中,配置構建操作
先展示下我的構建操作,很簡單,maven編譯,打包
在jenkins的服務器上,找到jenkins的home路徑,其下的workspace目錄中,
我這邊拿來舉例的job是test-deploy。
編譯完成后,我這邊的目錄結構是這樣的:
2、配置構建后操作--ssh發布
cmd /c call C:\Users\Administrator\deploy.bat
以上配置,可以和上一步的構建后文件目錄結構,仔細對照下就知道怎么填寫了。
我這邊也不是最佳實踐,可以留言討論。
3、遠端windows ssh服務器
在C:\Users\Administrator\下的目錄文件結構:
deploy.bat的內容:
echo hello java -version javaw -Xms512m -Xmx512m -Xmn512m -jar target\bdmp-backstage-rest.jar echo bye :end
重點關注的是上面標紅的地方:
這里,必須是javaw。不信可以多試試。最終是怎么選定這個方案的,后面說。只關注結果的話,復制上面的就可以。
我這邊因為bat水平有限,只寫了啟動程序的部分。
按理說,是需要上傳后,拷貝jar或war到別的目錄,先殺掉以前的進程,(避免端口沖突而啟動失敗),再開啟新進程。
這塊留作todo吧,有興趣的朋友可以跟我交流。
4、終於ok了,測試一下
在job頁面,點“立即構建“。
運行過程如下:
我們在遠程ssh主機上,看看我們的服務啟動了沒,我這邊通過看端口的方式:
netstat -ano|findstr "18083"
其中18083是我這邊的spring boot應用的服務端口:
不放心的話,可以看看任務管理器。
好了。啟動起來了。
我們再看看jenkins中,job運行完了沒?
運行完了,但是是超時退出的,我這邊還沒想到好辦法。如果你有好的解決辦法,歡迎分享。
五、嘗試過的失敗方案-shell版
我最開始只打算做linux主機之間的持續集成,所以只弄了shell。基於懶,因此看看能不能通過在windows上安裝shell環境來執行shell。
1、准備shell
准備shell環境,我是通過安裝了一個git。然后將git的cmd和bin,配置到了path中。
我瞎搞的,不知道運維界的最佳實踐是啥。(我只是個java開發。。。)
2、windows上手動運行
這邊我把shell共享下(我也是在前人基礎上改的,本身shell水平堪憂)。
bdmp-backstage-rest.sh:

#!/bin/bash source /etc/profile export SERVICE_NAME=bdmp-backstage-rest export JAR_ARTIFACT_NAME=./target/bdmp-backstage-rest.jar export DEBUG_PORT=2222 export JAVA_OPTS="-Xms1024m -Xmx1024m -Xmn512m" cd `dirname $0` echo "CURRENT DIRECTORY:"`pwd` export SCRIPT_NAME=$0 echo "SCRIPT_NAME:"$SCRIPT_NAME" parameters:"$@ echo "Invoke _server.sh now!" ./_server.sh $@
_server.sh:

#!/bin/bash #date 2018-04-12 #author caokunliang #version 1.0 #desc: echo "_server.sh is called! paramters is "$@ echo "JAR_ARTIFACT_NAME:"$JAR_ARTIFACT_NAME echo "SERVICE_NAME:"$SERVICE_NAME echo "JAVA_OPTS:"$JAVA_OPTS if [ -z "$JAR_ARTIFACT_NAME" ]; then echo "請不要直接調用本腳本,並確保調用腳本中設置了JAVA_MAIN_CLASS" exit 1 fi if [ -z "$SERVICE_NAME" ]; then echo "請在調用腳本中設置SERVICE_NAME" exit 2 fi if [ -z "$JAVA_OPTS" ]; then JAVA_OPTS="-Xms1024m -Xmx1024m -Xmn512m" fi #服務進程的pid pids="" #調試模式下的參數 DEBUG_OPTS="" #============================================================================== #根據啟動類類名搜索服務進程的pid #============================================================================== function get_pids() { echo "(${FUNCNAME[@]})" #默認使用JDK自帶的jps echo "jps -l:" jps -l JPS_STATUS=$? if [ $JPS_STATUS -ne 0 ]; then pids=`ps -e -o pid -o command | grep -vi ' grep ' | grep -i 'java' | grep "$JAR_ARTIFACT_NAME" | awk '{print $1}'` else pids=`jps -l|grep "$JAR_ARTIFACT_NAME" | awk '{print $1}'` fi echo "" if [ -z "$pids" ]; then echo "the process of "$JAR_ARTIFACT_NAME " is not found" fi } #============================================================================== #終止服務進程 #============================================================================== function svr_stop() { get_pids if [ -n "$pids" ]; then for p in $pids do echo "kill -9 "$p kill -9 $p done echo "" echo "the process are stopped" fi } #============================================================================== #啟動服務前進行環境准備 #============================================================================== function svr_test() { echo "" java -version > /dev/null JAVA_STATUS=$? if [ $JAVA_STATUS -ne 0 ]; then echo "java not found under the path,please make sure jdk is installed" exit 3 fi cd `dirname $0` if [ "$1" = "debug" ]; then DEBUG_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address="$DEBUG_PORT" " fi if [ -n "$SERVICE_NAME" ]; then echo "SERVICE_NAME = "$SERVICE_NAME fi echo "JAR_ARTIFACT_NAME = "$JAR_ARTIFACT_NAME echo "JAVA_OPTS = "$JAVA_OPTS echo "DEBUG_OPTS = "$DEBUG_OPTS echo "CUSTOM_PARAM = "$@ echo "HOME_DIR = "`pwd` echo "LAUNCH_COMMAND = nohup java "$JAVA_OPTS" "$DEBUG_OPTS" "-jar" "$JAR_ARTIFACT_NAME" >/dev/null 2>&1 &" echo "" } #============================================================================== #啟動服務 #============================================================================== function svr_start() { #nohup java $JAVA_OPTS $DEBUG_OPTS -jar $JAR_ARTIFACT_NAME > nohup.out & nohup java $JAVA_OPTS $DEBUG_OPTS -jar $JAR_ARTIFACT_NAME >/dev/null 2>&1 & } #============================================================================== #根據參數調用對應的服務 #============================================================================== if [ "$1" = "stop" ]; then svr_stop elif [ "$1" = "start" ]; then svr_test $@ svr_start $@ elif [ "$1" = "restart" ]; then svr_stop sleep 2 svr_test $@ svr_start $@ elif [ "$1" = "debug" ]; then svr_stop sleep 2 svr_test $@ svr_start $@ elif [ "$1" = "test" ]; then svr_test $@ get_pids if [ -n "$pids" ]; then for p in $pids do echo "當前服務進程PID = "$p done fi elif [ "$1" = "dump" ]; then get_pids if [ -n "$pids" ]; then for p in $pids do echo "當前服務進程PID = "$p echo "" echo "jinfo "$p" > "$SERVICE_NAME"."$p".jinfo.log" jinfo $p > $SERVICE_NAME.$p.jinfo.log echo "" echo "jstack "$p" > "$SERVICE_NAME"."$p".jstack.log" jstack $p > $SERVICE_NAME.$p.jstack.log echo "" echo "jmap -heap "$p" > "$SERVICE_NAME"."$p".heap.log" jmap -heap $p > $SERVICE_NAME.$p.heap.log echo "" echo "jmap -histo "$p" > "$SERVICE_NAME"."$p".histo.log" jmap -histo $p > $SERVICE_NAME.$p.histo.log echo "" echo "jmap -dump:format=b,file="$SERVICE_NAME"."$p".dump "$p jmap -dump:format=b,file=$SERVICE_NAME.$p.dump $p echo "" TAR_NAME=$SERVICE_NAME".dump."$p"."`date +%Y%m%d.%H%M`".tar.gz" echo "tar --remove-files --exclude=\"*.gz\" -zcvf "$TAR_NAME" "$SERVICE_NAME"."$p".*" tar --remove-files --exclude="*.gz" -zcvf $TAR_NAME $SERVICE_NAME.$p.* echo "" echo "生成打包文件 "`du -h $TAR_NAME | awk '{print $1}'` echo `pwd`"/"$TAR_NAME done fi else echo "" echo "用法: "$SCRIPT_NAME" [參數]" echo "" echo " start 正常啟動服務,服務器首次啟動時使用,綁定端口時如果端口已經被占用" echo " 會直接報錯退出" echo "" echo " stop 使用直接終止進程的方式關閉服務" echo "" echo "" echo " restart 重啟服務,先將原來的服務進程關閉,然后啟動服務" echo "" echo " debug 關閉當前的服務並用debug模式重啟,用於遠程調試" echo "" echo "" echo " dump 保存服務進程的內存堆棧等信息,用於排查問題" echo "" echo " test 查看服務啟動配置信息,不會啟動服務" fi echo "success"
我通過遠程桌面,在服務器里,手動執行bdmp-backstage-rest.sh文件,服務(端口18083)是可以啟動起來的。
3、jenkins ssh啟動shell:
job配置:
job的狀態是success,日志如下:
但是實際上,服務並沒有啟動(或者是啟動了,但是ssh退出登錄時又關閉了)。
4、shell腳本-修改腳本睡眠時間1:
運行:
應該是卡在sleep那一句了。此時,看看windows主機上,進程是否啟動。
進程是啟動的。
后續不出所料的,jenkins那邊,job超時,標記為unstable。然后我繼續查看windows主機,發現進程還在。這有點出乎我的意料。
我想是不是因為睡眠的時間太長,可能睡眠結束,這個進程也就被kill了。
5、shell腳本-修改腳本睡眠時間2:
job日志顯示,20s后,job結束:
windows上,進程還在:
順便看看我的日志吧:
6、shell腳本-修改腳本睡眠時間3:
接下來,修改為3s,我的程序啟動大概要10多秒,這邊如果設為小於程序啟動並綁定端口的時間的話,會怎樣?
這邊不截圖了,效果和第五步一樣。
7、疑惑
但是,為啥一定要sleep呢?昨天我用shell方案的時候,沒想到這茬,因此在多次嘗試無果后,轉向了bat。
有知道的,麻煩解惑。
六、嘗試過的失敗方案-bat版
1、方案1
2、方案2
這個是最開始的版本。網上都是這樣寫的。在遠程桌面進去,手動執行,沒問題的
start javaw -Xms512m -Xmx512m -Xmn512m -jar target\bdmp-backstage-rest.jar
但是,用在我這里,(ssh登錄進來執行bat的方式),卻不行。
后來,我在javaw后,加了pause。
這時,可以查到進程了。但是一旦jenkins那邊,ssh退出了。就查不到了。估計是被kill了。
所以我開始查找,windows要怎么忽略nohup信號(像linux那樣)
3、結論
參考:https://stackoverflow.com/questions/3382082/whats-the-nohup-on-windows
仔細看了這個問題下的每個回答,我總算知道了原因。
在ssh到windows的情況下,只要ssh退出,ssh啟動的任務都會被kill掉。唯一可行的辦法,就是使用javaw。
七、結論
所以,linux通過ssh遠程到windows上,執行sh或者bat,去啟動java子進程。
目前我只知道這兩種方式,一種shell,(需要加sleep,具體原因未明),一種就是bat,必須使用javaw