申明:
ezdpl不是開箱即用的,需要根據自己的應用環境定製。對初學者來說使用起來反倒困難更多、風險更大。
它不是一個通用的項目,更多的是提供一種思路,將繁瑣的操作變得簡單易行,而且是用最原始的上傳檔案、執行指令碼的方式。但是要享受簡單,首先要做艱苦的準備和測試工作。
地址: https://github.com/Panblack/ezdpl
一、說明
1、組成
1) ezdpl.sh 主指令碼,批量部署用的 auto.sh 指令碼
2) 應用目錄,可以包含任意個版本目錄
3) 版本目錄,含files和可選的 pre.sh、fin.sh 指令碼
2、工作目錄
├── 應用A
│ ├── 版本1
│ │ ├── files
│ │ │ ├── etc
│ │ │ ├── usr
│ │ │ └── opt
│ │ ├── pre.sh
│ │ └── fin.sh
│ ├── 版本2
│ │ └── fin.sh
│ ├── 版本3
│ │ └── files
├── 應用B
│ └── 版本1
│ └── files
├──ezdpl.sh
└──auto.sh
3、流程
首先操作機需要能無密碼登入所有目標伺服器,指令 ' ssh-keygen -t rsa ; ssh-copy-id 使用者名稱@目標伺服器 ' 。
主指令碼有2/3是判斷參數和目標伺服器是否合法,真正的操作部分只有三處:
1) 如果有 pre.sh 指令碼,就上傳並在目標伺服器執行;
2) 複製 files 目錄下的所有檔案到目標伺服器的根目錄 /;
3) 如果有 fin.sh 指令碼,就上傳並在目標伺服器執行;
最後判斷是否要重啟目標伺服器。
為什麼要有兩個指令碼呢?很簡單,複製檔案之前可能需要對系統進行配置,有些系統配置需要特定檔案上傳之後。
具體怎麼用?看您心情。
4、用法
./ezdpl.sh <Silent Mode Y|N> <ip address>:[port] <app/version> [reboot Y|N(N)] [username(root)]
主指令碼共 5 個參數
<Silent Mode Y|N> Y - 靜默模式, N - 需要手工確認,只能是 Y 或 N 。
<ip address>:[port] 目標伺服器 IP地址:連接埠,如果目標伺服器sshd連接埠不是預設的 22,就需要指明。
<app/version> 工作目錄下的應用目錄和版本,比如 orders/20151012 。
[reboot Y|N(N)] 執行完 fin.sh 指令碼後是否重啟,一般新裝系統往往需要重啟。預設為 N,可省略。
[username(root)] 執行遠程登入的使用者名稱,預設是 root,可省略。
5、最佳實務
* 能用檔案解決的就盡量不用指令碼,除非你的sed、awk、Regex功底深厚並且願意承擔風險。檔案的好處是準確率高,便於事先審核、事後排錯。
* 在測試環境配置一套“乾淨”的系統,然後將配好的檔案複製到 ezdpl 工作目錄。記住檔案需要帶完整路徑,用' cp --parents '就可以了。
* 如果有軟串連則必須壓縮打包再傳輸,因為 scp 會將軟串連複製成目標檔案。
* 穩定版本保持不動,用' chattr +i ' 指令保護起來,或者打包備份,防止意外修改。微小變更也要建立新的版本並加以說明,以便跟蹤變更曆史,而且利於回退。
* 累積的小變更定期合并到新的穩定版。
cp --parents 樣本,我們要從伺服器 webserver1 上複製出 httpd 的設定檔:
[root@webserver1:/etc/httpd]# cp -r --parents ./conf /tmp/files
結果會得到:
/tmp/files/etc/httpd/conf/ (含conf內的檔案和目錄)
順便說一下,要讓 CentOS 在提示符顯示全路徑,在 /root/.bash_profile 末尾添上這一行:
PS1='[\u@\h:$PWD]# '
注意大小寫、單引號並且井號後面有個空格。
再執行
source /root/.bash_profile
如果要讓建立使用者也具備這個特性,在 /etc/skel/.bash_profile 末尾添上這一行:
PS1='[\u@\h:$PWD]$ '
注意要用美元符 。
二、範例
1、情境
為新裝作業系統準備的應用伺服器初始化環境,“應用/版本” 為 appserver/current 。
[root@operation:/opt/ezDpl]# ll
total 28
-rw-r--r-- 1 root root 304 Oct 24 18:49 auto.sh
-rw-r--r-- 1 root root 3383 Oct 24 18:57 ezdpl.sh
drwxr-xr-x 3 root root 4096 Oct 24 13:48 appserver
drwxr-xr-x 3 root root 4096 Oct 24 13:49 loadbalance
drwxr-xr-x 3 root root 4096 Oct 24 13:49 orders
drwxr-xr-x 3 root root 4096 Oct 24 13:49 stocks
drwxr-xr-x 3 root root 4096 Oct 24 13:49 crm
2、appserver目錄結構
appserver/
├── current
│ ├── files
│ │ │ ├── cron.daily
│ │ │ │ └── ntpsync
│ │ │ ├── profile
│ │ │ ├── profile.d
│ │ │ │ └── colorls.sh
│ │ │ ├── security
│ │ │ │ └── limits.conf
│ │ │ ├── skel
│ │ │ │ └── .bash_profile
│ │ │ ├── sysconfig
│ │ │ │ └── iptables
│ │ │ └── sysctl.conf
│ │ ├── opt
│ │ │ ├── jdk1.8.0_65
│ │ │ │ └── bin
│ │ │ ├── logs
│ │ │ ├── packages
│ │ │ └── tomcat8
│ │ │ ├── bin
│ │ │ ├── conf
│ │ │ ├── lib
│ │ │ ├── temp
│ │ │ ├── webapps
│ │ │ └── work
│ │ └── usr
│ │ └── local
│ │ └── bin
│ ├── fin.sh
│ └── pre.sh
└──20151012
└── fin.sh
上述目錄結構中,etc目錄下是已經改寫好的設定檔,分別為
etc/cron.daily/ntpsync ntpdate 0.pool.ntp.org 1.pool.ntp.org 自動擷取網路時間
etc/profile 設定java環境變數
etc/profile.d/colorls.sh 用 2015-10-12 09:00 的格式顯示檔案日期時間,alias ll='ls -l --color=auto --time-style=long-iso' 2>/dev/null
etc/security/limits.conf 修改ulimits值以適應高並發
etc/skel/.bash_profile 設定建立使用者的環境
etc/sysconfig/iptables 防火牆設定
etc/sysctl.conf 核心參數
opt裡面是基礎應用,比如 jdk,調整好的 tomcat 等,上傳到目標伺服器的 /opt 目錄。
usr/local/bin 裡面是平常使用的伺服器管理指令碼,上傳到目標伺服器的 /usr/local/bin 目錄。
3、pre.sh 指令碼
作用:為無法串連外網的伺服器配置內部 yum 源(源必須事先準備好,本例中源所在伺服器為10.6.1.200),安裝必要的包,備份原始設定檔。
#!/bin/bash
mkdir -p /etc/yum.repos.d/temp
mv /etc/yum.repos.d/* /etc/yum.repos.d/temp
local_repo="
[Local]
name=Local repo
baseurl=http://10.6.1.200/centos6
gpgcheck=0
enabled=1
"
echo -e "$local_repo" > /etc/yum.repos.d/local.repo
nginx_repo="
[nginx]
name=nginx repo
baseurl=http://10.6.1.200/nginx
gpgcheck=0
enabled=1
"
echo -e "$nginx_repo" > /etc/yum.repos.d/nginx.repo
yum -y install telnet man vim wget zip unzip ntpdate tree gcc iptraf tcpdump bind-utils
yum -y install nginx
echo
echo "Packages installed..."
echo
/bin/cp /etc/sysconfig/iptables /etc/sysconfig/iptables.`date +%Y%m%d`
/bin/cp /etc/security/limits.conf /etc/security/limits.conf.`date +%Y%m%d`
/bin/cp /etc/sysctl.conf /etc/sysctl.conf.`date +%Y%m%d`
#....(其餘檔案備份指令省略)
4、fin.sh 建立使用者,並成為應用程式目錄所有者
#!/bin/bash
useradd operuser && echo HisPassWord | passwd --stdin operuser
chown -R operuser:operuser /opt
5、auto.sh
將appserver的current版本依次部署到10.6.1.x等五台伺服器上。簡單吧?
#!/bin/bash
sh ezdpl.sh Y 10.6.1.11 appserver/current Y
sh ezdpl.sh Y 10.6.1.12 appserver/current Y
sh ezdpl.sh Y 10.6.1.13 appserver/current Y
sh ezdpl.sh Y 10.6.1.14 appserver/current Y
sh ezdpl.sh Y 10.6.1.15 appserver/current Y
為了達到並行的目的,auto.sh指令碼可以稍加修改,比如:
sh ezdpl.sh Y 10.6.1.11 appserver/current Y > /tmp/10.6.1.11.log &
sh ezdpl.sh Y 10.6.1.12 appserver/current Y > /tmp/10.6.1.12.log &
sh ezdpl.sh Y 10.6.1.13 appserver/current Y > /tmp/10.6.1.13.log &
...
6、變更
伺服器運行一段時間後需要統一修改 operuser 的密碼,並且將該使用者加入到 developing 組。
將指令寫入 fin.sh 指令碼(pre.sh 也行,因為此項變更不涉及到上傳檔案,所以沒有前後之分)。
#!/bin/bash
echo HisNewPassWord | passwd --stdin operuser
usermod -aG developing operuser
修改auto.sh指令碼並執行。
#!/bin/bash
sh ezdpl.sh Y 10.6.1.11 appserver/20151012 Y > /tmp/10.6.1.11.log &
sh ezdpl.sh Y 10.6.1.12 appserver/20151012 Y > /tmp/10.6.1.12.log &
sh ezdpl.sh Y 10.6.1.13 appserver/20151012 Y > /tmp/10.6.1.13.log &
sh ezdpl.sh Y 10.6.1.14 appserver/20151012 Y > /tmp/10.6.1.14.log &
sh ezdpl.sh Y 10.6.1.15 appserver/20151012 Y > /tmp/10.6.1.15.log &
也很簡單對吧?
7、回退
部署了應用orders的版本20151018,但是應用出現新的BUG,要回退到版本20151012。
部署:(部署時的 pre.sh 一般要包含刪除伺服器目前的版本的指令)
sh ezdpl.sh Y 10.6.1.11 orders/20151018 Y > /tmp/10.6.1.11.log &
sh ezdpl.sh Y 10.6.1.12 orders/20151018 Y > /tmp/10.6.1.12.log &
sh ezdpl.sh Y 10.6.1.13 orders/20151018 Y > /tmp/10.6.1.13.log &
...
回退:(相當於部署上一個版本,當然,pre.sh 指令碼會先刪除有BUG的 版本20151018。還是很簡單吧?)
sh ezdpl.sh Y 10.6.1.11 orders/20151012 Y > /tmp/10.6.1.11.log &
sh ezdpl.sh Y 10.6.1.12 orders/20151012 Y > /tmp/10.6.1.12.log &
sh ezdpl.sh Y 10.6.1.13 orders/20151012 Y > /tmp/10.6.1.13.log &
...
但是,依舊強烈建議在生產環境實施之前,要做充分的測試。
三、ezdpl.sh 版本1.1
#!/bin/bash
# https://github.com/Panblack/ezdpl
# Check Parameters
#echo $1
#echo $2
#echo $3
#echo
if [ -n "$1" ]; then
_silent=$1
if [ "$_silent" != "Y" ]; then
if [ "$_silent" != "N" ]; then
echo "The first parameter must be Y or N. Exit!"
exit 1
fi
fi
else
echo "silent. Usage: ./ezdpl.sh <Silent Mode Y|N> <ip address>:[port] <app/version> [reboot Y|N(N)] [username(root)]"
exit 1
fi
if [ -n "$2" ]; then
#Detailed param check will be needed.
_ipaddress=$(echo $2|awk -F':' '{print $1}')
_port=$(echo $2|awk -F':' '{print $2}')
if [ ${#_port} -eq 0 ]; then
_port="22"
fi
else
echo "ipaddress:port. Usage: ./ezdpl.sh <Silent Mode Y|N> <ip address>:[port] <app/version> [reboot Y|N(N)] [username(root)]"
exit 1
fi
if [ -n "$3" ]; then
_app_version=$3
else
echo "app/version. Usage: ./ezdpl.sh <Silent Mode Y|N> <ip address>:[port] <app/version> [reboot Y|N(N)] [username(root)]"
exit 1
fi
# Optional parameters
if [ -n "$4" ]; then
_reboot=$4
else
_reboot="N"
fi
if [ -n "$5" ]; then
_username=$5
else
_username="root"
fi
# Silent mode or not
if [ "$_silent" != "Y" ]; then
echo
echo "Ezdpl does things in a raw and simple way."
echo "https://github.com/Panblack/ezdpl"
echo
echo "Will initialize a new server, or deploy apps to a certain server, or upgrade a production server."
echo "Usage: ./ezdpl.sh <Silent Mode Y|N> <ip address>:[port] <app/version> [reboot Y|N(N)] [username(root)]"
echo "Manually Initialize 10.1.1.1: ./ezdpl.sh N 10.1.1.1 common/current Y"
echo "Silently Deploy app_a to 10.1.1.1: ./ezdpl.sh Y 10.1.1.1:22 app_a/current Y root"
echo "Silently Upgrade 10.1.1.2's app_a: ./ezdpl.sh Y 10.1.1.2:2222 app_a/20150720"
echo "Manually Upgrade 10.1.1.2's conf: ./ezdpl.sh N 10.1.1.2:2222 app_a/2015-10-12"
echo
# Confirmation
read -p "Will overwrite configuration files or apps on $_ipaddress. Enter Y to continue: "
if [ "$REPLY" != "Y" ]; then
echo "Exit"
exit 0
fi
# Confirmation again
read -p "Are you sure? Enter Y to continue: "
if [ "$REPLY" != "Y" ]; then
echo "Exit"
exit 0
fi
fi
# Check
echo "Target Server: ${_ipaddress}..."
ssh -p $_port $_username@$_ipaddress uname > /dev/null
if [ "$?" != "0" ]; then
echo
echo "$_ipaddress is not reachable. "
exit 1
fi
if [ ! -d "./$_app_version" ]; then
echo
echo "There is no $_app_version configured here !"
exit 1
fi
# Everything seems OK. Go!
# Run pre.sh on the target server
if [ -f "./$_app_version/pre.sh" ]; then
scp -P $_port ./$_app_version/pre.sh $_username@$_ipaddress:/tmp/
ssh -p $_port $_username@$_ipaddress sh /tmp/pre.sh
echo "$_username@$_ipaddress:/tmp/pre.sh executed."
fi
# Start copy app/version/files/*
if [ -d ./$_app_version/files ]; then
scp -P $_port -r ./$_app_version/files/* $_username@$_ipaddress:/
echo "./$_app_version/files/* copied."
fi
# Run fin.sh on the target server
if [ -f "./$_app_version/fin.sh" ]; then
scp -P $_port ./$_app_version/fin.sh $_username@$_ipaddress:/tmp/
ssh -p $_port $_username@$_ipaddress sh /tmp/fin.sh
echo "$_username@$_ipaddress:/tmp/fin.sh executed."
fi
# Reboot target server.
if [ "$_reboot" = "Y" ]; then
echo
echo "Target server will reboot..."
echo
ssh -p $_port $_username@$_ipaddress reboot
fi
echo "Target Server: ${_ipaddress} done!"; echo
# End of ezdpl.sh