打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Docker编排工具调研:Rancher – 陈飞 – 码农

Rancher提供了一套完整的Docker编排解决方案(重点是开源的)。功能上包括网络,存储,负载均衡,安全,服务发现和资源管理等。Rancher可以管理DigitalOceanAWSOpenStack等云主机,自动创建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 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 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实现的,也比较简单,可以在界面上直接创建一个负载均衡。而且负载均衡可以动态修改,容器创建也可以自动绑定到负载均衡器。这样就可以实现双向互动,比较方便。

附录

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                                          |+-----------------------------------------------+
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Rancher的快速使用及问题分析(二)之网络驱动分析与问题解决
无标题
详解如何解决docker容器无法通过IP访问宿主机问题
Rancher概览和server安装
企业级容器管理平台Rancher
如何在 Docker 容器之间设置网络
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服