Add support for tcp labels in docker provider
This commit is contained in:
parent
ec1952157b
commit
0f2c4fb5f4
6 changed files with 1310 additions and 566 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
Loading…
Reference in a new issue