saltstack编译安装与小马运维开发


http://potaski.blog.51cto.com/3099084/1317853

本系列文章由初学者写,高手就不要浪费时间了

 

环境:centos6.2 minimum(最小化安装)

特点:无外网

 

测试机没有外网,就自己手动源码安装把,的确是要累一些...做个笔记记录一下

官方文档说:安装需求

python >=2.6 <3.0

zeromq >=2.1.9

pyzmp >=2.1.9

pycrypto

msgpack-python

yaml

jinja2

 

 

===== 安装流水帐如下了 =====

先安装了salt-0.17,然后一发不可收拾...

 

启动master报错 - ImportError: No module named yaml

解决

PyYAML-3.10.tar.gz

https://pypi.python.org/pypi/PyYAML/3.10#downloads

 

启动master报错 - ImportError: No module named jinja2

解决

Jinja2-2.7.1.tar.gz

https://pypi.python.org/pypi/Jinja2/2.7.1

安装jinja2报错 - error: Could not find suitable distribution for Requirement.parse('markupsafe')

解决

MarkupSafe-0.18.tar.gz

https://pypi.python.org/simple/MarkupSafe/

 

启动master报错 - ImportError: No module named zmq

解决

pyzmq-13.1.0.tar.gz

https://pypi.python.org/pypi/pyzmq/13.1.0#downloads

安装pyzmq报错,其中有一段 - Failed with default libzmq, trying again with /usr/local

解决

libzmq-master.zip

https://github.com/zeromq/libzmq

 

启动master报错 - ImportError: No module named M2Crypto

M2Crypto-0.21.1.tar.gz

https://pypi.python.org/pypi/M2Crypto/0.21.1

安装m2crypto报错 - /usr/include/openssl/opensslconf.h:31: Error: CPP #error ""This openssl-devel package does not work your architecture?"". Use the -cpperraswarn option to continue swig processing.

error: command 'swig' failed with exit status 1

解决

msgpack-python-0.3.0.tar.gz

https://pypi.python.org/pypi/msgpack-python/0.3.0

 

然后发现master和minion在运行一段时间后会报以下错误 - NameError: global name 'AES' is not defined

只好在源码中搜索了

[root@DFS-1 salt]# grep -R AES /usr/lib/python2.6/site-packages/salt/

/usr/lib/python2.6/site-packages/salt/crypt.py: from Crypto.Cipher import AES

然后google了一下Crypto.Cipher,解决

解决:

https://pypi.python.org/pypi/pycrypto/2.6

========================

 

整理一下安装过程:

unzip libzmq-master.zip

cd libzmq-master

./autogen.sh

cd ..

tar zxf PyYAML-3.10.tar.gz

cd PyYAML-3.10

/usr/bin/python setup.py install > install.log

cd ..

tar zxf MarkupSafe-0.18.tar.gz

cd MarkupSafe-0.18

/usr/bin/python setup.py install > install.log

cd ..

tar zxf Jinja2-2.7.1.tar.gz

cd Jinja2-2.7.1

/usr/bin/python setup.py install > install.log

cd ..

tar zxf pyzmq-13.1.0.tar.gz

cd pyzmq-13.1.0

/usr/bin/python setup.py install > install.log

cd ..

tar zxf M2Crypto-0.21.1.tar.gz

cd M2Crypto-0.21.1

sed -i 's/python/\/usr\/bin\/python/g' fedora_setup.sh

# 注:我的默认python版本为2.5,此举为切换运行环境到centos6.2自带的python2.6上

./fedora_setup.sh install

cd ..

tar zxf msgpack-python-0.3.0.tar.gz

cd msgpack-python-0.3.0

/usr/bin/python setup.py install > install.log

cd ..

tar zxf pycrypto-2.6.tar.gz

cd pycrypto-2.6

/usr/bin/python setup.py install > install.log

cd ..

tar zxf salt-0.17.0.tar.gz

cd salt-0.17.0

/usr/bin/python setup.py install > install.log

cd ..

 

注:安装完后salt在启动后仍然发现一个缺包的warning

[WARNING ] virtualenv not installed, please install first

virtualenv是啥呢,搜索得

https://pypi.python.org/pypi/virtualenv/

不影响使用,慢慢再深入学习

 

salt运行

源码安装后,还需要手动拷贝配置文件模板到/etc/salt/内

cp salt-0.17.0/conf/master /etc/salt/

cp salt-0.17.0/conf/minion /etc/salt/

salt的master与minion安装方式相同,启动不同的服务即可。正确安装完毕后可以看到一批salt命令

[root@DFS-1 ~]# salt

salt salt-call salt-cp salt-key salt-master salt-minion salt-run salt-ssh salt-syndic

启动master:会启动4505、4506端口

[root@DFS-1 ~]# salt-master

启动minion:不启动任何端口

[root@DFS-1 ~]# salt-minion

 

salt配置

基本上按配置文件模板启动就可以使用,测试中进行了一些自定义

[root@DFS-1 salt]# diff master master.bak |grep "<"

< default_include: /data1/salt/master.d/*.conf # 我在这里单独进行file_root、pillar_root、nodegroup的配置

< publish_port: 4505

< user: root

< max_open_files: 100000

< worker_threads: 1

< ret_port: 4506

< pidfile: /tmp/salt-master.pid

< root_dir: /data1/salt

< pki_dir: /data1/salt/pki/master

< cachedir: /data1/salt/cache/master

< auto_accept: True # 自动认证,免除一些手续

minion的配置没有做任何改动(除了minion id)

 

本文出自 “我是闪电~我是闪电~” 博客,请务必保留此出处http://potaski.blog.51cto.com/3099084/1317853

 

 

 

http://blog.csdn.net/app_ops/article/details/17147083

SaltStack的非标准安装

先解释下题目吧,什么是"SaltStack的非标准安装"。

简单先介绍下环境,笔者所运维的环境基本上以CentOS为主,有少部分Ubuntu。CentOS系统的版本也从5.4到6.4都有包括,进尔导致salt所依赖的python语言环境的版本也从2.4.3到2.6.6都有。因为是线上的系统,系统版本的升级或者python版本的升级,都有可能带来一定的风险性。所以从本意上来说,这次做的salt安装,最好不要对线上环境有任何的影响。(做过运维的都知道,最好不要对稳定的环境做任何的变化。)

 

好了,基本的思路定了,再看下新版salt的安装需求:

  • Python 2.6 >= 2.6 <3.0

  • ZeroMQ >= 2.1.9

  • pyzmq >= 2.1.9 

  • PyCrypto 

  • msgpack-python 

  • YAML

  • Jinja2 

这个列表,给了我们提供了一个好消息,一个坏消息。

先说坏消息:因为我们的python版本不统一,有部分系统的python版本不在支持的范围内;如果强行安装,可能会导致稳定性的问题。

再说好消息:既然有了安装需求,我们可以自定义安装,这样可以做到完全不影响系统环境。

 

既然已经决定了我们要走"自定义安装"的道路,那么我们就"兵马未行,粮草先动"。(本文就当个教程写了,之前的探索过程就不写了)

  1. 准备所需要的程序包。

    Jinja2-2.7.1.tar.gz

    M2Crypto-0.21.1.tar.gz

    MarkupSafe-0.18.tar.gz

    PyYAML-3.10.tar.gz

    Python-2.7.5.tgz

    msgpack-python-0.1.12.tar.gz

    pycrypto-2.6.1.tar.gz

    pyzmq-13.1.0.zip

    salt-0.17.1.tar.gz

    setuptools-1.1.6.tar.gz

    yaml-0.1.4.tar.gz

    zeromq-4.0.1.tar.gz

  2. 安装前的准备。

    安装目录:/usr/local/SaltStack (我们的目标很明确)

  3. 开始安装,下面是我写的一个很简单的脚本。

    master和minion的安装部署是一样的,区别只是使用的配置文件不同。

    M2Crypto的安装,请根据系统的具体环境参考安装说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#!/bin/bash                                                                                                                                      
#
# Author : john.lee.cn.do@gmail.com
# Date    : 2013.10.25
# Desc    : Install Salt
# Usage  : bash install.sh
#
    
export  LANG=C
_IP=` ifconfig  | grep  'inet addr' | grep  - v  '127.0.0.1' | sed  's/[ ]*/ /' | awk  '{print $2}' | awk  -F:  '{print $NF}' | head  -n 1`
_MASTER=172.17.37.207
    
_PATH= /usr/local/src/SaltStack
# Install Python
cd  $_PATH
tar  zxvf Python-2.7.5.tgz
cd  Python-2.7.5
. /configure  --prefix= /usr/local/SaltStack/python-2 .7.5
make  &&  make  install
    
    
export  _PYTHON= /usr/local/SaltStack/python-2 .7.5 /bin/python
    
# Install setuptools
cd  $_PATH
tar  zxvf setuptools-1.1.6. tar .gz
cd  setuptools-1.1.6
$_PYTHON setup.py  install
    
# Install MarkupSafe
cd  $_PATH
tar  zxvf MarkupSafe-0.18. tar .gz
cd  MarkupSafe-0.18
$_PYTHON setup.py  install
    
# Install jinja2
cd  $_PATH
tar  zxvf Jinja2-2.7.1. tar .gz
cd  Jinja2-2.7.1
$_PYTHON setup.py  install
    
    
# Install  msgpack-python
cd  $_PATH
tar  zxvf msgpack-python-0.1.12. tar .gz
cd  msgpack-python-0.1.12
$_PYTHON setup.py  install
    
# Install  pycrypto
cd  $_PATH
tar  zxvf pycrypto-2.6.1. tar .gz
cd  pycrypto-2.6.1
$_PYTHON setup.py  install
    
# Install PyYAML
cd  $_PATH
tar  zxvf PyYAML-3.10. tar .gz
cd  PyYAML-3.10
$_PYTHON setup.py  install
    
# Install zeromq
cd  $_PATH
tar  zxvf zeromq-4.0.1. tar .gz
cd  zeromq-4.0.1
. /configure  --prefix= /usr/local/SaltStack/zeromq-4 .0.1
make  &&  make  install
    
    
# Install pyzmq
cd  $_PATH
unzip pyzmq-13.1.0.zip
cd  pyzmq-13.1.0
$_PYTHON setup.py  install  --zmq= /usr/local/SaltStack/zeromq-4 .0.1
    
# Install  M2Crypto
# Need yum install swig
cd  $_PATH
tar  zxvf M2Crypto-0.21.1. tar .gz
cd  M2Crypto-0.21.1
# Modify python path in fedora_setup.sh
bash  fedora_setup.sh   install
    
    
# Install Salt
cd  $_PATH
tar  zxvf salt-0.17.1. tar .gz
cd  salt-0.17.1
$_PYTHON setup.py  install


4.  因为master和minion的区别,只是配置文件的不同,在请参考官方文档(salt的配置文件模板在salt-0.17.1/conf)

5.  启动脚本。

    既然我们把路径都已经修改了,那么我们的启动脚本也要修改一下了。

    salt-minion(此例中,将配置文件也放在了/etc/salt中,如果进行了修改,可以修改脚本即可。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#!/bin/sh
#                                                                                                                                                 
# Salt minion
###################################
    
# LSB header
    
### BEGIN INIT INFO
# Provides:           salt-minion
# Required-Start:     $local_fs $remote_fs $network $named $time
# Should-Start:         $time ypbind smtp
# Required-Stop:        $local_fs $remote_fs $network $named $time
# Should-Stop:          ypbind smtp
# Default-Start:      3 5
# Default-Stop:       0 1 2 6
# Short-Description:  Salt minion daemon
# Description:        This is the Salt minion daemon that can be controlled by the Salt master.
### END INIT INFO
    
    
# chkconfig header
    
# chkconfig: 345 97 04
# description:  This is the Salt minion daemon that can be controlled by the Salt master.
#
# processname: /usr/bin/salt-minion
    
    
SALTMINION= /usr/local/SaltStack/python-2 .7.5 /bin/salt-minion
PYTHON= /usr/local/SaltStack/python-2 .7.5 /bin
    
# Sanity checks.
[ -x $SALTMINION ] ||  exit  0
    
# Source function library.
/etc/rc .d /init .d /functions
    
SERVICE=salt-minion
PROCESS=salt-minion
CONFIG_ARGS= " -c /etc/salt"
RETVAL=0
    
start() {
         if  [ $(pidofproc $PROCESS) ];  then
               RETVAL=$?
               echo  -n  "already running"
         else
               echo  -n $ "Starting salt-minion daemon: "
               daemon --check $SERVICE $SALTMINION -d $CONFIG_ARGS
               RETVAL=$?
         fi
     echo
     return  $RETVAL
}
    
stop() {
     echo  -n $ "Stopping salt-minion daemon: "
     killproc $PROCESS
     RETVAL=$?
     echo
}
    
restart() {
    stop
    start
}
    
# See how we were called.
case  "$1"  in
     start|stop|restart)
         $1
         ;;
     status)
         if  [ -f $SUSE_RELEASE ];  then
             echo  -n  "Checking for service salt-minion "
             checkproc $SALTMINION
             rc_status - v
         elif  [ -f $DEBIAN_VERSION ];  then
             if  [ -f $LOCKFILE ];  then                                                                                                             
                 RETVAL=0
                 echo  "salt-minion is running."
             else
                 RETVAL=1
                 echo  "salt-minion is stopped."
             fi
         else
             status $PROCESS
             RETVAL=$?
         fi
         ;;
     condrestart)
         [ -f $LOCKFILE ] && restart || :
         ;;
     reload)
         echo  "can't reload configuration, you have to restart it"
         if  [ -f $SUSE_RELEASE ];  then
             rc_status - v
         else
             RETVAL=$?
         fi
         ;;
     *)
         echo  $ "Usage: $0 {start|stop|status|restart|condrestart|reload}"
         exit  1
         ;;
esac
exit  $RETVAL


因为master就只有一台机器,所以就没有写脚本,手工操作也没太多的工作量,而且会更加的灵活。

其它说明:

  1. 我们是把python环境单独安装在了/usr/local/SaltStack/python-2.7.5中,所以每次手工执行命令时,需要使用绝对路径。

  2. 目前上前做的环境中,只有一台master机器,存在单点危险,而且没有做负载均衡。目前还没有想到很好的解决方案。

     

 

欢迎大家一起讨论。

 

 

 

[python]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
 
    1. <pre code_snippet_id="98726" snippet_file_name="blog_20131205_4_6128993"></pre>  
    2. <pre></pre>  

 

 

 

 

http://www.iyunv.com/thread-40232-1-1.html

二、Master批量管理配置

服务器端写sls模,他的默认路径在 /srv/salt/下,没有就新建目录,然后新建一个top.sls就跟puppet的入口文件 site.pp 类似。

<ignore_js_op> 

·        Base: 默认的的起点配置项:

·        '*':这个引号内的是匹配对象,针对所有受控主机

·        rd.sc:就是指资源文件/srv/salt/rd/sc.sls

<ignore_js_op>

 

看rd目录下的资源文件

<ignore_js_op>

 

利用py模式的sls配置文件(其实就是python脚本,只要返回yaml格式的字典文件就好了),我们可以将以上的操作简化成1步,思路如下:
1,/srv/pillar/top.sls中编写配置:

base: '*': - custom

2,使用py模式编写/srv/pillar/custom/init.sls,自动读取pillar配置,例如salt id是:10.1.1.1-centos.game.web,那么project为game,然后根据获取的pillar_root组合成路径/srv/pillar/custom/game/10.1.1.1-centos.game.web.yaml,利用yaml模块从文件中读取信息,返回字典
3,在/srv/salt/top.sls文件中匹配所有的minion

‘*’:  - centos.public_services

4,/srv/salt/centos/public_services/init.sls文件使用py模式编写,配置会获取对应的minion的pillar信息,如果包含mysql配置信息且配置正确的话,则返回mysql实例的配置。

那现在要怎么使用呢,很简单,例如你的id为10.1.1.1-centos.game.web,首先在/srv/pillar/custom/目录下建个game目录(从salt id获取的项目名),然后在game目录先新建文件10.1.1.1-centos.game.web.yaml,里面写上配置信息:

mysql:  ports:    - 3306    - 3307    - 3308  version:  '5_5_25' 
最后执行命令:salt 10.1.1.1-centos.game.web state.highstate -v -t 300

静静的等待执行完成就好了!

=======================

http://blog.liuts.com/post/240/

  Saltstack是一个具备puppet与func功能为一身的集中化管理平台,saltstack基于python实现,功能十分强大,各模块融合度及复用性极高,官方极力推荐作为云计算平台的基础架构。轻松维护成千上万台服务器不是问题,现分享作者基于saltstack实现一个集中化的配置管理平台,以Nginx配置例子展开,涉及salt的grains、grains_module、pillar、States、jinja(template)等,本文适合有salt基础的同学阅读。
一、设备环境说明
    有两组web业务服务器,组名分别为web1group与web2group,设备硬件配置、web根目录存在异常,见下图:
     点击在新窗口中浏览此图片

二、master配置说明
    1、关键配置定义:
  1. nodegroups:  
  2.    web1group: 'L@SN2012-07-010,SN2012-07-011,SN2012-07-012'  
  3.    web2group: 'L@SN2013-08-021,SN2013-08-022'  
  4.   
  5. file_roots:  
  6.   base:  
  7.     - /srv/salt  
  8.   
  9. pillar_roots:  
  10.   base:  
  11.     - /srv/pillar  

    2、定义的文件树结构(具体文件后续说明)
点击在新窗口中浏览此图片

三、自定义grains_module
1)#vi /srv/salt/_grains/nginx_config.py
  1. import os,sys,commands  
  2.   
  3. def NginxGrains():  
  4.     ''' 
  5.         return Nginx config grains value 
  6.     '''  
  7.     grains = {}  
  8.     max_open_file=65536  
  9.     #Worker_info={'cpus2':'01 10','cpus4':'1000 0100 0010 0001','cpus8':'10000000 01000000 00100000 00010000 00001000 00000100 00000010 00000001'}  
  10.     try:  
  11.         getulimit=commands.getstatusoutput('source /etc/profile;ulimit -n')  
  12.     except Exception,e:  
  13.         pass  
  14.     if getulimit[0]==0:  
  15.         max_open_file=int(getulimit[1])  
  16.     grains['max_open_file'] = max_open_file  
  17.     return grains  

2)同步grains模块
salt '*' saltutil.sync_all

3)刷新模块(让minion编译模块)
salt '*' sys.reload_modules

4)验证max_open_file key的value
[root@SN2013-08-020 _grains]# salt '*' grains.item max_open_file              
SN2013-08-022:
  max_open_file: 1024
SN2013-08-021:
  max_open_file: 1024
SN2012-07-011:
  max_open_file: 1024
SN2012-07-012:
  max_open_file: 1024
SN2012-07-010:
  max_open_file: 1024

四、配置pillar
    本例使用分组规则定义pillar,即不同分组引用各自的sls属性
1)定义入口top.sls
#vi /srv/pillar/top.sls
  1. base:  
  2.   web1group:  
  3.     - match: nodegroup  
  4.     - web1server  
  5.   web2group:  
  6.     - match: nodegroup  
  7.     - web2server  

2)定义私有配置,本例只配置web_root的数据,当然可以根据不同需求进行定制,格式为python的字典形式,即"key:value"。
#vi /srv/pillar/web1server.sls 
  1. nginx:  
  2.     root: /www  

#vi /srv/pillar/web2server.sls 
  1. nginx:  
  2.     root: /data  

3)验证配置结果:
#salt 'SN2013-08-021' pillar.data nginx
SN2013-08-021:
    ----------
    root:
        /data

#salt 'SN2012-07-010' pillar.data nginx 
SN2012-07-010:
    ----------
    root:
        /www

五、配置States
1)定义入口top.sls
#vi /srv/salt/top.sls
  1. base:  
  2.   '*':  
  3.     - nginx   

2)定义nginx配置及重启服务SLS,其中salt://nginx/nginx.conf为配置模板文件位置。
#vi /srv/salt/nginx.sls
  1. nginx:  
  2.   pkg:  
  3.    - installed  
  4.   file.managed:  
  5.    - source: salt://nginx/nginx.conf  
  6.    - name: /etc/nginx/nginx.conf  
  7.    - user: root  
  8.    - group: root  
  9.    - mode: 644  
  10.    - template: jinja  
  11.   
  12.   service.running:  
  13.    - enable: True  
  14.    - reload: True  
  15.    - watch:  
  16.      - file: /etc/nginx/nginx.conf  
  17.      - pkg: nginx  

3)Nginx配置文件(引用jinja模板)
功能点:
1、worker_processes参数采用grains['num_cpus'] 上报值(与设备CPU核数一致);
2、worker_cpu_affinity分配多核CPU根据当前设备核数进行匹配,分别为2\4\8\其它核;
3、worker_rlimit_nofile参数与grains['max_open_file'] 获取的系统ulimit -n一致;
4、worker_connections 参数理论上为grains['max_open_file'];
5、 root参数为定制的pillar['nginx']['root']值。
#vi /srv/salt/nginx/nginx.conf
  1. # For more information on configuration, see:  
  2. user              nginx;  
  3. worker_processes  {{ grains['num_cpus'] }};  
  4. {% if grains['num_cpus'] == 2 %}  
  5. worker_cpu_affinity 01 10;  
  6. {% elif grains['num_cpus'] == 4 %}  
  7. worker_cpu_affinity 1000 0100 0010 0001;  
  8. {% elif grains['num_cpus'] >= 8 %}  
  9. worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000;  
  10. {% else %}  
  11. worker_cpu_affinity 1000 0100 0010 0001;  
  12. {% endif %}  
  13. worker_rlimit_nofile {{ grains['max_open_file'] }};  
  14.   
  15. error_log  /var/log/nginx/error.log;  
  16. #error_log  /var/log/nginx/error.log  notice;  
  17. #error_log  /var/log/nginx/error.log  info;  
  18.   
  19. pid        /var/run/nginx.pid;  
  20.   
  21. events {  
  22.     worker_connections  {{ grains['max_open_file'] }};  
  23. }  
  24.   
  25.   
  26. http {  
  27.     include       /etc/nginx/mime.types;  
  28.     default_type  application/octet-stream;  
  29.   
  30.     log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '  
  31.                       '$status $body_bytes_sent "$http_referer" '  
  32.                       '"$http_user_agent" "$http_x_forwarded_for"';  
  33.   
  34.     access_log  /var/log/nginx/access.log  main;  
  35.   
  36.     sendfile        on;  
  37.     #tcp_nopush     on;  
  38.   
  39.     #keepalive_timeout  0;  
  40.     keepalive_timeout  65;  
  41.   
  42.     #gzip  on;  
  43.       
  44.     # Load config files from the /etc/nginx/conf.d directory  
  45.     # The default server is in conf.d/default.conf  
  46.     #include /etc/nginx/conf.d/*.conf;  
  47.     server {  
  48.         listen       80 default_server;  
  49.         server_name  _;  
  50.   
  51.         #charset koi8-r;  
  52.   
  53.         #access_log  logs/host.access.log  main;  
  54.   
  55.         location / {  
  56.             root   {{ pillar['nginx']['root'] }};  
  57.             index  index.html index.htm;  
  58.         }  
  59.   
  60.         error_page  404              /404.html;  
  61.         location = /404.html {  
  62.             root   /usr/share/nginx/html;  
  63.         }  
  64.   
  65.         # redirect server error pages to the static page /50x.html  
  66.         #  
  67.         error_page   500 502 503 504  /50x.html;  
  68.         location = /50x.html {  
  69.             root   /usr/share/nginx/html;  
  70.         }  
  71.   
  72.     }  
  73.   
  74. }  

4)同步配置
#salt '*' state.highstate
点击在新窗口中浏览此图片
(由于非第一次运行,看不到配置文件比对的信息)

5)验证结果:
1、登录root@SN2013-08-021
#vi /etc/nginx/nginx.conf
点击在新窗口中浏览此图片


点击在新窗口中浏览此图片

2、登录root@SN2012-07-010
#vi /etc/nginx/nginx.conf
点击在新窗口中浏览此图片


点击在新窗口中浏览此图片
 
Tags:  saltstack ,  salt ,  运维 ,  自动化
 
 
hanxiao  Email  Homepage
2015/03/17 17:31
我知道是什么问题了,是因为minion主机没有设置id号,
hanxiao  Email  Homepage
2015/03/17 16:16
其实我知道上午的主要问题是什么了,主要是 nodegroup match不成功,但我不知道为什么不成功,master配置文件里已经添加了node group信息
hanxiao  Email  Homepage
2015/03/17 11:07
感觉 YAML格式要求很严格,行首空格、制表符多一个少一个都不行。我在在配置 pillar 时遇到了问题1、设置了nodegroups :web1group、web2groupsalt -N web1group test.pingvm0:    Truevm1:    True2、然后按书上的写了 top.sls、web1server.sls、web2server.sls刷新 salt '*' saltutil.refresh_pillar
hanxiao  Email  Homepage
2015/03/17 10:57
感觉YAML的格式要求很严格,行首多一个空格或制表符都不行我在配置pillar时,就有困惑,我使用salt -N web1group test.ping   #正常salt -N web1group test.ping vm1:    Truevm0:    True写了  /srv/pillar/top.sls、web1server.sls、web2server.sls,然后刷新 salt '*' saltutil.refresh_pillar但在master上执行 salt 'vm0' pillar.data nginxvm0:    ----------后来我直接复制了你的sls文件,也不行,这是怎么回事呢,前面的 max_open_file倒测试正常。我是个新手,向你学习
甄码农  Email  Homepage
2014/11/06 10:03
请教个问题,在master上_grains目录下的python文件同步到minion服务器上的说呢么位置了?grain调试有什么好的方法? 谢谢
网游加速器  Homepage
2014/08/17 16:55
感觉管理好麻烦的啊
变压器  Homepage
2014/07/27 20:50
路过,偷偷带走,谢谢分享
园林假山  Email  Homepage
2014/07/24 08:44
不错,学习学习,欢迎回访
胶水  Email  Homepage
2014/07/21 16:10
我是来打酱油的,欢迎回访我的博客。
ran
2014/02/21 14:31
saltstack针对编译安装的服务怎么维护呢?怎么进行服务的 - enable: True    - reload: True这两个功能?
张伟  Email
2013/10/23 15:52
哈哈,我被主机名勾引出来了
刘天斯 回复于 2013/10/28 16:18
汗颜~
一路狂飙  Email
2013/10/14 09:18
生成了如何分发呢?
刘天斯 回复于 2013/10/14 18:09
运行#salt '*' state.highstate 就是包含校验、分发,具体项看SLS配置。
藤家具  Homepage
2013/09/24 17:25
确实很有压力的呀
北南
2013/09/05 16:46
谢谢分享!回去捣鼓下。
chenqing  Email  Homepage
2013/09/04 12:59
不错,楼主依然强悍。。。

 

=========================================================

http://www.xiaomastack.com/2014/09/20/yunweipingtai2/

第一个运维平台_第二版

其实这个版本与第一个 http://www.xiaomastack.com/2014/07/05/yunweipingtai/ 比已经是脱胎换骨了,该版本后端采用Django,前端用BootStrap。底层还是用的saltsatck、saltAPI、zabbix、zabbixAPI、git等,毕竟整体架构是不能随随便便就改变的。我来简单的介绍下原理和功能。

1、配置管理。

配置管理主要用saltstack来做,与saltstack的通信用saltAPI来完成。”执行salt命令“ 的功能就是在WEB界面执行saltstack命令(安全简单选择预置的命令就可以了,关键是命令可以预置这样在应用层就防止了误操作,虽然我们在salt的代码层也做了类似的处理)。”配置下发“ 这个基于salt命令,依次执行salt命令就好了。

peizhiguanli

2、资产管理。

资产管理主要分为硬件资产和软件资产,硬件资产由saltsatck的grains来采集,软件资产的收集由saltstack下发执行采集脚本来完成。所有的这些操作都自动完成(采集、入库、展示)。打码的地方太多我用PS来打吧!

zhichang01

zhichang02

3、版本发布。

版本发布实现功能

a、能从操作者本地上传zip更新包,更新到正式环境或测试环境。如果更新到测试环境经过测试后没有问题再推送到正式环境。也可以直接更新到正式环境。

b、开发人员提供包名后,根据包名从ftp服务器下载zip更新包,更新到正式环境或测试环境。如果更新到测试环境后没有问题再推送到正式环境。也可以直接更新到正式环境。

c、能够回滚到上一个版本(测试或正式环境)。

banbengengxin

4、数据展示。

主要做的是Zabbix监控数据的展示,分类、分应用、分主机。原始数据由Zabbix采集,然后另外的机器用ZabbixAPI向Zabbix取监控数据存放到rrd文件,最后由RRDTool绘图展示监控数据。

硬件监控数据展示分类沿用Zabbix自身的分类,软件应用监控数据的分类采用再绘图时的自定义的分类。

点击一台主机,监控数据默认展示的是1天的监控数据。

jiankongshuju01jiankongshuju02

点击图可以进入该监项在过去1天、1周、1月、1年的监控图,默认30秒刷新一次(也可以手动刷新)

jiankongshuju03jiankongshuju04

点击1天的监控图,可以进入该监控项2小时的监控图,默认30秒刷新,也可以手动刷新。

jiankongshuju05

转载请注明出处:http://www.xiaomastack.com/2014/09/20/yunweipingtai2/ 谢谢!

 

 

11 Comments

 Add your comment
  1.  
    Turandot  2015/03/18 at 14:54

    请问这个能分享和共同学习一下?

  2.  

    最近也在边学边写运维平台,刚好也是用django;方便给下代码吗?感谢!

  3.  
    Dengwenjia  2015/03/03 at 17:26

    你好,我现在已经做好一部分自动化运维平台django+bootstrap实现,完成资产管理,部分配置管理,堡垒机全部功能,现在想接入zabbix告警报表,看你的做的不错,能共享下吗,可以QQ私聊,你需要我的也可以共享。代码写的烂 现在主要是思路 希望慢慢优化,完善 。谢谢 QQ:1873447820

  4.  

    方便发一下代码给我参考一下么,我也一直想开发这样一个平台

  5.  

    小马哥,搞的不错开源么?

 

 

 

 

 

 

==========

 

 

 

http://www.xiaomastack.com/2014/11/18/salt-api/

配置管理(3) salt-api安装、配置、使用

salt-api也用了一段时间了,现在从安装、配置、使用三个方面梳理下知识。
1、安装
采用pip安装方便快捷,当然编译安装也很nice。
安装pip采用的编译安装的方式,版本当前最新1.5.6,下载、解压、编译、安装是不变的法则。

1
2
3
4
5
6
7
[root@saltstack ~] #tar xvfz pip-1.5.6.tar.gz
[root@saltstack ~] #cd pip-1.5.6
[root@saltstack pip-1.5.6] #python setup.py build
[root@saltstack pip-1.5.6] #python setup.py install
#安装完成后可以用pip freeze查看已安装的packages
[root@saltstack pip-1.5.6] #pip freeze

安装CherryPy,版本3.2.3

1
[root@saltstack ~] #pip install cherrypy==3.2.3

安装salt-api,版本0.8.3

1
[root@saltstack ~] #pip install salt-api==0.8.3

2、配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[root@saltstack ~] # cd /etc/pki/tls/certs
[root@saltstack certs] # make testcert
umask 77 ; \
     /usr/bin/openssl genrsa -aes128 2048 > /etc/pki/tls/private/localhost .key
Generating RSA private key, 2048 bit long modulus
...+++
..................................................................+++
e is 65537 (0x10001)
Enter pass phrase:    #键入加密短语,4到8191个字符
Verifying - Enter pass phrase:    #确认加密短语
umask 77 ; \
     /usr/bin/openssl req -utf8 -new -key /etc/pki/tls/private/localhost .key -x509 -days 365 -out /etc/pki/tls/certs/localhost .crt -set_serial 0
Enter pass phrase for /etc/pki/tls/private/localhost .key:    #再次输入相同的加密短语
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.' , the field will be left blank.
-----
Country Name (2 letter code) [XX]:CN    #都可以选填
State or Province Name (full name) []:Shanghai
Locality Name (eg, city) [Default City]:Shanghai
Organization Name (eg, company) [Default Company Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname ) []:
Email Address []:1989051805@qq.com
[root@saltstack certs] # cd ../private/
[root@saltstack private] # openssl rsa -in localhost.key -out localhost_nopass.key
Enter pass phrase for localhost.key:    #输入之前的加密短语
writing RSA key

如果遇到这样的错误

1
2
3
4
5
6
[root@saltstack certs] # make testcert
umask 77 ; \
     /usr/bin/openssl req -utf8 -new -key /etc/pki/tls/private/localhost .key -x509 -days 365 -out /etc/pki/tls/certs/localhost .crt -set_serial 0
unable to load Private Key
139696733648712:error:0906D06C:PEM routines:PEM_read_bio:no start line:pem_lib.c:703:Expecting: ANY PRIVATE KEY
make : *** [ /etc/pki/tls/certs/localhost .crt]

删掉文件/etc/pki/tls/private/localhost.key文件,然后再make testcert。
为salt-api创建用户并设定密码,用户名没有特别要求,我就用saltapi好了。

1
2
3
[root@saltstack ~] #useradd -M -s /sbin/nologin saltapi
#由于是测试,故采用了弱密码"password",正式环境必须采用强密码,多用特殊字符
[root@saltstack ~] # passwd saltapi

新增加配置文件/etc/salt/master.d/api.conf和/etc/salt/master.d/eauth.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
#该配置文件给予saltapi用户所有模块使用权限,出于安全考虑一般只给予特定模块使用权限
[root@saltstack master.d] # cat eauth.conf
external_auth:
   pam:
     saltapi:
       - .*
[root@saltstack master.d] #
[root@saltstack master.d] # cat api.conf
rest_cherrypy:
   port: 8888
   ssl_crt: /etc/pki/tls/certs/localhost .crt
   ssl_key: /etc/pki/tls/private/localhost_nopass .key
[root@saltstack master.d] #

寻找salt-api的启动脚本,我比较懒就不自己写了,在页面https://github.com/saltstack/salt-api/releases下载salt-api的tar.gz包,启动脚本在解压包的这个位置./pkg/rpm/salt-api。
不过提供的脚本貌似有个小的bug,就是使用restart参数时,salt-api能够stop但是不能start,如下:

1
2
3
[root@saltstack ~] # /etc/init.d/salt-api restart
Stopping salt-api daemon:                                  [确定]
Starting salt-api daemon:                                  [失败]

我估计可能是有些相关资源在下次启动前没有来得及释放造成的,解决方法很简单在脚本的restart函数的stop和start之间加上sleep语句。

1
2
3
4
5
restart() {
    stop
    sleep 1
    start
}

然后重启就没有问题了

1
2
3
4
[root@saltstack ~] # /etc/init.d/salt-api restart
Stopping salt-api daemon:                                  [确定]
Starting salt-api daemon:                                  [确定]
[root@saltstack ~] #

最后重启salt-master在启动salt-api并将salt-api加入开机启动,安装就完成了。

1
2
3
4
5
6
7
8
[root@saltstack ~] # chkconfig salt-api on
[root@saltstack ~] # /etc/init.d/salt-master restart
Stopping salt-master daemon:                               [确定]
Starting salt-master daemon:                               [确定]
[root@saltstack ~] # /etc/init.d/salt-api restart
Stopping salt-api daemon:                                  [确定]
Starting salt-api daemon:                                  [确定]
[root@saltstack ~] #

3、使用(基本的使用方法)
登录获取token

1
2
3
4
5
6
7
8
9
[root@syndic02 ~] # curl -k https://192.168.186.134:8888/login -H "Accept: application/x-yaml" -d username='saltapi' -d password='password' -d eauth='pam'
return :
- eauth: pam
   expire: 1416324685.2597771
   perms:
   - .*
   start: 1416281485.2597761
   token: 6171a922a9718ccb40e94ee7c8eb8768f4eea4e5
   user: saltapi

获取token后就可以使用token通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#相当于在salt-master本地执行salt \* test.ping
[root@syndic02 ~] # curl -k https://192.168.186.134:8888/ -H "Accept: application/x-yaml" -H "X-Auth-Token: 6171a922a9718ccb40e94ee7c8eb8768f4eea4e5" -d client='local' -d tgt='*' -d fun='test.ping'
return :
- syndic01: true
   syndic01-minion02: true
   syndic02: true
   syndic02-minion02: true
 
#相当于在salt-master本地执行salt \* test.echo 'hello world'
[root@syndic02 ~] # curl -k https://192.168.186.134:8888/ -H "Accept: application/x-yaml" -H "X-Auth-Token: 6171a922a9718ccb40e94ee7c8eb8768f4eea4e5" -d client='local' -d tgt='*' -d fun='test.echo' -d arg='hello world'
return :
- syndic01: hello world
   syndic01-minion02: hello world
   syndic02: hello world
   syndic02-minion02: hello world
[root@syndic02 ~] #

运维开发这样使用还是不方便的,下面写的是一个salt-api的类(其它的文章也提到过)可以参考。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#!/usr/bin/env python
#coding=utf-8
 
import urllib2, urllib, json, re
 
class saltAPI:
     def __init__( self ):
         self .__url = 'https://192.168.186.134:8888'       #salt-api监控的地址和端口如:'https://192.168.186.134:8888'
         self .__user =  'saltapi'             #salt-api用户名
         self .__password = 'password'          #salt-api用户密码
         self .__token_id = self .salt_login()
 
     def salt_login( self ):
         params = { 'eauth' : 'pam' , 'username' : self .__user, 'password' : self .__password}
         encode = urllib.urlencode(params)
         obj = urllib.unquote(encode)
         headers = { 'X-Auth-Token' :''}
         url = self .__url + '/login'
         req = urllib2.Request(url, obj, headers)
         opener = urllib2.urlopen(req)
         content = json.loads(opener.read())
         try :
             token = content[ 'return' ][ 0 ][ 'token' ]
             return 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[ 'return' ]
 
     def saltCmd( self , params):
         obj = urllib.urlencode(params)
         obj, number = re.subn( "arg\d" , 'arg' , obj)
         res = self .postRequest(obj)
         return res
 
def main():
     #以下是用来测试saltAPI类的部分
     sapi = saltAPI()
     params = { 'client' : 'local' , 'fun' : 'test.ping' , 'tgt' : '*' }
     #params = {'client':'local', 'fun':'test.ping', 'tgt':'某台服务器的key'}
     #params = {'client':'local', 'fun':'test.echo', 'tgt':'某台服务器的key', 'arg1':'hello'}
     #params = {'client':'local', 'fun':'test.ping', 'tgt':'某组服务器的组名', 'expr_form':'nodegroup'}
     test = sapi.saltCmd(params)
     print test
 
if __name__ = = '__main__' :
     main()

测试效果

1
2
3
[root@syndic02 ~] # python salt-api.py
[{u 'syndic02' : True, u 'syndic02-minion02' : True, u 'syndic01' : True, u 'syndic01-minion02' : True}]
[root@syndic02 ~] #

以上只是一些基本的实例,salt-api还可以实现更多功能。
文章出处:http://www.xiaomastack.com/2014/11/18/salt-api/

 
 
http://www.xiaomastack.com/2014/10/31/saltstack-grains/

配置管理篇(2)_SaltStack grains

grains主要负责采集并返回客户端minion的一些基本信息, saltstack自带grains模块可以采集机器的很多基本信息,对于比较特别的信息grains可以在客户端或服务器端自定义item后采集,不管是那种方式自定义grains的item,最终grains的item值都是由客户端minion采集完成后上报给服务端master的,采集动作只在启动客户端或有新的grains添加时触发,grains采集上报的数据可以作为资产管理的元数据。一个客户端在启动时就将自定义或基本的item信息值读取到内存了,这些值是个常量在下次重启客户端之前不会改变。

先看看怎样使用,然后瞧瞧怎样自定义(4种自定义方法)
查看所有的grains项 grains.ls

1
2
3
4
5
6
7
8
9
[root@saltstack ~] # salt minion01 grains.ls
minion01:
     - biosreleasedate
     - biosversion
     - cpu_flags
     - cpu_model
     - cpuarch
     - defaultencoding
... ... ...

查看所有的grains项的详细信息 grains.items

1
2
3
4
5
[root@saltstack ~] # salt minion01 grains.items
minion01:
   biosreleasedate: 07 /02/2012
   biosversion: 6.00
... ... ...

查看grains指定的item信息

1
2
3
4
5
6
7
8
9
10
11
[root@saltstack ~] # salt minion01 grains.item shell
minion01:
   shell: /bin/sh
[root@saltstack ~] # salt minion01 grains.item os
minion01:
   os: CentOS
[root@saltstack ~] # salt minion01 grains.item ipv4
minion01:
   ipv4:
       127.0.0.1
       192.168.186.135

grains添加自定义items

1、在master端添加
在配置文件file_roots指定的目录(默认是/srv/salt)下创建_grains目录,编写可以返回一个字典的py文件(需要懂点python)。

1
2
3
4
5
6
7
8
[root@saltstack ~] # grep -Ev "^$|^#" /etc/salt/master
file_roots:
   base:
     - /srv/salt
pillar_roots:
   base:
     - /srv/pillar
[root@saltstack ~] #

例如grains添加自定义item(disk_size)返回磁盘的大小,注意python脚本返回值是一个字典。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@saltstack _grains] # cat disk.py
#coding=utf-8
import os
 
def disk_size():
     ''' get disk size '''
 
     disk = os.statvfs( "/" )
     capacity = disk.f_bsize * disk.f_blocks
     ret = int ( float (capacity) / 1000 / 1000 / 1000 )
     if ret > 1000 :
         obj = ret / 1000.0
         disk_size = ( "%.1f" % obj) + 'T'
     else :
         ret = str (ret)
         disk_size = ret + 'G'
 
     return { 'disk_size' : disk_size}

用saltutil.sync_grains或者saltutil.sync_all将自定义的grains item 同步到minion端,如果只同步grains建议使用saltutil.sync_grains。

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@saltstack _grains] # salt minion01 saltutil.sync_grains
minion01:
     - grains.__init__
     - grains.disk
[root@saltstack _grains] # salt minion01 saltutil.sync_all
minion01:
     ----------
     grains:
     modules:
     outputters:
     renderers:
     returners:
     states:

从结果可以看到sync_grains和sync_all不同之处了吧。

同步完成后在minion端的以下目录中可以找到master下发的grains文件

1
2
[root@minion01 minion.d] # ls /var/cache/salt/minion/extmods/grains/
disk.py      disk.pyc     __init__.py

看看是否同步成功

1
2
3
4
[root@saltstack _grains] # salt minion01 grains.item disk_size
minion01:
   disk_size: 19G
[root@saltstack _grains] #

2、在minion端添加
可以直接在配置文件/etc/salt/minion中添加,但一般不这么干。一般会将default_include: minion.d/*.conf配置项打开,然后将自定义grains的以.conf结尾的配置文件放到minion.d这个目录中。

1
2
3
4
[root@minion01 ~] # grep -Ev "^$|#" /etc/salt/minion
default_include: minion.d/*.conf
master: 192.168.186.134
id : minion01

在minion.d目录中添加并编辑grains.conf文件(文件名随意)

1
2
3
4
5
[root@minion01 minion.d] # cat grains.conf
grains:
   IDC: xxx-xxx-xxx
   dFlag: 2014 /10/31
[root@minion01 minion.d] #

重启salt-minion后生效,然后在master端查看自定义的grains

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@saltstack _grains] # salt minion01 grains.get IDC
minion01:
     xxx-xxx-xxx
[root@saltstack _grains] # salt minion01 grains.get dFlag
minion01:
     2014 /10/31
[root@saltstack _grains] # salt minion01 grains.item dFlag
minion01:
   dFlag: 2014 /10/31
[root@saltstack _grains] # salt minion01 grains.item IDC
minion01:
   IDC: xxx-xxx-xxx
[root@saltstack _grains] #

3、在master端直接用grains.setval为指定minion的grains设定一个item
设定welcome的值为’hello world’

1
2
3
[root@saltstack _grains] # salt minion01 grains.setval welcome 'hello world'
minion01:
   welcome: hello world

查看定义的item的值

1
2
3
4
5
6
7
[root@saltstack _grains] # salt minion01 grains.item welcome
minion01:
   welcome: hello world
[root@saltstack _grains] # salt minion01 grains.get welcome
minion01:
     hello world
[root@saltstack _grains] #

这次定义的grains保存在minion端的配置文件/etc/salt/grains中

1
2
3
4
[root@minion01 salt] # pwd
/etc/salt
[root@minion01 salt] # cat grains
welcome: hello world

4、在master端通过states的grains方法也可以自定义grains
为了方便管理,在文件夹/salt/srv下再创建文件夹gitems,然后再该文件夹下编辑新的sls文件gtest.sls

1
2
3
4
5
6
7
8
9
[root@saltstack gitems] # pwd
/srv/salt/gitems
[root@saltstack gitems] # ls
gtest.sls
[root@saltstack gitems] # cat gtest.sls
country:
   grains.present:
     - value: china
[root@saltstack gitems] #

下发到minion端,并查看新添加的item

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@saltstack gitems] # salt minion01 state.sls gitems.gtest
minion01:
----------
           ID: country
     Function: grains.present
       Result: True
      Comment: Set grain country to china
      Changes:
               ----------
               country:
                   china
 
Summary
------------
Succeeded: 1
Failed:    0
------------
Total:     1
[root@saltstack gitems] # salt minion01 grains.get country
minion01:
     china

该方法定义的grains同样保存在minion端的配置文件/etc/salt/grains中

1
2
3
4
[root@minion01 salt] # cat grains
country: china
welcome: hello world
[root@minion01 salt] #

转载请注明出处:http://www.xiaomastack.com/2014/10/31/saltstack-grains/ 谢谢!

 
http://www.xiaomastack.com/2014/10/16/saltstack01/

配置管理篇(1)_SaltStack常用可执行模块(execution modules)及解决rm -f带来的困惑

所有的可执行模块(execution modules)可以查看官方文档,记录下我常用到的模块test、status、cmdmod、useradd、system、systemd、file和解决rm -f带来困惑的方法。使用方法除了查看官方文档和手册外,查看源代码(源代码中也详细描述了使用方法和示例)也是一个好的方法。
1、test
test模块提供对minion端有限项的任意测试,常见的有ping、echo、version等,源码在minion端/usr/lib/python2.6/site-packages/salt/modules/test.py,详细的使用方法查源码最直接了。

1
2
3
4
5
6
7
8
9
10
[root@saltstack ~] # salt minion01 test.ping
minion01:
     True
[root@saltstack ~] # salt minion01 test.echo 'hello world'
minion01:
     hello world
[root@saltstack ~] # salt minion01 test.version
minion01:
     2014.1.10
[root@saltstack ~] #

2、status
status可以查看minion的基本状态如CPU、内存、硬盘、网络等,常见的有all_status、uptime、w、netstats等,源码在minion端/usr/lib/python2.6/site-packages/salt/modules/status.py。
对于all_status方法查看源码可以看到这个版本支持的可查询状态有cpuinfo、cpustatus、… …等

1
2
3
4
5
6
7
8
9
10
11
12
13
def all_status():
     ... ... ...
     return { 'cpuinfo' : cpuinfo(),
             'cpustats' : cpustats(),
             'diskstats' : diskstats(),
             'diskusage' : diskusage(),
             'loadavg' : loadavg(),
             'meminfo' : meminfo(),
             'netdev' : netdev(),
             'netstats' : netstats(),
             'uptime' : uptime(),
             'vmstats' : vmstats(),
             'w' : w()}

使用方法很简单,例如:

1
2
3
4
[root@saltstack ~] # salt minion01 status.uptime
minion01:
      14:27:49 up  4:43,  1 user,  load average: 0.00, 0.00, 0.00
[root@saltstack ~] #

3、cmdmod与rm -f带来的困惑
这个模块中的run可以执行在bash里执行的所有命令,很强大但是也得注意在run中执行类似rm这样的命令是很危险的。源码位置在minion端的/usr/lib/python2.6/site-packages/salt/modules/cmdmod.py。
cmd.run执行命令就像在minion端本地执行命令一样亲切。

1
2
3
4
5
6
7
[root@saltstack ~] # salt minion01 cmd.run 'df -hT'
minion01:
     Filesystem    Type    Size  Used Avail Use% Mounted on
     /dev/sda3     ext4     19G  2.5G   15G  15% /
     tmpfs        tmpfs    242M     0  242M   0% /dev/shm
     /dev/sda1     ext4    504M   39M  440M   9% /boot
[root@saltstack ~] #

亲切的同时也会带来困惑,如果误操作是这样的,后果很可怕。

1
[root@saltstack ~] # salt \* cmd.run "rm -rf /"

可以修改源代码禁止执行rm这种较危险的命令,打开minion端的文件/usr/lib/python2.6/site-packages/salt/modules/cmdmod.py找到run()函数,在其调用_run()函数的时候判断参数cmd中是否包含rm命令,如果包含了就返回并提示”rm命令很危险,任务执行失败”,这样一来master上cmd.run就不能执行rm命令了。

1
2
3
4
5
6
7
8
9
10
'''
... ... ...
      salt '*' cmd.run "grep f" stdin='one\\ntwo\\nthree\\nfour\\nfive\\n
'''
#在以下位置添加代码
if r 'rm ' in cmd:
     return "'rm' is dangerous!!!  MISSION FAILED!!!"
#在以上位置添加代码
ret = _run(cmd,
... ... ...

修改文件后重启minion后生效,测试下效果

1
2
3
4
[root@saltstack ~] # salt minion01 cmd.run "rm -rf /"
minion01:
     'rm' is dangerous!!!  MISSION FAILED!!!
[root@saltstack ~] #

可以看出修改已经生效,再也不用担心cmd.run误操作了。不过有个问题就是,在cmdmod模块中不仅cmd.run可以执行rm命令还有cmd.run_all、cmd.run_stdout等都可以执行rm命令,为了更彻底的封掉rm这个命名,仔细看了下代码发现它们最终都是调用的_run()来完成任务的,可以修改_run()这个函数(在/usr/lib/python2.6/site-packages/salt/modules/cmdmod.py文件第213行)来彻底的封掉rm这个命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
'''
     ... ... ...
     Do the DRY thing and only call subprocess.Popen() once
     '''
     #在以下位置添加代码
     if r 'rm ' in cmd:
         ret = {}
         ret[ 'stdout' ] = "'rm' is dangerous!!!  MISSION FAILED!!!"
         ret[ 'stderr' ] = ''
         ret[ 'pid' ] = ''
         ret[ 'retcode' ] = 0
         return ret
     #在以上位置添加代码
     if salt.utils.is_true(quiet):
     ... ... ...

如果是修改_run()函数后就不用修改run()等其它函数了,修改文件后重启minion后生效,测试下效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@saltstack ~] # salt minion01 cmd.run "rm -rf /"
minion01:
     'rm' is dangerous!!!  MISSION FAILED!!!
[root@saltstack ~] # salt minion01 cmd.run_all "rm -f test"
minion01:
     ----------
     pid:
 
     retcode:
         0
     stderr:
 
     stdout:
         'rm' is dangerous!!!  MISSION FAILED!!!

将修改过的salt-minion打成rpm包后放到本地的yum源,然后重新安装所有的minion端就可以解决rm带来的困惑了。

 

4、useradd
这个模块主是用来用户管理的,源文件位置在minion端(下同)/usr/lib/python2.6/site-packages/salt/modules/useradd.py,记录几个常用的。
user.add添加新用户

1
2
3
[root@saltstack ~] # salt minion01 user.add testuser
minion01:
     True

user.list_users列出所有的用户,可以看到刚才添加的用户

1
2
3
[root@saltstack ~] # salt minion01 user.list_users | grep testuser
     - testuser
[root@saltstack ~] #

user.info查看用户信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@saltstack ~] # salt minion01 user.info testuser
minion01:
     ----------
     fullname:
 
     gid:
         500
     groups :
         - testuser
     home:
         /home/testuser
     homephone:
 
     name:
         testuser
     passwd :
         x
     roomnumber:
 
     shell:
         /bin/bash
     uid:
         500
     workphone:

user模块还有很多可用的方法,可以根据名称就可以知道其功能了比如

1
2
3
4
5
6
7
8
9
10
user.getent
user.delete name remove=True force=True
user.chworkphone foo "7735550123"
user.chuid foo 4376
user.chshell foo /bin/zsh
user.chroomnumber foo 123
user.chfullname foo "Foo Bar"
user.chgid foo 4376
user.chgroups foo wheel,root True
user.chhome foo /home/users/foo True

5、system
系统管理模块,能看懂的就不记录了。源码位置/usr/lib/python2.6/site-packages/salt/modules/system.py

1
2
3
4
5
system.halt        #停止正在运行的系统
system.init 3      #切换到字符界面,5是图形界面
system.poweroff
system.reboot
system. shutdown

6、systemd
系统服务管理模块,源码位置/usr/lib/python2.6/site-packages/salt/modules/systemd.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
service.available sshd            #查看服务是否可用
service.disable <service name>    #设置开机启动的服务
service. enable <service name>
service.disabled <service name>   #查看服务是不是开机启动
service.enabled <service name>
service.get_disabled              #返回所有关闭的服务
service.get_enabled               #返回所有开启的服务
service.get_all                   #返回所有服务
service.reload <service name>     #重新载入指定的服务
service.restart <service name>    #重启服务
service.start <service name>
service.stop <service name>
service.status <service name>
service.force_reload <service name>  #强制载入指定的服务

7、file
文件管理模块,这个模块的方法太多,非常强大,选些常用的记一下。源文件/usr/lib/python2.6/site-packages/salt/modules/file.py中有所有方法的功能和使用说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
file .copy /path/to/src /path/to/dst                    #将master文件拷贝到minion上,salt还有个专门的拷文件的命令salt-cp
file .append /etc/motd "append test."                   #为minion的文件/etc/moth追加内容
file .check_hash /etc/fstab md5=<md5sum>                #检测并对比文件的md5值,并返回布尔值
file .get_hash /etc/passwd    获取文件的md5值
file .check_perms /etc/sudoers '{}' root root 400       #检测并更改文件的用户、组、权限
file . chgrp /etc/passwd root
file . chown /etc/passwd root root
file .directory_exists /tmp                             #检测文件夹是否存在
file .file_exists /etc/passwd
file .get_group /etc/passwd
file .get_user /etc/passwd
file .get_mode /etc/passwd                              #检测文件权限
file .set_mode /etc/passwd 0644                         #设置权限
file .get_gid /etc/passwd
file .get_uid /etc/passwd

转载请注明出处:http://www.xiaomastack.com/2014/10/16/saltstack01/ 谢谢!

 
http://www.xiaomastack.com/2014/10/16/saltstack00/

1、安装
安装saltstack用EPEL源安装简单快捷,实际部署的时候可以将saltstack相关的rpm包放到本地的yum源用本地源安装。
安装EPEL源:

1
2
[root@saltstack ~] #rpm -ivh epel-release-6-8.noarch.rpm

安装salt-master,如果master要对自己进行配置管理则服务器master端本地也要安装minion

1
2
3
[root@saltstack ~] #yum install salt-master
#安装salt-minion可选
[root@saltstack ~] #yum install salt-minion

2、修改配置文件并启动服务。saltstack配置比较简单,一般不需要修改太多的参数。
salt-master端,暂时没有什么配置的,默认就好。

1
2
3
4
5
6
7
8
[root@saltstack ~] # grep -Ev "^#|^$" /etc/salt/master
file_roots:
   base:
     - /srv/salt
pillar_roots:
   base:
     - /srv/pillar
[root@saltstack ~] # salt-master -d

salt-minion端,配置 “master”项指向master的IP地址,配置 “id” 项为主机名(一般用主机名,也可以配成其它的标识符)

1
2
3
4
5
[root@minion01 ~] # grep -Ev "^#|^$" /etc/salt/minion
master: 192.168.186.134
id : minion01
[root@minion01 ~] # salt-minion -d
[root@minion01 ~] #
1
2
3
4
5
[root@minion02 ~] # grep -Ev "^#|^$" /etc/salt/minion
master: 192.168.186.134
id : minion02
[root@minion02 ~] # salt-minion -d
[root@minion02 ~] #

3、master认证端添minion的key,并做简单的存活测试。
salt-key管理所有的key,-L参数列出所有的key.”Unaccepted Keys”就是所有未认证minion端的key。

1
2
3
4
5
6
[root@saltstack ~] # salt-key -L
Accepted Keys:
Unaccepted Keys:
minion01
minion02
Rejected Keys:

-A参数接纳(认证)所有未被接纳(认证)的key,-a参数认证单个指定的key。

1
2
3
4
5
6
7
8
[root@saltstack ~] # salt-key -A
The following keys are going to be accepted:
Unaccepted Keys:
minion01
minion02
Proceed? [n /Y ] y
Key for minion minion01 accepted.
Key for minion minion02 accepted.

再查看下所有key的情况,可以看到”Accepted Keys”已经认证的key。

1
2
3
4
5
6
[root@saltstack ~] # salt-key -L
Accepted Keys:
minion01
minion02
Unaccepted Keys:
Rejected Keys:

用test.ping测试下minion端的存活,可以从返回值看到minion01和minion02在线

1
2
3
4
5
6
[root@saltstack ~] # salt \* test.ping
minion01:
     True
minion02:
     True
[root@saltstack ~] #

用test.ping命令测试时,如果等待的返回时间较长有可能是某些minion已经不在线了,可以用salt-run来查看所有minion的存活状态。

1
2
3
4
5
6
[root@saltstack ~] # salt-run manage.status
down:
up:
     - minion01
     - minion02
[root@saltstack ~] #

转载请注明出处:http://www.xiaomastack.com/2014/10/16/saltstack00/ 谢谢!

 

 

 

 http://www.xiaomastack.com/2014/10/13/release-3/

版本更新发布WEB化(3)_小插曲自定义请求saltAPI的类

安装了saltAPI后就可以在任何一台服务器上调用saltAPI让saltstack执行相关的操作。之前用PHP和python写过相关的调用类,如果有兴趣可以移步以下文章《用PHP实现salt-api调用》《用Python实现salt-api调用》。思路都是一样的,不过这两篇代码不够优雅。下面再上一个后来用python用写的比较优雅的一个类,看上去会简洁美观很多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#!/usr/bin/env python
#coding=utf-8
 
import urllib2, urllib, json, re
 
class saltAPI:
     def __init__( self ):
         self .__url = 'https://saltAPI监控的地址和端口如:192.168.192.168:8000'
         self .__user = '调用saltAPI时的用户名'
         self .__password = '调用saltAPI时的密码'
         self .__token_id = self .salt_login()
 
     def salt_login( self ):
         params = { 'eauth' : 'pam' , 'username' : self .__user, 'password' : self .__password}
         encode = urllib.urlencode(params)
         obj = urllib.unquote(encode)
         headers = { 'X-Auth-Token' :''}
         url = self .__url + '/login'
         req = urllib2.Request(url, obj, headers)
         opener = urllib2.urlopen(req)
         content = json.loads(opener.read())
         try :
             token = content[ 'return' ][ 0 ][ 'token' ]
             return 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[ 'return' ]
 
     def saltCmd( self , params):
         obj = urllib.urlencode(params)
         obj, number = re.subn( "arg\d" , 'arg' , obj)
         res = self .postRequest(obj)
         return res
 
def main():
     #以下是用来测试saltAPI类的部分
     sapi = saltAPI()
     params = { 'client' : 'local' , 'fun' : 'test.ping' , 'tgt' : '某台服务器的key' }
     #params = {'client':'local', 'fun':'test.echo', 'tgt':'某台服务器的key', 'arg1':'hello'}
     #params = {'client':'local', 'fun':'test.ping', 'tgt':'某组服务器的组名', 'expr_form':'nodegroup'}
     test = sapi.saltCmd(params)
     print test
 
if __name__ = = '__main__' :
     main()

转载请注明出处:http://www.xiaomastack.com/2014/10/13/release-3/ 谢谢!

 

 

http://www.xiaomastack.com/2014/06/26/php-salt-api/

用PHP实现salt-api调用

saltstack由于其强大的配置部署及管理功能,是越来越火,被很多运维人员所认可。由于最近要开发一个运维平台,底层用到了salt-api来实现。如果你是入门的用户可以参考saltstack安装的bolg http://blog.coocla.org/category/saltstack 还有很多优秀的blog,可以百度神马的很多的,还有我们的saltstack中文站也可以逛逛  http://www.saltstack.cn/ 。

安装salt-api请参考 http://www.saltstack.cn/projects/cssug-kb/wiki/salt-api-deploy-and-use

经过saltsatck和salt-api的学习后,开始了苦逼的堆代码的工作,首先用PHP实现了对salt-api的访问,后来由于别的需求也实现了Python对salt-api的访问。

下面的代码是最初的比较原始的版本saltapi.php,不过功能够用了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?php
function get_token(){
global $token ;
$url = 'https://ip:8000/login' ; //ip是已经配好的api地址如 192.168.1.22
$ch = curl_init( $url );
curl_setopt( $ch ,CURLOPT_POST,TRUE);
curl_setopt( $ch ,CURLOPT_SSL_VERIFYHOST,2);
curl_setopt( $ch ,CURLOPT_SSL_VERIFYPEER,False);
curl_setopt( $ch ,CURLOPT_HTTPHEADER, Array( 'Accept: application/x-yaml' ));
curl_setopt( $ch ,CURLOPT_POSTFIELDS, 'username=你的用户名&password=用户对应的密码&eauth=pam' );
curl_setopt( $ch ,CURLOPT_RETURNTRANSFER,TRUE);
$token = curl_exec( $ch );
$token = strstr ( $token , 'token' , false);   //取登录后返回字符串中的token
$token = strstr ( $token , 'user' , true);
$token = explode ( " " , $token )[1];
curl_close( $ch );
$token = rtrim( $token , "\n" ); //删掉token字符串最后面的'\n',不然后面死活登不上去
}
 
//适用于带一个或不带参数的salt exc模块如 salt \* test.ping 或 salt \* test.echo "hello"这样的模块
function exc_salt( $tgt , $fun , $arg , $arg_num ){
global $token ;
global $report ;
$url = 'https://ip:8000/' ;
$ch = curl_init( $url );
curl_setopt( $ch ,CURLOPT_POST,TRUE);
curl_setopt( $ch ,CURLOPT_SSL_VERIFYHOST,2);
curl_setopt( $ch ,CURLOPT_SSL_VERIFYPEER,False);
curl_setopt( $ch ,CURLOPT_HTTPHEADER,Array( 'Accept: application/x-yaml' , "X-Auth-Token: $token" ));
if ( $arg_num )
     curl_setopt( $ch ,CURLOPT_POSTFIELDS, "client=local&tgt=$tgt&fun=$fun&arg=$arg" );
else
     curl_setopt( $ch ,CURLOPT_POSTFIELDS, "client=local&tgt=$tgt&fun=$fun" );
curl_setopt( $ch ,CURLOPT_RETURNTRANSFER,TRUE);
$report = curl_exec( $ch );
curl_close( $ch );
return $report ;
}
 
//适用于带两个参数的salt exc模块
function exc_salt_2( $tgt , $fun , $arg_1 , $arg_2 ){
global $token ;
global $report ;
$url = 'https://ip:8000/' ;
$ch = curl_init( $url );
curl_setopt( $ch ,CURLOPT_POST,TRUE);
curl_setopt( $ch ,CURLOPT_SSL_VERIFYHOST,2);
curl_setopt( $ch ,CURLOPT_SSL_VERIFYPEER,False);
curl_setopt( $ch ,CURLOPT_HTTPHEADER,Array( 'Accept: application/x-yaml' , "X-Auth-Token: $token" ));
curl_setopt( $ch ,CURLOPT_POSTFIELDS, "client=local&tgt=$tgt&fun=$fun&arg=$arg_1&arg=$arg_2" );
curl_setopt( $ch ,CURLOPT_RETURNTRANSFER,TRUE);
$report = curl_exec( $ch );
curl_close( $ch );
return $report ;
}
 
//以下是测试这个脚本功能的部分,正式上线是得注释掉
get_token();
print exc_salt( '主机key' , 'test.echo' , 'hello' , 1); //带一个参数的
print exc_salt( '主机key' , 'test.ping' , '' , 0);   //不带参数的
?>

运行结果如下:

phpsalt

这些函数还可以封装成一个类,方便调用。如果要求不高这样也可以满足要求了。

salt-api手册参考 http://salt-api.readthedocs.org/en/latest/

如果你的python还可以的话,直接看源代码,也是一样一样的。

转载请注明出处http://www.xiaomastack.com/2014/06/26/php-salt-api/ 谢谢!

 

http://www.xiaomastack.com/2014/06/30/saltminionerror/

解决启动salt-minion时“ImportError: No module named salt.scripts”错误

这是当初部署saltstack时候的问题了,saltstack用的是0.17.4的版本。正当开心minion部署到最后一台时时杀出了个程咬金,当启动 minion端时报错 “ImportError: No module named salt.scripts” 具体信息如下图:

saltminionerror01

但是明明是存在 scripts 脚本和完整的模块的,salt硬是找不到,汗!后来发现这台机器上有几个python环境,7层地狱啊!用 python –version 命令查看默认的 python环境是2.7的,而saltstack安装环境python2.6的,saltstack 模块是安装在 python2.6 的库里面的。所以会一直提示找不到模块。

好了,原因找到了问题就好解决了,手动指定 salt-minion 启动脚本的运行环境为 python2.6(2.6系统默认安装CentOS6.3) 如下:

saltminionerror02

然后,然后问题就解决了。

转载请注明出处 http://www.xiaomastack.com/2014/06/30/saltminionerror/ 谢谢!

 

 

 

http://www.xiaomastack.com/2014/06/30/saltstackmasterz/

解决salt-master部分僵尸子进程问题

saltstack在线上稳定的运行了一段时间,最近发现时不时 master 的少量的子进程变成了僵尸进程,最快的解决方法是重启master,但是治标不治本,一段时间后又会出现少量的僵尸进程。开始还以为是master的子进程启少了,如是修改 /etc/salt/master 配置文件增加子线程的数量,但是还是会发现僵尸进程。

经过一段时间的摸索,发现master端的超时时间默认是 5s 。由于运维平台有更多的采集任务需要master来执行,master在有任务突发的情况下,有些配置的下发或者脚本的执行很容易超时,超时的最直接结果是“配置和操作步骤都没有问题,但是批量执行时会随机的报错,把报错的任务单独执行又完全正确”。

解决方法是更改 /etc/salt/master 配置文件,将 timeout 项的注释取消,将时间改成 5s 的倍数(这只是我改参数的习惯。其它也行,只要比5s大合适就行)。然后重启 master 就不会出现僵尸进程了。

转载请注明出处 http://www.xiaomastack.com/2014/06/30/saltstackmasterz/ 谢谢!

 

 

http://www.xiaomastack.com/2014/06/27/python-salt-api/

之前写过用PHP实现salt-api调用,由于业务需求又整了个Python版的,实现的思路和PHP一样,这里只贴出访问带1个参数或不带参数salt执行模块的代码,如果需要使用更多的参数,多堆几段代码就好了。如果不懂 saltstack 或 salt-api 建议参考PHP那篇中给的链接先好好学习学习(saltsatck是个运维利器,值得你这么做)。

先贴出我的代码,不好勿喷:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#!/usr/bin/python
# -*- coding: utf-8 -*-
 
import pycurl
import StringIO
 
#登录salt-api,获取token
def api_login():
     global token
     url = 'https://IP地址:8000/login'
     ch = pycurl.Curl()
     ch.setopt(ch.URL, url)
     info = StringIO.StringIO()
     ch.setopt(ch.WRITEFUNCTION, info.write)
     ch.setopt(ch.POST, True )
     #如果是https就要开启这两行
     ch.setopt(ch.SSL_VERIFYPEER, 0 )
     ch.setopt(ch.SSL_VERIFYHOST, 2 )
     ch.setopt(ch.HTTPHEADER, [ 'Accept: application/x-yaml' ])
     ch.setopt(ch.POSTFIELDS, 'username=你的用户名&password=对应用户的密码&eauth=pam' )
     #要包头信息
     #ch.setopt(ch.HEADER, True)
     #不要包头信息
     ch.setopt(ch.HEADER, False )
     ch.perform()
     html = info.getvalue()
     #提取token
     token = html.split( "\n" )[ - 3 ].replace( "\n" , '')
     token = token.split( ' ' )[ 3 ]
     info.close()
     ch.close()
 
def api_exec(target, fun, arg = '', arg_num = 0 ):
     global token
     url = 'https://IP地址:8000/'
     ch = pycurl.Curl()
     ch.setopt(ch.URL, url)
     info = StringIO.StringIO()
     ch.setopt(ch.WRITEFUNCTION, info.write)
     ch.setopt(ch.POST, True )
     ch.setopt(ch.SSL_VERIFYPEER, 0 )
     ch.setopt(ch.SSL_VERIFYHOST, 2 )
     ch.setopt(ch.HTTPHEADER, [ 'Accept: application/x-yaml' , "X-Auth-Token: %s" % (token)])
     if arg_num = = 0 :
         ch.setopt(ch.POSTFIELDS, "client=local&amp;tgt=%s&amp;fun=%s" % (target, fun))
     elif arg_num = = 1 :
         ch.setopt(ch.POSTFIELDS, "client=local&amp;tgt=%s&amp;fun=%s&amp;arg=%s" % (target, fun, arg))
     ch.setopt(ch.HEADER, False )
     ch.perform()
     html = info.getvalue()
     info.close()
     ch.close()
     return html
 
#测试时用的,做为模块使用时请注释下面两行
api_login()
print api_exec( '主机key' , 'test.ping' , '', 0 )

先亮执行结果截图,哈哈:

python-salt-api

关键的地方是提取经过认证后的 token,不要忘记除掉结尾的换行符”\n”,不然就悲剧了。

转载请注明出处http://www.xiaomastack.com/2014/06/27/python-salt-api/ 谢谢!

 

 

 

 

 

 

 
 
 

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM