在使用 Docker 构建 PaaS 平台的过程中,我们首先遇到的问题是需要选择一个满足需求的网络模型:

  • 让每个容器拥有自己的网络栈,特别是独立的 IP 地址
  • 能够进行跨服务器的容器间通讯,同时不依赖特定的网络设备
  • 有访问控制机制,不同应用之间互相隔离,有调用关系的能够通讯

调研了几个主流的网络模型:

  • Docker 原生的 Bridge 模型:NAT 机制导致无法使用容器 IP 进行跨服务器通讯(后来发现自定义网桥可以解决通讯问题,但是觉得方案比较复杂)
  • Docker 原生的 Host 模型:大家都使用和服务器相同的 IP,端口冲突问题很麻烦
  • Weave OVS 等基于隧道的模型:由于是基于隧道的技术,在用户态进行封包解包,性能折损比较大,同时出现问题时网络抓包调试会很蛋疼

在对上述模型都不怎么满意的情况下,发现了一个还不怎么被大家关注的新项目:Project Calico 。

Project Calico 是纯三层的 SDN 实现,它基于 BPG 协议和 Linux 自己的路由转发机制,不依赖特殊硬件,没有使用 NAT 或 Tunnel 等技术。能够方便的部署在物理服务器,虚拟机(如 OpenStack)或者容器环境下。同时它自带的基于 Iptables 的 ACL 管理组件非常灵活,能够满足比较复杂的安全隔离需求。

使用 Calico 来实现 Docker 的跨服务器通讯

环境准备

  • 两个 Linux 环境 node1|2(物理机,VM 均可),假定 IP 为:192.168.78.21|22
  • 为了简单,请将 node1|2 上的 Iptables INPUT 策略设为 ACCEPT,同时安装 Docker
  • 一个可访问的 Etcd 集群(192.168.78.21:2379),Calico 使用其进行数据存放和节点发现

启动 Calico

在 node1|2 上面下载控制脚本:

# wget https://github.com/projectcalico/calico-docker/releases/download/v0.4.9/calicoctl

启动

# export ETCD_AUTHORITY=192.168.78.21:2379
# ./calicoctl node --ip=192.168.78.21|22

docker ps 能看到:

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
74cc20b90b0f calico/node:v0.4.9 "/sbin/my_init" 24 seconds ago Up 23 seconds calico-node

部署测试实例

在 Calico 中,有一个 Profile 的概念(类似 AWS 的 Security Group),位于同一个 Profile 中的实例才能互相通讯,所以我们先创建一个名为 db 的 Profile:

在 node1 上执行:

[node1]# ./calicoctl profile add db

然后启动测试实例:

[node1]# export DOCKER_HOST=localhost:2377
[node1]# docker run -n container1 -e CALICO_IP=auto -e CALICO_PROFILE=db -td ubuntu

这里大家注意,我们注入了两个环境变量:CALICO_IP 和 CALICO_PROFILE 。

前者告诉 CALICO 自动进行 IP 分配,后者将此容器加入到 Profile db 中。

那么 Calico 是怎么做到在容器启动的时候分配 IP 的呢?

大家注意我们在 run 一个容器前,先执行了一个 export,这里其实就是将 Docker API 的入口劫持到了 Calico 那里。Calico 内部是一个 twistd 实现的 Python Daemon,转发所有 Docker 的 API 请求给真正的 Docker 服务,如果发现是start 则插入自己的逻辑创建容器的网络栈。

容器启动后我们查看 container1 获取的 IP 地址:

[container1]# ip addr
...
8: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 1e:48:3e:ec:71:52 brd ff:ff:ff:ff:ff:ff
inet 192.168.0.1/32 scope global eth1
valid_lft forever preferred_lft forever

我们会看到 eth1 这个网络接口被设置了 IP 192.168.0.1

同样在 node2 上面部署 container2。

默认设置下 IP 会在 192.168.0.0/16 中按顺序分配,所以 container2 会是192.168.0.2

然后我们就会发现 container1|2 能够互相 ping 通了!

路由实现

接下来让我们看一下在上面的 demo 中,Calico 是如何让不在一个节点上的两个容器互相通讯的:

  • Calico 节点启动后会查询 Etcd,和其他 Calico 节点使用 BGP 协议建立连接
[node1]# netstat -anpt | grep 179
tcp 0 0 0.0.0.0:179 0.0.0.0:* LISTEN 21887/bird
tcp 0 0 192.168.78.21:46427 192.168.78.22:179 ESTABLISHED 21887/bird
  • 容器启动时,劫持相关 Docker API,进行网络初始化

    • 如果没有指定 IP,则查询 Etcd 自动分配一个可用 IP
    • 创建一对 veth 接口用于容器和主机间通讯,设置好容器内的 IP 后,打开 IP 转发
    • 在主机路由表添加指向此接口的路由
主机上:
[node1]# ip link show
...
7: cali2466cece7bc: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT qlen 1000
link/ether 96:c4:86:4d:d7:2c brd ff:ff:ff:ff:ff:ff
容器内:
[container1]# ip addr
...
8: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 1e:48:3e:ec:71:52 brd ff:ff:ff:ff:ff:ff
inet 192.168.0.1/32 scope global eth1
valid_lft forever preferred_lft forever
主机路由表:
[node1]# ip route
...
192.168.0.1 dev cali2466cece7bc scope link
  • 然后将此路由通过 BGP 协议广播给其他所有节点,在两个节点上的路由表最终是这样的:
[node1]# ip route
...
192.168.0.1 dev cali2466cece7bc scope link
192.168.0.2 via 192.168.78.22 dev enp0s8 proto bird
[node2]# ip route
...
192.168.0.1 via 192.168.78.21 dev enp0s8 proto bird
192.168.0.2 dev caliea3aaf5a7be scope link

大家看这个路由,node2 上面的 container2 要访问 container1(192.168.0.1),通过查路由表得知需要将包转给 192.168.78.21,也就是 node1。形象的展示数据流向是这样的:

container2[eth1] -> node2[caliea3aaf5a7be] -> route -> node1[cali2466cece7bc] -> container1[eth1]

至此,跨节点通讯打通,整个流程没有任何 NAT,Tunnel 封包。所以只要三层可达的环境,就可以应用 Calico。

利用 Profile 实现 ACL

在之前的 demo 中我们提到了 Profile,Calico 每个 Profile 都自带一个规则集,用于对 ACL 进行精细控制,如刚刚的db 的默认规则集是:

[node1]# ./calicoctl profile db rule json
{
"id": "db",
"inbound_rules": [
{
"action": "allow",
"src_tag": "db"
}
],
"outbound_rules": [
{
"action": "allow"
}
]
}

这个规则集表示入连接只允许来自 Profile 名字是 db 的实例,出连接不限制,最后隐含了一条默认策略是不匹配的全部 drop,所以同时位于不同 Profile 的实例互相是不能通讯的,这就解决了隔离的需求。

下面是一个更复杂的例子:

在常见的网站架构中,一般是前端 WebServer 将请求反向代理给后端的 APP 服务,服务调用后端的 DB:

WEB -> APP -> DB

所以我们要实现:

  • WEB 暴露 80 和 443 端口
  • APP 允许 WEB 访问
  • DB 允许 APP 访问 3306 端口
  • 除此之外,禁止所有跨服务访问

那么我们就可以如此构建 json:

对于 WEB

[node1]# cat web-rule.json
{
"id": "web",
"inbound_rules": [
{
"action": "allow",
"src_tag": "web"
},
{
"action": "allow",
"protocol": "tcp",
"dst_ports": [
80,
443
]
}
],
"outbound_rules": [
{
"action": "allow"
}
]
}
[node1]# ./calicoctl profile web rule update < web-rule.json

入站规则我们增加了一条允许 80 443

对于 APP

[node1]# cat app-rule.json
{
"id": "app",
"inbound_rules": [
{
"action": "allow",
"src_tag": "app"
},
{
"action": "allow",
"src_tag": "web"
}
],
"outbound_rules": [
{
"action": "allow"
}
]
}
[node1]# ./calicoctl profile app rule update < app-rule.json

对于后端服务,我们只允许来自 web 的连接。

对于 DB,我们在只允许 APP 访问的基础上还限制了只能连接 3306。

[node1]# cat db-rule.json
{
"id": "db",
"inbound_rules": [
{
"action": "allow",
"src_tag": "db"
},
{
"action": "allow",
"src_tag": "APP",
"protocol": "tcp",
"dst_ports": [
3306
]
}
],
"outbound_rules": [
{
"action": "allow"
}
]
}
[node1]# ./calicoctl profile db rule update < db-rule.json

很简单的几条规则,我们就实现了上述需求。

Profile 高级特性:Tag

有同学可能说,在现实环境中,会有多组不同的 APP 都需要访问 DB,如果每个 APP 都在 db 中增加一条规则也很麻烦同时还容易出错。

这里我们可以利用Profile 的高级特性 Tag 来简化操作:

  • 每个 Profile 默认拥有一个和 Profile 名字相同的 Tag
  • 每个 Profile 可以有多个 Tag,以 List 形式保存

利用 Tag 我们可以将一条规则适配到指定的一组 Profile 上。

参照上面的例子,我们给所有需要访问 DB 的 APP 的 Profile 都加上 db-users这个 Tag:

[node1]# ./calicoctl profile app1 tag add db-users
[node1]# ./calicoctl profile app2 tag add db-users
[node1]# ./calicoctl profile app3 tag add db-users
...

然后修改 db-rule.json 为:

{
"id": "db",
"inbound_rules": [
{
"action": "allow",
"src_tag": "db"
},
{
"action": "allow",
"src_tag": "db-users",
"protocol": "tcp",
"dst_ports": [
3306
]
}
],
"outbound_rules": [
{
"action": "allow"
}
]
}

将之前的 src_tag: app 替换为 src_tag: db-users。这样所有打了 db-user 这个 Tag 的实例就都能访问数据库了。

Profile 的实现

Profile 的实现基于 Iptables 和 IPSet。我们以刚刚的 db 规则集中 inbound 部分为例:

Calico 在启动后会在 Iptables 中新建一些 Chain,数据包会在不同的 Chain 之间跳转,下面我截取了一些关键的规则列表:

[node1]# iptables -n -L -v
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
target prot in out source destination
felix-FORWARD all * * 0.0.0.0/0 0.0.0.0/0
Chain felix-FORWARD (1 references)
target prot in out source destination
felix-TO-ENDPOINT all * cali+ 0.0.0.0/0 0.0.0.0/0
Chain felix-TO-ENDPOINT (1 references)
target prot in out source destination
felix-to-2466cece7bc all * cali2466cece7bc 0.0.0.0/0 0.0.0.0/0 [goto]
Chain felix-to-2466cece7bc (1 references)
target prot in out source destination
felix-p-db-i all * * 0.0.0.0/0 0.0.0.0/0
Chain felix-p-db-i (2 references)
target prot in out source destination
RETURN all * * 0.0.0.0/0 0.0.0.0/0 match-set felix-v4-db src
RETURN tcp * * 0.0.0.0/0 0.0.0.0/0 match-set felix-v4-db-users src multiport dports 3306

这个略复杂,我们慢慢看。基本上数据包是从上到下一步步跳转的。

当发给 container1 的数据包到达 node1 后,由于目标 IP 192.168.0.1和 node1 自身 IP 不同,会被放入FORWARD 链,然后跳转到felix-FORWARD,通过查询路由表:

192.168.0.1 dev cali2466cece7bc scope link

得知下一跳接口为 cali2466cece7bc,于是先跳转到felix-TO-ENDPOINT,再跳转到 felix-to-2466cece7bc

在这里,定义了具体的 ACL 列表,felix-p-db-i,这个 db 是不是很眼熟?

对,就是这个 container 所属 Profile 的名字,而 felix-p-db-i 中就是 Profiledb 的 inbound 规则集。而 felix-p-db-i 的内容:

match-set felix-v4-db src
match-set felix-v4-db-users src multiport dports 3306

felix-v4-db 和 felix-v4-db-users 是不是也很熟悉?

在 db 规则集中的两个 Tag 在这里加了个前缀变成了 IPSet,它包括了所有打了这个 Tag 的 IP 列表:

[node1]# ipset list
...
Name: felix-v4-db
Type: hash:ip
Revision: 1
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 16576
References: 1
Members:
192.168.0.1
192.168.0.2

至此,ACL 部分分析完毕。可见 Calico 灵活运用了 Iptables 的种种高级特性。

性能测试

来自官方测试结果

测试环境

  • 8 core CPU
  • 64GiB RAM
  • 10Gb 网卡直连
  • Ubuntu 14.04.2 with 3.13 Kernel(3.10 版本的 Kernel 修复了一些 veth 性能的问题)
  • 没有额外的内核参数调优

测试了四种场景:

  • 物理服务器(基准)
  • 部署了 Calico 的容器之间
  • 部署了 Calico 的 OpenStack VM 之间
  • 部署了 OVS with VxLAN 的 OpenStack VM 之间

测试了两种数据大小:

  • 20000 byte
  • 500 byte

吞吐量 & CPU 使用率测试

吞吐量极限

同一时刻 CPU 的使用率

把它们合并成一张图就是每 Gb/s 的 CPU 使用率

可以看到,部署了 Calico 的两个场景都非常贴近物理服务器的性能。

延迟测试

测试方法是:节点间交换 1 byte 数据包

这个结果显示,Calico 容器非常接近物理服务器,而 OpenStack 场景由于网络虚拟化的缘故延迟稍大。

结论

测试结果表明,Calico 的性能非常接近物理服务器,比基于隧道的 OVS 性能好很多。

Calico 的发展

Calico 和 Docker 一样是很年轻的项目,但是坑比后者少多了,我遇到了一些,如docker inspect 没有显示 Calico 分配的 IP,BGP 客户端重启姿势不正确导致路由周期性消失重建等等。但是他们的开发进度非常快,一个 issue 提出来到修复可能就一两天时间(再次鄙视 Docker)。

目前唯一一个比较麻烦的问题是,Calico 这种劫持 Docker API 的方式,容器的网络栈是在容器启动后才进行初始化,所以在头几秒其实是没有网络可用的,这会导致那些启动就要访问网络的容器挂掉。解决方案有两个:

  • 升级 Docker 到支持 libnetwork 的版本,Calico 在新版本(>0.5)中支持了 libnetwork,理论上能够解决这个问题。但是代价要踩新版本 Docker 带来的更多的坑。
  • 自定义容器的 CMD,实现一个 entry 脚本,待网络可用后再 exec 载入真正的进程。

Q&A

Q:自定义容器的 CMD,实现一个 entry 脚本,待网络可用后再 exec 载入真正的进程。 有没有具体的?

主要实现方式就是 Dockerfile 中的 CMD 可以这个样子: /entry.sh your-cmd 。这个 entry.sh 中判断 IP 是否已经分配好,如果没有就 sleep 重试。分配好后再用 exec 载入后面的 your-cmd 。

Q:是否每增加一个容器宿主机就需要增加一条路由?如果容器数量很多会有问题吗?

是的。关于这个问题我咨询过开发团队,他们表示压测过单机 10 万条路由,没有问题。同时将来会推出路由段的广播机制,即:每台服务器使用一小段,之间只需要广播此段即可。

Q:如果要做容器间通讯限速,Calico能做吗?

由于每个由 Calico 管理的容器在宿主机上面都有一个唯一的网络接口(veth 的一端),通过限制此接口流量即可进行限速。Calico 官方没有提供这个功能,我们可以用常规的其他手段解决。同时这个接口还有一个好处就是非常容易做容器的流量监控,只要看接口计数器即可。

Q:Calico 和 Kubernetes 的整合你们尝试过吗?Calico只是接管了容器间的通信,和 Kubernetes 的 Service Cluster IP 没有关系吧?

我们没有使用 Kubernetes 的解决方案,而是自行开发的调度编排等组件。Cailco 官方是支持的并且有相关文档可以参考。具体请参考:https://github.com/projectcalico/calico-docker/blob/master/docs/kubernetes/README.md

原文链接

PaaS 平台的网络需求的更多相关文章

  1. 洪强宁:宜信PaaS平台基于Calico的容器网络实践

    洪强宁:宜信PaaS平台基于Calico的容器网络实践   本文内容来自由七牛云主办的ECUG Con,独家授权InfoQ整理完成 容器云面临的网络挑战 在传统的IDC的架构里面网络是很重要的事情,在 ...

  2. PaaS平台型IT运维&运营模式能给企业带来什么?

    关注嘉为科技,获取运维新知 什么是PaaS平台型IT自动化运维&运营模式 PaaS平台型IT运维和运维模式是指:将通用的运维能力与具体的运维场景解耦合,将能够复用的,具备独立功能的通用能力纳入 ...

  3. 如何理解PaaS平台,与SaaS、IaaS有什么区别?

    我们经常会看到SaaS.PaaS.IaaS,但总是会摸不着头脑,有的人甚至会以为是恐怖组织的代号.其实,无论是SaaS.PaaS还是IaaS,都代表的是某一种服务,比如SaaS的含义为"软件 ...

  4. 移动App开发需要更多的PaaS平台而不是IaaS

    时代的变迁,创业的大潮,越来越多的人关注了有点开发,越来越多的人了解了互联网服务术语:PaaS.IaaS.SaaS.BaaS等.今天大家在开发App的时候这么多复杂的云服务如何来选择呢? IaaS服务 ...

  5. PAAS平台构建7×24小时高可用应用的方案设计

    本博客迁移到部署在jae上的独立博客系统wordpress,博客地址:点击打开独立博客.欢迎大家一起来讨论IT技术. 现在很多企业都在搭建自己的私有PAAS平台,当然也有很多大型互联网公司搭建共有PA ...

  6. PAAS平台的web应用性能测试与分析

    引言 为什么我会写这一篇博客,因为最近很多京东云擎jae的用户反应一个问题就是他们部署在jae上面的应用访问很慢,有极少数应用甚至经常出现504超时现象,当然大家首先想到的是jae性能太差,这也是人之 ...

  7. 搜狐云景paas平台实践之路

    前言: 搜狐云景作为搜狐的paas平台,在2014年5月22日的云计算大会上正式发布了公测.初测,注册用户必须先申请邀请码参与公测会赠送用户100元电子券,经过实名认证之后会再赠送100电子券,目测可 ...

  8. PAAS平台7&#215;24小时可用性应用设计

    如今非常多企业都在搭建自己的私有PAAS平台,当然也有非常多大型互联网公司搭建共同拥有PAAS平台(比如SAE/BAE/JAE(jae.jd.com)).那么使用PAAS平台来部署SAAS应用有哪些优 ...

  9. PaaS平台的尴尬与变革

    当今时代只要提到云计算这个词语,一定会提到云计算分为IaaS.PaaS.SaaS 这三个层面,现阶段云环境中IaaS和SaaS都实现了商品化.但是,PaaS作为云计算的服务模式之一,既不像IaaS那样 ...

随机推荐

  1. cocos2d-x v3.0新特性及使用

    八月份cocos2d-x官网发布了v3.0版本,这次更新的内容特别多,包括2dx的架构以及使用 总得来说,给开发者带来了很大的便利: 运行环境需求: Android 2.3 or newer iOS ...

  2. java设计模式2--抽象工厂模式(Abstract Factory)

    本文地址:http://www.cnblogs.com/archimedes/p/java-abstract-factory-pattern.html,转载请注明源地址. 抽象工厂模式(别名:配套) ...

  3. android4.0 USB Camera实例(三)UVC

    前面我写了两篇文章说明了zc301的实现 详细请看 http://blog.csdn.net/hclydao/article/details/21235919 以下顺便把通用的USB也写上 前面的ZC ...

  4. Android -- Messenger与Service

    如果你需要你的service和其他进程通信,那么你可以使用一个Messenger来提供这个接口. 这种方法允许你在不使用 AIDL的情况下,进行跨进程通信IPC. 实现步骤 下面是一个如何使用 Mes ...

  5. 如何修改Git commit的信息

    原文地址: http://xiguada.org/change-git-commit-message   Git cimmit信息push后,如何修改,amend可以修改最后一次commit信息,但对 ...

  6. 【笔记】css 1像素边框

    有时候在移动端显示1像素的边框时 可能因为dpi 的原因造成像素有变差 所以为了达到显示的像素达到预期效果就要编写一个css 通用类达到1像素边框的效果 关于dpi 的解释请看张鑫旭老师的文章:htt ...

  7. 在低带宽或不可靠的网络环境中安装 Visual Studio 2017

    在低带宽或不可靠的网络环境中安装 Visual Studio 2017 2017-4-141 分钟阅读时长 作者  https://docs.microsoft.com/zh-cn/visualstu ...

  8. mysql更新日志问题

    [root@localhost ~]# /etc/init.d/mysqld restart 停止 mysqld: [确定] 正在启动 mysqld: [确定] 故障:今天在维护以前数据库日志的时候, ...

  9. HDU1004——Let the Balloon Rise

    Problem Description Contest time again! How excited it is to see balloons floating around. But to te ...

  10. Hadoop HelloWord Examples -对Hadoop FileSystem进行操作 - 基于Java

    我之前对hadoop的各种文件操作都是基于命令行的,但是进阶后,经常需要直接从java的代码中对HDFS进行修改.今天来练习下. 一个简单的demo,将hdfs的一个文件的内容拷贝到另外hdfs一个文 ...