V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Recommended Services
Amazon Web Services
LeanCloud
New Relic
ClearDB
feiyang21687
V2EX  ›  云计算

Docker 编排工具调研: Rancher

  •  
  •   feiyang21687 · 2015-10-14 10:14:47 +08:00 · 6444 次点击
    这是一个创建于 3330 天前的主题,其中的信息可能已经有所发展或是发生改变。

    转载自:[feiyang21687.github.io/Rancher](feiyang21687.github.io/Rancher)

    Rancher 提供了一套完整的 Docker 编排解决方案(重点是开源的)。功能上包括网络,存储,负载均衡,安全,服务发现和资源管理等。 Rancher 可以管理[DigitalOcean](cloud.digitalocean.com)、[AWS](aws.amazone.com)、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                                          |
    +-----------------------------------------------+
    
    5 条回复    2016-09-07 15:57:02 +08:00
    carmark
        1
    carmark  
       2015-10-14 15:10:54 +08:00
    搞了 kubernetes+mesos 的事情?
    feiyang21687
        2
    feiyang21687  
    OP
       2015-10-14 16:08:05 +08:00
    @carmark 简版的 Kubernetes ,动态调度能力比较简单,但是对于大部分中小型规模应用场景足够了
    cppgohan
        3
    cppgohan  
       2015-10-17 11:10:57 +08:00
    因为有 UI, 看起来好像比 coreos 要好上手一点.

    Rancher 最少 1G 内存是系统要求里说的吗? 如果直接用 RancherOS 也要至少 1G 内存了?
    feiyang21687
        4
    feiyang21687  
    OP
       2015-10-17 23:06:46 +08:00
    @cppgohan 官方文档还真说了,最开始没注意,以为没必要用这么大内存呢。
    wwek
        5
    wwek  
       2016-09-07 15:57:02 +08:00
    挖坟
    Rancher 很好用,不过国内用的人不多呀
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5637 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 08:08 · PVG 16:08 · LAX 00:08 · JFK 03:08
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.