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 registerruntime/cgo: pthread_create failed: Resource temporarily unavailableSIGABRT: abortPC=0x6f8177 m=0goroutine 0 [idle]:goroutine 1 [running]:runtime.systemstack_switch() /usr/local/go/src/runtime/asm_amd64.s:216 fp=0xc820024770 sp=0xc820024768runtime.main() /usr/local/go/src/runtime/proc.go:49 +0x62 fp=0xc8200247c0 sp=0xc820024770runtime.goexit() /usr/local/go/src/runtime/asm_amd64.s:1696 +0x1 fp=0xc8200247c8 sp=0xc8200247c0goroutine 17 [syscall, locked to thread]:runtime.goexit() /usr/local/go/src/runtime/asm_amd64.s:1696 +0x1rax 0x0rbx 0xc30488rcx 0x6f8177rdx 0x6rdi 0x8crsi 0x8crbp 0x734b32rsp 0x7ffe7ce05578r8 0xar9 0x27ae880r10 0x8r11 0x202r12 0x27b0bc0r13 0x8ed580r14 0x0r15 0x8rip 0x6f8177rflags 0x202cs 0x33fs 0x0gs 0x0runtime/cgo: pthread_create failed: Resource temporarily unavailableSIGABRT: abortPC=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配置也比较标准,除了添加了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网络,第二个是健康检查,第三个是调度策略。下面一个个来看:
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 routedefault via 192.241.212.1 dev eth010.42.0.0/16 dev docker0 proto kernel scope link src 10.42.0.1172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.42.1192.241.212.0/24 dev eth0 proto kernel scope link src 192.241.212.19root@rancher:~# ip addr4: 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 destinationCATTLE_PREROUTING all -- anywhere anywhereDOCKER all -- anywhere anywhere ADDRTYPE match dst-type LOCALChain INPUT (policy ACCEPT)target prot opt source destinationChain OUTPUT (policy ACCEPT)target prot opt source destinationDOCKER all -- anywhere !loopback/8 ADDRTYPE match dst-type LOCALChain POSTROUTING (policy ACCEPT)target prot opt source destinationCATTLE_POSTROUTING all -- anywhere anywhereMASQUERADE all -- 172.17.0.0/16 anywhereMASQUERADE udp -- 172.17.0.4 172.17.0.4 udp dpt:ipsec-nat-tMASQUERADE udp -- 172.17.0.4 172.17.0.4 udp dpt:isakmpChain CATTLE_POSTROUTING (1 references)target prot opt source destinationACCEPT all -- 10.42.0.0/16 169.254.169.250MASQUERADE tcp -- 10.42.0.0/16 !10.42.0.0/16 masq ports: 1024-65535MASQUERADE udp -- 10.42.0.0/16 !10.42.0.0/16 masq ports: 1024-65535MASQUERADE all -- 10.42.0.0/16 !10.42.0.0/16MASQUERADE tcp -- 172.17.0.0/16 anywhere masq ports: 1024-65535MASQUERADE udp -- 172.17.0.0/16 anywhere masq ports: 1024-65535Chain CATTLE_PREROUTING (1 references)target prot opt source destinationDNAT udp -- anywhere anywhere ADDRTYPE match dst-type LOCAL udp dpt:ipsec-nat-t to:10.42.241.7:4500DNAT udp -- anywhere anywhere ADDRTYPE match dst-type LOCAL udp dpt:isakmp to:10.42.241.7:500DNAT tcp -- anywhere anywhere ADDRTYPE match dst-type LOCAL tcp dpt:http-alt to:10.42.75.19:8080Chain DOCKER (2 references)target prot opt source destinationDNAT udp -- anywhere anywhere udp dpt:ipsec-nat-t to:172.17.0.4:4500DNAT 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.8312:10:44.663797 IP 192.241.212.19.1024 > 45.55.29.83.ipsec-nat-t: isakmp-nat-keep-alive12: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 10012:10:47.230028 IP 192.241.212.19.1024 > 45.55.29.83.ipsec-nat-t: UDP-encap: ESP(spi=0x0b788656,seq=0x10), length 10012: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 10012: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 10012: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 10012:10:47.230665 IP 192.241.212.19.1024 > 45.55.29.83.ipsec-nat-t: UDP-encap: ESP(spi=0x0b788656,seq=0x11), length 10012:10:47.231275 IP 192.241.212.19.1024 > 45.55.29.83.ipsec-nat-t: UDP-encap: ESP(spi=0x0b788656,seq=0x12), length 10012: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 10012:10:47.511757 IP 45.55.29.83.ipsec-nat-t > 192.241.212.19.ipsec-nat-t: isakmp-nat-keep-alive
tcpdump -i eth016: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 016: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 016: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 016: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 616: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 016: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 016: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 016: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.250169.254.169.250 dev eth0 scope link src 10.42.84.2
至于为什么这么蹩脚的实现,目前能想到的合理解释就是为了实现只有link的服务才能解析到域名。例如上图中web容器link了memcached容器,这是在web容器中可以解析到memcached域名,而其他容器无法解析(虽然ip还是能够ping的通)。
Rancher的负载均衡是通过HAProxy实现的,也比较简单,可以在界面上直接创建一个负载均衡。而且负载均衡可以动态修改,容器创建也可以自动绑定到负载均衡器。这样就可以实现双向互动,比较方便。
+-----------------------------------------------+| 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 |+-----------------------------------------------+
联系客服