Skip to content

Commit

Permalink
Merge pull request #426 from rileyje/add-alias-option
Browse files Browse the repository at this point in the history
Add option to allow Fabio to register frontend services in Consul on behalf of user services
  • Loading branch information
magiconair committed Feb 7, 2018
2 parents 4b4ad2f + 9ece33a commit cb5de44
Show file tree
Hide file tree
Showing 12 changed files with 247 additions and 57 deletions.
31 changes: 16 additions & 15 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,19 +126,20 @@ type File struct {
}

type Consul struct {
Addr string
Scheme string
Token string
KVPath string
NoRouteHTMLPath string
TagPrefix string
Register bool
ServiceAddr string
ServiceName string
ServiceTags []string
ServiceStatus []string
CheckInterval time.Duration
CheckTimeout time.Duration
CheckScheme string
CheckTLSSkipVerify bool
Addr string
Scheme string
Token string
KVPath string
NoRouteHTMLPath string
TagPrefix string
Register bool
ServiceAddr string
ServiceName string
ServiceTags []string
ServiceStatus []string
CheckInterval time.Duration
CheckTimeout time.Duration
CheckScheme string
CheckTLSSkipVerify bool
CheckDeregisterCriticalServiceAfter string
}
25 changes: 13 additions & 12 deletions config/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,19 @@ var defaultConfig = &Config{
Registry: Registry{
Backend: "consul",
Consul: Consul{
Addr: "localhost:8500",
Scheme: "http",
KVPath: "/fabio/config",
NoRouteHTMLPath: "/fabio/noroute.html",
TagPrefix: "urlprefix-",
Register: true,
ServiceAddr: ":9998",
ServiceName: "fabio",
ServiceStatus: []string{"passing"},
CheckInterval: time.Second,
CheckTimeout: 3 * time.Second,
CheckScheme: "http",
Addr: "localhost:8500",
Scheme: "http",
KVPath: "/fabio/config",
NoRouteHTMLPath: "/fabio/noroute.html",
TagPrefix: "urlprefix-",
Register: true,
ServiceAddr: ":9998",
ServiceName: "fabio",
ServiceStatus: []string{"passing"},
CheckInterval: time.Second,
CheckTimeout: 3 * time.Second,
CheckScheme: "http",
CheckDeregisterCriticalServiceAfter: "90m",
},
Timeout: 10 * time.Second,
Retry: 500 * time.Millisecond,
Expand Down
3 changes: 2 additions & 1 deletion config/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,8 @@ func load(cmdline, environ, envprefix []string, props *properties.Properties) (c
f.StringSliceVar(&cfg.Registry.Consul.ServiceStatus, "registry.consul.service.status", defaultConfig.Registry.Consul.ServiceStatus, "valid service status values")
f.DurationVar(&cfg.Registry.Consul.CheckInterval, "registry.consul.register.checkInterval", defaultConfig.Registry.Consul.CheckInterval, "service check interval")
f.DurationVar(&cfg.Registry.Consul.CheckTimeout, "registry.consul.register.checkTimeout", defaultConfig.Registry.Consul.CheckTimeout, "service check timeout")
f.BoolVar(&cfg.Registry.Consul.CheckTLSSkipVerify, "registry.consul.register.checkTLSSkipVerify", defaultConfig.Registry.Consul.CheckTLSSkipVerify, "service check TLS verifcation")
f.BoolVar(&cfg.Registry.Consul.CheckTLSSkipVerify, "registry.consul.register.checkTLSSkipVerify", defaultConfig.Registry.Consul.CheckTLSSkipVerify, "service check TLS verification")
f.StringVar(&cfg.Registry.Consul.CheckDeregisterCriticalServiceAfter, "registry.consul.register.checkDeregisterCriticalServiceAfter", defaultConfig.Registry.Consul.CheckDeregisterCriticalServiceAfter, "critical service deregistration timeout")
f.IntVar(&cfg.Runtime.GOGC, "runtime.gogc", defaultConfig.Runtime.GOGC, "sets runtime.GOGC")
f.IntVar(&cfg.Runtime.GOMAXPROCS, "runtime.gomaxprocs", defaultConfig.Runtime.GOMAXPROCS, "sets runtime.GOMAXPROCS")
f.StringVar(&cfg.UI.Access, "ui.access", defaultConfig.UI.Access, "access mode, one of [ro, rw]")
Expand Down
1 change: 1 addition & 0 deletions docs/content/cfg/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Option | Description
`proto=https` | Upstream service is HTTPS
`tlsskipverify=true` | Disable TLS cert validation for HTTPS upstream
`host=name` | Set the `Host` header to `name`. If `name == 'dst'` then the `Host` header will be set to the registered upstream host name
`register=name` | Register fabio as new service `name`. Useful for registering hostnames for host specific routes.

##### Example

Expand Down
13 changes: 13 additions & 0 deletions fabio.properties
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,19 @@
# registry.consul.register.checkTLSSkipVerify = false


# registry.consul.register.checkDeregisterCriticalServiceAfter configures
# automatic deregistration of a service after the health check is critical for
# this length of time.
#
# Fabio registers an http health check on http(s)://${ui.addr}/health
# and this value tells consul to deregister the associated service if the check
# is critical for the specified duration.
#
# The default is
#
# registry.consul.register.checkDeregisterCriticalServiceAfter = 90m


# metrics.target configures the backend the metrics values are
# sent to.
#
Expand Down
10 changes: 8 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func main() {
if registry.Default == nil {
return
}
registry.Default.Deregister()
registry.Default.DeregisterAll()
})

// init metrics early since that create the global metric registries
Expand Down Expand Up @@ -362,7 +362,7 @@ func initBackend(cfg *config.Config) {
}

if err == nil {
if err = registry.Default.Register(); err == nil {
if err = registry.Default.Register(nil); err == nil {
return
}
}
Expand Down Expand Up @@ -404,6 +404,12 @@ func watchBackend(cfg *config.Config, first chan bool) {
continue
}

aliases, err := route.ParseAliases(next)
if err != nil {
log.Printf("[WARN]: %s", err)
}
registry.Default.Register(aliases)

t, err := route.NewTable(next)
if err != nil {
log.Printf("[WARN] %s", err)
Expand Down
9 changes: 6 additions & 3 deletions registry/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package registry

type Backend interface {
// Register registers fabio as a service in the registry.
Register() error
Register(services []string) error

// Deregister removes the service registration for fabio.
Deregister() error
// Deregister removes all service registrations for fabio.
DeregisterAll() error

// Deregister removes the given service registration for fabio.
Deregister(service string) error

// ManualPaths returns the list of paths for which there
// are overrides.
Expand Down
76 changes: 63 additions & 13 deletions registry/consul/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type be struct {
c *api.Client
dc string
cfg *config.Consul
dereg chan bool
dereg map[string](chan bool)
}

func NewBackend(cfg *config.Consul) (registry.Backend, error) {
Expand All @@ -36,25 +36,66 @@ func NewBackend(cfg *config.Consul) (registry.Backend, error) {
return &be{c: c, dc: dc, cfg: cfg}, nil
}

func (b *be) Register() error {
if !b.cfg.Register {
log.Printf("[INFO] consul: Not registering fabio in consul")
return nil
func (b *be) Register(services []string) error {
if b.dereg == nil {
b.dereg = make(map[string](chan bool))
}

service, err := serviceRegistration(b.cfg)
if err != nil {
return err
if b.cfg.Register {
services = append(services, b.cfg.ServiceName)
}

// deregister unneeded services
for service := range b.dereg {
if stringInSlice(service, services) {
continue
}
err := b.Deregister(service)
if err != nil {
return err
}
}

// register new services
for _, service := range services {
if b.dereg[service] != nil {
log.Printf("[DEBUG] %q already registered", service)
continue
}

serviceReg, err := serviceRegistration(b.cfg, service)
if err != nil {
return err
}

b.dereg[service] = register(b.c, serviceReg)
}

b.dereg = register(b.c, service)
return nil
}

func (b *be) Deregister() error {
if b.dereg != nil {
b.dereg <- true // trigger deregistration
<-b.dereg // wait for completion
func (b *be) Deregister(service string) error {
dereg := b.dereg[service]
if dereg == nil {
log.Printf("[WARN]: Attempted to deregister unknown service %q", service)
return nil
}
dereg <- true // trigger deregistration
<-dereg // wait for completion
delete(b.dereg, service)

return nil
}

func (b *be) DeregisterAll() error {
log.Printf("[DEBUG]: consul: Deregistering all registered aliases.")
for name, dereg := range b.dereg {
if dereg == nil {
continue
}
log.Printf("[INFO] consul: Deregistering %q", name)
dereg <- true // trigger deregistration
<-dereg // wait for completion
}
return nil
}
Expand Down Expand Up @@ -122,3 +163,12 @@ func datacenter(c *api.Client) (string, error) {
}
return dc, nil
}

func stringInSlice(str string, strSlice []string) bool {
for _, s := range strSlice {
if s == str {
return true
}
}
return false
}
20 changes: 11 additions & 9 deletions registry/consul/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@ func register(c *api.Client, service *api.AgentServiceRegistration) chan bool {

register := func() string {
if err := c.Agent().ServiceRegister(service); err != nil {
log.Printf("[ERROR] consul: Cannot register fabio in consul. %s", err)
log.Printf("[ERROR] consul: Cannot register fabio [name:%q] in Consul. %s", service.Name, err)
return ""
}

log.Printf("[INFO] consul: Registered fabio as %q", service.Name)
log.Printf("[INFO] consul: Registered fabio with id %q", service.ID)
log.Printf("[INFO] consul: Registered fabio with address %q", service.Address)
log.Printf("[INFO] consul: Registered fabio with tags %q", strings.Join(service.Tags, ","))
Expand All @@ -51,7 +52,7 @@ func register(c *api.Client, service *api.AgentServiceRegistration) chan bool {
}

deregister := func(serviceID string) {
log.Printf("[INFO] consul: Deregistering fabio")
log.Printf("[INFO] consul: Deregistering %s", service.Name)
c.Agent().ServiceDeregister(serviceID)
}

Expand All @@ -76,7 +77,7 @@ func register(c *api.Client, service *api.AgentServiceRegistration) chan bool {
return dereg
}

func serviceRegistration(cfg *config.Consul) (*api.AgentServiceRegistration, error) {
func serviceRegistration(cfg *config.Consul, serviceName string) (*api.AgentServiceRegistration, error) {
hostname, err := os.Hostname()
if err != nil {
return nil, err
Expand All @@ -101,7 +102,7 @@ func serviceRegistration(cfg *config.Consul) (*api.AgentServiceRegistration, err
}
}

serviceID := fmt.Sprintf("%s-%s-%d", cfg.ServiceName, hostname, port)
serviceID := fmt.Sprintf("%s-%s-%d", serviceName, hostname, port)

checkURL := fmt.Sprintf("%s://%s:%d/health", cfg.CheckScheme, ip, port)
if ip.To16() != nil {
Expand All @@ -110,15 +111,16 @@ func serviceRegistration(cfg *config.Consul) (*api.AgentServiceRegistration, err

service := &api.AgentServiceRegistration{
ID: serviceID,
Name: cfg.ServiceName,
Name: serviceName,
Address: ip.String(),
Port: port,
Tags: cfg.ServiceTags,
Check: &api.AgentServiceCheck{
HTTP: checkURL,
Interval: cfg.CheckInterval.String(),
Timeout: cfg.CheckTimeout.String(),
TLSSkipVerify: cfg.CheckTLSSkipVerify,
HTTP: checkURL,
Interval: cfg.CheckInterval.String(),
Timeout: cfg.CheckTimeout.String(),
TLSSkipVerify: cfg.CheckTLSSkipVerify,
DeregisterCriticalServiceAfter: cfg.CheckDeregisterCriticalServiceAfter,
},
}

Expand Down
8 changes: 6 additions & 2 deletions registry/static/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@ func NewBackend(cfg *config.Static) (registry.Backend, error) {
return &be{cfg}, nil
}

func (b *be) Register() error {
func (b *be) Register(services []string) error {
return nil
}

func (b *be) Deregister() error {
func (b *be) Deregister(serviceName string) error {
return nil
}

func (b *be) DeregisterAll() error {
return nil
}

Expand Down
39 changes: 39 additions & 0 deletions route/parse_new.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ route add <svc> <src> <dst>[ weight <w>][ tags "<t1>,<t2>,..."][ opts "k1=v1 k2=
proto=tcp : upstream service is TCP, dst is ':port'
proto=https : upstream service is HTTPS
tlsskipverify=true : disable TLS cert validation for HTTPS upstream
host=name : set the Host header to 'name'. If 'name == "dst"' then the 'Host' header will be set to the registered upstream host name
register=name : register fabio as new service 'name'. Useful for registering hostnames for host specific routes.
route del <svc>[ <src>[ <dst>]]
- Remove route matching svc, src and/or dst
Expand Down Expand Up @@ -87,6 +89,43 @@ func Parse(in string) (defs []*RouteDef, err error) {
return defs, nil
}

// ParseAliases scans a set of route commands for the "register" option and
// returns a list of services which should be registered by the backend.
func ParseAliases(in string) (names []string, err error) {
var defs []*RouteDef
var def *RouteDef
for i, s := range strings.Split(in, "\n") {
def, err = nil, nil
s = strings.TrimSpace(s)
switch {
case reComment.MatchString(s) || reBlankLine.MatchString(s):
continue
case reRouteAdd.MatchString(s):
def, err = parseRouteAdd(s)
case reRouteDel.MatchString(s):
def, err = parseRouteDel(s)
case reRouteWeight.MatchString(s):
def, err = parseRouteWeight(s)
default:
err = errors.New("syntax error: 'route' expected")
}
if err != nil {
return nil, fmt.Errorf("line %d: %s", i+1, err)
}
defs = append(defs, def)
}

var aliases []string

for _, d := range defs {
registerName, ok := d.Opts["register"]
if ok {
aliases = append(aliases, registerName)
}
}
return aliases, nil
}

// route add <svc> <src> <dst>[ weight <w>][ tags "<t1>,<t2>,..."][ opts "k=v k=v ..."]
// 1: service 2: src 3: dst 4: weight expr 5: weight val 6: tags expr 7: tags val 8: opts expr 9: opts val
var reAdd = mustCompileWithFlexibleSpace(`^route add (\S+) (\S+) (\S+)( weight (\S+))?( tags "([^"]*)")?( opts "([^"]*)")?$`)
Expand Down
Loading

0 comments on commit cb5de44

Please sign in to comment.