织网

身体和灵魂,总有一个在路上

Ansible Dynamic Inventory

| Comments

Ansible 在使用的过程中,如果机器数量比较固定,且变更不多的情况下,可在 /etc/ansible/hosts 文件里面配置固定的组合机器IP, 并给他起组的别名,执行 Ansible 脚本便可以通过别名找到相应的机器。

1
2
[webservers]
111.222.333.444 ansible_ssh_port=888

假如你有很多台机器,且机器经常变更导致IP时常变换,你还想把IP逐个写入 /etc/ansible/hosts 就不现实了。你也许会问,若不把 IP 写进 /etc/ansible/hosts,那不是没法用 Ansible 指挥这些机器? 感谢 Ansible Dynamic Inventory, 如果我们能通过编程等手段获取变更机器的IP,我们还是有办法实现的。

Dynamic Inventory 的原理

  • 通过编程的方式,也就是动态获取机器的 json 信息;
  • Ansible 通过解析这串 json 字符串;
1
ansible -i yourprogram.py -m raw  -a 'cd /home'

Ansible Dynamic Inventory 对程序返回的 json 的转义是这样的:

1
{"devtest-asg": {"hosts": ["172.31.21.164"], "vars": {"ansible_ssh_port": 12306}}}

翻译一下就是 /etc/ansible/hosts 中的:

1
2
[devtest-asg]
172.31.21.164 ansible_ssh_port=12306

一个实战的例子

官方文档对 Inventory 仅作概念性描述,阅读完后仍是一头雾水,不知如何下手。 让我们用一个例子来豁然开朗吧。 我们使用 AWS 的 AutoScaling Group,以下简称 ASG,ASG 会在某种自定义的条件下会自动开启和关闭机器,这给我们在辨别IP,定位机器的时候造成困扰。因此我们需要 Ansible Dynamic Inventory

我们使用 AWS 的 boto 库来获取 ASG 的实例信息.以下程序(get_host.py)中要实现的方法就是列出返回机器信息的 json 串。

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import json
import boto
import boto.ec2
import boto.ec2.autoscale

AWS_REGION = 'BBB'
AWS_ACCESS_KEY = 'xxxx'
AWS_SECRET_KEY = 'yyy'

result = {}
def getData():
    conn_as = boto.ec2.autoscale.connect_to_region(
            'cn-north-1',
            aws_access_key_id=AWS_ACCESS_KEY,
            aws_secret_access_key=AWS_SECRET_KEY)
    group = conn_as.get_all_groups(names=['devtest-asg'])[0]
    conn_ec2 = boto.ec2.connect_to_region(
            AWS_REGION,
            aws_access_key_id=AWS_ACCESS_KEY,
            aws_secret_access_key=AWS_SECRET_KEY)

    instance_ids = [i.instance_id for i in group.instances]
    reservations = conn_ec2.get_all_instances(instance_ids)
    instances = [i for r in reservations for i in r.instances]

    result['devtest-asg'] = {}
    result['devtest-asg']['hosts'] = []
    for r in reservations:
        for i in r.instances:
            result['devtest-asg']['hosts'].append('%s' % i.private_ip_address)
            result['devtest-asg']['vars'] = {'ansible_ssh_port': 36000}

def getlists():
    getData()
    print json.dumps(result)

getlists()

执行以下命令就可以愉快地使用 Ansible 了,其中 devtest-asg 是 ASG 的别名:

1
ansible -i get_host.py  devtest-asg -m raw -a 'ls /'

Flume 实时收集 Nginx 日志

| Comments

在分布式系统中,各个机器都有程序运行的本地日志,有时为了分析需求,不得不这些分散的日志汇总需求,相信很多人会选择 Rsync,Scp 之类, 但它们的实时性不强,而且也会带来名字冲突的问题。扩展性差强人意,一点也不优雅。

现实中,我们就碰到了这样的需求:实时汇总线上多台服务器的 Nginx 日志。Flume 立功了。

Flume 简介

Flume 是一个分布式,可靠高效的日志收集系统,它允许用户自定义数据传输模型,因此可扩展性也强。也有较强的容错和恢复机制. 以下是几个重要的概念

  • Event:Event 是 Flume 数据传输的基本单元。flume 以事件的形式将数据从源头传送到最终的目的。
  • Agent:Agent包含 Sources, Channels, Sinks 和其他组件,它利用这些组件将events从一个节点传输到另一个节点或最终目的。
  • Source:Source负责接收events,并将events批量的放到一个或多个Channels。
  • Channel:Channel位于 Source 和 Sink 之间,用于缓存进来的events,当Sink成功的将events发送到下一跳的channel或最终目的,events从Channel移除。
  • Sink:Sink 负责将 events 传输到下一跳或最终目的,成功完成后将events从channel移除。

  • Source 就有 Syslog Source, Kafka Source,HTTP Source, Exec Source Avro Source 等。
  • Sink 有 Kafka Sink, Avro Sink, File Roll Sink, HDFS Sink 等。
  • Channel 有 Memory Channel,File Channel 等

它提供了一个骨架,以及多种 Source, Sink, Channel, 让你设计合适的数据模型。事实上也可以多个 Flume 联动完成,就像地铁的车厢一样。

定义数据流模型

回到我们开头的场景,我们要将多台服务器的 Nginx 日志进行汇总分析,

分成两个 flume 来实现

  • Flume1 数据流是 Exec Source -> Memory Channel -> Avro Sink,部署在业务机器上
  • Flume2 数据流是 Avro Source -> Memory Channel -> FileRoll Sink

需要的准备

你需要安装

  • 下载 Flume
  • 安装 JavaSDk,并在下载解压之后的 conf/flume-env.sh,配置
1
2
# 我用的是oracle-java-8
export JAVA_HOME=/usr/lib/jvm/java-8-oracle/jre/
  • 思考你的数据流动模型,编写配置,如上文所说的Flume1, tail2avro.conf :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
agent.sources = s1
agent.channels = c1
agent.sinks = k1

agent.sources.s1.type=exec
agent.sources.s1.command=tail -F <Your File Path>
agent.sources.s1.channels=c1

agent.channels.c1.type=memory
agent.channels.c1.capacity=10000
agent.channels.c1.transactionCapacity=10000

agent.sinks.k1.type = avro
agent.sinks.k1.hostname = <Your Target Address>
agent.sinks.k1.port = <Your Target Port>
agent.sinks.k1.channel=c1

Flume2 中的 avro2file.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
agent.sources = s1
agent.channels = c1
agent.sinks = k1

agent.sources.s1.type = avro
agent.sources.s1.bind = <Your Address>
agent.sources.s1.port = <Your Port>
agent.sources.s1.channels = c1

agent.sinks.k1.type = file_roll
agent.sinks.k1.sink.directory = /data/log/ngxlog
# 滚动间隔
agent.sinks.k1.sink.rollInterval = 86400
agent.sinks.k1.channel = c1

agent.channels.c1.type = memory
# 队列里 Event 的容量
agent.channels.c1.capacity = 10000
agent.channels.c1.transactionCapacity = 10000
agent.channels.c1.keep-alive = 60
  • 启动运行
1
2
3
4
5
6
7
# 启动flume1
bin/flume-ng agent -n agent -c conf -f conf/tail2avro.conf \
-Dflume.root.logger=WARN

# 启动flume2
in/flume-ng agent -n agent -c conf -f conf/avro2file.conf \
-Dflume.root.logger=INFO

参考

Redis 该选择哪种持久化配置

| Comments

这个标题或许会让你想起《黑客帝国》里经典的台词,你要选择蓝色药丸,还是红色药丸?

Redis 是我们重度使用的一个开源软件,对它的持久化配置做一番相对深入的总结,是值得的。目前它有两种主流的持久化存储方式 SnapShot 以及 AOF 。

什么是 Snapshot

Snapshot 将内存中数据以结构化的方式序列化到 rdb 文件中,是默认的持久化方式,便于解析引擎快速解析和内存实施。快照得由间隔时间,变更次数同时符合才会触发, 该过程中并不阻塞客户端请求,copy-on-write 方式也意味着极端情况下可能会导致实际数据2倍内存的使用量。它首先将数据写入临时文件,结束后,将临时文件重名为 dump.rdb。可以使用 redis-check-dump 用来检测完整性

只有快照结束后才会将旧的文件替换成新的,因此任何时候 RDB 文件都是完整的。如果在触发 snapshot 之前,server 失效。会导致上一个时间点之后的数据未能序列化到 rdb 文件,安全性上稍弱。

我们可手动执行 save 或 bgsave 命令让 redis 执行快照。两个命令的区别在于:

  • save 是由主进程进行快照操作,会阻塞其它请求;
  • bgsave 会通过 fork 子进程进行快照操作;

RDB 文件默认是经过压缩的二进制文件,占用的空间会小于内存中的数据,更加利于传输。设置如下,可以关闭快照功能

1
save ""

相关配置

1
2
3
4
5
6
7
8
# snapshot触发的时机,save <seconds> <changes>, 比如600秒有2个操作
save 600 2
# 当snapshot 时出现错误无法继续时,是否阻塞客户端变更操作 
stop-writes-on-bgsave-error yes 
# 是否启用rdb文件压缩,默认为 yes cpu消耗,快速传输  
rdbcompression yes  
# rdb文件名称  
dbfilename dump.rdb  

什么是 AOF

Append-only file,将 操作 + 数据 以格式化指令的方式追加到操作日志文件的尾部,在 append 操作返回后, 已经写入到文件或者即将写入,才进行实际的数据变更,日志文件保存了历史的操作过程;当 server 需要数据恢复时,可以直接回放此日志文件,即可还原所有的操作过程。 如果你期望数据更少的丢失,那么可以采用 AOF 模式。可以用 redis-check-aof 检测文件是否完整。

AOF 就是日志会记录变更操(例如:set/del等),会导致AOF文件非常的庞大,意味着server失效后,数据恢复的过程将会很长;事实上,一条数据经过多次变更,将会产生多条AOF记录,其实只要保存当前的状态,历史的操作记录是可以抛弃的, 由此催生了 AOF ReWrite。

什么是 AOF Rewrite

其实是压缩 AOF 文件的过程,Redis 采取了类似 Snapshot 的方式:基于 copy-on-write,全量遍历内存中数据,然后逐个序列到 aof 文件中。因此 AOF Rewrite 能够正确反应当前内存数据的状态, Rewrite 过程中,新的变更操作将仍然被写入到原 AOF 文件中,同时这些新的变更操作也会被收集起来, 并不阻塞客户端请求。

相关配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
##只有在yes下,aof重写/文件同步等特性才会生效  
appendonly no  
  
##指定aof文件名称  
appendfilename appendonly.aof  
  
##指定aof操作中文件同步策略,有三个合法值:always everysec no,默认为everysec  
appendfsync everysec  

##在aof-rewrite期间,appendfsync 是否暂缓文件同步,no 表示不暂缓,yes 表示暂缓,默认为no  
no-appendfsync-on-rewrite no  
  
##aof文件rewrite触发的最小文件尺寸 只有大于此aof文件大于此尺寸是才会触发rewrite,默认64mb,建议512mb  
auto-aof-rewrite-min-size 64mb  
  
##相对于上一次rewrite,本次rewrite触发时aof文件应该增长的百分比
auto-aof-rewrite-percentage 100  

appendfsync 方式:

  • always:每一条 aof 记录都立即同步到文件,这是最安全的方式,但是更多的磁盘操作和阻塞延迟,IO 开支较大。
  • everysec:每秒同步一次,性能和安全也是redis推荐的方式。如果服务器故障,有可能导致最近一秒内aof记录丢失。
  • no:redis并不直接调用文件同步,而是交给操作系统来处理,操作系统可以根据buffer填充情况等择机触发同步;性能较好,在物理服务器故障时,数据丢失量会因OS配置有关。

选择哪种药丸

  • AOF更安全,可将数据及时同步到文件中,但需要较多的磁盘IO,AOF文件尺寸较大,文件内容恢复相对较慢, 也更完整。
  • Snapshot,安全性较差,它是正常时期数据备份及 master-slave 数据同步的最佳手段,文件尺寸较小,恢复数度较快。

主从架构的环境下的选择

  • 通常 master 使用AOF,slave 使用 Snapshot,master 需要确保数据完整性,slave 提供只读服务.
  • 如果你的网络稳定性差, 物理环境糟糕情况下,那么 master, slave均采取 AOF,这个在 master, slave角色切换时,可以减少时间成本;

实时监控 Nginx Qps 的拓展

| Comments

用下班时间写了一个 ngx lua 的拓展, GitHub。可以:

  • [x] 实时监控域名的 qps
  • [x] 实时监控域名的 5xx 个数
  • [x] 实时监控域名的 响应时长
  • [x] 并附带 Ganglia 监控插件

使用

  • 配置 nginx.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
http {
    ...
    ...

    lua_shared_dict statics_dict    1M; # 初始化变量
    lua_package_path "/etc/nginx/ngx_lua_reqstatus/?.lua";  #路径
    log_by_lua_file "/etc/nginx/ngx_lua_reqstatus/hook.lua"; # 添加此句

    server {
        listen 80;
        server_name  justforfun.com; 

        location /{
            ...
        }
    }
    # 监控服务
    server {
        listen 127.0.0.1:6080;
        location /{
            access_by_lua_file "/etc/nginx/ngx_lua_reqstatus/status.lua";
        }
    }
}
  • 效果
1
curl localhost:6080/?domain=justforfun.com
  • 输出
1
2
3
4
5
6
Server Name:    justforfun.com
Seconds SinceLast:   1.4399998188019 secs
Request Count:      1
Average Req Time:   0 secs
Requests Per Secs:  0.69444453182781
5xx num:    0

Nginx 日志利器 GoAccess

| Comments

我们经常需要从 Nginx 日志分析得出很多有价值的东西,分析的方法是各种 shell, awk, python, 现在 GoAccess 给你另外一种选择, 值得拥有。

  • 安装 用以下的方式安装,才能得到新版的 GoAccess, 功能更健全
1
2
3
4
$ echo "deb http://deb.goaccess.io $(lsb_release -cs) main"|sudo tee -a /etc/apt/sources.list
$ wget -O - http://deb.goaccess.io/gnugpg.key | sudo apt-key add -
$ sudo apt-get update
$ sudo apt-get install goaccess
  • 推荐配置

安装完成之后,会生成一份 /etc/goaccess.conf 稍作编辑,这就是默认的配置,免去了后续每次都要定义格式

1
2
3
time-format %T   # 只有这种方式才能解决 0.0us 的显示问题
date-format %d/%b/%Y
log-format %h %^[%d:%t %^] "%r" %s %b "%R" "%u" %T
  • 使用

输出报表,报表中,我们可以看到最常访问的 IP, 接口,以及每个接口使用带宽,平均响应时长,状态码等,对业务分析有较好的便利性

1
2
3
4
5
6
7
8
9
10
11
终端显示
goaccess -f access.log 
 
输出报表,报表中,我们可以看到top ip, 接口,以及接口使用带宽,平均响应时长,状态码等
goaccess -f access.log > report.html
 
具体某段时间的输出
sed -n '/5\/Dec\/2015/,/10\/Dec\/2010/ p' access.log | goaccess -a
 
处理已经压缩的日志
zcat access.log.*.gz | goaccess

Gre 隧道与 Keepalived

| Comments

这一篇文章是做了不少功课的。

什么是 Gre 隧道

GRE 隧道是一种 IP-2-IP 的隧道,是通用路由封装协议,可以对某些网路层协议的数据报进行封装,使这些被封装的数据报能够在 IPv4/IPv6 网络中传输。Tunnel 是一个虚拟的点对点的连接,提供了一条通路使封装的数据报文能够在这个通路上传输,并且在一个Tunnel 的两端分别对数据报进行封装及解封装。Linux 上创建 GRE 隧道,需要 ip_gre 内核模块,它是Pv4 隧道的驱动程序。

假设我有2台服务器,想做这两台之间创建 GRE 隧道

  • $server_A_ip 表示服务器A的IP
  • $server_B_ip 服务器B 的内网IP
1
2
3
4
5
6
7
8
9
10
11
# 在 A 机器上执行
# 创建 GRE 协议的 虚拟网络设备, 指定本地和对端 IP
sudo ip link add gretap1 type gretap local $server_a_ip remote $server_b_ip 
sudo ip link set dev gretap1 up  # 启动该设备
sudo ip addr add dev gretap1 10.1.1.2/24 # 给该设备一个虚拟网络地址

# 在 B 机器上执行
# 创建 GRE 协议的 虚拟网络设备, 指定本地和对端 IP
sudo ip link add gretap1 type gretap local $server_b_ip remote $server_a_ip 
sudo ip link set dev gretap1 up  # 启动该设备
sudo ip addr add dev gretap1 10.1.1.3/24 # 给该设备一个虚拟网络地址

如果想停止或者删除上述网卡

1
2
ip link set gretap1 down
ip tunnel del gretap1

至此点到点得隧道建立。

什么是 vrrp 协议

VRRP(Virtual Router Redundancy Protocol), 即虚拟路由冗余协议。是实现路由器高可用的容错协议。

即将N台提供相同功能的路由器组成一个路由器组,这个组里面有一个 master 和多个 backup, 但在外界看来就像一台一样,构成虚拟路由器,拥有一个虚拟IP(vip),占有这个IP 的 master 实际负责 ARP 相应和转发 IP 数据包, 组中的其它路由器作为备份的角色处于待命状态。 master 会发组播消息,当 backup 在超时时间内收不到 vrrp 包时就认为 master 宕掉了,这时就需要根据VRRP的优先级来选举一个backup当master,保证路由器的高可用。

Keepalived 是什么

Keepalived 是一个基于 VRRP 协议来实现的服务高可用方案,可以利用其来避免IP单点故障。

  • 一个经典的案例

一个WEB服务至少会有2台服务器运行 Keepalived,一台为主服务器,一台为备份服务器, 但是对外表现为一个虚拟IP,主服务器会发送特定的消息给备份服务器,当备份服务器收不到这个消息的时候,即主服务器宕机的时候,备份服务器就会接管虚拟IP,继续提供服务,从而保证了高可用性。

怎么玩 Keepalived

  • 安装
1
sudo apt-get install keepalived

之前提到的,我们在 A, B 两台服务器建立起了 GRE 隧道了。 现在我们有一个虚拟的内网IP, 姑且叫做 $virtual_third_ip 接着在 A 服务器上

  • 配置

编辑服务器 A, B 的 /etc/keepalived/keepalived.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

global_defs {
    router_id LVS_DEVEL
}

vrrp_instance VI_1 {
    state MASTER
    interface gretap1 # 绑在建立起来的隧道上
    virtual_router_id 51
    # 优先级别,越高的设备会被弄成主设备, A,B 服务器要有所差别,其他都一样
    priority 100          advert_int 1      # 广播时间间隔
    authentication {  #  验证相关
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        $virtual_third_ip dev eth0
    }
}

我们将服务器 A 作为 Master, 服务器 B 当做 Backup, 当服务器 A 需要停机的时候,$virtual_third_ip 就会漂移到服务器 B 上面。 我们两台机器都有相同配置的 Nginx 服务,就可以保障机器出现故障的时候,Nginx 服务丝毫不受影响。

附录

给 Tengine 加上 Lua 拓展

| Comments

Tengine 能动态加载第三方模块,成为我们青睐的选择,我们可以编译动态链接文件,而不需要重新安装 Nginx, 这对在线增强 webservice 很有帮助. 感谢 agentzh, lua-nginx-module, 可以让我们使用 lua 增强nginx的功能, 不言而喻,我们必须现有 Lua 的环境,才能运行 ngx_lua;

编译 nginx_lua

官方推荐使用LuaJit,虽然也可以使用Lua,但是即时编译(Just-In-Time Compiler)混合了动态编译和静态编译,一句一句编译源代码,但是会将翻译过的代码缓存起来以降低性能损耗。

  • 下载安装
1
2
3
4
5
wget http://luajit.org/download/LuaJIT-2.0.4.tar.gz
tar zxvf LuaJIT-2.0.4.tar.gz
cd LuaJIT-2.0.4
make
make install
  • 设置环境变量
1
2
export LUAJIT_LIB=/usr/local/lib
export LUAJIT_INC=/usr/local/include/luajit-2.0
  • 然后编译ngx-lua-module.so
1
/usr/local/share/dso_tool/ --path=/Path/To/Lua-Nginx-module
  • 设置动态库
1
2
> echo "/usr/local/lib" > /etc/ld.so.conf.d/usr_local_lib.conf
> ldconfig

在 Tengine 中启用

nginx.conf 中先加载动态库

1
2
3
dso {
    load ngx_load_module;
}

在 nginx.conf 中添加

1
lua_code_cache on/off;

来开启是否将代码缓存,所以每次变更 “*.lua” 文件时,必须重启 nginx 才可生效.

使用 ngx_lua_waf

有了基础环境,我们要开始发挥 ngx lua 的优点了, 我用他安装了 waf (web application firework) ngx_lua_waf,这是一个通过 ngx_lua 编写的 web 应用防火墙, 在使用过程中也发现了 ngx_lua_waf 一个bug,给他提了一个Pull Request, 码农生涯第一个 PR.


注: 静态编译的程序在执行前全部被翻译为机器码,而动态直译执行的则是一句一句边运行边翻译。

记录使用 Flask 的点滴

| Comments

喜欢 Flask 经典的 RestFul 设计风格,以及它与 Gevent 的优雅结合,可以帮助我们轻松构建异步非阻塞应用,烂笔头记下一些较好的实践。

消息反馈

Flask 提供了一个非常简单的方法来使用闪现系统向用户反馈信息。 闪现系统使得在一个请求结束的时候记录一个信息,然后在且仅仅在下一个请求中访问这个数据。这通常配合一个布局模板实现

文档链接

1
2
3
4
5
6
# 视图函数需要调用
flash('your response message for user')

# 前端页面调用
for message in get_flashed_messages()
就可以输出反馈信息

Flask 上下文

有两种上下文:程序上下文,请求上下文

  • current_app: 程序级别上下文,当前激活程序的实例。
  • g: 请求级别的上下文
  • request: 是请求级别的上下文,封装了客户端发出的 Http 请求中的内容
  • session: 用户会话,用于存储请求之间需要记住的键值对

注册 JinJia 模板过滤

1
2
3
4
def reverse_filter(s):
    return s[::-1]

app.jinja_env.filters['reverse'] = reverse_filter

itsdangerous 生成过期时间 Json 签名

1
2
3
4
5
6
7
8
9
10
serialaizer = Serializer(SECRET_KEY, expires_in=EXPIRES)
info = {'id':'xxx'}
session = serialaizer.dumps(info)

# 判断 session 时间
info = None
try:
    info = serialaizer.loads(session)
except Exception:
    return jsonify(ERR_SESS_TIMEOUT)

用途:生成一个有时间限制的签名,用于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
▾ app/
    ▾ controlers/
        ▾ admin/                    #管理后台的蓝图控制器
            ▾ forms/                #表单限制文件
                __init__.py     
                xx.py            # blueprint 文件
            __init__.py
            administrator.py
        ▾ api/                      # API 控制器
            __init__.py
        ▾ site/                     # 站点控制器
            __init__.py             # blueprint 文件
            xx.py
        __init__.py
        error.py
    ▸ models/         # SQLAlchemy 的各类模型
    ▸ static/         # 需要的静态资源,css, js, imgs
    ▾ templates/   # 前端页面,跟 controller 分类一一对应
        ▸ admin/
        ▸ error/
        ▸ site/
    ▸ utilities/    #  功能函数
      __init__.py       # 初始化app需要的各种插件,比如 redis, db, 注册蓝图
run.py                  # 相当于 main 函数,创建 app, 执行app.run() 函数
settings.py             # 配置文件

BluePrint 的好

每个 Blueprint 就像独立的 Application, 可以管理自己的模板, 路由, 反向路由url_for, 甚至是静态文件,最后统一挂载到 Application 下。从头到尾都是 RestFul。

在创建 app (app/init.py) 的时候调用如下:

1
2
from app.controllers.site.console import console 
app.register_blueprint(console, url_prefix='/console')

视图文件 (app/controllers/site/console.py):

1
console = BLueprint('console', __name__)

Json 返回

1
2
from flask import jsonify
return jsonify({'code': 0})

自定义出错页面

1
2
3
4
5
6
7
@app.errorhandler(404)
def not_found(error):
    return render_template('404.html'), 404

@app.errorhandler(500)
def crash(error):
    return render_template('5xx.html'), 500

WTF 跨站脚本防御

Flask-WTF 能保护表单免受跨站请求伪造的攻击,恶意网站把请求发送到被攻击者已经登录的其他玩战会引发 CSRF 攻击

  • app config 文件中,开启 CSRF 开关并配置密钥
1
2
CSRF_ENABLED = True
SECRET_KEY = 'you-will-never-guess'
  • 表单的定义
1
2
3
4
5
from flask.ext.wtf import Form, TextField, BooleanField
from flask.ext.wtf import Required
class LoginForm(Form):
    openid = TextField('openid', validators = [Required()])
    remember_me = BooleanField('remember_me', default = False)
  • 页面渲染
1
2
3
4
5
6
<form action="" method="post" name="login">
    form.hidden_tag()
    <p> Please enter your OpenID:form.openid(size=80)<br></p>
    <p>form.remember_me </p>
    <p><input type="submit" value="Sign In"></p>
</form>
  • 控制器函数
1
2
3
4
5
6
7
@app.route('/login', methods = ['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        flash('Login requested for OpenID="' + form.openid.data))
        return redirect('/index')
    return render_template('login.html', title = 'Sign In',form = form)

我们在配置中开启了CSRF(跨站伪造请求)功能,模板参数 form.hidden_tag() 会被替换成一个具有防止CSRF功能的隐藏表单字段。 在开启了CSRF功能后,所有模板的表单中都需要添加这个模板参数

Python 使用 LDAP

| Comments

写在开头

过去的两个多星期,几位小伙伴同心协力完成一个自研的,类似pushover的产品。感谢 Leader 的信任,让我在负责开发的同时也兼顾了一把项目经理。谢谢 IOS, Android 客户端的兄弟,设计师的支持,还有一位实习生,开心看到他的点滴成长。

提下背景,我们之前使用 pushover 来做报警推送,但是它对天朝用户不友好,Android 用户需要翻墙才能使用,有时候不稳定,体现为会收不到信息。pushove 需要付费。我们的受众不仅有程序汪,还有运营产品汪,他们需要一款更容易上手的推送软件,至少不需要番羽墙。于是自己开发一个很有必要。

为最大程度降低用户使用门槛,同时保证用户信息的安全,我们用了 LDAP 账户登陆,严格控制权限,以及 HTTPS 协议开发。下面提一提 LDAP 这个东西。

LDAP 是什么

LDAP维基百科

简单地讲,它是以目录树的方式存放账户信息。

这次项目中,我们不希望用户重新注册账户,而是采用原有的用户体系,这样对单点登录以及权限控制的好处不严而喻, LDAP 协议呼之欲出。

virtualenv 下安装 python-ldap

我们采用 Python 开发,这就需要 python-ldap 的帮助了, 记下安装笔记的好处是下次不用在此纠结太长时间。

1
2
apt-get install libsasl2-dev python-dev libldap2-dev libssl-dev
pip install python-ldap

身份验证

1
2
3
4
5
6
7
8
9
10
11
# LDAP 服务的域名和端口
SERVER_NAME = 'xxxx'
SERVER_PORT = xxx
try:
    conn = ldap.open(SERVER_NAME, SERVER_PORT)  
    conn.protocol_version = ldap.VERSION3 #设置ldap协议版本 
    username = "user=xxx,dc=company,dc=com" #身份信息
    password = "xxx" #访问密码
    conn.simple_bind_s(username,password) # 开始绑定,验证成功的话不会抛出异常
except ldap.LDAPError, e: #捕获出错信息
    print e

一点感悟

鄙人觉得,技术领导要带领项目成长, 除了有责任把控项目风险,推进项目进度。 还必须要花很多的心血在驾驭技术上, 身先士卒去调研可行性, 以及做技术攻关, 而非命令式地分配任务,让同事干活, 只问责结果。 否则很容易导致凝聚力不足,团队技术氛围不足,这样的团队易消极,也易滋生失败的项目。 然而在天朝, 很多人存在一个潜意识:写好代码是为了以后不写代码,这种阶级思想让我反感。

以前看过一个新闻, 硅谷在面试技术 VP ,仍然要求其在各位工程师面前手写代码,以此作为面试的重要环节, 不得不点赞。

Sysdig 值得拥有

| Comments

定位服务器问题时, 我们需要各式各样的武器, 诸如 iftop, ifstat, netstat, tcpdump, iostat。dstat 等, 因此工具箱需要装满很多工具, 在面对问题的时候才能显得不费吹灰之力, 迅速定位问题并解决, 保障服务稳定运行。Sysdig 的横空出世, 对我们而言, 就是一把瑞士军刀, 灵活小巧, 武艺多端.

安装

1
2
3
4
curl -s https://s3.amazonaws.com/download.draios.com/DRAIOS-GPG-KEY.public | apt-key add -
curl -s -o /etc/apt/sources.list.d/draios.list http://download.draios.com/stable/deb/draios.list
apt-get update
apt-get -y install sysdig

常用操作

sysdig 有很多 chisel, chisel 意为 铲子, 可以理解为定位某类问题的工具, sysdig 采用 Lua 编写的。

  • 查看 chisel 列表
1
sysdig -cl  
  • 查看具体某个 chisel 的提示
1
sysdig -i spy_logs
  • 使用某个 chisel
1
sysdig -c spy_logs
  • 过滤器可以帮助我们从各种输出信息中, 筛选出我们需要的, 比如 proc.name=foo , 如果你记住不了太多过滤器也无妨, 我们可以借助如下命令查看过滤器
1
sysdig -l
  • 记录定位信息到文本, 以及从文本读取信息
1
2
sysdig -w tracefile.cap
sysdig -r tracefile.dump proc.name=sshd

高效的实战

  • 服务器上经常需要查看哪个服务带宽占用使用较高, 特别是被 DDOS 的时候。
1
sudo sysdig -c topprocs_net
  • 查看某个IP的通讯数据,并以ASCII 码输出
1
sudo sysdig -s2000 -A -c echo_fds fd.cip=127.0.0.1
  • 查看非请求 redis-server 的其他请求进程和句柄
1
sudo sysdig -p"%proc.name %fd.name" "evt.type=accept and proc.name!=redis-server"
  • 查看访问该服务器的所有 GET 请求数据
1
sudo sysdig -s 2000 -A -c echo_fds fd.port=80 and evt.buffer contains GET
  • 查看访问该服务器的 SQL 语句
1
sudo sysdig -s 2000 -A -c echo_fds evt.buffer contains SELECT
  • 查看磁盘读写最高的进程
1
sysdig -c topprocs_file
  • 查看延迟最大的系统调用
1
sysdig -c topscalls_time
  • 查看具体文件的操作细节
1
sysdig -A -c echo_fds "fd.filename=syslog"
  • 查看 IO 延迟大于 1ms 的文件
1
sudo sysdig -c fileslower 1
  • 监视某个文件是否被操作, 从安全出发想象空间很大哦
1
sudo sysdig evt.type=open and fd.name contains /etc

Sysdig 官网