标签 Linux 下的文章

部署devstack

新公司是一家数据与基础设施提供商(to B)。初来乍到,和这里的同事了解了一些云计算平台和大数据平台的技术栈。对于“新鲜”(only to me)的技术栈,自己总有一种折腾的冲动,于是就有了这一篇备忘性质的文章,记录一下自己部署devstack的步骤、遇到的问题和解决方法。

和诸多国内提供公有云的厂商一样,公司的云产品也是基于成熟的OpenStack云计算平台框架和组件搭建的,并做了一些定制。长久以来,我一直以为OpenStack等都是Java技术栈的,对Java技术栈出品的东西总有一种莫名的恐惧感,现在我才发现原来OpenStack是Python系(那个汗汗汗啊)。而OpenStack的另外一个竞争对手:CloudStack才是正经八百的Java系。

OpenStack是一堆云计算平台组件(诸如存储、网络、镜像管理等)的合称,十分庞大且十分复杂,入门门槛不低,即便是为开发目的而进行的OpenStack部署也会让你折腾许久,甚至始终无法搭建成功。为此OpenStack为入门者和开发者推出了一个OpenStack开发环境:devstack。通过devstack,你可以在一个主机节点上部署一个“五脏俱全”的OpenStack Cloud。

一、安装devstack

建议将devstack部署在物理机上,这样可以屏蔽掉许多在虚拟机上部署devstack的问题(具体不详,很多书籍都推荐这么做^_^)。这里讲devstack部署在一台ubuntu 14.04.1的刀片服务器上。

1、下载devstack源码

$ git clone https://github.com/openstack-dev/devstack.git ./devstack
Cloning into './devstack'...
remote: Counting objects: 33686, done.
remote: Compressing objects: 100% (10/10), done.
Receiving objects:   1% (337/33686), 92.01 KiB | 35.00 KiB/s
Receiving objects:   4% (1457/33686), 452.01 KiB | 62.00 KiB/s
... ...

这里直接用trunk上的最新revision:

devstack版本 revision:
commit 96ffde28b6e2f55f95997464aec47ae2c6cf91d3
Merge: c4a0d21 e3a04dd
Author: Jenkins <jenkins@review.openstack.org>
Date:   Tue Apr 26 10:21:16 2016 +0000

2、创建stack账户

$cd devstack/tools
~/devstack/tools$ sudo ./create-stack-user.sh
[sudo] password for baiming:
Creating a group called stack
Creating a user called stack
Giving stack user passwordless sudo privileges

$ cat /etc/passwd|grep stack
stack:x:1006:1006::/opt/stack:/bin/bash

修改devstack目录的owner和group权限:

$ sudo chown -R stack:stack ./devstack/

切换到stack用户下:

baiming@baiming:~$ sudo -i -u stack
stack@baiming:~$ cd /home/baiming/devstack
stack@baiming:/home/baiming/devstack$ ls
clean.sh  exerciserc   extras.d   functions-common  HACKING.rst  LICENSE          openrc     run_tests.sh  setup.py  tests    unstack.sh
data      exercises    files      FUTURE.rst        inc          MAINTAINERS.rst  pkg        samples       stackrc   tools
doc       exercise.sh  functions  gate              lib          Makefile         README.md  setup.cfg     stack.sh  tox.ini

二、启动devstack

我们在devstack目录下创建配置文件:local.conf

# Credentials
ADMIN_PASSWORD=devstack
MYSQL_PASSWORD=devstack
RABBIT_PASSWORD=devstack
SERVICE_PASSWORD=devstack
SERVICE_TOKEN=token
#Enable/Disable Services
disable_service n-net
enable_service q-svc
enable_service q-agt
enable_service q-dhcp
enable_service q-l3
enable_service q-meta
enable_service neutron
enable_service tempest
HOST_IP=10.10.105.71

#NEUTRON CONFIG
#Q_USE_DEBUG_COMMAND=True
#CINDER CONFIG
VOLUME_BACKING_FILE_SIZE=102400M
#GENERAL CONFIG
API_RATE_LIMIT=False
# Output
LOGFILE=/home/baiming/devstack/logs/stack.sh.log
VERBOSE=True
LOG_COLOR=False
SCREEN_LOGDIR=/home/baiming/devstack/logs

执行devstack下的stack.sh来启动各OpenStack组件,stack.sh是devstack“一键安装”的总控脚本,stack.sh执行ok了,devstack也就部署OK了:

$>./stack.sh

但问题接踵而至!

三、问题与解决方法

stack.sh的执行过程是漫长的,且问题也是多多。

1、git协议 or https协议

stack.sh执行后会去openstack官方库下载一些东西,于是遇到了第一个错误:

+functions-common:git_timed:599            timeout -s SIGINT 0 git clone git://git.openstack.org/openstack/requirements.git /opt/stack/requirements
Cloning into '/opt/stack/requirements'...
fatal: unable to connect to git.openstack.org:
git.openstack.org[0: 104.130.246.128]: errno=Connection timed out
git.openstack.org[1: 2001:4800:7819:103:be76:4eff:fe06:63c]: errno=Network is unreachable

+functions-common:git_timed:602            [[ 128 -ne 124 ]]
+functions-common:git_timed:603            die 603 'git call failed: [git clone' git://git.openstack.org/openstack/requirements.git '/opt/stack/requirements]'
+functions-common:die:186                  local exitcode=0
+functions-common:die:187                  set +o xtrace
[Call Trace]
./stack.sh:708:git_clone
/home/baiming/devstack/functions-common:536:git_timed
/home/baiming/devstack/functions-common:603:die
[ERROR] /home/baiming/devstack/functions-common:603 git call failed: [git clone git://git.openstack.org/openstack/requirements.git /opt/stack/requirements]
Error on exit
./stack.sh: line 488: generate-subunit: command not found

stack.sh尝试用git clone git://xxxx,但由于我的主机在代理后面,因此git协议不能被支持,需要改为支持的协议类型,比如https。

解决方法:修改stackrc,更换git_base协议,并且增加http和https代理变量:

# Base GIT Repo URL
# Another option is https://git.openstack.org
GIT_BASE=${GIT_BASE:-https://git.openstack.org}

export http_proxy='http://10.10.126.187:3129'
export https_proxy='http://10.10.126.187:3129'

stackrc之于stack.sh类似.bashrc之于bash,在stack.sh执行时会对stackrc进行source,使其中的export环境变量生效。http_proxy等环境变量添加到stackrc中的效果就是:在stack.sh执行过程中会有类似如下语句出现:

sudo -H http_proxy=http://10.10.126.187:3129 https_proxy=http://10.10.126.187:3129 no_proxy= PIP_FIND_LINKS= /usr/local/bin/pip2.7 install -c /opt/stack/requirements/upper-constraints.txt -U virtualenv

2、重启stack.sh

解决完上述问题后,如果直接重新执行stack.sh,那么会收到“有另外一个stack.sh session在执行的”错误信息。

为此,每次重启stack.sh之前都要先执行:./unstack.sh,清理一下环境。

3、apt包下载错误

在stack.sh执行过程中,会更新ubuntu apt repository,并下载许多第三方包或工具:

Preconfiguring packages ...
(Reading database ... 123098 files and directories currently installed.)
Preparing to unpack .../libitm1_4.8.4-2ubuntu1~14.04.1_amd64.deb ...
Unpacking libitm1:amd64 (4.8.4-2ubuntu1~14.04.1) over (4.8.4-2ubuntu1~14.04) ...
Preparing to unpack .../libgomp1_4.8.4-2ubuntu1~14.04.1_amd64.deb ...
Unpacking libgomp1:amd64 (4.8.4-2ubuntu1~14.04.1) over (4.8.4-2ubuntu1~14.04) ...
Preparing to unpack .../libasan0_4.8.4-2ubuntu1~14.04.1_amd64.deb ...
Unpacking libasan0:amd64 (4.8.4-2ubuntu1~14.04.1) over (4.8.4-2ubuntu1~14.04) ...
Preparing to unpack .../libatomic1_4.8.4-2ubuntu1~14.04.1_amd64.deb ...
Unpacking libatomic1:amd64 (4.8.4-2ubuntu1~14.04.1) over (4.8.4-2ubuntu1~14.04) ...
Preparing to unpack .../libtsan0_4.8.4-2ubuntu1~14.04.1_amd64.deb ...
Unpacking libtsan0:amd64 (4.8.4-2ubuntu1~14.04.1) over (4.8.4-2ubuntu1~14.04) ...
Preparing to unpack .../libquadmath0_4.8.4-2ubuntu1~14.04.1_amd64.deb ...
Unpacking libquadmath0:amd64 (4.8.4-2ubuntu1~14.04.1) over (4.8.4-2ubuntu1~14.04) ...
Preparing to unpack .../libstdc++-4.8-dev_4.8.4-2ubuntu1~14.04.1_amd64.deb ...

.... ..... ....

Setting up libitm1:amd64 (4.8.4-2ubuntu1~14.04.1) ...
Setting up libgomp1:amd64 (4.8.4-2ubuntu1~14.04.1) ...
Setting up libasan0:amd64 (4.8.4-2ubuntu1~14.04.1) ...
Setting up libatomic1:amd64 (4.8.4-2ubuntu1~14.04.1) ...
Setting up libtsan0:amd64 (4.8.4-2ubuntu1~14.04.1) ...
.... .....

 * Setting sysfs variables...                  [ OK ]
Setting up vlan (1.9-3ubuntu10) ...
Processing triggers for libc-bin (2.19-0ubuntu6) ...
Processing triggers for ureadahead (0.100.0-16) ...
Processing triggers for initramfs-tools (0.103ubuntu4.2) ...
update-initramfs: Generating /boot/initrd.img-3.16.0-57-generic
.... ....

如果你的source.list中添加了一些不稳定的源,那么这个包更新过程很可能会失败,从而导致stack.sh执行失败。解决方法就是识别出哪些源导致的失败,将之注释掉!

4、MySQL access denied

继续执行stack.sh,我们遇到了如下MySQL访问错误:

+lib/databases/mysql:configure_database_mysql:91  sudo mysql -u root -p devstack -h 127.0.0.1 -e 'GRANT ALL PRIVILEGES ON *.* TO '\''root'\''@'\''%'\'' identified by '\''devstack'\'';'
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
+lib/databases/mysql:configure_database_mysql:1  exit_trap
+./stack.sh:exit_trap:474                  local r=1
++./stack.sh:exit_trap:475                  jobs -p
+./stack.sh:exit_trap:475                  jobs=
+./stack.sh:exit_trap:478                  [[ -n '' ]]
+./stack.sh:exit_trap:484                  kill_spinner
+./stack.sh:kill_spinner:370               '[' '!' -z '' ']'
+./stack.sh:exit_trap:486                  [[ 1 -ne 0 ]]
+./stack.sh:exit_trap:487                  echo 'Error on exit'
Error on exit
+./stack.sh:exit_trap:488                  generate-subunit 1461911655 5243 fail
+./stack.sh:exit_trap:489                  [[ -z /opt/stack/logs ]]
+./stack.sh:exit_trap:492                  /home/baiming/devstack/tools/worlddump.py -d /opt/stack/logs
World dumping... see /opt/stack/logs/worlddump-2016-04-29-080139.txt for details

这个问题浪费了我不少时间,遍历了许多网上资料,最终下面这个方法解决了问题:

查看/etc/mysql/debian.cnf文件:

# Automatically generated for Debian scripts. DO NOT TOUCH!
[client]
host     = localhost
user     = debian-sys-maint
password = WY9OFagMxMb4YmyV
socket   = /var/run/mysqld/mysqld.sock
[mysql_upgrade]
host     = localhost
user     = debian-sys-maint
password = WY9OFagMxMb4YmyV
socket   = /var/run/mysqld/mysqld.sock
basedir  = /usr

修改mysql root密码:

$ [root@localhost ~]# mysql -u debian-sys-maint - p

输入密码: WY9OFagMxMb4YmyV  ,进入到mysql数据库

mysql>use mysql  ;
mysql>update user set password=password("你的新密码") where user="root";
mysql>flush privileges;
mysql>exit

然后尝试使用新密码登录,如果登录成功,说明密码修改ok。再执行stack.sh就不会出现MySQL相关错误了。

5、openvswitch/db.sock权限问题

接下来我们遇到的问题是openvswitch/db.sock权限问题,错误日志如下:

+lib/keystone:create_keystone_accounts:368  local admin_project
++lib/keystone:create_keystone_accounts:369  openstack project show admin -f value -c id
Discovering versions from the identity service failed when creating the password plugin. Attempting to determine version from URL.
Could not determine a suitable URL for the plugin
+lib/keystone:create_keystone_accounts:369  admin_project=
+lib/keystone:create_keystone_accounts:1   exit_trap
+./stack.sh:exit_trap:474                  local r=1
++./stack.sh:exit_trap:475                  jobs -p
+./stack.sh:exit_trap:475                  jobs=
+./stack.sh:exit_trap:478                  [[ -n '' ]]
+./stack.sh:exit_trap:484                  kill_spinner
+./stack.sh:kill_spinner:370               '[' '!' -z '' ']'
+./stack.sh:exit_trap:486                  [[ 1 -ne 0 ]]
+./stack.sh:exit_trap:487                  echo 'Error on exit'
Error on exit
+./stack.sh:exit_trap:488                  generate-subunit 1461920296 413 fail
+./stack.sh:exit_trap:489                  [[ -z /opt/stack/logs ]]
+./stack.sh:exit_trap:492                  /home/baiming/devstack/tools/worlddump.py -d /opt/stack/logs
World dumping... see /opt/stack/logs/worlddump-2016-04-29-090510.txt for details
2016-04-29T09:05:10Z|00001|reconnect|WARN|unix:/var/run/openvswitch/db.sock: connection attempt failed (Permission denied)
ovs-vsctl: unix:/var/run/openvswitch/db.sock: database connection failed (Permission denied)
+./stack.sh:exit_trap:498                  exit 1

我们手工执行ovs-vsctl命令:

$ sudo service openvswitch-switch status
openvswitch-switch start/running

 $ ovs-vsctl show
2016-04-29T09:44:19Z|00001|reconnect|WARN|unix:/var/run/openvswitch/db.sock: connection attempt failed (Permission denied)
ovs-vsctl: unix:/var/run/openvswitch/db.sock: database connection failed (Permission denied)

同样的错误。这个问题在网上似乎也没有很好的答案,这里做了一个权限更改处理:

$>chmod 777 /var/run/openvswitch/db.sock

问题解决了!

6、http proxy问题

我们接下来停在了这里:

++lib/keystone:create_keystone_accounts:369  openstack project show admin -f value -c id
Discovering versions from the identity service failed when creating the password plugin. Attempting to determine version from URL.
Could not determine a suitable URL for the plugin

还是停在这里,但这回不是/var/run/openvswitch /db.sock权限问题了。似乎是stack.sh想访问某个url获得一些version信息,但没有获取到。我开始怀疑是代理设置的问题:这个环境是有代理设置的,一旦走代理访问自己,那么肯定什么信息都得不到。但代理还不能去掉,因此很多组件下载都需要使用到代理访问外网。为此我们需要在stackrc中加上no_proxy环境变量:

export no_proxy='10.10.105.71'

再执行stack.sh,至少这个问题是pass了。

四、devstack部署ok

在经过很不耐烦的漫长等待后,devstack终于算是部署成功了!stack.sh打印出了下面信息后成功退出了:

=========================
DevStack Component Timing
=========================
Total runtime         2574

run_process            57
apt-get-update        120
pip_install           859
restart_apache_server  11
wait_for_service       32
apt-get                14
=========================
This is your host IP address: 10.10.105.71
This is your host IPv6 address: ::1
Horizon is now available at http://10.10.105.71/dashboard
Keystone is serving at http://10.10.105.71:5000/
The default users are: admin and demo
The password: devstack
2016-05-03 08:01:03.667 | stack.sh completed in 2574 seconds.

我们看devstack究竟运行了哪些组件:

$ ps -ef|grep python
stack     1464  1461  0 15:24 pts/5    00:00:14 /usr/bin/python /usr/bin/dstat -tcmndrylpg --output /opt/stack/logs/dstat-csv.log
stack     1465  1461  6 15:24 pts/5    00:03:01 /usr/bin/python /usr/bin/dstat -tcmndrylpg --top-cpu-adv --top-io-adv --swap
stack    11641 11490  0 15:48 pts/10   00:00:03 /usr/bin/python /usr/local/bin/glance-registry --config-file=/etc/glance/glance-registry.conf
stack    11899 11641  0 15:48 pts/10   00:00:00 /usr/bin/python /usr/local/bin/glance-registry --config-file=/etc/glance/glance-registry.conf
stack    11900 11641  0 15:48 pts/10   00:00:00 /usr/bin/python /usr/local/bin/glance-registry --config-file=/etc/glance/glance-registry.conf
stack    11978 11821  1 15:48 pts/11   00:00:24 /usr/bin/python /usr/local/bin/glance-api --config-file=/etc/glance/glance-api.conf
stack    12105 11978  1 15:48 pts/11   00:00:30 /usr/bin/python /usr/local/bin/glance-api --config-file=/etc/glance/glance-api.conf
stack    12106 11978  1 15:48 pts/11   00:00:30 /usr/bin/python /usr/local/bin/glance-api --config-file=/etc/glance/glance-api.conf
stack    13411 13262  2 15:51 pts/12   00:00:29 /usr/bin/python /usr/local/bin/nova-api
stack    13551 13411  0 15:52 pts/12   00:00:03 /usr/bin/python /usr/local/bin/nova-api
stack    13552 13411  0 15:52 pts/12   00:00:03 /usr/bin/python /usr/local/bin/nova-api
stack    13823 13411  0 15:52 pts/12   00:00:00 /usr/bin/python /usr/local/bin/nova-api
stack    13824 13411  0 15:52 pts/12   00:00:00 /usr/bin/python /usr/local/bin/nova-api
stack    14309 14159  1 15:52 pts/13   00:00:25 /usr/bin/python /usr/local/bin/nova-conductor --config-file /etc/nova/nova.conf
stack    15092 14941  3 15:52 pts/14   00:00:39 /usr/bin/python /usr/local/bin/nova-network --config-file /etc/nova/nova.conf
stack    15352 14309  2 15:52 pts/13   00:00:38 /usr/bin/python /usr/local/bin/nova-conductor --config-file /etc/nova/nova.conf
stack    15353 14309  2 15:52 pts/13   00:00:38 /usr/bin/python /usr/local/bin/nova-conductor --config-file /etc/nova/nova.conf
stack    15432 15274  1 15:52 pts/15   00:00:14 /usr/bin/python /usr/local/bin/nova-scheduler --config-file /etc/nova/nova.conf
stack    15920 15768  0 15:52 pts/16   00:00:05 /usr/bin/python /usr/local/bin/nova-novncproxy --config-file /etc/nova/nova.conf --web /opt/stack/noVNC
stack    16571 16415  1 15:52 pts/17   00:00:13 /usr/bin/python /usr/local/bin/nova-consoleauth --config-file /etc/nova/nova.conf
stack    17134 17131  3 15:53 pts/18   00:00:46 /usr/bin/python /usr/local/bin/nova-compute --config-file /etc/nova/nova.conf
stack    17890 17740  1 15:54 pts/19   00:00:23 /usr/bin/python /usr/local/bin/cinder-api --config-file /etc/cinder/cinder.conf
stack    18027 17890  0 15:54 pts/19   00:00:00 /usr/bin/python /usr/local/bin/cinder-api --config-file /etc/cinder/cinder.conf
stack    18028 17890  0 15:54 pts/19   00:00:01 /usr/bin/python /usr/local/bin/cinder-api --config-file /etc/cinder/cinder.conf
stack    18363 18212  2 15:54 pts/20   00:00:33 /usr/bin/python /usr/local/bin/cinder-scheduler --config-file /etc/cinder/cinder.conf
stack    18853 18699  1 15:54 pts/21   00:00:22 /usr/bin/python /usr/local/bin/cinder-volume --config-file /etc/cinder/cinder.conf
stack    19060 18853  2 15:54 pts/21   00:00:28 /usr/bin/python /usr/local/bin/cinder-volume --config-file /etc/cinder/cinder.conf

果然很复杂。devstack的安装体验比OpenStack似乎也好不到那里去。stack.sh执行的时间足够编译10次linux os内核了。好多依赖,好多download。

在devstack目录下,我们还可以执行一下devstack的测试,./exercise.sh会执行这些测试:

*********************************************************************
SUCCESS: End DevStack Exercise: /home/baiming/devstack/exercises/volumes.sh
*********************************************************************
=====================================================================
SKIP neutron-adv-test
SKIP swift
PASS aggregates
PASS client-args
PASS client-env
PASS sec_groups
PASS volumes
FAILED boot_from_volume
FAILED floating_ips
=====================================================================

此刻访问 http://10.10.105.71/dashboard,我们可以看到devstack horizon的首页:

img{512x368}

不过由于是通过SecureCRT端口映射访问到的主页,不知为何,登录后始终无法显示dashboard的页面。但通过后台horizon的日志来看,登录(admin/devstack)是成功的。我们仅能探索cli操作devstack的方式了。

五、CLI方式操作devstack

devstack提供了CLI方式对虚拟机、存储和网络等组件进行操作,其功能还要超过GUI所能提供的。在使用cli工具前,我们需要设置一些cli所需的用户变量,放在shell文件中(比如.bashrc):

export OS_USERNAME=admin
export OS_PASSWORD=devstack
export OS_TENANT_NAME=admin
export OS_AUTH_URL=http://10.10.105.71:5000/v2.0

上述变量生效后,我们就可以通过cli来hack devstack了:

nova位置和nova版本:

$ which nova
/usr/local/bin/nova

$ nova --version
4.0.0

当前image列表:

$ nova image-list
WARNING: Command image-list is deprecated and will be removed after Nova 15.0.0 is released. Use python-glanceclient or openstackclient instead.
+--------------------------------------+---------------------------------+--------+--------+
| ID                                   | Name                            | Status | Server |
+--------------------------------------+---------------------------------+--------+--------+
| b3f25af2-b5e1-43fe-8648-842fe48ed380 | cirros-0.3.4-x86_64-uec         | ACTIVE |        |
| d6bcc064-e2aa-4550-89e7-fd2f6a454758 | cirros-0.3.4-x86_64-uec-kernel  | ACTIVE |        |
| 788dec66-8989-4e84-8722-d9f4c9ee5ab0 | cirros-0.3.4-x86_64-uec-ramdisk | ACTIVE |        |
+--------------------------------------+---------------------------------+--------+--------+

虚拟机规格列表:

$ nova flavor-list
+----+-----------+-----------+------+-----------+------+-------+-------------+-----------+
| ID | Name      | Memory_MB | Disk | Ephemeral | Swap | VCPUs | RXTX_Factor | Is_Public |
+----+-----------+-----------+------+-----------+------+-------+-------------+-----------+
| 1  | m1.tiny   | 512       | 1    | 0         |      | 1     | 1.0         | True      |
| 2  | m1.small  | 2048      | 20   | 0         |      | 1     | 1.0         | True      |
| 3  | m1.medium | 4096      | 40   | 0         |      | 2     | 1.0         | True      |
| 4  | m1.large  | 8192      | 80   | 0         |      | 4     | 1.0         | True      |
| 42 | m1.nano   | 64        | 0    | 0         |      | 1     | 1.0         | True      |
| 5  | m1.xlarge | 16384     | 160  | 0         |      | 8     | 1.0         | True      |
| 84 | m1.micro  | 128       | 0    | 0         |      | 1     | 1.0         | True      |
| c1 | cirros256 | 256       | 0    | 0         |      | 1     | 1.0         | True      |
| d1 | ds512M    | 512       | 5    | 0         |      | 1     | 1.0         | True      |
| d2 | ds1G      | 1024      | 10   | 0         |      | 1     | 1.0         | True      |
| d3 | ds2G      | 2048      | 10   | 0         |      | 2     | 1.0         | True      |
| d4 | ds4G      | 4096      | 20   | 0         |      | 4     | 1.0         | True      |
+----+-----------+-----------+------+-----------+------+-------+-------------+-----------+

启动一个虚拟机:

$ nova boot --flavor 1 --image b3f25af2-b5e1-43fe-8648-842fe48ed380 devstack_instance_1
+--------------------------------------+----------------------------------------------------------------+
| Property                             | Value                                                          |
+--------------------------------------+----------------------------------------------------------------+
| OS-DCF:diskConfig                    | MANUAL                                                         |
| OS-EXT-AZ:availability_zone          |                                                                |
| OS-EXT-SRV-ATTR:host                 | -                                                              |
| OS-EXT-SRV-ATTR:hostname             | devstack-instance-1                                            |
| OS-EXT-SRV-ATTR:hypervisor_hostname  | -                                                              |
| OS-EXT-SRV-ATTR:instance_name        | instance-00000005                                              |
| OS-EXT-SRV-ATTR:kernel_id            | d6bcc064-e2aa-4550-89e7-fd2f6a454758                           |
| OS-EXT-SRV-ATTR:launch_index         | 0                                                              |
| OS-EXT-SRV-ATTR:ramdisk_id           | 788dec66-8989-4e84-8722-d9f4c9ee5ab0                           |
| OS-EXT-SRV-ATTR:reservation_id       | r-3rpqat0r                                                     |
| OS-EXT-SRV-ATTR:root_device_name     | -                                                              |
| OS-EXT-SRV-ATTR:user_data            | -                                                              |
| OS-EXT-STS:power_state               | 0                                                              |
| OS-EXT-STS:task_state                | scheduling                                                     |
| OS-EXT-STS:vm_state                  | building                                                       |
| OS-SRV-USG:launched_at               | -                                                              |
| OS-SRV-USG:terminated_at             | -                                                              |
| accessIPv4                           |                                                                |
| accessIPv6                           |                                                                |
| adminPass                            | dGBd6vj55vP2                                                   |
| config_drive                         |                                                                |
| created                              | 2016-05-03T09:00:33Z                                           |
| description                          | -                                                              |
| flavor                               | m1.tiny (1)                                                    |
| hostId                               |                                                                |
| host_status                          |                                                                |
| id                                   | bdb93a06-0c4f-434f-a1b5-ae2ca9293c58                           |
| image                                | cirros-0.3.4-x86_64-uec (b3f25af2-b5e1-43fe-8648-842fe48ed380) |
| key_name                             | -                                                              |
| locked                               | False                                                          |
| metadata                             | {}                                                             |
| name                                 | devstack_instance_1                                            |
| os-extended-volumes:volumes_attached | []                                                             |
| progress                             | 0                                                              |
| security_groups                      | default                                                        |
| status                               | BUILD                                                          |
| tenant_id                            | ce19134da8774d509bfa15daaca83665                               |
| updated                              | 2016-05-03T09:00:34Z                                           |
| user_id                              | 45436c9a744b4f41921edb3c368ce5f7                               |
+--------------------------------------+----------------------------------------------------------------+

通过nova list可以查看到当前主机上的虚拟机详情:

$ nova list
+--------------------------------------+---------------------+--------+------------+-------------+------------------+
| ID                                   | Name                | Status | Task State | Power State | Networks         |
+--------------------------------------+---------------------+--------+------------+-------------+------------------+
| bdb93a06-0c4f-434f-a1b5-ae2ca9293c58 | devstack_instance_1 | ACTIVE | -          | Running     | private=10.0.0.5 |
+--------------------------------------+---------------------+--------+------------+-------------+------------------+

在host上ping该虚拟机实例,可以ping通:

$ ping 10.0.0.5
PING 10.0.0.5 (10.0.0.5) 56(84) bytes of data.
64 bytes from 10.0.0.5: icmp_seq=1 ttl=64 time=0.935 ms
64 bytes from 10.0.0.5: icmp_seq=2 ttl=64 time=0.982 ms
^C
--- 10.0.0.5 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1000ms
rtt min/avg/max/mdev = 0.935/0.958/0.982/0.038 ms

通过网桥工具查看网桥设备,看到多出一个br100的网桥,eth0、vnet0~vnet2均连接在该网桥上:

$ brctl show
bridge name    bridge id        STP enabled    interfaces
br100        8000.0017a447a8a9    no        eth0
                            vnet0
                            vnet1
                            vnet2

挂起虚拟机:

$ nova suspend bdb93a06-0c4f-434f-a1b5-ae2ca9293c58
$ nova list
+--------------------------------------+---------------------+-----------+------------+-------------+------------------+
| ID                                   | Name                | Status    | Task State | Power State | Networks         |
+--------------------------------------+---------------------+-----------+------------+-------------+------------------+
| bdb93a06-0c4f-434f-a1b5-ae2ca9293c58 | devstack_instance_1 | SUSPENDED | -          | Shutdown    | private=10.0.0.5 |
+--------------------------------------+---------------------+-----------+------------+-------------+------------------+

六、小结

devstack号称是为开发准备的,已经“一键化”,但从实际效果来看,体验依旧不佳。由此也可以估计出OpenStack的部署难度和坎坷度了:)。

现代企业应用架构-使用Docker CaaS交付敏捷的、可移植的、受控的应用

年初,火得发烫的独角兽IT公司Docker发布了一款新的企业级产品 Docker Datacenter (简称:DDC)。作 为拥有原生Docker容器技术的公司,其每个市场动作都会让轻量级容器生态圈内的公司不敢小觑。而要揣度Docker对商业改变的理解、对容器 技术栈应用的理解以及对新产品和服务在生态圈中的定位,就有必要对Docker的这款产品做一些比较深刻的了解。而其技术白皮书 恰是我们了解 Docker该产品的入口。这里我就基于自己对容器相关技术栈的粗浅理解,翻译一下这篇篇幅不长的技术白皮书,希望能给大家带来些许帮助。

标题:现代企业应用架构-使用Docker CaaS交付敏捷的、可移植的、受控的应用

译文全文如下:

摘要

开发人员不接受被锁住的平台。就像《金发小女孩和三只熊》 故事那样,开发人员们一直在为其开发环境寻找一种可以在自由和约束之间拥 有最佳平衡的权力。在这个过程中,他们发现“平台即服务”(PaaS)模型层次太高、过于抽象以及约束过多,并且为了实现一个完全锁定的、黑盒的 环境而牺牲了灵活性;同时,他们也发现“基础设施即服务”(IaaS)模型提供的各自的容器服务也是不够的,因为那种服务仅驻留在各自的基础设施 中,缺乏远见。在寻求适当方案的过程中,一些组织开始提供基于Docker的“容器即服务”(CaaS)的环境,这种模型为开发团队提供了敏捷 性;为运维团队提供了控制力;为应用程序提供了跨基础设施的可移植性 — 从本地数据中心到公有云,横跨诸多网络和存储设备供应商。

Docker平台为基础设施无关的CaaS模型提供了一套集成套件。使用这个方案,IT运维团队既可以对基础设施,也可以对基础应用内容进行安全 保护、配置和管理;同时开发人员也能够以自助的方式来构建和部署他们的应用。

在本白皮书中,我们将讨论新软件模型的驱动力,Docker平台的能力,细化CaaS的需求,以及详细说明在构建、交付(运输)和运行应用程序过 程中解决核心问题的重要性。

重要结论包括:

• 云、数据和微服务是如何改变商业的
• 理解Docker的发展历程
• Docker CaaS模型的能力与优势

一、通过软件改变商业

运行成品软件的私有数据中心以及一年更新一次的巨大单一代码库的时代已经离我们远去了。一切都在变化。不管是迁移到云上,在云间移植,用现代化的 方法改造遗留程序,还是构建新的应用和数据结构,我们想要的结果都是相同的 – 速度。你动作的越快,你的公司将会越成功。

软件是定义你的公司的关键IP(知识产权),即便你的公司实际出售的商品可能只是一件T恤、一辆车或复利(compounding interest)。软件就是你如何接洽客户,如何吸引新用户,如何理解他们的数据,如何推广你的产品或服务以及如何处理他们的订单。

要做好这些,当今的软件正趋向定制化。为一个非常具体的工作而设计的软件片段被称为微服务(microservice)。微服务的设计目标是让 每一个由必要组件构建出来的服务在适当类型的底层基础设施资源上运行一个特定的工作(job)。接下来,这些服务松耦合在一起,可以随时被修改, 无需担心服 务运行的先后次序。

这种方法,虽然对持续改进十分有利,但在达成最终结果的过程中也提出了许多挑战。首先,它创建了一个新的、不断膨胀的服务、依赖和基础设施矩阵, 让它自身很难于管理。此外,它没有考虑到眼前大量已经存在的遗留程序,完全异构的应用程序栈以及实际中必须保证运行起来的进程。

二、Docker的发展历程以及AND的力量

2013年,Docker以具备构建、交付、到处运行的应用容器而出现在大众视野当中。与今天集装箱的运输类似,软件容器就是一个软件的标准单 元,不管容器内存放的代码和依赖是什么,容器外部看起来都相同。这使得开发人员和系统管理员可以跨基础设施和各种各样环境传输容器,而无需做任何 修改和考虑不同环境下的不同配置。Docker的历程就从此时开始了。

敏捷性: Docker的速度和简洁让Docker一经推出便大受开发者欢迎,同时也使得其开源项目的热度以流星般速度蹿升。现在开发者能很容易地将软件以及其依赖 打包到一个容器中。开发者可以使用任何语言、版本和工具,因为这些都被打包到一个容器中,容器将所有异质性标准化了,并且无需付出任何代价。

可移植性: Docker技术的本质让那批开发者意识到他们的应用容器现在可移植了,而且是以在以前不可能的方式。他们可以将应用从开发环境直接交付到测试和产品环境 且代码总是按设计那样正常工作。环境中的任何差异都不会影响到容器里面的应用。应用也无需修改就可以正常工作在生产环境中。这同样也是IT运维团 队的一个福音,因为现在他们可以跨数据中心迁移应用来避免厂商的平台锁定了。

控制: 当应用程序沿着通往生产环境的生命周期前进时,关于安全性、可管理性以及伸缩性等新问题需要进一步得到解答。Docker标准化了你的环境,同时维护着你 的业务所需的异质性。Docker提供了设置适当控制级别的能力以及维护服务级别、性能以及监管的灵活性。IT运维组能够通过供应、安全加固、监 控和伸缩基础设施和应用来保持峰值服务水平。没有两个程序或业务是一样的,Docker允许你决定如何去控制你的应用环境。

Docker成长历程的核心是AND的力量。Docker是唯一一个可以跨应用生命周期所有阶段,为开发者和IT运维团队在提供敏捷性、可移植性 和控制的方案。从这些核心原则来看,CaaS的脱颖而出正是由于由其构建的新应用又好又快。

三、Docker Containers as a Service(CaaS)

容器即服务(CaaS)是什么?它是基于基础设施和内容的一个IT受控的、安全的应用环境,利用它开发人员可以以自助的方式构建和部署应用。

img{512x368}

在上面的CaaS图示中,开发和IT运维团队通过registry相互协作。registry服务用于维护一个安全的、经过签名的映像仓库。左边 的开发者通过registry服务可以将软件拉(pull)到本地,按自己的步伐构建软件。当软件通过集成测试,开发者将其内容推回(push back)registry以保存最新版本。部署步骤因内部过程的不同而异,既可以通过工具自动进行,也可以是人工部署。

上图中右侧的IT运维组为生产环境基础设施管理着不同供应商的合同,诸如:计算、网络和存储。这些团队负责提供应用所需的计算资源,使用 Docker Universal Control Plane随时随地监控集群和应用。他们能在云间迁移应用,或伸缩服务来维持峰值服务水平。

四、关键特性和考量

Docker CaaS为组织提供了一套框架用于统一他们环境中的各种系统、语言和工具,并为业务提供所需的控制、安全或特权级别。由于是一种支持全部Docker API的Docker原生方案,Docker CaaS能够无缝地将应用从本地开发环境部署到生产环境,而无需改变代码或简化部署周期。

以下特性组成了组织应用环境的最低需求。在这个范式中,开发和运维团队被授权使用各自最佳的工具,而无需担心对系统、其他人的工作流或锁定状态造 成破坏。

1、开发者和运维的需求。 许多工具仅能解决针对一个团队的功能需求,但CaaS打破了持续改进的周期。为了获得从开发到生产环境运行的真正加速,你需要在一个连续周期内同时满足两类用户的需求。Docker为每个团队都提供了独特的能力,同时还提供了横跨整个平台的一致的API,保证了从一个团队到另外一个团队的无缝过渡。

2、应用程序生命周期的所有阶段。 从持续集成到持续交付以及开发运维(devops),这些实践都是为了消除瀑布开发方法以及其带来的滞后的周期。通过给开发和运维团队提供工具,Docker可以无缝的支持应用从构建、测试到部署到生产环境运行的所有阶段。

3、任何语言。开发者敏捷性意味着开发者在构建他们的应用的时候可以自由选择使用任何应用特性需要的编程语言、版本和工具。同时,在同一时间运行一个语言的多个版本的能力也为开发者提供了极大的灵活性。Docker让你的团队更加关注于构建应用程序本身,而不是思考如何构建一个可以在Docker中运行的应用。

4、任何操作系统。 绝大多数的组织拥有不止一款操作系统。一些工具在Linux上工作的更好,而另外一些可能在Windows上运行的更优异。应用平台需要考虑和支持这种多样性。否则,只能算是解决了部分问题而已。Docker起初是为Linux社区量身打造的,但Docker和微软公司正着手在Windows Server上实现Docker,以支持数百万现存企业应用以及未来企业应用。

5、任何基础设施。 谈到基础设施,组织想要的是选择、备份和杠杆作用。这是否意味着你需要拥有多个私有数据中心,一个混合云或者多个云提供商呢,其实关键点在于具备将应用负荷在不同环境间迁移而又不出问题的能力。Docker技术架构将基础设施与应用分离,使得应用容器可以在横跨基础设施在任意基础设施上运行。

6、Open API,插件式架构和生态系统。 一个平台不能算作是一个真正的平台,如果它只是一个封闭的孤岛。如果你想首先改良更新你现有的环境,通过实现新技术一般是不可行的。Docker的一个基本指导原则就是一个开放的平台。开放意味着API和插件可以让你利用上你已有的投资并让Docker适应你的环境和过程。开放性可以让生态系统更加活跃,且当你的CaaS增加特定功能时,它可以给你提供更多的灵活性和更多的选择。

虽然CaaS具有许多特性,但上述这些特性却是关键的,因为这种新的定制化应用范式只是为你的技术架构引入了更多异质性。Docker CaaS平台根本上就是为了支持这种多样性而设计的,并且针对任意规模提供相应的控制能力。

五、Docker CaaS

平台组件: Docker CaaS平台由一系列集成软件方案以及一个灵活的部署模型组成,以满足你的业务需求。

img{512x368}

本地数据中心/虚拟私有云(VPC): 对于那些要使用自己网络的组织,Docker Trusted Registry和Docker Universal Control Plan可以被部署在本地数据中心或虚拟私有云中,并且可以连接你已有的基础设施以及系统,比如存储、Active Directory/LDAP以及监控与日志解决方案。映像文件存储在你自己的存储架构中,Trusted Registry提供存储和管理服务能力,并且同时提供基于角色的对映像的基本访问控制。Universal Control Plane提供对Docker环境的可视化管理,包括Swarm集群、Trusted Registry仓库,容器以及多容器应用。

在云中: 对于那些接受使用SaaS方案的组织来说,Docker Hub和Docker Cloud提供了基于Docker上运行和管理的registry和control plane服务。Hub是一个云Registry服务,用于存储和管理映像文件以及用户权限。Docker Cloud供应和管理部署集群,同时也监控和管理已部署应用。使用Docker Cloud连接到你选择的云基础设施或使用你自己的物理节点来部署你的应用吧。

你的Docker CaaS可以设计成集中控制和管理,也可以设计成分布式管理以授权给各自应用团队。这种灵活性使得你可以建立一个最适合你的业务的模型,就像你选择基础设施和内容实现过程那样。CaaS是构建、交付和运行应用理念的一个延伸。

事实上由于CaaS统一了跨环境的本质,加速了许多IT倡议被接纳的过程。每个组织都有其自己采纳的倡议:从容器化,包括对已有应用的改造和迁移,到微服务,再到持续集成、持续交付和devops以及对各类云的接纳、迁移、混合及支持多种云。在每个场景中,Docker CaaS都能带来敏捷性、可移植性和控制,使得组织能接受那些用例。

六、AND的力量

总之,云、应用和数据的变化已经将技术和商业之间的对话,从“你如何帮我削减成本”换成了“你如何加速我的商业”。当你踏上你的旅途时,Docker提供了额外的灵活性帮你选择在哪里存储你的应用内容以及在哪里部署你的控制台。让你的CaaS适配你的业务需求,不管是部署在本地数据中心或虚拟私有云上,还是作为云服务被平滑地消费。无论你的业务是什么,Docker CaaS平台都会提供敏捷性、可移植性和控制力,尽可能又快又好的构建最好的应用,以最优的代价提供峰值性能的服务,并且不会被平台锁定。

理解Docker跨多主机容器网络

Docker 1.9 出世前,跨多主机的容器通信方案大致有如下三种:

1、端口映射

将宿主机A的端口P映射到容器C的网络空间监听的端口P’上,仅提供四层及以上应用和服务使用。这样其他主机上的容器通过访问宿主机A的端口P实 现与容器C的通信。显然这个方案的应用场景很有局限。

2、将物理网卡桥接到虚拟网桥,使得容器与宿主机配置在同一网段下

在各个宿主机上都建立一个新虚拟网桥设备br0,将各自物理网卡eth0桥接br0上,eth0的IP地址赋给br0;同时修改Docker daemon的DOCKER_OPTS,设置-b=br0(替代docker0),并限制Container IP地址的分配范围为同物理段地址(–fixed-cidr)。重启各个主机的Docker Daemon后,处于与宿主机在同一网段的Docker容器就可以实现跨主机访问了。这个方案同样存在局限和扩展性差的问题:比如需将物理网段的地址划分 成小块,分布到各个主机上,防止IP冲突;子网划分依赖物理交换机设置;Docker容器的主机地址空间大小依赖物理网络划分等。

3、使用第三方的基于SDN的方案:比如 使用Open vSwitch – OVSCoreOSFlannel 等。

关于这些第三方方案的细节大家可以参考O’Reilly的《Docker Cookbook》 一书。

Docker在1.9版本中给大家带来了一种原生的跨多主机容器网络的解决方案,该方案的实质是采用了基于VXLAN 的覆盖网技术。方案的使用有一些前提条件:

1、Linux Kernel版本 >= 3.16;
2、需要一个外部Key-value Store(官方例子中使用的是consul);
3、各物理主机上的Docker Daemon需要一些特定的启动参数;
4、物理主机允许某些特定TCP/UDP端口可用。

本文将带着大家一起利用Docker 1.9.1创建一个跨多主机容器网络,并分析基于该网络的容器间通信原理。

一、实验环境建立

1、升级Linux Kernel

由于实验环境采用的是Ubuntu 14.04 server amd64,其kernel版本不能满足建立跨多主机容器网络要求,因此需要对内核版本进行升级。在Ubuntu的内核站点 下载3.16.7 utopic内核 的三个文件:

linux-headers-3.16.7-031607_3.16.7-031607.201410301735_all.deb
linux-image-3.16.7-031607-generic_3.16.7-031607.201410301735_amd64.deb
linux-headers-3.16.7-031607-generic_3.16.7-031607.201410301735_amd64.deb

在本地执行下面命令安装:

sudo dpkg -i linux-headers-3.16.7-*.deb linux-image-3.16.7-*.deb

需要注意的是:kernel mainline上的3.16.7内核没有带linux-image-extra,也就没有了aufs 的驱动,因此Docker Daemon将不支持默认的存储驱动:–storage-driver=aufs,我们需要将storage driver更换为devicemapper

内核升级是一个有风险的操作,并且是否能升级成功还要看点“运气”:我的两台刀片服务器,就是一台升级成功一台升级失败(一直报网卡问题)。

2、升级Docker到1.9.1版本

从国内下载Docker官方的安装包比较慢,这里利用daocloud.io提供的方法 快速安装Docker最新版本:

$ curl -sSL https://get.daocloud.io/docker | sh

3、拓扑

本次的跨多主机容器网络基于两台在不同子网网段内的物理机承载,基于物理机搭建,目的是简化后续网络通信原理分析。

拓扑图如下:

img{512x368}

二、跨多主机容器网络搭建

1、创建consul 服务

考虑到kv store在本文并非关键,仅作跨多主机容器网络创建启动的前提条件之用,因此仅用包含一个server节点的”cluster”。

参照拓扑图,我们在10.10.126.101上启动一个consul,关于consul集群以及服务注册、服务发现等细节可以参考我之前的一 篇文章

$./consul -d agent -server -bootstrap-expect 1 -data-dir ./data -node=master -bind=10.10.126.101 -client=0.0.0.0 &

2、修改Docker Daemon DOCKER_OPTS参数

前面提到过,通过Docker 1.9创建跨多主机容器网络需要重新配置每个主机节点上的Docker Daemon的启动参数:

ubuntu系统这个配置在/etc/default/docker下:

DOCKER_OPTS="--dns 8.8.8.8 --dns 8.8.4.4  -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock --cluster-advertise eth0:2375 --cluster-store consul://10.10.126.101:8500/network --storage-driver=devicemapper"

这里多说几句:

-H(或–host)配置的是Docker client(包括本地和远程的client)与Docker Daemon的通信媒介,也是Docker REST api的服务端口。默认是/var/run/docker.sock(仅用于本地),当然也可以通过tcp协议通信以方便远程Client访问,就像上面 配置的那样。非加密网通信采用2375端口,而TLS加密连接则用2376端口。这两个端口已经申请在IANA注册并获批,变成了知名端口。-H可以配置多个,就像上面配置的那样。 unix socket便于本地docker client访问本地docker daemon;tcp端口则用于远程client访问。这样一来:docker pull ubuntu,走docker.sock;而docker -H 10.10.126.101:2375 pull ubuntu则走tcp socket。

–cluster-advertise 配置的是本Docker Daemon实例在cluster中的地址;
–cluster-store配置的是Cluster的分布式KV store的访问地址;

如果你之前手工修改过iptables的规则,建议重启Docker Daemon之前清理一下iptables规则:sudo iptables -t nat -F, sudo iptables -t filter -F等。

3、启动各节点上的Docker Daemon

以10.10.126.101为例:

$ sudo service docker start

$ ps -ef|grep docker
root      2069     1  0 Feb02 ?        00:01:41 /usr/bin/docker -d --dns 8.8.8.8 --dns 8.8.4.4 --storage-driver=devicemapper -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock --cluster-advertise eth0:2375 --cluster-store consul://10.10.126.101:8500/network

启动后iptables的nat, filter规则与单机Docker网络初始情况并无二致。

101节点上初始网络driver类型:
$docker network ls
NETWORK ID          NAME                DRIVER
47e57d6fdfe8        bridge              bridge
7c5715710e34        none                null
19cc2d0d76f7        host                host

4、创建overlay网络net1和net2

在101节点上,创建net1:

$ sudo docker network create -d overlay net1

在71节点上,创建net2:

$ sudo docker network create -d overlay net2

之后无论在71节点还是101节点,我们查看当前网络以及驱动类型都是如下结果:

$ docker network ls
NETWORK ID          NAME                DRIVER
283b96845cbe        net2                overlay
da3d1b5fcb8e        net1                overlay
00733ecf5065        bridge              bridge
71f3634bf562        none                null
7ff8b1007c09        host                host

此时,iptables规则也并无变化。

5、启动两个overlay net下的containers

我们分别在net1和net2下面启动两个container,每个节点上各种net1和net2的container各一个:

101:
sudo docker run -itd --name net1c1 --net net1 ubuntu:14.04
sudo docker run -itd --name net2c1 --net net2 ubuntu:14.04

71:
sudo docker run -itd --name net1c2 --net net1 ubuntu:14.04
sudo docker run -itd --name net2c2 --net net2 ubuntu:14.04

启动后,我们就得到如下网络信息(容器的ip地址可能与前面拓扑图中的不一致,每次容器启动ip地址都可能变化):

net1:
    net1c1 - 10.0.0.7
    net1c2 - 10.0.0.5

net2:
    net2c1 - 10.0.0.4
    net2c2 -  10.0.0.6

6、容器连通性

在net1c1中,我们来看看其到net1和net2的连通性:

root@021f14bf3924:/# ping net1c2
PING 10.0.0.5 (10.0.0.5) 56(84) bytes of data.
64 bytes from 10.0.0.5: icmp_seq=1 ttl=64 time=0.670 ms
64 bytes from 10.0.0.5: icmp_seq=2 ttl=64 time=0.387 ms
^C
--- 10.0.0.5 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.387/0.528/0.670/0.143 ms

root@021f14bf3924:/# ping 10.0.0.4
PING 10.0.0.4 (10.0.0.4) 56(84) bytes of data.
^C
--- 10.0.0.4 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1008ms

可见,net1中的容器是互通的,但net1和net2这两个overlay net之间是隔离的。

三、跨多主机容器网络通信原理

在“单机容器网络”一文中,我们说过容器间的通信以及容器到外部网络的通信是通过docker0网桥并结合iptables实现的。那么在上面已经建立的跨多主机容器网络里,容器的通信又是如何实现的呢?下面我们一起来理解一下。注意:有了单机容器网络基础后,这里很多网络细节就不再赘述了。

我们先来看看,在net1下的容器的网络配置,以101上的net1c1容器为例:

$ sudo docker attach net1c1

root@021f14bf3924:/# ip route
default via 172.19.0.1 dev eth1
10.0.0.0/24 dev eth0  proto kernel  scope link  src 10.0.0.4
172.19.0.0/16 dev eth1  proto kernel  scope link  src 172.19.0.2

root@021f14bf3924:/# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
8: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
    link/ether 02:42:0a:00:00:04 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.4/24 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:aff:fe00:4/64 scope link
       valid_lft forever preferred_lft forever
10: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:13:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.19.0.2/16 scope global eth1
       valid_lft forever preferred_lft forever
    inet6 fe80::42:acff:fe13:2/64 scope link
       valid_lft forever preferred_lft forever

可以看出net1c1有两个网口:eth0(10.0.0.4)和eth1(172.19.0.2);从路由表来看,目的地址在172.19.0.0/16范围内的,走eth1;目的地址在10.0.0.0/8范围内的,走eth0。

我们跳出容器,回到主机网络范畴:

在101上:
$ ip a
... ...
5: docker_gwbridge: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:52:35:c9:fc brd ff:ff:ff:ff:ff:ff
    inet 172.19.0.1/16 scope global docker_gwbridge
       valid_lft forever preferred_lft forever
    inet6 fe80::42:52ff:fe35:c9fc/64 scope link
       valid_lft forever preferred_lft forever
6: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN
    link/ether 02:42:4b:70:68:9a brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 scope global docker0
       valid_lft forever preferred_lft forever
11: veth26f6db4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker_gwbridge state UP
    link/ether b2:32:d7:65:dc:b2 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::b032:d7ff:fe65:dcb2/64 scope link
       valid_lft forever preferred_lft forever
16: veth54881a0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker_gwbridge state UP
    link/ether 9e:45:fa:5f:a0:15 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::9c45:faff:fe5f:a015/64 scope link
       valid_lft forever preferred_lft forever

我们看到除了我们熟悉的docker0网桥外,还多出了一个docker_gwbridge网桥:

$ brctl show
bridge name    bridge id        STP enabled    interfaces
docker0        8000.02424b70689a    no
docker_gwbridge        8000.02425235c9fc    no        veth26f6db4
                            veth54881a0

并且从brctl的输出结果来看,两个veth都桥接在docker_gwbridge上,而不是docker0上;docker0在跨多主机容器网络中并没有被用到。docker_gwbridge替代了docker0,用来实现101上隶属于net1网络或net2网络中容器间的通信以及容器到外部的通信,其职能就和单机容器网络中docker0一样。

但位于不同host且隶属于net1的两个容器net1c1和net1c2间的通信显然并没有通过docker_gwbridge完成,从net1c1路由表来看,当net1c1 ping net1c2时,消息是通过eth0,即10.0.0.4这个ip出去的。从host的视角,net1c1的eth0似乎没有网络设备与之连接,那网络通信是如何完成的呢?

这一切是从创建network开始的。前面我们执行docker network create -d overlay net1来创建net1 overlay network,这个命令会创建一个新的network namespace。

我们知道每个容器都有自己的网络namespace,从容器的视角看其网络名字空间,我们能看到网络设备诸如:lo、eth0。这个eth0与主机网络名字空间中的vethx是一个虚拟网卡pair。overlay network也有自己的net ns,而overlay network的net ns与容器的net ns之间也有着一些网络设备对应关系。

我们先来查看一下network namespace的id。为了能利用iproute2工具对network ns进行管理,我们需要做如下操作:

$cd /var/run
$sudo ln -s /var/run/docker/netns netns

这是因为iproute2只能操作/var/run/netns下的net ns,而docker默认的net ns却放在/var/run/docker/netns下。上面的操作成功执行后,我们就可以通过ip命令查看和管理net ns了:

$ sudo ip netns
29170076ddf6
1-283b96845c
5ae976d9dc6a
1-da3d1b5fcb

我们看到在101主机上,有4个已经建立的net ns。我们大胆猜测一下,这四个net ns分别是两个container的net ns和两个overlay network的net ns。从netns的ID格式以及结合下面命令输出结果中的network id来看:

$ docker network ls
NETWORK ID          NAME                DRIVER
283b96845cbe        net2                overlay
da3d1b5fcb8e        net1                overlay
dd84da8e80bf        host                host
3295c22b22b8        docker_gwbridge     bridge
b96e2d8d4068        bridge              bridge
23749ee4292f        none                null

我们大致可以猜测出来:

1-da3d1b5fcb 是 net1的net ns;
1-283b96845c是 net2的net ns;
29170076ddf6和5ae976d9dc6a则分属于两个container的net ns。

由于我们以net1为例,因此下面我们就来分析net1的net ns – 1-da3d1b5fcb。通过ip命令我们可以得到如下结果:

$ sudo ip netns exec 1-da3d1b5fcb ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP
    link/ether 06:b0:c6:93:25:f3 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.1/24 scope global br0
       valid_lft forever preferred_lft forever
    inet6 fe80::b80a:bfff:fecc:a1e0/64 scope link
       valid_lft forever preferred_lft forever
7: vxlan1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br0 state UNKNOWN
    link/ether ea:0c:e0:bc:19:c5 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::e80c:e0ff:febc:19c5/64 scope link
       valid_lft forever preferred_lft forever
9: veth2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master br0 state UP
    link/ether 06:b0:c6:93:25:f3 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::4b0:c6ff:fe93:25f3/64 scope link
       valid_lft forever preferred_lft forever

$ sudo ip netns exec 1-da3d1b5fcb ip route
10.0.0.0/24 dev br0  proto kernel  scope link  src 10.0.0.1

$ sudo ip netns exec 1-da3d1b5fcb brctl show
bridge name    bridge id        STP enabled    interfaces
br0        8000.06b0c69325f3    no        veth2
                            vxlan1

看到br0、veth2,我们心里终于有了底儿了。我们猜测net1c1容器中的eth0与veth2是一个veth pair,并桥接在br0上,通过ethtool查找veth序号的对应关系可以证实这点:

$ sudo docker attach net1c1
root@021f14bf3924:/# ethtool -S eth0
NIC statistics:
     peer_ifindex: 9

101主机:
$ sudo ip netns exec 1-da3d1b5fcb ip -d link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP
    link/ether 06:b0:c6:93:25:f3 brd ff:ff:ff:ff:ff:ff
    bridge
7: vxlan1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br0 state UNKNOWN
    link/ether ea:0c:e0:bc:19:c5 brd ff:ff:ff:ff:ff:ff
    vxlan
9: veth2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master br0 state UP
    link/ether 06:b0:c6:93:25:f3 brd ff:ff:ff:ff:ff:ff
    veth

可以看到net1c1的eth0的pair peer index为9,正好与net ns 1-da3d1b5fcb中的veth2的序号一致。

那么vxlan1呢?注意这个vxlan1并非是veth设备,在ip -d link输出的信息中,它的设备类型为vxlan。前面说过Docker的跨多主机容器网络是基于vxlan的,这里的vxlan1就是net1这个overlay network的一个 VTEP,即VXLAN Tunnel End Point – VXLAN隧道端点。它是VXLAN网络的边缘设备。VXLAN的相关处理都在VTEP上进行,例如识别以太网数据帧所属的VXLAN、基于 VXLAN对数据帧进行二层转发、封装/解封装报文等。

至此,我们可以大致画出一幅跨多主机网络的原理图:

img{512x368}

如果在net1c1中ping net1c2,数据包的行走路径是怎样的呢?

1、net1c1(10.0.0.4)中ping net1c2(10.0.0.5),根据net1c1的路由表,数据包可通过直连网络到达net1c2。于是arp请求获取net1c2的MAC地址(在vxlan上的arp这里不详述了),得到mac地址后,封包,从eth0发出;
2、eth0桥接在net ns 1-da3d1b5fcb中的br0上,这个br0是个网桥(交换机)虚拟设备,需要将来自eth0的包转发出去,于是将包转给了vxlan设备;这个可以通过arp -a看到一些端倪:

$ sudo ip netns exec 1-da3d1b5fcb arp -a
? (10.0.0.5) at 02:42:0a:00:00:05 [ether] PERM on vxlan1

3、vxlan是个特殊设备,收到包后,由vxlan设备创建时注册的设备处理程序对包进行处理,即进行VXLAN封包(这期间会查询consul中存储的net1信息),将ICMP包整体作为UDP包的payload封装起来,并将UDP包通过宿主机的eth0发送出去。

4、71宿主机收到UDP包后,发现是VXLAN包,根据VXLAN包中的相关信息(比如Vxlan Network Identifier,VNI=256)找到vxlan设备,并转给该vxlan设备处理。vxlan设备的处理程序进行解包,并将UDP中的payload取出,整体通过br0转给veth口,net1c2从eth0收到ICMP数据包,回复icmp reply。

我们可以通过wireshark抓取相关vxlan包,高版本wireshark内置VXLAN协议分析器,可以直接识别和展示VXLAN包,这里安装的是2.0.1版本(注意:一些低版本wireshark不支持VXLAN分析器,比如1.6.7版本):

img{512x368}

关于VXLAN协议的细节,过于复杂,在后续的文章中maybe会有进一步理解。




这里是Tony Bai的个人Blog,欢迎访问、订阅和留言!订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:


如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:


以太币:


如果您喜欢通过微信App浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:



本站Powered by Digital Ocean VPS。

选择Digital Ocean VPS主机,即可获得10美元现金充值,可免费使用两个月哟!

著名主机提供商Linode 10$优惠码:linode10,在这里注册即可免费获得。

阿里云推荐码:1WFZ0V立享9折!

View Tony Bai's profile on LinkedIn


文章

评论

  • 正在加载...

分类

标签

归档











更多