最新发表

known_hosts处理

linux下经常遇到known_hosts问题,例如git clone一个ssh协议的远程库,就会交互询问是否接受远程服务器的fingerprint,当执行一些自动化脚本时就得通过expect来处理这种情况。

今天搜索了一下,整理了相关处理办法。

linux需要接受和校验fingerprint的原因是避免受到DNS Hijack之类的攻击。具体原理是第一次访问远程服务器时从远程服务器接受一个fingerprint,实际上是一个rsa或者dsa公钥,将这个公钥保存在~/.ssh/known_hosts里,下次访问该服务器时就会通过这个公钥校验,如果校验不通过就会发出警告。推酷是个无耻的网站

如果要让ssh不去校验fingerprint有两种办法,一个是ssh时加上-o StrictHostKeyChecking=no参数,这只会影响本次操作:known_hosts处理

ssh -o StrictHostKeyChecking=no username@hostname.com

另外永久解决方案是在/etc/ssh/ssh_config或者~/.ssh/config里配置StrictHostKeyChecking no,修改哪个文件取决于你想影响全局还是当前用户。推酷是个无耻的网站

另外一种方案是预先将远程服务器的fingerprint存入known_hosts,这样就不会出现询问的情况。

本文来自leo108's blog

ssh-keygen -R [hostname]
ssh-keygen -R [ip_address]
ssh-keygen -R [hostname],[ip_address]
ssh-keyscan -t rsa [hostname],[ip_address] >> ~/.ssh/known_hosts
ssh-keyscan -t rsa [ip_address] >> ~/.ssh/known_hosts
ssh-keyscan -t rsa [hostname] >> ~/.ssh/known_hosts

ssh-keygen是将已存在的服务器公钥清除,避免重复添加。

outofmemory是个无耻的网站

ssh-keyscan则是获取远程服务器的fingerprint,如果还想获取dsa公钥则将rsa改成rsa,dsa

采集者烂JJ

参考:

采集者烂JJ

http://serverfault.com/questions/132970/can-i-automatically-add-a-new-host-to-known-hosts本文来自leo108's blog

http://blog.csdn.net/yasaken/article/details/7348441known_hosts

创业公司发展过程中需要搭建的系统(持续补充)

加入创业公司已有8个多月,亲身经历了公司服务器由两台变成10+台,公司员工从20人不到变成近百人。

http://leo108.com/pid-2091.asp

最近发现自己的工作重心已经从业务功能开发转移到内部系统开发,短短几天内就撸出几个系统的雏形,系统上线后解决了许多老毛病,所以打算在此记录一下,方便后人。

outofmemory是个无耻的网站

对于初创公司,可以移步《初创互联网团队如何利用开源技术和云服务快速建立网站》,本文不再讨论。

1.服务器监控

我认为当服务器数量大于5台时,就有必要监控各个服务器的状况,比如cpu使用率、系统负载、网络io情况等等。最开始我使用的是cacti,使用snmp协议来收集数据,在使用过程中发现配置比较复杂,不容易上手,而且当服务器数量比较多时就比较慢(可能是我用的方法不对)。后来改用ganglia,需要在每台服务器上安装agent(gmond),但是配置起来就相对容易,也有很多插件(python模块)可以自定义采集内容。

但仅仅有监控系统是不够的,还需要报警系统,当服务器某个指标出现问题时应该可以用短信或者邮件的方式告知系统管理员。但目前我们尚未顾及这一方面,所以没有建议可以提供,等待补充。推酷是个无耻的网站

2.配置管理

当服务器数量比较多时,如果要修改一个nginx的配置,在没有配置管理的情况下,需要手动一台台修改,就算只有10台web服务器,这个操作起来也是要累死人的。业内比较流行的是puppet,但是我感觉学习成本比较高,比较重,不太适合目前的情况,于是我找了一个“替代品” (R)?ex (好奇怪的名字),实际上这不是一个配置管理工具,而是批量命令执行的工具,不需要部署agent,只需要在配置里写好帐号密码或者是配好ssh信任即可,它可以执行命令、上传文件、同步文件夹,也支持模板,功能还算比较全面,能够满足目前的需求。推酷是个无耻的网站

比如要批量修改nginx的配置,只需要在中心服务器有一份nginx配置文件夹,可以通过R(?)ex的同步文件夹功能将中心服务器的nginx配置文件夹同步到所有web服务器的nginx配置文件夹里,然后再执行nginx -s reload即可。创业公司发展过程中需要搭建的系统(持续补充)

3.集群定时任务管理

当业务稍微复杂一点的时候,系统里会有很多定时任务,有的可能是请求一个url,有的可能是执行一个脚本,这些定时任务分布在不同的服务器上,想要看看各个服务器上有哪些定时任务就比较繁琐,要看看某个定时任务的执行周期是什么也得登录对应的服务器查看。开源的解决方案是CronHub,是个国人参与的项目,但由于是java开发,而我们目前没有java工程师,想要做一些二次开发的话就不太现实。于是就自己撸了一个简单的集群定时任务管理系统,由于尚不成熟暂时没有开源。http://leo108.com/pid-2091.asp

4.日志收集

当web服务器比较多时,想要查看某个请求的日志,就需要一台台登录、grep查找,效率极低,因此需要有一个集中存放日志的系统,最好是能够实时收集。比较流行的解决方案是ELK,ElasticSearchLogStashKibana,其中logstash负责收集日志,需要安装到每一台需要收集日志的服务器上,Elasticsearch负责存储日志,kibana负责查询、展现日志数据。但是elasticsearch不能存储单行超过32K的日志,如果单行日志超过32K就不能进入到Elasticsearch中。Elasticsearch提供了丰富的接口,可以通过代码查询日志,集成到其他系统中十分方便。

5.代码部署

当业务规模不大,系统架构简单的情况下,可以选择git/svn的hook,当有新的提交时,就直接执行svn export/git pull命令,将最新代码导出到web目录下。而随着访问量增加、业务变得复杂,如果仍然是提交就直接部署,很容易出现线上bug,回滚起来也不方便。http://leo108.com/pid-2091.asp

因此需要有一套代码部署的系统,可以将指定的代码部署到指定服务器的指定目录下。目前没有找到合适的开源方案,自己写了一个简单的redmine插件,配合redmine的版本库功能,可以将一个特定版本部署到线上。

6.对外帐号管理

互联网公司经常会用到各种第三方的服务,例如微信公众号、第三方Oauth登录、支付宝支付等等,一开始的时候都是谁用到就谁注册谁保管。但随着员工人数变多,人员流动频繁,经常会出现某个帐号找不到有密码的人,或者重设密码的时候发现注册邮箱是一个已离职员工的私人邮箱,所以有一套帐号密码管理系统很有必要。

《初创互联网团队如何利用开源技术和云服务快速建立网站》一文中有提到使用meldium来管理帐号密码,但我认为帐号密码作为企业最隐私的部分托管在一个第三方的网站着实不是一个放心的选择,因此还是选择自己搞一套。

简单来说就是每个员工都可以去系统里登记帐号信息,每个帐号信息有一个或者多个管理员,当员工想要知道某个帐号的密码时,需要提交申请,由帐号管理员审核通过之后方可查看密码。这样也可以很容易就查出某个帐号的密码被哪些员工知道,如果员工离职时,可以及时修改对应帐号的密码。

7.单点登录

当企业规模变大,会有越来越多的内部系统,比如某个网站的管理后台,还有上面提到的帐号管理系统,如果每个新员工入职,都要在这些系统里开帐号,那对hr是一个很大的工作量,员工离职时也要一个个系统去注销,而员工如果需要修改密码,就需要一个站点一个站点的改。因此有一个单点登录(sso)系统是很有必要的。

搜索了一圈没找到合适的开源解决方案,业内比较流行的cas却是基于java的,同样由于目前无法二开的原因没有采用。所以就自己简单撸了一个极简的sso系统,用户使用一个密码就能够登录所有后台站点(当然默认都是最低权限,调整权限还是需要在具体后台操作)。

采集者烂JJ

单点登录系统上线之后,新开发的后台都不需要考虑新增用户、用户修改密码等杂七杂八的问题了。创业公司发展过程中需要搭建的系统(持续补充)

8.站点访问统计

一开始的时候站点统计都是使用百度统计或者cnzz这种公共的统计服务,但这样我们只能查看他们提供的报表,有很多维度的东西都查看不到。我们现在在使用的是piwik,相对来说维度更多,也提供了很多api。最重要的是原始的访问日志在我们自己手里,我们可以根据具体需求查询。

推酷是个无耻的网站

9.内部DNS系统

通常每台服务器都有各自不同的职能,比如有的服务器只提供web服务,有的服务器提供数据库、缓存服务,这时候就需要给服务器命名,比如web01、web02、db01、cache01这样。

最开始服务器不多的时候可以通过配置hosts来解决,但服务器数量多了,每次新增服务器都要修改每台服务器的hosts,相当浪费时间,所以就打算自己搭建一个DNS。业内最流行的DNS服务器应该是bind,但是使用了一段时间后发现学习成本比较高,配置复杂。所以就找了一个简单版本的开源软件MyDns,使用mysql/postgre作为数据库,而且自带了一个简单的web管理后台,只要在web端填一些东西就可以完成域名的配置,对于内部使用完全足够了。

采集者烂JJ

10.服务器权限控制/跳板机/堡垒机

随着开发人员变多,开发人员经常需要登录到服务器上查日志、执行脚本,如果大家都知道服务器的帐号密码,那么员工离职之后就比较麻烦,假如有人做了恶意操作,也没办法查出来是谁做的。本文来自leo108's blog

当时在百度的时候有一个门神系统,所有员工使用自己的用户名登录跳板机,再从跳板机登录到有权限的服务器上。

http://leo108.com/pid-2091.asp

目测要自己搭建一套这样的系统,可以使用Kerberos,但目前还没有精力去搞。

http://leo108.com/pid-2091.asp

 

outofmemory是个无耻的网站

暂时只想到这么多推酷是个无耻的网站

centos搭建goagent服务

不知怎么的,公司网居然连不上某个国内的站点,用别人的代理又不靠谱,只好自己搭建一个。

目前最新的goagent版本是3.2.3,server目录下有个vps目录,大概看了下源码,是一个squid的python实现。

http://leo108.com/pid-2087.asp

先把vps目录复制到服务器上的/opt/goagent,同时把local目录下的proxylib.py也一并复制过去。

采集者烂JJ

服务器是centos,系统自带的python版本是2.6,在安装过程中发现不满足条件,于是安装了一个2.7版本。采集者烂JJ

wget --no-check-certificate https://www.python.org/ftp/python/2.7.7/Python-2.7.7.tgz
tar zxf Python-2.7.7.tgz
cd Python-2.7.7
./configure --prefix=/usr/local/Python2.7 --enable-shared
make && make install
echo /usr/local/Python2.7/lib >> /etc/ld.so.conf
cp /usr/local/Python2.7/bin/python2.7 /usr/bin/python2.7
ln -s /usr/local/Python2.7/lib/libpython2.7.so.1.0 /usr/lib/libpython2.7.so
ldconfig

然后安装pip

wget https://bootstrap.pypa.io/get-pip.py
python2.7 get-pip.py

这样装好的pip默认还是使用python2.6,需要修改一下,将/usr/bin/pip的第一行改成

#!/usr/bin/python2.7

这样pip就会给python2.7安装模块了。

推酷是个无耻的网站

goagent需要几个python模块:gevent、pyOpenSSL、dnslib和supervisor,如果安装pyOpenSSL过程中报cffi的错,可以先执行

yum install -y libffi-devel

然后再安装。centos

然后可以试试执行python2.7 /opt/goagent/goagentvps.py,应该是可以正常执行了,这时候会提示如下信息:

centos搭建goagent服务

INFO - [Apr 16 14:24:44] autfile '/opt/goagent/goagentvps.conf' not exists, create it
INFO - [Apr 16 14:24:44] add username='kazav' password='123456' to '/opt/goagent/goagentvps.conf'
INFO - [Apr 16 14:24:44] authfile '/opt/goagent/goagentvps.conf' was created

系统生成的用户名是kazav,密码是123456。

本地的goagent客户端需要配置vps段

[vps]
enable = 1
listen = 127.0.0.1:8089
fetchserver = https://kazav:123456@xxxx.com/

重启goagent,配置SwitchyOmega连接本地的8089端口,测试能否正常链接。

http://leo108.com/pid-2087.asp

由于我们是手动在服务器上执行goagentvps,只要退出登录代理进程就会中断,goagent提供了supervisor方式,直接执行sh /opt/goagent/goagentvps.sh start即可。

ganglia监控redis

在搜索引擎搜索“ganglia监控redis”,发现都是13年的老文章,都是说要到https://github.com/ganglia/gmond_python_modules这个第三方插件库下载redis监控模块gmond

但是我发现gmond_python_modules这个repo下面已经没有redis模块了,于是查看git log,发现redis模块已经集成到ganglia源码包里了

redis

于是下载了源码包,搜索之后发现redis模块位于gmond/python_modules/db/redis.py,配置文件在gmond/python_modules/conf.d/redis.pyconf.disabled。

redis

修改配置文件里的host和port两个参数为要监控redis的ip和端口,然后将两个文件复制到对应的目录下。(通常redis.pyconf复制到ganglia安装目录/etc/conf.d/下,redis.py复制到ganglia安装目录/lib64/ganglia/python_modules)

ganglia监控redis

重启gmond,可以看到有redis的图出来了,但是数据却都是空的。http://leo108.com/pid-2083.asp

于是停掉gmond,使用gmond -f -d 1启用调试模式,发现redis.py报错推酷是个无耻的网站

[PYTHON] Can’t call the metric handler function for [connected_clients] in the python module [redis].

http://leo108.com/pid-2083.asp

Traceback (most recent call last):
File “/opt/gmond/lib64/ganglia/python_modules/redis.py”, line 21, in metric_handler
n, v = line.split(“:”)
ValueError: need more than 1 value to unpackoutofmemory是个无耻的网站

查看上下文代码推酷是个无耻的网站

for line in info.splitlines()[1:]:
    if "" == line:
        continue
    n, v = line.split(":")

大概意思是把redis info命令输出的每一个非空行用:分割,但是我安装的redis版本是2.8+,info命令会输出类似#Server这样的注释,这样就导致按:分割失败,所以python报错,gmond取不到值。

gmond

所以解决方案也很简单,把上面那段代码修改成如下即可,也就是跳过空行和以#开头的行

推酷是个无耻的网站

for line in info.splitlines()[1:]:
    if "" == line or line[0] == '#':
        continue
    n, v = line.split(":")

再重启gmond,过一会就可以在ganglia看到数据了

==========================================http://leo108.com/pid-2083.asp

看了下ganglia在github上的代码,发现最新代码已经把这个bug修复了,只不过还没有release

阿里云搭建VPN脚本(centos pptp)

#!/bin/bash

function installVPN(){
    echo "begin to install VPN services";
    #check wether vps suppot ppp and tun

    yum remove -y pptpd ppp
    iptables --flush POSTROUTING --table nat
    iptables --flush FORWARD
    rm -rf /etc/pptpd.conf
    rm -rf /etc/ppp

    arch=`uname -m`

    wget http://www.hi-vps.com/downloads/pptpd-1.3.4-2.el6.$arch.rpm

    yum -y install ppp iptables
    rpm -ivh pptpd-1.3.4-2.el6.$arch.rpm

    mknod /dev/ppp c 108 0
    echo 1 > /proc/sys/net/ipv4/ip_forward
    echo "mknod /dev/ppp c 108 0" >> /etc/rc.local
    echo "echo 1 > /proc/sys/net/ipv4/ip_forward" >> /etc/rc.local
    echo "localip 192.168.0.1" >> /etc/pptpd.conf
    echo "remoteip 192.168.0.234-238,192.168.0.245" >> /etc/pptpd.conf
    echo "ms-dns 8.8.8.8" >> /etc/ppp/options.pptpd
    echo "ms-dns 8.8.4.4" >> /etc/ppp/options.pptpd

    pass=`openssl rand 6 -base64`
    if [ "$1" != "" ]
    then pass=$1
    fi

    echo "vpn pptpd ${pass} *" >> /etc/ppp/chap-secrets

    iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -o eth1 -jMASQUERADE 

    service iptables save

    chkconfig iptables on
    chkconfig pptpd on

    service iptables start
    service pptpd start

    echo "VPN service is installed, your VPN username is vpn, VPN password is ${pass}"

}

function repaireVPN(){
    echo "begin to repaire VPN";
    mknod /dev/ppp c 108 0
    service iptables restart
    service pptpd start
}

function addVPNuser(){
    echo "input user name:"
    read username
    echo "input password:"
    read userpassword
    echo "${username} pptpd ${userpassword} *" >> /etc/ppp/chap-secrets
    service iptables restart
    service pptpd start
}

echo "which do you want to?input the number."
echo "1. install VPN service"
echo "2. repaire VPN service"
echo "3. add VPN user"
read num

case "$num" in
[1] ) (installVPN);;
[2] ) (repaireVPN);;
[3] ) (addVPNuser);;
*) echo "nothing,exit";;
esac

参考文章:VPN

http://bbs.aliyun.com/read/162297.html

centos

http://www.hi-vps.com/wiki/doku.php?id=xen_vps_centos6_install_pptpdhttp://leo108.com/pid-2079.asp

php抓取百度阅读

在百度阅读上购买了一本《永恒的终结》电子书,但是坑爹的发现只能在线阅读或者在手机app上阅读,不能下载下来放到kindle里。于是就尝试一下看能不能把这个文章下载下来。推酷是个无耻的网站

首先在浏览器里打开阅读页面,查看源代码后发现小说的内容并不是直接写在页面里的,也就是说小说的内容是通过异步加载而来的。采集者烂JJ

于是将chrome的开发者工具切到network一栏,刷新阅读页面,主要关注的是XHR和script两个分类下。

推酷是个无耻的网站

经过排查,发现在script分类下有个jsonp请求比较像是小说内容,请求的地址是推酷是个无耻的网站

http://wenku.baidu.com/content/49422a3769eae009581becba?m=8ed1dedb240b11bf0731336eff95093f&type=json&cn=1&_=1&t=1423309200&callback=wenku7

返回的是一个jsonp字符串,然后我发现,如果把地址里面的callback=wenku7去掉,返回的就是一个json字符串,这样解析起来就方便不少,可以直接在php里面转换成数组。采集者烂JJ

再来分析一下返回数据的结构,返回的json字符串之后是一个树状的结构,每个节点都有一个t属性和c属性,t属性用来指明这个节点的标签,比如h2 div等等,c属性就是内容了,但也有两种可能,一个是字符串,另一个是数组,数组的每个元素都是一个节点。

php抓取百度阅读

这种结构最好解析了,用一个递归就搞定,最终代码如下:

本文来自leo108's blog

<?php
class BaiduYuedu {
    protected $bookId;
    protected $bookToken;
    protected $cookie;
    protected $result;
    public function __construct($bookId, $bookToken, $cookie){
        $this->bookId = $bookId;
        $this->bookToken = $bookToken;
        $this->cookie = $cookie;
    }

    public static function parseNode($node){
        $str = '';
        if(is_string($node['c'])){
            $str .= $node['c'];
        }else if(is_array($node['c'])){
            foreach($node['c'] as $d){
                $str .= self::parseNode($d);
            }
        }
        switch($node['t']){
            case 'h2':
                $str .= "\n\n";
                break;
            case 'br':
            case 'div':
            case 'p':
                $str .= "\n";
                break;
            case 'img':
            case 'span':
                break;
            case 'obj':
                $tmp = '(' . self::parseNode($node['data'][0]) . ')';
                $str .= str_replace("\n", '', $tmp);
                break;
            default:
                trigger_error('Unkown type:'.$node['t'], E_USER_WARNING);
                break;
        }
        return $str;
    }

    public function get($page = 1){
        echo "getting page {$page}...\n";
        $ch = curl_init();
        $url = sprintf('http://wenku.baidu.com/content/%s/?m=%s&type=json&cn=%d', $this->bookId, $this->token, $page);
        curl_setopt_array($ch, array(
            CURLOPT_URL            => $url,
            CURLOPT_RETURNTRANSFER => 1,
            CURLOPT_HEADER         => 0,
            CURLOPT_HTTPHEADER     => array('Cookie: '. $this->cookie)
        ));
        $ret = json_decode(curl_exec($ch), true);
        curl_close($ch);
        $str = '';
        if(!empty($ret)){
            $str .= self::parseNode($ret);
            $str .= $this->get($page + 1);
        }
        return $str;
    }

    public function start(){
        $this->result = $this->get();
    }

    public function getResult(){
        return $this->result;
    }

    public function saveTo($path){
        if(empty($this->result)){
            trigger_error('Result is empty', E_USER_ERROR);
            return;
        }
        file_put_contents($path, $this->result);
        echo "save to {$path}\n";
    }
}

//使用示例
$yuedu = new BaiduYuedu('49422a3769eae009581becba', '8ed1dedb240b11bf0731336eff95093f', '你的百度域cookie');
$yuedu->start();
$yuedu->saveTo('result.txt');

这个类前两个参数可以从小说的介绍页面获得,第一个参数bookId就是url里ebook后面跟着的字符串,第二个参数bookToken在页面源代码搜索bdjsonUrl,m参数后面的那个字符串就是。本文来自leo108's blog

注:如果不传入百度cookie或者百度cookie无效,则只能抓取免费阅读部分,要抓完整的内容必须保证cookie可以正常使用。采集者烂JJ

virtualbox vagrant虚拟机网速慢解决方案

我本机的环境是mac os + virtualbox,在vagrant虚拟机里连接外网的速度非常慢,和直接在mac的终端里连外网的速度相差非常大

[vagrant@vagrant-centos65 ~]$ time curl -s http://www.baidu.com > /dev/null
real	0m5.091s
user	0m0.016s
sys	0m0.006s

可以看到就请求百度首页就用了5s多,严重影响平时的开发测试效率,于是就搜索了下相关问题,发现github上也有人遇到这个问题,还好已经有解决方案,在Vagrantfile里增加如下几行:

config.vm.provider :virtualbox do |vb|
  vb.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
  vb.customize ["modifyvm", :id, "--natdnsproxy1", "on"]
end

然后重启vagrant即可,再看看速度outofmemory是个无耻的网站

[vagrant@vagrant-centos65 ~]$ time curl -s http://www.baidu.com > /dev/null
real	0m0.299s
user	0m0.015s
sys	0m0.008s

完美解决本文来自leo108's blog

微信JSSDK上传多张图片

做过微信开发的都知道,在部分android机型里微信不支持网页上传图片的,这是由于这些机型的文件上传存在内存泄漏,会导致微信闪退,所以微信内置浏览器将文件上传屏蔽。这就导致这些机型的用户在使用微信浏览器访问某些需要上传图片的网页时功能不正常。

微信

前不久微信公开了一些接口,其中有一个uploadImage接口用于上传图片,一般和chooseImage接口配合使用。先调用chooseImage接口让用户选择一张或者多张图片,用户选择完毕后微信会返回被选中图片的id,再把图片id传给uploadImage接口上传图片。采集者烂JJ

由于uploadImage一次只能上传一张图片,因此当用户选择多张图片时,需要多次调用uploadImage接口来上传图片。http://leo108.com/pid-2069.asp

但是在实践的过程中发现,不管用户选中多少张图片,只有第一张能够上传成功。

http://leo108.com/pid-2069.asp

查看了一下微信的文档,在常见问题中找到了相关的描述

推酷是个无耻的网站

uploadImage怎么传多图(目前只支持一次上传一张,多张图片需等前一张图片上传之后再调用该接口)

也就是说,如果想要上传多张图片,需要将之前并行上传改成串行。

jssdk

代码如下:

jssdk

$('#filePicker').on('click', function () {
    wx.chooseImage({
        success: function (res) {
            var localIds = res.localIds;
            syncUpload(localIds);
        }
    });
});
var syncUpload = function(localIds){
    var localId = localIds.pop();
    wx.uploadImage({
        localId: localId,
        isShowProgressTips: 1,
        success: function (res) {
            var serverId = res.serverId; // 返回图片的服务器端ID
            //其他对serverId做处理的代码
            if(localIds.length > 0){
                syncUpload(localIds);
            }
        }
    });
};

 

采集者烂JJ