1. 程式人生 > >Saltstack自動化運維部署

Saltstack自動化運維部署

saltstack是基於python開發的一套C/S架構配置管理工具,底層使用ZeroMQ訊息佇列進行通訊,使用SSL證書籤發的方式進行認證管理,ZeroMQ使saltstack能快速在成千上萬臺機器上進行各種操作,它是一款訊息佇列軟體saltstack通過訊息佇列來管理成天上萬臺主機客戶端,傳輸指令相關操作,而且採用RSA key方式進行身份確認,傳輸採用AES方式進行加密,以保證它的安全性。
saltstack主要包含三個部分分別是python軟體集,saltstack軟體集,ZeroMQ訊息佇列軟體
saltstack軟體是一個C/S架構的軟體,通過管理端下發指令,客戶端接受指令的方式進行操作,管理端成為master,客戶端成為minion,saltstack客戶端minion在啟動時,會自動生成一套金鑰包含公鑰和私鑰。之後將公鑰發給伺服器端,伺服器端驗證並接收公鑰,以此建立可靠且加密的通訊連結,同時通過ZeroMQ訊息佇列在master和minion之間建立系統通訊橋樑,Master上執行某條指令通過訊息佇列下發到各個Minions去執行,並返回結果其中Daemon運行於每一個成員內的守護程序,承擔著釋出訊息及埠監聽的功能對應的埠分別為4505和4506。

命令的釋出過程:
1.Saltstack的Master與Minion之間通過ZeroMQ進行訊息傳遞,使用ZeroMQ的釋出訂閱模式,連線方式包括TCP和IPC。
2.在Master和Minion建立互信之後,salt命令,將cmd.run ls命令從salt.client.LocalClient.cmd_cli釋出到Master,獲取一個Jobid,很劇Jobid獲取命令執行結果。
3.Master接收到命令後,將要執行的命令傳送給客戶端minion。
4.Minion從訊息總線上接收到要處理的命令,交給minion._handle_aes處理。
5.Minion._handle_aes發起一個本地執行緒呼叫cmdmod執行ls命令,執行緒執行完ls後,呼叫Minion._return_pub方法,將執行結果通過訊息匯流排返回給master。
6.Master接收到客戶端返回的結果,呼叫master._handle_aes方法將結果寫入檔案。
7.Salt.client.LocalClient.cmd_cli通過輪詢獲取Job執行結果,將結果輸出到終端。

服務的安裝及基本配置


server1   Master  IP:172.25.62.1
server2   Minion   IP:172.25.62.2
server3   Minion   IP:172.25.62.3
注意防火牆關閉 selinux狀態disabled 三臺主機之間相互有解析 因為yum源中沒有saltstack相關的rpm包所以我們使用自己的yum源
本地yum源配置

在server1上安裝master,server2和server3上安裝MInion


server1和server2和server3上都安裝tree和lsof    做實驗用

將從節點指向主節點  在server2和server3上


啟動slave服務  啟動服務後才會生成祕鑰有興趣的可以試一下

master端檢視並啟動服務   因為這裡我已經允許過了 所以有所不同

檢視master端的公鑰和master發給minion的公鑰


檢視minion端傳送給master端的公鑰

由此可知,其驗證是雙向驗證,即master端將其公鑰傳送到minion端,minion也將自己的公鑰傳送給master端

其中405負責傳送資料到客戶端,4506負責接收客戶端的資料到伺服器。

基礎應用
部署遠端httpd服務,在master上定義路徑,並重啟服務



瞭解YAML:預設的sls檔案的renderer 是YAML renderer ,YAML是一個有很多強大特性的標記性語言,salt 使用了一個YAML的小型子集,對映非常常規的資料結構,向列表和字典,YAML renderer 的工作是將YAML資料格式的結構編譯成python資料結構給salt使用
規則一 :
縮排: YAML 使用一個固定的縮排風格表示資料層結構關係,salt需要每一個縮排級別都有兩個空格組成,不要使用tab
規則二:
冒號:python 的字典當然是簡單的鍵值對
規則三:
短橫槓:想要表示列表項,使用一個短橫槓加一個空格,多項使用同樣的縮排級別作為同一列表的一部分。

安裝httpd


其中apache-install只是一個名字pkg.installed中pkg是資料包處理的類,是一個大的方式,installed 表示其是對資料包進行安裝處理-pkgs 用於指定安裝多個數據包
執行,安裝httpd


server2上檢視有沒有安裝httpd和php

在master上設定httpd的部署和啟動


其中service也是一個大的類,而running是其中的方法其中有reload、restart、enable等

在master上執行完畢之後 到server2上看httpd服務有沒有啟動


配置httpd配置檔案並推送

將server2上的httpd的配置檔案傳送給server1  兩個檔案的md5碼相同有興趣的可以檢視一下



其中使用到了file.managed模組
--name:表示目標目錄,及客戶端對應的目錄
--source:表示配置檔案的來源路徑,其是相對於/srv/salt的路徑

正常推送一次

在server1上修改htttpd的預設埠為8080再推送一次




到server2上檢視埠是否改變


原始碼推送nginx
在server1上獲取需要的nginx安裝包,解決依賴問題

獲取安裝包,放到指定的目錄下


推送nginx服務


同樣的這裡只是進行了服務的安裝,nginx服務並沒有啟動
將server3上nginx的配置檔案發給server1


對於server3來說沒有nginx使用者  在採用yum安裝的時候會幫你建立nginx使用者




include:
  - nginx.install
  - user.adduser

/usr/local/nginx/conf/nginx.conf:
  file.managed:
    - source: salt://nginx/files/nginx.conf

/usr/local/nginx/html/index.html:
  file.managed:
    - source: salt://nginx/files/index.html

nginx-service:
  file.managed:
    - name: /etc/init.d/nginx
    - source: salt://nginx/files/nginx
    - mode: 755

  service.running:
    - name: nginx
    - enable: true
    - reload: true
    - require:
      - user: nginx
    - watch:
      - file: /usr/local/nginx/conf/nginx.conf

nginx的啟動指令碼和推送的預設頁面,將準備好的nginx啟動指令碼放到指定的目錄中

#!/bin/sh
#
# nginx        Startup script for nginx
#
# chkconfig: - 85 15
# processname: nginx
# config: /usr/local/nginx/conf/nginx/nginx.conf
# pidfile: /usr/local/nginx/logs/nginx.pid
# description: nginx is an HTTP and reverse proxy server
#
### BEGIN INIT INFO
# Provides: nginx
# Required-Start: $local_fs $remote_fs $network
# Required-Stop: $local_fs $remote_fs $network
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: start and stop nginx
### END INIT INFO

# Source function library.
. /etc/rc.d/init.d/functions

if [ -L $0 ]; then
    initscript=`/bin/readlink -f $0`
else
    initscript=$0
fi

#sysconfig=`/bin/basename $initscript`

#if [ -f /etc/sysconfig/$sysconfig ]; then
#    . /etc/sysconfig/$sysconfig
#fi

nginx=${NGINX-/usr/local/nginx/sbin/nginx}
prog=`/bin/basename $nginx`
conffile=${CONFFILE-/usr/local/nginx/conf/nginx.conf}
lockfile=${LOCKFILE-/var/lock/subsys/nginx}
pidfile=${PIDFILE-/usr/local/nginx/logs/nginx.pid}
SLEEPMSEC=${SLEEPMSEC-200000}
UPGRADEWAITLOOPS=${UPGRADEWAITLOOPS-5}
RETVAL=0

start() {
    echo -n $"Starting $prog: "

    daemon --pidfile=${pidfile} ${nginx} -c ${conffile}
    RETVAL=$?
    echo
    [ $RETVAL = 0 ] && touch ${lockfile}
    return $RETVAL
}

stop() {
    echo -n $"Stopping $prog: "
    killproc -p ${pidfile} ${prog}
    RETVAL=$?
    echo
    [ $RETVAL = 0 ] && rm -f ${lockfile} ${pidfile}
}

reload() {
    echo -n $"Reloading $prog: "
    killproc -p ${pidfile} ${prog} -HUP
    RETVAL=$?
    echo
}

upgrade() {
    oldbinpidfile=${pidfile}.oldbin

    configtest -q || return
    echo -n $"Starting new master $prog: "
    killproc -p ${pidfile} ${prog} -USR2
    echo

    for i in `/usr/bin/seq $UPGRADEWAITLOOPS`; do
        /bin/usleep $SLEEPMSEC
        if [ -f ${oldbinpidfile} -a -f ${pidfile} ]; then
            echo -n $"Graceful shutdown of old $prog: "
            killproc -p ${oldbinpidfile} ${prog} -QUIT
            RETVAL=$?
            echo
            return
        fi
    done

    echo $"Upgrade failed!"
    RETVAL=1
}

configtest() {
    if [ "$#" -ne 0 ] ; then
        case "$1" in
            -q)
                FLAG=$1
                ;;
            *)
                ;;
        esac
        shift
    fi
    ${nginx} -t -c ${conffile} $FLAG
    RETVAL=$?
    return $RETVAL
}

rh_status() {
    status -p ${pidfile} ${nginx}
}

# See how we were called.
case "$1" in
    start)
        rh_status >/dev/null 2>&1 && exit 0
        start
        ;;
    stop)
        stop
        ;;
    status)
        rh_status
        RETVAL=$?
        ;;
    restart)
        configtest -q || exit $RETVAL
        stop
        start
        ;;
    upgrade)
        rh_status >/dev/null 2>&1 || exit 0
        upgrade
        ;;
    condrestart|try-restart)
        if rh_status >/dev/null 2>&1; then
            stop
            start
        fi
        ;;
    force-reload|reload)
        reload
        ;;
    configtest)
        configtest
        ;;
    *)
        echo $"Usage: $prog {start|stop|restart|condrestart|try-restart|force-reload|upgrade|reload|status|help|configtest}"
        RETVAL=2
esac

exit $RETVAL


在釋出頁面中寫nginx.page

[[email protected] files]# echo nginx.page > index.html

向server3推送nginx


在server3上檢視nginx服務相關



推送多個主機不同的服務


推送   高階推




推送叢集Nginx+httpd+haproxy
用server1部署haproxy,安裝salt-minion

將server1上的minion指向master


啟動服務並允許註冊

安裝haproxy編寫配置檔案


編寫配置檔案,包括服務的重啟動,自啟動等

建立準用的資料夾存放haproxy的配置檔案 並進行修改


推送多個服務





寫一個nginx和httpd預設釋出頁面


測試一下  因為之前改過apache的埠 訪問的時候加上更改的埠號



salt的相關命令    1.查詢server2的ip  2.查詢server2的uuid  3.檢視server2的系統型別


Grians匹配

salt -G 'os:RedHat' cmd.run 'touch /mnt/han'    ##在作業系統。。Mnt下建檔案
[[email protected] salt]# salt -G 'os:RedHat' test.ping  ##嘗試連結
[[email protected] salt]# salt -G 'os:RedHat' cmd.run hostname  
##run 一個長命令要用單引號引起來
[[email protected] salt]# salt -G 'os:RedHat' cmd.run 'ip addr show eth0'  
##在作業系統為redhat的主機上顯示eth0資訊





設定標籤對於server2上的httpd服務


重啟服務

對於server3上的nginx也需要進行更改




效果

也可以新增使用者 方便管理,檢視




每臺機器有了不同的標示之後,便於找出他們的不同點進行不同的部署

高階推送走一波



Grains適用於靜態  pillar適用於動態

重新整理動態引數

在server2上


在server3上


測試在server1上


自定義模組 之後進行同步



到server3上檢視

執行同步後的模組
在server1上執行


jinjia模板




測試  先remove##server2上已經安裝好的apache

在server1上推送


在server2上進行埠檢視


全域性引用
在server1上   原始檔埠號是8080不動它

測試  直接推送出去


可以看到全域性變數生效拉 在server2上檢視埠號 變成了80


變數的定義靜態grain
在server1上



將之前的80改成8080

推送一波


埠程式設計拉8080 再到server2檢視


原始碼包推送keepalived
在server1上建立對應的資料夾存放安裝包

編寫配置檔案

推送keepalived


成功後到server2上獲取配置檔案和指令碼

到server1上繼續配置install.sls


更改keepalived的配置檔案


靜態引數設定


啟動檔案的設定


批量推送檔案


批量推送


檢測  失敗的原因為master vip在server2上  keepalived都已經安裝




salt syndic:salt proxy 安裝與配置
sakt-master: master  salt-minion: server2 server3
在server1上   關掉salt-minion 關閉自啟動 刪除註冊

在master上安裝salt-syndic


配置master指向topmaster


新開server4作為topmaster  注意server4的yum  同時路徑存在






啟動服務 允許server1連線

測試一下   游標卡一會屬於正常 稍等一下就好了


salt-ssh序列
server1安裝salt-ssh


測試連線


api服務


修改master的配置檔案使其支援api


建立使用者並修改密碼

編寫認證檔案

填寫資料 生成祕鑰

啟動服務 檢視8000埠


測試minion端的連通性

API連線測試,並獲取token

進行獲取客戶端列表

配置saltapi.py用於生成相關服務的指令碼

指令碼需要修改的地方



這裡ip為server1的IP 密碼是剛設定的redhat

完整的指令碼

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import urllib2,urllib
import time

try:
    import json
except ImportError:
    import simplejson as json
SERVER=raw_input("請輸入要安裝軟體所在的主機名:")
PATH=raw_input("請輸入其呼叫指令碼位置(/srv/salt)為相對位置:")
class SaltAPI(object):
    __token_id = ''
    def __init__(self,url,username,password):
        self.__url = url.rstrip('/')
        self.__user = username
        self.__password = password

    def token_id(self):
        ''' user login and get token id '''
        params = {'eauth': 'pam', 'username': self.__user, 'password': self.__password}
        encode = urllib.urlencode(params)
        obj = urllib.unquote(encode)
        content = self.postRequest(obj,prefix='/login')
	try:
            self.__token_id = content['return'][0]['token']
        except KeyError:
            raise KeyError

    def postRequest(self,obj,prefix='/'):
        url = self.__url + prefix
        headers = {'X-Auth-Token'   : self.__token_id}
        req = urllib2.Request(url, obj, headers)
        opener = urllib2.urlopen(req)
        content = json.loads(opener.read())
        return content

    def list_all_key(self):
        params = {'client': 'wheel', 'fun': 'key.list_all'}
        obj = urllib.urlencode(params)
        self.token_id()
        content = self.postRequest(obj)
        minions = content['return'][0]['data']['return']['minions']
        minions_pre = content['return'][0]['data']['return']['minions_pre']
        return minions,minions_pre

    def delete_key(self,node_name):
        params = {'client': 'wheel', 'fun': 'key.delete', 'match': node_name}
        obj = urllib.urlencode(params)
        self.token_id()
        content = self.postRequest(obj)
        ret = content['return'][0]['data']['success']
        return ret

    def accept_key(self,node_name):
        params = {'client': 'wheel', 'fun': 'key.accept', 'match': node_name}
        obj = urllib.urlencode(params)
        self.token_id()
        content = self.postRequest(obj)
        ret = content['return'][0]['data']['success']
        return ret

    def remote_noarg_execution(self,tgt,fun):
        ''' Execute commands without parameters '''
        params = {'client': 'local', 'tgt': tgt, 'fun': fun}
        obj = urllib.urlencode(params)
        self.token_id()
        content = self.postRequest(obj)
        ret = content['return'][0][tgt]
        return ret

    def remote_execution(self,tgt,fun,arg):
        ''' Command execution with parameters '''        
        params = {'client': 'local', 'tgt': tgt, 'fun': fun, 'arg': arg}
        obj = urllib.urlencode(params)
        self.token_id()
        content = self.postRequest(obj)
        ret = content['return'][0][tgt]
        return ret

    def target_remote_execution(self,tgt,fun,arg):
        ''' Use targeting for remote execution '''
        params = {'client': 'local', 'tgt': tgt, 'fun': fun, 'arg': arg, 'expr_form': 'nodegroup'}
        obj = urllib.urlencode(params)
        self.token_id()
        content = self.postRequest(obj)
        jid = content['return'][0]['jid']
        return jid

    def deploy(self,tgt,arg):
        ''' Module deployment '''
        params = {'client': 'local', 'tgt': tgt, 'fun': 'state.sls', 'arg': arg}
        obj = urllib.urlencode(params)
        self.token_id()
        content = self.postRequest(obj)
        return content

    def async_deploy(self,tgt,arg):
        ''' Asynchronously send a command to connected minions '''
        params = {'client': 'local_async', 'tgt': tgt, 'fun': 'state.sls', 'arg': arg}
        obj = urllib.urlencode(params)
        self.token_id()
        content = self.postRequest(obj)
        jid = content['return'][0]['jid']
        return jid

    def target_deploy(self,tgt,arg):
        ''' Based on the node group forms deployment '''
        params = {'client': 'local_async', 'tgt': tgt, 'fun': 'state.sls', 'arg': arg, 'expr_form': 'nodegroup'}
        obj = urllib.urlencode(params)
        self.token_id()
        content = self.postRequest(obj)
        jid = content['return'][0]['jid']
        return jid

def main():
    sapi = SaltAPI(url='https://172.25.62.1:8000',username='saltapi',password='redhat')
    sapi.token_id()
    print sapi.list_all_key()
    #sapi.delete_key('test-01')
    #sapi.accept_key('test-01')
    sapi.deploy(SERVER,PATH)
    #print sapi.remote_noarg_execution('test-01','grains.items')

if __name__ == '__main__':
    main()


在客戶端刪除相應的服務,並通過salt-api建立


檢視是否安裝成功