zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    jQuery选择器总结
    HTML页面跳转的5种方法
    与孩子共情就是这么简单!
    C#中使用OracleTransaction
    如何查看Mysql服务器上的版本
    使用C#通过Oracle.DataAccess连接Oracle,部署时需要注意版本问题
    使用docker搭建hadoop环境,并配置伪分布式模式
    Hadoop安装教程【转】
    PHP+Hadoop+Hive+Thrift+Mysql实现数据统计分析
    《阿里巴巴 Java 开发手册》划重点!
  • 原文地址:https://www.cnblogs.com/panblack/p/Easy_deployment_cn.html
Copyright © 2011-2022 走看看