traefikkop

package module
v0.19.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 7, 2025 License: MIT Imports: 37 Imported by: 0

README

traefik-kop

A dynamic docker->redis->traefik discovery agent.

Solves the problem of running a non-Swarm/Kubernetes multi-host cluster with a single public-facing traefik instance.

                        +---------------------+          +---------------------+
                        |                     |          |                     |
+---------+     :443    |  +---------+        |   :8088  |  +------------+     |
|   WAN   |--------------->| traefik |<-------------------->| svc-nginx  |     |
+---------+             |  +---------+        |          |  +------------+     |
                        |       |             |          |                     |
                        |  +---------+        |          |  +-------------+    |
                        |  |  redis  |<-------------------->| traefik-kop |    |
                        |  +---------+        |          |  +-------------+    |
                        |             docker1 |          |             docker2 |
                        +---------------------+          +---------------------+

traefik-kop solves this problem by using the same traefik docker-provider logic. It reads the container labels from the local docker node and publishes them to a given redis instance. Simply configure your traefik node with a redis provider and point it to the same instance, as in the diagram above.

Contents

Usage

Configure traefik to use the redis provider, for example via traefik.yml:

providers:
  providersThrottleDuration: 2s
  docker:
    watch: true
    endpoint: unix:///var/run/docker.sock
    swarmModeRefreshSeconds: 15s
    exposedByDefault: false
  redis:
    endpoints:
      # assumes a redis link with this service name running on the same
      # docker host as traefik
      - "redis:6379"

Run traefik-kop on your other nodes via docker-compose:

services:
  traefik-kop:
    image: "ghcr.io/jittering/traefik-kop:latest"
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - "REDIS_ADDR=192.168.1.50:6379"
      - "BIND_IP=192.168.1.75"
      # Alternatively, derive from an interface (requires network_mode: host)
      # - "BIND_INTERFACE=eth0"

Then add the usual labels to your target service:

services:
  nginx:
    image: "nginx:alpine"
    restart: unless-stopped
    ports:
      # The host port binding will automatically be picked up for use as the
      # service endpoint. See 'service port binding' in the configuration
      # section for more.
      - 8088:80
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.nginx.rule=Host(`nginx-on-docker2.example.com`)"
      - "traefik.http.routers.nginx.tls=true"
      - "traefik.http.routers.nginx.tls.certresolver=default"
      # [opptional] explicitly set the port binding for this service.
      # See 'service port binding' in the configuration section for more.
      - "traefik.http.services.nginx.loadbalancer.server.scheme=http"
      - "traefik.http.services.nginx.loadbalancer.server.port=8088"

See also the IP Binding section below.

Configuration

traefik-kop can be configured via either CLI flags or environment variables.

USAGE:
   traefik-kop [global options] command [command options] [arguments...]

GLOBAL OPTIONS:
   --hostname value       Hostname to identify this node in redis (default: "server.local") [$KOP_HOSTNAME]
   --bind-ip value        IP address to bind services to [$BIND_IP]
   --bind-interface value Network interface to derive bind IP (overrides auto-detect) [$BIND_INTERFACE]
   --skip-replace         Disable custom IP replacement (default: false) [$SKIP_REPLACE]
   --redis-addr value     Redis address (default: "127.0.0.1:6379") [$REDIS_ADDR]
   --redis-user value     Redis username (default: "default") [$REDIS_USER]
   --redis-pass value     Redis password (if needed) [$REDIS_PASS]
   --redis-db value       Redis DB number (default: 0) [$REDIS_DB]
   --redis-ttl value      Redis TTL (in seconds) (default: 0) [$REDIS_TTL]
   --docker-host value    Docker endpoint (default: "unix:///var/run/docker.sock") [$DOCKER_HOST]
   --docker-config value  Docker provider config (file must end in .yaml) [$DOCKER_CONFIG]
   --docker-prefix value  Docker label prefix [$DOCKER_PREFIX]
   --poll-interval value  Poll interval for refreshing container list (default: 60) [$KOP_POLL_INTERVAL]
   --namespace value      Namespace to process containers for [$NAMESPACE]
   --verbose              Enable debug logging (default: false) [$VERBOSE, $DEBUG]
   --help, -h             show help
   --version, -V          Print the version (default: false)

Most important are the bind-ip/bind-interface and redis-addr flags.

IP Binding

There are a number of ways to set the IP published to traefik. Below is the order of precedence (highest first) and detailed descriptions of each setting.

  1. kop.<service name>.bind.ip label
  2. kop.bind.ip label
  3. Container networking IP
  4. --bind-ip CLI flag (or BIND_IP env var)
  5. --bind-interface CLI flag (or BIND_INTERFACE env var), requires network_mode: host
  6. Auto-detected host IP
bind-ip

Since your upstream docker nodes are external to your primary traefik server, traefik needs to connect to these services via the server's public IP rather than the usual method of using the internal docker-network IPs (by default 172.20.0.x or similar).

When using host networking this can be auto-detected, however it is advisable in the majority of cases to manually set this to the desired IP address. This can be done using the docker image by exporting the BIND_IP environment variable.

bind-interface

If you prefer to bind using the primary IPv4 address of a specific network interface, you can specify it via --bind-interface or the BIND_INTERFACE environment variable, for example --bind-interface eth0. This is used when --bind-ip is not provided. If the interface has multiple addresses, the first non-loopback IPv4 address is selected.

This option requires that the container be run with network_mode: host so that the interface is visible to the container.

traefik-kop service labels

The bind IP can be set via label for each service/container.

Labels can be one of two keys:

  • kop.<service name>.bind.ip=2.2.2.2
  • kop.bind.ip=2.2.2.2

For a container with a single exposed service, or where all services use the same IP, the latter is sufficient.

Container Networking

If your container is configured to use a network-routable IP address via an overlay network or CNI plugin, that address will override the bind-ip configuration above when the traefik.docker.network label is present on the service.

If using a global overlay network in your Traefik Docker Provider Config, it is recommended that you disable IP replacement entirely (see below).

Disable IP Replacement (auto-detection)

traefik-kop's custom IP and port auto-detection can be disabled by passing the --skip-replace flag or setting the SKIP_REPLACE=1 environment variable. When set, traefik-kop will rely solely on traefik's native IP and port detection. Other relevant flags such as --bind-ip or --bind-interface will have no effect.

This works best when your services are using an overlay network, as described in Container Networking above.

Load Balancer Merging

If your service is running on multiple nodes and load balanced by traefik, you can enable merging of load balancers by adding the following label to your container:

  • kop.merge-lbs=true

When set, kop will check in redis for an existing definition and, if found, append it's service address to the ones already present.

This setting is off by default as there are some cases where it could cause an issue, such as if your node's IP changes. In this case, the dead IP would be left in place and the new IP would get added to the list, causing some of your traffic to fail.

Service port binding

By default, the service port will be picked up from the container port bindings if only a single port is bound. For example:

services:
  nginx:
    image: "nginx:alpine"
    restart: unless-stopped
    ports:
      - 8088:80

8088 would automatically be used as the service endpoint's port in traefik. If you have more than one port or are using host networking, you will need to explicitly set the port binding via service label, like so:

services:
  nginx:
    image: "nginx:alpine"
    network_mode: host
    ports:
      - 8088:80
      - 8888:81
    labels:
      # (note: other labels snipped for brevity)
      - "traefik.http.services.nginx.loadbalancer.server.port=8088"

NOTE: unlike the standard traefik-docker usage, we need to expose the service port on the host and tell traefik to bind to that port (8088 in the example above) in the load balancer config, not the internal port (80). This is so that traefik can reach it over the network.

Namespaces

traefik-kop has the ability to target containers via namespaces(s). Simply configure kop with a namespace:

services:
  traefik-kop:
    image: "ghcr.io/jittering/traefik-kop:latest"
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - "REDIS_ADDR=192.168.1.50:6379"
      - "BIND_IP=192.168.1.75"
      - "NAMESPACE=staging"

Then add the kop.namespace label to your target services, along with the usual traefik labels:

services:
  nginx:
    image: "nginx:alpine"
    restart: unless-stopped
    ports:
      - 8088:80
    labels:
      - "kop.namespace=staging"
      - "traefik.enable=true"
      - "traefik..."

Multiple namespaces can be used by comma-delimiting your values. Traefik-kop will include a container as long as one of its namespaces is found.

services:
  traefik-kop:
    # ...
    environment:
      # will expose any service with either 'dev' or 'staging'
      - "NAMESPACE=dev,staging"
services:
  nginx:
    # ...
    labels:
      # will be exposed because it has namespace 'staging'
      - "kop.namespace=staging,experimental"
Namespace via label prefix

An alternative method of namespacing is to add a custom prefix for your traefik-related labels. This works as an inclusion filter for traefik-kop and an exclusion filter for traefik. This can be useful in a scenario here you are running both traefik and traefik-kop side-by-side on the same host.

services:
  nginx:
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers..."
      - "traefik..."

becomes

services:
  traefik-kop:
    image: "ghcr.io/jittering/traefik-kop:latest"
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - "REDIS_ADDR=192.168.1.50:6379"
      - "BIND_IP=192.168.1.75"
      - "DOCKER_PREFIX=kop.public"

  nginx:
    labels:
      - "kop.public.traefik.enable=true"
      - "kop.public.traefik.http.routers..."
      - "kop.public.traefik..."

Docker API

traefik-kop expects to connect to the Docker host API via a unix socket, by default at /var/run/docker.sock. The location can be overridden via the DOCKER_HOST env var or --docker-host flag.

Other connection methods (like ssh, http/s) are not supported.

By default, traefik-kop will listen for push events via the Docker API in order to detect configuration changes. In some circumstances, a change may not be pushed correctly. For example, when using healthchecks in certain configurations, the start -> healthy change may not be detected via push event. As a failsafe, there is an additional polling mechanism to detect those missed changes.

The default interval of 60 seconds should be light so as not to cause any issues, however it can be adjusted as needed via the KOP_POLL_INTERVAL env var or set to 0 to disable it completely.

Traefik Docker Provider Config

In addition to the simple --docker-host setting above, all Docker Provider configuration options are available via the --docker-config <filename.yaml> flag which expects either a filename to read configuration from or an inline YAML document.

For example:

services:
  traefik-kop:
    image: "ghcr.io/jittering/traefik-kop:latest"
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      REDIS_ADDR: "172.28.183.97:6380"
      BIND_IP: "172.28.183.97"
      DOCKER_CONFIG: |
        ---
        docker:
          defaultRule: Host(`{{.Name}}.foo.example.com`)

Releasing

To release a new version, simply push a new tag to github.

git push
git tag -a v0.11.0
git push --tags

To update the changelog:

make update-changelog
# or (replace tag below)
docker run -it --rm -v "$(pwd)":/usr/local/src/your-app \
  githubchangeloggenerator/github-changelog-generator \
  -u jittering -p traefik-kop --output "" \
  --since-tag v0.10.1

License

traefik-kop: MIT, (c) 2015, Pixelcop Research, Inc.

traefik: MIT, (c) 2016-2025 Containous SAS; 2020-2022 Traefik Labs

Documentation

Index

Constants

This section is empty.

Variables

View Source
var Version = ""

Functions

func ConfigToKV

func ConfigToKV(conf dynamic.Configuration) (map[string]interface{}, error)

ConfigToKV flattens the given configuration into a format suitable for putting into a KV store such as redis

func Start

func Start(config Config)

Start the main traefik-kop run-loop

Types

type Config

type Config struct {
	DockerConfig string
	DockerHost   string

	// prefix for traefik labels to accept
	DockerPrefix string
	Hostname     string
	BindIP       string
	SkipReplace  bool
	RedisAddr    string
	RedisTTL     int
	RedisUser    string
	RedisPass    string
	RedisDB      int
	PollInterval int64
	Namespace    []string
}

type ConfigFile added in v0.13.1

type ConfigFile struct {
	Docker docker.Provider `yaml:"docker"`
}

type DockerProxyServer added in v0.18.1

type DockerProxyServer struct {
	// contains filtered or unexported fields
}

DockerProxyServer is a proxy server that filters docker labels based on a prefix

type KV

type KV struct {
	// contains filtered or unexported fields
}

func NewKV

func NewKV() *KV

func (*KV) SetBase

func (kv *KV) SetBase(b string)

type MultiProvider added in v0.12.1

type MultiProvider struct {
	// contains filtered or unexported fields
}

MultiProvider simply wraps an array of providers (more generic than ProviderAggregator)

func NewMultiProvider added in v0.12.1

func NewMultiProvider(upstream []provider.Provider) *MultiProvider

func (MultiProvider) Init added in v0.12.1

func (p MultiProvider) Init() error

func (MultiProvider) Provide added in v0.12.1

func (p MultiProvider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error

type PollingProvider added in v0.12.1

type PollingProvider struct {
	// contains filtered or unexported fields
}

PollingProvider simply wraps the target upstream provider with a poller.

func NewPollingProvider added in v0.12.1

func NewPollingProvider(refreshInterval time.Duration, upstream provider.Provider, store TraefikStore) *PollingProvider

func (PollingProvider) Init added in v0.12.1

func (p PollingProvider) Init() error

func (PollingProvider) Provide added in v0.12.1

func (p PollingProvider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error

type RedisStore added in v0.13.3

type RedisStore struct {
	Hostname string
	TTL      time.Duration // TTL in seconds, 0 means no TTL
	// contains filtered or unexported fields
}

func (*RedisStore) Get added in v0.18.1

func (s *RedisStore) Get(key string) (string, error)

func (*RedisStore) Gets added in v0.18.1

func (s *RedisStore) Gets(key string) (map[string]string, error)

func (*RedisStore) KeepConfAlive added in v0.18.1

func (s *RedisStore) KeepConfAlive() error

Push the last configuration if needed

func (*RedisStore) NeedsUpdate added in v0.18.1

func (s *RedisStore) NeedsUpdate() bool

NeedsUpdate checks if Redis needs a full configuration refresh by checking for the sentinel key's existence

func (*RedisStore) Ping added in v0.13.3

func (s *RedisStore) Ping() error

func (*RedisStore) Store added in v0.13.3

func (s *RedisStore) Store(conf dynamic.Configuration) error

type TraefikStore added in v0.13.3

type TraefikStore interface {
	Store(conf dynamic.Configuration) error
	Get(key string) (string, error)
	Gets(key string) (map[string]string, error)
	Ping() error
	KeepConfAlive() error
}

func NewRedisStore added in v0.13.3

func NewRedisStore(hostname string, addr string, ttl int, user string, pass string, db int) TraefikStore

Directories

Path Synopsis
bin
traefik-kop command
testing
helloworld module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL