Support systemd socket-activation
Co-authored-by: Michael <michael.matur@gmail.com>
This commit is contained in:
parent
9e0800f938
commit
b7de043991
6 changed files with 91 additions and 20 deletions
|
@ -1175,3 +1175,25 @@ entryPoints:
|
||||||
```
|
```
|
||||||
|
|
||||||
{!traefik-for-business-applications.md!}
|
{!traefik-for-business-applications.md!}
|
||||||
|
|
||||||
|
## Systemd Socket Activation
|
||||||
|
|
||||||
|
Traefik supports [systemd socket activation](https://www.freedesktop.org/software/systemd/man/latest/systemd-socket-activate.html).
|
||||||
|
|
||||||
|
When a socket activation file descriptor name matches an EntryPoint name, the corresponding file descriptor will be used as the TCP listener for the matching EntryPoint.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemd-socket-activate -l 80 -l 443 --fdname web:websecure ./traefik --entrypoints.web --entrypoints.websecure
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! warning "EntryPoint Address"
|
||||||
|
|
||||||
|
When a socket activation file descriptor name matches an EntryPoint name its address configuration is ignored.
|
||||||
|
|
||||||
|
!!! warning "TCP Only"
|
||||||
|
|
||||||
|
Socket activation is not yet supported with UDP entryPoints.
|
||||||
|
|
||||||
|
!!! warning "Docker Support"
|
||||||
|
|
||||||
|
Socket activation is not supported by Docker but works with Podman containers.
|
||||||
|
|
|
@ -48,8 +48,15 @@ const (
|
||||||
var (
|
var (
|
||||||
clientConnectionStates = map[string]*connState{}
|
clientConnectionStates = map[string]*connState{}
|
||||||
clientConnectionStatesMu = sync.RWMutex{}
|
clientConnectionStatesMu = sync.RWMutex{}
|
||||||
|
|
||||||
|
socketActivationListeners map[string]net.Listener
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Populates pre-defined socketActivationListeners by socket activation.
|
||||||
|
populateSocketActivationListeners()
|
||||||
|
}
|
||||||
|
|
||||||
type connState struct {
|
type connState struct {
|
||||||
State string
|
State string
|
||||||
KeepAliveState string
|
KeepAliveState string
|
||||||
|
@ -96,6 +103,7 @@ func NewTCPEntryPoints(entryPointsConfig static.EntryPoints, hostResolverConfig
|
||||||
return clientConnectionStates
|
return clientConnectionStates
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
serverEntryPointsTCP := make(TCPEntryPoints)
|
serverEntryPointsTCP := make(TCPEntryPoints)
|
||||||
for entryPointName, config := range entryPointsConfig {
|
for entryPointName, config := range entryPointsConfig {
|
||||||
protocol, err := config.GetProtocol()
|
protocol, err := config.GetProtocol()
|
||||||
|
@ -113,7 +121,7 @@ func NewTCPEntryPoints(entryPointsConfig static.EntryPoints, hostResolverConfig
|
||||||
OpenConnectionsGauge().
|
OpenConnectionsGauge().
|
||||||
With("entrypoint", entryPointName, "protocol", "TCP")
|
With("entrypoint", entryPointName, "protocol", "TCP")
|
||||||
|
|
||||||
serverEntryPointsTCP[entryPointName], err = NewTCPEntryPoint(ctx, config, hostResolverConfig, openConnectionsGauge)
|
serverEntryPointsTCP[entryPointName], err = NewTCPEntryPoint(ctx, entryPointName, config, hostResolverConfig, openConnectionsGauge)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error while building entryPoint %s: %w", entryPointName, err)
|
return nil, fmt.Errorf("error while building entryPoint %s: %w", entryPointName, err)
|
||||||
}
|
}
|
||||||
|
@ -169,10 +177,10 @@ type TCPEntryPoint struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTCPEntryPoint creates a new TCPEntryPoint.
|
// NewTCPEntryPoint creates a new TCPEntryPoint.
|
||||||
func NewTCPEntryPoint(ctx context.Context, configuration *static.EntryPoint, hostResolverConfig *types.HostResolverConfig, openConnectionsGauge gokitmetrics.Gauge) (*TCPEntryPoint, error) {
|
func NewTCPEntryPoint(ctx context.Context, name string, config *static.EntryPoint, hostResolverConfig *types.HostResolverConfig, openConnectionsGauge gokitmetrics.Gauge) (*TCPEntryPoint, error) {
|
||||||
tracker := newConnectionTracker(openConnectionsGauge)
|
tracker := newConnectionTracker(openConnectionsGauge)
|
||||||
|
|
||||||
listener, err := buildListener(ctx, configuration)
|
listener, err := buildListener(ctx, name, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error preparing server: %w", err)
|
return nil, fmt.Errorf("error preparing server: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -181,19 +189,19 @@ func NewTCPEntryPoint(ctx context.Context, configuration *static.EntryPoint, hos
|
||||||
|
|
||||||
reqDecorator := requestdecorator.New(hostResolverConfig)
|
reqDecorator := requestdecorator.New(hostResolverConfig)
|
||||||
|
|
||||||
httpServer, err := createHTTPServer(ctx, listener, configuration, true, reqDecorator)
|
httpServer, err := createHTTPServer(ctx, listener, config, true, reqDecorator)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error preparing http server: %w", err)
|
return nil, fmt.Errorf("error preparing http server: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rt.SetHTTPForwarder(httpServer.Forwarder)
|
rt.SetHTTPForwarder(httpServer.Forwarder)
|
||||||
|
|
||||||
httpsServer, err := createHTTPServer(ctx, listener, configuration, false, reqDecorator)
|
httpsServer, err := createHTTPServer(ctx, listener, config, false, reqDecorator)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error preparing https server: %w", err)
|
return nil, fmt.Errorf("error preparing https server: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
h3Server, err := newHTTP3Server(ctx, configuration, httpsServer)
|
h3Server, err := newHTTP3Server(ctx, config, httpsServer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error preparing http3 server: %w", err)
|
return nil, fmt.Errorf("error preparing http3 server: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -206,7 +214,7 @@ func NewTCPEntryPoint(ctx context.Context, configuration *static.EntryPoint, hos
|
||||||
return &TCPEntryPoint{
|
return &TCPEntryPoint{
|
||||||
listener: listener,
|
listener: listener,
|
||||||
switcher: tcpSwitcher,
|
switcher: tcpSwitcher,
|
||||||
transportConfiguration: configuration.Transport,
|
transportConfiguration: config.Transport,
|
||||||
tracker: tracker,
|
tracker: tracker,
|
||||||
httpServer: httpServer,
|
httpServer: httpServer,
|
||||||
httpsServer: httpsServer,
|
httpsServer: httpsServer,
|
||||||
|
@ -460,17 +468,29 @@ func buildProxyProtocolListener(ctx context.Context, entryPoint *static.EntryPoi
|
||||||
return proxyListener, nil
|
return proxyListener, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildListener(ctx context.Context, entryPoint *static.EntryPoint) (net.Listener, error) {
|
func buildListener(ctx context.Context, name string, config *static.EntryPoint) (net.Listener, error) {
|
||||||
listenConfig := newListenConfig(entryPoint)
|
var listener net.Listener
|
||||||
listener, err := listenConfig.Listen(ctx, "tcp", entryPoint.GetAddress())
|
var err error
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error opening listener: %w", err)
|
// if we have predefined listener from socket activation
|
||||||
|
if ln, ok := socketActivationListeners[name]; ok {
|
||||||
|
listener = ln
|
||||||
|
} else {
|
||||||
|
if len(socketActivationListeners) > 0 {
|
||||||
|
log.Warn().Str("name", name).Msg("Unable to find socket activation listener for entryPoint")
|
||||||
|
}
|
||||||
|
|
||||||
|
listenConfig := newListenConfig(config)
|
||||||
|
listener, err = listenConfig.Listen(ctx, "tcp", config.GetAddress())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error opening listener: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
listener = tcpKeepAliveListener{listener.(*net.TCPListener)}
|
listener = tcpKeepAliveListener{listener.(*net.TCPListener)}
|
||||||
|
|
||||||
if entryPoint.ProxyProtocol != nil {
|
if config.ProxyProtocol != nil {
|
||||||
listener, err = buildProxyProtocolListener(ctx, entryPoint, listener)
|
listener, err = buildProxyProtocolListener(ctx, config, listener)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error creating proxy protocol listener: %w", err)
|
return nil, fmt.Errorf("error creating proxy protocol listener: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,7 +85,7 @@ func TestHTTP3AdvertisedPort(t *testing.T) {
|
||||||
epConfig := &static.EntryPointsTransport{}
|
epConfig := &static.EntryPointsTransport{}
|
||||||
epConfig.SetDefaults()
|
epConfig.SetDefaults()
|
||||||
|
|
||||||
entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{
|
entryPoint, err := NewTCPEntryPoint(context.Background(), "", &static.EntryPoint{
|
||||||
Address: "127.0.0.1:8090",
|
Address: "127.0.0.1:8090",
|
||||||
Transport: epConfig,
|
Transport: epConfig,
|
||||||
ForwardedHeaders: &static.ForwardedHeaders{},
|
ForwardedHeaders: &static.ForwardedHeaders{},
|
||||||
|
|
|
@ -72,7 +72,7 @@ func testShutdown(t *testing.T, router *tcprouter.Router) {
|
||||||
epConfig.RespondingTimeouts.ReadTimeout = ptypes.Duration(5 * time.Second)
|
epConfig.RespondingTimeouts.ReadTimeout = ptypes.Duration(5 * time.Second)
|
||||||
epConfig.RespondingTimeouts.WriteTimeout = ptypes.Duration(5 * time.Second)
|
epConfig.RespondingTimeouts.WriteTimeout = ptypes.Duration(5 * time.Second)
|
||||||
|
|
||||||
entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{
|
entryPoint, err := NewTCPEntryPoint(context.Background(), "", &static.EntryPoint{
|
||||||
// We explicitly use an IPV4 address because on Alpine, with an IPV6 address
|
// We explicitly use an IPV4 address because on Alpine, with an IPV6 address
|
||||||
// there seems to be shenanigans related to properly cleaning up file descriptors
|
// there seems to be shenanigans related to properly cleaning up file descriptors
|
||||||
Address: "127.0.0.1:0",
|
Address: "127.0.0.1:0",
|
||||||
|
@ -159,7 +159,7 @@ func TestReadTimeoutWithoutFirstByte(t *testing.T) {
|
||||||
epConfig.SetDefaults()
|
epConfig.SetDefaults()
|
||||||
epConfig.RespondingTimeouts.ReadTimeout = ptypes.Duration(2 * time.Second)
|
epConfig.RespondingTimeouts.ReadTimeout = ptypes.Duration(2 * time.Second)
|
||||||
|
|
||||||
entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{
|
entryPoint, err := NewTCPEntryPoint(context.Background(), "", &static.EntryPoint{
|
||||||
Address: ":0",
|
Address: ":0",
|
||||||
Transport: epConfig,
|
Transport: epConfig,
|
||||||
ForwardedHeaders: &static.ForwardedHeaders{},
|
ForwardedHeaders: &static.ForwardedHeaders{},
|
||||||
|
@ -196,7 +196,7 @@ func TestReadTimeoutWithFirstByte(t *testing.T) {
|
||||||
epConfig.SetDefaults()
|
epConfig.SetDefaults()
|
||||||
epConfig.RespondingTimeouts.ReadTimeout = ptypes.Duration(2 * time.Second)
|
epConfig.RespondingTimeouts.ReadTimeout = ptypes.Duration(2 * time.Second)
|
||||||
|
|
||||||
entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{
|
entryPoint, err := NewTCPEntryPoint(context.Background(), "", &static.EntryPoint{
|
||||||
Address: ":0",
|
Address: ":0",
|
||||||
Transport: epConfig,
|
Transport: epConfig,
|
||||||
ForwardedHeaders: &static.ForwardedHeaders{},
|
ForwardedHeaders: &static.ForwardedHeaders{},
|
||||||
|
@ -236,7 +236,7 @@ func TestKeepAliveMaxRequests(t *testing.T) {
|
||||||
epConfig.SetDefaults()
|
epConfig.SetDefaults()
|
||||||
epConfig.KeepAliveMaxRequests = 3
|
epConfig.KeepAliveMaxRequests = 3
|
||||||
|
|
||||||
entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{
|
entryPoint, err := NewTCPEntryPoint(context.Background(), "", &static.EntryPoint{
|
||||||
Address: ":0",
|
Address: ":0",
|
||||||
Transport: epConfig,
|
Transport: epConfig,
|
||||||
ForwardedHeaders: &static.ForwardedHeaders{},
|
ForwardedHeaders: &static.ForwardedHeaders{},
|
||||||
|
@ -282,7 +282,7 @@ func TestKeepAliveMaxTime(t *testing.T) {
|
||||||
epConfig.SetDefaults()
|
epConfig.SetDefaults()
|
||||||
epConfig.KeepAliveMaxTime = ptypes.Duration(time.Millisecond)
|
epConfig.KeepAliveMaxTime = ptypes.Duration(time.Millisecond)
|
||||||
|
|
||||||
entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{
|
entryPoint, err := NewTCPEntryPoint(context.Background(), "", &static.EntryPoint{
|
||||||
Address: ":0",
|
Address: ":0",
|
||||||
Transport: epConfig,
|
Transport: epConfig,
|
||||||
ForwardedHeaders: &static.ForwardedHeaders{},
|
ForwardedHeaders: &static.ForwardedHeaders{},
|
||||||
|
|
24
pkg/server/socket_activation_unix.go
Normal file
24
pkg/server/socket_activation_unix.go
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/coreos/go-systemd/activation"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func populateSocketActivationListeners() {
|
||||||
|
listenersWithName, _ := activation.ListenersWithNames()
|
||||||
|
|
||||||
|
socketActivationListeners = make(map[string]net.Listener)
|
||||||
|
for name, lns := range listenersWithName {
|
||||||
|
if len(lns) != 1 {
|
||||||
|
log.Error().Str("listenersName", name).Msg("Socket activation listeners must have one and only one listener per name")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
socketActivationListeners[name] = lns[0]
|
||||||
|
}
|
||||||
|
}
|
5
pkg/server/socket_activation_windows.go
Normal file
5
pkg/server/socket_activation_windows.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
func populateSocketActivationListeners() {}
|
Loading…
Reference in a new issue