Add support for tcp labels in docker provider

This commit is contained in:
Julien Salleyron 2019-03-21 15:22:06 +01:00 committed by Traefiker Bot
parent ec1952157b
commit 0f2c4fb5f4
6 changed files with 1310 additions and 566 deletions

View file

@ -228,6 +228,27 @@ You can declare pieces of middleware using labels starting with `traefik.http.mi
If you declare multiple middleware with the same name but with different parameters, the middleware fails to be declared. If you declare multiple middleware with the same name but with different parameters, the middleware fails to be declared.
### TCP
You can declare TCP Routers and/or Services using labels.
??? example "Declaring TCP Routers and Services"
```yaml
services:
my-container:
# ...
labels:
- traefik.tcp.routers.my-router.rule="HostSNI(`my-host.com`)"
- traefik.tcp.routers.my-router.rule.tls="true"
- traefik.tcp.services.my-service.loadbalancer.server.port="4123"
```
!!! warning "TCP and HTTP"
If you declare a TCP Router/Service, it will prevent Traefik from automatically create an HTTP Router/Service (like it does by default if no TCP Router/Service is defined).
You can declare both a TCP Router/Service and an HTTP Router/Service for the same container (but you have to do so manually).
### Specific Options ### Specific Options
#### traefik.enable #### traefik.enable

View file

@ -144,6 +144,43 @@ func (s *DockerSuite) TestDefaultDockerContainers(c *check.C) {
c.Assert(version["Version"], checker.Equals, "swarm/1.0.0") c.Assert(version["Version"], checker.Equals, "swarm/1.0.0")
} }
func (s *DockerSuite) TestDockerContainersWithTCPLabels(c *check.C) {
tempObjects := struct {
DockerHost string
DefaultRule string
}{
DockerHost: s.getDockerHost(),
DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)",
}
file := s.adaptFile(c, "fixtures/docker/simple.toml", tempObjects)
defer os.Remove(file)
// Start a container with some labels
labels := map[string]string{
"traefik.tcp.Routers.Super.Rule": "HostSNI(`my.super.host`)",
"traefik.tcp.Routers.Super.tls": "true",
"traefik.tcp.Services.Super.Loadbalancer.server.port": "8080",
}
s.startContainerWithLabels(c, "containous/whoamitcp", labels, "-name", "my.super.host")
// Start traefik
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`my.super.host`)"))
c.Assert(err, checker.IsNil)
who, err := guessWho("127.0.0.1:8000", "my.super.host", true)
c.Assert(err, checker.IsNil)
c.Assert(who, checker.Contains, "my.super.host")
}
func (s *DockerSuite) TestDockerContainersWithLabels(c *check.C) { func (s *DockerSuite) TestDockerContainersWithLabels(c *check.C) {
tempObjects := struct { tempObjects := struct {
DockerHost string DockerHost string

View file

@ -53,6 +53,23 @@ type TCPLoadBalancerService struct {
Method string `json:"method,omitempty" toml:",omitempty"` Method string `json:"method,omitempty" toml:",omitempty"`
} }
// Mergeable tells if the given service is mergeable.
func (l *TCPLoadBalancerService) Mergeable(loadBalancer *TCPLoadBalancerService) bool {
savedServers := l.Servers
defer func() {
l.Servers = savedServers
}()
l.Servers = nil
savedServersLB := loadBalancer.Servers
defer func() {
loadBalancer.Servers = savedServersLB
}()
loadBalancer.Servers = nil
return reflect.DeepEqual(l, loadBalancer)
}
// Mergeable tells if the given service is mergeable. // Mergeable tells if the given service is mergeable.
func (l *LoadBalancerService) Mergeable(loadBalancer *LoadBalancerService) bool { func (l *LoadBalancerService) Mergeable(loadBalancer *LoadBalancerService) bool {
savedServers := l.Servers savedServers := l.Servers
@ -70,6 +87,11 @@ func (l *LoadBalancerService) Mergeable(loadBalancer *LoadBalancerService) bool
return reflect.DeepEqual(l, loadBalancer) return reflect.DeepEqual(l, loadBalancer)
} }
// SetDefaults Default values for a LoadBalancerService.
func (l *TCPLoadBalancerService) SetDefaults() {
l.Method = "wrr"
}
// SetDefaults Default values for a LoadBalancerService. // SetDefaults Default values for a LoadBalancerService.
func (l *LoadBalancerService) SetDefaults() { func (l *LoadBalancerService) SetDefaults() {
l.PassHostHeader = true l.PassHostHeader = true
@ -97,9 +119,15 @@ type Server struct {
// TCPServer holds a TCP Server configuration // TCPServer holds a TCP Server configuration
type TCPServer struct { type TCPServer struct {
Address string `json:"address" label:"-"` Address string `json:"address" label:"-"`
Port string `toml:"-" json:"-"`
Weight int `json:"weight"` Weight int `json:"weight"`
} }
// SetDefaults Default values for a Server.
func (s *TCPServer) SetDefaults() {
s.Weight = 1
}
// SetDefaults Default values for a Server. // SetDefaults Default values for a Server.
func (s *Server) SetDefaults() { func (s *Server) SetDefaults() {
s.Weight = 1 s.Weight = 1

View file

@ -36,6 +36,12 @@ func Merge(ctx context.Context, configurations map[string]*config.Configuration)
routersToDelete := map[string]struct{}{} routersToDelete := map[string]struct{}{}
routers := map[string][]string{} routers := map[string][]string{}
servicesTCPToDelete := map[string]struct{}{}
servicesTCP := map[string][]string{}
routersTCPToDelete := map[string]struct{}{}
routersTCP := map[string][]string{}
middlewaresToDelete := map[string]struct{}{} middlewaresToDelete := map[string]struct{}{}
middlewares := map[string][]string{} middlewares := map[string][]string{}
@ -61,6 +67,20 @@ func Merge(ctx context.Context, configurations map[string]*config.Configuration)
} }
} }
for serviceName, service := range conf.TCP.Services {
servicesTCP[serviceName] = append(servicesTCP[serviceName], root)
if !AddServiceTCP(configuration.TCP, serviceName, service) {
servicesTCPToDelete[serviceName] = struct{}{}
}
}
for routerName, router := range conf.TCP.Routers {
routersTCP[routerName] = append(routersTCP[routerName], root)
if !AddRouterTCP(configuration.TCP, routerName, router) {
routersTCPToDelete[routerName] = struct{}{}
}
}
for middlewareName, middleware := range conf.HTTP.Middlewares { for middlewareName, middleware := range conf.HTTP.Middlewares {
middlewares[middlewareName] = append(middlewares[middlewareName], root) middlewares[middlewareName] = append(middlewares[middlewareName], root)
if !AddMiddleware(configuration.HTTP, middlewareName, middleware) { if !AddMiddleware(configuration.HTTP, middlewareName, middleware) {
@ -81,6 +101,18 @@ func Merge(ctx context.Context, configurations map[string]*config.Configuration)
delete(configuration.HTTP.Routers, routerName) delete(configuration.HTTP.Routers, routerName)
} }
for serviceName := range servicesTCPToDelete {
logger.WithField(log.ServiceName, serviceName).
Errorf("Service TCP defined multiple times with different configurations in %v", servicesTCP[serviceName])
delete(configuration.TCP.Services, serviceName)
}
for routerName := range routersTCPToDelete {
logger.WithField(log.RouterName, routerName).
Errorf("Router TCP defined multiple times with different configurations in %v", routersTCP[routerName])
delete(configuration.TCP.Routers, routerName)
}
for middlewareName := range middlewaresToDelete { for middlewareName := range middlewaresToDelete {
logger.WithField(log.MiddlewareName, middlewareName). logger.WithField(log.MiddlewareName, middlewareName).
Errorf("Middleware defined multiple times with different configurations in %v", middlewares[middlewareName]) Errorf("Middleware defined multiple times with different configurations in %v", middlewares[middlewareName])
@ -90,6 +122,31 @@ func Merge(ctx context.Context, configurations map[string]*config.Configuration)
return configuration return configuration
} }
// AddServiceTCP Adds a service to a configurations.
func AddServiceTCP(configuration *config.TCPConfiguration, serviceName string, service *config.TCPService) bool {
if _, ok := configuration.Services[serviceName]; !ok {
configuration.Services[serviceName] = service
return true
}
if !configuration.Services[serviceName].LoadBalancer.Mergeable(service.LoadBalancer) {
return false
}
configuration.Services[serviceName].LoadBalancer.Servers = append(configuration.Services[serviceName].LoadBalancer.Servers, service.LoadBalancer.Servers...)
return true
}
// AddRouterTCP Adds a router to a configurations.
func AddRouterTCP(configuration *config.TCPConfiguration, routerName string, router *config.TCPRouter) bool {
if _, ok := configuration.Routers[routerName]; !ok {
configuration.Routers[routerName] = router
return true
}
return reflect.DeepEqual(configuration.Routers[routerName], router)
}
// AddService Adds a service to a configurations. // AddService Adds a service to a configurations.
func AddService(configuration *config.HTTPConfiguration, serviceName string, service *config.Service) bool { func AddService(configuration *config.HTTPConfiguration, serviceName string, service *config.Service) bool {
if _, ok := configuration.Services[serviceName]; !ok { if _, ok := configuration.Services[serviceName]; !ok {
@ -137,10 +194,33 @@ func MakeDefaultRuleTemplate(defaultRule string, funcMap template.FuncMap) (*tem
return template.New("defaultRule").Funcs(defaultFuncMap).Parse(defaultRule) return template.New("defaultRule").Funcs(defaultFuncMap).Parse(defaultRule)
} }
// BuildTCPRouterConfiguration Builds a router configuration.
func BuildTCPRouterConfiguration(ctx context.Context, configuration *config.TCPConfiguration) {
for routerName, router := range configuration.Routers {
loggerRouter := log.FromContext(ctx).WithField(log.RouterName, routerName)
if len(router.Rule) == 0 {
delete(configuration.Routers, routerName)
loggerRouter.Errorf("Empty rule")
continue
}
if len(router.Service) == 0 {
if len(configuration.Services) > 1 {
delete(configuration.Routers, routerName)
loggerRouter.
Error("Could not define the service name for the router: too many services")
continue
}
for serviceName := range configuration.Services {
router.Service = serviceName
}
}
}
}
// BuildRouterConfiguration Builds a router configuration. // BuildRouterConfiguration Builds a router configuration.
func BuildRouterConfiguration(ctx context.Context, configuration *config.HTTPConfiguration, defaultRouterName string, defaultRuleTpl *template.Template, model interface{}) { func BuildRouterConfiguration(ctx context.Context, configuration *config.HTTPConfiguration, defaultRouterName string, defaultRuleTpl *template.Template, model interface{}) {
logger := log.FromContext(ctx)
if len(configuration.Routers) == 0 { if len(configuration.Routers) == 0 {
if len(configuration.Services) > 1 { if len(configuration.Services) > 1 {
log.FromContext(ctx).Info("Could not create a router for the container: too many services") log.FromContext(ctx).Info("Could not create a router for the container: too many services")
@ -151,7 +231,7 @@ func BuildRouterConfiguration(ctx context.Context, configuration *config.HTTPCon
} }
for routerName, router := range configuration.Routers { for routerName, router := range configuration.Routers {
loggerRouter := logger.WithField(log.RouterName, routerName) loggerRouter := log.FromContext(ctx).WithField(log.RouterName, routerName)
if len(router.Rule) == 0 { if len(router.Rule) == 0 {
writer := &bytes.Buffer{} writer := &bytes.Buffer{}
if err := defaultRuleTpl.Execute(writer, model); err != nil { if err := defaultRuleTpl.Execute(writer, model); err != nil {

View file

@ -33,6 +33,21 @@ func (p *Provider) buildConfiguration(ctx context.Context, containersInspected [
continue continue
} }
if len(confFromLabel.TCP.Routers) > 0 || len(confFromLabel.TCP.Services) > 0 {
err := p.buildTCPServiceConfiguration(ctxContainer, container, confFromLabel.TCP)
if err != nil {
logger.Error(err)
continue
}
provider.BuildTCPRouterConfiguration(ctxContainer, confFromLabel.TCP)
if len(confFromLabel.HTTP.Routers) == 0 &&
len(confFromLabel.HTTP.Middlewares) == 0 &&
len(confFromLabel.HTTP.Services) == 0 {
configurations[containerName] = confFromLabel
continue
}
}
err = p.buildServiceConfiguration(ctxContainer, container, confFromLabel.HTTP) err = p.buildServiceConfiguration(ctxContainer, container, confFromLabel.HTTP)
if err != nil { if err != nil {
logger.Error(err) logger.Error(err)
@ -57,6 +72,28 @@ func (p *Provider) buildConfiguration(ctx context.Context, containersInspected [
return provider.Merge(ctx, configurations) return provider.Merge(ctx, configurations)
} }
func (p *Provider) buildTCPServiceConfiguration(ctx context.Context, container dockerData, configuration *config.TCPConfiguration) error {
serviceName := getServiceName(container)
if len(configuration.Services) == 0 {
configuration.Services = make(map[string]*config.TCPService)
lb := &config.TCPLoadBalancerService{}
lb.SetDefaults()
configuration.Services[serviceName] = &config.TCPService{
LoadBalancer: lb,
}
}
for _, service := range configuration.Services {
err := p.addServerTCP(ctx, container, service.LoadBalancer)
if err != nil {
return err
}
}
return nil
}
func (p *Provider) buildServiceConfiguration(ctx context.Context, container dockerData, configuration *config.HTTPConfiguration) error { func (p *Provider) buildServiceConfiguration(ctx context.Context, container dockerData, configuration *config.HTTPConfiguration) error {
serviceName := getServiceName(container) serviceName := getServiceName(container)
@ -102,6 +139,36 @@ func (p *Provider) keepContainer(ctx context.Context, container dockerData) bool
return true return true
} }
func (p *Provider) addServerTCP(ctx context.Context, container dockerData, loadBalancer *config.TCPLoadBalancerService) error {
serverPort := ""
if loadBalancer != nil && len(loadBalancer.Servers) > 0 {
serverPort = loadBalancer.Servers[0].Port
}
ip, port, err := p.getIPPort(ctx, container, serverPort)
if err != nil {
return err
}
if len(loadBalancer.Servers) == 0 {
server := config.TCPServer{}
server.SetDefaults()
loadBalancer.Servers = []config.TCPServer{server}
}
if serverPort != "" {
port = serverPort
loadBalancer.Servers[0].Port = ""
}
if port == "" {
return errors.New("port is missing")
}
loadBalancer.Servers[0].Address = net.JoinHostPort(ip, port)
return nil
}
func (p *Provider) addServer(ctx context.Context, container dockerData, loadBalancer *config.LoadBalancerService) error { func (p *Provider) addServer(ctx context.Context, container dockerData, loadBalancer *config.LoadBalancerService) error {
serverPort := getLBServerPort(loadBalancer) serverPort := getLBServerPort(loadBalancer)
ip, port, err := p.getIPPort(ctx, container, serverPort) ip, port, err := p.getIPPort(ctx, container, serverPort)

File diff suppressed because it is too large Load diff