分类目录归档:python

Python使用Kerberos认证查询Impala

最近做QoS报告,数据来源于Impala,客户端认证采用的是Kerberos。
Impala是Cloudera公司开发并开源的一款基于HDFS/Hbase的MPP SQL引擎,它提供SQL语义,能够查询存储在Hadoop的HDFS和HBase中的PB级大数据。Kerberous本身是一个网络认证授权协议,借由中心服务器认证,对通信双方的客户端/服务端进行授权而不需要传递双方的密码。Kerberos的认证流程比较有意思,分为三个阶段

  • 客户端认证
    • 1 客户端发送自己用户名
    • 2 认证服务器返回使用客户端密钥加密的Client/TGS会话密钥和使用票据授权服务器密钥加密的TGT, 包括sessions key,用户信息及有效期
    • 3 客户端使用自己的密钥解密出Client/TGS会话密钥
  • 服务授权
    • 1 客户端发送两条消息:接收到的TGT和所请求的服务ID;使用Client/TGS会话密钥加密的用户ID和时间戳
    • 2 票据授权服务器使用自己的密钥解密TGT得到客户端的Client/TGS会话密钥,然后使用它解密出用户ID并进行认证。返回使用所请求服务端密钥加密的client-server票据和使用Client/TGS会话密钥加密的Client/Server会话密钥
    • 3 客户端使用Client/TGS会话密钥(Client/TGS Session Key)解密出Client/Server会话密钥
  • 服务请求
    • 1 客户端发送两条消息:使用所请求服务端密钥加密的client-server票据及使用Client/Server会话密钥加密的用户ID和时间戳
    • 2 服务端使用自己的密钥解密client-server票据从而得到Client/Server会话密钥,使用该密钥解密获得用户信息并认证。返回使用Client/Server会话密钥的新时间戳
    • 3 客户端使用Client/Server会话密钥解密该消息,认证结束并请求服务
    • 4 服务端提供服务

在CentOS上安装Kerberos

yum install krb5-devel pam_krb5 krb5-libs krb5-workstation

编辑配置

vim /etc/krb5.conf

配置KDC,认证服务器

[logging]
default = FILE:/var/log/krb5libs.log
kdc = FILE:/var/log/krb5kdc.log
admin_server = FILE:/var/log/kadmind.log
[libdefaults]
default_realm = EXAMPLE.COM
dns_lookup_realm = false
dns_lookup_kdc = true
ticket_lifetime = 24h
renew_lifetime = 7d
forwardable = true
default_tkt_enctypes = rc4-hmac
default_tgs_enctypes = rc4-hmac
permitted_enctypes = rc4-hmac
[realms]
EXAMPLE.COM = {
default_domain = example.com
kdc = kdc01.example.com
kdc = kdc02.example.com
admin_server = adc01.example.com
admin_server = adc02.example.com
}
[domain_realm]
.example.com = EXAMPLE.COM
example.com = EXAMPLE.COM

测试一下

[root@localhost rc]# kinit abc.xyz@EXAMPLE.COM
Password for abc.xyz@EXAMPLE.COM:

注意这个配置文件每行前面的空格被删掉,是因为在VirtualBox里面每行开头有莫名其妙的乱码,Linux下并不可见,在EditPlus下面才发现,否则会乱报错

kinit: Improper format of Kerberos configuration file while initializing Kerberos 5 library
kinit: Cannot find KDC for realm "EXAMPLE.COM" while getting initial credentials

查看一下认证的ticket

[root@localhost vagrant]# klist
Ticket cache: FILE:/tmp/krb5cc_0
Default principal: abc.xyz@EXAMPLE.COM

Valid starting       Expires              Service principal
09/21/2017 08:30:50  09/21/2017 18:30:50  krbtgt/EXAMPLE.COM@EXAMPLE.COM
        renew until 09/28/2017 08:30:42

这个ticket在28号就会过期了,到时候又要输入密码,这样也不便于自动化程序使用。可以使用ktutil创建keytab文件

$ ktutil
ktutil:  addent -password -p abc.xyz@EXAMPLE.COM -k 1 -e RC4-HMAC
Password for abc.xyz@EXAMPLE.COM:
ktutil:  wkt abc.xyz.keytab
ktutil:  q
$ ls
abc.xyz.keytab

测试一下

$ kinit -kt abc.xyz.keytab abc.xyz@EXAMPLE.COM
$ klist -k abc.xyz.keytab
Keytab name: FILE:abc.xyz.keytab
KVNO Principal
---- --------------------------------------------------------------------------
  1 abc.xyz@EXAMPLE.COM

之后便可以使用kinit自动更新ticket了。注意,如果更换了密码,需要重新生成新的keytab。
另外,相同用户生成的授权ticket在任意一台机器上都是相同的, kinit时会自动同步回来的。
公司的大数据平台使用Hue来提供基于web界面的查询,Impala也支持使用ODBC方式查询。在Python里使用的是impyla来查询,首先安装sasl的依赖

yum install libgsasl-devel cyrus-sasl-devel cyrus-sasl-gssapi
pip install impyla thrift_sasl

测试脚本

from impala.dbapi import connect
conn = connect(host="impalad.example.com", port=21050, auth_mechanism='GSSAPI', kerberos_service_name='impala', database='acme')
cur =  conn.cursor()
cur.execute(r'SELECT * FROM acme WHERE dt="2017-09-12" LIMIT 5')
print(cur.fetchall())

运行下

python test.py

如下报错,则是服务器不能连接,检查一下网络,DNS/hosts及VPN

thriftpy.transport.TTransportException: TTransportException(type=1, message="Could not connect to ('impalad.example.com', 21050)")

如下报错,CentOS则是需要cyrus-sasl-gssapi模块

thriftpy.transport.TTransportException: TTransportException(type=1, message="Could not start SASL: b'Error in sasl_client_start (-4) SASL(-4): no mechanism available: No worthy mechs found'")

参考链接:
Impala:新一代开源大数据分析引擎
大数据时代快速SQL引擎-Impala
CDH 5.2中Impala认证集成LDAP和Kerberos
Kerberos
Configuring Kerberos Authentication for Windows
Speaking Kerberos with KNIME Big Data Extensions

使用Supervisor 管理监控进程

Supervisor是用Python写的一款应用程监控管理工具,能够启动,停止,重启死进程,提供web管理界面,XML-RPC接口及事件监听。通常我们写了一些脚本都不会带有daemon功能,而是加&或者nohub,screen什么的丢到后台去运行,同时使用corntab定时检测脚本是否存活,以便重新运行脚本。使用Supervisor可以将这些脚本,程序转为守护进程,自动重启它们;还可以监控机器的进程运行状况,输出警报等。
Supervisor只能运行于Python 2.x的环境,但子进程可以为其他任意程序,比如Python 3,PHP等。这里使用pip来安装

$ wget https://bootstrap.pypa.io/get-pip.py
$ python -V
$ sudo python get-pip.py

生成配置文件及日志目录

$ sudo echo_supervisord_conf > /etc/supervisord.conf
$ mkdir /var/log/supervisor
$ chmod 655 /var/log/supervisor

启动supervisord

$ sudo supervisord -c /etc/supervisor
$ supervisorctl
$ Server requires authentication
$ Username:user
$ Password:

$ supervisor> status
$ supervisor> help

default commands (type help <topic>):
=====================================
add    exit      open  reload  restart   start   tail
avail  fg        pid   remove  shutdown  status  update
clear  maintail  quit  reread  signal    stop    version
$ supervisor> quit

这里没有任何进程。以下为常用命令:

  • supervisorctl stop program 停止某个进程
  • supervisorctl start program 启动某个进程
  • supervisorctl restart program 重启某个进程
  • supervisorctl stop group 重启属于group分组的所有进程(start,restart同理)
  • supervisorctl stop all 停止全部进程,注:start、restart、stop都不会载入最新的配置文件
  • supervisorctl reload 载入最新配置文件,停止原有进程并按新配置启动进程
  • supervisorctl update 根据最新配置文件,启动新配置或有改动的进程,没有改动的进程不受影响
  • 编辑supervisord.conf启用web界面,账号密码为web及supervisorctl共用,必须更改

    $ sudo vim /etc/supervisord.conf
    #取消以下行的注释
    [inet_http_server]         ; inet (TCP) server disabled by default
    port=*:9002           ; ip_address:port specifier, *:port for all iface
    username=user              ; default is no username (open server)
    password=123               ; default is no password (open server)
    
    #添加新应用qrd
    [program:qrd]
    command = /usr/bin/gunicorn --access-logfile - --workers 3 --bind 127.0.0.1:8000 qrd.wsgi
    directory = /home/qrd/qrd/
    user = root
    autostart = true
    autorestart = true
    startsecs = 5
    startretries = 3
    stdout_logfile = /var/log/supervisor/qrd.log
    stderr_logfile = /var/log/supervisor/qrd.log
    

    关于supervisord.conf配置项,命令释义可以参考这里

  • command 为要运行的脚本或程序
  • directory 为脚本或程序运行时的工作目录
  • user 为脚本或程序运行时的用户
  • autostart 随supervisord启动
  • startsecs 启动后等待时间,过了这个时间没起来就重新启动
  • startretries 启动失败后重试的次数
  • stdout_logfile,stderr_logfile 为输出日志
  • 重新加载所有应用

    $ supervisorctl
    Server requires authentication
    Username:user
    Password:
    
    supervisor> reload
    Really restart the remote supervisord process y/N? y
    Restarted supervisord
    supervisor> status
    qrd                              RUNNING   pid 3861, uptime 0:00:22
    

    可以看到定义的qrd程序已经起来了。如果qrd程序意外退出了,那么supervisord将会重启它。如果杀掉了supervisord,那么qrd对应的进程也将被杀死。也可以去web界面查看http://127.0.0.1:9002/

    可以通过web页面来启动,停止进程,查看日志等。
    再增加下Celery的Worker,Beat配置

    ; ==================================
    ;  celery worker supervisor example
    ; ==================================
    
    [program:qrdworker]
    ; Set full path to celery program if using virtualenv
    command=/usr/bin/celery worker -A qrd --loglevel=INFO
    
    ; Alternatively,
    ;command=celery --app=your_app.celery:app worker --loglevel=INFO -n worker.%%h
    ; Or run a script
    ;command=celery.sh
    
    directory=/home/qrd/qrd/
    user=nobody
    numprocs=1
    stdout_logfile=/var/log/supervisor/qrdworker.log
    stderr_logfile=/var/log/supervisor/qrdworker.log
    autostart=true
    autorestart=true
    startsecs=10
    
    ; Need to wait for currently executing tasks to finish at shutdown.
    ; Increase this if you have very long running tasks.
    stopwaitsecs = 600
    
    ; Causes supervisor to send the termination signal (SIGTERM) to the whole process group.
    stopasgroup=true
    
    ; Set Celery priority higher than default (999)
    ; so, if rabbitmq is supervised, it will start first.
    priority=1000
    
    ; ================================
    ;  celery beat supervisor example
    ; ================================
    
    [program:qrdbeat]
    ; Set full path to celery program if using virtualenv
    command=/usr/bin/celery -A qrd beat -l info -s /tmp/celerybeat-schedule
    
    ; remove the -A myapp argument if you aren't using an app instance
    
    directory=/home/qrd/qrd/
    user=nobody
    numprocs=1
    stdout_logfile=/var/log/supervisor/beat.log
    stderr_logfile=/var/log/supervisor/beat.log
    autostart=true
    autorestart=true
    startsecs=10
    
    ; Causes supervisor to send the termination signal (SIGTERM) to the whole process group.
    stopasgroup=true
    
    ; if rabbitmq is supervised, set its priority higher
    ; so it starts first
    priority=999
    

    然后启用它们

    supervisor> update
    qrdbeat: added process group
    supervisor> status
    qrd                              RUNNING   pid 4468, uptime 0:03:49
    qrdbeat                          BACKOFF   Exited too quickly (process log may have details)
    qrdworker                        RUNNING   pid 4469, uptime 0:03:49
    

    查看下进程

    $ sudo ps aux | grep python
    root      1038  0.0  3.1 562392 15720 ?        Ssl  01:49   0:01 /usr/bin/python -Es /usr/sbin/tuned -l -P
    root      3992  0.1  3.0 222124 15224 ?        Ss   03:49   0:00 /bin/python /bin/supervisord -c /etc/supervisord.conf
    root      3993  0.3  3.8 211868 19296 ?        S    03:49   0:00 /usr/local/python3/bin/python3.6 /usr/bin/gunicorn --access-logfile - --workers 3 --bind 127.0.0.1:8000 qrd.wsgi
    root      3996  0.3  6.9 264048 34780 ?        S    03:49   0:00 /usr/local/python3/bin/python3.6 /usr/bin/gunicorn --access-logfile - --workers 3 --bind 127.0.0.1:8000 qrd.wsgi
    root      3998  0.3  6.9 264056 34784 ?        S    03:49   0:00 /usr/local/python3/bin/python3.6 /usr/bin/gunicorn --access-logfile - --workers 3 --bind 127.0.0.1:8000 qrd.wsgi
    root      3999  0.4  6.9 264024 34784 ?        S    03:49   0:00 /usr/local/python3/bin/python3.6 /usr/bin/gunicorn --access-logfile - --workers 3 --bind 127.0.0.1:8000 qrd.wsgi
    root      4014  0.0  0.1 112660   976 pts/0    R+   03:50   0:00 grep --color=auto python
    

    除了program标签,Supervisor也支持group标签,用来启动一系列的program,比如先启动数据库,再启动web服务器。
    Supervisor也可以用来管理PHP进程,比如使用 Supervisor 管理 Laravel 队列进程
    Docker官方推荐一个docker容器只运行一个服务,如果你想启动多个脚本或程序,可以使用Supervisor会更简单点
    如果需要Supervisor开启启动,可以使用GitHub上的脚本和配置。CentOS 7上可以配置为service,由systemctl来管理:

    $ sudo vim /etc/systemd/system/supervisord.service
    

    内容如下:

    # supervisord service for systemd (CentOS 7.0+)
    # by ET-CS (https://github.com/ET-CS)
    [Unit]
    Description=Supervisor daemon
    
    [Service]
    Type=forking
    ExecStart=/usr/bin/supervisord
    ExecStop=/usr/bin/supervisorctl $OPTIONS shutdown
    ExecReload=/usr/bin/supervisorctl $OPTIONS reload
    KillMode=process
    Restart=on-failure
    RestartSec=42s
    
    [Install]
    WantedBy=multi-user.target
    

    然后启用

    $ sudo systemctl enable supervisord.service
    Created symlink from /etc/systemd/system/multi-user.target.wants/supervisord.service to /etc/systemd/system/supervisord.service.
    $ sudo systemctl start supervisord.service
    $ sudo ps aux | grep python
    

    虽然supervisorctl及web界面都只能管理本机,但是Supervisor提供了XML-RPC接口,可以获取进程运行信息,因此诞生了许多监控平台,以便监控服务器集群的进程的运行状况。
    Supervisor还提供了event listener配置,在这里Supervisor将会通知脚本其他进程的运行状态变更的事件,可以用来发送警报监控等。

    参考链接:
    使用Supervisor3.2.1基于Mac10.10.3对系统进程进行管理
    supervisor的配置浅析
    Python 进程管理工具 Supervisor 使用教程
    Supervisor进程监护使用指南
    supervisord 的 XML-RPC API 使用说明
    Supervisor Event Listener
    Dockerizing Nginx and SSH using Supervisord

    PHP MongoDB Replica Set应用

    MongoDB是个面向文档管理的NoSQL数据库,能够直接存取JSON数据,支持海量数据。公司内部的一个单点登录系统使用MongoDB来存储用户的session,以便在不同应用,服务器之间共享登录信息,解决了服务器切换用户状态丢失(被登出)的问题。单点登录系统前端采用PHP,与LDAP交互认证用户信息,提供登录界面,API等。MongoDB则采用Replica Set模式以便支持高可用。
    在CentOS 6.5上安装MongoDB,首先添加源仓库

    $ suod vim /etc/yum.repos.d/mongodb.repo
    [mongodb]
    name=MongoDB Repository
    baseurl=http://downloads-distro.mongodb.org/repo/redhat/os/x86_64/
    gpgcheck=0
    enabled=0
    

    安装,启动,连接MongoDB

    $ sudo yum --enablerepo=mongodb install mongodb-org
    $ sudo /sbin/chkconfig --levels 235 mongod on
    $ sudo mongod --port 27017 --dbpath /data/db1
    $ mongo --port 27017
    

    创建用户SSOReplUser

    > use admin 
    > db.createUser( { user: "SSOReplUser", pwd: "<password>", roles: [ { role: "root", db: "admin" } ] });
    

    创建集群要用到的key

    $ sudo openssl rand -base64 741 > /home/sso/mongodb-keyfile 
    $ sudo chmod 600 /home/sso/mongodb-keyfile
    

    编辑MongoDB配置文件

    $ sudo vim /etc/mongod.conf
    # Replication Options 
    # in replicated mongo databases, specify the replica set name here 
    replSet=SSOReplSet 
    # maximum size in megabytes for replication operation log 
    #oplogSize=1024 
    # path to a key file storing authentication info for connections # between replica set members keyFile=/home/sso/mongodb-keyfile
    

    重启mongod服务

    $ sudo /etc/init.d/mongod stop   
    $ sudo /usr/bin/mongod -f /etc/mongod.conf
    

    使用SSOReplUser登录

    > use admin 
    > db.auth("SSOReplUser", "<password>");
    

    初始化集群

    > rs.initiate()
    > rs.conf()   
    {
      "_id": "SSOReplSet",
      "version": 1,
      "members": [
        {
          "_id": 1,
          "host": "192.168.33.10:27017"
        }
      ]
    }
    

    在其他机器上按照上面步骤安装MongoDB,更改配置,复制对应的mongodb-keyfile并启动。
    回到192.168.33.10并添加机器

    SSOReplSet:PRIMARY> rs.add("192.168.33.11:27017") 
    SSOReplSet:PRIMARY> rs.add("192.168.33.12:27017") 
    SSOReplSet:PRIMARY> rs.add("192.168.33.13:27017")
    #SSOReplSet:PRIMARY> rs.remove("192.168.33.13:27017")
    

    在从机上认证查看

    SSOReplSet:SECONDARY> use admin 
    SSOReplSet:SECONDARY> db.auth("SSOReplUser", "<password>");   
    SSOReplSet:SECONDARY> rs.status()
    

    MongoDB的Replica set模式最少要求3台机器,以便在PRIMARY机器故障时,能够自我选举出新的PRIMARY,但需要n/2+1的机器投票同意。例如刚才配置了4台机器,那么需要4/2+1=3台机器投票,能够承受一台机器故障。如果是集群有5台机器,则能够承受2台机器故障。因此再配置一台不存储数据的Arbiter机器比较合理。
    按照上面的步骤安装,复制mongodb-keyfile文件,但需要更改mongod.conf

    $sudo vim /etc/mongod.conf
    nojournal=true
    

    然后启动mongod服务并在PRIMARY机器上将这台机器加入机器

    SSOReplSet:PRIMARY> use admin   
    SSOReplSet:PRIMARY> db.auth("SSOReplUser", "<password>");   
    SSOReplSet:PRIMARY> rs.addArb("<ip of arbiter>");
    

    PHP的mongo扩展安装比较简单,下载对应版本,启用即可。然而在应用过程中发现登录有点慢,启用看PHP mongo扩展profiling功能,查看PHP日志

    <?php
    \MongoLog::setLevel(\MongoLog::ALL);
    \MongoLog::setModule(\MongoLog::ALL);
    try{
        echo  microtime(true) . PHP_EOL;
        echo "aaa01(primary)" . PHP_EOL;
        $m = new \MongoClient("mongodb://admin:XXXXXXXXX@192.168.33.10:27017/?replicaSet=SSOReplSet ");
        echo  microtime(true) . PHP_EOL;
        echo "aaa01(primay), bbb01(secondary),ccc01(secondary),ddd01(secondary)" . PHP_EOL;
        $m = new \MongoClient("mongodb://admin:XXXXXXXXX@192.168.33.10:27017,192.168.33.11:27017,192.168.33.12:27017,192.168.33.13:27017/?replicaSet=SSOReplSet ");
        echo  microtime(true) . PHP_EOL;
    } catch (\MongoConnectionException $e) {
        var_dump($e);
    }
    

    发现PHP连接MongoDB集群是这样子的

    即:

  • 1)在连接里面配置了几个MongoDB连接服务器,PHP每个都会创建连接去查询
  • 2)从每台服务器上查询出整个集群的服务器列表,再分别ping和连接这些服务器,如果连接不存在或不匹配则创建,无效的则销毁
  • 3)汇总所有服务器返回集群列表
  • 4)选择离自己最近的服务器并使用该与该服务器的连接
  • MongoDB 的写操作默认在Primary节点上操作完成即返回成功;读操作默认是也是从Primary上读取,所以需要去查询服务器。由于SSO应用部署在多个数据中心,网络抖动会造成较大影响,跨数据中心的查询并不会很快,如果每次都去连接并查询整个列表是比较耗时。另外如果在php里面配置的是IP而MongoDB Replica Set里面配置的是域名,则连接名会出现不匹配,而创建新的连接并销毁旧连接,也耗时。如果配置的是域名,则需要DNS解析。由于每台PHP服务器均已配置HA检测,最终每个应用只配置了一台服务器,并统一配置MongoDB集群为IP。而连接最快的是在Primary机器上,可以根据本机是否Primary来做HA:

    #!/usr/bin/env bash
    count=`ps -fe |grep "mongod" | grep -v "grep" | wc -l`
    FILE="/home/scripts-bits/master.conf"
    SERVER=`hostname`
    
    if [ $count -lt 1 ]; then
        rm -f $FILE
    else
        PRIMAY=`/usr/bin/mongo ${SERVER}:27017 --quiet --eval 'printjson(db.isMaster().ismaster);'`
        if [ "$PRIMAY" == "true" ]; then
        	if [ ! -f "$FILE" ]; then
        		touch "$FILE"
        	fi
        	REMOVE=`/usr/bin/mongo ${SERVER}:27017/admin --quiet /home/scripts-bits/mongo_status.js`
        fi
    fi
    

    删除故障节点(not reachable/healthy)的脚本mongo_status.js:

    db.auth('admin','<password>');
    conf=rs.status();
    members=conf["members"];
    for(i in members){
    	if(members[i]["state"] == 8){
    		rs.remove(members[i]["name"]);
    	}
    }
    

    这中间也出现过因为网络/机器故障,导致PHP等待连接超时的情况的,将该机器从集群中移除即可。然而当服务器是启动的(可以到达)情况下,MongoDB故障,则不会超时。可以更改connectTimeoutMS,以便减少等待。
    MongoDB的日志默认都是记录在/var/log/mongodb/下面并且会越来越大。创建Python脚本来定时清除它:

    #!/bin/env python
    import commands
    import datetime,time
    
    def rotate_log(path, expire = 30):
        str_now = time.strftime("%Y-%m-%d")
        dat_now = time.strptime(str_now, "%Y-%m-%d")
        array_dat_now = datetime.datetime(dat_now[0], dat_now[1], dat_now[2])
        lns = commands.getoutput("/bin/ls --full-time %s|awk '{print $6, $9}'" % path)
        for ln in lns.split('\n'):
            ws = ln.split()
            if len(ws) != 2:
                continue
            ws1 = time.strptime(ws[0], "%Y-%m-%d")
            ws2 = datetime.datetime(ws1[0], ws1[1], ws1[2])
            if (array_dat_now - ws2).days > expire:
                v_del = commands.getoutput("/bin/rm -rf %s/%s" % (path, ws[1]))
    
    
    def rotate_mongo():
        # get mongo pid
        mongo_pid = commands.getoutput("/sbin/pidof mongod")
        #print mongo_pid
        # send Sig to mongo
        if mongo_pid != '':
            cmd = "/bin/kill -USR1 %s" % (mongo_pid)
            # print cmd
            mongo_rotate = commands.getoutput(cmd)
        else:
            print "mongod is not running..."
    
    if __name__ == "__main__":
        log_path = "/var/log/mongodb/"
        expire = 30
        rotate_mongo()
        rotate_log(log_path, expire)
    
    

    加入到crontab里面去执行

    10 1 * * * /usr/bin/python /home/sso/mongo_rotate.py > /dev/null 2>&1
    

    参考连接
    mongodb与mysql相比的优缺点
    PHP MongoDB 复制集合
    MongoDB Replication
    MongoDB Enable Auth
    MongoDB Rotate Log Files
    Blocking connect() leads to cumulative timeouts for multiple inaccessible servers
    How Raft consensus algorithm will make replication even better in MongoDB 3.2
    Write Concern for Replica Sets
    MongoDB Read Preference

    在Raspberry Pi上面运行Docker

    公司有个项目是运行在Raspberry Pi上面,这个项目涉及到Perl,Python,Java Applet,并且未来Pi的数量可能达到上千个。最近碰到了一样的代码遵循一样的部署脚本,在不同的树莓派上运行竟然到不同的结果。于是也想将Docker应用到树莓派上面去,使用docker主要优点:
    1 消除环境差异,程序运行所依赖的环境已经一起打包,随身携带了
    2 简化配置,只有docker相关配置,不再需要每个语言单独配置
    3 重复利用
    4 方便升级,之前有差异时,是重写镜像。。
    5 方便管理,可以使用docker已有的工具来管理
    6 性能接近原生应用

    Raspberry Pi在jessie这个版本就已经支持了docker,检查版本:

    $ lsb_release -da
    No LSB modules are available.
    Distributor ID: Raspbian
    Description:    Raspbian GNU/Linux 8.0 (jessie)
    Release:    8.0
    Codename:  jessie
    

    如果你的版本已经支持,直接执行以下命令就可以了:

    curl -sSL https://get.docker.com | sh
    

    如果你的版本不支持,你需要升级系统或重新刷镜像,可以采用hypriot

    #下载
    wget https://downloads.hypriot.com/hypriotos-rpi-v1.0.0.img.zip
    #解压
    unzip hypriotos-rpi-v1.0.0.img.zip
    #查找 SD card ID
    lsblk
    #查找类似/dev/mmcblk0
    
    #弹出SD卡
    umount /run/media/mac/8734-1E4C
    #将镜像写入 SD card
    sudo dd if=hypriotos-rpi-v1.0.0.img of=/dev/mmcblk0 bs=1m
    
    

    然后启动,默认的用户名/密码是pirate/hypriot。
    当然也可以采用Debian或者是Alpine Linux定制(Raspberry Pi仅支持 arm版本的系统,并且只能在arm平台上构建,或者这个)。
    运行测试一下:

    docker run -d -p 80:80 hypriot/rpi-busybox-httpd
    

    访问以下树莓派的IP,就会看到一个非常神器的网页,而这个容器仅仅几M。在这里可以看到基于hypriot的好几个镜像,也可以搜索其他的raspberry pi镜像。
    现在我们使用hypriot/rpi-python来构建一个使用Python Selenium测试带有Java Applet的网页:

    docker run -idt -P --name web hypriot/rpi-python
    docker attach web
    

    进入到容器里面,执行以下命令:

    sudo apt-get update
    #安装openjdk
    sudo apt-get install openjdk-7-jdk
    #安装Firefox
    sudo apt-get install iceweasel
    #安装Java Applet插件
    sudo apt-get install icedtea-7-plugin
    #安装虚拟桌面
    sudo apt-get install xvfb
    #安装Python包管理工具
    sudo apt-get install python-pip
    #安装selenium
    sudo pip install selenium
    #安装Python虚拟桌面库
    sudo pip install pyvirtualdisplay
    

    创建测试test.py:

    from selenium import webdriver
    from selenium.webdriver.common.keys import Keys
    import time
    from pyvirtualdisplay import Display
    from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
    from selenium.webdriver.firefox.firefox_binary import FirefoxBinary
    display = Display(visible=0, size=(800, 600))
    display.start()
    caps = DesiredCapabilities.FIREFOX
    caps["marionette"] = False
    #caps["binary"] = "/usr/bin/firefox"
    firefox_binary = FirefoxBinary("/usr/bin/firefox")
    fp = webdriver.FirefoxProfile()
    fp.set_preference("security.enable_java", True )
    fp.set_preference("plugin.state.java", 2)
    driver = webdriver.Firefox(firefox_profile=fp,firefox_binary=firefox_binary)
    driver.maximize_window()
    driver.get("http://businessconnect.telus.com/bandwidth/en_us/index.html?voip=on&voiplines=1&testlength=15&codec=g711&_=1470641590&startTest=on")
    time.sleep(120)
    mos = driver.find_element_by_class_name("sectiontitle18").find_element_by_tag_name("span").get_attribute("innerHTML")
    print mos
    driver.close()
    display.stop()
    

    运行测试用例:

    python test.py
    

    如果仅仅是这样子,是行不通的,Java Applet的安全询问弹窗超出了浏览器范围是点击不到的,需要更为强大的组件来做,比如。
    这里采用取巧的方法,首先把pyvirtualdisplay相关代码注释掉,在界面上观察具体的执行效果,并点击安全询问,运行通过。然后取消注释,再次运行,就可以了。最后将运行通过的配置文件/root/.icedtea目录拷贝出来,放到其他的树莓派里面就可以了。
    如果碰到报错”selenium.common.exceptions.WebDriverException: Can’t load the profile”,通常是说Firefox或selenium版本太低,如果检查都不是,需要修改WebDriver的超时时间

    nano /usr/local/lib/python2.7/dist-packages/selenium/webdriver/firefox/webdriver.py
    

    timeout从30秒改为120秒:

    class WebDriver(RemoteWebDriver):
    
    
        # There is no native event support on Mac
        NATIVE_EVENTS_ALLOWED = sys.platform != "darwin"
    
    
        def __init__(self, firefox_profile=None, firefox_binary=None, timeout=120,
    

    修改为容器后,提交变更:

    root@black-pearl:/home/pirate# docker ps -l
    CONTAINER ID        IMAGE                COMMAND            CREATED            STATUS              PORTS              NAMES
    04762770df2d        hypriot/rpi-python  "bash"              2 days ago          Up 2 days                              web
    root@black-pearl:/home/pirate# docker commit 04762770df2d hypriot/rpi-python
    root@black-pearl:/home/pirate# docker rm web
    

    运行新镜像

    #docker run -idt -P --name web hypriot/rpi-python
    #映射程序目录和配置文件进去
    docker run -idt -P --name web -v /home/pi:/home/pi -v /home/pi/.icedtea:/root/.icedtea hypriot/rpi-python
    

    程序正常运行后,可以将这个镜像打包:

    docker save -o hypriot.tar hypriot/rpi-python:latest
    

    分发给其他树莓派运行:

    docker load --input hypriot.tar
    docker images
    docker run -idt -P --name web -v /home/pi:/home/pi -v /home/pi/.icedtea:/root/.icedtea hypriot/rpi-python
    

    这样子便可以利用docker工具来管理这个应用程序,比起原本复杂的环境配置要简单很多。

    在查找Pi可用镜像的时候,顺便发现了一个IOT(internet of things)管理平台:resin

    参考链接:
    add support to install Docker on raspbian/jessie
    Getting started with Docker on your Raspberry Pi
    How to get Docker running on your Raspberry Pi using Linux
    Getting selenium python to work on Raspberry Pi Model B

    Centos 6.4 安装 Python 2.7

    终于又开始学Python了,不过这次是在Centos 6.4 上面,也碰到了好多问题。Centos6.4
    并不能通过yum安装Python 2.7,系统自带的yum等使用的都是Python2.6.6,将系统的Python软链接指向2.7版本会有各种问题,包括依赖库等等;或者只能创建新的可执行命令,如Python27。最终按照这篇文章的介绍成功安装了Python2.7。
    首先安装相关的工具,要不然等下编译Python会报各种各样的错:

    $sudo yum groupinstall "Development tools"
    $sudo yum install zlib-devel
    $sudo yum install bzip2-devel
    $sudo yum install openssl-devel
    $sudo yum install ncurses-devel
    $sudo yum install sqlite-devel
    

    然后下载Python并安装,注意这里是make altinstall而不是make install,参考这里

    $ wget https://www.python.org/ftp/python/2.7.11/Python-2.7.11.tar.xz
    $ tar xf Python-2.7.11.tar.xz
    $ cd Python-2.7.11
    $ ./configure --prefix=/usr/local
    $ sudo make 
    $ sudo make altinstall
    

    检查一下是不是安装到了/usr/local/bin/python2.7下面去了,后面Python 2.7相关的库也将安装到这里

    $ ls -ltr /usr/local/bin/python*
    

    检查一下Python2.6是不是还在/usr/bin/下面

    $ ls -ltr /usr/bin/python*
    

    检查一下系统路径变量PATH,保证/usr/local/bin在/usr/bin之前,然后将Python的软链接指向2.7。

    $ echo $PATH
    /usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin
    $ ln -s /usr/local/bin/python2.7 /usr/local/bin/python
    

    这样子普通用户登录的时候就可以使用Python2.7了,而root用户(sudo)仍然使用Python2.6,yum等才不会出错。

    $ which python
    /usr/local/bin/python
    $ python -V
    Python 2.7.11
    
    $ sudo -s
    which python
    #/usr/bin/python
    python -V
    #Python 2.6.6
    exit
    

    安装Python 2.7的包管理工具

    $ wget https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py
    $ sudo /usr/local/bin/python2.7 ez_setup.py
    $ sudo /usr/local/bin/easy_install-2.7 pip
    

    检查一下是不是对了

    $ which pip
    /usr/local/bin/pip
    
    $ which easy_install
    /usr/local/bin/easy_install
    

    在普通用户下面操作使用easy_install 就是安装到Python2.7的目录下面去了

    $ easy_install requests
    

    这时候会报错,因为相关的Python 2.7目录并没有写的权限

    $ sudo chmod 664 /usr/local/bin
    

    或者

    sudo /usr/local/bin/easy_install-2.7 requests
    

    注意:如果是在root用户或者sudo命令下,使用的仍然是Python 2.6,所以必须要指明使用那个版本的easy_install。
    接下来就可以愉快的使用pip安装Python2.7相关的库了。

    参考链接:
    Installing python 2.7 on centos 6.3. Follow this sequence exactly for centos machine only
    How To Set Up Python 2.7.6 and 3.3.3 on CentOS 6.4
    How to install Python 2.7 and Python 3.3 on CentOS 6
    Centos 6.4 python 2.6 升级到 2.7
    CENTOS 6.5 安装 Python 2.7 总结
    Difference in details between “make install” and “make altinstall”
    Common Python Tools: Using virtualenv, Installing with Pip, and Managing Packages