Consul + fabio realizes automatic service discovery and load balancing

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.

consul-ui.png


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.png


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

docker-consul.png


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

http://dockone.io/article/1567

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=327033431&siteId=291194637
Recommended