天天看點

開發Jenkins Pipeline并結合Ansible,進行自動化批量釋出

公司的CI/CD是使用Jenkins,開發、測試、預發的CI和CD都是在一起的,而生産環境的CI/CD我們是分開的

CI任務結束之後,開發可以選擇釋出哪個release版本。

可以看一下整體預覽情況:

開發Jenkins Pipeline并結合Ansible,進行自動化批量釋出

每個Job的Pipeline狀态:

開發Jenkins Pipeline并結合Ansible,進行自動化批量釋出

自定義釋出機器、同時有釋出及復原功能:

開發Jenkins Pipeline并結合Ansible,進行自動化批量釋出

我們都是基于maven的Java應用,進行編譯打包其實比較簡單,這裡的CI較為簡單,我這裡隻簡單說明及其需要注意的點

maven編譯打包時,可以去掉單元測試

若Jenkins的機器配置比較高,可以可以開啟maven的并發編譯(3.3版本以上,預設支援并發)

适當調整maven的JVM

下面着重介紹CD部分的配置

以: 我們的msg-server服務為例

添加參數化建構

a.添加PROJECT(字元參數):辨別:應用名稱

開發Jenkins Pipeline并結合Ansible,進行自動化批量釋出

      b.添加DEPLOY_TYPE(Active Choices Parameter ):辨別:釋出還是復原

開發Jenkins Pipeline并結合Ansible,進行自動化批量釋出

      c.添加Version( Active Choices Reactive Parameter的):辨別釋出的版本

開發Jenkins Pipeline并結合Ansible,進行自動化批量釋出

    d.添加 HOTS( Active Choices Reactive Parameter),表示釋出的主機清單

開發Jenkins Pipeline并結合Ansible,進行自動化批量釋出

 e.添加 DEPLOYED_HOSTS(Active Choices Reactive Parameter ),辨別:已經釋出過的主機清單

開發Jenkins Pipeline并結合Ansible,進行自動化批量釋出

2.基礎共用腳本部分

cat get_versions.sh

#!/bin/bash

#prod

packageDir="/data/jenkins/repos/master/"

#test

#packageDir="/data/jenkins/repos/test"

#dev

#packageDir="/data/jenkins/repos/dev"

project=$1

deploy_type=$2

if [[ "${deploy_type}" == "Rollback" ]] ; then 

  lasted_version=`ls -lt ${packageDir}/${project}|grep ${project}|awk '{print $9}'|awk -F '.' '{print $1}'`

  echo ${lasted_version}|awk -F " " '{for(i=1;i<=NF;i++) a[i,NR]=$i}END{for(i=1;i<=NF;i++) {for(j=1;j<=NR;j++) printf a[i,j] " ";print ""}}'

else

fi

cat get_deploy_lists.sh

PACKAGE_DIR=/data/jenkins/repos/release_package

version=$3

db_url='ops-db.xxxxxx.com'

db_user='xxxxx'

db_pwd="xxxxxxxx"

db_port=3306

format_version=`echo ${version}|awk -F '##' '{print $1}'`

#查詢已經釋出或復原過的主機清單

query_sql="select host_ip from deploy_history where status=1 and deploy_type='${deploy_type}'  and version='${format_version}' and hostname like '%${project}%' "

result=`mysql -u${db_user} -p${db_pwd} -h${db_url} -P${db_port} -B jenkins  -e "${query_sql}" |awk 'NR>1'`

echo ${result}|awk -F " " '{for(i=1;i<=NF;i++) a[i,NR]=$i}END{for(i=1;i<=NF;i++) {for(j=1;j<=NR;j++) printf a[i,j] " ";print ""}}'

cat get_undeploy_lists.sh

db_url='ops-db.xxxxxxx.com'

#查詢待釋出的主機

#if [ "${deploy_type}" ==  "Deploy"  ] ; then

#query_sql="select host_ip from hosts where host_ip not in (select host_ip from deploy_history where status=1 and deploy_type='${deploy_type}'  and version='${format_version}' and hostname like '%${project}%' )  "

#result=`mysql -u${db_user} -p${db_pwd} -h${db_url} -P${db_port} -B jenkins  -e "${query_sql}" |awk 'NR>1'`

#echo ${result}|awk -F " " '{for(i=1;i<=NF;i++) a[i,NR]=$i}END{for(i=1;i<=NF;i++) {for(j=1;j<=NR;j++) printf a[i,j] " ";print ""}}'

#fi 

#if [ "${deploy_type}" ==  "Rollback"  ] ; then       

#if rollback ,

#        query_sql="select host_ip from hosts where host_ip not in (select host_ip from deploy_history where status=1 and deploy_type='Rollback'  and version='${format_version}' and hostname like '%${project}%' )  "

#        result=`mysql -u${db_user} -p${db_pwd} -h${db_url} -P${db_port} -B jenkins  -e "${query_sql}" |awk 'NR>1'`

#        echo ${result}|awk -F " " '{for(i=1;i<=NF;i++) a[i,NR]=$i}END{for(i=1;i<=NF;i++) {for(j=1;j<=NR;j++) printf a[i,j] " ";print ""}}'

#fi

        query_sql="select host_ip from hosts where host_ip not in (select host_ip from deploy_history where status=1 and deploy_type='${deploy_type}'  and version='${format_version}' and hostname like '%${project}%' ) and hostname like '%${project}%' "

        result=`mysql -u${db_user} -p${db_pwd} -h${db_url} -P${db_port} -B jenkins  -e "${query_sql}" |awk 'NR>1'`

        echo ${result}|awk -F " " '{for(i=1;i<=NF;i++) a[i,NR]=$i}END{for(i=1;i<=NF;i++) {for(j=1;j<=NR;j++) printf a[i,j] " ";print ""}}'

cat deploy_success_update.sh

host_ip=$2

deploy_type=$3

version=$4

db_url='ops-db.xxxxxxxx.com'

db_pwd="xxxxxx"

insert_sql="insert into  deploy_history(hostname,host_ip,deploy_type,status,version) values(\"${project}\",\"${host_ip}\",\"${deploy_type}\",1,\"${version}\" )"

echo ${insert_sql}

count_result=`mysql -u${db_user} -p${db_pwd} -h${db_url} -P${db_port} -B jenkins  -e "${insert_sql}" `

3.建立資料庫及定義表結構

/*

Navicat MySQL Data Transfer

Source Server         : ops-db

Source Server Version : 50728

Source Host           : ops-db. xxxxx.com:3306

Source Database       : jenkins

Target Server Type    : MYSQL

Target Server Version : 50728

File Encoding         : 65001

Date: 2020-08-13 14:25:52

*/

Create Database: CREATE DATABASE `jenkins` /*!40100 DEFAULT CHARACTER SET utf8mb4 */

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------

-- Table structure for deploy_history

CREATE TABLE `deploy_history` (

  `id` bigint(20) NOT NULL AUTO_INCREMENT,

  `hostname` varchar(255) NOT NULL COMMENT '釋出的主機名',

  `host_ip` varchar(255) NOT NULL COMMENT '釋出主機IP位址',

  `deploy_type` varchar(255) NOT NULL,

  `status` tinyint(1) NOT NULL COMMENT '0 釋出失敗; 1釋出成功',

  `version` varchar(255) NOT NULL,

  `ctm_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

  PRIMARY KEY (`id`),

  KEY `udx_deploy_type_version` (`deploy_type`,`version`) USING BTREE,

  KEY `idx_host_ip` (`host_ip`) USING BTREE

) ENGINE=InnoDB AUTO_INCREMENT=128 DEFAULT CHARSET=utf8mb4;

-- Table structure for hosts

CREATE TABLE `hosts` (

  `id` int(11) NOT NULL AUTO_INCREMENT,

  `hostname` varchar(255) NOT NULL,

  `host_ip` varchar(255) NOT NULL,

  `comment` varchar(255) DEFAULT NULL,

) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8mb4;

-- Table structure for hosts-test

CREATE TABLE `hosts-test` (

#可以檢視以往的曆史釋出記錄:

開發Jenkins Pipeline并結合Ansible,進行自動化批量釋出

  4.Pipelin流水線代碼

開發Jenkins Pipeline并結合Ansible,進行自動化批量釋出

#Groovy代碼如下:

/**

 * Description:Master環境JavaSpringBoot的Pipeline腳本

 * Author: ledi

 * Date:2020-03-01

 */

pipeline {

    agent any

    environment {

        packageDir="/data/jenkins/repos/master"

        backupDir="/data/release_package"

        deployDir="/data/www"

        ansible_Dir="/etc/ansible/roles/masterv2"

        port="20881"

    }

    stages {

        stage("推包至遠端"){

            steps{

                script{

    if ( "${DEPLOY_TYPE}" == 'Deploy' ){

                    sh '''

HOSTS=`echo ${HOSTS}|sed s/[[:space:]]//g`

                    source /etc/profile &> /dev/null

                    #/bin/mv  ${WORKSPACE}/${PROJECT}/target/${PROJECT}.jar ${packageDir}/${PROJECT}_${BUILD_NUMBER}.jar

                    old_version=`echo "${VERSION}"`

                    new_version=`echo "${VERSION}"|awk -F '##' '{print $1}'`

                    ansible-playbook -i ${ansible_Dir}/hosts ${ansible_Dir}/copy_jar.yaml -e "backupDir=${backupDir} packageDir=${packageDir} old_version=${old_version}  new_version=${new_version} project=${PROJECT}" --limit ${HOSTS}

                   '''

   }else{

echo "執行復原操作,無需推包至遠端"

}

                } // end script

            } // end steps

        } //end stage

        stage("停應用") {

            steps {

                script {

                    ansible-playbook -i ${ansible_Dir}/hosts ${ansible_Dir}/stop_app.yaml -e "project=${PROJECT} " --limit ${HOSTS}

                    '''

                }

            }

        }

        stage("啟應用"){

new_version=`echo "${VERSION}"|awk -F '##' '{print $1}'`

                    ansible-playbook -i ${ansible_Dir}/hosts ${ansible_Dir}/start_app.yaml -e "project=${PROJECT} new_version=${new_version} " --limit ${HOSTS}

        stage("健康檢查"){

                    hosts_ip=`echo ${HOSTS} |sed  "s/,/  /g" `

                    function check_health(){

                    for (( i=1;i<="$#";i++ )); do

                     ansible ${!i} -u www -i ${ansible_Dir}/hosts  -m shell -a "tail -1000 /data/logs/${PROJECT}/nohup.out"

                      for (( j=1;j<=60;j++ ))

                        do

                             echo "ip is ${!i}"

                             if  [[ `(echo "status -l ";sleep 1;exit)|telnet ${!i} ${port} |grep "server"| grep -o "OK"` == "OK" ]]; then

                                    echo  " ^_^^_^ IP ${!i} ${port} 端口檢查成功 ^_^^_^"

sh /data/jenkins/scripts/deploy_success_update.sh ${PROJECT} ${!i}  ${DEPLOY_TYPE} ${new_version} 

                                    break 1;

                             else

                                    echo "==== IP ${!i} ${port} 端口異常,繼續探測 ==== "

                                    sleep 3

                             fi

                        done

                       if [[ `(echo "status -l ";sleep 1;exit)|telnet ${!i} ${port} |grep "server"| grep -o "OK"` != "OK" ]] ; then

                          echo  "==== IP ${!i} ${port} 端口異常,啟動失敗,請檢查應用 === "

                          exit 1

                        fi

                    done

                    }

                    check_health ${hosts_ip}

                     '''

                }// end script

            } // end stage heal

    } //end stages

說明:

這裡也可以将此Groovy代碼和應用放在一起,放入GitLab中,這樣更容易維護及管理。

5.Ansible 公用部分

cat copy_jar.yaml 

---

- hosts: all

  gather_facts: False

  remote_user: root

  vars:

    backupDir: {backupDir}

    packageDir: {packageDir}

    project: {PROJECT}

    old_version: {old_version}

    new_version: {new_version}

  tasks:

   - name: Copy Jar to Remoute Machine

     copy: src={{ packageDir }}/{{project }}/{{ old_version }}.jar    dest={{ backupDir }}/{{ new_version }}.jar   owner=www group=www mode=0664 force=yes

cat stop_app.yaml

    - name: Stop Java App

      shell: sh /home/www/scripts/stop_springboot.sh {{ project }} owner=www group=www

cat start_app.yaml 

  remote_user: www

    - name: Start Java App

      shell: /bin/bash  /home/www/scripts/start_springboot.sh  {{ project }} {{ new_version }} owner=www group=www

最後,開發們可以自定義釋出主機,假設一個微服務有20台機器,可以先選擇5台進行釋出,通過後,再選擇5台或以上,然後慢慢類似滾動釋出。

存在不足的點:

若一個微服務有20台機器,若采用每5台釋出,則開發需要手工進行4次操作,改進的地方:若第一次5台成功後,則自動執行後面的操作

在使用Ansible推包到阿裡雲ECS時,很慢;這個是我們的網絡機房問題,因為打包機器在公司内部,和阿裡雲ECS通過虛拟隧道過去的,是以比較慢,改進的地方:

 一是将打包機器也遷移到雲端;二是通過專線将公司網絡與阿裡雲互通,形成實體線路;不過上面兩個都是需要¥的哈,運維同學不單單考慮系統可用性、穩定性,也要為公司節約一定成本哈。

繼續閱讀