2015-11-01 15:35:01 +00:00
package provider
2015-09-12 13:10:03 +00:00
2015-09-07 22:15:14 +00:00
import (
2016-08-16 15:26:10 +00:00
"context"
2015-09-15 20:32:09 +00:00
"errors"
2016-08-25 04:22:06 +00:00
"math"
2016-08-05 14:02:46 +00:00
"net"
2016-04-08 12:20:54 +00:00
"net/http"
2015-09-24 15:16:13 +00:00
"strconv"
"strings"
"text/template"
"time"
2015-09-10 20:54:37 +00:00
"github.com/BurntSushi/ty/fun"
2016-09-19 17:08:39 +00:00
"github.com/cenk/backoff"
"github.com/containous/traefik/job"
2016-09-23 16:27:01 +00:00
"github.com/containous/traefik/log"
2016-03-31 16:57:08 +00:00
"github.com/containous/traefik/safe"
2016-02-24 15:43:39 +00:00
"github.com/containous/traefik/types"
2016-07-21 14:33:49 +00:00
"github.com/containous/traefik/version"
2016-04-08 12:20:54 +00:00
"github.com/docker/engine-api/client"
dockertypes "github.com/docker/engine-api/types"
2016-08-05 14:02:46 +00:00
dockercontainertypes "github.com/docker/engine-api/types/container"
2016-04-08 12:20:54 +00:00
eventtypes "github.com/docker/engine-api/types/events"
"github.com/docker/engine-api/types/filters"
2016-08-05 14:02:46 +00:00
"github.com/docker/engine-api/types/swarm"
swarmtypes "github.com/docker/engine-api/types/swarm"
"github.com/docker/go-connections/nat"
2016-04-08 12:20:54 +00:00
"github.com/docker/go-connections/sockets"
"github.com/vdemeester/docker-events"
2017-03-08 14:10:21 +00:00
"regexp"
2015-09-07 08:38:58 +00:00
)
2015-09-09 20:39:08 +00:00
2016-08-05 14:02:46 +00:00
const (
// SwarmAPIVersion is a constant holding the version of the Docker API traefik will use
SwarmAPIVersion string = "1.24"
// SwarmDefaultWatchTime is the duration of the interval when polling docker
SwarmDefaultWatchTime = 15 * time . Second
)
2016-04-08 12:20:54 +00:00
2016-08-16 17:13:18 +00:00
var _ Provider = ( * Docker ) ( nil )
2015-11-01 18:29:47 +00:00
// Docker holds configurations of the Docker provider.
2015-11-02 18:48:34 +00:00
type Docker struct {
2016-06-24 07:58:42 +00:00
BaseProvider ` mapstructure:",squash" `
2016-07-14 09:32:15 +00:00
Endpoint string ` description:"Docker server endpoint. Can be a tcp or a unix socket endpoint" `
Domain string ` description:"Default domain used" `
2016-06-27 14:14:56 +00:00
TLS * ClientTLS ` description:"Enable Docker TLS support" `
2016-07-14 09:32:15 +00:00
ExposedByDefault bool ` description:"Expose containers by default" `
2016-09-20 12:52:35 +00:00
UseBindPortIP bool ` description:"Use the ip address from the bound port, rather than from the inner network" `
2016-08-05 14:02:46 +00:00
SwarmMode bool ` description:"Use Docker on Swarm Mode" `
}
// dockerData holds the need data to the Docker provider
type dockerData struct {
2017-02-03 01:18:12 +00:00
ServiceName string
2016-08-05 14:02:46 +00:00
Name string
Labels map [ string ] string // List of labels set to container or service
NetworkSettings networkSettings
2016-10-03 09:01:37 +00:00
Health string
2016-08-05 14:02:46 +00:00
}
// NetworkSettings holds the networks data to the Docker provider
type networkSettings struct {
NetworkMode dockercontainertypes . NetworkMode
Ports nat . PortMap
Networks map [ string ] * networkData
}
// Network holds the network data to the Docker provider
type networkData struct {
Name string
Addr string
Port int
Protocol string
ID string
2015-11-20 15:05:06 +00:00
}
2016-04-08 12:20:54 +00:00
func ( provider * Docker ) createClient ( ) ( client . APIClient , error ) {
var httpClient * http . Client
httpHeaders := map [ string ] string {
2016-07-21 14:33:49 +00:00
"User-Agent" : "Traefik " + version . Version ,
2016-04-08 12:20:54 +00:00
}
if provider . TLS != nil {
2016-06-27 14:14:56 +00:00
config , err := provider . TLS . CreateTLSConfig ( )
2016-04-08 12:20:54 +00:00
if err != nil {
return nil , err
}
tr := & http . Transport {
TLSClientConfig : config ,
}
proto , addr , _ , err := client . ParseHost ( provider . Endpoint )
if err != nil {
return nil , err
}
sockets . ConfigureTransport ( tr , proto , addr )
httpClient = & http . Client {
Transport : tr ,
}
2016-06-27 14:14:56 +00:00
2016-04-08 12:20:54 +00:00
}
2016-08-05 14:02:46 +00:00
var version string
if provider . SwarmMode {
version = SwarmAPIVersion
} else {
version = DockerAPIVersion
}
return client . NewClient ( provider . Endpoint , version , httpClient , httpHeaders )
2016-04-08 12:20:54 +00:00
}
2015-11-01 18:29:47 +00:00
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
2016-11-09 18:27:04 +00:00
func ( provider * Docker ) Provide ( configurationChan chan <- types . ConfigMessage , pool * safe . Pool , constraints types . Constraints ) error {
2016-05-30 13:05:58 +00:00
provider . Constraints = append ( provider . Constraints , constraints ... )
2016-04-13 18:36:23 +00:00
// TODO register this routine in pool, and watch for stop channel
2016-03-31 16:57:08 +00:00
safe . Go ( func ( ) {
2016-02-25 17:30:13 +00:00
operation := func ( ) error {
var err error
2015-11-01 18:29:47 +00:00
2016-04-08 12:20:54 +00:00
dockerClient , err := provider . createClient ( )
2016-02-25 17:30:13 +00:00
if err != nil {
log . Errorf ( "Failed to create a client for docker, error: %s" , err )
return err
}
2016-06-08 17:39:38 +00:00
ctx := context . Background ( )
version , err := dockerClient . ServerVersion ( ctx )
2016-04-08 12:20:54 +00:00
log . Debugf ( "Docker connection established with docker %s (API %s)" , version . Version , version . APIVersion )
2016-08-05 14:02:46 +00:00
var dockerDataList [ ] dockerData
if provider . SwarmMode {
2017-01-07 08:20:52 +00:00
dockerDataList , err = provider . listServices ( ctx , dockerClient )
2016-08-05 14:02:46 +00:00
if err != nil {
log . Errorf ( "Failed to list services for docker swarm mode, error %s" , err )
return err
}
} else {
dockerDataList , err = listContainers ( ctx , dockerClient )
if err != nil {
log . Errorf ( "Failed to list containers for docker, error %s" , err )
return err
}
2016-02-25 17:30:13 +00:00
}
2016-08-05 14:02:46 +00:00
configuration := provider . loadDockerConfig ( dockerDataList )
2016-02-25 17:30:13 +00:00
configurationChan <- types . ConfigMessage {
ProviderName : "docker" ,
Configuration : configuration ,
}
if provider . Watch {
2016-06-08 17:39:38 +00:00
ctx , cancel := context . WithCancel ( ctx )
2016-08-05 14:02:46 +00:00
if provider . SwarmMode {
// TODO: This need to be change. Linked to Swarm events docker/docker#23827
ticker := time . NewTicker ( SwarmDefaultWatchTime )
pool . Go ( func ( stop chan bool ) {
for {
select {
case <- ticker . C :
2017-01-07 08:20:52 +00:00
services , err := provider . listServices ( ctx , dockerClient )
2016-08-05 14:02:46 +00:00
if err != nil {
log . Errorf ( "Failed to list services for docker, error %s" , err )
return
}
configuration := provider . loadDockerConfig ( services )
if configuration != nil {
configurationChan <- types . ConfigMessage {
ProviderName : "docker" ,
Configuration : configuration ,
}
}
case <- stop :
ticker . Stop ( )
cancel ( )
return
}
}
} )
} else {
pool . Go ( func ( stop chan bool ) {
for {
select {
case <- stop :
cancel ( )
return
}
}
} )
f := filters . NewArgs ( )
f . Add ( "type" , "container" )
options := dockertypes . EventsOptions {
Filters : f ,
}
eventHandler := events . NewHandler ( events . ByAction )
startStopHandle := func ( m eventtypes . Message ) {
log . Debugf ( "Docker event received %+v" , m )
containers , err := listContainers ( ctx , dockerClient )
if err != nil {
log . Errorf ( "Failed to list containers for docker, error %s" , err )
// Call cancel to get out of the monitor
2016-06-16 20:49:57 +00:00
cancel ( )
return
}
2016-08-05 14:02:46 +00:00
configuration := provider . loadDockerConfig ( containers )
if configuration != nil {
configurationChan <- types . ConfigMessage {
ProviderName : "docker" ,
Configuration : configuration ,
}
2015-09-10 20:54:37 +00:00
}
2015-09-10 07:06:37 +00:00
}
2016-08-05 14:02:46 +00:00
eventHandler . Handle ( "start" , startStopHandle )
eventHandler . Handle ( "die" , startStopHandle )
2016-10-03 09:01:37 +00:00
eventHandler . Handle ( "health_status: healthy" , startStopHandle )
eventHandler . Handle ( "health_status: unhealthy" , startStopHandle )
eventHandler . Handle ( "health_status: starting" , startStopHandle )
2016-04-13 18:36:23 +00:00
2016-08-05 14:02:46 +00:00
errChan := events . MonitorWithHandler ( ctx , dockerClient , options , eventHandler )
if err := <- errChan ; err != nil {
return err
}
2016-04-08 12:20:54 +00:00
}
2015-11-01 18:29:47 +00:00
}
2016-02-25 17:30:13 +00:00
return nil
}
notify := func ( err error , time time . Duration ) {
log . Errorf ( "Docker connection error %+v, retrying in %s" , err , time )
}
2016-12-08 12:32:12 +00:00
err := backoff . RetryNotify ( safe . OperationWithRecover ( operation ) , job . NewBackOff ( backoff . NewExponentialBackOff ( ) ) , notify )
2016-02-25 17:30:13 +00:00
if err != nil {
2016-08-19 08:36:54 +00:00
log . Errorf ( "Cannot connect to docker server %+v" , err )
2016-02-25 17:30:13 +00:00
}
2016-03-31 16:57:08 +00:00
} )
2015-11-01 18:29:47 +00:00
2015-10-01 10:04:25 +00:00
return nil
2015-09-07 08:38:58 +00:00
}
2016-08-05 14:02:46 +00:00
func ( provider * Docker ) loadDockerConfig ( containersInspected [ ] dockerData ) * types . Configuration {
2015-10-08 19:21:51 +00:00
var DockerFuncMap = template . FuncMap {
2016-08-25 04:22:06 +00:00
"getBackend" : provider . getBackend ,
"getIPAddress" : provider . getIPAddress ,
"getPort" : provider . getPort ,
"getWeight" : provider . getWeight ,
"getDomain" : provider . getDomain ,
"getProtocol" : provider . getProtocol ,
"getPassHostHeader" : provider . getPassHostHeader ,
"getPriority" : provider . getPriority ,
"getEntryPoints" : provider . getEntryPoints ,
"getFrontendRule" : provider . getFrontendRule ,
"hasCircuitBreakerLabel" : provider . hasCircuitBreakerLabel ,
"getCircuitBreakerExpression" : provider . getCircuitBreakerExpression ,
"hasLoadBalancerLabel" : provider . hasLoadBalancerLabel ,
"getLoadBalancerMethod" : provider . getLoadBalancerMethod ,
"hasMaxConnLabels" : provider . hasMaxConnLabels ,
"getMaxConnAmount" : provider . getMaxConnAmount ,
"getMaxConnExtractorFunc" : provider . getMaxConnExtractorFunc ,
2016-06-08 19:08:03 +00:00
"getSticky" : provider . getSticky ,
2017-01-07 08:20:52 +00:00
"getIsBackendLBSwarm" : provider . getIsBackendLBSwarm ,
2017-03-08 14:10:21 +00:00
"hasServices" : provider . hasServices ,
"getServiceNames" : provider . getServiceNames ,
"getServicePort" : provider . getServicePort ,
"getServiceWeight" : provider . getServiceWeight ,
"getServiceProtocol" : provider . getServiceProtocol ,
"getServiceEntryPoints" : provider . getServiceEntryPoints ,
"getServiceFrontendRule" : provider . getServiceFrontendRule ,
"getServicePassHostHeader" : provider . getServicePassHostHeader ,
"getServicePriority" : provider . getServicePriority ,
"getServiceBackend" : provider . getServiceBackend ,
2015-09-10 20:54:37 +00:00
}
// filter containers
2016-08-05 14:02:46 +00:00
filteredContainers := fun . Filter ( func ( container dockerData ) bool {
2016-09-20 12:52:35 +00:00
return provider . containerFilter ( container )
2016-08-05 14:02:46 +00:00
} , containersInspected ) . ( [ ] dockerData )
2015-09-10 20:54:37 +00:00
2016-08-05 14:02:46 +00:00
frontends := map [ string ] [ ] dockerData { }
2016-09-28 14:29:19 +00:00
backends := map [ string ] dockerData { }
servers := map [ string ] [ ] dockerData { }
2015-09-10 20:54:37 +00:00
for _ , container := range filteredContainers {
2016-06-06 19:59:58 +00:00
frontendName := provider . getFrontendName ( container )
frontends [ frontendName ] = append ( frontends [ frontendName ] , container )
2016-09-28 14:29:19 +00:00
backendName := provider . getBackend ( container )
backends [ backendName ] = container
servers [ backendName ] = append ( servers [ backendName ] , container )
2015-09-07 08:38:58 +00:00
}
2015-09-09 14:49:51 +00:00
templateObjects := struct {
2016-09-28 11:28:20 +00:00
Containers [ ] dockerData
Frontends map [ string ] [ ] dockerData
2016-09-28 14:29:19 +00:00
Backends map [ string ] dockerData
Servers map [ string ] [ ] dockerData
2016-09-28 11:28:20 +00:00
Domain string
2015-09-07 08:38:58 +00:00
} {
2015-09-10 20:54:37 +00:00
filteredContainers ,
2015-10-23 07:49:19 +00:00
frontends ,
2016-09-28 14:29:19 +00:00
backends ,
servers ,
2015-09-09 15:10:43 +00:00
provider . Domain ,
2015-09-07 08:38:58 +00:00
}
2015-11-13 10:50:32 +00:00
configuration , err := provider . getConfiguration ( "templates/docker.tmpl" , DockerFuncMap , templateObjects )
2015-09-07 08:38:58 +00:00
if err != nil {
2015-11-13 10:50:32 +00:00
log . Error ( err )
2015-09-07 08:38:58 +00:00
}
2015-11-13 10:50:32 +00:00
return configuration
}
2015-09-07 08:38:58 +00:00
2016-08-05 14:02:46 +00:00
func ( provider * Docker ) hasCircuitBreakerLabel ( container dockerData ) bool {
2016-08-25 04:22:06 +00:00
if _ , err := getLabel ( container , "traefik.backend.circuitbreaker.expression" ) ; err != nil {
return false
}
return true
}
2017-03-08 14:10:21 +00:00
// Regexp used to extract the name of the service and the name of the property for this service
// All properties are under the format traefik.<servicename>.frontent.*= except the port/weight/protocol directly after traefik.<servicename>.
var servicesPropertiesRegexp = regexp . MustCompile ( ` ^traefik\.(?P<service_name>.*?)\.(?P<property_name>port|weight|protocol|frontend\.(.*))$ ` )
// Map of services properties
// we can get it with label[serviceName][propertyName] and we got the propertyValue
type labelServiceProperties map [ string ] map [ string ] string
// Check if for the given container, we find labels that are defining services
func ( provider * Docker ) hasServices ( container dockerData ) bool {
return len ( extractServicesLabels ( container . Labels ) ) > 0
}
// Extract the service labels from container labels of dockerData struct
func extractServicesLabels ( labels map [ string ] string ) labelServiceProperties {
v := make ( labelServiceProperties )
for index , serviceProperty := range labels {
matches := servicesPropertiesRegexp . FindStringSubmatch ( index )
if matches != nil {
result := make ( map [ string ] string )
for i , name := range servicesPropertiesRegexp . SubexpNames ( ) {
if i != 0 {
result [ name ] = matches [ i ]
}
}
serviceName := result [ "service_name" ]
if _ , ok := v [ serviceName ] ; ! ok {
v [ serviceName ] = make ( map [ string ] string )
}
v [ serviceName ] [ result [ "property_name" ] ] = serviceProperty
}
}
return v
}
// Gets the entry for a service label searching in all labels of the given container
func getContainerServiceLabel ( container dockerData , serviceName string , entry string ) ( string , bool ) {
value , ok := extractServicesLabels ( container . Labels ) [ serviceName ] [ entry ]
return value , ok
}
// Gets array of service names for a given container
func ( provider * Docker ) getServiceNames ( container dockerData ) [ ] string {
labelServiceProperties := extractServicesLabels ( container . Labels )
keys := make ( [ ] string , 0 , len ( labelServiceProperties ) )
for k := range labelServiceProperties {
keys = append ( keys , k )
}
return keys
}
// Extract entrypoints from labels for a given service and a given docker container
func ( provider * Docker ) getServiceEntryPoints ( container dockerData , serviceName string ) [ ] string {
if entryPoints , ok := getContainerServiceLabel ( container , serviceName , "frontend.entryPoints" ) ; ok {
return strings . Split ( entryPoints , "," )
}
return provider . getEntryPoints ( container )
}
// Extract passHostHeader from labels for a given service and a given docker container
func ( provider * Docker ) getServicePassHostHeader ( container dockerData , serviceName string ) string {
if servicePassHostHeader , ok := getContainerServiceLabel ( container , serviceName , "frontend.passHostHeader" ) ; ok {
return servicePassHostHeader
}
return provider . getPassHostHeader ( container )
}
// Extract priority from labels for a given service and a given docker container
func ( provider * Docker ) getServicePriority ( container dockerData , serviceName string ) string {
if value , ok := getContainerServiceLabel ( container , serviceName , "frontend.priority" ) ; ok {
return value
}
return provider . getPriority ( container )
}
// Extract backend from labels for a given service and a given docker container
func ( provider * Docker ) getServiceBackend ( container dockerData , serviceName string ) string {
if value , ok := getContainerServiceLabel ( container , serviceName , "frontend.backend" ) ; ok {
return value
}
return provider . getBackend ( container ) + "-" + normalize ( serviceName )
}
// Extract rule from labels for a given service and a given docker container
func ( provider * Docker ) getServiceFrontendRule ( container dockerData , serviceName string ) string {
if value , ok := getContainerServiceLabel ( container , serviceName , "frontend.rule" ) ; ok {
return value
}
return provider . getFrontendRule ( container )
}
// Extract port from labels for a given service and a given docker container
func ( provider * Docker ) getServicePort ( container dockerData , serviceName string ) string {
if value , ok := getContainerServiceLabel ( container , serviceName , "port" ) ; ok {
return value
}
return provider . getPort ( container )
}
// Extract weight from labels for a given service and a given docker container
func ( provider * Docker ) getServiceWeight ( container dockerData , serviceName string ) string {
if value , ok := getContainerServiceLabel ( container , serviceName , "weight" ) ; ok {
return value
}
return provider . getWeight ( container )
}
// Extract protocol from labels for a given service and a given docker container
func ( provider * Docker ) getServiceProtocol ( container dockerData , serviceName string ) string {
if value , ok := getContainerServiceLabel ( container , serviceName , "protocol" ) ; ok {
return value
}
return provider . getProtocol ( container )
}
2016-08-05 14:02:46 +00:00
func ( provider * Docker ) hasLoadBalancerLabel ( container dockerData ) bool {
2016-09-28 11:28:20 +00:00
_ , errMethod := getLabel ( container , "traefik.backend.loadbalancer.method" )
_ , errSticky := getLabel ( container , "traefik.backend.loadbalancer.sticky" )
if errMethod != nil && errSticky != nil {
2016-08-25 04:22:06 +00:00
return false
}
return true
}
2016-08-05 14:02:46 +00:00
func ( provider * Docker ) hasMaxConnLabels ( container dockerData ) bool {
2016-08-25 04:22:06 +00:00
if _ , err := getLabel ( container , "traefik.backend.maxconn.amount" ) ; err != nil {
return false
}
if _ , err := getLabel ( container , "traefik.backend.maxconn.extractorfunc" ) ; err != nil {
return false
}
return true
}
2016-08-05 14:02:46 +00:00
func ( provider * Docker ) getCircuitBreakerExpression ( container dockerData ) string {
2016-08-25 04:22:06 +00:00
if label , err := getLabel ( container , "traefik.backend.circuitbreaker.expression" ) ; err == nil {
return label
}
return "NetworkErrorRatio() > 1"
}
2016-08-05 14:02:46 +00:00
func ( provider * Docker ) getLoadBalancerMethod ( container dockerData ) string {
2016-08-25 04:22:06 +00:00
if label , err := getLabel ( container , "traefik.backend.loadbalancer.method" ) ; err == nil {
return label
}
return "wrr"
}
2016-08-05 14:02:46 +00:00
func ( provider * Docker ) getMaxConnAmount ( container dockerData ) int64 {
2016-08-25 04:22:06 +00:00
if label , err := getLabel ( container , "traefik.backend.maxconn.amount" ) ; err == nil {
i , errConv := strconv . ParseInt ( label , 10 , 64 )
if errConv != nil {
log . Errorf ( "Unable to parse traefik.backend.maxconn.amount %s" , label )
return math . MaxInt64
}
return i
}
return math . MaxInt64
}
2016-08-05 14:02:46 +00:00
func ( provider * Docker ) getMaxConnExtractorFunc ( container dockerData ) string {
2016-08-25 04:22:06 +00:00
if label , err := getLabel ( container , "traefik.backend.maxconn.extractorfunc" ) ; err == nil {
return label
}
return "request.host"
}
2016-08-05 14:02:46 +00:00
func ( provider * Docker ) containerFilter ( container dockerData ) bool {
_ , err := strconv . Atoi ( container . Labels [ "traefik.port" ] )
2016-05-28 22:16:57 +00:00
if len ( container . NetworkSettings . Ports ) == 0 && err != nil {
log . Debugf ( "Filtering container without port and no traefik.port label %s" , container . Name )
2015-11-13 10:50:32 +00:00
return false
2015-09-07 08:38:58 +00:00
}
2015-11-13 10:50:32 +00:00
2016-09-20 12:52:35 +00:00
if ! isContainerEnabled ( container , provider . ExposedByDefault ) {
2015-11-13 10:50:32 +00:00
log . Debugf ( "Filtering disabled container %s" , container . Name )
return false
}
2016-08-05 14:02:46 +00:00
constraintTags := strings . Split ( container . Labels [ "traefik.tags" ] , "," )
2016-06-11 17:05:54 +00:00
if ok , failingConstraint := provider . MatchConstraints ( constraintTags ) ; ! ok {
2016-06-06 19:59:58 +00:00
if failingConstraint != nil {
log . Debugf ( "Container %v pruned by '%v' constraint" , container . Name , failingConstraint . String ( ) )
}
return false
}
2016-10-03 09:01:37 +00:00
if container . Health != "" && container . Health != "healthy" {
log . Debugf ( "Filtering unhealthy or starting container %s" , container . Name )
return false
}
2015-11-13 10:50:32 +00:00
return true
2015-09-09 14:49:51 +00:00
}
2016-08-05 14:02:46 +00:00
func ( provider * Docker ) getFrontendName ( container dockerData ) string {
2015-10-23 07:49:19 +00:00
// Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78
2016-03-27 00:05:17 +00:00
return normalize ( provider . getFrontendRule ( container ) )
2015-10-23 07:49:19 +00:00
}
2015-11-13 10:50:32 +00:00
// GetFrontendRule returns the frontend rule for the specified container, using
// it's label. It returns a default one (Host) if the label is not present.
2016-08-05 14:02:46 +00:00
func ( provider * Docker ) getFrontendRule ( container dockerData ) string {
2015-11-13 10:50:32 +00:00
if label , err := getLabel ( container , "traefik.frontend.rule" ) ; err == nil {
return label
}
2017-03-04 12:31:07 +00:00
if labels , err := getLabels ( container , [ ] string { "com.docker.compose.project" , "com.docker.compose.service" } ) ; err == nil {
2017-03-09 21:27:09 +00:00
return "Host:" + provider . getSubDomain ( labels [ "com.docker.compose.service" ] + "." + labels [ "com.docker.compose.project" ] ) + "." + provider . Domain
2017-03-04 12:31:07 +00:00
}
2017-02-03 01:18:12 +00:00
return "Host:" + provider . getSubDomain ( container . ServiceName ) + "." + provider . Domain
2015-11-13 10:50:32 +00:00
}
2016-08-05 14:02:46 +00:00
func ( provider * Docker ) getBackend ( container dockerData ) string {
2015-11-13 10:50:32 +00:00
if label , err := getLabel ( container , "traefik.backend" ) ; err == nil {
2016-11-23 20:31:37 +00:00
return normalize ( label )
2015-11-13 10:50:32 +00:00
}
2017-03-04 12:31:07 +00:00
if labels , err := getLabels ( container , [ ] string { "com.docker.compose.project" , "com.docker.compose.service" } ) ; err == nil {
2017-03-09 21:27:09 +00:00
return normalize ( labels [ "com.docker.compose.service" ] + "_" + labels [ "com.docker.compose.project" ] )
2017-03-04 12:31:07 +00:00
}
2017-02-03 01:18:12 +00:00
return normalize ( container . ServiceName )
2015-11-13 10:50:32 +00:00
}
2016-08-05 14:02:46 +00:00
func ( provider * Docker ) getIPAddress ( container dockerData ) string {
2016-06-01 05:11:17 +00:00
if label , err := getLabel ( container , "traefik.docker.network" ) ; err == nil && label != "" {
2016-08-05 14:02:46 +00:00
networkSettings := container . NetworkSettings
if networkSettings . Networks != nil {
network := networkSettings . Networks [ label ]
2016-06-01 05:11:17 +00:00
if network != nil {
2016-08-05 14:02:46 +00:00
return network . Addr
2016-06-01 05:11:17 +00:00
}
2017-03-19 17:40:09 +00:00
log . Warnf ( "Could not find network named '%s' for container '%s'! Maybe you're missing the project's prefix in the label? Defaulting to first available network." , label , container . Name )
2016-06-01 05:11:17 +00:00
}
}
2016-07-05 09:44:30 +00:00
// If net==host, quick n' dirty, we return 127.0.0.1
// This will work locally, but will fail with swarm.
2016-08-05 14:02:46 +00:00
if "host" == container . NetworkSettings . NetworkMode {
2016-07-05 09:44:30 +00:00
return "127.0.0.1"
}
2016-09-20 12:52:35 +00:00
if provider . UseBindPortIP {
port := provider . getPort ( container )
for netport , portBindings := range container . NetworkSettings . Ports {
if string ( netport ) == port + "/TCP" || string ( netport ) == port + "/UDP" {
for _ , p := range portBindings {
return p . HostIP
}
}
}
}
2016-07-05 09:44:30 +00:00
for _ , network := range container . NetworkSettings . Networks {
2016-08-05 14:02:46 +00:00
return network . Addr
2016-06-01 05:11:17 +00:00
}
return ""
}
2016-08-05 14:02:46 +00:00
func ( provider * Docker ) getPort ( container dockerData ) string {
2015-11-13 10:50:32 +00:00
if label , err := getLabel ( container , "traefik.port" ) ; err == nil {
return label
}
for key := range container . NetworkSettings . Ports {
return key . Port ( )
}
return ""
}
2016-08-05 14:02:46 +00:00
func ( provider * Docker ) getWeight ( container dockerData ) string {
2015-11-13 10:50:32 +00:00
if label , err := getLabel ( container , "traefik.weight" ) ; err == nil {
return label
}
2016-11-23 13:49:55 +00:00
return "0"
2015-11-13 10:50:32 +00:00
}
2016-09-28 11:28:20 +00:00
func ( provider * Docker ) getSticky ( container dockerData ) string {
2017-01-07 08:20:52 +00:00
if label , err := getLabel ( container , "traefik.backend.loadbalancer.sticky" ) ; err == nil {
return label
}
return "false"
}
func ( provider * Docker ) getIsBackendLBSwarm ( container dockerData ) string {
if label , err := getLabel ( container , "traefik.backend.loadbalancer.swarm" ) ; err == nil {
return label
2016-06-02 18:36:14 +00:00
}
2016-06-08 19:08:03 +00:00
return "false"
2016-06-02 18:36:14 +00:00
}
2016-08-05 14:02:46 +00:00
func ( provider * Docker ) getDomain ( container dockerData ) string {
2015-11-13 10:50:32 +00:00
if label , err := getLabel ( container , "traefik.domain" ) ; err == nil {
return label
}
return provider . Domain
2015-10-23 07:49:19 +00:00
}
2016-08-05 14:02:46 +00:00
func ( provider * Docker ) getProtocol ( container dockerData ) string {
2015-11-13 10:50:32 +00:00
if label , err := getLabel ( container , "traefik.protocol" ) ; err == nil {
return label
}
return "http"
}
2016-08-05 14:02:46 +00:00
func ( provider * Docker ) getPassHostHeader ( container dockerData ) string {
2015-11-13 10:50:32 +00:00
if passHostHeader , err := getLabel ( container , "traefik.frontend.passHostHeader" ) ; err == nil {
return passHostHeader
}
2016-05-10 11:43:24 +00:00
return "true"
2015-11-13 10:50:32 +00:00
}
2016-08-05 14:02:46 +00:00
func ( provider * Docker ) getPriority ( container dockerData ) string {
2016-06-06 20:30:23 +00:00
if priority , err := getLabel ( container , "traefik.frontend.priority" ) ; err == nil {
return priority
}
return "0"
}
2016-08-05 14:02:46 +00:00
func ( provider * Docker ) getEntryPoints ( container dockerData ) [ ] string {
2016-02-01 15:08:58 +00:00
if entryPoints , err := getLabel ( container , "traefik.frontend.entryPoints" ) ; err == nil {
return strings . Split ( entryPoints , "," )
}
return [ ] string { }
}
2016-08-05 14:02:46 +00:00
func isContainerEnabled ( container dockerData , exposedByDefault bool ) bool {
return exposedByDefault && container . Labels [ "traefik.enable" ] != "false" || container . Labels [ "traefik.enable" ] == "true"
2016-07-14 09:32:15 +00:00
}
2016-08-05 14:02:46 +00:00
func getLabel ( container dockerData , label string ) ( string , error ) {
for key , value := range container . Labels {
2015-10-23 07:49:19 +00:00
if key == label {
return value , nil
2015-09-09 14:49:51 +00:00
}
}
2015-10-23 07:49:19 +00:00
return "" , errors . New ( "Label not found:" + label )
}
2016-08-05 14:02:46 +00:00
func getLabels ( container dockerData , labels [ ] string ) ( map [ string ] string , error ) {
2015-11-05 14:14:25 +00:00
var globalErr error
2015-10-26 23:26:35 +00:00
foundLabels := map [ string ] string { }
for _ , label := range labels {
2015-11-13 10:50:32 +00:00
foundLabel , err := getLabel ( container , label )
2015-11-05 14:14:25 +00:00
// Error out only if one of them is defined.
2015-11-01 18:29:47 +00:00
if err != nil {
2015-11-05 14:14:25 +00:00
globalErr = errors . New ( "Label not found: " + label )
continue
2015-10-26 23:26:35 +00:00
}
2015-11-01 18:29:47 +00:00
foundLabels [ label ] = foundLabel
2015-11-05 14:14:25 +00:00
2015-10-26 23:26:35 +00:00
}
2015-11-05 14:14:25 +00:00
return foundLabels , globalErr
2015-10-26 23:26:35 +00:00
}
2016-08-05 14:02:46 +00:00
func listContainers ( ctx context . Context , dockerClient client . ContainerAPIClient ) ( [ ] dockerData , error ) {
2016-06-08 17:39:38 +00:00
containerList , err := dockerClient . ContainerList ( ctx , dockertypes . ContainerListOptions { } )
2016-04-08 12:20:54 +00:00
if err != nil {
2016-08-05 14:02:46 +00:00
return [ ] dockerData { } , err
2016-04-08 12:20:54 +00:00
}
2016-08-05 14:02:46 +00:00
containersInspected := [ ] dockerData { }
2015-10-23 07:49:19 +00:00
2015-11-13 10:50:32 +00:00
// get inspect containers
for _ , container := range containerList {
2016-06-08 17:39:38 +00:00
containerInspected , err := dockerClient . ContainerInspect ( ctx , container . ID )
2016-04-08 12:20:54 +00:00
if err != nil {
2016-06-20 10:15:31 +00:00
log . Warnf ( "Failed to inspect container %s, error: %s" , container . ID , err )
2016-06-08 17:39:38 +00:00
} else {
2016-08-05 14:02:46 +00:00
dockerData := parseContainer ( containerInspected )
containersInspected = append ( containersInspected , dockerData )
2016-04-08 12:20:54 +00:00
}
2015-10-23 07:49:19 +00:00
}
2016-04-08 12:20:54 +00:00
return containersInspected , nil
2015-09-12 13:10:03 +00:00
}
2016-05-31 21:23:23 +00:00
2016-08-05 14:02:46 +00:00
func parseContainer ( container dockertypes . ContainerJSON ) dockerData {
dockerData := dockerData {
NetworkSettings : networkSettings { } ,
}
if container . ContainerJSONBase != nil {
dockerData . Name = container . ContainerJSONBase . Name
2017-02-03 01:18:12 +00:00
dockerData . ServiceName = dockerData . Name //Default ServiceName to be the container's Name.
2016-08-05 14:02:46 +00:00
if container . ContainerJSONBase . HostConfig != nil {
dockerData . NetworkSettings . NetworkMode = container . ContainerJSONBase . HostConfig . NetworkMode
}
2016-11-28 15:46:37 +00:00
if container . State != nil && container . State . Health != nil {
dockerData . Health = container . State . Health . Status
}
2016-08-05 14:02:46 +00:00
}
if container . Config != nil && container . Config . Labels != nil {
dockerData . Labels = container . Config . Labels
}
if container . NetworkSettings != nil {
if container . NetworkSettings . Ports != nil {
dockerData . NetworkSettings . Ports = container . NetworkSettings . Ports
}
if container . NetworkSettings . Networks != nil {
dockerData . NetworkSettings . Networks = make ( map [ string ] * networkData )
for name , containerNetwork := range container . NetworkSettings . Networks {
dockerData . NetworkSettings . Networks [ name ] = & networkData {
ID : containerNetwork . NetworkID ,
Name : name ,
Addr : containerNetwork . IPAddress ,
}
}
}
}
return dockerData
}
2017-01-24 10:29:45 +00:00
// Escape beginning slash "/", convert all others to dash "-", and convert underscores "_" to dash "-"
2016-06-01 14:47:39 +00:00
func ( provider * Docker ) getSubDomain ( name string ) string {
2017-01-24 10:29:45 +00:00
return strings . Replace ( strings . Replace ( strings . TrimPrefix ( name , "/" ) , "/" , "-" , - 1 ) , "_" , "-" , - 1 )
2016-05-31 21:23:23 +00:00
}
2016-08-05 14:02:46 +00:00
2017-01-07 08:20:52 +00:00
func ( provider * Docker ) listServices ( ctx context . Context , dockerClient client . APIClient ) ( [ ] dockerData , error ) {
2016-08-05 14:02:46 +00:00
serviceList , err := dockerClient . ServiceList ( ctx , dockertypes . ServiceListOptions { } )
if err != nil {
return [ ] dockerData { } , err
}
networkListArgs := filters . NewArgs ( )
networkListArgs . Add ( "driver" , "overlay" )
networkList , err := dockerClient . NetworkList ( ctx , dockertypes . NetworkListOptions { Filters : networkListArgs } )
networkMap := make ( map [ string ] * dockertypes . NetworkResource )
if err != nil {
log . Debug ( "Failed to network inspect on client for docker, error: %s" , err )
return [ ] dockerData { } , err
}
for _ , network := range networkList {
2016-10-07 14:35:27 +00:00
networkToAdd := network
networkMap [ network . ID ] = & networkToAdd
2016-08-05 14:02:46 +00:00
}
var dockerDataList [ ] dockerData
2017-01-07 08:20:52 +00:00
var dockerDataListTasks [ ] dockerData
2016-08-05 14:02:46 +00:00
for _ , service := range serviceList {
dockerData := parseService ( service , networkMap )
2017-01-07 08:20:52 +00:00
useSwarmLB , _ := strconv . ParseBool ( provider . getIsBackendLBSwarm ( dockerData ) )
2017-02-16 16:50:41 +00:00
isGlobalSvc := service . Spec . Mode . Global != nil
2016-08-05 14:02:46 +00:00
2017-01-07 08:20:52 +00:00
if useSwarmLB {
dockerDataList = append ( dockerDataList , dockerData )
} else {
2017-02-16 16:50:41 +00:00
dockerDataListTasks , err = listTasks ( ctx , dockerClient , service . ID , dockerData , networkMap , isGlobalSvc )
2017-01-07 08:20:52 +00:00
for _ , dockerDataTask := range dockerDataListTasks {
dockerDataList = append ( dockerDataList , dockerDataTask )
}
}
2016-08-05 14:02:46 +00:00
}
return dockerDataList , err
}
func parseService ( service swarmtypes . Service , networkMap map [ string ] * dockertypes . NetworkResource ) dockerData {
dockerData := dockerData {
2017-02-03 01:18:12 +00:00
ServiceName : service . Spec . Annotations . Name ,
2016-08-05 14:02:46 +00:00
Name : service . Spec . Annotations . Name ,
Labels : service . Spec . Annotations . Labels ,
NetworkSettings : networkSettings { } ,
}
if service . Spec . EndpointSpec != nil {
switch service . Spec . EndpointSpec . Mode {
case swarm . ResolutionModeDNSRR :
log . Debug ( "Ignored endpoint-mode not supported, service name: %s" , dockerData . Name )
case swarm . ResolutionModeVIP :
dockerData . NetworkSettings . Networks = make ( map [ string ] * networkData )
for _ , virtualIP := range service . Endpoint . VirtualIPs {
networkService := networkMap [ virtualIP . NetworkID ]
if networkService != nil {
ip , _ , _ := net . ParseCIDR ( virtualIP . Addr )
network := & networkData {
Name : networkService . Name ,
ID : virtualIP . NetworkID ,
Addr : ip . String ( ) ,
}
dockerData . NetworkSettings . Networks [ network . Name ] = network
} else {
log . Debug ( "Network not found, id: %s" , virtualIP . NetworkID )
}
2017-01-07 08:20:52 +00:00
}
}
}
return dockerData
}
func listTasks ( ctx context . Context , dockerClient client . APIClient , serviceID string ,
2017-02-16 16:50:41 +00:00
serviceDockerData dockerData , networkMap map [ string ] * dockertypes . NetworkResource , isGlobalSvc bool ) ( [ ] dockerData , error ) {
2017-01-07 08:20:52 +00:00
serviceIDFilter := filters . NewArgs ( )
serviceIDFilter . Add ( "service" , serviceID )
2017-02-16 16:50:41 +00:00
serviceIDFilter . Add ( "desired-state" , "running" )
2017-01-07 08:20:52 +00:00
taskList , err := dockerClient . TaskList ( ctx , dockertypes . TaskListOptions { Filter : serviceIDFilter } )
if err != nil {
return [ ] dockerData { } , err
}
var dockerDataList [ ] dockerData
for _ , task := range taskList {
2017-03-16 14:38:40 +00:00
if task . Status . State != swarm . TaskStateRunning {
continue
}
2017-02-16 16:50:41 +00:00
dockerData := parseTasks ( task , serviceDockerData , networkMap , isGlobalSvc )
2017-01-07 08:20:52 +00:00
dockerDataList = append ( dockerDataList , dockerData )
}
return dockerDataList , err
}
2016-08-05 14:02:46 +00:00
2017-02-16 16:50:41 +00:00
func parseTasks ( task swarmtypes . Task , serviceDockerData dockerData , networkMap map [ string ] * dockertypes . NetworkResource , isGlobalSvc bool ) dockerData {
2017-01-07 08:20:52 +00:00
dockerData := dockerData {
2017-02-03 01:18:12 +00:00
ServiceName : serviceDockerData . Name ,
2017-01-07 08:20:52 +00:00
Name : serviceDockerData . Name + "." + strconv . Itoa ( task . Slot ) ,
Labels : serviceDockerData . Labels ,
NetworkSettings : networkSettings { } ,
}
2017-02-16 16:50:41 +00:00
if isGlobalSvc == true {
dockerData . Name = serviceDockerData . Name + "." + task . ID
}
2017-01-07 08:20:52 +00:00
if task . NetworksAttachments != nil {
dockerData . NetworkSettings . Networks = make ( map [ string ] * networkData )
for _ , virtualIP := range task . NetworksAttachments {
if networkService , present := networkMap [ virtualIP . Network . ID ] ; present {
// Not sure about this next loop - when would a task have multiple IP's for the same network?
for _ , addr := range virtualIP . Addresses {
ip , _ , _ := net . ParseCIDR ( addr )
network := & networkData {
ID : virtualIP . Network . ID ,
Name : networkService . Name ,
Addr : ip . String ( ) ,
}
dockerData . NetworkSettings . Networks [ network . Name ] = network
}
2016-08-05 14:02:46 +00:00
}
}
}
return dockerData
}