Docker 编排工具调研: Rancher

2015-10-14 10:14:47 +08:00
 feiyang21687
> 转载自:[feiyang21687.github.io/Rancher](feiyang21687.github.io/Rancher)

Rancher 提供了一套完整的 Docker 编排解决方案(重点是[开源]( https://github.com/rancher/rancher)的)。功能上包括网络,存储,负载均衡,安全,服务发现和资源管理等。 Rancher 可以管理[DigitalOcean](cloud.digitalocean.com)、[AWS](aws.amazone.com)、[OpenStack]( http://openstack.org)等云主机,自动创建 Docker 运行环境,实现跨云管理。使用上可以通过 Web 界面或者命令行方式进行操作。

![]( http://docs.rancher.com/img/rancher/rancher_overview.png)

下面介绍一下使用上的一些体会,并且分析一下一些有意思的特性的技术实现。先说下整体上的体验, Rancher 在使用上非常流畅,当然是指排除“功夫王”的影响之外。学习成本也比较小,稍微有一点 Docker 知识的同学,都能很快上手。

## 运行

Rancher 提供了 Docker 镜像,直接通过`docker run -d --restart=always -p 8080:8080 rancher/server`命令运行即可。除了标准的 Docker 镜像之外, Rancher 还提供了手动、 Vagrant 、 Puppet 、 Ansible 等方式安装。

这里要提到一点, Rancher 也是采用 Master-Agent 的方式,但是 Rancher 的 Agent 也是以容器方式运行的,虚机环境修改很少。

Rancher 容器是采用的胖容器方式,会运行[Cattle]( https://github.com/rancher/cattle), Mysql , rancher-compose-executor , go-machine-service 等几个服务,以及 crontab 等定时任务。其中值得一提[Cattle]( https://github.com/rancher/cattle), Cattle 是 Rancher 实现的一套基于 Docker 的编排引擎,初步上来看跟 Docker Swarm 类似,是用 Java 语言实现的,初步看代码实现的功能还是比较多的。

另外需要注意,由于 Rancher 目前还仅支持管理墙外的云服务,所以在墙内运行的话会导致创建虚机失败率非常高。另外 Rancher 运行至少需要 1G 的内存,这也导致小编在 DO 上创建 512MB 的 Droplet 运行 Rancher 服务失败,辛辛苦苦排查了半天,如果你遇到下面类似的异常日志,那说明你得换一个大一点的 Droplet :

```
2015-10-12 10:17:58,910 INFO [main] [ConsoleStatus] [99/102] [43348ms] [8ms] Starting register
runtime/cgo: pthread_create failed: Resource temporarily unavailable
SIGABRT: abort
PC=0x6f8177 m=0

goroutine 0 [idle]:

goroutine 1 [running]:
runtime.systemstack_switch()
/usr/local/go/src/runtime/asm_amd64.s:216 fp=0xc820024770 sp=0xc820024768
runtime.main()
/usr/local/go/src/runtime/proc.go:49 +0x62 fp=0xc8200247c0 sp=0xc820024770
runtime.goexit()
/usr/local/go/src/runtime/asm_amd64.s:1696 +0x1 fp=0xc8200247c8 sp=0xc8200247c0

goroutine 17 [syscall, locked to thread]:
runtime.goexit()
/usr/local/go/src/runtime/asm_amd64.s:1696 +0x1

rax 0x0
rbx 0xc30488
rcx 0x6f8177
rdx 0x6
rdi 0x8c
rsi 0x8c
rbp 0x734b32
rsp 0x7ffe7ce05578
r8 0xa
r9 0x27ae880
r10 0x8
r11 0x202
r12 0x27b0bc0
r13 0x8ed580
r14 0x0
r15 0x8
rip 0x6f8177
rflags 0x202
cs 0x33
fs 0x0
gs 0x0
runtime/cgo: pthread_create failed: Resource temporarily unavailable
SIGABRT: abort
PC=0x6f8177 m=0
```

另外还有一个需要注意的, Rancher 容器启动之后并不能立即对外提供服务, Rancher 内部服务启动需要耗费一定的时间。从日志上看大概需要将近 1 分钟的启动时间,这个有待优化啊!所以如果你运行完容器之后,访问界面抛出```Connection Refuse Exception```不要以为是启动方式有问题,请耐心等待......

```
09:36:32.655 [main] INFO ConsoleStatus - [DONE ] [50277ms] Startup Succeeded, Listening on port 8081
```

## 虚机

![]( http://rancher.com/wp-content/uploads/2015/06/container_networking.png)

从 Web 界面上看,操作是非常简便的,例如对于 DO 来说直接输入 APPKEY 就可以完成 Droplet 的创建。目前 Rancher 还仅支持 Ubuntu 版本的虚机。从运行日志上看, Rancher 在初始化虚机的时候做了这么几件事情:

- 通过定制化的 Docker Machine 创建虚机,完成 Docker 安装
- 初始化 SSH 证书
- 上传 rancher.tar.gz 到虚机
- 拉取 rancher/agent:v0.8.2 镜像

可以看出环境依赖非常少。 Docker 配置也比较标准,除了添加了 TLS 认证之外没有额外的配置。主机配置也就是一些证书文件,没有额外的配置。从这方面来看 Rancher 本身是比较容易支持扩展的,引入对阿里云的支持也会比较简单。但是另外一方面,由于代码架构上的原因,第三方开发支持云服务可能会比较纠结,比较可行的方式还是 Rancher 和各云服务提供商配合。这可能也会限制 Rancher 在国内的推广。

另外创建出来的虚机仅支持根据 ssh key 方式登陆,不支持 root 密码登陆。目前有两种方式可以 ssh 登陆到虚机,一个是通过 Rancher 提供的 Web SSH 界面,一个是下载主机配置,使用里面的 id_rsa 文件来登陆```ssh -i id_rsa root@<IP_OF_HOST>```。

虚机支持按 Tag 属性管理,不支持直接按组管理,对于多租户场景可以通过 Rancher 提供的```Management Environment```功能来实现。

另外一个比较有意思的是 Rancher 提供了一个```View in API```功能,返回的是一个 JSON 格式数据,其中 links 字段包含了一些有关的接口地址,这个功能对二次功能的开发比较友好,可以通过接口 API 获取 API ,这样就不必学习接口调用方式、参数等等内容了。

```
{
"id": "1h1",
"type": "host",
"links": {
"self": "…/v1/projects/1a5/hosts/1h1",
"account": "…/v1/projects/1a5/hosts/1h1/account",
"clusters": "…/v1/projects/1a5/hosts/1h1/clusters",
"containerEvents": "…/v1/projects/1a5/hosts/1h1/containerevents",
"hostLabels": "…/v1/projects/1a5/hosts/1h1/hostlabels",
"instances": "…/v1/projects/1a5/hosts/1h1/instances",
"ipAddresses": "…/v1/projects/1a5/hosts/1h1/ipaddresses",
"loadBalancerHostMaps": "…/v1/projects/1a5/hosts/1h1/loadbalancerhostmaps",
"loadBalancers": "…/v1/projects/1a5/hosts/1h1/loadbalancers",
"physicalHost": "…/v1/projects/1a5/hosts/1h1/physicalhost",
"serviceEvents": "…/v1/projects/1a5/hosts/1h1/serviceevents",
"storagePools": "…/v1/projects/1a5/hosts/1h1/storagepools",
"stats": "…/v1/projects/1a5/hosts/1h1/stats",
"hostStats": "…/v1/projects/1a5/hosts/1h1/hoststats",
"containerStats": "…/v1/projects/1a5/hosts/1h1/containerstats",
},
"actions": {
"update": "…/v1/projects/1a5/hosts/1h1/?action=update",
"deactivate": "…/v1/projects/1a5/hosts/1h1/?action=deactivate",
},
"name": "rancher",
"state": "active",
"accountId": "1a5",
"agentState": null,
"computeTotal": 1000000,
"created": "2015-10-13T04:08:55Z",
"createdTS": 1444709335000,
"description": null,
"info": {
"osInfo": {
```

同时还支持基础层面的监控,而且是用 WebSocket 实现的,```"url": "ws://104.236.151.239:8080/v1/hoststats"```。看代码应该是没有用到什么第三方的监控解决方案,比如 ELK 、 Graphite 之类的,应该 Cattle 自行实现的。猜测实现是在[io.cattle.platform.host.stats.api]( https://github.com/rancher/cattle/tree/2a075f16346176fc2294ba5e68ebbbccf3ef2629/code/implementation/host-stats/src/main/java/io/cattle/platform/host/stats/api)。不过貌似不是特别稳定,窗口切换后会导致监控数据不再刷新。像下图这种情况:

![]( )

## 容器

容器编排本身并没有什么特别的功能,基本是对 Docker 命令的封装。其中有几个特性值得关注,一个是 Managed 网络,第二个是健康检查,第三个是调度策略。下面一个个来看:

### Managed 网络

Rancher 在 Docker 本身提供的几种网络基础上提供了一种叫做 Managed 的网络特性,按照其官方说法是:

> The Rancher network uses IPsec tunnelling and encryption for security

简单理解就是在容器之间构建了一条私有网络,只有容器与容器之间可以访问。 Rancher 会为每个容器分配一个```10.42.*.*```的私有网络地址,这个地址只在容器之间是可达的,对外不可见,有点类似一种纯软件上的 VPC 方式。

![]( )

从上面的部署上看, Rancher 并没有对网络有什么特殊的设置,它是如何实现的呢?从路由上可以看出,对于 10.42 网段的路由是通过 docker0 接口, docker0 的特殊的配置了 IP 也包含了 10.42 网段。

```
root@rancher:~# ip route
default via 192.241.212.1 dev eth0
10.42.0.0/16 dev docker0 proto kernel scope link src 10.42.0.1
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.42.1
192.241.212.0/24 dev eth0 proto kernel scope link src 192.241.212.19

root@rancher:~# ip addr
4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:21:6e:92:f5 brd ff:ff:ff:ff:ff:ff
inet 172.17.42.1/16 scope global docker0
valid_lft forever preferred_lft forever
inet 10.42.0.1/16 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:21ff:fe6e:92f5/64 scope link
valid_lft forever preferred_lft forever
```

实现流量转发应该是靠 iptables 实现的,规则如下:

```
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
CATTLE_PREROUTING all -- anywhere anywhere
DOCKER all -- anywhere anywhere ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT)
target prot opt source destination

Chain OUTPUT (policy ACCEPT)
target prot opt source destination
DOCKER all -- anywhere !loopback/8 ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
CATTLE_POSTROUTING all -- anywhere anywhere
MASQUERADE all -- 172.17.0.0/16 anywhere
MASQUERADE udp -- 172.17.0.4 172.17.0.4 udp dpt:ipsec-nat-t
MASQUERADE udp -- 172.17.0.4 172.17.0.4 udp dpt:isakmp

Chain CATTLE_POSTROUTING (1 references)
target prot opt source destination
ACCEPT all -- 10.42.0.0/16 169.254.169.250
MASQUERADE tcp -- 10.42.0.0/16 !10.42.0.0/16 masq ports: 1024-65535
MASQUERADE udp -- 10.42.0.0/16 !10.42.0.0/16 masq ports: 1024-65535
MASQUERADE all -- 10.42.0.0/16 !10.42.0.0/16
MASQUERADE tcp -- 172.17.0.0/16 anywhere masq ports: 1024-65535
MASQUERADE udp -- 172.17.0.0/16 anywhere masq ports: 1024-65535

Chain CATTLE_PREROUTING (1 references)
target prot opt source destination
DNAT udp -- anywhere anywhere ADDRTYPE match dst-type LOCAL udp dpt:ipsec-nat-t to:10.42.241.7:4500
DNAT udp -- anywhere anywhere ADDRTYPE match dst-type LOCAL udp dpt:isakmp to:10.42.241.7:500
DNAT tcp -- anywhere anywhere ADDRTYPE match dst-type LOCAL tcp dpt:http-alt to:10.42.75.19:8080

Chain DOCKER (2 references)
target prot opt source destination
DNAT udp -- anywhere anywhere udp dpt:ipsec-nat-t to:172.17.0.4:4500
DNAT udp -- anywhere anywhere udp dpt:isakmp to:172.17.0.4:500
```

对 iptables 规则本身不是特别了解,所以只能转包看看网络包是如何被转发的。构建一个两个虚机的测试环境,创建容器之后,让其中一个容器 nc 监听一个端口,然后在另外一台虚机的容器中 nc 连接这个端口。在 Docker 自身的网络模型中,这种方式是不能 work 的,因为在一个中随意 open 一个端口,由于没有在```docker run```中设置```export```端口,所以外界是无法访问的。但是 Rancher 的这种网络结构却可以实现这一点。在虚机一上```tcpdump -i eth0 host 45.55.29.83```,在虚机一的容器中```tcpdump -i eth0```,这是可以发现实际上数据包是通过 IPsec 封装传输到目标容器的,猜测实际网络传输是 UDP 协议封装的:

![]( )

```
tcpdump -i eth0 host 45.55.29.83
12:10:44.663797 IP 192.241.212.19.1024 > 45.55.29.83.ipsec-nat-t: isakmp-nat-keep-alive
12:10:47.229600 IP 45.55.29.83.ipsec-nat-t > 192.241.212.19.ipsec-nat-t: UDP-encap: ESP(spi=0x09b0aceb,seq=0x2f), length 100
12:10:47.230028 IP 192.241.212.19.1024 > 45.55.29.83.ipsec-nat-t: UDP-encap: ESP(spi=0x0b788656,seq=0x10), length 100
12:10:47.230495 IP 45.55.29.83.ipsec-nat-t > 192.241.212.19.ipsec-nat-t: UDP-encap: ESP(spi=0x09b0aceb,seq=0x30), length 100
12:10:47.230579 IP 45.55.29.83.ipsec-nat-t > 192.241.212.19.ipsec-nat-t: UDP-encap: ESP(spi=0x09b0aceb,seq=0x31), length 100
12:10:47.230602 IP 45.55.29.83.ipsec-nat-t > 192.241.212.19.ipsec-nat-t: UDP-encap: ESP(spi=0x09b0aceb,seq=0x32), length 100
12:10:47.230665 IP 192.241.212.19.1024 > 45.55.29.83.ipsec-nat-t: UDP-encap: ESP(spi=0x0b788656,seq=0x11), length 100
12:10:47.231275 IP 192.241.212.19.1024 > 45.55.29.83.ipsec-nat-t: UDP-encap: ESP(spi=0x0b788656,seq=0x12), length 100
12:10:47.231511 IP 45.55.29.83.ipsec-nat-t > 192.241.212.19.ipsec-nat-t: UDP-encap: ESP(spi=0x09b0aceb,seq=0x33), length 100
12:10:47.511757 IP 45.55.29.83.ipsec-nat-t > 192.241.212.19.ipsec-nat-t: isakmp-nat-keep-alive
```

```
tcpdump -i eth0
16:10:47.229876 IP 10.42.116.141.36379 > 10.42.75.19.1234: Flags [S], seq 867849729, win 29200, options [mss 1460,sackOK,TS val 289931 ecr 0,nop,wscale 8], length 0
16:10:47.229941 IP 10.42.75.19.1234 > 10.42.116.141.36379: Flags [S.], seq 4135689986, ack 867849730, win 28960, options [mss 1460,sackOK,TS val 10776863 ecr 289931,nop,wscale 8], length 0
16:10:47.230535 IP 10.42.116.141.36379 > 10.42.75.19.1234: Flags [.], ack 1, win 115, options [nop,nop,TS val 289932 ecr 10776863], length 0
16:10:47.230623 IP 10.42.116.141.36379 > 10.42.75.19.1234: Flags [P.], seq 1:7, ack 1, win 115, options [nop,nop,TS val 289932 ecr 10776863], length 6
16:10:47.230638 IP 10.42.75.19.1234 > 10.42.116.141.36379: Flags [.], ack 7, win 114, options [nop,nop,TS val 10776863 ecr 289932], length 0
16:10:47.230642 IP 10.42.116.141.36379 > 10.42.75.19.1234: Flags [F.], seq 7, ack 1, win 115, options [nop,nop,TS val 289932 ecr 10776863], length 0
16:10:47.231246 IP 10.42.75.19.1234 > 10.42.116.141.36379: Flags [F.], seq 1, ack 8, win 114, options [nop,nop,TS val 10776863 ecr 289932], length 0
16:10:47.231546 IP 10.42.116.141.36379 > 10.42.75.19.1234: Flags [.], ack 2, win 115, options [nop,nop,TS val 289932 ecr 10776863], length 0
```

不得不说,这个特性非常赞!很多场景都迎刃而解,比如像测试环境快速构建就有这种需求。开发工程师完成编码之后进行快速验证,申请一个测试环境容器(假设是一个 Ubuntu ),第一步就是把代码包上传,然后改 BUG 后再上传,比较土的做法就是 nc 。但是由于 Docker 的网络限制,这种需求不太好支持。但是 Rancher 实现的这种虚拟网络能够很好的解决这类诉求。

### 健康检查 && 调度策略

Rancher 还自带了一个```Health Check```功能,然而貌似并没有看到实际作用。另外比较担心如果管理规模比较大的容器集群时,健康检查所带来的主节点的负载压力会不会较大,进而影响正常功能使用?

![]( )

Rancher 的调度策略也比较有意思,最开始以为也是什么 CPU 、 Memory 到了多少扩容多少个容器之类的策略。实际一看发现 Rancher 还是比较务实的,只是实现了类似 Swarm 的 Affinity 的调度功能,通过这个功能可以完成一些有状态容器的编排,还是比较实用的,至少比根据 CPU 扩容靠谱多了。

![]( )

当然 Rancher 还有很多有意思的特性,例如整合了 docker compose (话说貌似 docker compose 就合到 Rancher 了),可以实现一些固定场景部署的自动化操作;还有 Stack 的概念,支持拓扑展示;这里就不一一介绍了,有兴趣的同学可以尝试一下。

![]( http://rancher.com/wp-content/uploads/2015/06/service_discovery.png)

## 服务发现 && 负载均衡

提到容器编排肯定是少不了服务发现和负载均衡的, Rancher 的服务发现是基于 DNS 实现的,具体实现是在```/var/lib/cattle/bin/rancher-dns```,比较有意思的是所有容器的 DNS 服务器都指向了 169.254.169.250 ,然后又通过路由转发,实际是转给了此容器对应的 rancher/agent-instance:v0.4.1 容器中的 DNS 服务器。

```
nameserver 169.254.169.250

169.254.169.250 dev eth0 scope link src 10.42.84.2
```

至于为什么这么蹩脚的实现,目前能想到的合理解释就是为了实现只有 link 的服务才能解析到域名。例如上图中 web 容器 link 了 memcached 容器,这是在 web 容器中可以解析到 memcached 域名,而其他容器无法解析(虽然 ip 还是能够 ping 的通)。

Rancher 的负载均衡是通过 HAProxy 实现的,也比较简单,可以在界面上直接创建一个负载均衡。而且负载均衡可以动态修改,容器创建也可以自动绑定到负载均衡器。这样就可以实现双向互动,比较方便。

![]( http://rancher.com/wp-content/uploads/2015/06/load_balancer.png)

## 附录

### Mysql 表结构定义

```
+-----------------------------------------------+
| Tables_in_cattle |
+-----------------------------------------------+
| DATABASECHANGELOG |
| DATABASECHANGELOGLOCK |
| account |
| agent |
| agent_group |
| auth_token |
| certificate |
| cluster_host_map |
| config_item |
| config_item_status |
| container_event |
| credential |
| credential_instance_map |
| data |
| environment |
| external_handler |
| external_handler_external_handler_process_map |
| external_handler_process |
| generic_object |
| global_load_balancer |
| healthcheck_instance |
| healthcheck_instance_host_map |
| host |
| host_ip_address_map |
| host_label_map |
| host_vnet_map |
| image |
| image_storage_pool_map |
| instance |
| instance_host_map |
| instance_label_map |
| instance_link |
| ip_address |
| ip_address_nic_map |
| ip_association |
| ip_pool |
| label |
| load_balancer |
| load_balancer_certificate_map |
| load_balancer_config |
| load_balancer_config_listener_map |
| load_balancer_host_map |
| load_balancer_listener |
| load_balancer_target |
| mount |
| network |
| network_service |
| network_service_provider |
| network_service_provider_instance_map |
| nic |
| offering |
| physical_host |
| port |
| process_execution |
| process_instance |
| project_member |
| resource_pool |
| service |
| service_consume_map |
| service_event |
| service_expose_map |
| setting |
| snapshot |
| snapshot_storage_pool_map |
| storage_pool |
| storage_pool_host_map |
| subnet |
| subnet_vnet_map |
| task |
| task_instance |
| user_preference |
| vnet |
| volume |
| volume_storage_pool_map |
| zone |
+-----------------------------------------------+
```
6350 次点击
所在节点    云计算
5 条回复
carmark
2015-10-14 15:10:54 +08:00
搞了 kubernetes+mesos 的事情?
feiyang21687
2015-10-14 16:08:05 +08:00
@carmark 简版的 Kubernetes ,动态调度能力比较简单,但是对于大部分中小型规模应用场景足够了
cppgohan
2015-10-17 11:10:57 +08:00
因为有 UI, 看起来好像比 coreos 要好上手一点.

Rancher 最少 1G 内存是系统要求里说的吗? 如果直接用 RancherOS 也要至少 1G 内存了?
feiyang21687
2015-10-17 23:06:46 +08:00
@cppgohan 官方文档还真说了,最开始没注意,以为没必要用这么大内存呢。
wwek
2016-09-07 15:57:02 +08:00
挖坟
Rancher 很好用,不过国内用的人不多呀

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/227867

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX