Summary
- Introducing Consul
- Introducing Fabio
- Features of Service Discovery
- working principle
- Demo
- Combined with Kubernetes expansion
Consul
Hashicorp team development is the famous team developing vagrant.
Consul is a service software that provides service discovery, health detection, and K/V storage to support distributed high availability and multiple data centers.
It is similar to ZooKeeper but has some more functions. For details, please refer to the difference between Consul and ZooKeeper .
fabio
fabio is a fast, simple and zero-configuration load balancing router developed by the ebay team with golang that enables applications deployed by consul to quickly support http(s).
Because consul supports service registration and health check, fabio can provide load with zero configuration, and upgrade deployment has never been easier.
According to the introduction of the project, fabio can provide 15,000 requests per second.
With these two components, it is very easy to do service discovery and automatic load balancing, "The artifact is in hand, I have it in the world!" ^ _ ^
Features of Service Discovery
The invocation between services needs to fill in the host and port in the configuration file, which is not easy to maintain, and it is not easy to deploy and expand in a distributed environment.
Then you need to consider registering the host, port and some other information to the registry when the service is started, so that other services can find it.
It is even simpler to "address" by means of DNS after registration. For example, Zookeeper can do this job well, but there is another drawback: how to ensure that the service will be available after the service's health check service is registered in the registry? At this point, you need to write your own logic and automatically log off from the registry when the service is unavailable. Then Consul can easily solve this problem.
working principle
Consul provides a set of health detection mechanism. Simply speaking, for http type services (consul also supports other types such as tcp), you can register health detection information by the way when registering, providing a health detection address (url) and a frequency In this case, the consul will periodically request to set the secondary service to a healthy state when the status code is 200, otherwise it is a failure state.
Since the service registered to consul can maintain its own health status, the work of fabio is very simple! It is to directly take out the healthy service from the consul registry and automatically create its own routing table according to the tags configuration when the service is registered, and then automatically perform load balancing when an http request comes. The
simple flow chart is as follows:
====== Service Registration ================== A service <------> consul cluster ----> healthy A/unhealthy A cluster ====== Health Check ========= ========= ^ | Add/remove routing table | ======== fabio cluster ======== | | A service If found, route successfully otherwise return error V http request
Demo
Here we start to write a demo service to experience a wave of consul + fabio and use docker + k8s to orchestrate the expansion.
Because both consul + fabio support the docker mode to run, the docker mode is used as an example here.
docker pull magiconair/fabio
docker pull consul
consul can be used in cluster development environment or in dev mode, please refer to here .
Here you can refer to the docker compose configuration of the single-machine deployment consul cluster I compiled:
version: '2' services: consul_server_1: image: "consul:latest" container_name: "consul_server_1" environment: CONSUL_LOCAL_CONFIG: '{"leave_on_terminate": true}' networks: app_net: ipv4_address: 172.17.0.3 command: "agent -server -bind=172.17.0.3 -client=172.17.0.3 -retry-join=172.17.0.2" consul_server_2: image: "consul:latest" container_name: "consul_server_2" ports: - "8600:8600" - "8500:8500" networks: app_net: ipv4_address: 172.17.0.4 command: "agent -server -bind=172.17.0.4 -client=172.17.0.4 -retry-join=172.17.0.3 -ui" consul_server_3: image: "consul:latest" container_name: "consul_server_3" environment: CONSUL_LOCAL_CONFIG: '{"leave_on_terminate": true}' networks: app_net: ipv4_address: 172.17.0.5 command: "agent -server -bind=172.17.0.5 -client=172.17.0.5 -retry-join=172.17.0.4 -bootstrap-expect=3" networks: app_net: driver: bridge ipam: config: - subnet: 172.17.0.0/24
You can also deploy it yourself according to the documentation on docker hub, and access the consul ui after the startup is successful.
Then deploy faibo
docker-compose.yml
fabio: image: "magiconair/fabio" ports: - "9998:9998" - "9999:9999" volumes: - ./fabio.properties:/etc/fabio/fabio.properties
Although fabio is said to be zero configuration, in some cases, you still need to configure some things individually. At this time, you can write a simple configuration fabio.properties
to specify the address port of consul and some of its own statistics, etc.
registry.consul.register.addr = 172.16.0.21:9998 registry.consul.addr = 172.16.0.21:8500 metrics.target = stdout
fabio can not only be combined with consul, but also can manually write some routing rules. The
syntax is as follows:
route add <svc> <src> <dst> weight <w> tags "<t1>,<t2>,..." - Add route for service svc from src to dst and assign weight and tags route add <svc> <src> <dst> weight <w> - Add route for service svc from src to dst and assign weight
Please move for details .
At this point, after the installation and deployment is complete, run a demo to try.
The official provides a simple implementation:
package main import ( "flag" "fmt" "log" "net" "net/http" "os" "os/signal" "path/filepath" "strconv" "strings" "github.com/magiconair/fabio-example/_third_party/github.com/hashicorp/consul/api" ) func main() { var addr, name, prefix string flag.StringVar(&addr, "addr", "127.0.0.1:5000", "host:port of the service") flag.StringVar(&name, "name", filepath.Base(os.Args[0]), "name of the service") flag.StringVar(&prefix, "prefix", "", "comma-sep list of host/path prefixes to register") flag.Parse() if prefix == "" { flag.Usage() os.Exit(1) } // register prefixes prefixes := strings.Split(prefix, ",") for _, p := range prefixes { http.HandleFunc(p, func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Serving %s from %s on %s\n", r.RequestURI, name, addr) }) } // start http server go func() { log.Printf("Listening on %s serving %s", addr, prefix) if err := http.ListenAndServe(addr, nil); err != nil { log.Fatal(err) } }() // register consul health check endpoint http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "OK") }) // build urlprefix-host/path tag list // e.g. urlprefix-/foo, urlprefix-/bar, ... var tags []string for _, p := range prefixes { tags = append(tags, "urlprefix-"+p) } // get host and port as string/int host, portstr, err: = net.SplitHostPort (addr) if err != nil { log.Fatal(err) } port, err := strconv.Atoi(portstr) if err != nil { log.Fatal(err) } // register service with health check serviceID := name + "-" + addr service := &api.AgentServiceRegistration{ ID: serviceID, Name: name, Port: port, Address: host, Tags: tags, Check: &api.AgentServiceCheck{ HTTP: "http://" + addr + "/health", Interval: "1s", Timeout: "1s", }, } client, err := api.NewClient(api.DefaultConfig()) if err != nil { log.Fatal(err) } if err := client.Agent().ServiceRegister(service); err != nil { log.Fatal(err) } log.Printf("Registered service %q in consul with tags %q", name, strings.Join(tags, ",")) // run until we get a signal quit := make(chan os.Signal, 1) signal.Notify(quit, os.Interrupt, os.Kill) <-quit // deregister service if err := client.Agent().ServiceDeregister(serviceID); err != nil { log.Fatal(err) } log.Printf("Deregistered service %q in consul", name) }
The program registers and registers the address /health of the health check when it starts, and cancels the registration when it exits. When registering a service with consul, there is a tag that allows to pass a label to the service. Fabio automatically associates the route according to this parameter. map.
Try running
CONSUL_HTTP_ADDR=172.16.0.21:8500 ./fabio-example -addr=172.16.0.17:9876 -prefix=a.com/
At this point consul has received the registered fabio route has been added.
# Service Host Path Dest Weight 1 fabio-example a.com / http://172.16.0.17:9876/ 100%
➜ ~ curl -iv -H 'Host: a.com' 172.16.0.21:9999/ * Trying 172.16.0.21... * Connected to 172.16.0.21 (172.16.0.21) port 9999 (#0) > GET / HTTP/1.1 > Host: a.com > User-Agent: curl/7.43.0 > Accept: */* > < HTTP/1.1 200 OK HTTP/1.1 200 OK < Content-Length: 49 Content-Length: 49 < Content-Type: text/plain; charset=utf-8 Content-Type: text/plain; charset=utf-8 < Date: Fri, 22 Jul 2016 01:01:28 GMT Date: Fri, 22 Jul 2016 01:01:28 GMT < Serving / from fabio-example on 172.16.0.17:9876 * Connection #0 to host 172.16.0.21 left intact
Combined with kubernetes expansion
If the arrangement is based on k8s, some modifications need to be made. For example: the ip port should be dynamically obtained from the container, and the tags prefix needs to be configured when the service is started. You can refer to fabio's documentation .
Here I use java to quickly build one. The Spring framework provides a one-stop service for service discovery. For consul, spring-cloud-consul can be used quickly with just one line of code and a few lines of configuration:)
Add the @EnableDiscoveryClient annotation to the Application class.
application.yml
spring: cloud: consul: discovery: healthCheckPath: ${management.contextPath}/health #Path to health check healthCheckInterval: 15s #Frequency of health checks tags: urlprefix-api.xxxx.com/ #fabio routing rules
ps: spring health detection can be quickly implemented with spring-boot-starter-actuator.
Then the k8s needs to be expanded and can be expanded arbitrarily, because if the k8s expansion does not use the service discovery method, the http request may be forwarded to the started container but the service is not available (for example, although the java process is started, it may take a few minutes to initialize ...).
Feel the expansion of 10
$ kubectl scale --replicas=10 rc api $ kubectl get pods [root@172-16-0-17 fabio-example]# kubectl get pods NAME READY STATUS RESTARTS AGE api-6xytx 1/1 Running 0 11s api-9e5838075aae036e2dc971984855e379-ac30s 1/1 Running 0 14h api-dfmtv 1/1 Running 0 11s api-eo01h 1/1 Running 0 11s api-hn1kv 1/1 Running 0 11s api-iyqmg 1/1 Running 0 11s api-k32ud 1/1 Running 0 11s api-q10a7 1/1 Running 0 11s api-re7e1 1/1 Running 0 11s api-tm2pk 1/1 Running 0 11s
Instantly expand to 10 in 10 seconds and then look at consul and fabio
fabio
# Service Host Path Dest Weight 1 api api.com / http://172.31.9.3:8080/ 10% 2 api api.com / http://172.31.9.2:8080/ 10% 3 api api.com / http://172.31.82.6:8080/ 10% 4 api api.com / http://172.31.82.4:8080/ 10% 5 api api.com / http://172.31.28.3:8080/ 10% 6 api api.com / http://172.31.28.2:8080/ 10% 7 api api.com / http://172.31.23.7:8080/ 10% 8 api api.com / http://172.31.23.2:8080/ 10% 9 api api.com / http://172.31.12.6:8080/ 10% 10 api api.com / http://172.31.12.4:8080/ 10%
At this time, fabio can also update the routing table immediately when it shrinks to one.
===
The combination of fabio+consul can easily deploy and expand in a distributed environment.
In addition, consul also provides all other components that can complete similar functions such as consul-template .
Reference link
- https://github.com/eBay/fabio/wiki/Configuration
- https://www.consul.io/docs/agent/basics.html
- http://cloud.spring.io/spring- ... .html
http://dockone.io/article/1567