ezdpl:完全依赖脚本和ssh的自动化部署方案

ezdpl是easy deployment的简写,使用简单的ssh和shell脚本来部署、升级、回滚和重新配置linux服务器。

重要提示:
警告:这个项目还处于测试过程中,请仔细阅读说明,并且自己承担可能带来的风险。
最佳实践:根据自己的生产环境修改脚本,部署之前需要充分测试。

最新版本请关注我的github https://github.com/Panblack/ezdpl


为什么要写ezdpl?
现在很流行使用puppet之类的工具进行自动化的系统配置。puppet方便、高效而且可以在实际配置之前“预演”,日常工作可以简化为编写puppet脚本,剩下的让puppet自己去忙活就行了,可以轻松管理成百台的服务器。

不过有人就是对这个东西不感冒,也许没有那么多服务器,也许学习另一套“系统”来管理手头的系统是个负担。我就是这种情况,而且,我喜欢用“原始、简单”的方法,不要什么代理、插件、模块、剧本什么的。我必须始终知道服务器具体是怎么配置的,配置文件是咋写的。如果习惯了puppet之类的工具,哪天碰上没有这些工具的环境怎么干活呢?这可不是件令人欣慰的事情。

但是,自动化管理确实很必要。不用puppet之类的工具怎么管理一大堆服务器呢?对了,用shell脚本。只需要一台操作机(或者叫跳板机),保存着初始化或者升级系统用的脚本、配置文件和应用,所有文件都是“原始”形态,甚至目录结构都跟生产服务器一样。操作机具有对目标服务器的root免密码登录权限以便自动运行,所有工作仅仅需要一个脚本。

“等一等,哥们,ansible就是这么做的。你这不是重复造轮子吗?不明智!”你笑了吧?

没错,我是在重复造轮子,不过是个更简单的轮子,只为了好玩。而且呢,我不需要担心忘掉指令啊脚本啊这些“终极武器”,这很让人欣慰啊,呵


用原始、简单的方式工作
ezdpl非常非常简单,仅仅用到如下技术:
* 精心组织的目录和文件(最关键的其实在这儿)
* scp(比如 'scp -r 目录 root@目标服务器:/ ')
* ssh(比如 'ssh root@目标服务器 指令 ')

基本的目录结构,有三个级别
级别0:ezdpl文件
级别1:应用名
级别2:版本
对某个应用服务器的任何变更或更新,都在“版本”级别建立一个新的目录来实现。
如果需要回滚,只需要在脚本参数里指定上一版本即可。

开始的设想比现在复杂得多,目录层次也很乱。现在的方案实实在在的说明了简单就是美。


场景:
* 所有服务器(操作机,目标服务器)都安装了 Centos6 x86_64
* 目标服务器仅配置了IP地址和主机名
* 操作机具有对所有目标服务器的root免密码登录权限,如果没有,则脚本运行时需要输入密码
* ezdpl部署在操作机 /home/ezdpl目录
* 操作机的ssh密钥最好有passphrase密码保护
* 所有目标服务器的应用都部署在/opt

置备应用目录
应用需要的目录和文件可以从头手工建立,或者从当前的生产服务器复制。以下指令会很有帮助:

[root@java_c-server /] mkdir -p /tmp/java_c
[root@java_c-server /] /bin/cp -r --parents /etc/logrotate.d/java_c /tmp/java_c
[root@java_c-server /] /bin/cp -r --parents /home/operuser/bin /tmp/java_c
[root@java_c-server /] /bin/cp -r --parents /opt/java_c /tmp/java_c
[root@java_c-server /] find /opt/logs/ -type d -exec mkdir -p /tmp/java_c/{} \;
[root@java_c-server /] scp -r /tmp/java_c/* root@operation-server:/home/ezdpl/apps/java_c/current/

说明:
ezdpl需要按照文件原始的目录结构保存每个文件,这样才能保证复制到目标服务器正确的路径下。cp -r --parent 可以带着父目录一起拷贝文件,正好满足这个需求。
有些生产环境需要准备一些空的目录结构,而生产服务器的目录里已经有了文件。上述的find 指令就可以从生产服务器里仅复制目录结构,忽略文件。

目录结构范例
级别 0,1

目录               描述
--------------    ------------------------------------------------
ezdpl        
├── apps          [级别 0]
│   ├── common    [级别 1, common并不是真正的应用,只是所有服务器都需要的脚本和配置文件]
│   ├── web_a     [级别 1, tomcat webapp a, 需要部署一台或多台]
│   ├── web_b     [级别 1, tomcat webapp b, 需要部署一台或多台]
│   └── java_c    [级别 1, java app c, 需要部署三台,每台需要配置多个IP地址]
├── ezdpl.sh      [级别 0, 主脚本]
├── ezdpl_auto.sh [级别 0, 主脚本, 静默模式]
└── README        [级别 0, 不用解释吧? ;)]


级别 2

目录                                描述
-------------------------------    ------------------------------------------------
common/                    
├── 20150720                       [级别2, 版本20150720(暂时空)]
└── current                        [级别2, 当前版本]
    ├── etc                
    │   ├── cron.daily
    │   │   └── ntp_client.sh      [ntp 时间同步脚本]
    │   └── sysconfig
    │       ├── iptables
    │       └── static-routes
    ├── runme.sh                   [初始化脚本]
    └── tmp                        [需要独立安装的软件包]
        └── jdk-7u75-linux-x64.rpm

web_a/
├── 20150406                       [级别2, 版本20150406]
│   └── opt
│       └── tomcat-web_a
│           └── webapps            [tomcat webapps]
└── current                        [级别2, current version]
    ├── etc
    │   └── logrotate.d
    │       └── web_a
    ├── opt
    │   ├── logs
    │   │   └── web_a              [web_a 的日志目录(在tomcat-web_a/conf/logging.properties里设置]
    │   └── tomcat-web_a
    │       ├── bin
    │       ├── conf
    │       ├── lib
    │       ├── LICENSE
    │       ├── NOTICE
    │       ├── RELEASE-NOTES
    │       ├── RUNNING.txt
    │       ├── temp
    │       ├── webapps
    │       └── work
    └── root
        └── bin                  [web_a的管理脚本]
            ├── showlog
            ├── shutdown_web_a
            └── start_web_a

web_b/
(ommited)

java_c/
├── current
│   ├── etc
│   │   └── logrotate.d
│   │       └── java_c
│   ├── home
│   │   └── operuser            [java_c应用需要以普通用户执行]
│   │       └── bin             [java_c的管理脚本]
│   │           ├── showlog
│   │           ├── shutdown_java_c
│   │           └── start_java_c
│   ├── opt
│   │   ├── logs
│   │   │   └── java_c
│   │   └── java_c
│   │       ├── conf
│   │       ├── lib
│   │       ├── output
│   │       └── java_c.jar
│   └── runme.sh
├── java_c1
│   ├── etc
│   │   └── sysconfig
│   │       └── network-scripts   [java_c 第一台服务器的ip配置文件若干]
│   └── runme.sh
├── java_c2
│   ├── etc
│   │   └── sysconfig
│   │       └── network-scripts
│   └── runme.sh
└── java_c3
    ├── etc
    │   └── sysconfig
    │       └── network-scripts
    └── runme.sh

 


主脚本 ezdpl.sh 只需要做以下工作:
* 复制指定目录下的所有文件到目标服务器,比如 ./apps/app_name/version
* 远程执行初始化脚本,比如 ./apps/app_name/version/runme.sh
* 可以指定目标服务器的用户名,默认是root
* 可根据需要远程重启目标服务器,默认不重启

ezdpl_auto.sh 跟ezdpl.sh 几乎一样,只是去掉了交互确认部分,适合批量部署。

主脚本:
ezdpl/ezdpl.sh

#!/bin/bash
echo
echo "ezdpl does things in a raw and simple way."
echo "https://github.com/Panblack/ezdpl"
echo
echo "Will initialize a new target server."
echo "Or deploy an app to the target server."
echo "Or upgrade a running production server."
echo "Usage: ./ezdpl.sh <ip address> <app/version> [reboot Y/N(N)] [username(root)]"
echo "Init 10.1.1.1:         ./ezdpl.sh 10.1.1.1 common/current"
echo "Deploy web_a to 10.1.1.1: ./ezdpl.sh 10.1.1.1 web_a/current Y root"
echo "Upgrade 10.1.1.2's app:    ./ezdpl.sh 10.1.1.2 java_c/20150720 N"
echo "Upgrade 10.1.1.2's conf:    ./ezdpl.sh 10.1.1.2 java_c/java_c2 N"
echo

# Confirmation
read -p "Will overwrite configuration files or app on $1. Enter Y to continue: "
if [ "$REPLY" != "Y" ]; then
  echo "Exit"
  exit 0
fi
read -p "Are you sure? Enter Y to continue: "
if [ "$REPLY" != "Y" ]; then
  echo "Exit"
  exit 0
fi

# variables
_ipaddress=$1
_app_version=$2
if [ -n "$3" ]; then
  _reboot=$3
fi
if [ -n "$4" ]; then
  _username=$4
else
  _username="root"
fi

# Check
if [ ! -d "./apps/$_app_version" ]; then
  echo
  echo "There is no $_app_version configured here !"
  exit 1
fi

chkaccess=`ssh $_username@$_ipaddress ls -d /opt`
if [ ! -n "$chkaccess" ]; then
  echo
  echo "$_ipaddress is not reachable. "
  exit 1
fi


# Start copy app/version
scp -r ./apps/$_app_version/* $_username@$_ipaddress:/
echo "./apps/$_app_version/* copied."

# Run runme.sh on the target server
if [ -f "./apps/$_app_version/runme.sh" ]; then
  ssh $_username@$_ipaddress sh /runme.sh
  echo "$_username@$_ipaddress:/runme.sh executed."
  #ssh $_username@$_ipaddress /bin/rm /runme.sh
  #echo "$_username@$_ipaddress:/runme.sh deleted."
fi

# Reboot target server.
if [ "$_reboot" = "Y" ]; then
  echo
  echo "Target server will reboot..."
  echo
  ssh $_username@$_ipaddress reboot
fi


runme.sh 范例
ezdpl/apps/common/current/runme.sh

#!/bin/bash
#make it your script
#set -e

# /etc/profile
/bin/cp /etc/profile /etc/profile.bak
# Turn off mail check
chkmailcheck=$(cat /etc/profile |grep "unset MAILCHECK"|grep -v "#")
if [ ! -n "$chkmailcheck" ]; then
 echo "unset MAILCHECK" >> /etc/profile
fi
# make vim default
chkvim=$(cat /etc/profile |grep "alias vi='vim'"|grep -v "#")
if [ ! -n "$chkvim" ]; then
 echo "alias vi='vim'" >> /etc/profile
fi
# set LANG
chklang=$(cat /etc/profile |grep "export LANG=en_US.UTF-8"|grep -v "#")
if [ ! -n "$chklang" ]; then
  echo "export LANG=en_US.UTF-8" >> /etc/profile
fi
echo
echo "/etc/profile modified."
echo

# ll with long-iso date format
/bin/cp /etc/profile.d/colorls.sh /etc/profile.d/colorls.sh.bak
chkll=$(cat /etc/profile.d/colorls.sh |grep "alias ll='ls -l --color=auto --time-style=long-iso'"|grep -v "#")
if [ ! -n "$chkll" ]; then
  echo "alias ll='ls -l --color=auto --time-style=long-iso' 2>/dev/null" >> /etc/profile.d/colorls.sh
fi
echo
echo "/etc/profile.d/colorls.sh modified."
echo

# Selinux
sed 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config -i
echo
echo "/etc/selinux/config modified."
echo

# disable cron mail
sed 's/MAILTO=root/MAILTO=""/g' /etc/crontab -i
echo
echo "/etc/crontab modified."
echo

# install/reinstall jdk
for x in $(rpm -qa|egrep "jdk|jre"); do
 rpm -e --nodeps $x
done
rpm -ivh  /tmp/jdk-7u75-linux-x64.rpm
echo
echo "jdk installed/reinstalled."
echo

# install necessary packages:
yum clean all
yum install zip unzip man vim tree ntpdate sysstat wget gcc tcpdump telnet bind-utils -y
echo
echo "necessary packages installed. "
echo

# Finish
source /etc/profile
setenforce 0
chkconfig ip6tables off
chkconfig crond on
chkconfig iptables on
/etc/init.d/crond restart
/etc/init.d/iptables restart
/etc/init.d/network restart

echo
echo "services restarted."
echo

 

posted @ 2015-07-21 10:45  Panblack  阅读(2106)  评论(0编辑  收藏  举报