blob.png blob.png

 上面左边是我的个人微信,如需进一步沟通,请加微信。  右边是我的公众号“Openstack私有云”,如有兴趣,请关注。




现在Openstack社区的安装部署方式已经开始推荐使用kolla进行部署,kolla项目现在包括两个子项目:kolla-ansiblekolla-kubernetes,其中kolla-ansible应用于生产环境案例多些并且使用广泛一些,本文档kolla是指kolla-ansible。  

kolla-ansible项目是基于ansible playbook的部署方式,原来openstack ansible的部署方式支持baremetellxc容器两种方式进行部署,kolla的部署方式是完全基于dockeransible两大工具实现。


2018年3月1日注:为了提高部署效率,可以通过直接使用制作好的ISO镜像镜像安装,详见我的另一编博客文章:

openstack 之 kolla安装镜像



1. Kolla简介

KollaOpenStack下的一个项目,项目的目标是提供可用于生产的容器化部署工具,以便使用社区最佳实践来运行可扩展、快速、可靠和可升级的OpenStack云。

Kolla使用Docker容器和Anisble playbooks来实现这个目标。Kolla是开箱即用的,即使你是个新手也可以很快的使用kolla快速部署你的openstack集群。Kolla也允许你根据实际的需求来定制化的部署。

kolla目前已经可以部署以下openstack项目:

Aodh

Barbican

Bifrost

Ceilometer

Cinder

CloudKitty

Congress

Designate

Glance

Gnocchi

Heat

Horizon

Ironic

Keystone

Kuryr

Magnum

Manila

Mistral

Murano

Neutron

Nova

Rally

Sahara

Senlin

Swift

Tempest

Trove

Vmtp

Watcher

Zaqar

 

可以部署的基础组件包括:

Ceph cinder/nova/glance存储后端

[collectd (https://collectd.org), [InfluxDB (https://influxdata.com/time-series-platform/influxdb/), and [Grafana (http://grafana.org) for performance monitoring.

[Elasticsearch (https://www.elastic.co/de/products/elasticsearch) and [Kibana (https://www.elastic.co/de/products/kibana) 日志分析

[HAProxy (http://www.haproxy.org/) and [Keepalived (http://www.keepalived.org/) 高可用

[Heka (http://hekad.readthedocs.org/) 分布式可扩展的日志系统

[MariaDB and Galera Cluster (https://mariadb.com/kb/en/mariadb/galera-cluster/) 高可用数据库

[MongoDB (https://www.mongodb.org/) CeilometerGnocchi的数据库后端

[Open vSwitch (http://openvswitch.org/)

[RabbitMQ (https://www.rabbitmq.com/) 消息队列

 

1.1 技术实现

容器化的OpenStack实现,其实都差不多,就看各家谁更加彻底,更加优雅,更加安全。所谓彻底,就是完全对操作系统是免疫的,把容器删掉后,操作系统就好像没操作过一样。

一般来说都是build各种服务的docker 镜像。利用工具进行编排,对镜像分发,把镜像启动起来。有些厂商仅仅是把控制节点容器化。对于kolla项目来说,是把OpenStack和周边项目全部容器化:

ceph

qemu

libvirt

这些都容器化,甚至在讨论NTP服务,也需要进行容器化。

Kolla项目 https://github.com/openstack/kolla

1.2. 升级

这个其实大家都可以想到,容器最大的特点,就是升级。企业使用OpenStack,最大的一个顾虑,就是升级。尤其在OpenStack一年两个版本下,不断的有新的功能的需求的情况下,如果不能升级,其实是很痛苦。尤其在企业的迅速发展的过程中。

 

容器化的OpenStack,升级有多么简单呢?其实就是删掉容器,换上新的容器,用户基本是无感知的状态下完成。

 

升级子所以很困难,有一个很现实的原因,线上环境,很难模拟,升级验证测试很难进行。当采用容器化以后,我们很容易模拟出一个线上环境,进行升级测试,升级失败,回滚。其实这些都做的很漂亮。

1.3. 灵活

以前厂商的解决方案,都是3个控制节点,如果我希望增加到5个控制节点,或者把控制节点某个服务单独部署,那么这个基本是很难完成的任务。

以前厂商都厂商把OpenStack的各个服务放到虚拟机里,这样部署灵活性提高不少。但是虚拟机还是很重,面对客户千百万化的需求,就有点无能为力。

举一个例子:

企业基本节点,我规模很小,可能就只有几台机器,这时候,我可能不需要控制节点高可用,我就需要1个控制节点,管理机柜计算节点。

随着时间的发展,希望扩大规模,控制节点变成高可用。

规模进一步扩大,我就需要把消息队列单独出来部署,解决性能的问题。

这种需求,很实在,OpenStack厂商也在努力满足企业的这些需求,其实MirantisFuel,已经在很多程度,满足了企业这种需求,不过代价很大。

对于容器化的OpenStack,就变得很简单,无非就是调整各个节点的容器分布,编排的问题。控制节点是3个,还是五个,rabbitmq放在什么位置,根本就不是问题。

1.4. 配置管理

OpenStack过去使用最广的配置管理工具是Puppet,对于企业用户来说,这个是很难掌控的。其实在国内,就算是互联网公司,负责Puppet的运维人员离职,其实都是很难招聘回来相应的人员。

对于OpenStack厂商来说,要想完全掌控Puppet,还是很困难的。更别说,要满足各种灵活的需求。

配置管理工具,其实SaltAnsible,是python开发,比较易用,不过在OpenStack的生态圈里,不如puppet强大,很难超越Puppet

容器化后的OpenStack,配置管理工具,或者编排的工具,就很多选择,ansibleslatK8S,都是可以支持。你就不需要受ruby的折磨。

其实这也是大大降低企业掌控OpenStack难度。

1.5. 操作系统厂商依赖

厂商都在宣传所谓没有厂商绑定。其实你用了红帽的OpenStack,要想换到Ubuntu下,不是不可能,其实肯定很痛苦。如果要换成Suse,难度就更高。

各种配置管理工具,其实都是依赖发行版的包管理。国内的银行其实都使用Suse。但是社区的Puppet工具不支持Suse。或者操作系统发行版没有提供包,怎么办?容器化的OpenStack。其实理论上,可以跑在任何支持容器的操作系统上。内核的版本高,无非就是性能更好一点。其实你只需要做点测试,就可以实现这种跨操作系统的部署。

容器里,可以使用rpm包,Deb包,也是可以跑源码安装,这样其实对于操作系统厂商来说,基本就没任何的依赖。不受制操作系统厂商。

1.6. 软件依赖

OpenStack项目的增多,软件互相依赖的问题,越来越严重。因为OpenStack很多项目是需要使用外部项目,例如Ceph,他的依赖很可能和OpenStack组件的依赖产生冲突。这种问题,可以解决,但是解决,没任何的意义和技术含量,很让技术人员抓狂。其实发行版都在投入大量的精力去解决各个软件包的互相依赖的问题。

容器化的OpenStack,很好的解决了这个问题。

1.7. 部署时间

在生产环境中,部署时间1个小时,和一天,其实区别不大,毕竟部署是一次性的工作。对于测试来说,就完全不一样。如果我10分钟可以完成一次部署,可以测试验证的东西,和几个小时才能完成一次的部署,差异还是很大的。

容器化OpenStack,大大加快了部署的时间,通常10分钟,就可以完成一次完整功能的部署,验证OpenStack各种新功能的代价,就大大减少。

1.8. 显得简单

OpenStack在企业的实际使用中,都是抱怨太复杂,这其实也是因为OpenStack,松耦合,功能强大,同时也让用户感觉很复杂。尤其在出现错误的时候,很无奈。

容器化后,用户感觉OpenStack各个组件,就类似累积木一样,搭建起来,可以根据自己的需求,选择哪个模块。感觉自己是可控的。你可以很方便的装上某个模块,不满意,删掉。背后的复杂的逻辑,社区已经帮你完善。

遇到问题,寻求帮助,也显得简单很多。因为大家容器里的东西都是一样的,无非就是外面的配置文件。

也只要让企业感觉自己可以掌控OpenStack,这样OpenStack才会大量的进入企业的IT系统。这个时候,无论是采用外包还是自己运维。

1.9. 计算节点HA

如果实现计算节点挂掉后,上面的虚拟机自动在别的节点启动起来。这个问题解决的办法,其实有很多,解决的难点,就在于我如何判断这台节点真的挂掉。因为虚拟机的迁移的东西,是很大的,必须很小心。也很容易造成误判。

海云捷迅提出一个使用consul的解决方案,就是一个容器里做健康检查的组件,放到openstack计算节点,类似peer to peer,互相检查。

 

当容器化的OpenStack后,那么就可以利用容器强大社区,各种的实现方式,第一时间知道节点失效。肯定你也是可以使用consul来解决这个问题,更加直接。

1.10. 监控和日志分析

OpenStack一直都在完善自己的监控日志分析。不过进展并不太好。容器化的OpenStack,面临的监控,日志的问题,和以前的OpenStack有很大差异。

不过不得不承认,容器的世界里,这方面非常完善,太多选择,可以帮助你解决监控和日志分析的问题。

可以利用强大的Docker社区,来完善OpenStack短板的地方。

1.11. 创新

容器化后的OpenStack,其实带来很多意想不到的创新和变化。很多以前很炫的概念,慢慢走向现实。

OpenStack一个版本的发行周期大概是分为B1B2B3,每个阶段大概45天,后续就发布RC,正式版本。

以往OpenStack公司都是等到一个版本正式发布后,进行打包,测试,验证,经过3个月和半年,正式对外发布。那么这种发布周期,其实已经有点跟不上OpenStack的步伐。例如Mitaka版本发布的时候,红帽的Liberty版本才正式对外发布。

能不能做到,OpenStack一边开发,发行版也在同步进行打包,测试呢?其实在OpenStack发展初期,有人提出这样的建议,不过对操作系统厂商来说,代价太大,不愿意去做。

有了容器化以后,完全不需要依赖操作系统的打包,我可以根据自己的需求,进行build image,测试,这样我的产品的发布周期,就会大大缩短。

 

(以上内容参考:http://www.chenshake.com/openstack-benefits-of-container/

2. 准备工作

这些工作都是在OpenStack服务(目标主机)节点进行。对于kolla来说,准备工作做完后,安装OpenStack非常快,如果从网上下载镜像需要看网络情况,如果先准备好了docker本地registry并提前build images,那么安装也就是1个小时以内的事情。

 

目标主机相关准备工作:

1. 关闭Selinux

2. 关闭Firewalld

3. 关闭NetworkManager

4. 设置hostname FQDN,让机器可以通过hostname进行互相访问,统一 /etc/hosts文件

5. 同步时间,先确保时间是基本一致,和硬件也统一时间

6. 设置docker registry 服务器位置,设置不安全的访问。

7. 检查机器是否支持KVM

8. 设置docker源,安装docker 1.12.5

9. 设置访问私有的registry

10. 打开Docker shared mount 功能

11. 设置网卡,网络,符合部署需求。如果需要bonding,需要把bonding做好。

12. 对于要部署Ceph的节点的磁盘,进行打标签

13. 检查网络,重点,确保装好后,网络功能正常。

  

kolla部署节点(Master节点),我们需要做的准备工作:

        1. 关闭Selinux

2. 关闭NetworkManager

3. 设置hostname FQDN,让机器可以通过hostname进行互相访问,统一 /etc/hosts文件

4. 同步时间,先确保时间是基本一致,和硬件也统一时间

5. 设置docker

 

上面面5条,与目标节点的准备工作是相同的,不同的是:

1. 搭建私有的registry服务器,存放build好的OpenStack Docker镜像

2. 安装ansible

3. 部署kolla-ansible

 

2.1. CentOS 7.3初始化

最小化安装操作系统。

1 关闭NetworkManager

2 关闭Selinux

关闭Firewalld

4  安装Epel

5 设置Hostname

同步时间

2.1.1. 关闭NetworkManager服务
systemctl stop NetworkManager
systemctl disable NetworkManager
2.1.2. 关闭Selinux

编辑  /etc/selinux/config

SELINUX=disabled

重启机器,查看selinux状态

# sestatus
SELinux status:                 disabled
2.1.3. 关闭Firewalld
systemctl status firewalld
systemctl stop firewalld
systemctl disable firewalld
systemctl status firewalld
firewall-cmd --state
2.1.4. 安装Epel
yum install epel-release

查看repo情况:

yum repolist
2.1.5. 设置Hostname
#cat /etc/hostname
kolla
# cat /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
10.0.7.10   kolla.dd.com     kolla

检查

#hostname -F /etc/hostname
#hostname
kolla
#hostname -f
kolla.dd.com
2.1.6. 同步时间
yum install ntp
systemctl enable ntpd.service
systemctl start ntpd.service

手工同步时间:

ntpdate 0.centos.pool.ntp.org

2.2. 安装基础包

一定要启用EPELrepo

yum install python-devel libffi-devel gcc openssl-devel git python-pip

安装常用软件包:

yum install vim wget -y

2.3. 安装Docker

目前最新版本的Docker1.13.XKolla目前支持的Docker1.12.x,所以我们要指定Docker的版本来安装,并且一定要采用Docker官方的源,不能使用红帽的源,红帽的源的Dockerbug

2.3.1. 设置repo
tee /etc/yum.repos.d/docker.repo << 'EOF'
[dockerrepo]
name=Docker Repository
baseurl=https://yum.dockerproject.org/repo/main/centos/$releasever/
enabled=1
gpgcheck=1
gpgkey=https://yum.dockerproject.org/gpg
EOF
2.3.2. 安装Docker 1.12.5
yum install docker-engine-1.12.5 docker-engine-selinux-1.12.5 -y
2.3.3. 设置docker服务参数MountFlags
mkdir /etc/systemd/system/docker.service.d
tee /etc/systemd/system/docker.service.d/kolla.conf << 'EOF'
[Service]
MountFlags=shared
EOF

重启相关服务:

systemctl daemon-reload
systemctl enable docker
systemctl restart docker
2.3.4. 设置访问私有的Docker仓库

编辑  /usr/lib/systemd/system/docker.service

#ExecStart=/usr/bin/dockerd
ExecStart=/usr/bin/dockerd --insecure-registry 10.0.7.10:4000

注意,将10.0.7.10改为安装时的kolla-master实际内网地址;

重启服务

systemctl daemon-reload
systemctl restart docker

3. 安装配置Ansible(在kolla-master节点上执行)

Kolla项目的Mitaka版本要求ansible版本低于2.0Newton版本以后的就只支持2.x以上的版本。我们这里安装Ocata版本,因此要求ansible版本高于2.0 ,默认安装即可 。

yum install ansible -y

编辑/etc/hosts文件,增加以下内容:

10.0.7.10   kolla.dd.com     kolla
10.0.7.221   control01.dd.com     control01
10.0.7.222   compute01.dd.com     compute01
10.0.7.223   storage01.dd.com     storage01

注意,以上IP地址和主机名,根据最终确定的IP地址和主机名进行修改。剩下的主机解析记录根据最终的主机确定并添加。

建立ansiblessh信任关系:

kolla主机上执行:  ssh-keygen

编辑ansible host文件:vi /tmp/all

[all]
control01
compute01
storage01

分别在kolla上用ssh使用root账户登录control01/compute01/storage01 ,使kolla记录 know_hosts,然后执行下面的命令建立ssh信任关系,使kolla免密登陆三台主机:

ansible control01 -i /tmp/all -m authorized_key -a "user=root key='{{lookup('file','/root/.ssh/id_rsa.pub')}}'" -k
ansible compute01 -i /tmp/all -m authorized_key -a "user=root key='{{lookup('file','/root/.ssh/id_rsa.pub')}}'" -k
ansible storage01 -i /tmp/all -m authorized_key -a "user=root key='{{lookup('file','/root/.ssh/id_rsa.pub')}}'" -k

4. 配置Registry服务器(在kolla-master节点上执行)

Docker Registry服务器是一个本地托管的镜像管理服务器,它取代了从Docker Hub获取镜像。Kolla可以在有或没有本地镜像管理服务器的情况下部署,2.3版之前的Docker注册表性能非常差,因为所有容器数据都是针对每个映像推送的,而不是利用Docker分层来优化推送操作。因此,Kolla社区建议使用注册表2.3或更高版本。要使用版本2.3或更高版本部署Registry服务器,请执行以下操作:

mkdir /opt/registry
docker run -d -v /opt/registry:/var/lib/registry -p 4000:5000 \
--restart=always --name registry registry:2

注:默认docker的registry是使用5000端口,对于OpenStack来说,有端口冲突,所以改成4000

下载kolla官方提供的镜像:

http://tarballs.openstack.org/kolla/images/

这是kolla官方提供的镜像给CI使用,只保留最新版本和最新的stable版本。下载Ocata版本:

wget  http://tarballs.openstack.org/kolla/images/centos-source-registry-ocata.tar.gz 

注:官网上面已经移除了,我已上传到百度云盘,下载地址:https://pan.baidu.com/s/1A3KFi3kPj4b0PBDR9cOOlQ
密码: p7ns

tar zxvf centos-source-registry-ocata.tar.gz -C /opt/registry/

这样就把kolla的docker镜像文件放到Regisitry服务器上。


 5. kolla-ansible(在kolla-master节点上执行)

下载kolla-ansible的代码:

cd /home
git clone https://github.com/openstack/kolla-ansible -b stable/ocata

安装kolla-ansible

cd kolla-ansible
pip install .

一个小技巧,如果pip速度很慢,后面可以加上参数,指定国内的pip

pip install . -i https://pypi.tuna.tsinghua.edu.cn/simple

(以上内容参考: http://www.cnblogs.com/microman/p/6107879.html

复制相关文件

cp -r etc/kolla /etc/kolla/
cp ansible/inventory/* /home/

如果是在虚拟机里装kolla,希望可以启动再启动虚拟机,那么你需要把virt_type=qemu,默认是kvm :

mkdir -p /etc/kolla/config/nova
cat << EOF > /etc/kolla/config/nova/nova-compute.conf
[libvirt]
virt_type=qemu
cpu_mode = none
EOF

 生成密码文件:

kolla-genpwd

编辑 /etc/kolla/passwords.yml

keystone_admin_password: admin   #登录Dashboard,admin使用的密码
database_password: mysql    #mysql数据库密码

相关的密码可以根据自己需要进行修改。

编辑 /etc/kolla/globals.yml  文件

kolla_internal_vip_address: "10.0.7.11"

注意,将10.0.7.11这个地址替换为现场使用地址,这个地址用作内网所有openstack组件HA高可用地址

kolla_install_type: "source"
openstack_release: "4.0.3"     //注意和kolla_ansible的版本保持一致
docker_registry: "10.0.7.10:4000"   
docker_namespace: "lokolla"
network_interface: "ens33"
neutron_external_interface: "ens34"

10.0.7.11,这个ip是一个没有使用的的ip地址,他是给haproxy使用。 网络接口名称  ens33是openstack内部网络 、 ens34是外部网络, 请根据实际情况修改 。


6. 安装OpenStack(在kolla-master节点上执行)

部署前的bootstrap准备:

kolla-ansible -i /home/multinode bootstrap-servers

这个命令会做一些安装前的准备工作,比如在每个目标节点修改/etc/hosts文件,准备ansible playbook文件等等。

部署前的检查:

kolla-ansible prechecks -i /home/multinode

整个部署过程只需要运行一个命令:

kolla-ansible deploy -i /home/multinode

/home/multinodeinventory文件,里面具体定义了哪一台主机安装什么openstack模块。例如,默认我们会把compute主机放到compute节点组,如果我想将control03主机也作为comute节点使用,那么我只需要到这样修改:

[compute]
compute01
compute02
compute03
compute04
compute05
control03

即,将control03 加入到[compute]组中。同理,其他的功能模块也可以根据设计非常灵活的配置。建议通读这个文件。

另外,在 /etc/kolla/globle.yml文件中可以定义决定安装或者不安装相关的openstack组件,比如,默认情况不安装neutron的负载均衡和防火墙功能,如果希望启用,那么在globle.yml中将下面的行:

#enable_neutron_lbaas: "no"
#enable_neutron_fwaas: "no"

改为:

enable_neutron_lbaas: "yes"
enable_neutron_fwaas: "yes"

具体哪些组件是默认安装,哪些组件是默认禁用,需要参考这个文件:kolla-ansible/ansible/group_vars/all.yml ,建议通读这个文件。

全局配置文件/etc/kolla/globle.yml中可以修改上面这个文件的默认参数值,默认的/etc/kolla/globle.yml文件,建议通读这个文件。


7. 验证部署(在kolla-master节点上执行)

 kolla-ansible post-deploy

这个命令会创建 /etc/kolla/admin-openrc.sh 文件

安装OpenStack client端:

pip install python-openstackclient

编辑 /usr/share/kolla-ansible/init-runonce

网络需要根据实际情况修改:

EXT_NET_CIDR='192.168.12.0/24'
EXT_NET_RANGE='start=192.168.12.30,end=192.168.12.40'
EXT_NET_GATEWAY='192.168.12.1'

这里解释一下,192.168.12.0的网络,就是上面ens34接的网络,这个网络是通过路由器访问互联网。这个地方需要好好理解。配置好这个,装完虚拟机就可以直接ping通。

运行

source /etc/kolla/admin-openrc.sh
cd /usr/share/kolla-ansible
./init-runonce

最后可以创建一个虚拟机,根据最后的命令提示:

openstack server create \
    --image cirros \
    --flavor m1.tiny \
    --key-name mykey \
    --nic net-id=a9f8b46b-41c1-4e0a-b015-457dffc89afe  \

这个时候,你可以登录Dashboard,给虚拟机分配一个floating ip,如果顺利,应该就可以直接ping floating ip的地址。

[root@kolla kolla-ansible]# openstack server create     --image cirros     --flavor m1.tiny     --key-name mykey     --nic net-id=a9f8b46b-41c1-4e0a-b015-457dffc89afe     demo1
+-------------------------------------+-----------------------------------------------+
| Field                               | Value                                         |
+-------------------------------------+-----------------------------------------------+
| OS-DCF:diskConfig                   | MANUAL                                        |
| OS-EXT-AZ:availability_zone         |                                               |
| OS-EXT-SRV-ATTR:host                | None                                          |
| OS-EXT-SRV-ATTR:hypervisor_hostname | None                                          |
| OS-EXT-SRV-ATTR:instance_name       |                                               |
| OS-EXT-STS:power_state              | NOSTATE                                       |
| OS-EXT-STS:task_state               | scheduling                                    |
| OS-EXT-STS:vm_state                 | building                                      |
| OS-SRV-USG:launched_at              | None                                          |
| OS-SRV-USG:terminated_at            | None                                          |
| accessIPv4                          |                                               |
| accessIPv6                          |                                               |
| addresses                           |                                               |
| adminPass                           | hx9jW5tbru3U                                  |
| config_drive                        |                                               |
| created                             | 2017-11-06T13:35:12Z                          |
| flavor                              | m1.tiny (1)                                   |
| hostId                              |                                               |
| id                                  | 1d269c03-1ee4-44ab-8a12-9ecaa6cdcd86          |
| image                               | cirros (48332374-1fd5-4f58-b8ce-b0e3f4b2015a) |
| key_name                            | mykey                                         |
| name                                | demo1                                         |
| progress                            | 0                                             |
| project_id                          | 80db9a28c916458ab3d4f84b9eb315d0              |
| properties                          |                                               |
| security_groups                     | name='default'                                |
| status                              | BUILD                                         |
| updated                             | 2017-11-06T13:35:14Z                          |
| user_id                             | 08fb93292c9341f78efb3d454a9f4cd8              |
| volumes_attached                    |                                               |
+-------------------------------------+-----------------------------------------------+
[root@kolla kolla-ansible]# openstack server list
+--------------------------------------+-------+--------+----------+--------+---------+
| ID                                   | Name  | Status | Networks | Image  | Flavor  |
+--------------------------------------+-------+--------+----------+--------+---------+
| 1d269c03-1ee4-44ab-8a12-9ecaa6cdcd86 | demo1 | BUILD  |          | cirros | m1.tiny |
+--------------------------------------+-------+--------+----------+--------+---------+
[root@kolla kolla-ansible]# openstack server list
+--------------------------------------+-------+--------+--------------------+--------+---------+
| ID                                   | Name  | Status | Networks           | Image  | Flavor  |
+--------------------------------------+-------+--------+--------------------+--------+---------+
| 1d269c03-1ee4-44ab-8a12-9ecaa6cdcd86 | demo1 | ACTIVE | demo-net=10.0.0.10 | cirros | m1.tiny |
+--------------------------------------+-------+--------+--------------------+--------+---------+


参考文档

http://docs.openstack.org/developer/kolla-ansible/quickstart.html

http://www.cnblogs.com/lienhua34/p/4922130.html

http://www.cnblogs.com/microman/p/6107879.html