003_饿了么chaosmonkey实现
背景
公司目前的服务设计大部分满足 design for failure 理念。随着业务复杂度的提升,我们很难再保证对系统故障的容错性。我们需要工具来验证服务的容错性,基于这个需求我们使用了 tc 工具,并开发了chaosmonkey工具。本文主要围绕这两个工具进行讲述。
TC流控
基本概念
Netem 是 Linux 2.6 及以上内核版本提供的一个网络模拟功能模块。该功能模块可以模拟出局域网诸如低带宽、传输延迟、丢包、乱序、重复等情况。
基本原理
包从进入tc开始,分为多个qd(qdisc)。每个qd可以包含多个子qd,qd彼此连接形成一颗树。每个qd上可以附加filter,选择进入哪个child。流量控制控发不控收。
基本操作
# 网络延迟
# sudo tc qdisc add dev eth0 root netem delay 100ms
# 网络丢包
# sudo tc qdisc add dev eth0 root netem loss 1%
# 基于filter
# 192.168.33.1被延迟,其他地址无效果
# sudo tc qdisc add dev eth0 root handle 1: prio
# sudo tc qdisc add dev eth0 parent 1:1 handle 10: netem delay 1s loss 2%
# sudo tc filter add dev eth0 protocol ip parent 1: prio 1 u32 match ip dst 192.168.33.1 flowid 1:1
# 删除规则
# sudo tc qdisc del dev eth0 root
缺点
tc 工具在配置时比较繁琐,不能模拟一段时间内的故障,不能作为一个服务定期演练。自己开发的chaosmonkey可以满足以上条件。
chaosmonkey
功能
chaosmonkey基于golang开发,它包含三个功能:网络故障模拟、服务重定向、资源限制。如图。
网络故障模拟
用户向服务发送http请求,服务解析参数,模拟丢包、延迟,其中主要使用的package是 netlink 。部分代码如下:
// 用户提交的参数
type addNetwork struct {
Duration uint32
Limit *network.LinkLimit
}
type LinkLimit struct {
Latency uint32
Jitter uint32
Loss float32
Corruption float32
}
//设置丢包、延迟
func (l *Link) AddLimit(limit LinkLimit) error {
netemQdiscAttrs := netlink.NetemQdiscAttrs{
Latency: limit.Latency,
Jitter: limit.Jitter,
Loss: limit.Loss,
CorruptProb: limit.Corruption,
}
qdiscAttrs := netlink.QdiscAttrs{
Parent: netlink.HANDLE_ROOT,
LinkIndex: l.Attrs().Index,
}
netem := netlink.NewNetem(qdiscAttrs, netemQdiscAttrs)
if err := netlink.QdiscAdd(netem); err != nil {
return fmt.Errorf("qdisc add %v: %v", netem, err)
}
log.Infof("AddLimit finish with %v", netem)
return nil
}
服务重定向
服务主要使用exec包执行iptables命令,部分代码如下:
// 用户提交的参数
type startRedirect struct {
Duration uint32
Redirectors []redirector.Redirector
Name string
}
type Redirector struct {
Protocol string `json:"protocol"`
Destination string `json:"destination"`
Dport string `json:"dport"`
Target string `json:"target"`
}
//增加iptables规则
for _, r := range redirectors {
if err := ipt.Append("nat", "OUTPUT", "-p", r.Protocol, "-d", r.Destination, "--dport", r.Dport, "-j", "DNAT", "--to", r.Target); err != nil {
return fmt.Errorf("append tables %v: %v", r, err)
}
log.Infof("append iptables rule finish: %v", r)
...
}
func (ipt *IPtables) Append(table string, chain string, rulespec ...string) error {
cmd := append([]string{"-t", table, "-A", chain}, rulespec...)
return ipt.run(cmd...)
}
func (ipt *IPtables) runWithOutput(args []string, stdout io.Writer) error {
args = append([]string{ipt.path}, args...)
var stderr bytes.Buffer
cmd := exec.Cmd{
Path: ipt.path,
Args: args,
Stdout: stdout,
Stderr: &stderr,
}
err := cmd.Run()
if err != nil {
return &Error{*(err.(*exec.ExitError)), stderr.String()}
}
return nil
}
资源限制
主要使用的packge是 fs 和 configs 。代码如下:
type addResources struct {
Pids []int
Pattern string
Duration uint32
Limit resources.Limit
}
type Limit struct {
Cpu int64
Memory int64
BlkioReadBPS uint64 `json:"blkio_read_bps"`
BlkioReadIOPS uint64 `json:"blkio_read_iops"`
BlkioWriteBPS uint64 `json:"blkio_write_bps"`
BlkioWriteIOPS uint64 `json:"blkio_write_iops"`
}
// 设置cgroup
config := limit.toConfig()
monkey, err := factory.CreateManager(config)
...
for _, pid := range pids {
if manager, err := factory.GetManager(pid); err != nil {
return fmt.Errorf("get manager for pid %d: %v", pid, err)
} else {
p := &process{manager: manager, pid: pid, monkey: monkey, wait: r.wait}
if err := p.start(duration); err != nil {
return fmt.Errorf("process apply monkey: %v", err)
} else {
r.processes = append(r.processes, p)
}
}
}
if err := monkey.Set(config); err != nil {
return fmt.Errorf("set monkey config: %v", err)
} else {
log.Infof("set monkey %v for pids %v", limit, pids)
}