fabiolb / fabio

Consul Load-Balancing made simple
https://fabiolb.net
MIT License
7.27k stars 616 forks source link

Support additional backends #12

Closed ghost closed 8 years ago

ghost commented 9 years ago

It would be pretty great if this had a backend that could read from Apache Mesos (or alternatively, Marathon) in addition to consul. There's a great project in the space called Traefik which will do HTTP in a very similar fashion but the only solutions I've seen for straight TCP have revolved around scripted HAProxy which is pretty ugly.

magiconair commented 9 years ago

Additional backends is something I'm considering but I want to do some other things first.

ghost commented 9 years ago

If you need any testing done let me know!

magiconair commented 9 years ago

I'm going to take a first step in this direction by refactoring the code to hide the consul specific logic behind an interface. This will make supporting additional backends simpler.

However, my main concern is whether these systems (e.g. Marathon) allow services to publish their routes in the registry (e.g. zookeeper or etcd) in an atomic way. The purpose of fabio is to be stateless and build the routing table automatically and not just to use a different storage mechanism.

magiconair commented 8 years ago

Support for Google Compute Platform is in the works by @emicklei

composer22 commented 8 years ago

I'd request one for the docker/swarm API. This would eliminate consul and registrator background containers completely.

Docker API allows you to query networks (incl overlay network) for running containers. You can use this to extract running services and IP addresses. Another interesting resource, with overlay networks and custom bridge networks in Docker, you get a /etc/host file in every running container which points to other running containers in the network(s). If a container is stopped or destroyed, it is pulled from the host file by docker automatically. This could also be used to wire up services.

Containers can be run in multiple custom networks, hence one Fabio instance could be a service point for consolidating needs.

The metadata that is attached to a container which propogates via registrator to Consul could still be used to create services in Fabio with no change IMHO. Hence current config patterns should not be affected except they would need to be started or join a network the Fabio instance is sharing.

magiconair commented 8 years ago

@composer22 How does fabio get notified of changes in the swarm setup? Polling? Other than that it looks interesting.

Kosta-Github commented 8 years ago

You can hook into the docker events via this HTTP endpoint: https://docs.docker.com/engine/reference/api/docker_remote_api_v1.21/#monitor-docker-s-events

It looks like the anchor is not positioning the page correctly, so you probably have the search for "monitor docker's events"...

composer22 commented 8 years ago

@magiconair @Kosta-Github

Yes, monitoring events √

Loot at the package:

https://github.com/fsouza/go-dockerclient

and I think this particularly:

https://godoc.org/github.com/fsouza/go-dockerclient#Client.AddEventListener

I'm performing some EXP work on the API usage. Not sure how it would evolve with an enhancement to Fabio.

Here are some thoughts.

magiconair commented 8 years ago

Fabio performing health checks does not sound like a direction I want to go. Fabio is only a client of a service registry. Something else needs to maintain the state.

composer22 commented 8 years ago

You're performing this now indirectly.

A status or heartbeat check isn't state. It's not maintaining state for the service. It's checking status from the service to determine it's state. You're currently querying through Consul for it which is the same thing. What's the diff except the loss of a middle man?

I don't know any router/lb out there that doesn't perform some status check.

Fabio =status check=> Consul =status check => Service

vs

Fabio =status check=> Service

You only need an object representing the service in Fabio for a Heath Check which you need now anyway. Moreover, the config information doesn't need to be kept after the initial query, since you've already performed your own config of a route on the initial discovery.

https://github.com/eBay/fabio/blob/master/registry/consul/service.go

config = append(config, fmt.Sprintf("route add %s %s%s http://%s:%d/ tags %q", name, host, path, addr, port, strings.Join(svc.ServiceTags, ",")))
magiconair commented 8 years ago

fabio does not perform a status check with consul. It literally waits until consul has determined that the state of the ensemble it manages has changed. Either through a change in health checks or by adding or removing services or instances. fabio only reacts to these changes in the registry and that is by intention.

How would fabio determine how to perform the health check? Remember that fabio is supposed to be zero-conf, i.e. it rebuilds its routing table from data that the services provide. A health check does not need to be on /. It doesn't need to be HTTP. HTTP 200 OK may not be sufficient. What about TCP services, e.g. MySQL? Do you want to add health checks for all these things into fabio?

Consul is more than a glorified nagios or zookeeper. It eliminates the backend load balancer by providing service discovery between the services and to deliver that you need health checks since otherwise you don't know which services are up.

This means that fabio can work with the state of the services without being involved in how the state is determined.

I'm not saying this isn't useful but I don't think it is as simple as you describe it.

composer22 commented 8 years ago

I understand the position you are taking. You want the backend to be a lightweight interface for an API for determining container state and route status. In this case, I assume you're spinning off a go routine that blocks until the state is modified:

func watchServices(client *api.Client, tagPrefix string, config chan string) {
    var lastIndex uint64
    for {
        ...
              checks, meta, err := client.Health().State("any", q)
        ..
        }
}

This might be replaced with the docker event check API call for state of the containers that are running. However docker doesn't know anything about health of each route provided by the container. To do this, either fabio would have to be enhanced to also perform health checks, or a facade would need to be written that runs in parallel and emulates the same information that Fabio needs from Consul. This would essentially spoof Consul's role:

Fabio <=> DockerMon == hc => Containers
                          ^ 
                          ||
                           <======> docker API ===> docker daemon

...and provide the same blocking function.

DockerMon basically is emulating a light subset of features from Consul and registrator. It's a question of where to put DockerMon features. I can see two competing ways to go here:

The first benefits eliminating a fat environment of services running at the cost of additional code. The second keeps things simple at the cost of still retaining a fatter environment of services. Fabio would still need to be modified to accept this registry instead of Consul.

Another option is to enhance Consul to monitor Docker API for service discovery instead of registrator. Currently Consul does have limited health checks against docker API

{
"check": {
    "id": "mem-util",
    "name": "Memory utilization",
    "docker_container_id": "f972c95ebf0e",
    "shell": "/bin/bash",
    "script": "/usr/local/bin/check_mem.py",
    "interval": "10s"
  }
}
magiconair commented 8 years ago

The backend registry is abstracted away behind an interface so adding health checks to fabio is not the problem. The problem is how does fabio know what to check for? The main idea behind fabio is to reverse the configuration management. Instead of maintaining an LB configuration in addition to your service configuration the services "tell" fabio which routes they accept and which instances are available to serve them. I don't know enough (read squat) about the Docker API at this moment to determine what can work for this setup but I'll have a look.

composer22 commented 8 years ago

When I get some time I'm going to try and create an app (Dockermon) that essentially looks like a Consul instance for Fabio. Therefore the same API calls will still work, yet the app will poll Docker API instead of using Consul/Registrator. It will be an interesting experience. Thanks

magiconair commented 8 years ago

I've spent some time on adding to additional backends for fabio: static and file which replace the special case for the proxy.routes parameter. They also serve as an example on how to add another registry backend. For example, making the file backend watch the config file and push a change would be a simple change. Adding a redis, mongodb, mysql backend would also be simple. Consider this a starting ground for more complex registry backends like the one for the Docker API. The work on this issue also triggered the fix for issue #43 to override all runtime vars from the config file via environment variables which should make the Docker use case simpler. Whoever is interested in additional backends please have a look at this change.

magiconair commented 8 years ago

Issue #46 describes an idea on how to externalize the API that fabio uses to talk to a registry. I'm not yet sold on the idea since I'm not a firm believer in the plugin extension model but it may be worth considering.

I will close this ticket since the latest change provides now three backends and an internal API on how to implement them. Also, the configuration is more explicit on how to select a backend.

Please create separate tickets for each backend you would like to see support for so that we can trace progress there. I'll open one for the Google compute platform from @emicklei.

pascalandy commented 7 years ago

Hey folks! Core user of Traefik here. Looking to try an alternative like fabio :) So far this is what i could understand from the docs (wiki) but I'm there yet

touch /etc/fabio/fabio.properties

#fabio.properties is empty at this point

docker run -d --name fabio \
-p 9999:9999 -p 9998:9998 \
-v /etc/fabio/fabio.properties:/etc/fabio/fabio.properties \
magiconair/fabio

docker run -d \
--name consul \
-p "8400:8400" -p "8500:8500" -p "8600:53/udp" \
consul \
consul agent -server -dev -client=0.0.0.0 -ui -bootstrap -log-level warn

docker run -d \
--name=registrator \
--net=host \
-v /var/run/docker.sock:/tmp/docker.sock \
gliderlabs/registrator:latest \
consul://localhost:8500

docker run -d \
--name nginxfoo \
--expose 80 \
-e SERVICE_TAGS=urlprefix-/foo \
nginx:alpine

docker run -d \
--name nginxbar \
--expose 80 \
-e SERVICE_TAGS=urlprefix-/bar \
nginx:alpine

If someone have a docker-compose or a docker stack I could try I'll be happy to test. I run Docker Swarm Mode. Cheers!