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

[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()


在客户端删除相应的服务,并通过salt-api创建


查看是否安装成功

猜你喜欢

转载自blog.csdn.net/qq_41636653/article/details/83010625