2017-04-15 13:49:53 +00:00
package docker
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"
2020-11-06 08:26:03 +00:00
"errors"
2019-01-21 18:06:02 +00:00
"fmt"
2018-01-15 13:26:03 +00:00
"io"
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"
2019-01-21 18:06:02 +00:00
"text/template"
2015-09-24 15:16:13 +00:00
"time"
2020-02-26 09:36:05 +00:00
"github.com/cenkalti/backoff/v4"
2019-12-18 14:28:04 +00:00
"github.com/docker/cli/cli/connhelper"
2017-07-06 14:28:13 +00:00
dockertypes "github.com/docker/docker/api/types"
dockercontainertypes "github.com/docker/docker/api/types/container"
eventtypes "github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
swarmtypes "github.com/docker/docker/api/types/swarm"
2017-10-23 08:33:02 +00:00
"github.com/docker/docker/api/types/versions"
2017-07-06 14:28:13 +00:00
"github.com/docker/docker/client"
2016-08-05 14:02:46 +00:00
"github.com/docker/go-connections/nat"
2016-04-08 12:20:54 +00:00
"github.com/docker/go-connections/sockets"
2020-08-17 16:04:03 +00:00
ptypes "github.com/traefik/paerser/types"
2020-09-16 13:46:04 +00:00
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/job"
"github.com/traefik/traefik/v2/pkg/log"
"github.com/traefik/traefik/v2/pkg/provider"
"github.com/traefik/traefik/v2/pkg/safe"
"github.com/traefik/traefik/v2/pkg/types"
"github.com/traefik/traefik/v2/pkg/version"
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 (
2020-08-21 09:12:04 +00:00
// DockerAPIVersion is a constant holding the version of the Provider API traefik will use.
2019-06-03 09:28:07 +00:00
DockerAPIVersion = "1.24"
2019-01-21 18:06:02 +00:00
// SwarmAPIVersion is a constant holding the version of the Provider API traefik will use.
2017-11-03 12:51:24 +00:00
SwarmAPIVersion = "1.24"
2016-08-05 14:02:46 +00:00
)
2016-04-08 12:20:54 +00:00
2019-06-03 09:28:07 +00:00
// DefaultTemplateRule The default template for the default rule.
const DefaultTemplateRule = "Host(`{{ normalize .Name }}`)"
2017-04-15 13:49:53 +00:00
var _ provider . Provider = ( * Provider ) ( nil )
2016-08-16 17:13:18 +00:00
2017-04-17 10:50:02 +00:00
// Provider holds configurations of the provider.
2017-04-15 13:49:53 +00:00
type Provider struct {
2019-07-01 09:30:05 +00:00
Constraints string ` description:"Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container." json:"constraints,omitempty" toml:"constraints,omitempty" yaml:"constraints,omitempty" export:"true" `
2020-03-04 15:48:05 +00:00
Watch bool ` description:"Watch Docker Swarm events." json:"watch,omitempty" toml:"watch,omitempty" yaml:"watch,omitempty" export:"true" `
2019-07-01 09:30:05 +00:00
Endpoint string ` description:"Docker server endpoint. Can be a tcp or a unix socket endpoint." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty" `
DefaultRule string ` description:"Default rule." json:"defaultRule,omitempty" toml:"defaultRule,omitempty" yaml:"defaultRule,omitempty" `
TLS * types . ClientTLS ` description:"Enable Docker TLS support." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true" `
ExposedByDefault bool ` description:"Expose containers by default." json:"exposedByDefault,omitempty" toml:"exposedByDefault,omitempty" yaml:"exposedByDefault,omitempty" export:"true" `
UseBindPortIP bool ` description:"Use the ip address from the bound port, rather than from the inner network." json:"useBindPortIP,omitempty" toml:"useBindPortIP,omitempty" yaml:"useBindPortIP,omitempty" export:"true" `
SwarmMode bool ` description:"Use Docker on Swarm Mode." json:"swarmMode,omitempty" toml:"swarmMode,omitempty" yaml:"swarmMode,omitempty" export:"true" `
Network string ` description:"Default Docker network used." json:"network,omitempty" toml:"network,omitempty" yaml:"network,omitempty" export:"true" `
2020-08-17 16:04:03 +00:00
SwarmModeRefreshSeconds ptypes . Duration ` description:"Polling interval for swarm mode." json:"swarmModeRefreshSeconds,omitempty" toml:"swarmModeRefreshSeconds,omitempty" yaml:"swarmModeRefreshSeconds,omitempty" export:"true" `
2020-08-28 08:02:03 +00:00
HTTPClientTimeout ptypes . Duration ` description:"Client timeout for HTTP connections." json:"httpClientTimeout,omitempty" toml:"httpClientTimeout,omitempty" yaml:"httpClientTimeout,omitempty" export:"true" `
2019-01-21 18:06:02 +00:00
defaultRuleTpl * template . Template
2016-08-05 14:02:46 +00:00
}
2019-06-17 09:48:05 +00:00
// SetDefaults sets the default values.
func ( p * Provider ) SetDefaults ( ) {
p . Watch = true
p . ExposedByDefault = true
p . Endpoint = "unix:///var/run/docker.sock"
p . SwarmMode = false
2020-08-17 16:04:03 +00:00
p . SwarmModeRefreshSeconds = ptypes . Duration ( 15 * time . Second )
2019-06-17 09:48:05 +00:00
p . DefaultRule = DefaultTemplateRule
}
2019-01-21 18:06:02 +00:00
// Init the provider.
2019-01-18 14:18:04 +00:00
func ( p * Provider ) Init ( ) error {
2019-01-21 18:06:02 +00:00
defaultRuleTpl , err := provider . MakeDefaultRuleTemplate ( p . DefaultRule , nil )
if err != nil {
2020-05-11 10:06:07 +00:00
return fmt . Errorf ( "error while parsing default rule: %w" , err )
2019-01-21 18:06:02 +00:00
}
p . defaultRuleTpl = defaultRuleTpl
2019-03-27 14:02:06 +00:00
return nil
2018-07-11 07:08:03 +00:00
}
2019-01-21 18:06:02 +00:00
// dockerData holds the need data to the provider.
2016-08-05 14:02:46 +00:00
type dockerData struct {
2019-01-18 14:18:04 +00:00
ID string
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
2017-10-25 18:20:03 +00:00
Node * dockertypes . ContainerNode
2019-01-18 14:18:04 +00:00
ExtraConf configuration
2016-08-05 14:02:46 +00:00
}
2019-01-21 18:06:02 +00:00
// NetworkSettings holds the networks data to the provider.
2016-08-05 14:02:46 +00:00
type networkSettings struct {
NetworkMode dockercontainertypes . NetworkMode
Ports nat . PortMap
Networks map [ string ] * networkData
}
2019-01-21 18:06:02 +00:00
// Network holds the network data to the provider.
2016-08-05 14:02:46 +00:00
type networkData struct {
Name string
Addr string
Port int
Protocol string
ID string
2015-11-20 15:05:06 +00:00
}
2017-12-04 10:40:03 +00:00
func ( p * Provider ) createClient ( ) ( client . APIClient , error ) {
2019-12-18 14:28:04 +00:00
opts , err := p . getClientOpts ( )
if err != nil {
return nil , err
}
httpHeaders := map [ string ] string {
"User-Agent" : "Traefik " + version . Version ,
}
opts = append ( opts , client . WithHTTPHeaders ( httpHeaders ) )
apiVersion := DockerAPIVersion
if p . SwarmMode {
apiVersion = SwarmAPIVersion
}
opts = append ( opts , client . WithVersion ( apiVersion ) )
return client . NewClientWithOpts ( opts ... )
}
func ( p * Provider ) getClientOpts ( ) ( [ ] client . Opt , error ) {
helper , err := connhelper . GetConnectionHelper ( p . Endpoint )
if err != nil {
return nil , err
}
// SSH
if helper != nil {
// https://github.com/docker/cli/blob/ebca1413117a3fcb81c89d6be226dcec74e5289f/cli/context/docker/load.go#L112-L123
httpClient := & http . Client {
Transport : & http . Transport {
DialContext : helper . Dialer ,
} ,
}
return [ ] client . Opt {
client . WithHTTPClient ( httpClient ) ,
2020-08-28 08:02:03 +00:00
client . WithTimeout ( time . Duration ( p . HTTPClientTimeout ) ) ,
2019-12-18 14:28:04 +00:00
client . WithHost ( helper . Host ) , // To avoid 400 Bad Request: malformed Host header daemon error
client . WithDialContext ( helper . Dialer ) ,
} , nil
}
opts := [ ] client . Opt {
client . WithHost ( p . Endpoint ) ,
2020-08-28 08:02:03 +00:00
client . WithTimeout ( time . Duration ( p . HTTPClientTimeout ) ) ,
2019-12-18 14:28:04 +00:00
}
2017-11-28 10:16:03 +00:00
2017-04-15 13:49:53 +00:00
if p . TLS != nil {
2019-01-18 14:18:04 +00:00
ctx := log . With ( context . Background ( ) , log . Str ( log . ProviderName , "docker" ) )
2019-12-18 14:28:04 +00:00
2019-01-18 14:18:04 +00:00
conf , err := p . TLS . CreateTLSConfig ( ctx )
2016-04-08 12:20:54 +00:00
if err != nil {
2021-10-26 08:54:11 +00:00
return nil , fmt . Errorf ( "unable to create client TLS configuration: %w" , err )
2016-04-08 12:20:54 +00:00
}
2018-03-23 12:30:03 +00:00
hostURL , err := client . ParseHostURL ( p . Endpoint )
2016-04-08 12:20:54 +00:00
if err != nil {
return nil , err
}
2019-12-18 14:28:04 +00:00
tr := & http . Transport {
TLSClientConfig : conf ,
2016-04-08 12:20:54 +00:00
}
2016-06-27 14:14:56 +00:00
2019-12-18 14:28:04 +00:00
if err := sockets . ConfigureTransport ( tr , hostURL . Scheme , hostURL . Host ) ; err != nil {
return nil , err
}
2017-11-28 10:16:03 +00:00
2020-08-28 08:02:03 +00:00
opts = append ( opts , client . WithHTTPClient ( & http . Client { Transport : tr , Timeout : time . Duration ( p . HTTPClientTimeout ) } ) )
2016-08-05 14:02:46 +00:00
}
2017-11-28 10:16:03 +00:00
2019-12-18 14:28:04 +00:00
return opts , nil
2016-04-08 12:20:54 +00:00
}
2019-01-21 18:06:02 +00:00
// Provide allows the docker provider to provide configurations to traefik using the given configuration channel.
2019-07-10 07:26:04 +00:00
func ( p * Provider ) Provide ( configurationChan chan <- dynamic . Message , pool * safe . Pool ) error {
2018-10-29 14:30:04 +00:00
pool . GoCtx ( func ( routineCtx context . Context ) {
2019-01-18 14:18:04 +00:00
ctxLog := log . With ( routineCtx , log . Str ( log . ProviderName , "docker" ) )
logger := log . FromContext ( ctxLog )
2016-02-25 17:30:13 +00:00
operation := func ( ) error {
var err error
2019-01-18 14:18:04 +00:00
ctx , cancel := context . WithCancel ( ctxLog )
2018-10-29 14:30:04 +00:00
defer cancel ( )
2019-01-18 14:18:04 +00:00
ctx = log . With ( ctx , log . Str ( log . ProviderName , "docker" ) )
2017-04-15 13:49:53 +00:00
dockerClient , err := p . createClient ( )
2016-02-25 17:30:13 +00:00
if err != nil {
2019-01-18 14:18:04 +00:00
logger . Errorf ( "Failed to create a client for docker, error: %s" , err )
2016-02-25 17:30:13 +00:00
return err
}
2016-06-08 17:39:38 +00:00
2017-11-28 10:16:03 +00:00
serverVersion , err := dockerClient . ServerVersion ( ctx )
2017-08-18 00:18:02 +00:00
if err != nil {
2019-01-18 14:18:04 +00:00
logger . Errorf ( "Failed to retrieve information of the docker client and server host: %s" , err )
2017-08-18 00:18:02 +00:00
return err
}
2019-01-18 14:18:04 +00:00
logger . Debugf ( "Provider connection established with docker %s (API %s)" , serverVersion . Version , serverVersion . APIVersion )
2016-08-05 14:02:46 +00:00
var dockerDataList [ ] dockerData
2017-04-15 13:49:53 +00:00
if p . SwarmMode {
2019-01-18 14:18:04 +00:00
dockerDataList , err = p . listServices ( ctx , dockerClient )
2016-08-05 14:02:46 +00:00
if err != nil {
2019-01-18 14:18:04 +00:00
logger . Errorf ( "Failed to list services for docker swarm mode, error %s" , err )
2016-08-05 14:02:46 +00:00
return err
}
} else {
2019-01-18 14:18:04 +00:00
dockerDataList , err = p . listContainers ( ctx , dockerClient )
2016-08-05 14:02:46 +00:00
if err != nil {
2019-01-18 14:18:04 +00:00
logger . Errorf ( "Failed to list containers for docker, error %s" , err )
2016-08-05 14:02:46 +00:00
return err
}
2016-02-25 17:30:13 +00:00
}
2016-08-05 14:02:46 +00:00
2019-01-18 14:18:04 +00:00
configuration := p . buildConfiguration ( ctxLog , dockerDataList )
2019-07-10 07:26:04 +00:00
configurationChan <- dynamic . Message {
2016-02-25 17:30:13 +00:00
ProviderName : "docker" ,
Configuration : configuration ,
}
2017-04-15 13:49:53 +00:00
if p . Watch {
if p . SwarmMode {
2017-05-02 15:20:20 +00:00
errChan := make ( chan error )
2019-09-26 09:00:06 +00:00
2016-08-05 14:02:46 +00:00
// TODO: This need to be change. Linked to Swarm events docker/docker#23827
2019-06-17 09:48:05 +00:00
ticker := time . NewTicker ( time . Duration ( p . SwarmModeRefreshSeconds ) )
2019-01-18 14:18:04 +00:00
2019-09-26 09:00:06 +00:00
pool . GoCtx ( func ( ctx context . Context ) {
2019-01-18 14:18:04 +00:00
ctx = log . With ( ctx , log . Str ( log . ProviderName , "docker" ) )
logger := log . FromContext ( ctx )
2017-05-02 15:20:20 +00:00
defer close ( errChan )
2016-08-05 14:02:46 +00:00
for {
select {
case <- ticker . C :
2019-01-18 14:18:04 +00:00
services , err := p . listServices ( ctx , dockerClient )
2016-08-05 14:02:46 +00:00
if err != nil {
2019-01-18 14:18:04 +00:00
logger . Errorf ( "Failed to list services for docker, error %s" , err )
2017-05-02 15:20:20 +00:00
errChan <- err
2016-08-05 14:02:46 +00:00
return
}
2019-01-18 14:18:04 +00:00
configuration := p . buildConfiguration ( ctx , services )
2016-08-05 14:02:46 +00:00
if configuration != nil {
2019-07-10 07:26:04 +00:00
configurationChan <- dynamic . Message {
2016-08-05 14:02:46 +00:00
ProviderName : "docker" ,
Configuration : configuration ,
}
}
2018-10-29 14:30:04 +00:00
case <- ctx . Done ( ) :
2016-08-05 14:02:46 +00:00
ticker . Stop ( )
return
}
}
} )
2017-05-02 15:20:20 +00:00
if err , ok := <- errChan ; ok {
return err
}
// channel closed
2016-08-05 14:02:46 +00:00
} else {
f := filters . NewArgs ( )
f . Add ( "type" , "container" )
options := dockertypes . EventsOptions {
Filters : f ,
}
2017-07-06 14:28:13 +00:00
2016-08-05 14:02:46 +00:00
startStopHandle := func ( m eventtypes . Message ) {
2019-01-18 14:18:04 +00:00
logger . Debugf ( "Provider event received %+v" , m )
containers , err := p . listContainers ( ctx , dockerClient )
2016-08-05 14:02:46 +00:00
if err != nil {
2019-01-18 14:18:04 +00:00
logger . Errorf ( "Failed to list containers for docker, error %s" , err )
2016-08-05 14:02:46 +00:00
// Call cancel to get out of the monitor
2016-06-16 20:49:57 +00:00
return
}
2019-01-18 14:18:04 +00:00
configuration := p . buildConfiguration ( ctx , containers )
2016-08-05 14:02:46 +00:00
if configuration != nil {
2019-07-10 07:26:04 +00:00
message := dynamic . Message {
2016-08-05 14:02:46 +00:00
ProviderName : "docker" ,
Configuration : configuration ,
}
2018-11-19 15:40:03 +00:00
select {
case configurationChan <- message :
case <- ctx . Done ( ) :
}
2015-09-10 20:54:37 +00:00
}
2015-09-10 07:06:37 +00:00
}
2017-07-06 14:28:13 +00:00
eventsc , errc := dockerClient . Events ( ctx , options )
2018-01-15 13:26:03 +00:00
for {
select {
case event := <- eventsc :
if event . Action == "start" ||
event . Action == "die" ||
strings . HasPrefix ( event . Action , "health_status" ) {
startStopHandle ( event )
}
case err := <- errc :
2020-11-06 08:26:03 +00:00
if errors . Is ( err , io . EOF ) {
2019-01-18 14:18:04 +00:00
logger . Debug ( "Provider event stream closed" )
2018-01-15 13:26:03 +00:00
}
return err
2018-10-29 14:30:04 +00:00
case <- ctx . Done ( ) :
return nil
2017-07-06 14:28:13 +00:00
}
}
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
}
2019-01-18 14:18:04 +00:00
2016-02-25 17:30:13 +00:00
notify := func ( err error , time time . Duration ) {
2019-01-18 14:18:04 +00:00
logger . Errorf ( "Provider connection error %+v, retrying in %s" , err , time )
2016-02-25 17:30:13 +00:00
}
2019-01-18 14:18:04 +00:00
err := backoff . RetryNotify ( safe . OperationWithRecover ( operation ) , backoff . WithContext ( job . NewBackOff ( backoff . NewExponentialBackOff ( ) ) , ctxLog ) , notify )
2016-02-25 17:30:13 +00:00
if err != nil {
2019-01-18 14:18:04 +00:00
logger . 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
}
2019-01-18 14:18:04 +00:00
func ( p * Provider ) 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 {
2017-12-02 18:26:44 +00:00
return nil , err
2016-04-08 12:20:54 +00:00
}
2015-10-23 07:49:19 +00:00
2019-01-18 14:18:04 +00:00
var inspectedContainers [ ] dockerData
2015-11-13 10:50:32 +00:00
// get inspect containers
for _ , container := range containerList {
2018-02-12 16:50:05 +00:00
dData := inspectContainers ( ctx , dockerClient , container . ID )
2019-01-18 14:18:04 +00:00
if len ( dData . Name ) == 0 {
continue
}
extraConf , err := p . getConfiguration ( dData )
if err != nil {
log . FromContext ( ctx ) . Errorf ( "Skip container %s: %v" , getServiceName ( dData ) , err )
continue
2016-04-08 12:20:54 +00:00
}
2019-01-18 14:18:04 +00:00
dData . ExtraConf = extraConf
inspectedContainers = append ( inspectedContainers , dData )
2015-10-23 07:49:19 +00:00
}
2019-01-18 14:18:04 +00:00
return inspectedContainers , nil
2015-09-12 13:10:03 +00:00
}
2016-05-31 21:23:23 +00:00
2018-02-12 16:50:05 +00:00
func inspectContainers ( ctx context . Context , dockerClient client . ContainerAPIClient , containerID string ) dockerData {
containerInspected , err := dockerClient . ContainerInspect ( ctx , containerID )
if err != nil {
2019-01-18 14:18:04 +00:00
log . FromContext ( ctx ) . Warnf ( "Failed to inspect container %s, error: %s" , containerID , err )
2019-03-04 15:40:05 +00:00
return dockerData { }
2018-02-12 16:50:05 +00:00
}
2019-03-04 15:40:05 +00:00
2020-09-16 13:46:04 +00:00
// This condition is here to avoid to have empty IP https://github.com/traefik/traefik/issues/2459
2019-03-04 15:40:05 +00:00
// We register only container which are running
if containerInspected . ContainerJSONBase != nil && containerInspected . ContainerJSONBase . State != nil && containerInspected . ContainerJSONBase . State . Running {
return parseContainer ( containerInspected )
}
return dockerData { }
2018-02-12 16:50:05 +00:00
}
2016-08-05 14:02:46 +00:00
func parseContainer ( container dockertypes . ContainerJSON ) dockerData {
2017-12-02 18:26:44 +00:00
dData := dockerData {
2016-08-05 14:02:46 +00:00
NetworkSettings : networkSettings { } ,
}
if container . ContainerJSONBase != nil {
2019-01-18 14:18:04 +00:00
dData . ID = container . ContainerJSONBase . ID
2017-12-02 18:26:44 +00:00
dData . Name = container . ContainerJSONBase . Name
2018-03-23 12:30:03 +00:00
dData . ServiceName = dData . Name // Default ServiceName to be the container's Name.
2017-12-02 18:26:44 +00:00
dData . Node = container . ContainerJSONBase . Node
2016-08-05 14:02:46 +00:00
if container . ContainerJSONBase . HostConfig != nil {
2017-12-02 18:26:44 +00:00
dData . NetworkSettings . NetworkMode = container . ContainerJSONBase . HostConfig . NetworkMode
2016-08-05 14:02:46 +00:00
}
2016-11-28 15:46:37 +00:00
if container . State != nil && container . State . Health != nil {
2017-12-02 18:26:44 +00:00
dData . Health = container . State . Health . Status
2016-11-28 15:46:37 +00:00
}
2016-08-05 14:02:46 +00:00
}
if container . Config != nil && container . Config . Labels != nil {
2017-12-02 18:26:44 +00:00
dData . Labels = container . Config . Labels
2016-08-05 14:02:46 +00:00
}
if container . NetworkSettings != nil {
if container . NetworkSettings . Ports != nil {
2017-12-02 18:26:44 +00:00
dData . NetworkSettings . Ports = container . NetworkSettings . Ports
2016-08-05 14:02:46 +00:00
}
if container . NetworkSettings . Networks != nil {
2017-12-02 18:26:44 +00:00
dData . NetworkSettings . Networks = make ( map [ string ] * networkData )
2016-08-05 14:02:46 +00:00
for name , containerNetwork := range container . NetworkSettings . Networks {
2017-12-02 18:26:44 +00:00
dData . NetworkSettings . Networks [ name ] = & networkData {
2016-08-05 14:02:46 +00:00
ID : containerNetwork . NetworkID ,
Name : name ,
Addr : containerNetwork . IPAddress ,
}
}
}
}
2017-12-02 18:26:44 +00:00
return dData
2016-05-31 21:23:23 +00:00
}
2016-08-05 14:02:46 +00:00
2019-01-18 14:18:04 +00:00
func ( p * Provider ) listServices ( ctx context . Context , dockerClient client . APIClient ) ( [ ] dockerData , error ) {
logger := log . FromContext ( ctx )
2016-08-05 14:02:46 +00:00
serviceList , err := dockerClient . ServiceList ( ctx , dockertypes . ServiceListOptions { } )
if err != nil {
2017-12-02 18:26:44 +00:00
return nil , err
2016-08-05 14:02:46 +00:00
}
2017-10-23 08:33:02 +00:00
serverVersion , err := dockerClient . ServerVersion ( ctx )
2017-12-02 18:26:44 +00:00
if err != nil {
return nil , err
}
2017-10-23 08:33:02 +00:00
2016-08-05 14:02:46 +00:00
networkListArgs := filters . NewArgs ( )
2017-10-23 08:33:02 +00:00
// https://docs.docker.com/engine/api/v1.29/#tag/Network (Docker 17.06)
if versions . GreaterThanOrEqualTo ( serverVersion . APIVersion , "1.29" ) {
networkListArgs . Add ( "scope" , "swarm" )
} else {
networkListArgs . Add ( "driver" , "overlay" )
}
2016-08-05 14:02:46 +00:00
networkList , err := dockerClient . NetworkList ( ctx , dockertypes . NetworkListOptions { Filters : networkListArgs } )
if err != nil {
2019-01-18 14:18:04 +00:00
logger . Debugf ( "Failed to network inspect on client for docker, error: %s" , err )
2017-12-02 18:26:44 +00:00
return nil , err
2016-08-05 14:02:46 +00:00
}
2017-12-02 18:26:44 +00:00
networkMap := make ( map [ string ] * dockertypes . NetworkResource )
2016-08-05 14:02:46 +00:00
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 {
2019-01-18 14:18:04 +00:00
dData , err := p . parseService ( ctx , service , networkMap )
if err != nil {
logger . Errorf ( "Skip container %s: %v" , getServiceName ( dData ) , err )
continue
}
2016-08-05 14:02:46 +00:00
2019-01-18 14:18:04 +00:00
if dData . ExtraConf . Docker . LBSwarm {
2018-02-07 14:24:44 +00:00
if len ( dData . NetworkSettings . Networks ) > 0 {
2017-12-02 18:26:44 +00:00
dockerDataList = append ( dockerDataList , dData )
2018-02-05 10:34:03 +00:00
}
} else {
isGlobalSvc := service . Spec . Mode . Global != nil
2018-02-07 14:24:44 +00:00
dockerDataListTasks , err = listTasks ( ctx , dockerClient , service . ID , dData , networkMap , isGlobalSvc )
if err != nil {
2019-01-18 14:18:04 +00:00
logger . Warn ( err )
2017-12-01 13:34:03 +00:00
} else {
2018-02-07 14:24:44 +00:00
dockerDataList = append ( dockerDataList , dockerDataListTasks ... )
2017-01-07 08:20:52 +00:00
}
}
2016-08-05 14:02:46 +00:00
}
return dockerDataList , err
}
2019-01-18 14:18:04 +00:00
func ( p * Provider ) parseService ( ctx context . Context , service swarmtypes . Service , networkMap map [ string ] * dockertypes . NetworkResource ) ( dockerData , error ) {
logger := log . FromContext ( ctx )
2017-12-02 18:26:44 +00:00
dData := dockerData {
2019-01-18 14:18:04 +00:00
ID : service . ID ,
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 { } ,
}
2019-01-18 14:18:04 +00:00
extraConf , err := p . getConfiguration ( dData )
if err != nil {
return dockerData { } , err
}
dData . ExtraConf = extraConf
2016-08-05 14:02:46 +00:00
if service . Spec . EndpointSpec != nil {
2017-12-01 13:34:03 +00:00
if service . Spec . EndpointSpec . Mode == swarmtypes . ResolutionModeDNSRR {
2019-01-18 14:18:04 +00:00
if dData . ExtraConf . Docker . LBSwarm {
logger . Warnf ( "Ignored %s endpoint-mode not supported, service name: %s. Fallback to Traefik load balancing" , swarmtypes . ResolutionModeDNSRR , service . Spec . Annotations . Name )
2018-02-05 10:34:03 +00:00
}
2017-12-01 13:34:03 +00:00
} else if service . Spec . EndpointSpec . Mode == swarmtypes . ResolutionModeVIP {
2017-12-02 18:26:44 +00:00
dData . NetworkSettings . Networks = make ( map [ string ] * networkData )
2016-08-05 14:02:46 +00:00
for _ , virtualIP := range service . Endpoint . VirtualIPs {
networkService := networkMap [ virtualIP . NetworkID ]
if networkService != nil {
2018-02-12 16:50:05 +00:00
if len ( virtualIP . Addr ) > 0 {
ip , _ , _ := net . ParseCIDR ( virtualIP . Addr )
network := & networkData {
Name : networkService . Name ,
ID : virtualIP . NetworkID ,
Addr : ip . String ( ) ,
}
dData . NetworkSettings . Networks [ network . Name ] = network
} else {
2019-01-18 14:18:04 +00:00
logger . Debugf ( "No virtual IPs found in network %s" , virtualIP . NetworkID )
2016-08-05 14:02:46 +00:00
}
} else {
2019-01-18 14:18:04 +00:00
logger . Debugf ( "Network not found, id: %s" , virtualIP . NetworkID )
2016-08-05 14:02:46 +00:00
}
2017-01-07 08:20:52 +00:00
}
}
}
2019-01-18 14:18:04 +00:00
return dData , nil
2017-01-07 08:20:52 +00:00
}
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
2017-12-02 18:26:44 +00:00
taskList , err := dockerClient . TaskList ( ctx , dockertypes . TaskListOptions { Filters : serviceIDFilter } )
2017-01-07 08:20:52 +00:00
if err != nil {
2017-12-02 18:26:44 +00:00
return nil , err
2017-01-07 08:20:52 +00:00
}
2017-12-02 18:26:44 +00:00
var dockerDataList [ ] dockerData
2017-01-07 08:20:52 +00:00
for _ , task := range taskList {
2017-07-06 14:28:13 +00:00
if task . Status . State != swarmtypes . TaskStateRunning {
2017-03-16 14:38:40 +00:00
continue
}
2019-01-18 14:18:04 +00:00
dData := parseTasks ( ctx , task , serviceDockerData , networkMap , isGlobalSvc )
2018-02-12 16:50:05 +00:00
if len ( dData . NetworkSettings . Networks ) > 0 {
dockerDataList = append ( dockerDataList , dData )
}
2017-01-07 08:20:52 +00:00
}
return dockerDataList , err
}
2016-08-05 14:02:46 +00:00
2019-01-18 14:18:04 +00:00
func parseTasks ( ctx context . Context , task swarmtypes . Task , serviceDockerData dockerData ,
2018-02-12 16:50:05 +00:00
networkMap map [ string ] * dockertypes . NetworkResource , isGlobalSvc bool ) dockerData {
2017-12-02 18:26:44 +00:00
dData := dockerData {
2019-01-18 14:18:04 +00:00
ID : task . ID ,
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 ,
2019-01-18 14:18:04 +00:00
ExtraConf : serviceDockerData . ExtraConf ,
2017-01-07 08:20:52 +00:00
NetworkSettings : networkSettings { } ,
}
2017-08-18 00:18:02 +00:00
if isGlobalSvc {
2017-12-02 18:26:44 +00:00
dData . Name = serviceDockerData . Name + "." + task . ID
2017-02-16 16:50:41 +00:00
}
2017-01-07 08:20:52 +00:00
if task . NetworksAttachments != nil {
2017-12-02 18:26:44 +00:00
dData . NetworkSettings . Networks = make ( map [ string ] * networkData )
2017-01-07 08:20:52 +00:00
for _ , virtualIP := range task . NetworksAttachments {
if networkService , present := networkMap [ virtualIP . Network . ID ] ; present {
2018-02-12 16:50:05 +00:00
if len ( virtualIP . Addresses ) > 0 {
// 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 ( ) ,
}
dData . NetworkSettings . Networks [ network . Name ] = network
2017-01-07 08:20:52 +00:00
}
2018-02-12 16:50:05 +00:00
} else {
2019-01-18 14:18:04 +00:00
log . FromContext ( ctx ) . Debugf ( "No IP addresses found for network %s" , virtualIP . Network . ID )
2017-01-07 08:20:52 +00:00
}
2016-08-05 14:02:46 +00:00
}
}
}
2017-12-02 18:26:44 +00:00
return dData
2016-08-05 14:02:46 +00:00
}