Jepsen使用

Jepsen是一款用来验证分布式系统安全性的工具。

安装

Jepsen是使用Clojure编写,Clojure基于JVM上的一款Lisp方言,本文假设对Lisp和函数式编程是有基础知识。特别地,我们关注以下几点语法

  1. 函数
    函数使用defn定义,使用一个中括号接受参数。在参数列表中可以使用[& args],表示&之后的args是一个可变参数列表。函数可以拥有多个重载,如下面代码所示

    1
    2
    3
    4
    5
    (defn hello3
    ([] "Hello World")
    ([name] (str "Hello " name)))
    (hello3 "Jake") ; => "Hello Jake"
    (hello3) ; => "Hello World"
  2. 函数调用
    使用/调用静态函数,例如c/cd调用类c里面的cd方法。
    使用.调用成员函数,例如. (Date.) getTime调用了一个Date类型的对象Date.的成员方法getTime

  3. 映射数据结构
    我们使用如下所示的方式定义一个Map

    1
    2
    3
    4
    (def popsicle-map
    (hash-map :red :cherry, :green :apple, :purple :grape))
    (def popsicle-map
    {:red :cherry, :green :apple, :purple :grape}) ; same as previous

    然后使用这样的方式访问其中的元素

    1
    2
    3
    (get popsicle-map :green)
    (popsicle-map :green)
    (:green popsicle-map)
  4. doto
    类似vb里面的With

  5. reify
    创建一个继承某个特定interface/protocol或者java.lang.Object的匿名类

Leiningen是Clojure项目的自动化配置工具,可以通过其官网的自安装脚本使用。

在Docker上配置Jepsen

Jepsen官方推荐的Jepsen使用方式是借助于Docker或者虚拟机。特别地,试图用同一个IP上的多个端口来处理是很麻烦的。因此使用Docker是单机上运行Jespen的做法。
以etcd测试为例,Jespen喜欢从官网上下载项目的release并安装,这对于国内用户是相当不友好的。对此,我们可以进入Jespen项目中的docker/control文件夹,将我们需要的binary放进去。这样在up.sh中,Jepsen会统一将control目录中的文件进行打包拷贝到容器中。接着我们修改Dockerfile,用来将我们传入容器中的release安装到指定位置。以etcd为例,我们编写如下所示的代码

1
2
3
4
# /docker/control/Dockerfile
RUN mkdir -p /opt/etcd/
ADD ./etcd /opt/etcd/etcd
ADD ./etcdctl /opt/etcd/etcdctl

并且注释掉etcd/src/jepsen/etcd.clj里面的从googleapis下载etcd的命令。

接着我们清除旧的容器,并启动新的

1
2
3
4
docker stop $(docker ps -aq)
docker rm $(docker ps -aq)
bash ./up.sh
docker exec -it jepsen-control bash

常见错误

ssh配置问题

我在使用百度的jepsen时出现这个错误。通常原因是没有配置好ssh

1
2
3
4
5
lein test :only jepsen.atomic-test/create-partition

ERROR in (create-partition) (Session.java:512)
Uncaught exception, not in assertion.
expected: nil

docker配置问题

下面这个错误的第一个原因是jepsen-control上没有sudo

1
2
ERROR [2019-02-21 14:04:00,576] main - jepsen.cli Oh jeez, I'm sorry, Jepsen broke. Here's why:
java.util.concurrent.ExecutionException: java.lang.RuntimeException: sudo -S -u root bash -c "cd /; echo \`date +'%Y-%m-%d %H:%M:%S'\` \"Jepsen starting\" etcd \":--name n1 :--listen-peer-urls http://n1:2380 :--listen-client-urls http://n1:2379 :--advertise-client-urls http://n1:2379 :--initial-cluster-state :new :--initial-advertise-peer-urls http://n1:2380 :--initial-cluster n1=http://n1:2380,n2=http://n2:2380,n3=http://n3:2380,n4=http://n4:2380,n5=http://n5:2380 :--log-output :stdout\" >> /opt/etcd/etcd.log" returned non-zero exit status 1 on n1. STDOUT:

第二个原因是jepsen-n1等节点上没有etcd,我们需要改以下的细节

  1. 在up.sh中加上

    1
    2
    3
    4
    5
    6
    INFO "Copying .. to node/jepsen"
    (
    rm -rf ./node/jepsen
    mkdir ./node/jepsen
    (cd ..; tar --exclude=./docker --exclude=./.git -cf - .) | tar Cxf ./node/jepsen -
    )
  2. 参照control/Dockerfile修改node/Dockerfile

  3. 将etcd也复制到node文件夹里面

  4. 修改etcd.clj里面的代码,注释掉下载etcd和删除etcd文件夹的语句。

etcd问题

在control节点中运行lein run test --concurrency 10时出现这个问题

1
2
3
4
5
6
7
8
9
10
11
12
INFO [2019-02-22 07:37:52,339] jepsen worker 5 - jepsen.util 35 :invoke :write  [0 1]
INFO [2019-02-22 07:37:52,347] jepsen worker 1 - jepsen.util 31 :info :cas [0 [1 4]] indeterminate: Connection refused (Connection refused)
INFO [2019-02-22 07:37:52,348] jepsen worker 8 - jepsen.util 38 :invoke :cas [0 [3 0]]
INFO [2019-02-22 07:37:52,352] jepsen worker 3 - jepsen.util 33 :info :cas [0 [0 0]] indeterminate: Connection refused (Connection refused)
INFO [2019-02-22 07:37:52,355] jepsen worker 9 - jepsen.util 39 :invoke :read [0 nil]
INFO [2019-02-22 07:37:52,360] jepsen worker 0 - jepsen.util 30 :invoke :read [0 nil]
INFO [2019-02-22 07:37:52,396] jepsen worker 7 - jepsen.util 37 :invoke :cas [0 [3 0]]
INFO [2019-02-22 07:37:52,407] jepsen worker 1 - jepsen.util 41 :invoke :cas [0 [1 2]]
INFO [2019-02-22 07:37:52,411] jepsen worker 3 - jepsen.util 43 :invoke :read [0 nil]
WARN [2019-02-22 07:37:52,422] jepsen worker 0 - jepsen.core Process 30 crashed
java.net.ConnectException: Connection refused (Connection refused)
at java.net.PlainSocketImpl.socketConnect(Native Method) ~[na:1.8.0_191]

查看n1节点的etcd日志发现以下错误

1
2
2019-02-22 07:37:45 Jepsen starting etcd :--name n1 :--listen-peer-urls http://n1:2380 :--listen-client-urls http://n1:2379 :--advertise-client-urls http://n1:2379 :--initial-cluster-state :new :--initial-advertise-peer-urls http://n1:2380 :--initial-cluster n1=http://n1:2380,n2=http://n2:2380,n3=http://n3:2380,n4=http://n4:2380,n5=http://n5:2380 :--log-output :stdout
2019-02-22 07:37:46.021492 E | etcdmain: error verifying flags, expected IP in URL for binding (http://n1:2380). See 'etcd --help'.

这是因为我们在url里面使用了n1、n2这样的名字。修改etcd.clj里面的代码,将所有的name改成jepsen.control.net/ip即可解决问题。特别地,这个问题在jepsen的一个PR中出现过