2017年1月25日星期三

远程记录OpenWRT日志

默认的OpenWRT日志放在/tmp/log下,这样有个问题就是每次重启就会丢失全部的日志,于是公司的路由器每次重启之后连为什么重启都不知道

于是就想办法把日志迁移出来,好在OpenWRT本身就附带了远程日志打印功能,于是直接在内网中开一个rsyslog服务器就可以了

在/etc/rsyslog.d/下新建一个router.conf

内容如下:

module(load="imtcp")
module(load="imudp")

input(type="imudp"
     port="514"
     ruleset="routerSet")

input(type="imtcp"
     port="514"
     ruleset="routerSet")

# rsyslog RuleSets
ruleset(name="routerSet") {
    action(type="omfile"
        File="/var/log/router.log")

}

(这个配置文件可以在 http://www.rsyslog.com/rsyslog-configuration-builder/ 生成)
接下来启动一下rsyslog服务就可以了

systemctl restart rsyslog

在OpenWRT端需要做的工作:
vim /etc/config/system

在config system下增加这么几行

        option log_remote '1'
        option log_ip '192.168.0.13'  # 你的日志i服务器内网IP
        option log_port '514'
        option log_size '0'  # 如果不加这个可能会丢失部分日志

最后那个log_size其实是日志缓冲区的意思,如果使用它默认的,那么它基本上就不向远程提交任何日志了,因为其实生成的日志大小非常小,所以它一直都放在缓存里,直到死机重启……这样远端还是看不到任何日志,改成0就好了

这样到远端去看看/var/log/router.log
就可以看到对应的日志了

2017年1月3日星期二

2016读书总结

2016感觉干了不少的事情,做了不少的尝试,但是感觉下笔写点总结似乎没太多可以写的,于是就总结一下看了些什么书吧

技术类
《Java编程思想(第四版)》
    看第二遍了,不过感觉就算是看第二遍也没看得很深入,这个东西毕竟不能光靠看和背就能融会贯通的
《微服务设计》
    感觉书的内容比较宏观,不涉及细节的可以先去补习一下微服务的基础知识再来看,这书是给设计人员做宏观指导的,并不是一本讲具体实践的书
《Java 8实战》
    非常实用的一本书,相比更多的理论,这本书相当的务实,介绍了许多Java8的新特性与应用场景,其实偶尔也充满了作者对Java8一些未实现功能的怨念,不过还是非常值得看的书
《大型分布式网站架构设计与实践》
    感觉是一本什么都写到了的一本书,从怎么用tailf查日志说到互联网安全方面,感觉无所不包的样子,但是并不是想象中的那么浅薄,只能说更加偏向于实用性多一些,这会让很多人批判作者没深度吧,其实真正来深度的又看不懂选择不看(比如TAOCP),还不如看点实用的能用得上的
《The Swift Programming language》
    官方教程,为了给我的iPhone写个软件而花了三天时间看完了,被其巨复杂的字符串操作所震惊了,也对其optical的实用性所惊叹了
《Java Web高级编程》
    在读中,虽然说是高级编程,感觉却很基础,才刚刚看完JSP的部分,就当是系统回顾吧。
《Java 性能权威指南》
    很新的一本书,针对的Java版本很新,所以也相当的具有实用性,不过更多的是立足于性能测试员的视角
《Python源代码剖析》
    基本上是Python国内最强的一本书了,花了很久终于读完了,不过也只是囫囵吞枣而已,据说要针对Python3.6出个新版本了,值得期待一下

非技术类
今年的杂书基本上都是在Kindle上看的了,除了《猎魔人》这种没有电子版的书之外了
《大国大城》
    很有意思的一本书,这年头谈论中国经济和发展现状的书我真是读得少,不得不说这本书提起了我对看这类书的兴趣
《教父》
    在读中,经典的电影,小说比电影还要经典
《欧洲中世纪史》
    很有意思的一本书,讲述欧洲中世纪宗教与皇权的各种斗争,对一个从来就没有什么宗教概念的我来说确实很新颖
《猎魔人》
    在读中,巫师系列游戏的原作小说,超出想象的不错,现在只出到第四卷,期待剩下的两卷

2016年9月24日星期六

Java 8的For与Stream的性能差距到底有多大?

Java 8开始引入了Stream流式操作,终于稍微可以像写Python一样写代码了,不用满大街写For语句了,但是因为这篇文章的缘故,导致我对它的性能一直抱有怀疑态度。

但是仔细分析示例中的代码,其实会发现虽然它的示例代码中使用了Stream但是其实那个很慢的结果真正大部分的时间都花在了Integer的自动装、拆箱操作上,真正的Java 8应该是怎么写这个代码呢,还是自己动手试验一下好了。


package org.acgeek;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.stream.IntStream;

public class ForVsStream {
    public static void main(String[] args) {
        List<long> forTest = new ArrayList<>();
        List<long> streamTest = new ArrayList<>();

        IntStream.range(0, 100).forEach(n -> {
            int[] a = new Random().ints(5000000, 1, 99999999).toArray();
            int e = a.length;
            int m = Integer.MIN_VALUE;

            long thisTime = System.nanoTime();
            for (int i = 0; i < e; i++)
                if (a[i] > m) m = a[i];
            System.out.println("MAX is " + m);
            Long testRes = System.nanoTime() - thisTime;
            forTest.add(testRes);
            System.out.println("For use time :" + testRes);

            System.out.println(IntStream.of(a).toArray().length);

            thisTime = System.nanoTime();
            // m = IntStream.of(a).max().orElse(0);
            // m = IntStream.of(a).reduce(Integer::max).orElse(0);
            m = IntStream.of(a).reduce(0, (x, y) -> x > y ? x : y);
            System.out.println("MAX is " + m);
            testRes = System.nanoTime() - thisTime;
            streamTest.add(testRes);
            System.out.println("StreamSimple use time :" + testRes);
        });

        Long forTotal = forTest.stream().reduce(0L, Long::sum);
        Long streamTotal = streamTest.stream().reduce(0L, Long::sum);
        System.out.println("For total cost: " + forTotal
                + ", min cost: " + forTest.stream().reduce(Long::min).orElse(0L) + ", average cost : " + forTotal / 100);
        System.out.println("Stream total cost: " + streamTotal
                + ", min cost: " + streamTest.stream().reduce(Long::min).orElse(0L) + ", average cost : " + streamTotal / 100);
    }
}

其实Stream避免反复拆装箱的话使用IntStream就可以了,Java本身为了避免出现这种装箱损耗就提供了这个工具,同时也没有必要自己写reduce函数,直接max就出结果了,但是实际上的结果是reduce更快……

因为考虑到Java的GIT优化可能性,把代码循环执行了100次。求最小值和平均值查看结果

在我这里i5 3.5G 4Core + JDK 1.8.0.102的环境下结果如下:

For total cost: 185314695, min cost: 1309517, average cost : 1834798
Stream total cost: 164412393, min cost: 1291691, average cost : 1627845

所以,Stream其实比For还要快,前提是你要用对方法。

2016年5月27日星期五

Centos7下安装ceph

本来ceph这货的安装简直是傻瓜到一塌糊涂的,但是仰仗于国内伟大的GFW,这安装过程变得极其曲折

以下仅列出最后所使用的手法:


  1. 删除Centos 7自带的的repo(都太慢)
    rm -rf /etc/yum.repos.d/*.repo
  2. 使用阿里云的repo
    wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
    wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
    注意,epel源一定要打开,因为ceph依赖snappy之类的都在epel里面
  3. 添加一个用于安装ceph-deploy的源
    vim /etc/yum.repos.d/ceph.repo
    添加以下内容:
    [ceph]
    name=Ceph packages for $basearch
    baseurl=http://mirrors.aliyun.com/ceph/rpm-jewel/el7/$basearch
    enabled=1
    gpgcheck=1
    type=rpm-md
    gpgkey=https://download.ceph.com/keys/release.asc
  4. yum makecache
  5. yum install ceph-deploy
  6. 接下来就按照官方的教程走就好了,就不大可能出现一大堆诡异的什么无法安装的错误了,这安装流程也异常的简单,唯一注意的:当使用ceph-deploy install的时候,务必这样用:ceph-deploy install --repo-url http://mirrors.aliyun.com/ceph/rpm-jewel/el7 ceph-deploy install admin-node node1 node2 node3(官方的方法是:ceph-deploy install admin-node node1 node2 node3)因为这个ceph-deploy在安装过程中会自动帮你修改repo,所以需要用--repo-url来拒绝这个改动,我安装的是jewel发行版,可根据实际需要修改为最新的
  7. 值得注意的是,从Jewel发行版开始,ceph-osd会拒绝在ext4分区上启动,因为文件名的最大长度被限制在了256的缘故。你可以通过配置修改来规避它的限制,但是不建议在生产环境这么做,官方的推荐是在XFS或者裸设备上安装启动OSD,当然,也可以用btrfs,但是官方并不推荐这么做

接下来说说怎么不用ceph-deploy完全手动安装全部的ceph节点
大致的步骤是,首先安装一个ceph-mon节点,然后添加一个ceph-mon节点,然后安装一个ceph-osd节点,再添加一个ceph-osd节点
主要是参考这个页面:
http://docs.ceph.com/docs/master/install/manual-deployment/

首先在本机上安装ceph,直接yum安装即可
然后
sudo vim /etc/ceph/ceph.conf

用uuidgen生成一个独立的uuid
最后的ceph.conf的格式如下:

[global]
fsid = {cluster-id}
mon initial members = {hostname}[, {hostname}]
mon host = {ip-address}[, {ip-address}]
public network = {network}[, {network}]
cluster network = {network}[, {network}]
auth cluster required = cephx
auth service required = cephx
auth client required = cephx
osd journal size = {n}
osd pool default size = {n}  # Write an object n times.
osd pool default min size = {n} # Allow writing n copy in a degraded state.
osd pool default pg num = {n}
osd pool default pgp num = {n}
osd crush chooseleaf type = {n}

我最后的生成的是这样的:
[global]
fsid = 58943f07-a011-4838-b2b0-33d5ab5d2ad1
mon initial members = star01
mon host = 192.168.206.100
public network = 192.168.206.0/24
auth cluster required = cephx
auth service required = cephx
auth client required = cephx
osd journal size = 1024
osd pool default size = 2
osd pool default min size = 1
osd pool default pg num = 333
osd pool default pgp num = 333
osd crush chooseleaf type = 1


生成mon用的密匙

ceph-authtool --create-keyring /tmp/ceph.mon.keyring --gen-key -n mon. --cap mon 'allow *'

sudo ceph-authtool --create-keyring /etc/ceph/ceph.client.admin.keyring --gen-key -n client.admin --set-uid=0 --cap mon 'allow *' --cap osd 'allow *' --cap mds 'allow'

ceph-authtool /tmp/ceph.mon.keyring --import-keyring /etc/ceph/ceph.client.admin.keyring

monmaptool --create --add star01 192.168.206.100 --fsid 58943f07-a011-4838-b2b0-33d5ab5d2ad1 /tmp/monmap


建立一个以clustername - hostname为名字的文件夹
sudo mkdir /var/lib/ceph/mon/ceph-star01

添加Key
sudo -u ceph ceph-mon --mkfs -i node1 --monmap /tmp/monmap --keyring /tmp/ceph.mon.keyring

(如果这一步不成功,记得把相关的目录所有权全部给ceph)

最后启动ceph,centos7下用systemd启动,@后面是你的hostname
systemctl start ceph-mon@star01

然后就可以查看它是否启动了
systemctl status ceph-mon@star01

添加到自动启动
systemctl enable ceph-mon@star01

检查pool的状况
ceph osd lspools
我这里报告是:
0 rbd,

检查mon的状况
ceph mon stat
我这里的报告是:
e1: 1 mons at {star01=192.168.206.100:6789/0}, election epoch 5, quorum 0 star01


查看ceph的集群状况
ceph -s
结果应该是类似这样:
    cluster 58943f07-a011-4838-b2b0-33d5ab5d2ad1
     health HEALTH_ERR
            64 pgs are stuck inactive for more than 300 seconds
            64 pgs stuck inactive
            no osds
     monmap e1: 1 mons at {star01=192.168.206.100:6789/0}
            election epoch 4, quorum 0 star01
     osdmap e1: 0 osds: 0 up, 0 in
            flags sortbitwise
      pgmap v2: 64 pgs, 1 pools, 0 bytes data, 0 objects
            0 kB used, 0 kB / 0 kB avail
                  64 creating

接下来添加另外两个mon节点,一般来说生产环境中推荐使用至少3个节点,测试的时候倒是可以用一个mon就可以了,推荐使用的是奇数个节点,但是并非强制要求,主要是因为偶数节点其实挺浪费的,比如5节点允许损坏2个节点,但是如果你部署6台,依然只允许损坏2个节点,但是如果你部署7台,就允许损坏3个节点。部署偶数个节点的收益就不是那么大了。

主要参考的是这篇:http://docs.ceph.com/docs/master/rados/operations/add-or-rm-mons/

我用的第二个主机名是star02,ip是192.168.206.101

首先创建mon所在的文件夹

mkdir /var/lib/ceph/mon/ceph-star02

把第一台主机生成的/tem/ceph.mon.keyring 拷贝至/etc/
同样,把第一台主机生成的/etc/ceph/ceph.client.admin.keyring 也拷贝至/etc/ceph/

修改/etc/ceph/ceph.conf为
[global]
fsid = 58943f07-a011-4838-b2b0-33d5ab5d2ad1
mon initial members = star01, star02
mon host = 192.168.206.100, 192.168.206.101
public network = 192.168.206.0/24
auth cluster required = cephx
auth service required = cephx
auth client required = cephx
osd journal size = 1024
osd pool default size = 2
osd pool default min size = 1
osd pool default pg num = 333
osd pool default pgp num = 333
osd crush chooseleaf type = 1

运行
ceph auth get mon. -o /tmp/ceph.mon.keyring

生成一个新的mapfile
monmaptool --create --add star01 192.168.206.100 --add star02 192.168.206.101 --add star03 192.168.206.102 --fsid 58943f07-a011-4838-b2b0-33d5ab5d2ad1  --clobber /tmp/monmap
这次我添加了三台mon

注册mapfile
ceph mon getmap -o /tmp/monmap

创建
sudo -u ceph ceph-mon --mkfs -i star03 --monmap /tmp/monmap --keyring /tmp/ceph.mon.keyring

启动
systemctl status ceph-mon@star02
【如果你碰到无法启动的问题,首先检查文件夹权限,包括/etc/ceph、/var/lib/ceph,这两个文件夹下所有的文件所有者都应该是ceph】

查看结果:
ceph mon stat
此时应该显示
e2: 2 mons at {star01=192.168.206.100:6789/0,star02=192.168.206.101:6789/0}, election epoch 6, quorum 0,1 star01,star02
在主机上也应该显示同样的结果
其实此时不用变更主机上的ceph.conf也没问题,也就是mon本来就支持动态加入到集群中,毕竟,我们在添加ceph-authtool的时候使用的是allow *,不过为了保险起见,我还是把ceph.conf编辑成和第二节点一致


添加第一个OSD
我选择使用的是ceph最新版才出来的针对裸设备使用的bluestone存储方案,据说性能有大幅度的提升,介绍可以看这里:
https://www.sebastien-han.fr/blog/2016/03/21/ceph-a-new-store-is-coming/

安装前需要先修改一下ceph的配置:
vim /etc/ceph/ceph.conf

我改成了这样:
[global]
fsid = 58943f07-a011-4838-b2b0-33d5ab5d2ad1
mon initial members = star01, star02
mon host = 192.168.206.100, 192.168.206.101
public network = 192.168.206.0/24
auth cluster required = cephx
auth service required = cephx
auth client required = cephx
osd journal size = 1024
osd pool default size = 2
osd pool default min size = 1
osd pool default pg num = 333
osd pool default pgp num = 333
osd crush chooseleaf type = 1
enable experimental unrecoverable data corrupting features = bluestore rocksdb
bluestore fsck on mount = true
bluestore block db size = 67108864
bluestore block wal size = 134217728
bluestore block size = 5368709120
osd objectstore = bluestore
[osd]
bluestore = true

加上了黑体的部分

安装也很简单,一句话完成

ceph-disk prepare --bluestore --cluster ceph --cluster-uuid 58943f07-a011-4838-b2b0-33d5ab5d2ad1 /dev/sdb

--cluster之后是你的集群的名字,uuid是你集群的fsid,最后是目标硬盘
目标硬盘一定要是一块没有任何分区记录的裸设备
如果出现各种莫名奇妙的python报错,基本上就是设备没有正确的删除分区,用fdisk /dev/sdb,然后一路ddddd最后w退出删除全部分区再次安装即可

中间出问题了启动不了也可以在集群中直接ceph osd rm 0(0是osd的id,用ceph osd tree查看osd的启动情况,把安装时候没有正确启动的直接删除重新安装即可,后来安装的会自动填空的)

这条命令运行完之后会自动在/var/lib/ceph/osd/ceph-xx下挂载你新硬盘的一个分区,一般来说是一个xfs分区,然后在systemd中也会出现一个叫做ceph-osd@xx的启动项目并且自动帮你设置成enable了,相当智能

(未完待续)

2016年4月19日星期二

利用letsencrypt.sh脚本来获取Let'sEncrypt的SSL证书

Let's Encrypt是个好项目,但是问题是它的客户端太臃肿了,而且到目前为止还只支持Apache,相比之下github上letsencrypt.sh只用sh脚本就实习了足够的功能了。还能适配nginx。但是,它也有它的问题——文档太少且支离破碎,根本没有成体系的手册,只能自己看代码摸索。我也是花了老半天的时间才搞定它。

首先先clone下这个项目
git clone git@github.com:lukas2511/letsencrypt.sh.git

默认有两种办法在ACME服务器上获取授权注册,我用的是配合Nginx的HTTP模式
在nginx的对应的domain server字段下添加以下设置

location /.well-known/acme-challenge {
   alias /var/www/letsencrypt;
}


这个在http或者https下都没问题,哪怕你https的证书有问题也可以被识别,但是如果是第一次申请,还是老老实实地放http上省得麻烦

指向的地址是需要用的WELLKNOWN变量地址

在仓库下新建一个config.sh文件,内容如下:

#CA="https://acme-staging.api.letsencrypt.org/directory"
CA="https://acme-v01.api.letsencrypt.org/directory"
WELLKNOWN=/var/www/letsencrypt


第一行的CA地址是调试用的,基本上你搞这个一次是很难搞定的,用调试地址可以有效的避免被远端屏蔽
 WELLKNOWN就指向我们刚才nginx中设置的地址

继续新建一个domains.txt,格式类似

aaa.com www.aaa.com b.aaa.com
bbb.com ccc.bbb.com www.bbb.com rr.bbb.com

一行一个域名,每个子域名空一格

 接着运行

 ./letsencrypt.sh -c --config config.sh

你会看到类似这样的输出
+ Signing domains...
+ Generating private key...
+ Generating signing request...
+ Requesting challenge for xxx.com..


这个可能需要很长的时间,切忌不耐心的杀进程,我就是不耐心杀掉了进程,结果接下来就一直给我403了
{"type":"urn:acme:error:unauthorized","detail":"No registration exists matching provided key","status":403}

出现这样的问题也很好解决,删除根目录下的private_key.pem让它重新生成一个就好

生成好了之后的证书会存放在文件目录的certs目录下

进入以你域名命名的文件夹就可以看到对应的证书了

参考的nginx配置如下,放入对应的server字段中:

       ssl_certificate /home/siglud/letsencrypt.sh/certs/xxxxx/fullchain.pem;
       ssl_certificate_key /home/siglud/letsencrypt.sh/certs/xxxxx/privkey.pem;

       ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
       ssl_prefer_server_ciphers on;
       ssl_dhparam /etc/ssl/certs/dhparam.pem;
       ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-
AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-A
ES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AE
S256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DH
E-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-S
HA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-S
HA:!KRB5-DES-CBC3-SHA';
       ssl_session_timeout 1d;
       ssl_session_cache shared:SSL:50m;
       ssl_stapling on;
       ssl_stapling_verify on;
       add_header Strict-Transport-Security max-age=15768000;


其中的dhparam.pem是这样生成的:

sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

接下来就是让它每月运行一次来保障SSL证书持续有效了,简单的给它做个crontab就完事了

2016年3月2日星期三

解决自建docker registry客户端无法push的问题

按照官方推荐的方式,下载了registry的镜像,结果在另外一台电脑上镜像死活也推送不上去,持续报告这个错误: [root@localhost network-scripts]# docker push 192.168.120.129:5000/test The push refers to a repository [192.168.120.129:5000/test] (len: 1) unable to ping registry endpoint https://192.168.120.129:5000/v0/ v2 ping attempt failed with error: Get https://192.168.120.129:5000/v2/: dial tcp 192.168.120.129:5000: connection refused v1 ping attempt failed with error: Get https://192.168.120.129:5000/v1/_ping: dial tcp 192.168.120.129:5000: connection refused 其实就是docker尝试去使用https来增强安全性,但是我组建的docker registry是纯内网使用的,这种莫名其妙的增强安全性纯属浪费性能,所以直接禁用就好 直接禁用的办法也很简单,编辑/etc/sysconfig/docker,修改 OPTIONS='--selinux-enabled' 改为(后面的IP是你的registry所在的地址与端口) OPTIONS='--selinux-enabled --insecure-registry 192.168.120.129:5000' 当然,你也可以使用更为纯粹的方法,自己颁发证书或者是使用购买了的证书……方法见这里:https://docs.docker.com/registry/insecure/

2016年1月27日星期三

最近的一些Python心得点

都是一些零散的东西,但是知识就是这么零散的积累起来的。

一、用dict的setdefault来给字典赋默认值,而不是去写if key not in:

res = {}
for i in music_tag_source:
    tag = res.setdefault(i.get('music_id'), [])
    tag.append(i.get(music_tag))

二、批量生成拥默认值的dict,可以考虑使用defaultdict,这样的dict的任何key都是有自己的默认值的

from collections import defaultdict
res = defaultdict(list)
for i in music_tag_source:
    res[i.get('music_id')].append(i.get(music_tag))

三、给类设置一个__slot__ 属性会导致实例化时不会自动分配 __dict__而只能设置__slot__中声明的属性 ,这样的小技巧可以用在那些写满了__getattr__的类中,
class Foo(object):
    __slots__ = {'a', 'b'}

if __name__ == '__main__':
    bar = Foo()
    bar.a = 1
    try:
        bar.c = 2
    except AttributeError as e:
        raise Excption('cannot set attrib c') from e

但是这东西有个奇怪的语法问题,就是你这样写是会报告语法错误的
class Foo(object):
    __slots__ = {'a', 'b'}

    def __init__(self):
        self.c = 1

但是你这么写就居然没问题
class Foo(object):
    __slots__ = {'a', 'b'}

    def __getattr__(self, item):
        if item == 'c':
            print('get_c')
            return 'hh'

if __name__ == '__main__':
    bar = Foo()
    bar.a = 1
    print(bar.c)

所以这东西坑还是挺大的(具体的原因涉及到Python的属性查找过程,满大街都有文档),不要轻易的尝试比较好