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建立
檢視是否安裝成功