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
[root@server1 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下建文件
[root@server1 salt]# salt -G 'os:RedHat' test.ping ##尝试链接
[root@server1 salt]# salt -G 'os:RedHat' cmd.run hostname
##run 一个长命令要用单引号引起来
[root@server1 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()