Lnmpy

Elvis's Blog

Proxychains Cross GFW

前言

做为一个dev肯定要经常访问404的page, shadowsocks在这里面作为一个不可或缺的一环(此处不展开), 但大家通常的做法只是用其来proxy浏览器的请求.
实际上配置好Proxychains就可以无缝的在commandline中使用一些有特殊要求的app了.

基于某些原因这里不能做太多的展开, 只是介绍一下配置, 作为mark.

配置文件

将以下内容保存为proxychains.conf

# proxychains.conf VER 4.x
strict_chain
quiet_mode
proxy_dns
remote_dns_subnet 224
tcp_read_time_out 15000
tcp_connect_time_out 8000
[ProxyList]
# socks5 IP PORT
socks5 127.0.0.1 1086

最后一行修改为对应的shadowsocks的ip/port, 需要与shadowsocks配置匹配, 如下

Mac上安装

brew install proxychains-ng
cp ./proxychains.conf /usr/local/etc/proxychains.conf
# 由于Mac的SIP限制, proxychanis 不能运行system app
proxychains4 curl google.com X # because curl is in /usr/bin
proxychains4 npm install uuid √ # because npm is in /usr/local/bin, not a system app
# 如果需要这个, 则需要关闭SIP的部分限制
csrutil enable --without debug

Ubuntu14.04

apt-get安装的proxychains版本太老无法使用了, 需要通过源码安装

git clone https://github.com/rofl0r/proxychains-ng.git
cd proxychains-ng
./configure --prefix=/usr --sysconfdir=/etc && make
sudo make install
sudo cp ./proxychains.conf /etc/proxychains.conf

用法

proxychains4 curl google.com
proxychains4 firebase init
# Mac open gui app in mac with proxy default
proxychains4 open '/Applications/Backup and Sync.app/' # 使用GoogleDrive

Python Flask中使用Decorator自动填充函数参数

前言

在写python flask的server code时, 有一个很累赘的code就是从request里面把参数取出来, 然后做校验, 比如说有这么一个api:

from flask import request, route
@route('/create_user', methods=['POST'])
def create_user():
request_args = request.get_json()
user_name = request_args.get('first_name')
check_user_name_format(user_name)
email = request_args.get('email')
check_email_format(email)
phone = request_args.get('phone')
check_phone_format(phone)
...

这样的code看起来就很累赘, 参数校验本就必不可少, 但是一个业务的code中如果这些都叠在一起就显得很累赘, 写着写着会发现有大量重复的code, 降低了可读性和可维护性

所以如果说有一个方式能够将request中传过来的参数转换成如下的形式, 势必结构会清晰很多.

from flask import request, route
@route('/create_user', methods=['POST'])
def create_user(user_name, email, phone):
check_user_name_format(user_name)
check_email_format(email)
check_phone_format(phone)
...

这里面就要用到了python的decorator了

目录

  1. python decorator简介
  2. inspect解析函数的参数列表
  3. 获取request中所有参数

python decorator简介

python的decorator比java的注解要简单得很多, 一个简单的decorator就像这样:

def sum(a, b):
return a + b
print sum(1, 2) # 3
def add_10(func):
def f(*args, **kwargs):
return func(*args, **kwargs) + 10
return f
@add_10
def sum(a, b):
return a + b
print sum(1, 2) # 13

其实一个decorator就是封装了当前的这个函数, 稍作封装, 然后返回了一个新函数

def add_10(func):
print 'the original sum method:', id(func) # 4490474752
print func.__name__ # sum
def f(*args, **kwargs):
return func(*args, **kwargs) + 10
print 'the new method inside decorator:', id(f) # 4490474752
print f.__name__ # f
return f
@add_10
def sum(a, b):
return a + b
print 'id of sum with decorator:', id(sum) # 4490474752
print sum.__name__ # f

所以上面的add_10decorator过后的函数已经变成了一个新的函数, 在输出中可以看到sum已经不是原来的那个sum, 而是在add_10中的f

inspect获取函数的参数列表

inspect是python当中一个比较底层的library, 通过inspect可以窥探到对象的一些内置信息.

对于我们想要实现函数参数自动填充, 那么我们首先就要获得改函数的函数列表, 可以通过inspect.getargspec来实现.

import inspect
def sum(a, b, c=10, *hello, **world):
return a + b + c
print inspect.getargspec(sum)
# ArgSpec(args=['a', 'b', 'c'], varargs='hello', keywords='world', defaults=(10,))

args就是函数定义的所需要的参数列表, defaults是提供的默认值列表, 和args还是比较好对应的, 所以实际中我是这么做的:

import inspect
def parse_func_args(func, args):
f_argspec = inspect.getargspec(func)
f_all_args = f_argspec.args
f_optional_args_num = len(f_argspec.defaults or [])
f_optional_args = set() if not f_optional_args_num else set(
f_all_args[-f_optional_args_num:])
f_required_args = set(f_all_args) - f_optional_args
u_provided_args = set(args.keys())
if not f_required_args.issubset(u_provided_args):
raise Exception("Parameters '%s' missing" %
', '.join(f_required_args - u_provided_args))
return {k: args[k] for k in set(f_all_args) & u_provided_args}
def sum(a, b, c=10):
return a + b + c
print parse_func_args(sum, {'a': 1, 'b': 2}) # {'a': 1, 'b': 2}
print parse_func_args(sum, {'a': 1, 'b': 2, 'c': 3}) # {'a': 1, 'c': 3, 'b': 2}
print parse_func_args(sum, {'a': 1, 'c': 3}) # Exception: Parameters 'b' missing

通过parse_func_args可以判断request的参数和我们需要的参数是否匹配, 如果不匹配的话就不用往下跑, 如果匹配的话, 实际中只需要把参数作为一个dict往下传即可.

获取request中所有参数

在一个flask的request中, 参数主要分为如下几种类型

type e.g position
url /user/< user_id > request.view_args
query string /user?page=1 request.args
json body {‘email’: ‘abc@example.com’} request.json
from body FormData request.form
file body FormData request.files

所以在验证并且call我们自己的route函数之前, 就需要简单的把上面提到的参数merge起来

from flask import request
def collect_request_args():
args = dict(request.view_args)
args.update(request.args.iteritems())
if request.method in {'POST', 'PUT'}:
args.update(request.files.to_dict())
args.update(request.form.to_dict())
if hasattr(request, 'json'):
args.update(request.get_json() or {})
return args

总结

OK, 基础的code已经都准备好了, 一个完整的例子如下:

import inspect
import functools
from flask import Flask, request, abort
app = Flask('my-app')
def collect_request_args():
args = dict(request.view_args)
args.update(request.args.iteritems())
if request.method in {'POST', 'PUT'}:
args.update(request.files.to_dict())
args.update(request.form.to_dict())
if hasattr(request, 'json'):
args.update(request.get_json() or {})
return args
def validate_func_args(func, args):
f_argspec = inspect.getargspec(func)
f_all_args = f_argspec.args
f_optional_args_num = len(f_argspec.defaults or [])
f_optional_args = set() if not f_optional_args_num else set(
f_all_args[-f_optional_args_num:])
f_required_args = set(f_all_args) - f_optional_args
u_provided_args = set(args.keys())
if not f_required_args.issubset(u_provided_args):
abort(400, "Parameters '%s' missing" %
', '.join(f_required_args - u_provided_args))
return {k: args[k] for k in set(f_all_args) & u_provided_args}
def auto_fill_args(func):
@functools.wraps(func)
def f():
args = collect_request_args()
validate_func_args(func, args)
return func(**args)
return f
@app.route('/user/<int:user_id>', methods=['GET'])
@auto_fill_args
def get_user_id(user_id):
return 'user_id is %d' % user_id
@app.route('/user/create', methods=['POST'])
@auto_fill_args
def create_user(user_name, email, phone):
return 'user_info is %s, %s, %s' % (user_name, email, phone)
app.debug = True
app.run()

当然这里有一个问题的是, 中间使用了functools.wraps作为decorator的实现. 这个就作为之专门介绍decorator的一个讲解吧.

参考

  1. python inspect library

Transcrypt --- Encrypt Git

背景

在代码里面经常会有些敏感信息, 如db的配置, api-key等. 明文将这些信息保存在代码中肯定是不安全的, 如果将其统一/逐个保存在跟code无关的地方中, 也会存在着不安全, 缺乏版本控制及使用麻烦的问题.

最好的方式就是, 将这个文件encrypt, 只有配置了能decrypt的key, 才能解开这些配置信息.

git-cryptTranscrypt就是用来做这件事情, 前者需要单独编译安装, 后者只是一个独立的python shell脚本. So, 这里选择的就是后者.

其他的特性可以参考其Github README的介绍, 如OpenSSL.

下载/安装

# 从github上下载原文件
wget https://raw.githubusercontent.com/elasticdog/transcrypt/master/transcrypt && chmod +x transcrypt
mv transcrypt /usr/local/bin
# 或者mac下使用Homebrew安装
brew install transcrypt

配置

encrypt新的repo

cd <path-to-your-repo>/
transcrypt
# 可以看到repo encrypt的状态, 包括密码, 还会提示你如何decrypt一个已有的repo
transcrypt --display

按照提示来, 中间会要求输入你的密码, 这个密码最后也会出现在.git/config中, 也就是说只有.git中配置了相关的信息, 才能够正确的decrypt.

执行完之后, 它会创建一个.gitattributes文件(如果没有的话), 里面用来指定哪些文件是被encrypt的. pattern匹配文件, 匹配模式和.gitignore一样.

默认内容是:

$ cat .gitattributes
#pattern filter=crypt diff=crypt

假设有一个secret.yml文件需要被encrypt, 只需要执行, 很简单吧

echo 'secret.yml filter=crypt diff=crypt' >> .gitattributes
git add .gitattributes secret.yml
# commit时文件会自动被encrypt
git commit

commit后可以执行下面的命令

# 列出当前哪些文件被encrypt
transcrypt --list
# 查看文件被decrypt之前的raw data
transcrypt -s secret.yml
# rekey 更换密码同时
transcrypt -r

至此, 完整的code repo中的敏感信息就已经完全的保存了

decrypt已有的repo

如果要decrypt一个repo, 只需要执行下面这句即可

cd <path-to-your-repo>/
transcrypt -c aes-256-cbc -p 'your-password'

原理

.gitattributes

如果你了解.gitattributes就可以忽略这段内容, 也可以直接参考https://git-scm.com/book/en/v2/Customizing-Git-Git-Attributes

git可以指定自定义的文件属性, 那么这个文件在被执行相应操作时可以执行特定的逻辑
比如:

  • pattern-file diff=func
    diff=func: 在执行git diff时先执行func后在去执行真正的diff, 这个可以用来diff word文档(使用docx2txt转换成txt)
  • pattern-file export-ignore
    export-ignore: 在执行git archive时忽略该文件(夹)

.git/config

其实他就加下了下面这段配置, 主要是定义了crypt的filter和diff, 这个在.gitattributes中被用到

[filter "crypt"]
clean = \"$(git rev-parse --show-toplevel)\"/.git/crypt/clean %f
smudge = \"$(git rev-parse --show-toplevel)\"/.git/crypt/smudge
[diff "crypt"]
textconv = \"$(git rev-parse --show-toplevel)\"/.git/crypt/textconv
[merge]
renormalize = true
[alias]
ls-crypt = "!git ls-files | git check-attr --stdin filter | awk 'BEGIN { FS = \":\" }; /crypt$/{ print $1 }'"

参考

  1. Transcrypt
  2. Customizing-Git-Git-Attributes

AngularJs Nginx的配置

最近为一个angular的project配置Nginx时踩了些坑, 在此mark一下.

定义

proxy_pass: 就是域名的代理, 将匹配的url请求转发到对应的domain中, 简单用法如下:

location ^~ /api/ {
proxy_pass http://example.com/mock-api;
}

rewrite: 重写url, 支持正则匹配和分组提取, 简单用法如下:

rewrite /api/(.+)$ /mock-api/$1 break;

rewrite 有几种不同的flag: last, break等. 常用的就这两个.

背景

Angular中的请求规则一般很简单, 主要分为三类:

  1. index.html
  2. 各种js, css等static资源
  3. api

先介绍下背景, 一开始api-server负责所有的请求和资源, nginx的配置也就非常简单, 就是将所有的请求都转给api-server中.

简化版的配置如下:

location @server {
proxy_pass http://localhost:8080;
}
location /api {
allow 176.168.2.100;
try_files $url @server;
}
location /static {
expires 86400;
try_files $url @server;
}
location / {
expires -1;
try_files $url @server;
}

不过基于性能的考虑, 要将index.htmlstatic资源都放在cdn中, api-server就只负责api那一部分. 所以nginx的配置变成了这样:

先提前说下, 下面的配置里面有两个bug

location /api {
allow 176.168.2.100;
proxy_pass http://localhost:8080;
}
location /static {
expires 86400;
rewrite ^/.*$ /;
proxy_pass http://cdn/prod/static;
}
location / {
expires -1;
rewrite ^/.*$ /index.html;
proxy_pass http://cdn/prod;
}

bug1: rewrite 匹配循环

好吧, 这里一开始是不清楚rewrite flag的区别

rewrite ^/.*$ /index.html;
// 会rewrite后重新进行location的匹配
// 也就是说 `location /`会一次一次的匹配自己, 导致nginx报错
// 所以应该改成下面这样, break表明不再进行location的匹配
rewrite ^/.*$ /index.html break;

bug2: proxy_pass与rewrite一起忽略后缀

// 单独一个proxy_pass, 所有请求都会转到 http://cdn/prod
location / {
proxy_pass http://cdn/prod;
}
// 按照这个的预期, 所有请求都会转到 http://cdn/prod/index.html
location / {
rewrite ^/.*$ /index.html break;
proxy_pass http://cdn/prod;
}

但事实并非如此, 实际中所有的请求都被转到了 http://cdn/index.html, rewriteproxy_pass一起, proxy_pass就只认host部分

location / {
rewrite ^/.*$ /prod/index.html break;
proxy_pass http://cdn;
}

这个限制完全没有想到, 通过debug_log一步一步的调试出来, 也是非常的无奈, 所以, proxy_pass比较好的使用方式就是只proxy domain比较好.

fixed: 正解

所以正解应该为:

location /api {
allow 176.168.2.100;
proxy_pass http://localhost:8080;
}
location /static {
expires 86400;
rewrite ^/.*$ /;
proxy_pass http://cdn/prod/static;
}
location / {
expires -1;
rewrite ^/.*$ /prod/index.html;
proxy_pass http://cdn;
}

参考

  1. Official Reference
  2. nginx配置location总结及rewrite规则写法

Designate配置及部署

Designate目前还在开发过程中, 且文档资料比较匮乏, 此处的介绍的内容就相对比较片面和不是那么严整了, 若有相应的错误,也请包涵并及时指正。

Designate的工作原理

此处有一张图,用来描述designate的运行流程:

gif动态图片, 参考Designate-MiniDNS-Pools

  1. 用户请求designate-api,添加record或者domain
  2. designate-api发送请求至mq中
  3. designate-central接收到mq请求,写入db,同时通过mq触发pool_manager进行更新操作
  4. pool_manager通过rndc(addzone/delzone/notifyzone)三个操作来通知pool_targets中定义的bind来进行操作
  5. bind使用axfr来请求同步mdns
  6. mdns从数据库中读取相应的domain信息来响应axfr请求

Designate的安装

此处只描述和介绍其中一种可用的部署模式(其应该具有多种部署模式, 此处就没有深究了)

架构如图(DB,MQ略):

按照目前个人的理解, 在kilo版本种, 一个pool_manager进程就管理一个pool, pool中可以指定多个dns-server. 但不一定准确

安装环境:

  • 系统为Ubuntu 12.04
  • designate-api, designate-central, designate-pool-manager, designate-mdns部署在 172.16.2.100
  • bind分别部署在172.16.2.101, 172.16.2.102, 172.16.2.103
  • 测试domain为lnmpy.com
  • 使用kilo版本的designate

配置bind & rndc(只以安装Bind-A为例, 其余类推)

安装 bind

apt-get install bind9 -y

修改/etc/bind/named.conf.options内容为:

options {
        directory "/var/cache/bind";
        dnssec-validation auto;
        auth-nxdomain no;
        allow-new-zones yes;  # 此配置必须加上, 其允许rndc进行zone的相关操作
        listen-on { <your-ip>; };  # 表面bind的53端口是监听在这个网络中
        listen-on-v6 { any; };
};

controls {
        inet 172.16.2.101 port 953
                allow { 172.16.2.100; } keys { "rndc-key"; };
};

# 以下内容来自: rndc-confgen
key "rndc-key" {
        algorithm hmac-md5;
        secret "jmED6H54nY+DD/SRJG6Okw==";
};

修改/etc/bind/rndc.key内容为(secret值保持与named.conf.options中一致):

key "rndc-key" {
        algorithm hmac-md5;
        secret "jmED6H54nY+DD/SRJG6Okw==";
};

172.16.2.100中调用以下命令进行测试:

rndc -s 172.16.2.101 -p 953 -k /etc/bind/rndc.key addzone lnmpy.com '{ type slave;masters { 172.16.2.100 port 5354;}; file "slave.lnmpy.com.ba4dbff3-a32f-4f54-bb7c-68710f7935a5"; };'

如果没有报错且172.16.2.101机器中的/var/cache/bind/出现了slave.lnmpy.com.ba4dbff3-a32f-4f54-bb7c-68710f7935a5, 则表明bind配置成功.

配置Designate(以全部安装在同一个机器上为例)

安装MySql,RabbitMq, 配置从略:

apt-get install -y rabbitmq-server mysql-server python-dev libmysqlclient-dev

git clone git://github.com/openstack/designate designate
cd designate
git checkout stable/kilo # checkout出指定版本
pip install virtualenv
virtualenv .venv
source .venv/bin/activate
sed -i 's/oslo.config>=1.9.3,<1.10.0  # Apache-2.0/oslo.config>=1.9.3,<=1.11.0  #Apache-2.0/' requirements.txt
pip install -r requirements.txt -i http://pypi.douban.com/simple  # 视网络情况可能会有超时失败多次,重复运行一次就行
while [ $? != 0 ]
do
pip install -r requirements.txt -i http://pypi.douban.com/simple
done
pip install mysql-python functools32 -i http://pypi.douban.com/simple
python setup.py install
cp -R etc/designate /etc/
ls /etc/designate/*.sample | while read f; do sudo cp $f $(echo $f | sed "s/.sample$//g"); done
mkdir /var/log/designate/ /var/cache/designate

修改/etc/designate/designate.conf

[DEFAULT]
verbose = True
debug = True
state_path = /var/lib/designate
logdir = /var/log/designate
notification_driver = messaging
notification_topics = notifications

# 默认的quota值, 按需设置
quota_domains = 100000
quota_domain_recordsets = 100000
quota_domain_records = 100000
quota_recordset_records = 100000

[oslo_messaging_rabbit]
rabbit_userid = guest # 默认不配置RabbitMq的话是guest, 建议修改
rabbit_password = guest
rabbit_virtual_host = /
rabbit_use_ssl = False
rabbit_hosts = 127.0.0.1:5672
[service:central]
[service:api]
auth_strategy = noauth # 此处为了便利, 关闭了auth认证
enable_api_v1 = True
enabled_extensions_v1 = sync, quotas
enable_api_v2 = True
[service:mdns]
threads = 1000
host = 0.0.0.0
port = 5354
tcp_backlog = 100
tcp_recv_timeout = 0.5
query_enforce_tsig = False
[service:pool_manager]
pool_id = 794ccc2c-d751-44fe-b57f-8894c9f5c842
[pool_manager_cache:sqlalchemy]
connection = mysql://root:r00t@127.0.0.1/designate_pool_manager
[storage:sqlalchemy]
connection = mysql://root:r00t@127.0.0.1:3306/designate
connection_debug = 0
[pool:794ccc2c-d751-44fe-b57f-8894c9f5c842]
nameservers = 0f66b842-96c2-4189-93fc-1dc95a08b012, 0f66b842-96c2-4189-93fc-1dc95a08b013
targets = f26e0b32-736f-4f0a-831b-039a415c481e, f26e0b32-736f-4f0a-831b-039a415c481f
[pool_nameserver:0f66b842-96c2-4189-93fc-1dc95a08b012]
port = 53
host = 172.16.2.101
[pool_target:f26e0b32-736f-4f0a-831b-039a415c481e]
options = rndc_host: 172.16.2.101, rndc_port: 953, rndc_key_file: /etc/bind/rndc.key
masters = 172.16.2.100:5354
type = bind9
port = 53
host = 172.16.2.101
[pool_nameserver:0f66b842-96c2-4189-93fc-1dc95a08b013]
port = 53
host = 172.16.2.102
[pool_target:f26e0b32-736f-4f0a-831b-039a415c481f]
options = rndc_host: 172.16.2.102, rndc_port: 953, rndc_key_file: /etc/bind/rndc.key
masters = 172.16.2.100:5354
type = bind9
port = 53
host = 172.16.2.102
[pool_nameserver:0f66b842-96c2-4189-93fc-1dc95a08b014]
port = 53
host = 172.16.2.103
[pool_target:f26e0b32-736f-4f0a-831b-039a415c4820]
options = rndc_host: 172.16.2.103, rndc_port: 953, rndc_key_file: /etc/bind/rndc.key
masters = 172.16.2.100:5354
type = bind9
port = 53
host = 172.16.2.103

初始化数据库

mysql -uroot -pr00t  -e 'drop database if exists designate; create database designate;'
mysql -uroot -pr00t  -e 'drop database if exists designate_pool_manager; create database designate_pool_manager;'

designate-manage database sync
designate-manage pool-manager-cache sync

启动Designate

# 在virtualenv中启动下面四个组件(无顺序要求)
designate-central
designate-api
designate-pool-manager
designate-mdns

如无报错,则表面正常启动, 再将其包装成upstart即可

Designate的API

此处使用了httpie作为客户端,也可以试用postmane或者curl

http 127.0.0.1:9001/v1/servers name=ns.lnmpy.com.

http 127.0.0.1:9001/v1/domains name=lnmpy.com. ttl:=3600 email=elvis@lnmpy.com

http 127.0.0.1:9001/v1/domains/{domain_id}/records name=www.lnmpy.com. type=A data=192.168.1.101
http 127.0.0.1:9001/v1/domains/{domain_id}/records name=mail.lnmpy.com. type=A data=192.168.1.102

dig @172.16.2.101 www.lnmpy.com
dig @172.16.2.102 www.lnmpy.com
dig @172.16.2.103 www.lnmpy.com

# 如果要创建PTR反向解析的话, 需要再单独创建一个domain
# 注:反向解析域必须以 in-addr.arpa. 结尾, designate只支持在 in-addr.arpa.的域中添加PTR记录
http 127.0.0.1:9001/v1/domains name=1.168.192.in-addr.arpa. ttl:=3600 email=elvis@lnmpy.com

http 127.0.0.1:9001/v1/domains/{ptr_domain_id}/records name=102.1.168.192.in-addr.arpa. type=PTR data=mail.lnmpy.com.

dig @172.16.2.101 -x 192.168.1.101
dig @172.16.2.102 -x 192.168.1.101
dig @172.16.2.103 -x 192.168.1.101

可以看到,通过dig测试,返回的结果表面3台bind-server均可(包括反向解析)解析www.lnmpy.com.

Designate HA

MQ, DB的HA不必说了。

  • desigante-api只会涉及到读取DB和rpc调用designate-central, 所以使用nginx在多台机器上部署,都没有问题
  • desigante-central会被desigante-apirpc调用,但是有oslo.concurrency的存在,也只会有一个被调用到, 所以在不同的机器中部署,也是没有问题的。其被调用后会发送一条mq消息给designate-pool-manager
  • 由于mq的消息是独占性的,desigante-pool-manager之间的消息自然也不会发生抢占,部署多个自然也是允许的
  • desigante-mdns逻辑上来讲也是只读取db并且响应axfr请求, 只要pool中的bind能够实现multi-master即可

修改pool_target内容为:

[pool_target:f26e0b32-736f-4f0a-831b-039a415c4820]
...
masters = 172.16.2.100:5354, 172.16.2.200:5354   # masters列表
...

那么/var/cache/bind/3bf305731dd26307.nzf中对应的zone就变成了:

...
zone lnmpy.com { type slave; masters { 172.16.2.100 port 5354; 172.16.2.200 port 5354;}; file "slave.lnmpy.com.60987de9-97a1-4ecf-a124-3f148b21af78"; };
...

这样,当其中一个mdns down了之后,bind依然能够sync另一个mdns

参考

  1. Designate Developer Docs
  2. Installing Juno on Ubuntu
  3. Designate Rest API
  4. designate-overview-openstack
  5. Designate-MiniDNS-Pools
  6. DNS视图及bind中rndc的使用

RabbitMQ HA和LB的配置

rabbitmq是使用erlang开发的,集群非常方便,且天生就支持并发分布式,但自身并不支持负载均衡. 常规的博客介绍都是说rabbitmq有几种部署模式,其中最常用的也就两种:

  1. 单一模式: 就是不做ha…
  2. 镜像模式模式: active/active模式的ha,当master挂掉了,按照策略选择某个slave(其实就是最先加入集群的那个slave)来担当master的角色

下面就直接切入正题,在实际中该如何针对rabbitmq进行ha

配置网络环境

实验中使用三台机器(mq-cluster1,2,3)来进行部署实验, 系统均为ubuntu-14.04, amd64, 直接将以下内容其加到/etc/hosts文件中.

其中mq-cluster1为master,其余的为slave

10.22.129.57 mq-cluster1
10.22.129.58 mq-cluster2
10.22.129.59 mq-cluster3

安装rabbitmq-server

apt-get install -y rabbitmq-server

同步erlang.cookie文件

rabbitmq集群是依赖erlang的集群来工作的,所以要保证集群中每个rabbitmq的/var/lib/rabbitmq/.erlang.cookie内容是一样的.

我是以mq-cluster1为master的,所以需要将其内容覆盖到mq-cluster2mq-cluster3

service rabbitmq-server stop
# 你也可以选择其它方式来进行同步
scp root@mq-cluster1:/var/lib/rabbitmq/.erlang.cookie /var/lib/rabbitmq/.erlang.cookie
service rabbitmq-server start

设置ha模式

rabbitmqctl set_policy [-p <vhostpath>] [--priority <priority>] [--apply-to <apply-to>] <name> <pattern> <definition>

  • name 策略名称
  • pattern 正则表达式,用来匹配资源,符合的就会应用设置的策略
  • definition 是json格式设置的策略。
  • apply-to 表示策略应用到什么类型的地方,一般有queues,exchange和all,默认是all
  • priority 是个整数优先级

其中ha-mode有三种模式:

  • all: 同步至所有的.
  • exactly: 同步最多N个机器. 当现有集群机器数小于N时,同步所有,大于等于N时则不进行同步. N需要额外通过ha-params来指定.
  • nodes: 只同步至符合指定名称的nodes. N需要额外通过ha-params来指定.
// 这里设置的是同步全部的queue, 可以按需自己选择指定的queue
rabbitmqctl set_policy ha-all '.*' '{"ha-mode":"all"}'

cluster2,3加入集群

mq-cluster2mq-cluster3中分别执行:

rabbitmqctl stop_app
rabbitmqctl join_cluster rabbit@mq-cluster1
rabbitmqctl start_app

加入之后, 可以通过rabbitmqctl cluster_status来查看cluster状态.

ps:

默认加入是一disc模式加入,可以执行rabbitmqctl change_cluster_node_type <ram|disc>进行模式的修改

以上是在rabbitmq 3.中使用, 而在rabbitmq 2.中使用:

rabbitmqctl stop_app
rabbitmqctl force_cluster rabbit@mq-cluster1 # 不加自己的node_name, 是ram模式
rabbitmqctl force_cluster rabbit@mq-cluster1 rabbit@mq-cluster2 # 加自己的node_name, 是disc模式
rabbitmqctl start_app

测试rabbitmq的ha

测试的python代码,依次发送消息到三个rabbitmq-server中,

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pika
mq_servers = ['10.22.129.57', '10.22.129.58', '10.22.129.59']
mq_exchange = 'test_exchange'
queue_name = 'test'
routing_key = 'test.test'
message = 'msg'
for mq_server in mq_exchange:
try:
connection = pika.BlockingConnection(pika.ConnectionParameters(host=mq_server))
channel = connection.channel()
channel.exchange_declare(exchange=mq_exchange, type='topic', durable=True, auto_delete=False)
channel.queue_declare(queue=queue_name, durable=True, exclusive=False, auto_delete=False)
channel.queue_bind(exchange=mq_exchange, queue=queue_name, routing_key=routing_key)
channel.basic_publish(exchange=mq_exchange,
routing_key=routing_key,
body=message,
properties=pika.BasicProperties(content_type='text/plain',
delivery_mode=2)
)
channel.close()
connection.close()
print 'success: ' + mq_server
except:
print 'failed: ' + mq_server

可以随机的操作去关闭任意一个mq-cluster上的rabbitmq-server服务, 再通过rabbitmqctl list_queues来查看消息的数量. 可以看到,尽管master挂了,消息依然能够发送成功,且当挂掉的机器(master或者slave)重新起起来之后,消息会马上同步过去.

搭建haproxy

安装和初始配置haproxy此处就从略.

在配置好的/etc/haproxy/haproxy.cfg尾端加上以下内容

listen rabbitmq 0.0.0.0:56720
mode tcp
balance roundrobin
option tcplog
option tcpka
server rabbit1 192.168.100.67:5672 check inter 5000
server rabbit2 192.168.100.68:5672 check inter 5000
server rabbit3 192.168.100.69:5672 check inter 5000

接着启动haproxy

haproxy -f /etc/haproxy/haproxy.cfg -D

测试rabbitmq的haproxy下的lb

将之前的测试代码中的

mq_servers = ['10.22.129.57', '10.22.129.58', '10.22.129.59']

改成

mq_servers = ['10.22.129.53', '10.22.129.53', '10.22.129.53']

执行测试代码,发现三个消息均发送成功,然后即使手动关闭其中一台mq,消息依然发送成功,通过rabbitctl list_queues也依然可以看到消息是成功收到3条的.

至此,可以看到rabbitmq-server成功的解除了single-point状态.

参考

  1. rabbitmq-ha
  2. rabbitmqctl命令介绍
  3. 软件级负载均衡器(LVS/HAProxy/Nginx)的特点简介和对比

Dell R720 ipmi配置

IPMI是一个intel,hp,dell等提出的一个跨软硬件平台的工业标准,用户可以通过其来监控或获取服务器的状态,但是需要提前配置

R720的配置

着重说明一下版本信息为1.57, 对于这个配置,不同的版本界面及配置均有所区别

重启进去System Setup界面,选择iDRAC Settings:

进入后,先配置Network:

先后在这个页面中配置了:

  • 启动iDrac网卡
  • 设置idrac的ip
  • 启用ipmi


如果服务器启用了idrac卡,在Nic Selection可以看到这个Dedicated的专用网卡,否则只能看到LOM1,LOM2(为Lan Of MotherBoard的缩写)这样的配置。没有这个只是功能缩减了一点,其余的配置依然相同。

启用ipmi后可以通过ipmitool来进行远程管理了

配置完网络后,再配置User Configuration,只是配置一下用户名及密码即可:

ipmitool的安装及使用

# 先安装ipmitool
apt-get install -y openipmi ipmitool

# 启动openimpi服务,否则会报错
service openipmi start

接下来就直接执行了

# ipmitool -I <open|lan|lanplus> -U <user> -P <passwd> command
ipmitool -I open
ipmitool -I lanplus .. chassis power staus

这里面有个Interface是需要区分的,使用的不对则会报错。我就不解释了,直接粘出处.

在实际中我只碰到这个问题:Error: Unable to establish LAN session。 其有可能是由两个问题引起的:

  1. 在idrac配置中,没有启用impi
  2. openipmi服务想没有启动
  3. 不同的ipmi版本使用的Interface版本也不相同,尝试lanlanplus或者几个其他的Interface试一下

参考

  1. 使用 ipmitool 实现 Linux 系统下对服务器的 ipmi 管理
  2. Cobbler Dell ipmi 设置

disk-image-builder制作虚拟机镜像

简介

DiskImage-Builder是通过chroot到一个创建的临时目录中(默认在/tmp/image.*中可以看到),同时绑定系统的/proc, /sys, 和/dev 目录来配置(硬件资源)环境的.当提供好的脚本执行完成后,将该tmp目录内容打入到镜像文件中即完成了所有的制作过程.

OK,既然DiskImage-Builder提供了一个完整的执行环境,那么要定制一个满足自己需求的镜像,只需要按照其提供的格式完成几个element, 接着就biubiu地完成了一个自己的镜像了.

element

element是一堆符合特定名称的文件(主要为脚本)/文件夹的集合. 其主要包含以下的元素:

  • 用于执行脚本, 命名及存放的路径均有特殊含义(这些脚本中描述了在制作镜像的过程中需要执行哪些操作,比如安装好apache,创建用户等.)
  • 依赖描述
  • 描述文件, 不是强制的,但提供这个就相当于有一个良好的注释, 便于他人阅读和使用

element的执行脚本

DiskImage-Builder默认提供了一些基础的element,可以在源码目录中diskimage-builder/elements中看到. 其中执行的脚本,是按照目录划分的,按照顺序执行.每一个目录操作都有几个属性:

  • 其执行的环境,是否在chroot中
  • 输入变量
  • 输出变量

以下列出了其执行的目录, 执行按先后顺率来排列

操作 执行目录 接受变量 输出变量
root.d outside chroot $ARCH=(i386 amd64 armhf), $TARGET_ROOT={path} -
extra-data.d outside chroot $TMP_HOOKS_PATH -
pre-install.d in chroot - -
install.d in chroot - -
post-install.d in chroot - -
block-device.d outside chroot $IMAGE_BLOCK_DEVICE={path}, $TARGET_ROOT={path} $IMAGE_BLOCK_DEVICE={path}
finalise.d in chroot - -
cleanup.d outside chroot $ARCH=(i386 amd64 armhf), $TARGET_ROOT={path} -

element之间的依赖

element的依赖主要由两个文件来描述

  • element-deps: 描述该elements所依赖的element, 那么在执行该elment之前会先执行其依赖的element.

    如默认的element: ubuntu

    cache-url
    cloud-init-datasources
    dib-run-parts
    dkms
    dpkg

    也就是说在执行elementubuntu之前会先去执行如dkms,dpkg,cache-url等所依赖的element.

  • element-provides: 描述该elements额外提供哪些element的功能, 也就说若执行该element,那么其额外提供的element就不会在执行.

    依然拿ubuntu来举例:

    operating-system

    这个就说明,若选择了ubuntu,则不会执行名为operating-system的element了

其实到这里,基本就已经了解了一个element的作用了,只要看一下相关的一两个例子,就可以写出满足自己定制需求的element脚本.

配置diskimage-builder

# 下载diskimage-builder,默认已有一些常用的element
git clone https://github.com/openstack/diskimage-builder.git

# 以下两个则是配置openstack需要涉及的一些element,可以自行参考和使用
git clone https://github.com/openstack/tripleo-image-elements
git clone https://github.com/openstack/heat-templates

绿色无需安装,下载即可使用. 当然需要安装qemu-utils和物理内存大于4G.

运行环境变量

以创建ubuntu为例

必选环境变量

# 指定版本
export DIB_RELEASE=precise|trusty

# 指定引用的elements路径
export ELEMENTS_PATH=elements_path1:element_path2

可选环境变量

# 指定image的源,比如ubuntu走的是这个
# 主要是用来优化下载系统镜像速度的
export DIB_CLOUD_IMAGES=https://cloud-images.ubuntu.com/

# 制定apt-source的源, 相当于使用自定义好的文件去替换
# 要使用到element: apt-sources
export DIB_APT_SOURCES=/opt/apt-source.list.${DIB_RELEASE}

运行参数

# 指定image-cache的缓存路径
# 默认是:~/.cache/image-create
--image-cache

# 设置不更新已存在的image-cache
# 其实也就是先判断一下sha256md5是否匹配,不匹配的情况下这个参数才有作用
# 默认是如有不同则进行uopdate
--offline

# 指定目标平台的系统版本
-a amd64|i386

# 输出镜像的名称
-o filename

运行示例

# 生成ubuntu-precise镜像
export ELEMENTS_PATH=elements
export DIB_RELEASE=precise
export DIB_APT_SOURCES=apt-source.list.${DIB_RELEASE}
diskimage-builder/bin/disk-image-create vm ubuntu apt-sources custom-script -a amd64 -o ubuntu-precise

# 将镜像上传到glance中
glance image-create --name="ubuntu-$DIB_RELEASE" --disk-format=qcow2 --container-format=bare --is-public=true < ubuntu-$>

参考

  1. Using Diskimage Builder for Heat Deploying Applications
  2. diskimages-heaticehousesummit

pypi本地源的搭建

介于Python的跨平台性,所以这里就不描述我本地的环境了

安装pypimirror

首先安装pypimirror

pip install z3c.pypimirror

安装的过程中会会出现BeautifulSoup版本不匹配的报错

Downloading/unpacking BeautifulSoup<=3.0.9999 (from z3c.pypimirror)
Could not find a version that satisfies the requirement BeautifulSoup<=3.0.9999 (from z3c.pypimirror) (from versions: 3.2.0, 3.2.1)
Cleaning up...
No distributions matching the version for BeautifulSoup<=3.0.9999 (from z3c.pypimirror)
Storing debug log for failure in /home/topgear/.pip/pip.log

/usr/local/lib/python2.7/dist-packages/z3c.pypimirror-1.0.16-py2.7.egg/EGG-INFO/requires.txt文件修改一下即可. (这里我是ubuntu的,具体的发行版的配置可能会不一样)

其中有一行是: BeautifulSoup<=3.0.9999, 将版本号去掉,即改成:BeautifulSoup. 改完之后再重新执行上面的安装命令即可.

配置pypimirror.cfg

配置pypimirror.cfg文件,保存路径无所谓

[DEFAULT]
# 镜像文件的本地存放路径
mirror_file_path = /opt/pypi/

# 远程镜像的url
base_url =  http://0.0.0.0/pypi

# 日志文件
log_filename = /var/log/pypi-mirror.log

# 防止重复运行的锁文件
lock_file_name = /tmp/pypi-mirror.lock

# days to fetch in past on update
fetch_since_days = 1

# 需要进行镜像拷贝的文件类型,不在列表中的则不进行拷贝
filename_matches =
    *.zip
    *.tgz
    *.egg
    *.tar.gz
    *.tar.bz2

# 需要进行镜像拷贝的文件名称,不在列表中的则不进行拷贝
# 默认拷贝全部,上面已经删选了类型
package_matches =
    *

# 删除本地有而服务器上没有的包
# 默认是True,会进行删除的,此处我调整为False
cleanup = False

# 创建索引文件
create_indexes = True

# 显示相信信息
verbose = True

拷贝远程镜像

执行pypimirror -h 可以看到pypimirror的帮助信息

-I: 初始化镜像

-U: 更新镜像(当已经初始化过了,只需要进行同步的更新)

-i: 为本地文件创建索引

-c: 同时输出到console中

-v: 显示详细信息

pypimirror -c -v -I pypimirror.cfg # 第一次需要初始化
pypimirror -c -v -U pypimirror.cfg # 如果已经存在本地镜像了则可以进行更新

创建本地索引

pypimirror -c -v -i pypimirror.cfg

整合apache

其实就是简单的执行一个软链接而已

ln -sf /opt/pypi /var/www/pypi

使用本地源

使用镜像源很简单,用-i指定就行了:

easy_install -i http://127.0.0.1/pypi/ django
pip install -i http://127.0.0.1/pypi/ django

当然实际中你会发现有些源即使你指定了第三方的源(本地源), 它竟然还是从官方的源去下载了, so(= ̄ω ̄=), 改下pip或者easy_install的源码,替换掉代码中hardcode的官方源吧.

参考

  1. 搭建本地pypi服务器
  2. Setting up a PyPI mirror

Proudly powered by Hexo and Theme by Hacker
© 2018 Elvis Macak