2019-01-18 14:18:04 +00:00
package docker
import (
"context"
"errors"
"fmt"
"net"
"strings"
2022-07-06 08:24:08 +00:00
dockertypes "github.com/docker/docker/api/types"
2019-01-18 14:18:04 +00:00
"github.com/docker/go-connections/nat"
2022-11-21 17:36:05 +00:00
"github.com/rs/zerolog/log"
2023-02-03 14:24:05 +00:00
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/config/label"
"github.com/traefik/traefik/v3/pkg/logs"
"github.com/traefik/traefik/v3/pkg/provider"
"github.com/traefik/traefik/v3/pkg/provider/constraints"
2019-01-18 14:18:04 +00:00
)
2019-07-10 07:26:04 +00:00
func ( p * Provider ) buildConfiguration ( ctx context . Context , containersInspected [ ] dockerData ) * dynamic . Configuration {
configurations := make ( map [ string ] * dynamic . Configuration )
2019-01-18 14:18:04 +00:00
for _ , container := range containersInspected {
containerName := getServiceName ( container ) + "-" + container . ID
2022-11-21 17:36:05 +00:00
logger := log . Ctx ( ctx ) . With ( ) . Str ( "container" , containerName ) . Logger ( )
ctxContainer := logger . WithContext ( ctx )
2019-01-18 14:18:04 +00:00
if ! p . keepContainer ( ctxContainer , container ) {
continue
}
confFromLabel , err := label . DecodeConfiguration ( container . Labels )
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) . Send ( )
2019-01-18 14:18:04 +00:00
continue
}
2020-02-20 21:24:05 +00:00
var tcpOrUDP bool
2019-03-21 14:22:06 +00:00
if len ( confFromLabel . TCP . Routers ) > 0 || len ( confFromLabel . TCP . Services ) > 0 {
2020-02-20 21:24:05 +00:00
tcpOrUDP = true
2019-03-21 14:22:06 +00:00
err := p . buildTCPServiceConfiguration ( ctxContainer , container , confFromLabel . TCP )
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) . Send ( )
2019-03-21 14:22:06 +00:00
continue
}
provider . BuildTCPRouterConfiguration ( ctxContainer , confFromLabel . TCP )
2020-02-20 21:24:05 +00:00
}
if len ( confFromLabel . UDP . Routers ) > 0 || len ( confFromLabel . UDP . Services ) > 0 {
tcpOrUDP = true
err := p . buildUDPServiceConfiguration ( ctxContainer , container , confFromLabel . UDP )
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) . Send ( )
2019-03-21 14:22:06 +00:00
continue
}
2020-02-20 21:24:05 +00:00
provider . BuildUDPRouterConfiguration ( ctxContainer , confFromLabel . UDP )
}
if tcpOrUDP && len ( confFromLabel . HTTP . Routers ) == 0 &&
len ( confFromLabel . HTTP . Middlewares ) == 0 &&
len ( confFromLabel . HTTP . Services ) == 0 {
configurations [ containerName ] = confFromLabel
continue
2019-03-21 14:22:06 +00:00
}
2019-03-14 08:30:04 +00:00
err = p . buildServiceConfiguration ( ctxContainer , container , confFromLabel . HTTP )
2019-01-18 14:18:04 +00:00
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) . Send ( )
2019-01-18 14:18:04 +00:00
continue
}
2019-01-21 18:06:02 +00:00
serviceName := getServiceName ( container )
model := struct {
2023-03-20 16:42:06 +00:00
Name string
ContainerName string
Labels map [ string ] string
2019-01-21 18:06:02 +00:00
} {
2023-03-20 16:42:06 +00:00
Name : serviceName ,
ContainerName : strings . TrimPrefix ( container . Name , "/" ) ,
Labels : container . Labels ,
2019-01-21 18:06:02 +00:00
}
2019-03-14 08:30:04 +00:00
provider . BuildRouterConfiguration ( ctx , confFromLabel . HTTP , serviceName , p . defaultRuleTpl , model )
2019-01-18 14:18:04 +00:00
configurations [ containerName ] = confFromLabel
}
return provider . Merge ( ctx , configurations )
}
2019-07-10 07:26:04 +00:00
func ( p * Provider ) buildTCPServiceConfiguration ( ctx context . Context , container dockerData , configuration * dynamic . TCPConfiguration ) error {
2019-03-21 14:22:06 +00:00
serviceName := getServiceName ( container )
if len ( configuration . Services ) == 0 {
2022-12-09 08:58:05 +00:00
configuration . Services = map [ string ] * dynamic . TCPService {
serviceName : {
LoadBalancer : new ( dynamic . TCPServersLoadBalancer ) ,
} ,
2019-03-21 14:22:06 +00:00
}
}
2022-07-06 08:24:08 +00:00
if container . Health != "" && container . Health != dockertypes . Healthy {
return nil
}
2019-09-13 17:28:04 +00:00
for name , service := range configuration . Services {
2022-11-21 17:36:05 +00:00
ctx := log . Ctx ( ctx ) . With ( ) . Str ( logs . ServiceName , name ) . Logger ( ) . WithContext ( ctx )
2022-07-06 08:24:08 +00:00
if err := p . addServerTCP ( ctx , container , service . LoadBalancer ) ; err != nil {
2020-01-20 14:28:06 +00:00
return fmt . Errorf ( "service %q error: %w" , name , err )
2019-03-21 14:22:06 +00:00
}
}
return nil
}
2020-02-20 21:24:05 +00:00
func ( p * Provider ) buildUDPServiceConfiguration ( ctx context . Context , container dockerData , configuration * dynamic . UDPConfiguration ) error {
serviceName := getServiceName ( container )
if len ( configuration . Services ) == 0 {
configuration . Services = make ( map [ string ] * dynamic . UDPService )
configuration . Services [ serviceName ] = & dynamic . UDPService {
2022-07-06 08:24:08 +00:00
LoadBalancer : & dynamic . UDPServersLoadBalancer { } ,
2020-02-20 21:24:05 +00:00
}
}
2022-07-06 08:24:08 +00:00
if container . Health != "" && container . Health != dockertypes . Healthy {
return nil
}
2020-02-20 21:24:05 +00:00
for name , service := range configuration . Services {
2022-11-21 17:36:05 +00:00
ctx := log . Ctx ( ctx ) . With ( ) . Str ( logs . ServiceName , name ) . Logger ( ) . WithContext ( ctx )
2022-07-06 08:24:08 +00:00
if err := p . addServerUDP ( ctx , container , service . LoadBalancer ) ; err != nil {
2020-02-20 21:24:05 +00:00
return fmt . Errorf ( "service %q error: %w" , name , err )
}
}
return nil
}
2019-07-10 07:26:04 +00:00
func ( p * Provider ) buildServiceConfiguration ( ctx context . Context , container dockerData , configuration * dynamic . HTTPConfiguration ) error {
2019-01-18 14:18:04 +00:00
serviceName := getServiceName ( container )
if len ( configuration . Services ) == 0 {
2019-07-10 07:26:04 +00:00
configuration . Services = make ( map [ string ] * dynamic . Service )
2019-08-26 08:30:05 +00:00
lb := & dynamic . ServersLoadBalancer { }
2019-01-18 14:18:04 +00:00
lb . SetDefaults ( )
2019-07-10 07:26:04 +00:00
configuration . Services [ serviceName ] = & dynamic . Service {
2019-01-18 14:18:04 +00:00
LoadBalancer : lb ,
}
}
2022-07-06 08:24:08 +00:00
if container . Health != "" && container . Health != dockertypes . Healthy {
return nil
}
2019-09-13 17:28:04 +00:00
for name , service := range configuration . Services {
2022-11-21 17:36:05 +00:00
ctx := log . Ctx ( ctx ) . With ( ) . Str ( logs . ServiceName , name ) . Logger ( ) . WithContext ( ctx )
2022-07-06 08:24:08 +00:00
if err := p . addServer ( ctx , container , service . LoadBalancer ) ; err != nil {
2020-01-20 14:28:06 +00:00
return fmt . Errorf ( "service %q error: %w" , name , err )
2019-01-18 14:18:04 +00:00
}
}
return nil
}
func ( p * Provider ) keepContainer ( ctx context . Context , container dockerData ) bool {
2022-11-21 17:36:05 +00:00
logger := log . Ctx ( ctx )
2019-01-18 14:18:04 +00:00
if ! container . ExtraConf . Enable {
2022-11-21 17:36:05 +00:00
logger . Debug ( ) . Msg ( "Filtering disabled container" )
2019-01-18 14:18:04 +00:00
return false
}
2019-11-29 16:16:05 +00:00
matches , err := constraints . MatchLabels ( container . Labels , p . Constraints )
2019-06-21 07:24:04 +00:00
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) . Msg ( "Error matching constraints expression" )
2019-06-21 07:24:04 +00:00
return false
}
if ! matches {
2022-11-21 17:36:05 +00:00
logger . Debug ( ) . Msgf ( "Container pruned by constraint expression: %q" , p . Constraints )
2019-01-18 14:18:04 +00:00
return false
}
2022-07-06 08:24:08 +00:00
if ! p . AllowEmptyServices && container . Health != "" && container . Health != dockertypes . Healthy {
2022-11-21 17:36:05 +00:00
logger . Debug ( ) . Msg ( "Filtering unhealthy or starting container" )
2019-01-18 14:18:04 +00:00
return false
}
return true
}
2019-09-13 18:00:06 +00:00
func ( p * Provider ) addServerTCP ( ctx context . Context , container dockerData , loadBalancer * dynamic . TCPServersLoadBalancer ) error {
2020-01-20 14:28:06 +00:00
if loadBalancer == nil {
return errors . New ( "load-balancer is not defined" )
}
2022-09-21 12:54:08 +00:00
if len ( loadBalancer . Servers ) == 0 {
loadBalancer . Servers = [ ] dynamic . TCPServer { { } }
2019-03-21 14:22:06 +00:00
}
2020-01-20 14:56:05 +00:00
2022-09-21 12:54:08 +00:00
serverPort := loadBalancer . Servers [ 0 ] . Port
loadBalancer . Servers [ 0 ] . Port = ""
2019-03-21 14:22:06 +00:00
ip , port , err := p . getIPPort ( ctx , container , serverPort )
if err != nil {
return err
}
if port == "" {
return errors . New ( "port is missing" )
}
loadBalancer . Servers [ 0 ] . Address = net . JoinHostPort ( ip , port )
2022-09-21 12:54:08 +00:00
2019-03-21 14:22:06 +00:00
return nil
}
2020-02-20 21:24:05 +00:00
func ( p * Provider ) addServerUDP ( ctx context . Context , container dockerData , loadBalancer * dynamic . UDPServersLoadBalancer ) error {
if loadBalancer == nil {
return errors . New ( "load-balancer is not defined" )
}
2022-09-21 12:54:08 +00:00
if len ( loadBalancer . Servers ) == 0 {
loadBalancer . Servers = [ ] dynamic . UDPServer { { } }
2020-02-20 21:24:05 +00:00
}
2022-09-21 12:54:08 +00:00
serverPort := loadBalancer . Servers [ 0 ] . Port
loadBalancer . Servers [ 0 ] . Port = ""
2020-02-20 21:24:05 +00:00
ip , port , err := p . getIPPort ( ctx , container , serverPort )
if err != nil {
return err
}
if port == "" {
return errors . New ( "port is missing" )
}
loadBalancer . Servers [ 0 ] . Address = net . JoinHostPort ( ip , port )
2022-09-21 12:54:08 +00:00
2020-02-20 21:24:05 +00:00
return nil
}
2019-08-26 08:30:05 +00:00
func ( p * Provider ) addServer ( ctx context . Context , container dockerData , loadBalancer * dynamic . ServersLoadBalancer ) error {
2020-01-20 14:28:06 +00:00
if loadBalancer == nil {
return errors . New ( "load-balancer is not defined" )
}
2019-01-18 14:18:04 +00:00
if len ( loadBalancer . Servers ) == 0 {
2019-07-10 07:26:04 +00:00
server := dynamic . Server { }
2019-01-18 14:18:04 +00:00
server . SetDefaults ( )
2019-07-10 07:26:04 +00:00
loadBalancer . Servers = [ ] dynamic . Server { server }
2019-01-18 14:18:04 +00:00
}
2022-09-21 12:54:08 +00:00
serverPort := loadBalancer . Servers [ 0 ] . Port
loadBalancer . Servers [ 0 ] . Port = ""
ip , port , err := p . getIPPort ( ctx , container , serverPort )
if err != nil {
return err
}
2019-01-18 14:18:04 +00:00
if port == "" {
return errors . New ( "port is missing" )
}
loadBalancer . Servers [ 0 ] . URL = fmt . Sprintf ( "%s://%s" , loadBalancer . Servers [ 0 ] . Scheme , net . JoinHostPort ( ip , port ) )
loadBalancer . Servers [ 0 ] . Scheme = ""
return nil
}
func ( p * Provider ) getIPPort ( ctx context . Context , container dockerData , serverPort string ) ( string , string , error ) {
2022-11-21 17:36:05 +00:00
logger := log . Ctx ( ctx )
2019-01-18 14:18:04 +00:00
var ip , port string
usedBound := false
if p . UseBindPortIP {
portBinding , err := p . getPortBinding ( container , serverPort )
2019-02-05 16:10:03 +00:00
switch {
case err != nil :
2022-11-21 17:36:05 +00:00
logger . Info ( ) . Msgf ( "Unable to find a binding for container %q, falling back on its internal IP/Port." , container . Name )
2019-02-05 16:10:03 +00:00
case portBinding . HostIP == "0.0.0.0" || len ( portBinding . HostIP ) == 0 :
2022-11-21 17:36:05 +00:00
logger . Info ( ) . Msgf ( "Cannot determine the IP address (got %q) for %q's binding, falling back on its internal IP/Port." , portBinding . HostIP , container . Name )
2019-02-05 16:10:03 +00:00
default :
2019-01-18 14:18:04 +00:00
ip = portBinding . HostIP
port = portBinding . HostPort
usedBound = true
}
}
if ! usedBound {
ip = p . getIPAddress ( ctx , container )
port = getPort ( container , serverPort )
}
if len ( ip ) == 0 {
return "" , "" , fmt . Errorf ( "unable to find the IP address for the container %q: the server is ignored" , container . Name )
}
return ip , port , nil
}
func ( p Provider ) getIPAddress ( ctx context . Context , container dockerData ) string {
2022-11-21 17:36:05 +00:00
logger := log . Ctx ( ctx )
2019-01-18 14:18:04 +00:00
2023-03-24 00:26:07 +00:00
netNotFound := false
2019-01-18 14:18:04 +00:00
if container . ExtraConf . Docker . Network != "" {
2019-02-05 16:10:03 +00:00
settings := container . NetworkSettings
if settings . Networks != nil {
network := settings . Networks [ container . ExtraConf . Docker . Network ]
2019-01-18 14:18:04 +00:00
if network != nil {
return network . Addr
}
2023-03-24 00:26:07 +00:00
netNotFound = true
2023-04-17 09:34:00 +00:00
logger . Warn ( ) . Msgf ( "Could not find network named %q for container %q. Maybe you're missing the project's prefix in the label?" , container . ExtraConf . Docker . Network , container . Name )
2019-01-18 14:18:04 +00:00
}
}
if container . NetworkSettings . NetworkMode . IsHost ( ) {
if container . Node != nil && container . Node . IPAddress != "" {
return container . Node . IPAddress
}
2020-02-11 10:56:05 +00:00
if host , err := net . LookupHost ( "host.docker.internal" ) ; err == nil {
return host [ 0 ]
}
2022-07-19 14:22:08 +00:00
if host , err := net . LookupHost ( "host.containers.internal" ) ; err == nil {
return host [ 0 ]
}
2019-01-18 14:18:04 +00:00
return "127.0.0.1"
}
if container . NetworkSettings . NetworkMode . IsContainer ( ) {
dockerClient , err := p . createClient ( )
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Warn ( ) . Err ( err ) . Msg ( "Unable to get IP address" )
2019-01-18 14:18:04 +00:00
return ""
}
connectedContainer := container . NetworkSettings . NetworkMode . ConnectedContainer ( )
containerInspected , err := dockerClient . ContainerInspect ( context . Background ( ) , connectedContainer )
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Warn ( ) . Err ( err ) . Msgf ( "Unable to get IP address for container %s: failed to inspect container ID %s" , container . Name , connectedContainer )
2019-01-18 14:18:04 +00:00
return ""
}
2020-05-28 17:58:04 +00:00
// Check connected container for traefik.docker.network, falling back to
// the network specified on the current container.
containerParsed := parseContainer ( containerInspected )
extraConf , err := p . getConfiguration ( containerParsed )
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Warn ( ) . Err ( err ) . Msgf ( "Unable to get IP address for container %s : failed to get extra configuration for container %s" , container . Name , containerInspected . Name )
2020-05-28 17:58:04 +00:00
return ""
}
if extraConf . Docker . Network == "" {
extraConf . Docker . Network = container . ExtraConf . Docker . Network
}
containerParsed . ExtraConf = extraConf
return p . getIPAddress ( ctx , containerParsed )
2019-01-18 14:18:04 +00:00
}
for _ , network := range container . NetworkSettings . Networks {
2023-03-24 00:26:07 +00:00
if netNotFound {
2023-04-17 09:34:00 +00:00
logger . Warn ( ) . Msgf ( "Defaulting to first available network (%q) for container %q." , network , container . Name )
2023-03-24 00:26:07 +00:00
}
2019-01-18 14:18:04 +00:00
return network . Addr
}
2022-11-21 17:36:05 +00:00
logger . Warn ( ) . Msg ( "Unable to find the IP address." )
2019-01-18 14:18:04 +00:00
return ""
}
func ( p * Provider ) getPortBinding ( container dockerData , serverPort string ) ( * nat . PortBinding , error ) {
port := getPort ( container , serverPort )
for netPort , portBindings := range container . NetworkSettings . Ports {
if strings . EqualFold ( string ( netPort ) , port + "/TCP" ) || strings . EqualFold ( string ( netPort ) , port + "/UDP" ) {
for _ , p := range portBindings {
return & p , nil
}
}
}
return nil , fmt . Errorf ( "unable to find the external IP:Port for the container %q" , container . Name )
}
func getPort ( container dockerData , serverPort string ) string {
if len ( serverPort ) > 0 {
return serverPort
}
var ports [ ] nat . Port
for port := range container . NetworkSettings . Ports {
ports = append ( ports , port )
}
less := func ( i , j nat . Port ) bool {
return i . Int ( ) < j . Int ( )
}
nat . Sort ( ports , less )
if len ( ports ) > 0 {
min := ports [ 0 ]
return min . Port ( )
}
return ""
}
func getServiceName ( container dockerData ) string {
serviceName := container . ServiceName
if values , err := getStringMultipleStrict ( container . Labels , labelDockerComposeProject , labelDockerComposeService ) ; err == nil {
serviceName = values [ labelDockerComposeService ] + "_" + values [ labelDockerComposeProject ]
}
2019-09-26 10:26:05 +00:00
return provider . Normalize ( serviceName )
2019-01-18 14:18:04 +00:00
}