VMware的PV驱动

用较新版本VMware创建虚拟机的时候可以看到,在建立磁盘和网卡时,可选的型号里有准虚拟化类型,即PV SCSI 卡和VMXNET 2/3网卡。一般来说,准虚拟化的设备比起全虚拟化的(比如虚拟出来的LSI Logic SCSI卡、Intel网卡)具有更好的性能和更低的资源占用率。所以在可能的情况下最好优先选择准虚拟化设备。

对于新建的Windows虚拟机,可以用虚拟软驱挂接PVSCSI的驱动盘,在安装时加载驱动(比如2003或更早版本Windows的F6大法)。至于网卡,装好系统后安装VMware tools即可。

但是,也许是出于OS兼容性和减少用户操作麻烦的考虑,VMware创建虚拟机时默认的SCSI卡和网卡都是全虚拟化的。如果在这种设置下安装好Windows,之后想切换到准虚拟化,网卡倒不是大问题,但SCSI卡就麻烦了。就像SATA AHCI刚出来那时候经典的“开AHCI蓝屏”问题一样,如果在虚拟机设置里把SCSI卡从LSI Logic切换到PVSCSI,启动虚拟机铁定蓝屏。好在可以借鉴AHCI的经验来处理这个问题:

  • 网上流传着一个从ThinkPAD驱动包里剥出来的脚本,用来拷贝文件、修改注册表,安装MSAHCI服务。可以模仿此脚本安装PVSCSI服务。不过这么折腾显然是不直观而且复杂的。有多少人愿意做就不知道了。
  • 以前有人通过使用板载第三方SATA卡AHCI模式或外插PCI-E/PCI SATA卡的方法来启用MSAHCI服务,然后再重启把南桥SATA卡切换到AHCI模式。这个办法同样可以用于VMware的PVSCSI卡。具体操作方法是: + 为虚拟机添加一个小硬盘。大小不重要,使用默认的8M即可。关键的是这块硬盘一定要接在原来的SCSI卡之外,比如原来硬盘是SCSI 0:0,那么新硬盘可以是1:0这样子的。这样,VMware才会为之创建一个新的SCSI卡。 + 硬盘创建好后,把新创建的卡的型号从LSI Logic改为PVSCSI,然后启动虚拟机。 + 虚拟机启动好之后,登录进去到设备管理器看看,是不是有两个硬盘了,其中一个挂在PVSCSI下。如果正常,就关机,删掉新建的小硬盘,并把原来的SCSI卡型号改为PVSCSI,再启动虚拟机。 + 大功告成,亲个嘴儿。

至于Linux,较新内核(比如广泛使用的3.2)已内置了vmx_pvscsi的驱动,只要initrd里包含进去了,就可以放心切换,不会有蓝屏的危险。如果是2.6内核,那就要自己折腾了。

瓶踪暇影——使用Flask开发web应用琐记(1)

不知不觉间,已经一年没写博客了。看来写博客这活太重量级,还是微博上灌水省心啊。

之前在一个项目里用 Flask 开发其中的 Web 部分,有些小心得,插空整理出来放到博客上权当备忘。

里面的代码都来自实际项目。

多个 app 部署在同一服务器下

有时我们需要把多个独立的 Flask 应用部署在同一服务器下,根据 URL 前缀转到不同的应用,这时候可以用 werkzeug 的 DispatcherMiddleware 来实现 app 串接。

from werkzeug.wsgi import DispatcherMiddleware

dummy_app = Flask(__name__)

@dummy_app.route('/')
def index():
    return redirect('/uc')

import uc.ucweb
import zs.zsweb

all_apps = ((None, dummy_app), ('/uc', uc.ucweb.app), ('/zs', zs.zsweb.app))

main_app = [app for (mp, app) in all_apps if not mp][0]
sub_apps = dict(((mp, app) for (mp, app) in all_apps if mp))
app = DispatcherMiddleware(main_app, sub_apps)

这个例子中创建了一个占位的 Flask 对象专用于转跳,这是项目中的某个特殊需求所致。一般来说把 all_apps 中想挂载到根路径的 app 对应的 URL 前缀设为 None 即可。后面的代码会根据 URL 前缀来把各应用自动分配给 DispatcherMiddleware 的构造函数。

instance_path 和配置文件读取

Flask 提供了从 Python 对象或者 .py 文件中读取配置的功能。但是因为 Flask 没有像 Pylons 那样使用 paster 来生成项目的初始结构,所以实际项目中这部分代码还是要自己写的。

import defaultcfg

kwargs = {}
kwargs['instance_relative_config'] = True

try:
    instance_folder = os.environ['INSTANCE_FOLDER']
    kwargs['instance_path'] = instance_folder
except KeyError:
    pass

app_name = __name__.split('.')[0]

deploy_mode = 'production'
try:
    if os.environ['DEPLOY_MODE'].upper() == 'DEBUG':
        deploy_mode = 'debug'
except KeyError:
    pass

app = Flask(app_name, **kwargs)

app.config.from_object(defaultcfg)
app.config.from_pyfile('common_%s.py' % (deploy_mode), silent=True)
app.config.from_pyfile('%s_%s.py' % (app_name, deploy_mode), silent=True)

字典 kwargs 将要传给Flask 类构造函数的参数,其中 instance_relative_config 表示是否从实例路径(instance path)中读取配置文件,这样可以把配置与程序本身的路径分离,并且不需要硬编码其路径。当然,程序的运行时数据(比如程序自己的本地数据库、 cache 过来的一些参数表等)也应该放到实例路径中去,具体做法在此不多说。

接下来,看 instance_path 的实际路径是否已在环境变量中设置(其实从 argv 中读取命令行参数也是个不错的主意),没有则取 Flask 的默认值。

app_name 按 Flask 文档中的建议取 __name__ 的第一段。

在构造了 app 对象后,首先从 defaultcfg 对象中读取配置的默认值,然后读取公共配置文件 common_<部署模式>.py ,最后读取应用相关的配置文件 <程序名>_<部署模式>.py 。由于之前设置为从 instance_path 中读取配置文件,因此可以把不同 app 的 instance_path 指定到同一个目录,这样多个 app 的公共配置部分(比如日志设置、公共的数据库设置等)就可以只写一份,而各 app 自己的独有配置则放到各自的配置文件中去。生产系统的配置和调试系统的配置则通过表示部署模式的 deploy_mode 来区分开。

如何让非特权进程监听特权端口(下)

上回书说了一小撮别有用心的其它操作系统,现在要说广大人民群众喜闻乐见的 Linux 了。

Linux 下用 POSIX.1e capabilities(能力) [PRIVS]_ 来实现我们的需求。这个 patch 已经集成进 mainstream 了。

pam_cap

一开始的 linux-privs 只支持基于进程的能力控制,这对我们的要求并没有什么帮助,因为仍然需要从特权身份启动来降低权限。理论上,可以编写一个利用了能力模型的 pam 模块,来指定哪些用户的会话继承哪些权限,但和早期内核配套的 libcap 包里并没有这样的模块,直到后面的 libcap2 才有 pam_cap 实现此功能。

  • /etc/pam.d/login 里加入
auth    optional                        pam_cap.so

注意一定要加在任何 auth sufficient 之前,否则就被跳过了。当然,在 Debian 上可以加到 /etc/pam.d/common-auth 里——也可以运行 pam-auth-update 来生成。这是针对 login 用户的,如果是 su 用户,还需要改相应的 pam 文件,因为 @common-auth 在 su 里排在后面。

  • /etc/security/capability.conf 里加入对应用户的设置,比如
cap_net_bind_service    adoal

表示以用户 adoal 身份运行的进程可以绑定到特权端口……且慢,经过实验,这样设置仍然不起作用,还需要再设置文件系统的能力才行。

文件系统能力

从 2.6.24 内核开始, linux-privs 又支持了基于文件系统的能力功能,能够给文件系统里特定的可执行程序指定一个能力,详见 [FCAP]_ 。简而言之执行

$ sudo setcap cap_net_bind_service=ep /usr/sbin/httpd

之后,任何用户(当然前提是对 /usr/bin/httpdx 权限)都可以启动 apache 监听 80 端口而不需要特权身份。

如果把命令里的 ep 改成 ei ,那么就只有在 /etc/security/capability.conf 里设定好的用户才可以了,其它用户无效——这里 i 表示的是继承(inheritance),也就是程序执行时继承用户的对应能力,而非像 p 那样不管用户是否有对应能力都直接设定。也就是说,如果需要为特定用户设置特定程序的特定能力,需要对用户和程序都做对应设置,缺一不可。

杯具的 RHEL5 和 SUSE

非常值得一提的是,在企业用户里很流行的 Red Hat Enterprise Linux 5 由于生命周期太长而又坚持 ABI 不变动的原则,于是其古老的内核并不能支持基于文件系统的能力模型。所以这里说的方法对 RHEL5 用户(以及 CentOS 5 之类的派生版用户)只能是梦想了。

RHEL6 里内核和 libcap 都更新了,已经可以支持上面两种方法。

SUSE 系(SLES/SLED 和 OpenSUSE)虽然内核支持,并且有 libcap2 包,但是 libcap2 打包时并没有构建 pam_cap.so 所以也不行。

如何让非特权进程监听特权端口(上)

特权端口(也就是 1024 以下的低端口)是 Unix 系统里最似是而非的安全设计,没有之一。有时候我们希望一个服务程序能够监听在 80 之类的知名低端口(便利),但程序的整个生命周期又要避开 root 身份(安全)。这种对便利和安全的双重追求就让我们感到特权端口的设计是多么蛋疼了。

要解决这个问题,也有一些似是而非的通用办法。比如前置的应用层协议代理(例如 HTTP 反向代理),用户态端口转发(例如 rinetd/xinetd),或者本机防火墙做内核态转发。但这些方法还是有一定限制的,有时候并不合适。我们需要的是让非特权进程真正监听在低端口上。顺便说一下,有些人提到的 authbind/privbind 程序,虽然能解决问题,但用到了 suid 的 helper 程序……

幸好一些主要的 Unix 类操作系统发展到当代还是有办法解决这个问题的。

FreeBSD 可以用下面的指令关闭特权端口:

# sysctl net.inet.ip.portrange.reservedlow=0 net.inet.ip.portrange.reservedhigh=0

这样非特权进程就能直接监听低端口了。如果需要做权限控制,比如以 uid=80 启动的用户只能听80 端口,那么还需要启动 mac_portacl 模块。具体见 [PORTACL]_ 。

Solaris 可以通过:

# /usr/sbin/usermod -K defaultpriv=basic,net_privaddr <user_name>

给特定的用户赋予监听特权端口的权限。但是,没有类似 FreeBSD 的全局选项,而且也没有端口权限控制(如有,请指正)。

NetBSD、 OpenBSD、 Dragonfly BSD 似乎没有相应的机制, OS/X 不知,还有待探索。

Linux 情况比较混乱,且待下回分解。