Rancher提供了一套完整的Docker编排解决方案(重点是开源的)。功能上包括网络,存储,负载均衡,安全,服务发现和资源管理等。Rancher可以管理DigitalOcean、AWS、OpenStack等云主机,自动创建Docker运行环境,实现跨云管理。使用上可以通过Web界面或者命令行方式进行操作。
下面介绍一下使用上的一些体会,并且分析一下一些有意思的特性的技术实现。先说下整体上的体验,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,Mysql,rancher-compose-executor,go-machine-service等几个服务,以及crontab等定时任务。其中值得一提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
虚机
从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。不过貌似不是特别稳定,窗口切换后会导致监控数据不再刷新。像下图这种情况:
容器
容器编排本身并没有什么特别的功能,基本是对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的概念,支持拓扑展示;这里就不一一介绍了,有兴趣的同学可以尝试一下。
服务发现 && 负载均衡
提到容器编排肯定是少不了服务发现和负载均衡的,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实现的,也比较简单,可以在界面上直接创建一个负载均衡。而且负载均衡可以动态修改,容器创建也可以自动绑定到负载均衡器。这样就可以实现双向互动,比较方便。
附录
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 |
+--------------------------
作者: 陈飞
原文: http://feiyang21687.github.io/Rancher/