运维 基于 docker 的高可用 Rails 集群方案探索

shawnyu · 2015年07月26日 · 最后由 zgm 回复于 2015年07月27日 · 8254 次阅读

前天看了一篇文章,名字叫做:基于 Docker 的 Rails 集群+Ruby 负载均衡代理。看完之后心里挺捉鸡的。刚好最近也遇到一些集群的管理问题,这边一起来聊聊基于 DOcker 的 Rails 集群。

0x0 问题

运维从来都是一个“不起眼”的大问题。运维工作还特别琐碎,一般要花去工程师大量的时间,同时还有突发性,对工程师机动性要求比较高。所以运维自动化一直在困扰暴走漫画团队。

虽说大部分运维工作都交给第三方云服务提供商了,但是平时还是有一些机器的维护之类的,繁琐的事情。

我做的最多的应该就是加减服务器了。暴漫的流量趋势非常规律,每年寒暑假是访问的高峰期,一般会比平时流量高出 2 倍。所以每年寒暑假之前都要添加足量的服务器以应对马上到来的流量高峰,而在寒暑假结束之后,要去掉一部分机器,以节省开支。刚开始完全是人肉运维,我一个人先去开 5 台机器,然后一个个上去改配置文件之类的,感觉好傻。后面就用了 ansible 作为自动化运维工具,这个要比 puppet 和 chef 轻量很多,对付我们这些简单的运维工作绰绰有余。于是现在就变成了开完机器,用 ansible 同步配置,代码,启动 app server,然后手动更新 nginx 配置。

使用 ansible 之后其实已经轻松很多了,但是还不够,我想象中的自动化集群,应该可以随意增加删除 node,node 不可用的时候自动删除,当然这些都不能影响服务的访问。

下面介绍一种自动化的 Rails 集群方案。

0x1 相关技术

0X2 整体架构

0x3 实践

由于不是科普帖,各种工具的详细信息就不自己介绍了。

consul & registrator

这两个工具是集群中每个节点的基础服务。我在一台机器上,所以把 consul cluster 给省掉了,只用了一个 consul 节点。下面是 docker-compose 配置。

consul:
   hostname: node1
   image: progrium/consul
   command: -server -bootstrap -advertise 172.17.42.1
   ports:
     - "8400:8400"
     - "8500:8500"
     - "8600:53/udp"
registrator:
   image: gliderlabs/registrator
   command: consul://consul:8500
   hostname: registrator1
   volumes:
     - /var/run/docker.sock:/tmp/docker.sock
   links:
     - consul

需要注意的是 consul 的启动参数里的 advertise。应该声明为 host 机器的 docker0 的 ip。如果不声明的话会默认使用容器 ip,这样 registrator 注册的设备 ip 都是不可访问的。

以上配置启动之后一套自动服务器发现功能就算完工了。

接下来,我们配置一个 web 应用测试一下。

web:
  image: nginx
  volumes:
    - ./sites-enabled:/etc/nginx/conf.d
  ports:
    - "80:80"
  links:
    - rails
rails:
  image: tutum/hello-world
  ports:
    - "80"

这个配置生成了 nginx 和 rails,并且挂载了本地的 sites-enabled 文件夹作为 nginx 的配置来源。注意 rails 的 ports 是随机的。

在 sites-enabled 中配置 nginx server 已 rails 作为 backend。

upstream backend {
}
server {
        server_name "10.0.2.15";
        location / {
                proxy_pass http://backend;
        }
}

对你并没有看错,upstream 并没有定义可用的 backend node。这个配置将会有 consul-template 根据服务列表自动填充。

让我们启动这个 web 应用,访问 80 端口是无法访问的 应为没有 backend node。

接下来需要安装 consule-template, 在 github 下载对应的 release。

然后创建一个配置文件 ct-nginx.conf

consul = "127.0.0.1:8500"
log_level = "warn"

template {
  source = "/home/vagrant/nginx.ctmpl"
  destination = "/home/vagrant/sites-enabled/backend.conf"
  command = "docker-compose -f /home/vagrant/docker-compose.yml restart web"
}

我有映射 consul 的端口,所以直接使用了 127.0.0.1。source 就是我们配置文件的模板。如下

upstream backend {
        {{range service "hello-world"}}
        server {{.Address}}:{{.Port}};{{end}}
}
server {
        server_name "10.0.2.15";
        location / {
                proxy_pass http://backend;
        }
}

consul-template 提供了方便的模板查询语句,可以直接从 consul 中查询并渲染出配置文件。这个模板中就找出了所有名字为“hello-world”的 service,并且循环输出 service 对应的 Address,也就是服务 ip,Port。

接着看 ct-nginx.conf,destination 代表模板生成完毕之后的位置,这里直接放在 nginx 的配置文件夹,最后 command 是生成配置后的动作,可以重启各种服务。我们这里在更新 nginx 之后重启生效。

ok,配置已经妥当,consul 和 registrator 也已经启动,让我们看看当前的服务列表。

$ curl localhost:8500/v1/catalog/service/hello-world?pretty
$ []

当前并没有注册名为 hello_world 的服务。

稍微提一下 registrator 注册服务的命名机制

可以通过环境变量来指定。如果不指定,比如我们上面的配置,默认会是镜像名。

接下来我们启动 consul-template

$ consul-template -config=./ct-nginx.conf

会直接根据模板生成一个 nginx 配置 虽然现在并没有 backend node。

$ cat /home/vagrant/sites-enabled/backend.conf
upstream backend {
}
server {
        server_name "10.0.2.15";
        location / {
                proxy_pass http://backend;
        }
}

然后启动我们的 web 应用。

启动之后,请求服务列表就可以看到 helle-world 服务,说明已经实现自动服务发现。

$ curl localhost:8500/v1/catalog/service/hello-world?pretty
[
    {
        "Node": "node1",
        "Address": "172.17.42.1",
        "ServiceID": "registrator1:vagrant_rails_1:80",
        "ServiceName": "hello-world",
        "ServiceTags": null,
        "ServiceAddress": "",
        "ServicePort": 32783
    }
]

浏览器中可以正常访问了,Yeah!

ok, 让我们更进一步,scale 我们的 rails node。

$ docker-compose scale rails=4

$ curl localhost:8500/v1/catalog/service/hello-world?pretty
[
    {
        "Node": "node1",
        "Address": "172.17.42.1",
        "ServiceID": "registrator1:vagrant_rails_1:80",
        "ServiceName": "hello-world",
        "ServiceTags": null,
        "ServiceAddress": "",
        "ServicePort": 32783
    },
    {
        "Node": "node1",
        "Address": "172.17.42.1",
        "ServiceID": "registrator1:vagrant_rails_2:80",
        "ServiceName": "hello-world",
        "ServiceTags": null,
        "ServiceAddress": "",
        "ServicePort": 32784
    },
    {
        "Node": "node1",
        "Address": "172.17.42.1",
        "ServiceID": "registrator1:vagrant_rails_3:80",
        "ServiceName": "hello-world",
        "ServiceTags": null,
        "ServiceAddress": "",
        "ServicePort": 32785
    },
    {
        "Node": "node1",
        "Address": "172.17.42.1",
        "ServiceID": "registrator1:vagrant_rails_4:80",
        "ServiceName": "hello-world",
        "ServiceTags": null,
        "ServiceAddress": "",
        "ServicePort": 32786
    }
]

此时 nginx 已经重启,可以看到 4 个 backend 都开始处理请求。

可以看到,rails 服务器只要启动之后就自动加入集群,如果一台节点挂掉之后,会自动从集群里去掉,基本上没有任何影响。

肯定有不足的地方,欢迎讨论,发出来就是为了学习。

原帖地址 http://dev.baozou.com/ye-tan-ji-yu-dockerde-railsji-qun/

为什么捉急?

好文,consul 必须三台以上的机器,保险起见可以在 service 上加 health check

赞楼主一个,有深度有实践的好文

#1 楼 @googya 因为根本不是那么玩的 哈哈

点赞,回去玩玩看

暴漫最近经常出好东西啊。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号