Merge branch v3.0 into master
This commit is contained in:
commit
cf1cbb24df
100 changed files with 3014 additions and 2559 deletions
|
@ -3,7 +3,7 @@ name: Traefik
|
|||
agent:
|
||||
machine:
|
||||
type: e1-standard-4
|
||||
os_image: ubuntu1804
|
||||
os_image: ubuntu2004
|
||||
|
||||
fail_fast:
|
||||
stop:
|
||||
|
@ -57,7 +57,7 @@ blocks:
|
|||
agent:
|
||||
machine:
|
||||
type: e1-standard-8
|
||||
os_image: ubuntu1804
|
||||
os_image: ubuntu2004
|
||||
secrets:
|
||||
- name: traefik
|
||||
env_vars:
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
## [v2.10.1](https://github.com/traefik/traefik/tree/v2.10.1) (2023-04-27)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.10.0...v2.10.1)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[middleware,oxy]** Update vulcand/oxy to be5cf38 ([#9874](https://github.com/traefik/traefik/pull/9874) by [rtribotte](https://github.com/rtribotte))
|
||||
|
||||
**Documentation:**
|
||||
- Fix v2.10 migration guide ([#9863](https://github.com/traefik/traefik/pull/9863) by [rtribotte](https://github.com/rtribotte))
|
||||
|
||||
## [v2.10.0](https://github.com/traefik/traefik/tree/v2.10.0) (2023-04-24)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.9.0-rc1...v2.10.0)
|
||||
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
|
||||
<p align="center">
|
||||
<img src="docs/content/assets/img/traefik.logo.png" alt="Traefik" title="Traefik" />
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="docs/content/assets/img/traefik.logo-dark.png">
|
||||
<source media="(prefers-color-scheme: light)" srcset="docs/content/assets/img/traefik.logo.png">
|
||||
<img alt="Traefik" title="Traefik" src="docs/content/assets/img/traefik.logo.png">
|
||||
</picture>
|
||||
</p>
|
||||
|
||||
[![Build Status SemaphoreCI](https://semaphoreci.com/api/v1/containous/traefik/branches/master/shields_badge.svg)](https://semaphoreci.com/containous/traefik)
|
||||
|
|
|
@ -34,7 +34,6 @@ import (
|
|||
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/acme"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/aggregator"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/hub"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/tailscale"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/traefik"
|
||||
"github.com/traefik/traefik/v3/pkg/safe"
|
||||
|
@ -235,19 +234,6 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
|
|||
}
|
||||
}
|
||||
|
||||
// Traefik Hub
|
||||
|
||||
if staticConfiguration.Hub != nil {
|
||||
if err = providerAggregator.AddProvider(staticConfiguration.Hub); err != nil {
|
||||
return nil, fmt.Errorf("adding Traefik Hub provider: %w", err)
|
||||
}
|
||||
|
||||
// API is mandatory for Traefik Hub to access the dynamic configuration.
|
||||
if staticConfiguration.API == nil {
|
||||
staticConfiguration.API = &static.API{}
|
||||
}
|
||||
}
|
||||
|
||||
// Service manager factory
|
||||
|
||||
var spiffeX509Source *workloadapi.X509Source
|
||||
|
@ -318,7 +304,7 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
|
|||
watcher.AddListener(switchRouter(routerFactory, serverEntryPointsTCP, serverEntryPointsUDP))
|
||||
|
||||
// Metrics
|
||||
if metricsRegistry.IsEpEnabled() || metricsRegistry.IsSvcEnabled() {
|
||||
if metricsRegistry.IsEpEnabled() || metricsRegistry.IsRouterEnabled() || metricsRegistry.IsSvcEnabled() {
|
||||
var eps []string
|
||||
for key := range serverEntryPointsTCP {
|
||||
eps = append(eps, key)
|
||||
|
@ -354,10 +340,7 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
|
|||
continue
|
||||
}
|
||||
|
||||
if _, ok := resolverNames[rt.TLS.CertResolver]; !ok &&
|
||||
// "traefik-hub" is an allowed certificate resolver name in a Traefik Hub Experimental feature context.
|
||||
// It is used to activate its own certificate resolution, even though it is not a "classical" traefik certificate resolver.
|
||||
(staticConfiguration.Hub == nil || rt.TLS.CertResolver != "traefik-hub") {
|
||||
if _, ok := resolverNames[rt.TLS.CertResolver]; !ok {
|
||||
log.Error().Err(err).Str(logs.RouterName, rtName).Str("certificateResolver", rt.TLS.CertResolver).
|
||||
Msg("Router uses a non-existent certificate resolver")
|
||||
}
|
||||
|
@ -397,11 +380,6 @@ func getDefaultsEntrypoints(staticConfiguration *static.Configuration) []string
|
|||
continue
|
||||
}
|
||||
|
||||
// Traefik Hub entryPoint should not be used as a default entryPoint.
|
||||
if hub.APIEntrypoint == name || hub.TunnelEntrypoint == name {
|
||||
continue
|
||||
}
|
||||
|
||||
protocol, err := cfg.GetProtocol()
|
||||
if err != nil {
|
||||
// Should never happen because Traefik should not start if protocol is invalid.
|
||||
|
|
BIN
docs/content/assets/img/traefik.logo-dark.png
Normal file
BIN
docs/content/assets/img/traefik.logo-dark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
|
@ -66,7 +66,6 @@ providers:
|
|||
docker:
|
||||
endpoint: "tcp://10.10.10.10:2375"
|
||||
exposedByDefault: true
|
||||
swarmMode: true
|
||||
|
||||
tls:
|
||||
ca: dockerCA
|
||||
|
@ -86,7 +85,6 @@ providers:
|
|||
docker:
|
||||
endpoint: "xxxx"
|
||||
exposedByDefault: true
|
||||
swarmMode: true
|
||||
|
||||
tls:
|
||||
ca: xxxx
|
||||
|
|
|
@ -158,6 +158,56 @@ By default, the following headers are automatically added when proxying requests
|
|||
For more details,
|
||||
please check out the [forwarded header](../routing/entrypoints.md#forwarded-headers) documentation.
|
||||
|
||||
## How Traefik is Storing and Serving TLS Certificates?
|
||||
|
||||
### Storing TLS Certificates
|
||||
|
||||
[TLS](../https/tls.md "Link to Traefik TLS docs") certificates are either provided directly by the [dynamic configuration](./configuration-overview.md#the-dynamic-configuration "Link to dynamic configuration overview") from [providers](../https/tls.md#user-defined "Link to the TLS configuration"),
|
||||
or by [ACME resolvers](../https/acme.md#providers "Link to ACME resolvers"), which act themselves as providers internally.
|
||||
|
||||
For each TLS certificate, Traefik produces an identifier used as a key to store it.
|
||||
This identifier is constructed as the alphabetically ordered concatenation of the SANs `DNSNames` and `IPAddresses` of the TLScertificate.
|
||||
|
||||
#### Examples:
|
||||
|
||||
| X509v3 Subject Alternative Name | TLS Certificate Identifier |
|
||||
|-----------------------------------------|-----------------------------|
|
||||
| `DNS:example.com, IP Address:127.0.0.1` | `127.0.0.1,example.com` |
|
||||
| `DNS:example.com, DNS:*.example.com` | `*.example.com,example.com` |
|
||||
|
||||
The identifier is used to store TLS certificates in order to be later used to handle TLS connections.
|
||||
This operation happens each time there are configuration changes.
|
||||
|
||||
If multiple TLS certificates are provided with the same SANs definition (same identifier), only the one processed first is kept.
|
||||
Because the dynamic configuration is aggregated from all providers,
|
||||
when processing it to gather TLS certificates,
|
||||
there is no guarantee of the order in which they would be processed.
|
||||
This means that along with configurations applied, it is possible that the TLS certificate retained for a given identifier differs.
|
||||
|
||||
### Serving TLS Certificates
|
||||
|
||||
For each incoming connection, Traefik is serving the "best" matching TLS certificate for the provided server name.
|
||||
|
||||
The TLS certificate selection process narrows down the list of TLS certificates matching the server name,
|
||||
and then selects the last TLS certificate in this list after having ordered it by the identifier alphabetically.
|
||||
|
||||
#### Examples:
|
||||
|
||||
| Selected TLS Certificates Identifiers | Sorted TLS Certificates Identifiers | Served Certificate Identifier |
|
||||
|-----------------------------------------------------|-----------------------------------------------------|-------------------------------|
|
||||
| `127.0.0.1,example.com`,`*.example.com,example.com` | `*.example.com,example.com`,`127.0.0.1,example.com` | `127.0.0.1,example.com` |
|
||||
| `*.example.com,example.com`,`example.com` | `*.example.com,example.com`,`example.com` | `example.com` |
|
||||
|
||||
### Caching TLS Certificates
|
||||
|
||||
While Traefik is serving the best matching TLS certificate for each incoming connection,
|
||||
the selection process cost for each incoming connection is avoided thanks to a cache mechanism.
|
||||
|
||||
Once a TLS certificate has been selected as the "best" TLS certificate for a server name,
|
||||
it is cached for an hour, avoiding the selection process for further connections.
|
||||
|
||||
Nonetheless, when a new configuration is applied, the cache is reset.
|
||||
|
||||
## What does the "field not found" error mean?
|
||||
|
||||
```shell
|
||||
|
|
|
@ -308,121 +308,126 @@ For example, `CF_API_EMAIL_FILE=/run/secrets/traefik_cf-api-email` could be used
|
|||
|
||||
For complete details, refer to your provider's _Additional configuration_ link.
|
||||
|
||||
| Provider Name | Provider Code | Environment Variables | |
|
||||
|--------------------------------------------------------------------------|--------------------|---------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------|
|
||||
| [ACME DNS](https://github.com/joohoi/acme-dns) | `acme-dns` | `ACME_DNS_API_BASE`, `ACME_DNS_STORAGE_PATH` | [Additional configuration](https://go-acme.github.io/lego/dns/acme-dns) |
|
||||
| [Alibaba Cloud](https://www.alibabacloud.com) | `alidns` | `ALICLOUD_ACCESS_KEY`, `ALICLOUD_SECRET_KEY`, `ALICLOUD_REGION_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/alidns) |
|
||||
| [all-inkl](https://all-inkl.com) | `allinkl` | `ALL_INKL_LOGIN`, `ALL_INKL_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/allinkl) |
|
||||
| [ArvanCloud](https://www.arvancloud.com/en) | `arvancloud` | `ARVANCLOUD_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/arvancloud) |
|
||||
| [Auroradns](https://www.pcextreme.com/dns-health-checks) | `auroradns` | `AURORA_USER_ID`, `AURORA_KEY`, `AURORA_ENDPOINT` | [Additional configuration](https://go-acme.github.io/lego/dns/auroradns) |
|
||||
| [Autodns](https://www.internetx.com/domains/autodns/) | `autodns` | `AUTODNS_API_USER`, `AUTODNS_API_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/autodns) |
|
||||
| [Azure](https://azure.microsoft.com/services/dns/) | `azure` | `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_SUBSCRIPTION_ID`, `AZURE_TENANT_ID`, `AZURE_RESOURCE_GROUP`, `[AZURE_METADATA_ENDPOINT]` | [Additional configuration](https://go-acme.github.io/lego/dns/azure) |
|
||||
| [Bindman](https://github.com/labbsr0x/bindman-dns-webhook) | `bindman` | `BINDMAN_MANAGER_ADDRESS` | [Additional configuration](https://go-acme.github.io/lego/dns/bindman) |
|
||||
| [Blue Cat](https://www.bluecatnetworks.com/) | `bluecat` | `BLUECAT_SERVER_URL`, `BLUECAT_USER_NAME`, `BLUECAT_PASSWORD`, `BLUECAT_CONFIG_NAME`, `BLUECAT_DNS_VIEW` | [Additional configuration](https://go-acme.github.io/lego/dns/bluecat) |
|
||||
| [Checkdomain](https://www.checkdomain.de/) | `checkdomain` | `CHECKDOMAIN_TOKEN`, | [Additional configuration](https://go-acme.github.io/lego/dns/checkdomain/) |
|
||||
| [Civo](https://www.civo.com/) | `civo` | `CIVO_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/civo) |
|
||||
| [CloudDNS](https://vshosting.eu/) | `clouddns` | `CLOUDDNS_CLIENT_ID`, `CLOUDDNS_EMAIL`, `CLOUDDNS_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/clouddns) |
|
||||
| [Cloudflare](https://www.cloudflare.com) | `cloudflare` | `CF_API_EMAIL`, `CF_API_KEY` [^5] or `CF_DNS_API_TOKEN`, `[CF_ZONE_API_TOKEN]` | [Additional configuration](https://go-acme.github.io/lego/dns/cloudflare) |
|
||||
| [ClouDNS](https://www.cloudns.net/) | `cloudns` | `CLOUDNS_AUTH_ID`, `CLOUDNS_AUTH_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/cloudns) |
|
||||
| [CloudXNS](https://www.cloudxns.net) | `cloudxns` | `CLOUDXNS_API_KEY`, `CLOUDXNS_SECRET_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/cloudxns) |
|
||||
| [ConoHa](https://www.conoha.jp) | `conoha` | `CONOHA_TENANT_ID`, `CONOHA_API_USERNAME`, `CONOHA_API_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/conoha) |
|
||||
| [Constellix](https://constellix.com) | `constellix` | `CONSTELLIX_API_KEY`, `CONSTELLIX_SECRET_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/constellix) |
|
||||
| [deSEC](https://desec.io) | `desec` | `DESEC_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/desec) |
|
||||
| [DigitalOcean](https://www.digitalocean.com) | `digitalocean` | `DO_AUTH_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/digitalocean) |
|
||||
| [DNS Made Easy](https://dnsmadeeasy.com) | `dnsmadeeasy` | `DNSMADEEASY_API_KEY`, `DNSMADEEASY_API_SECRET`, `DNSMADEEASY_SANDBOX` | [Additional configuration](https://go-acme.github.io/lego/dns/dnsmadeeasy) |
|
||||
| [dnsHome.de](https://www.dnshome.de) | `dnsHomede` | `DNSHOMEDE_CREDENTIALS` | [Additional configuration](https://go-acme.github.io/lego/dns/dnshomede) |
|
||||
| [DNSimple](https://dnsimple.com) | `dnsimple` | `DNSIMPLE_OAUTH_TOKEN`, `DNSIMPLE_BASE_URL` | [Additional configuration](https://go-acme.github.io/lego/dns/dnsimple) |
|
||||
| [DNSPod](https://www.dnspod.com/) | `dnspod` | `DNSPOD_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/dnspod) |
|
||||
| [Domain Offensive (do.de)](https://www.do.de/) | `dode` | `DODE_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/dode) |
|
||||
| [Domeneshop](https://domene.shop) | `domeneshop` | `DOMENESHOP_API_TOKEN`, `DOMENESHOP_API_SECRET` | [Additional configuration](https://go-acme.github.io/lego/dns/domeneshop) |
|
||||
| [DreamHost](https://www.dreamhost.com/) | `dreamhost` | `DREAMHOST_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/dreamhost) |
|
||||
| [Duck DNS](https://www.duckdns.org/) | `duckdns` | `DUCKDNS_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/duckdns) |
|
||||
| [Dyn](https://dyn.com) | `dyn` | `DYN_CUSTOMER_NAME`, `DYN_USER_NAME`, `DYN_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/dyn) |
|
||||
| [Dynu](https://www.dynu.com) | `dynu` | `DYNU_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/dynu) |
|
||||
| [EasyDNS](https://easydns.com/) | `easydns` | `EASYDNS_TOKEN`, `EASYDNS_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/easydns) |
|
||||
| [EdgeDNS](https://www.akamai.com/) | `edgedns` | `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET`, `AKAMAI_ACCESS_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/edgedns) |
|
||||
| [Epik](https://www.epik.com) | `epik` | `EPIK_SIGNATURE` | [Additional configuration](https://go-acme.github.io/lego/dns/epik) |
|
||||
| [Exoscale](https://www.exoscale.com) | `exoscale` | `EXOSCALE_API_KEY`, `EXOSCALE_API_SECRET`, `EXOSCALE_ENDPOINT` | [Additional configuration](https://go-acme.github.io/lego/dns/exoscale) |
|
||||
| [Fast DNS](https://www.akamai.com/) | `fastdns` | `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET`, `AKAMAI_ACCESS_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/edgedns) |
|
||||
| [Freemyip.com](https://freemyip.com) | `freemyip` | `FREEMYIP_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/freemyip) |
|
||||
| [G-Core Lab](https://gcorelabs.com/dns/) | `gcore` | `GCORE_PERMANENT_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/gcore) |
|
||||
| [Gandi v5](https://doc.livedns.gandi.net) | `gandiv5` | `GANDIV5_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/gandiv5) |
|
||||
| [Gandi](https://www.gandi.net) | `gandi` | `GANDI_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/gandi) |
|
||||
| [Glesys](https://glesys.com/) | `glesys` | `GLESYS_API_USER`, `GLESYS_API_KEY`, `GLESYS_DOMAIN` | [Additional configuration](https://go-acme.github.io/lego/dns/glesys) |
|
||||
| [GoDaddy](https://www.godaddy.com) | `godaddy` | `GODADDY_API_KEY`, `GODADDY_API_SECRET` | [Additional configuration](https://go-acme.github.io/lego/dns/godaddy) |
|
||||
| [Google Cloud DNS](https://cloud.google.com/dns/docs/) | `gcloud` | `GCE_PROJECT`, Application Default Credentials [^2] [^3], [`GCE_SERVICE_ACCOUNT_FILE`] | [Additional configuration](https://go-acme.github.io/lego/dns/gcloud) |
|
||||
| [Hetzner](https://hetzner.com) | `hetzner` | `HETZNER_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/hetzner) |
|
||||
| [hosting.de](https://www.hosting.de) | `hostingde` | `HOSTINGDE_API_KEY`, `HOSTINGDE_ZONE_NAME` | [Additional configuration](https://go-acme.github.io/lego/dns/hostingde) |
|
||||
| [Hosttech](https://www.hosttech.eu) | `hosttech` | `HOSTTECH_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/hosttech) |
|
||||
| [Hurricane Electric](https://dns.he.net) | `hurricane` | `HURRICANE_TOKENS` [^6] | [Additional configuration](https://go-acme.github.io/lego/dns/hurricane) |
|
||||
| [HyperOne](https://www.hyperone.com) | `hyperone` | `HYPERONE_PASSPORT_LOCATION`, `HYPERONE_LOCATION_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/hyperone) |
|
||||
| [IBM Cloud (SoftLayer)](https://www.ibm.com/cloud/) | `ibmcloud` | `SOFTLAYER_USERNAME`, `SOFTLAYER_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/ibmcloud) |
|
||||
| [IIJ DNS Platform Service](https://www.iij.ad.jp) | `iijdpf` | `IIJ_DPF_API_TOKEN` , `IIJ_DPF_DPM_SERVICE_CODE` | [Additional configuration](https://go-acme.github.io/lego/dns/iijdpf) |
|
||||
| [IIJ](https://www.iij.ad.jp/) | `iij` | `IIJ_API_ACCESS_KEY`, `IIJ_API_SECRET_KEY`, `IIJ_DO_SERVICE_CODE` | [Additional configuration](https://go-acme.github.io/lego/dns/iij) |
|
||||
| [Infoblox](https://www.infoblox.com/) | `infoblox` | `INFOBLOX_USERNAME`, `INFOBLOX_PASSWORD`, `INFOBLOX_HOST` | [Additional configuration](https://go-acme.github.io/lego/dns/infoblox) |
|
||||
| [Infomaniak](https://www.infomaniak.com) | `infomaniak` | `INFOMANIAK_ACCESS_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/infomaniak) |
|
||||
| [Internet.bs](https://internetbs.net) | `internetbs` | `INTERNET_BS_API_KEY`, `INTERNET_BS_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/internetbs) |
|
||||
| [INWX](https://www.inwx.de/en) | `inwx` | `INWX_USERNAME`, `INWX_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/inwx) |
|
||||
| [ionos](https://ionos.com/) | `ionos` | `IONOS_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/ionos) |
|
||||
| [iwantmyname](https://iwantmyname.com) | `iwantmyname` | `IWANTMYNAME_USERNAME` , `IWANTMYNAME_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/iwantmyname) |
|
||||
| [Joker.com](https://joker.com) | `joker` | `JOKER_API_MODE` with `JOKER_API_KEY` or `JOKER_USERNAME`, `JOKER_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/joker) |
|
||||
| [Liara](https://liara.ir) | `liara` | `LIARA_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/liara) |
|
||||
| [Lightsail](https://aws.amazon.com/lightsail/) | `lightsail` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `DNS_ZONE` | [Additional configuration](https://go-acme.github.io/lego/dns/lightsail) |
|
||||
| [Linode v4](https://www.linode.com) | `linode` | `LINODE_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/linode) |
|
||||
| [Liquid Web](https://www.liquidweb.com/) | `liquidweb` | `LIQUID_WEB_PASSWORD`, `LIQUID_WEB_USERNAME`, `LIQUID_WEB_ZONE` | [Additional configuration](https://go-acme.github.io/lego/dns/liquidweb) |
|
||||
| [Loopia](https://loopia.com/) | `loopia` | `LOOPIA_API_PASSWORD`, `LOOPIA_API_USER` | [Additional configuration](https://go-acme.github.io/lego/dns/loopia) |
|
||||
| [LuaDNS](https://luadns.com) | `luadns` | `LUADNS_API_USERNAME`, `LUADNS_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/luadns) |
|
||||
| [MyDNS.jp](https://www.mydns.jp/) | `mydnsjp` | `MYDNSJP_MASTER_ID`, `MYDNSJP_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/mydnsjp) |
|
||||
| [Mythic Beasts](https://www.mythic-beasts.com) | `mythicbeasts` | `MYTHICBEASTS_USER_NAME`, `MYTHICBEASTS_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/mythicbeasts) |
|
||||
| [name.com](https://www.name.com/) | `namedotcom` | `NAMECOM_USERNAME`, `NAMECOM_API_TOKEN`, `NAMECOM_SERVER` | [Additional configuration](https://go-acme.github.io/lego/dns/namedotcom) |
|
||||
| [Namecheap](https://www.namecheap.com) | `namecheap` | `NAMECHEAP_API_USER`, `NAMECHEAP_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/namecheap) |
|
||||
| [Namesilo](https://www.namesilo.com/) | `namesilo` | `NAMESILO_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/namesilo) |
|
||||
| [NearlyFreeSpeech.NET](https://www.nearlyfreespeech.net/) | `nearlyfreespeech` | `NEARLYFREESPEECH_API_KEY`, `NEARLYFREESPEECH_LOGIN` | [Additional configuration](https://go-acme.github.io/lego/dns/nearlyfreespeech) |
|
||||
| [Netcup](https://www.netcup.eu/) | `netcup` | `NETCUP_CUSTOMER_NUMBER`, `NETCUP_API_KEY`, `NETCUP_API_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/netcup) |
|
||||
| [Netlify](https://www.netlify.com) | `netlify` | `NETLIFY_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/netlify) |
|
||||
| [Nicmanager](https://www.nicmanager.com) | `nicmanager` | `NICMANAGER_API_EMAIL`, `NICMANAGER_API_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/nicmanager) |
|
||||
| [NIFCloud](https://cloud.nifty.com/service/dns.htm) | `nifcloud` | `NIFCLOUD_ACCESS_KEY_ID`, `NIFCLOUD_SECRET_ACCESS_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/nifcloud) |
|
||||
| [Njalla](https://njal.la) | `njalla` | `NJALLA_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/njalla) |
|
||||
| [NS1](https://ns1.com/) | `ns1` | `NS1_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/ns1) |
|
||||
| [Open Telekom Cloud](https://cloud.telekom.de) | `otc` | `OTC_DOMAIN_NAME`, `OTC_USER_NAME`, `OTC_PASSWORD`, `OTC_PROJECT_NAME`, `OTC_IDENTITY_ENDPOINT` | [Additional configuration](https://go-acme.github.io/lego/dns/otc) |
|
||||
| [Openstack Designate](https://docs.openstack.org/designate) | `designate` | `OS_AUTH_URL`, `OS_USERNAME`, `OS_PASSWORD`, `OS_TENANT_NAME`, `OS_REGION_NAME` | [Additional configuration](https://go-acme.github.io/lego/dns/designate) |
|
||||
| [Oracle Cloud](https://cloud.oracle.com/home) | `oraclecloud` | `OCI_COMPARTMENT_OCID`, `OCI_PRIVKEY_FILE`, `OCI_PRIVKEY_PASS`, `OCI_PUBKEY_FINGERPRINT`, `OCI_REGION`, `OCI_TENANCY_OCID`, `OCI_USER_OCID` | [Additional configuration](https://go-acme.github.io/lego/dns/oraclecloud) |
|
||||
| [OVH](https://www.ovh.com) | `ovh` | `OVH_ENDPOINT`, `OVH_APPLICATION_KEY`, `OVH_APPLICATION_SECRET`, `OVH_CONSUMER_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/ovh) |
|
||||
| [Porkbun](https://porkbun.com/) | `porkbun` | `PORKBUN_SECRET_API_KEY`, `PORKBUN_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/porkbun) |
|
||||
| [PowerDNS](https://www.powerdns.com) | `pdns` | `PDNS_API_KEY`, `PDNS_API_URL` | [Additional configuration](https://go-acme.github.io/lego/dns/pdns) |
|
||||
| [Rackspace](https://www.rackspace.com/cloud/dns) | `rackspace` | `RACKSPACE_USER`, `RACKSPACE_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/rackspace) |
|
||||
| [reg.ru](https://www.reg.ru) | `regru` | `REGRU_USERNAME`, `REGRU_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/regru) |
|
||||
| [RFC2136](https://tools.ietf.org/html/rfc2136) | `rfc2136` | `RFC2136_TSIG_KEY`, `RFC2136_TSIG_SECRET`, `RFC2136_TSIG_ALGORITHM`, `RFC2136_NAMESERVER` | [Additional configuration](https://go-acme.github.io/lego/dns/rfc2136) |
|
||||
| [RimuHosting](https://rimuhosting.com) | `rimuhosting` | `RIMUHOSTING_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/rimuhosting) |
|
||||
| [Route 53](https://aws.amazon.com/route53/) | `route53` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `[AWS_REGION]`, `[AWS_HOSTED_ZONE_ID]` or a configured user/instance IAM profile. | [Additional configuration](https://go-acme.github.io/lego/dns/route53) |
|
||||
| [Sakura Cloud](https://cloud.sakura.ad.jp/) | `sakuracloud` | `SAKURACLOUD_ACCESS_TOKEN`, `SAKURACLOUD_ACCESS_TOKEN_SECRET` | [Additional configuration](https://go-acme.github.io/lego/dns/sakuracloud) |
|
||||
| [Scaleway](https://www.scaleway.com) | `scaleway` | `SCALEWAY_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/scaleway) |
|
||||
| [Selectel](https://selectel.ru/en/) | `selectel` | `SELECTEL_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/selectel) |
|
||||
| [Servercow](https://servercow.de) | `servercow` | `SERVERCOW_USERNAME`, `SERVERCOW_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/servercow) |
|
||||
| [Simply.com](https://www.simply.com/en/domains/) | `simply` | `SIMPLY_ACCOUNT_NAME`, `SIMPLY_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/simply) |
|
||||
| [Sonic](https://www.sonic.com/) | `sonic` | `SONIC_USER_ID`, `SONIC_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/sonic) |
|
||||
| [Stackpath](https://www.stackpath.com/) | `stackpath` | `STACKPATH_CLIENT_ID`, `STACKPATH_CLIENT_SECRET`, `STACKPATH_STACK_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/stackpath) |
|
||||
| [Tencent Cloud DNS](https://cloud.tencent.com/product/cns) | `tencentcloud` | `TENCENTCLOUD_SECRET_ID`, `TENCENTCLOUD_SECRET_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/tencentcloud) |
|
||||
| [TransIP](https://www.transip.nl/) | `transip` | `TRANSIP_ACCOUNT_NAME`, `TRANSIP_PRIVATE_KEY_PATH` | [Additional configuration](https://go-acme.github.io/lego/dns/transip) |
|
||||
| [UKFast SafeDNS](https://docs.ukfast.co.uk/domains/safedns/index.html) | `safedns` | `SAFEDNS_AUTH_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/safedns) |
|
||||
| [Ultradns](https://neustarsecurityservices.com/dns-services) | `ultradns` | `ULTRADNS_USERNAME`, `ULTRADNS_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/ultradns) |
|
||||
| [Variomedia](https://www.variomedia.de/) | `variomedia` | `VARIOMEDIA_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/variomedia) |
|
||||
| [VegaDNS](https://github.com/shupp/VegaDNS-API) | `vegadns` | `SECRET_VEGADNS_KEY`, `SECRET_VEGADNS_SECRET`, `VEGADNS_URL` | [Additional configuration](https://go-acme.github.io/lego/dns/vegadns) |
|
||||
| [Vercel](https://vercel.com) | `vercel` | `VERCEL_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/vercel) |
|
||||
| [Versio](https://www.versio.nl/domeinnamen) | `versio` | `VERSIO_USERNAME`, `VERSIO_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/versio) |
|
||||
| [VinylDNS](https://www.vinyldns.io) | `vinyldns` | `VINYLDNS_ACCESS_KEY`, `VINYLDNS_SECRET_KEY`, `VINYLDNS_HOST` | [Additional configuration](https://go-acme.github.io/lego/dns/vinyldns) |
|
||||
| [VK Cloud](https://mcs.mail.ru/) | `vkcloud` | `VK_CLOUD_PASSWORD`, `VK_CLOUD_PROJECT_ID`, `VK_CLOUD_USERNAME` | [Additional configuration](https://go-acme.github.io/lego/dns/vkcloud) |
|
||||
| [Vscale](https://vscale.io/) | `vscale` | `VSCALE_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/vscale) |
|
||||
| [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/vultr) |
|
||||
| [Websupport](https://websupport.sk) | `websupport` | `WEBSUPPORT_API_KEY`, `WEBSUPPORT_SECRET` | [Additional configuration](https://go-acme.github.io/lego/dns/websupport) |
|
||||
| [WEDOS](https://www.wedos.com) | `wedos` | `WEDOS_USERNAME`, `WEDOS_WAPI_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/wedos) |
|
||||
| [Yandex Cloud](https://cloud.yandex.com/en/) | `yandexcloud` | `YANDEX_CLOUD_FOLDER_ID`, `YANDEX_CLOUD_IAM_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/yandexcloud) |
|
||||
| [Yandex](https://yandex.com) | `yandex` | `YANDEX_PDD_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/yandex) |
|
||||
| [Zone.ee](https://www.zone.ee) | `zoneee` | `ZONEEE_API_USER`, `ZONEEE_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/zoneee) |
|
||||
| [Zonomi](https://zonomi.com) | `zonomi` | `ZONOMI_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/zonomi) |
|
||||
| External Program | `exec` | `EXEC_PATH` | [Additional configuration](https://go-acme.github.io/lego/dns/exec) |
|
||||
| HTTP request | `httpreq` | `HTTPREQ_ENDPOINT`, `HTTPREQ_MODE`, `HTTPREQ_USERNAME`, `HTTPREQ_PASSWORD` [^1] | [Additional configuration](https://go-acme.github.io/lego/dns/httpreq) |
|
||||
| manual | `manual` | none, but you need to run Traefik interactively [^4], turn on debug log to see instructions and press <kbd>Enter</kbd>. | |
|
||||
| Provider Name | Provider Code | Environment Variables | |
|
||||
|------------------------------------------------------------------------|--------------------|---------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------|
|
||||
| [ACME DNS](https://github.com/joohoi/acme-dns) | `acme-dns` | `ACME_DNS_API_BASE`, `ACME_DNS_STORAGE_PATH` | [Additional configuration](https://go-acme.github.io/lego/dns/acme-dns) |
|
||||
| [Alibaba Cloud](https://www.alibabacloud.com) | `alidns` | `ALICLOUD_ACCESS_KEY`, `ALICLOUD_SECRET_KEY`, `ALICLOUD_REGION_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/alidns) |
|
||||
| [all-inkl](https://all-inkl.com) | `allinkl` | `ALL_INKL_LOGIN`, `ALL_INKL_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/allinkl) |
|
||||
| [ArvanCloud](https://www.arvancloud.com/en) | `arvancloud` | `ARVANCLOUD_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/arvancloud) |
|
||||
| [Auroradns](https://www.pcextreme.com/dns-health-checks) | `auroradns` | `AURORA_USER_ID`, `AURORA_KEY`, `AURORA_ENDPOINT` | [Additional configuration](https://go-acme.github.io/lego/dns/auroradns) |
|
||||
| [Autodns](https://www.internetx.com/domains/autodns/) | `autodns` | `AUTODNS_API_USER`, `AUTODNS_API_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/autodns) |
|
||||
| [Azure](https://azure.microsoft.com/services/dns/) | `azure` | `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_SUBSCRIPTION_ID`, `AZURE_TENANT_ID`, `AZURE_RESOURCE_GROUP`, `[AZURE_METADATA_ENDPOINT]` | [Additional configuration](https://go-acme.github.io/lego/dns/azure) |
|
||||
| [Bindman](https://github.com/labbsr0x/bindman-dns-webhook) | `bindman` | `BINDMAN_MANAGER_ADDRESS` | [Additional configuration](https://go-acme.github.io/lego/dns/bindman) |
|
||||
| [Blue Cat](https://www.bluecatnetworks.com/) | `bluecat` | `BLUECAT_SERVER_URL`, `BLUECAT_USER_NAME`, `BLUECAT_PASSWORD`, `BLUECAT_CONFIG_NAME`, `BLUECAT_DNS_VIEW` | [Additional configuration](https://go-acme.github.io/lego/dns/bluecat) |
|
||||
| [Brandit](https://www.brandit.com) | `brandit` | `BRANDIT_API_USERNAME`, `BRANDIT_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/brandit) |
|
||||
| [Bunny](https://bunny.net) | `bunny` | `BUNNY_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/bunny) |
|
||||
| [Checkdomain](https://www.checkdomain.de/) | `checkdomain` | `CHECKDOMAIN_TOKEN`, | [Additional configuration](https://go-acme.github.io/lego/dns/checkdomain/) |
|
||||
| [Civo](https://www.civo.com/) | `civo` | `CIVO_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/civo) |
|
||||
| [CloudDNS](https://vshosting.eu/) | `clouddns` | `CLOUDDNS_CLIENT_ID`, `CLOUDDNS_EMAIL`, `CLOUDDNS_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/clouddns) |
|
||||
| [Cloudflare](https://www.cloudflare.com) | `cloudflare` | `CF_API_EMAIL`, `CF_API_KEY` [^5] or `CF_DNS_API_TOKEN`, `[CF_ZONE_API_TOKEN]` | [Additional configuration](https://go-acme.github.io/lego/dns/cloudflare) |
|
||||
| [ClouDNS](https://www.cloudns.net/) | `cloudns` | `CLOUDNS_AUTH_ID`, `CLOUDNS_AUTH_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/cloudns) |
|
||||
| [CloudXNS](https://www.cloudxns.net) | `cloudxns` | `CLOUDXNS_API_KEY`, `CLOUDXNS_SECRET_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/cloudxns) |
|
||||
| [ConoHa](https://www.conoha.jp) | `conoha` | `CONOHA_TENANT_ID`, `CONOHA_API_USERNAME`, `CONOHA_API_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/conoha) |
|
||||
| [Constellix](https://constellix.com) | `constellix` | `CONSTELLIX_API_KEY`, `CONSTELLIX_SECRET_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/constellix) |
|
||||
| [deSEC](https://desec.io) | `desec` | `DESEC_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/desec) |
|
||||
| [DigitalOcean](https://www.digitalocean.com) | `digitalocean` | `DO_AUTH_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/digitalocean) |
|
||||
| [DNS Made Easy](https://dnsmadeeasy.com) | `dnsmadeeasy` | `DNSMADEEASY_API_KEY`, `DNSMADEEASY_API_SECRET`, `DNSMADEEASY_SANDBOX` | [Additional configuration](https://go-acme.github.io/lego/dns/dnsmadeeasy) |
|
||||
| [dnsHome.de](https://www.dnshome.de) | `dnsHomede` | `DNSHOMEDE_CREDENTIALS` | [Additional configuration](https://go-acme.github.io/lego/dns/dnshomede) |
|
||||
| [DNSimple](https://dnsimple.com) | `dnsimple` | `DNSIMPLE_OAUTH_TOKEN`, `DNSIMPLE_BASE_URL` | [Additional configuration](https://go-acme.github.io/lego/dns/dnsimple) |
|
||||
| [DNSPod](https://www.dnspod.com/) | `dnspod` | `DNSPOD_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/dnspod) |
|
||||
| [Domain Offensive (do.de)](https://www.do.de/) | `dode` | `DODE_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/dode) |
|
||||
| [Domeneshop](https://domene.shop) | `domeneshop` | `DOMENESHOP_API_TOKEN`, `DOMENESHOP_API_SECRET` | [Additional configuration](https://go-acme.github.io/lego/dns/domeneshop) |
|
||||
| [DreamHost](https://www.dreamhost.com/) | `dreamhost` | `DREAMHOST_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/dreamhost) |
|
||||
| [Duck DNS](https://www.duckdns.org/) | `duckdns` | `DUCKDNS_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/duckdns) |
|
||||
| [Dyn](https://dyn.com) | `dyn` | `DYN_CUSTOMER_NAME`, `DYN_USER_NAME`, `DYN_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/dyn) |
|
||||
| [Dynu](https://www.dynu.com) | `dynu` | `DYNU_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/dynu) |
|
||||
| [EasyDNS](https://easydns.com/) | `easydns` | `EASYDNS_TOKEN`, `EASYDNS_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/easydns) |
|
||||
| [EdgeDNS](https://www.akamai.com/) | `edgedns` | `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET`, `AKAMAI_ACCESS_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/edgedns) |
|
||||
| [Epik](https://www.epik.com) | `epik` | `EPIK_SIGNATURE` | [Additional configuration](https://go-acme.github.io/lego/dns/epik) |
|
||||
| [Exoscale](https://www.exoscale.com) | `exoscale` | `EXOSCALE_API_KEY`, `EXOSCALE_API_SECRET`, `EXOSCALE_ENDPOINT` | [Additional configuration](https://go-acme.github.io/lego/dns/exoscale) |
|
||||
| [Fast DNS](https://www.akamai.com/) | `fastdns` | `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET`, `AKAMAI_ACCESS_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/edgedns) |
|
||||
| [Freemyip.com](https://freemyip.com) | `freemyip` | `FREEMYIP_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/freemyip) |
|
||||
| [G-Core Lab](https://gcorelabs.com/dns/) | `gcore` | `GCORE_PERMANENT_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/gcore) |
|
||||
| [Gandi v5](https://doc.livedns.gandi.net) | `gandiv5` | `GANDIV5_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/gandiv5) |
|
||||
| [Gandi](https://www.gandi.net) | `gandi` | `GANDI_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/gandi) |
|
||||
| [Glesys](https://glesys.com/) | `glesys` | `GLESYS_API_USER`, `GLESYS_API_KEY`, `GLESYS_DOMAIN` | [Additional configuration](https://go-acme.github.io/lego/dns/glesys) |
|
||||
| [GoDaddy](https://www.godaddy.com) | `godaddy` | `GODADDY_API_KEY`, `GODADDY_API_SECRET` | [Additional configuration](https://go-acme.github.io/lego/dns/godaddy) |
|
||||
| [Google Domains](https://domains.google) | `googledomains` | `GOOGLE_DOMAINS_ACCESS_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/googledomains) |
|
||||
| [Google Cloud DNS](https://cloud.google.com/dns/docs/) | `gcloud` | `GCE_PROJECT`, Application Default Credentials [^2] [^3], [`GCE_SERVICE_ACCOUNT_FILE`] | [Additional configuration](https://go-acme.github.io/lego/dns/gcloud) |
|
||||
| [Hetzner](https://hetzner.com) | `hetzner` | `HETZNER_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/hetzner) |
|
||||
| [hosting.de](https://www.hosting.de) | `hostingde` | `HOSTINGDE_API_KEY`, `HOSTINGDE_ZONE_NAME` | [Additional configuration](https://go-acme.github.io/lego/dns/hostingde) |
|
||||
| [Hosttech](https://www.hosttech.eu) | `hosttech` | `HOSTTECH_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/hosttech) |
|
||||
| [Hurricane Electric](https://dns.he.net) | `hurricane` | `HURRICANE_TOKENS` [^6] | [Additional configuration](https://go-acme.github.io/lego/dns/hurricane) |
|
||||
| [HyperOne](https://www.hyperone.com) | `hyperone` | `HYPERONE_PASSPORT_LOCATION`, `HYPERONE_LOCATION_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/hyperone) |
|
||||
| [IBM Cloud (SoftLayer)](https://www.ibm.com/cloud/) | `ibmcloud` | `SOFTLAYER_USERNAME`, `SOFTLAYER_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/ibmcloud) |
|
||||
| [IIJ DNS Platform Service](https://www.iij.ad.jp) | `iijdpf` | `IIJ_DPF_API_TOKEN` , `IIJ_DPF_DPM_SERVICE_CODE` | [Additional configuration](https://go-acme.github.io/lego/dns/iijdpf) |
|
||||
| [IIJ](https://www.iij.ad.jp/) | `iij` | `IIJ_API_ACCESS_KEY`, `IIJ_API_SECRET_KEY`, `IIJ_DO_SERVICE_CODE` | [Additional configuration](https://go-acme.github.io/lego/dns/iij) |
|
||||
| [Infoblox](https://www.infoblox.com/) | `infoblox` | `INFOBLOX_USERNAME`, `INFOBLOX_PASSWORD`, `INFOBLOX_HOST` | [Additional configuration](https://go-acme.github.io/lego/dns/infoblox) |
|
||||
| [Infomaniak](https://www.infomaniak.com) | `infomaniak` | `INFOMANIAK_ACCESS_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/infomaniak) |
|
||||
| [Internet.bs](https://internetbs.net) | `internetbs` | `INTERNET_BS_API_KEY`, `INTERNET_BS_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/internetbs) |
|
||||
| [INWX](https://www.inwx.de/en) | `inwx` | `INWX_USERNAME`, `INWX_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/inwx) |
|
||||
| [ionos](https://ionos.com/) | `ionos` | `IONOS_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/ionos) |
|
||||
| [iwantmyname](https://iwantmyname.com) | `iwantmyname` | `IWANTMYNAME_USERNAME` , `IWANTMYNAME_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/iwantmyname) |
|
||||
| [Joker.com](https://joker.com) | `joker` | `JOKER_API_MODE` with `JOKER_API_KEY` or `JOKER_USERNAME`, `JOKER_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/joker) |
|
||||
| [Liara](https://liara.ir) | `liara` | `LIARA_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/liara) |
|
||||
| [Lightsail](https://aws.amazon.com/lightsail/) | `lightsail` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `DNS_ZONE` | [Additional configuration](https://go-acme.github.io/lego/dns/lightsail) |
|
||||
| [Linode v4](https://www.linode.com) | `linode` | `LINODE_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/linode) |
|
||||
| [Liquid Web](https://www.liquidweb.com/) | `liquidweb` | `LIQUID_WEB_PASSWORD`, `LIQUID_WEB_USERNAME`, `LIQUID_WEB_ZONE` | [Additional configuration](https://go-acme.github.io/lego/dns/liquidweb) |
|
||||
| [Loopia](https://loopia.com/) | `loopia` | `LOOPIA_API_PASSWORD`, `LOOPIA_API_USER` | [Additional configuration](https://go-acme.github.io/lego/dns/loopia) |
|
||||
| [LuaDNS](https://luadns.com) | `luadns` | `LUADNS_API_USERNAME`, `LUADNS_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/luadns) |
|
||||
| [MyDNS.jp](https://www.mydns.jp/) | `mydnsjp` | `MYDNSJP_MASTER_ID`, `MYDNSJP_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/mydnsjp) |
|
||||
| [Mythic Beasts](https://www.mythic-beasts.com) | `mythicbeasts` | `MYTHICBEASTS_USER_NAME`, `MYTHICBEASTS_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/mythicbeasts) |
|
||||
| [name.com](https://www.name.com/) | `namedotcom` | `NAMECOM_USERNAME`, `NAMECOM_API_TOKEN`, `NAMECOM_SERVER` | [Additional configuration](https://go-acme.github.io/lego/dns/namedotcom) |
|
||||
| [Namecheap](https://www.namecheap.com) | `namecheap` | `NAMECHEAP_API_USER`, `NAMECHEAP_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/namecheap) |
|
||||
| [Namesilo](https://www.namesilo.com/) | `namesilo` | `NAMESILO_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/namesilo) |
|
||||
| [NearlyFreeSpeech.NET](https://www.nearlyfreespeech.net/) | `nearlyfreespeech` | `NEARLYFREESPEECH_API_KEY`, `NEARLYFREESPEECH_LOGIN` | [Additional configuration](https://go-acme.github.io/lego/dns/nearlyfreespeech) |
|
||||
| [Netcup](https://www.netcup.eu/) | `netcup` | `NETCUP_CUSTOMER_NUMBER`, `NETCUP_API_KEY`, `NETCUP_API_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/netcup) |
|
||||
| [Netlify](https://www.netlify.com) | `netlify` | `NETLIFY_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/netlify) |
|
||||
| [Nicmanager](https://www.nicmanager.com) | `nicmanager` | `NICMANAGER_API_EMAIL`, `NICMANAGER_API_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/nicmanager) |
|
||||
| [NIFCloud](https://cloud.nifty.com/service/dns.htm) | `nifcloud` | `NIFCLOUD_ACCESS_KEY_ID`, `NIFCLOUD_SECRET_ACCESS_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/nifcloud) |
|
||||
| [Njalla](https://njal.la) | `njalla` | `NJALLA_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/njalla) |
|
||||
| [Nodion](https://www.nodion.com) | `nodion` | `NODION_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/nodion) |
|
||||
| [NS1](https://ns1.com/) | `ns1` | `NS1_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/ns1) |
|
||||
| [Open Telekom Cloud](https://cloud.telekom.de) | `otc` | `OTC_DOMAIN_NAME`, `OTC_USER_NAME`, `OTC_PASSWORD`, `OTC_PROJECT_NAME`, `OTC_IDENTITY_ENDPOINT` | [Additional configuration](https://go-acme.github.io/lego/dns/otc) |
|
||||
| [Openstack Designate](https://docs.openstack.org/designate) | `designate` | `OS_AUTH_URL`, `OS_USERNAME`, `OS_PASSWORD`, `OS_TENANT_NAME`, `OS_REGION_NAME` | [Additional configuration](https://go-acme.github.io/lego/dns/designate) |
|
||||
| [Oracle Cloud](https://cloud.oracle.com/home) | `oraclecloud` | `OCI_COMPARTMENT_OCID`, `OCI_PRIVKEY_FILE`, `OCI_PRIVKEY_PASS`, `OCI_PUBKEY_FINGERPRINT`, `OCI_REGION`, `OCI_TENANCY_OCID`, `OCI_USER_OCID` | [Additional configuration](https://go-acme.github.io/lego/dns/oraclecloud) |
|
||||
| [OVH](https://www.ovh.com) | `ovh` | `OVH_ENDPOINT`, `OVH_APPLICATION_KEY`, `OVH_APPLICATION_SECRET`, `OVH_CONSUMER_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/ovh) |
|
||||
| [Plesk](https://www.plesk.com) | `plesk` | `PLESK_SERVER_BASE_URL`, `PLESK_USERNAME`, `PLESK_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/plesk) |
|
||||
| [Porkbun](https://porkbun.com/) | `porkbun` | `PORKBUN_SECRET_API_KEY`, `PORKBUN_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/porkbun) |
|
||||
| [PowerDNS](https://www.powerdns.com) | `pdns` | `PDNS_API_KEY`, `PDNS_API_URL` | [Additional configuration](https://go-acme.github.io/lego/dns/pdns) |
|
||||
| [Rackspace](https://www.rackspace.com/cloud/dns) | `rackspace` | `RACKSPACE_USER`, `RACKSPACE_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/rackspace) |
|
||||
| [reg.ru](https://www.reg.ru) | `regru` | `REGRU_USERNAME`, `REGRU_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/regru) |
|
||||
| [RFC2136](https://tools.ietf.org/html/rfc2136) | `rfc2136` | `RFC2136_TSIG_KEY`, `RFC2136_TSIG_SECRET`, `RFC2136_TSIG_ALGORITHM`, `RFC2136_NAMESERVER` | [Additional configuration](https://go-acme.github.io/lego/dns/rfc2136) |
|
||||
| [RimuHosting](https://rimuhosting.com) | `rimuhosting` | `RIMUHOSTING_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/rimuhosting) |
|
||||
| [Route 53](https://aws.amazon.com/route53/) | `route53` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `[AWS_REGION]`, `[AWS_HOSTED_ZONE_ID]` or a configured user/instance IAM profile. | [Additional configuration](https://go-acme.github.io/lego/dns/route53) |
|
||||
| [Sakura Cloud](https://cloud.sakura.ad.jp/) | `sakuracloud` | `SAKURACLOUD_ACCESS_TOKEN`, `SAKURACLOUD_ACCESS_TOKEN_SECRET` | [Additional configuration](https://go-acme.github.io/lego/dns/sakuracloud) |
|
||||
| [Scaleway](https://www.scaleway.com) | `scaleway` | `SCALEWAY_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/scaleway) |
|
||||
| [Selectel](https://selectel.ru/en/) | `selectel` | `SELECTEL_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/selectel) |
|
||||
| [Servercow](https://servercow.de) | `servercow` | `SERVERCOW_USERNAME`, `SERVERCOW_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/servercow) |
|
||||
| [Simply.com](https://www.simply.com/en/domains/) | `simply` | `SIMPLY_ACCOUNT_NAME`, `SIMPLY_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/simply) |
|
||||
| [Sonic](https://www.sonic.com/) | `sonic` | `SONIC_USER_ID`, `SONIC_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/sonic) |
|
||||
| [Stackpath](https://www.stackpath.com/) | `stackpath` | `STACKPATH_CLIENT_ID`, `STACKPATH_CLIENT_SECRET`, `STACKPATH_STACK_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/stackpath) |
|
||||
| [Tencent Cloud DNS](https://cloud.tencent.com/product/cns) | `tencentcloud` | `TENCENTCLOUD_SECRET_ID`, `TENCENTCLOUD_SECRET_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/tencentcloud) |
|
||||
| [TransIP](https://www.transip.nl/) | `transip` | `TRANSIP_ACCOUNT_NAME`, `TRANSIP_PRIVATE_KEY_PATH` | [Additional configuration](https://go-acme.github.io/lego/dns/transip) |
|
||||
| [UKFast SafeDNS](https://docs.ukfast.co.uk/domains/safedns/index.html) | `safedns` | `SAFEDNS_AUTH_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/safedns) |
|
||||
| [Ultradns](https://neustarsecurityservices.com/dns-services) | `ultradns` | `ULTRADNS_USERNAME`, `ULTRADNS_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/ultradns) |
|
||||
| [Variomedia](https://www.variomedia.de/) | `variomedia` | `VARIOMEDIA_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/variomedia) |
|
||||
| [VegaDNS](https://github.com/shupp/VegaDNS-API) | `vegadns` | `SECRET_VEGADNS_KEY`, `SECRET_VEGADNS_SECRET`, `VEGADNS_URL` | [Additional configuration](https://go-acme.github.io/lego/dns/vegadns) |
|
||||
| [Vercel](https://vercel.com) | `vercel` | `VERCEL_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/vercel) |
|
||||
| [Versio](https://www.versio.nl/domeinnamen) | `versio` | `VERSIO_USERNAME`, `VERSIO_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/versio) |
|
||||
| [VinylDNS](https://www.vinyldns.io) | `vinyldns` | `VINYLDNS_ACCESS_KEY`, `VINYLDNS_SECRET_KEY`, `VINYLDNS_HOST` | [Additional configuration](https://go-acme.github.io/lego/dns/vinyldns) |
|
||||
| [VK Cloud](https://mcs.mail.ru/) | `vkcloud` | `VK_CLOUD_PASSWORD`, `VK_CLOUD_PROJECT_ID`, `VK_CLOUD_USERNAME` | [Additional configuration](https://go-acme.github.io/lego/dns/vkcloud) |
|
||||
| [Vscale](https://vscale.io/) | `vscale` | `VSCALE_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/vscale) |
|
||||
| [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/vultr) |
|
||||
| [Websupport](https://websupport.sk) | `websupport` | `WEBSUPPORT_API_KEY`, `WEBSUPPORT_SECRET` | [Additional configuration](https://go-acme.github.io/lego/dns/websupport) |
|
||||
| [WEDOS](https://www.wedos.com) | `wedos` | `WEDOS_USERNAME`, `WEDOS_WAPI_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/wedos) |
|
||||
| [Yandex Cloud](https://cloud.yandex.com/en/) | `yandexcloud` | `YANDEX_CLOUD_FOLDER_ID`, `YANDEX_CLOUD_IAM_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/yandexcloud) |
|
||||
| [Yandex](https://yandex.com) | `yandex` | `YANDEX_PDD_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/yandex) |
|
||||
| [Zone.ee](https://www.zone.ee) | `zoneee` | `ZONEEE_API_USER`, `ZONEEE_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/zoneee) |
|
||||
| [Zonomi](https://zonomi.com) | `zonomi` | `ZONOMI_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/zonomi) |
|
||||
| External Program | `exec` | `EXEC_PATH` | [Additional configuration](https://go-acme.github.io/lego/dns/exec) |
|
||||
| HTTP request | `httpreq` | `HTTPREQ_ENDPOINT`, `HTTPREQ_MODE`, `HTTPREQ_USERNAME`, `HTTPREQ_PASSWORD` [^1] | [Additional configuration](https://go-acme.github.io/lego/dns/httpreq) |
|
||||
| manual | `manual` | none, but you need to run Traefik interactively [^4], turn on debug log to see instructions and press <kbd>Enter</kbd>. | |
|
||||
|
||||
[^1]: More information about the HTTP message format can be found [here](https://go-acme.github.io/lego/dns/httpreq/).
|
||||
[^2]: [Providing credentials to your application](https://cloud.google.com/docs/authentication/production).
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
## Dynamic configuration
|
||||
labels:
|
||||
- traefik.http.routers.blog.rule=Host(`example.com`) && Path(`/blog`)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
## Dynamic configuration
|
||||
labels:
|
||||
- traefik.http.routers.blog.rule=(Host(`example.com`) && Path(`/blog`)) || Host(`blog.example.org`)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
## Dynamic configuration
|
||||
labels:
|
||||
- traefik.http.routers.blog.rule=Host(`example.com`) && Path(`/blog`)
|
||||
|
|
|
@ -87,7 +87,7 @@ A certificate resolver requests certificates for a set of domain names inferred
|
|||
|
||||
!!! example "Domain from Router's Rule Example"
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
## Dynamic configuration
|
||||
labels:
|
||||
- traefik.http.routers.blog.rule=Host(`monitoring.yak-bebop.ts.net`) && Path(`/metrics`)
|
||||
|
@ -141,7 +141,7 @@ A certificate resolver requests certificates for a set of domain names inferred
|
|||
|
||||
!!! example "Domain from Router's tls.domain Example"
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
## Dynamic configuration
|
||||
labels:
|
||||
- traefik.http.routers.blog.rule=Path(`/metrics`)
|
||||
|
|
|
@ -211,7 +211,7 @@ spec:
|
|||
- bar.example.org
|
||||
```
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
## Dynamic configuration
|
||||
labels:
|
||||
- "traefik.tls.stores.default.defaultgeneratedcert.resolver=myresolver"
|
||||
|
|
|
@ -14,7 +14,7 @@ The AddPrefix middleware updates the path of a request before forwarding it.
|
|||
|
||||
## Configuration Examples
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# Prefixing with /foo
|
||||
labels:
|
||||
- "traefik.http.middlewares.add-foo.addprefix.prefix=/foo"
|
||||
|
|
|
@ -14,7 +14,7 @@ The BasicAuth middleware restricts access to your services to known users.
|
|||
|
||||
## Configuration Examples
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# Declaring the user list
|
||||
#
|
||||
# Note: when used in docker-compose.yml all dollar signs in the hash need to be doubled for escaping.
|
||||
|
@ -88,7 +88,7 @@ The `users` option is an array of authorized users. Each user must be declared u
|
|||
Please note that these keys are not hashed or encrypted in any way, and therefore is less secure than other methods.
|
||||
You can find more information on the [Kubernetes Basic Authentication Secret Documentation](https://kubernetes.io/docs/concepts/configuration/secret/#basic-authentication-secret)
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# Declaring the user list
|
||||
#
|
||||
# Note: when used in docker-compose.yml all dollar signs in the hash need to be doubled for escaping.
|
||||
|
@ -177,7 +177,7 @@ The file content is a list of `name:hashed-password`.
|
|||
- If both `users` and `usersFile` are provided, the two are merged. The contents of `usersFile` have precedence over the values in `users`.
|
||||
- Because it does not make much sense to refer to a file path on Kubernetes, the `usersFile` field doesn't exist for Kubernetes IngressRoute, and one should use the `secret` field instead.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-auth.basicauth.usersfile=/path/to/my/usersfile"
|
||||
```
|
||||
|
@ -233,7 +233,7 @@ http:
|
|||
|
||||
You can customize the realm for the authentication with the `realm` option. The default value is `traefik`.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-auth.basicauth.realm=MyRealm"
|
||||
```
|
||||
|
@ -270,7 +270,7 @@ http:
|
|||
|
||||
You can define a header field to store the authenticated user using the `headerField`option.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.my-auth.basicauth.headerField=X-WebAuth-User"
|
||||
```
|
||||
|
@ -309,7 +309,7 @@ http:
|
|||
|
||||
Set the `removeHeader` option to `true` to remove the authorization header before forwarding the request to your service. (Default value is `false`.)
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-auth.basicauth.removeheader=true"
|
||||
```
|
||||
|
|
|
@ -18,7 +18,7 @@ This can help services avoid large amounts of data (`multipart/form-data` for ex
|
|||
|
||||
## Configuration Examples
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# Sets the maximum request body to 2MB
|
||||
labels:
|
||||
- "traefik.http.middlewares.limit.buffering.maxRequestBodyBytes=2000000"
|
||||
|
@ -66,7 +66,7 @@ The `maxRequestBodyBytes` option configures the maximum allowed body size for th
|
|||
|
||||
If the request exceeds the allowed size, it is not forwarded to the service, and the client gets a `413` (Request Entity Too Large) response.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.limit.buffering.maxRequestBodyBytes=2000000"
|
||||
```
|
||||
|
@ -105,7 +105,7 @@ _Optional, Default=1048576_
|
|||
|
||||
You can configure a threshold (in bytes) from which the request will be buffered on disk instead of in memory with the `memRequestBodyBytes` option.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.limit.buffering.memRequestBodyBytes=2000000"
|
||||
```
|
||||
|
@ -146,7 +146,7 @@ The `maxResponseBodyBytes` option configures the maximum allowed response size f
|
|||
|
||||
If the response exceeds the allowed size, it is not forwarded to the client. The client gets a `500` (Internal Server Error) response instead.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.limit.buffering.maxResponseBodyBytes=2000000"
|
||||
```
|
||||
|
@ -185,7 +185,7 @@ _Optional, Default=1048576_
|
|||
|
||||
You can configure a threshold (in bytes) from which the response will be buffered on disk instead of in memory with the `memResponseBodyBytes` option.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.limit.buffering.memResponseBodyBytes=2000000"
|
||||
```
|
||||
|
@ -226,7 +226,7 @@ You can have the Buffering middleware replay the request using `retryExpression`
|
|||
|
||||
??? example "Retries once in the case of a network error"
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.limit.buffering.retryExpression=IsNetworkError() && Attempts() < 2"
|
||||
```
|
||||
|
|
|
@ -17,7 +17,7 @@ It makes reusing the same groups easier.
|
|||
|
||||
Below is an example of a Chain containing `AllowList`, `BasicAuth`, and `RedirectScheme`.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.routers.router1.service=service1"
|
||||
- "traefik.http.routers.router1.middlewares=secured"
|
||||
|
|
|
@ -30,7 +30,7 @@ To assess if your system is healthy, the circuit breaker constantly monitors the
|
|||
|
||||
## Configuration Examples
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# Latency Check
|
||||
labels:
|
||||
- "traefik.http.middlewares.latency-check.circuitbreaker.expression=LatencyAtQuantileMS(50.0) > 100"
|
||||
|
|
|
@ -15,7 +15,7 @@ The activation of compression, and the compression method choice rely (among oth
|
|||
|
||||
## Configuration Examples
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# Enable compression
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-compress.compress=true"
|
||||
|
@ -82,7 +82,7 @@ Content types are compared in a case-insensitive, whitespace-ignored manner.
|
|||
|
||||
Note that `application/grpc` is never compressed.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-compress.compress.excludedcontenttypes=text/event-stream"
|
||||
```
|
||||
|
@ -125,7 +125,7 @@ _Optional, Default=1024_
|
|||
|
||||
Responses smaller than the specified values will not be compressed.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-compress.compress.minresponsebodybytes=1200"
|
||||
```
|
||||
|
|
|
@ -18,7 +18,7 @@ when it is not set by the backend.
|
|||
|
||||
## Configuration Examples
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# Enable auto-detection
|
||||
labels:
|
||||
- "traefik.http.middlewares.autodetect.contenttype=true"
|
||||
|
|
|
@ -14,7 +14,7 @@ The DigestAuth middleware restricts access to your services to known users.
|
|||
|
||||
## Configuration Examples
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# Declaring the user list
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-auth.digestauth.users=test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e"
|
||||
|
@ -72,7 +72,7 @@ The `users` option is an array of authorized users. Each user will be declared u
|
|||
- If both `users` and `usersFile` are provided, the two are merged. The contents of `usersFile` have precedence over the values in `users`.
|
||||
- For security reasons, the field `users` doesn't exist for Kubernetes IngressRoute, and one should use the `secret` field instead.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-auth.digestauth.users=test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e"
|
||||
```
|
||||
|
@ -132,7 +132,7 @@ The file content is a list of `name:realm:encoded-password`.
|
|||
- If both `users` and `usersFile` are provided, the two are merged. The contents of `usersFile` have precedence over the values in `users`.
|
||||
- Because it does not make much sense to refer to a file path on Kubernetes, the `usersFile` field doesn't exist for Kubernetes IngressRoute, and one should use the `secret` field instead.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-auth.digestauth.usersfile=/path/to/my/usersfile"
|
||||
```
|
||||
|
@ -188,7 +188,7 @@ http:
|
|||
|
||||
You can customize the realm for the authentication with the `realm` option. The default value is `traefik`.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-auth.digestauth.realm=MyRealm"
|
||||
```
|
||||
|
@ -225,7 +225,7 @@ http:
|
|||
|
||||
You can customize the header field for the authenticated user using the `headerField`option.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.my-auth.digestauth.headerField=X-WebAuth-User"
|
||||
```
|
||||
|
@ -264,7 +264,7 @@ http:
|
|||
|
||||
Set the `removeHeader` option to `true` to remove the authorization header before forwarding the request to your service. (Default value is `false`.)
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-auth.digestauth.removeheader=true"
|
||||
```
|
||||
|
|
|
@ -18,7 +18,7 @@ The Errors middleware returns a custom page in lieu of the default, according to
|
|||
|
||||
## Configuration Examples
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# Dynamic Custom Error Page for 5XX Status Code
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-errors.errors.status=500-599"
|
||||
|
|
|
@ -16,7 +16,7 @@ Otherwise, the response from the authentication server is returned.
|
|||
|
||||
## Configuration Examples
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# Forward authentication to example.com
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-auth.forwardauth.address=https://example.com/auth"
|
||||
|
@ -72,7 +72,7 @@ The following request properties are provided to the forward-auth target endpoin
|
|||
|
||||
The `address` option defines the authentication server address.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-auth.forwardauth.address=https://example.com/auth"
|
||||
```
|
||||
|
@ -109,7 +109,7 @@ http:
|
|||
|
||||
Set the `trustForwardHeader` option to `true` to trust all `X-Forwarded-*` headers.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-auth.forwardauth.trustForwardHeader=true"
|
||||
```
|
||||
|
@ -150,7 +150,7 @@ http:
|
|||
The `authResponseHeaders` option is the list of headers to copy from the authentication server response and set on
|
||||
forwarded request, replacing any existing conflicting headers.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-auth.forwardauth.authResponseHeaders=X-Auth-User, X-Secret"
|
||||
```
|
||||
|
@ -197,7 +197,7 @@ set on forwarded request, after stripping all headers that match the regex.
|
|||
It allows partial matching of the regular expression against the header key.
|
||||
The start of string (`^`) and end of string (`$`) anchors should be used to ensure a full match against the header key.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-auth.forwardauth.authResponseHeadersRegex=^X-"
|
||||
```
|
||||
|
@ -245,7 +245,7 @@ The `authRequestHeaders` option is the list of the headers to copy from the requ
|
|||
It allows filtering headers that should not be passed to the authentication server.
|
||||
If not set or empty then all request headers are passed.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-auth.forwardauth.authRequestHeaders=Accept,X-CustomHeader"
|
||||
```
|
||||
|
@ -298,7 +298,7 @@ _Optional_
|
|||
`ca` is the path to the certificate authority used for the secured connection to the authentication server,
|
||||
it defaults to the system bundle.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-auth.forwardauth.tls.ca=path/to/local.crt"
|
||||
```
|
||||
|
@ -355,7 +355,7 @@ _Optional_
|
|||
`cert` is the path to the public certificate used for the secure connection to the authentication server.
|
||||
When using this option, setting the `key` option is required.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-auth.forwardauth.tls.cert=path/to/foo.cert"
|
||||
- "traefik.http.middlewares.test-auth.forwardauth.tls.key=path/to/foo.key"
|
||||
|
@ -420,7 +420,7 @@ _Optional_
|
|||
`key` is the path to the private key used for the secure connection to the authentication server.
|
||||
When using this option, setting the `cert` option is required.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-auth.forwardauth.tls.cert=path/to/foo.cert"
|
||||
- "traefik.http.middlewares.test-auth.forwardauth.tls.key=path/to/foo.key"
|
||||
|
@ -484,7 +484,7 @@ _Optional, Default=false_
|
|||
|
||||
If `insecureSkipVerify` is `true`, the TLS connection to the authentication server accepts any certificate presented by the server regardless of the hostnames it covers.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-auth.forwardauth.tls.insecureSkipVerify=true"
|
||||
```
|
||||
|
|
|
@ -17,7 +17,7 @@ The GrpcWeb middleware converts gRPC Web requests to HTTP/2 gRPC requests before
|
|||
|
||||
## Configuration Examples
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-grpcweb.grpcweb.allowOrigins=*"
|
||||
```
|
||||
|
|
|
@ -20,7 +20,7 @@ A set of forwarded headers are automatically added by default. See the [FAQ](../
|
|||
|
||||
The following example adds the `X-Script-Name` header to the proxied request and the `X-Custom-Response-Header` header to the response
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.testHeader.headers.customrequestheaders.X-Script-Name=test"
|
||||
- "traefik.http.middlewares.testHeader.headers.customresponseheaders.X-Custom-Response-Header=value"
|
||||
|
@ -69,7 +69,7 @@ http:
|
|||
In the following example, requests are proxied with an extra `X-Script-Name` header while their `X-Custom-Request-Header` header gets stripped,
|
||||
and responses are stripped of their `X-Custom-Response-Header` header.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.testheader.headers.customrequestheaders.X-Script-Name=test"
|
||||
- "traefik.http.middlewares.testheader.headers.customrequestheaders.X-Custom-Request-Header="
|
||||
|
@ -123,7 +123,7 @@ http:
|
|||
Security-related headers (HSTS headers, Browser XSS filter, etc) can be managed similarly to custom headers as shown above.
|
||||
This functionality makes it possible to easily use security features by adding headers.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.testHeader.headers.framedeny=true"
|
||||
- "traefik.http.middlewares.testHeader.headers.browserxssfilter=true"
|
||||
|
@ -170,7 +170,7 @@ instead the response will be generated and sent back to the client directly.
|
|||
Please note that the example below is by no means authoritative or exhaustive,
|
||||
and should not be used as is for production.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.testheader.headers.accesscontrolallowmethods=GET,OPTIONS,PUT"
|
||||
- "traefik.http.middlewares.testheader.headers.accesscontrolallowheaders=*"
|
||||
|
|
|
@ -14,7 +14,7 @@ To proactively prevent services from being overwhelmed with high load, the numbe
|
|||
|
||||
## Configuration Examples
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-inflightreq.inflightreq.amount=10"
|
||||
```
|
||||
|
@ -57,7 +57,7 @@ http:
|
|||
The `amount` option defines the maximum amount of allowed simultaneous in-flight request.
|
||||
The middleware responds with `HTTP 429 Too Many Requests` if there are already `amount` requests in progress (based on the same `sourceCriterion` strategy).
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-inflightreq.inflightreq.amount=10"
|
||||
```
|
||||
|
@ -122,7 +122,7 @@ The `depth` option tells Traefik to use the `X-Forwarded-For` header and select
|
|||
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `3` | `"11.0.0.1"` |
|
||||
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `5` | `""` |
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.ipstrategy.depth=2"
|
||||
```
|
||||
|
@ -176,7 +176,7 @@ http:
|
|||
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"15.0.0.1,16.0.0.1"` | `"13.0.0.1"` |
|
||||
| `"10.0.0.1,11.0.0.1"` | `"10.0.0.1,11.0.0.1"` | `""` |
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7"
|
||||
```
|
||||
|
@ -222,7 +222,7 @@ http:
|
|||
|
||||
Name of the header used to group incoming requests.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.requestheadername=username"
|
||||
```
|
||||
|
@ -262,7 +262,7 @@ http:
|
|||
|
||||
Whether to consider the request host as the source.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.requesthost=true"
|
||||
```
|
||||
|
|
|
@ -12,7 +12,7 @@ IPAllowList accepts / refuses requests based on the client IP.
|
|||
|
||||
## Configuration Examples
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# Accepts request from defined IP
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-ipallowlist.ipallowlist.sourcerange=127.0.0.1/32, 192.168.1.7"
|
||||
|
@ -83,7 +83,7 @@ The `depth` option tells Traefik to use the `X-Forwarded-For` header and take th
|
|||
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `3` | `"11.0.0.1"` |
|
||||
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `5` | `""` |
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# Allowlisting Based on `X-Forwarded-For` with `depth=2`
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-ipallowlist.ipallowlist.sourcerange=127.0.0.1/32, 192.168.1.7"
|
||||
|
@ -149,7 +149,7 @@ http:
|
|||
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"15.0.0.1,16.0.0.1"` | `"13.0.0.1"` |
|
||||
| `"10.0.0.1,11.0.0.1"` | `"10.0.0.1,11.0.0.1"` | `""` |
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# Exclude from `X-Forwarded-For`
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-ipallowlist.ipallowlist.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7"
|
||||
|
|
|
@ -12,7 +12,7 @@ Controlling connections
|
|||
|
||||
## Configuration Example
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# As a Docker Label
|
||||
whoami:
|
||||
# A container that exposes an API to show its IP address
|
||||
|
|
|
@ -18,7 +18,7 @@ PassTLSClientCert adds the selected data from the passed client TLS certificate
|
|||
|
||||
Pass the pem in the `X-Forwarded-Tls-Client-Cert` header.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# Pass the pem in the `X-Forwarded-Tls-Client-Cert` header.
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.pem=true"
|
||||
|
@ -57,7 +57,7 @@ http:
|
|||
|
||||
??? example "Pass the pem in the `X-Forwarded-Tls-Client-Cert` header"
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# Pass all the available info in the `X-Forwarded-Tls-Client-Cert-Info` header
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.notafter=true"
|
||||
|
|
|
@ -14,7 +14,7 @@ It is based on a [token bucket](https://en.wikipedia.org/wiki/Token_bucket) impl
|
|||
|
||||
## Configuration Example
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# Here, an average of 100 requests per second is allowed.
|
||||
# In addition, a burst of 50 requests is allowed.
|
||||
labels:
|
||||
|
@ -73,7 +73,7 @@ It defaults to `0`, which means no rate limiting.
|
|||
The rate is actually defined by dividing `average` by `period`.
|
||||
So for a rate below 1 req/s, one needs to define a `period` larger than a second.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# 100 reqs/s
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-ratelimit.ratelimit.average=100"
|
||||
|
@ -121,7 +121,7 @@ r = average / period
|
|||
|
||||
It defaults to `1` second.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# 6 reqs/minute
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-ratelimit.ratelimit.average=6"
|
||||
|
@ -170,7 +170,7 @@ http:
|
|||
|
||||
It defaults to `1`.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-ratelimit.ratelimit.burst=100"
|
||||
```
|
||||
|
@ -232,7 +232,7 @@ The `depth` option tells Traefik to use the `X-Forwarded-For` header and select
|
|||
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `3` | `"11.0.0.1"` |
|
||||
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `5` | `""` |
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.ipstrategy.depth=2"
|
||||
```
|
||||
|
@ -313,7 +313,7 @@ and the first IP that is _not_ in the pool (if any) is returned.
|
|||
| `"10.0.0.1,11.0.0.1,13.0.0.1"` | `"15.0.0.1,16.0.0.1"` | `"13.0.0.1"` |
|
||||
| `"10.0.0.1,11.0.0.1"` | `"10.0.0.1,11.0.0.1"` | `""` |
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7"
|
||||
```
|
||||
|
@ -359,7 +359,7 @@ http:
|
|||
|
||||
Name of the header used to group incoming requests.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.requestheadername=username"
|
||||
```
|
||||
|
@ -399,7 +399,7 @@ http:
|
|||
|
||||
Whether to consider the request host as the source.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.requesthost=true"
|
||||
```
|
||||
|
|
|
@ -16,7 +16,7 @@ The RedirectRegex redirects a request using regex matching and replacement.
|
|||
|
||||
## Configuration Examples
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# Redirect with domain replacement
|
||||
# Note: all dollar signs need to be doubled for escaping.
|
||||
labels:
|
||||
|
|
|
@ -25,7 +25,7 @@ The RedirectScheme middleware redirects the request if the request scheme is dif
|
|||
|
||||
## Configuration Examples
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# Redirect to https
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-redirectscheme.redirectscheme.scheme=https"
|
||||
|
@ -75,7 +75,7 @@ http:
|
|||
|
||||
Set the `permanent` option to `true` to apply a permanent redirection.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# Redirect to https
|
||||
labels:
|
||||
# ...
|
||||
|
@ -123,7 +123,7 @@ http:
|
|||
|
||||
The `scheme` option defines the scheme of the new URL.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# Redirect to https
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-redirectscheme.redirectscheme.scheme=https"
|
||||
|
@ -166,7 +166,7 @@ http:
|
|||
|
||||
The `port` option defines the port of the new URL.
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# Redirect to https
|
||||
labels:
|
||||
# ...
|
||||
|
|
|
@ -16,7 +16,7 @@ Replace the path of the request URL.
|
|||
|
||||
## Configuration Examples
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# Replace the path with /foo
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-replacepath.replacepath.path=/foo"
|
||||
|
|
|
@ -16,7 +16,7 @@ The ReplaceRegex replaces the path of a URL using regex matching and replacement
|
|||
|
||||
## Configuration Examples
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# Replace path with regex
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-replacepathregex.replacepathregex.regex=^/foo/(.*)"
|
||||
|
|
|
@ -18,7 +18,7 @@ The Retry middleware has an optional configuration to enable an exponential back
|
|||
|
||||
## Configuration Examples
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# Retry 4 times with exponential backoff
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-retry.retry.attempts=4"
|
||||
|
|
|
@ -16,7 +16,7 @@ Remove the specified prefixes from the URL path.
|
|||
|
||||
## Configuration Examples
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# Strip prefix /foobar and /fiibar
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-stripprefix.stripprefix.prefixes=/foobar,/fiibar"
|
||||
|
|
|
@ -12,7 +12,7 @@ Remove the matching prefixes from the URL path.
|
|||
|
||||
## Configuration Examples
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-stripprefixregex.stripprefixregex.regex=/foo/[a-z0-9]+/[0-9]+/"
|
||||
```
|
||||
|
|
|
@ -23,7 +23,7 @@ Middlewares that use the same protocol can be combined into chains to fit every
|
|||
|
||||
## Configuration Example
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# As a Docker Label
|
||||
whoami:
|
||||
# A container that exposes an API to show its IP address
|
||||
|
|
|
@ -7,7 +7,7 @@ To proactively prevent services from being overwhelmed with high load, the numbe
|
|||
|
||||
## Configuration Examples
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.tcp.middlewares.test-inflightconn.inflightconn.amount=10"
|
||||
```
|
||||
|
|
|
@ -12,7 +12,7 @@ IPAllowList accepts / refuses connections based on the client IP.
|
|||
|
||||
## Configuration Examples
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# Accepts connections from defined IP
|
||||
labels:
|
||||
- "traefik.tcp.middlewares.test-ipallowlist.ipallowlist.sourcerange=127.0.0.1/32, 192.168.1.7"
|
||||
|
|
|
@ -12,7 +12,7 @@ Controlling connections
|
|||
|
||||
## Configuration Example
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# As a Docker Label
|
||||
whoami:
|
||||
# A container that exposes an API to show its IP address
|
||||
|
|
|
@ -38,7 +38,7 @@ Then any router can refer to an instance of the wanted middleware.
|
|||
|
||||
!!! info "v1"
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.frontend.rule=Host:test.localhost;PathPrefix:/test"
|
||||
- "traefik.frontend.auth.basic.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0"
|
||||
|
@ -100,7 +100,7 @@ Then any router can refer to an instance of the wanted middleware.
|
|||
|
||||
!!! info "v2"
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.routers.router0.rule=Host(`test.localhost`) && PathPrefix(`/test`)"
|
||||
- "traefik.http.routers.router0.middlewares=auth"
|
||||
|
@ -317,7 +317,7 @@ Then, a [router's TLS field](../routing/routers/index.md#tls) can refer to one o
|
|||
namespace: default
|
||||
```
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
# myTLSOptions must be defined by another provider, in this instance in the File Provider.
|
||||
# see the cross provider section
|
||||
|
@ -428,7 +428,7 @@ To apply a redirection:
|
|||
|
||||
!!! info "v2"
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
traefik.http.routers.app.rule: Host(`example.net`)
|
||||
traefik.http.routers.app.entrypoints: web
|
||||
|
@ -556,7 +556,7 @@ with the path `/admin` stripped, e.g. to `http://<IP>:<port>/`. In this case, yo
|
|||
|
||||
!!! info "v1"
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.frontend.rule=Host:example.org;PathPrefixStrip:/admin"
|
||||
```
|
||||
|
@ -588,7 +588,7 @@ with the path `/admin` stripped, e.g. to `http://<IP>:<port>/`. In this case, yo
|
|||
|
||||
!!! info "v2"
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.routers.admin.rule=Host(`example.org`) && PathPrefix(`/admin`)"
|
||||
- "traefik.http.routers.admin.middlewares=admin-stripprefix"
|
||||
|
@ -1044,7 +1044,7 @@ To activate the dashboard, you can either:
|
|||
|
||||
!!! info "v2"
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# dynamic configuration
|
||||
labels:
|
||||
- "traefik.http.routers.api.rule=Host(`traefik.docker.localhost`)"
|
||||
|
|
|
@ -87,3 +87,10 @@ In v3, the InfluxDB v1 metrics provider has been removed because InfluxDB v1.x m
|
|||
|
||||
In v3 the Kubernetes CRDs API Group `traefik.containo.us` has been removed.
|
||||
Please use the API Group `traefik.io` instead.
|
||||
|
||||
## Docker & Docker Swarm
|
||||
|
||||
In v3, the provider Docker has been split into 2 providers:
|
||||
|
||||
- Docker provider (without Swarm support)
|
||||
- Swarm provider (Swarm support only)
|
||||
|
|
|
@ -525,4 +525,4 @@ kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v2.10/docs/co
|
|||
|
||||
### Traefik Hub
|
||||
|
||||
In `v2.10`, Traefik Hub is GA and the `experimental.hub` flag is deprecated.
|
||||
In `v2.10`, Traefik Hub configuration has been removed because Traefik Hub v2 doesn't require this configuration.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# Dynamic Configuration
|
||||
labels:
|
||||
- "traefik.http.routers.api.rule=Host(`traefik.example.com`)"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
# Dynamic Configuration
|
||||
labels:
|
||||
- "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"
|
||||
|
|
|
@ -12,8 +12,7 @@ A Story of Labels & Containers
|
|||
|
||||
Attach labels to your containers and let Traefik do the rest!
|
||||
|
||||
Traefik works with both [Docker (standalone) Engine](https://docs.docker.com/engine/)
|
||||
and [Docker Swarm Mode](https://docs.docker.com/engine/swarm/).
|
||||
This provider works with [Docker (standalone) Engine](https://docs.docker.com/engine/).
|
||||
|
||||
!!! tip "The Quick Start Uses Docker"
|
||||
|
||||
|
@ -49,49 +48,6 @@ and [Docker Swarm Mode](https://docs.docker.com/engine/swarm/).
|
|||
- traefik.http.routers.my-container.rule=Host(`example.com`)
|
||||
```
|
||||
|
||||
??? example "Configuring Docker Swarm & Deploying / Exposing Services"
|
||||
|
||||
Enabling the docker provider (Swarm Mode)
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
docker:
|
||||
# swarm classic (1.12-)
|
||||
# endpoint: "tcp://127.0.0.1:2375"
|
||||
# docker swarm mode (1.12+)
|
||||
endpoint: "tcp://127.0.0.1:2377"
|
||||
swarmMode: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.docker]
|
||||
# swarm classic (1.12-)
|
||||
# endpoint = "tcp://127.0.0.1:2375"
|
||||
# docker swarm mode (1.12+)
|
||||
endpoint = "tcp://127.0.0.1:2377"
|
||||
swarmMode = true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
# swarm classic (1.12-)
|
||||
# --providers.docker.endpoint=tcp://127.0.0.1:2375
|
||||
# docker swarm mode (1.12+)
|
||||
--providers.docker.endpoint=tcp://127.0.0.1:2377
|
||||
--providers.docker.swarmMode=true
|
||||
```
|
||||
|
||||
Attach labels to services (not to containers) while in Swarm mode (in your docker compose file)
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
services:
|
||||
my-container:
|
||||
deploy:
|
||||
labels:
|
||||
- traefik.http.routers.my-container.rule=Host(`example.com`)
|
||||
- traefik.http.services.my-container-service.loadbalancer.server.port=8080
|
||||
```
|
||||
|
||||
## Routing Configuration
|
||||
|
||||
When using Docker as a [provider](./overview.md),
|
||||
|
@ -124,14 +80,13 @@ Port detection works as follows:
|
|||
- If a container [exposes](https://docs.docker.com/engine/reference/builder/#expose) multiple ports,
|
||||
or does not expose any port, then you must manually specify which port Traefik should use for communication
|
||||
by using the label `traefik.http.services.<service_name>.loadbalancer.server.port`
|
||||
(Read more on this label in the dedicated section in [routing](../routing/providers/docker.md#port)).
|
||||
(Read more on this label in the dedicated section in [routing](../routing/providers/docker.md#services)).
|
||||
|
||||
### Host networking
|
||||
|
||||
When exposing containers that are configured with [host networking](https://docs.docker.com/network/host/),
|
||||
the IP address of the host is resolved as follows:
|
||||
|
||||
<!-- TODO: verify and document the swarm mode case with container.Node.IPAddress coming from the API -->
|
||||
- try a lookup of `host.docker.internal`
|
||||
- if the lookup was unsuccessful, try a lookup of `host.containers.internal`, ([Podman](https://docs.podman.io/en/latest/) equivalent of `host.docker.internal`)
|
||||
- if that lookup was also unsuccessful, fall back to `127.0.0.1`
|
||||
|
@ -175,7 +130,6 @@ You can specify which Docker API Endpoint to use with the directive [`endpoint`]
|
|||
- Authorization with the [Docker Authorization Plugin Mechanism](https://web.archive.org/web/20190920092526/https://docs.docker.com/engine/extend/plugins_authorization/)
|
||||
- Accounting at networking level, by exposing the socket only inside a Docker private network, only available for Traefik.
|
||||
- Accounting at container level, by exposing the socket on a another container than Traefik's.
|
||||
With Swarm mode, it allows scheduling of Traefik on worker nodes, with only the "socket exposer" container on the manager nodes.
|
||||
- Accounting at kernel level, by enforcing kernel calls with mechanisms like [SELinux](https://en.wikipedia.org/wiki/Security-Enhanced_Linux), to only allows an identified set of actions for Traefik's process (or the "socket exposer" process).
|
||||
- SSH public key authentication (SSH is supported with Docker > 18.09)
|
||||
|
||||
|
@ -192,69 +146,13 @@ You can specify which Docker API Endpoint to use with the directive [`endpoint`]
|
|||
- [Letting Traefik run on Worker Nodes](https://blog.mikesir87.io/2018/07/letting-traefik-run-on-worker-nodes/)
|
||||
- [Docker Socket Proxy from Tecnativa](https://github.com/Tecnativa/docker-socket-proxy)
|
||||
|
||||
## Docker Swarm Mode
|
||||
|
||||
To enable Docker Swarm (instead of standalone Docker) as a configuration provider,
|
||||
set the [`swarmMode`](#swarmmode) directive to `true`.
|
||||
|
||||
### Routing Configuration with Labels
|
||||
|
||||
While in Swarm Mode, Traefik uses labels found on services, not on individual containers.
|
||||
|
||||
Therefore, if you use a compose file with Swarm Mode, labels should be defined in the
|
||||
[`deploy`](https://docs.docker.com/compose/compose-file/compose-file-v3/#labels-1) part of your service.
|
||||
|
||||
This behavior is only enabled for docker-compose version 3+ ([Compose file reference](https://docs.docker.com/compose/compose-file/compose-file-v3/)).
|
||||
|
||||
### Port Detection
|
||||
|
||||
Docker Swarm does not provide any [port detection](#port-detection) information to Traefik.
|
||||
|
||||
Therefore, you **must** specify the port to use for communication by using the label `traefik.http.services.<service_name>.loadbalancer.server.port`
|
||||
(Check the reference for this label in the [routing section for Docker](../routing/providers/docker.md#port)).
|
||||
|
||||
### Docker API Access
|
||||
|
||||
Docker Swarm Mode follows the same rules as Docker [API Access](#docker-api-access).
|
||||
|
||||
Since the Swarm API is only exposed on the [manager nodes](https://docs.docker.com/engine/swarm/how-swarm-mode-works/nodes/#manager-nodes),
|
||||
these are the nodes that Traefik should be scheduled on by deploying Traefik with a constraint on the node "role":
|
||||
|
||||
```shell tab="With Docker CLI"
|
||||
docker service create \
|
||||
--constraint=node.role==manager \
|
||||
#... \
|
||||
```
|
||||
|
||||
```yml tab="With Docker Compose"
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
traefik:
|
||||
# ...
|
||||
deploy:
|
||||
placement:
|
||||
constraints:
|
||||
- node.role == manager
|
||||
```
|
||||
|
||||
!!! tip "Scheduling Traefik on Worker Nodes"
|
||||
|
||||
Following the guidelines given in the previous section ["Docker API Access"](#docker-api-access),
|
||||
if you expose the Docker API through TCP, then Traefik can be scheduled on any node if the TCP
|
||||
socket is reachable.
|
||||
|
||||
Please consider the security implications by reading the [Security Note](#security-note).
|
||||
|
||||
A good example can be found on [Bret Fisher's repository](https://github.com/BretFisher/dogvscat/blob/master/stack-proxy-global.yml#L124).
|
||||
|
||||
## Provider Configuration
|
||||
|
||||
### `endpoint`
|
||||
|
||||
_Required, Default="unix:///var/run/docker.sock"_
|
||||
|
||||
See the sections [Docker API Access](#docker-api-access) and [Docker Swarm API Access](#docker-api-access_1) for more information.
|
||||
See the [Docker API Access](#docker-api-access) section for more information.
|
||||
|
||||
??? example "Using the docker.sock"
|
||||
|
||||
|
@ -464,54 +362,6 @@ providers:
|
|||
# ...
|
||||
```
|
||||
|
||||
### `swarmMode`
|
||||
|
||||
_Optional, Default=false_
|
||||
|
||||
Enables the Swarm Mode (instead of standalone Docker).
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
docker:
|
||||
swarmMode: true
|
||||
# ...
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.docker]
|
||||
swarmMode = true
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.docker.swarmMode=true
|
||||
# ...
|
||||
```
|
||||
|
||||
### `swarmModeRefreshSeconds`
|
||||
|
||||
_Optional, Default=15_
|
||||
|
||||
Defines the polling interval (in seconds) for Swarm Mode.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
docker:
|
||||
swarmModeRefreshSeconds: 30
|
||||
# ...
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.docker]
|
||||
swarmModeRefreshSeconds = 30
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.docker.swarmModeRefreshSeconds=30
|
||||
# ...
|
||||
```
|
||||
|
||||
### `httpClientTimeout`
|
||||
|
||||
_Optional, Default=0_
|
||||
|
|
|
@ -72,7 +72,7 @@ For the list of the providers names, see the [supported providers](#supported-pr
|
|||
|
||||
Using the add-foo-prefix middleware from other providers:
|
||||
|
||||
```yaml tab="Docker"
|
||||
```yaml tab="Docker & Swarm"
|
||||
your-container: #
|
||||
image: your-docker-image
|
||||
|
||||
|
|
697
docs/content/providers/swarm.md
Normal file
697
docs/content/providers/swarm.md
Normal file
|
@ -0,0 +1,697 @@
|
|||
---
|
||||
title: "Traefik Docker Swarm Documentation"
|
||||
description: "Learn how to achieve configuration discovery in Traefik through Docker Swarm. Read the technical documentation."
|
||||
---
|
||||
|
||||
# Traefik & Docker Swarm
|
||||
|
||||
A Story of Labels & Containers
|
||||
{: .subtitle }
|
||||
|
||||
![Docker](../assets/img/providers/docker.png)
|
||||
|
||||
Attach labels to your containers and let Traefik do the rest!
|
||||
|
||||
This provider works with [Docker Swarm Mode](https://docs.docker.com/engine/swarm/).
|
||||
|
||||
!!! tip "The Quick Start Uses Docker"
|
||||
|
||||
If you have not already read it, maybe you would like to go through the [quick start guide](../getting-started/quick-start.md) that uses the Docker provider.
|
||||
|
||||
## Configuration Examples
|
||||
|
||||
??? example "Configuring Docker Swarm & Deploying / Exposing Services"
|
||||
|
||||
Enabling the Swarm provider
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
swarm:
|
||||
# swarm classic (1.12-)
|
||||
# endpoint: "tcp://127.0.0.1:2375"
|
||||
# docker swarm mode (1.12+)
|
||||
endpoint: "tcp://127.0.0.1:2377"
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.swarm]
|
||||
# swarm classic (1.12-)
|
||||
# endpoint = "tcp://127.0.0.1:2375"
|
||||
# docker swarm mode (1.12+)
|
||||
endpoint = "tcp://127.0.0.1:2377"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
# swarm classic (1.12-)
|
||||
# --providers.swarm.endpoint=tcp://127.0.0.1:2375
|
||||
# docker swarm mode (1.12+)
|
||||
--providers.swarm.endpoint=tcp://127.0.0.1:2377
|
||||
```
|
||||
|
||||
Attach labels to services (not to containers) while in Swarm mode (in your docker compose file)
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
services:
|
||||
my-container:
|
||||
deploy:
|
||||
labels:
|
||||
- traefik.http.routers.my-container.rule=Host(`example.com`)
|
||||
- traefik.http.services.my-container-service.loadbalancer.server.port=8080
|
||||
```
|
||||
|
||||
## Routing Configuration
|
||||
|
||||
When using Docker as a [provider](./overview.md),
|
||||
Traefik uses [container labels](https://docs.docker.com/engine/reference/commandline/run/#label) to retrieve its routing configuration.
|
||||
|
||||
See the list of labels in the dedicated [routing](../routing/providers/docker.md) section.
|
||||
|
||||
### Routing Configuration with Labels
|
||||
|
||||
By default, Traefik watches for [container level labels](https://docs.docker.com/config/labels-custom-metadata/) on a standalone Docker Engine.
|
||||
|
||||
When using Docker Compose, labels are specified by the directive
|
||||
[`labels`](https://docs.docker.com/compose/compose-file/compose-file-v3/#labels) from the
|
||||
["services" objects](https://docs.docker.com/compose/compose-file/compose-file-v3/#service-configuration-reference).
|
||||
|
||||
!!! tip "Not Only Docker"
|
||||
|
||||
Please note that any tool like Nomad, Terraform, Ansible, etc.
|
||||
that is able to define a Docker container with labels can work
|
||||
with Traefik and the Swarm provider.
|
||||
|
||||
While in Swarm Mode, Traefik uses labels found on services, not on individual containers.
|
||||
|
||||
Therefore, if you use a compose file with Swarm Mode, labels should be defined in the
|
||||
[`deploy`](https://docs.docker.com/compose/compose-file/compose-file-v3/#labels-1) part of your service.
|
||||
|
||||
This behavior is only enabled for docker-compose version 3+ ([Compose file reference](https://docs.docker.com/compose/compose-file/compose-file-v3/)).
|
||||
|
||||
### Port Detection
|
||||
|
||||
Traefik retrieves the private IP and port of containers from the Docker API.
|
||||
|
||||
Docker Swarm does not provide any port detection information to Traefik.
|
||||
|
||||
Therefore, you **must** specify the port to use for communication by using the label `traefik.http.services.<service_name>.loadbalancer.server.port`
|
||||
(Check the reference for this label in the [routing section for Swarm](../routing/providers/swarm.md#services)).
|
||||
|
||||
### Host networking
|
||||
|
||||
When exposing containers that are configured with [host networking](https://docs.docker.com/network/host/),
|
||||
the IP address of the host is resolved as follows:
|
||||
|
||||
<!-- TODO: verify and document the swarm mode case with container.Node.IPAddress coming from the API -->
|
||||
- try a lookup of `host.docker.internal`
|
||||
- if the lookup was unsuccessful, try a lookup of `host.containers.internal`, ([Podman](https://docs.podman.io/en/latest/) equivalent of `host.docker.internal`)
|
||||
- if that lookup was also unsuccessful, fall back to `127.0.0.1`
|
||||
|
||||
On Linux, for versions of Docker older than 20.10.0, for `host.docker.internal` to be defined, it should be provided
|
||||
as an `extra_host` to the Traefik container, using the `--add-host` flag. For example, to set it to the IP address of
|
||||
the bridge interface (`docker0` by default): `--add-host=host.docker.internal:172.17.0.1`
|
||||
|
||||
### IPv4 && IPv6
|
||||
|
||||
When using a docker stack that uses IPv6,
|
||||
Traefik will use the IPv4 container IP before its IPv6 counterpart.
|
||||
Therefore, on an IPv6 Docker stack,
|
||||
Traefik will use the IPv6 container IP.
|
||||
|
||||
### Docker API Access
|
||||
|
||||
Traefik requires access to the docker socket to get its dynamic configuration.
|
||||
|
||||
You can specify which Docker API Endpoint to use with the directive [`endpoint`](#endpoint).
|
||||
|
||||
!!! warning "Security Note"
|
||||
|
||||
Accessing the Docker API without any restriction is a security concern:
|
||||
If Traefik is attacked, then the attacker might get access to the underlying host.
|
||||
{: #security-note }
|
||||
|
||||
As explained in the [Docker Daemon Attack Surface documentation](https://docs.docker.com/engine/security/#docker-daemon-attack-surface):
|
||||
|
||||
!!! quote
|
||||
|
||||
[...] only **trusted** users should be allowed to control your Docker daemon [...]
|
||||
|
||||
??? success "Solutions"
|
||||
|
||||
Expose the Docker socket over TCP or SSH, instead of the default Unix socket file.
|
||||
It allows different implementation levels of the [AAA (Authentication, Authorization, Accounting) concepts](https://en.wikipedia.org/wiki/AAA_(computer_security)), depending on your security assessment:
|
||||
|
||||
- Authentication with Client Certificates as described in ["Protect the Docker daemon socket."](https://docs.docker.com/engine/security/protect-access/)
|
||||
- Authorize and filter requests to restrict possible actions with [the TecnativaDocker Socket Proxy](https://github.com/Tecnativa/docker-socket-proxy).
|
||||
- Authorization with the [Docker Authorization Plugin Mechanism](https://web.archive.org/web/20190920092526/https://docs.docker.com/engine/extend/plugins_authorization/)
|
||||
- Accounting at networking level, by exposing the socket only inside a Docker private network, only available for Traefik.
|
||||
- Accounting at container level, by exposing the socket on a another container than Traefik's.
|
||||
It allows scheduling of Traefik on worker nodes, with only the "socket exposer" container on the manager nodes.
|
||||
- Accounting at kernel level, by enforcing kernel calls with mechanisms like [SELinux](https://en.wikipedia.org/wiki/Security-Enhanced_Linux), to only allows an identified set of actions for Traefik's process (or the "socket exposer" process).
|
||||
- SSH public key authentication (SSH is supported with Docker > 18.09)
|
||||
|
||||
??? info "More Resources and Examples"
|
||||
|
||||
- ["Paranoid about mounting /var/run/docker.sock?"](https://medium.com/@containeroo/traefik-2-0-paranoid-about-mounting-var-run-docker-sock-22da9cb3e78c)
|
||||
- [Traefik and Docker: A Discussion with Docker Captain, Bret Fisher](https://blog.traefik.io/traefik-and-docker-a-discussion-with-docker-captain-bret-fisher-7f0b9a54ff88)
|
||||
- [KubeCon EU 2018 Keynote, Running with Scissors, from Liz Rice](https://www.youtube.com/watch?v=ltrV-Qmh3oY)
|
||||
- [Don't expose the Docker socket (not even to a container)](https://www.lvh.io/posts/dont-expose-the-docker-socket-not-even-to-a-container/)
|
||||
- [A thread on Stack Overflow about sharing the `/var/run/docker.sock` file](https://news.ycombinator.com/item?id=17983623)
|
||||
- [To DinD or not to DinD](https://blog.loof.fr/2018/01/to-dind-or-not-do-dind.html)
|
||||
- [Traefik issue GH-4174 about security with Docker socket](https://github.com/traefik/traefik/issues/4174)
|
||||
- [Inspecting Docker Activity with Socat](https://developers.redhat.com/blog/2015/02/25/inspecting-docker-activity-with-socat/)
|
||||
- [Letting Traefik run on Worker Nodes](https://blog.mikesir87.io/2018/07/letting-traefik-run-on-worker-nodes/)
|
||||
- [Docker Socket Proxy from Tecnativa](https://github.com/Tecnativa/docker-socket-proxy)
|
||||
|
||||
Since the Swarm API is only exposed on the [manager nodes](https://docs.docker.com/engine/swarm/how-swarm-mode-works/nodes/#manager-nodes),
|
||||
these are the nodes that Traefik should be scheduled on by deploying Traefik with a constraint on the node "role":
|
||||
|
||||
```shell tab="With Docker CLI"
|
||||
docker service create \
|
||||
--constraint=node.role==manager \
|
||||
#... \
|
||||
```
|
||||
|
||||
```yml tab="With Docker Compose"
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
traefik:
|
||||
# ...
|
||||
deploy:
|
||||
placement:
|
||||
constraints:
|
||||
- node.role == manager
|
||||
```
|
||||
|
||||
!!! tip "Scheduling Traefik on Worker Nodes"
|
||||
|
||||
Following the guidelines given in the previous section ["Docker API Access"](#docker-api-access),
|
||||
if you expose the Docker API through TCP, then Traefik can be scheduled on any node if the TCP
|
||||
socket is reachable.
|
||||
|
||||
Please consider the security implications by reading the [Security Note](#security-note).
|
||||
|
||||
A good example can be found on [Bret Fisher's repository](https://github.com/BretFisher/dogvscat/blob/master/stack-proxy-global.yml#L124).
|
||||
|
||||
### `endpoint`
|
||||
|
||||
_Required, Default="unix:///var/run/docker.sock"_
|
||||
|
||||
See the [Docker Swarm API Access](#docker-api-access) section for more information.
|
||||
|
||||
??? example "Using the docker.sock"
|
||||
|
||||
The docker-compose file shares the docker sock with the Traefik container
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:v3.0 # The official v2 Traefik docker image
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
```
|
||||
|
||||
We specify the docker.sock in traefik's configuration file.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
swarm:
|
||||
endpoint: "unix:///var/run/docker.sock"
|
||||
# ...
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.swarm]
|
||||
endpoint = "unix:///var/run/docker.sock"
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.swarm.endpoint=unix:///var/run/docker.sock
|
||||
# ...
|
||||
```
|
||||
|
||||
??? example "Using SSH"
|
||||
|
||||
Using Docker 18.09+ you can connect Traefik to daemon using SSH
|
||||
We specify the SSH host and user in Traefik's configuration file.
|
||||
Note that is server requires public keys for authentication you must have those accessible for user who runs Traefik.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
docker:
|
||||
endpoint: "ssh://traefik@192.168.2.5:2022"
|
||||
# ...
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.swarm]
|
||||
endpoint = "ssh://traefik@192.168.2.5:2022"
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.swarm.endpoint=ssh://traefik@192.168.2.5:2022
|
||||
# ...
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
swarm:
|
||||
endpoint: "unix:///var/run/docker.sock"
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.swarm]
|
||||
endpoint = "unix:///var/run/docker.sock"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.swarm.endpoint=unix:///var/run/docker.sock
|
||||
```
|
||||
|
||||
### `useBindPortIP`
|
||||
|
||||
_Optional, Default=false_
|
||||
|
||||
Traefik routes requests to the IP/port of the matching container.
|
||||
When setting `useBindPortIP=true`, you tell Traefik to use the IP/Port attached to the container's _binding_ instead of its inner network IP/Port.
|
||||
|
||||
When used in conjunction with the `traefik.http.services.<name>.loadbalancer.server.port` label (that tells Traefik to route requests to a specific port),
|
||||
Traefik tries to find a binding on port `traefik.http.services.<name>.loadbalancer.server.port`.
|
||||
If it cannot find such a binding, Traefik falls back on the internal network IP of the container,
|
||||
but still uses the `traefik.http.services.<name>.loadbalancer.server.port` that is set in the label.
|
||||
|
||||
??? example "Examples of `usebindportip` in different situations."
|
||||
|
||||
| port label | Container's binding | Routes to |
|
||||
|--------------------|----------------------------------------------------|----------------|
|
||||
| - | - | IntIP:IntPort |
|
||||
| - | ExtPort:IntPort | IntIP:IntPort |
|
||||
| - | ExtIp:ExtPort:IntPort | ExtIp:ExtPort |
|
||||
| LblPort | - | IntIp:LblPort |
|
||||
| LblPort | ExtIp:ExtPort:LblPort | ExtIp:ExtPort |
|
||||
| LblPort | ExtIp:ExtPort:OtherPort | IntIp:LblPort |
|
||||
| LblPort | ExtIp1:ExtPort1:IntPort1 & ExtIp2:LblPort:IntPort2 | ExtIp2:LblPort |
|
||||
|
||||
!!! info ""
|
||||
In the above table:
|
||||
|
||||
- `ExtIp` stands for "external IP found in the binding"
|
||||
- `IntIp` stands for "internal network container's IP",
|
||||
- `ExtPort` stands for "external Port found in the binding"
|
||||
- `IntPort` stands for "internal network container's port."
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
swarm:
|
||||
useBindPortIP: true
|
||||
# ...
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.swarm]
|
||||
useBindPortIP = true
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.swarm.useBindPortIP=true
|
||||
# ...
|
||||
```
|
||||
|
||||
### `exposedByDefault`
|
||||
|
||||
_Optional, Default=true_
|
||||
|
||||
Expose containers by default through Traefik.
|
||||
If set to `false`, containers that do not have a `traefik.enable=true` label are ignored from the resulting routing configuration.
|
||||
|
||||
For additional information, refer to [Restrict the Scope of Service Discovery](./overview.md#restrict-the-scope-of-service-discovery).
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
swarm:
|
||||
exposedByDefault: false
|
||||
# ...
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.swarm]
|
||||
exposedByDefault = false
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.swarm.exposedByDefault=false
|
||||
# ...
|
||||
```
|
||||
|
||||
### `network`
|
||||
|
||||
_Optional, Default=""_
|
||||
|
||||
Defines a default docker network to use for connections to all containers.
|
||||
|
||||
This option can be overridden on a per-container basis with the `traefik.docker.network` label.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
swarm:
|
||||
network: test
|
||||
# ...
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.swarm]
|
||||
network = "test"
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.swarm.network=test
|
||||
# ...
|
||||
```
|
||||
|
||||
### `defaultRule`
|
||||
|
||||
_Optional, Default=```Host(`{{ normalize .Name }}`)```_
|
||||
|
||||
The `defaultRule` option defines what routing rule to apply to a container if no rule is defined by a label.
|
||||
|
||||
It must be a valid [Go template](https://pkg.go.dev/text/template/), and can use
|
||||
[sprig template functions](https://masterminds.github.io/sprig/).
|
||||
The container service name can be accessed with the `Name` identifier,
|
||||
and the template has access to all the labels defined on this container.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
swarm:
|
||||
defaultRule: "Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)"
|
||||
# ...
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.swarm]
|
||||
defaultRule = "Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)"
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.swarm.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)
|
||||
# ...
|
||||
```
|
||||
|
||||
### `swarmMode`
|
||||
|
||||
_Optional, Default=false_
|
||||
|
||||
Enables the Swarm Mode (instead of standalone Docker).
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
swarm:
|
||||
swarmMode: true
|
||||
# ...
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.swarm]
|
||||
swarmMode = true
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.swarm.swarmMode=true
|
||||
# ...
|
||||
```
|
||||
|
||||
### `swarmModeRefreshSeconds`
|
||||
|
||||
_Optional, Default=15_
|
||||
|
||||
Defines the polling interval (in seconds) for Swarm Mode.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
swarm:
|
||||
swarmModeRefreshSeconds: 30
|
||||
# ...
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.swarm]
|
||||
swarmModeRefreshSeconds = 30
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.swarm.swarmModeRefreshSeconds=30
|
||||
# ...
|
||||
```
|
||||
|
||||
### `httpClientTimeout`
|
||||
|
||||
_Optional, Default=0_
|
||||
|
||||
Defines the client timeout (in seconds) for HTTP connections. If its value is `0`, no timeout is set.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
swarm:
|
||||
httpClientTimeout: 300
|
||||
# ...
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.swarm]
|
||||
httpClientTimeout = 300
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.swarm.httpClientTimeout=300
|
||||
# ...
|
||||
```
|
||||
|
||||
### `watch`
|
||||
|
||||
_Optional, Default=true_
|
||||
|
||||
Watch Docker events.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
swarm:
|
||||
watch: false
|
||||
# ...
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.swarm]
|
||||
watch = false
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.swarm.watch=false
|
||||
# ...
|
||||
```
|
||||
|
||||
### `constraints`
|
||||
|
||||
_Optional, Default=""_
|
||||
|
||||
The `constraints` option can be set to an expression that Traefik matches against the container labels to determine whether
|
||||
to create any route for that container. If none of the container labels match the expression, no route for that container is
|
||||
created. If the expression is empty, all detected containers are included.
|
||||
|
||||
The expression syntax is based on the `Label("key", "value")`, and `LabelRegex("key", "value")` functions,
|
||||
as well as the usual boolean logic, as shown in examples below.
|
||||
|
||||
??? example "Constraints Expression Examples"
|
||||
|
||||
```toml
|
||||
# Includes only containers having a label with key `a.label.name` and value `foo`
|
||||
constraints = "Label(`a.label.name`, `foo`)"
|
||||
```
|
||||
|
||||
```toml
|
||||
# Excludes containers having any label with key `a.label.name` and value `foo`
|
||||
constraints = "!Label(`a.label.name`, `value`)"
|
||||
```
|
||||
|
||||
```toml
|
||||
# With logical AND.
|
||||
constraints = "Label(`a.label.name`, `valueA`) && Label(`another.label.name`, `valueB`)"
|
||||
```
|
||||
|
||||
```toml
|
||||
# With logical OR.
|
||||
constraints = "Label(`a.label.name`, `valueA`) || Label(`another.label.name`, `valueB`)"
|
||||
```
|
||||
|
||||
```toml
|
||||
# With logical AND and OR, with precedence set by parentheses.
|
||||
constraints = "Label(`a.label.name`, `valueA`) && (Label(`another.label.name`, `valueB`) || Label(`yet.another.label.name`, `valueC`))"
|
||||
```
|
||||
|
||||
```toml
|
||||
# Includes only containers having a label with key `a.label.name` and a value matching the `a.+` regular expression.
|
||||
constraints = "LabelRegex(`a.label.name`, `a.+`)"
|
||||
```
|
||||
|
||||
For additional information, refer to [Restrict the Scope of Service Discovery](./overview.md#restrict-the-scope-of-service-discovery).
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
swarm:
|
||||
constraints: "Label(`a.label.name`,`foo`)"
|
||||
# ...
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.swarm]
|
||||
constraints = "Label(`a.label.name`,`foo`)"
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.swarm.constraints=Label(`a.label.name`,`foo`)
|
||||
# ...
|
||||
```
|
||||
|
||||
### `tls`
|
||||
|
||||
_Optional_
|
||||
|
||||
Defines the TLS configuration used for the secure connection to Docker.
|
||||
|
||||
#### `ca`
|
||||
|
||||
_Optional_
|
||||
|
||||
`ca` is the path to the certificate authority used for the secure connection to Docker,
|
||||
it defaults to the system bundle.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
swarm:
|
||||
tls:
|
||||
ca: path/to/ca.crt
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.swarm.tls]
|
||||
ca = "path/to/ca.crt"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.swarm.tls.ca=path/to/ca.crt
|
||||
```
|
||||
|
||||
#### `cert`
|
||||
|
||||
`cert` is the path to the public certificate used for the secure connection to Docker.
|
||||
When using this option, setting the `key` option is required.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
swarm:
|
||||
tls:
|
||||
cert: path/to/foo.cert
|
||||
key: path/to/foo.key
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.swarm.tls]
|
||||
cert = "path/to/foo.cert"
|
||||
key = "path/to/foo.key"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.swarm.tls.cert=path/to/foo.cert
|
||||
--providers.swarm.tls.key=path/to/foo.key
|
||||
```
|
||||
|
||||
#### `key`
|
||||
|
||||
_Optional_
|
||||
|
||||
`key` is the path to the private key used for the secure connection Docker.
|
||||
When using this option, setting the `cert` option is required.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
swarm:
|
||||
tls:
|
||||
cert: path/to/foo.cert
|
||||
key: path/to/foo.key
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.swarm.tls]
|
||||
cert = "path/to/foo.cert"
|
||||
key = "path/to/foo.key"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.swarm.tls.cert=path/to/foo.cert
|
||||
--providers.swarm.tls.key=path/to/foo.key
|
||||
```
|
||||
|
||||
#### `insecureSkipVerify`
|
||||
|
||||
_Optional, Default=false_
|
||||
|
||||
If `insecureSkipVerify` is `true`, the TLS connection to Docker accepts any certificate presented by the server regardless of the hostnames it covers.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
swarm:
|
||||
tls:
|
||||
insecureSkipVerify: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.swarm.tls]
|
||||
insecureSkipVerify = true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.swarm.tls.insecureSkipVerify=true
|
||||
```
|
||||
|
||||
### `allowEmptyServices`
|
||||
|
||||
_Optional, Default=false_
|
||||
|
||||
If the parameter is set to `true`,
|
||||
any [servers load balancer](../routing/services/index.md#servers-load-balancer) defined for Docker containers is created
|
||||
regardless of the [healthiness](https://docs.docker.com/engine/reference/builder/#healthcheck) of the corresponding containers.
|
||||
It also then stays alive and responsive even at times when it becomes empty,
|
||||
i.e. when all its children containers become unhealthy.
|
||||
This results in `503` HTTP responses instead of `404` ones,
|
||||
in the above cases.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
swarm:
|
||||
allowEmptyServices: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.swarm]
|
||||
allowEmptyServices = true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.swarm.allowEmptyServices=true
|
||||
```
|
||||
|
||||
{!traefik-for-business-applications.md!}
|
|
@ -8,7 +8,7 @@ description: "Reference dynamic configuration with Docker labels in Traefik Prox
|
|||
Dynamic configuration with Docker Labels
|
||||
{: .subtitle }
|
||||
|
||||
The labels are case insensitive.
|
||||
The labels are case-insensitive.
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=foobar"
|
||||
- "traefik.docker.lbswarm=true"
|
||||
|
|
17
docs/content/reference/dynamic-configuration/swarm.md
Normal file
17
docs/content/reference/dynamic-configuration/swarm.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
title: "Traefik Docker Swarm Configuration Documentation"
|
||||
description: "Reference dynamic configuration with Docker Swarm labels in Traefik Proxy. Read the technical documentation."
|
||||
---
|
||||
|
||||
# Docker Swarm Configuration Reference
|
||||
|
||||
Dynamic configuration with Docker Labels
|
||||
{: .subtitle }
|
||||
|
||||
The labels are case-insensitive.
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
--8<-- "content/reference/dynamic-configuration/swarm.yml"
|
||||
--8<-- "content/reference/dynamic-configuration/docker-labels.yml"
|
||||
```
|
3
docs/content/reference/dynamic-configuration/swarm.yml
Normal file
3
docs/content/reference/dynamic-configuration/swarm.yml
Normal file
|
@ -0,0 +1,3 @@
|
|||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=foobar"
|
||||
- "traefik.docker.lbswarm=true"
|
|
@ -189,6 +189,9 @@ WriteTimeout is the maximum duration before timing out writes of the response. I
|
|||
`--entrypoints.<name>.udp.timeout`:
|
||||
Timeout defines how long to wait on an idle session before releasing the related resources. (Default: ```3```)
|
||||
|
||||
`--experimental.http3`:
|
||||
Enable HTTP3. (Default: ```false```)
|
||||
|
||||
`--experimental.kubernetesgateway`:
|
||||
Allow the Kubernetes gateway api provider usage. (Default: ```false```)
|
||||
|
||||
|
@ -222,21 +225,6 @@ resolv.conf used for DNS resolving (Default: ```/etc/resolv.conf```)
|
|||
`--hostresolver.resolvdepth`:
|
||||
The maximal depth of DNS recursive resolving (Default: ```5```)
|
||||
|
||||
`--hub`:
|
||||
Traefik Hub configuration. (Default: ```false```)
|
||||
|
||||
`--hub.tls.ca`:
|
||||
The certificate authority authenticates the Traefik Hub Agent certificate.
|
||||
|
||||
`--hub.tls.cert`:
|
||||
The TLS certificate for Traefik Proxy as a TLS client.
|
||||
|
||||
`--hub.tls.insecure`:
|
||||
Enables an insecure TLS connection that uses default credentials, and which has no peer authentication between Traefik Proxy and the Traefik Hub Agent. (Default: ```false```)
|
||||
|
||||
`--hub.tls.key`:
|
||||
The TLS key for Traefik Proxy as a TLS client.
|
||||
|
||||
`--log`:
|
||||
Traefik log settings. (Default: ```false```)
|
||||
|
||||
|
@ -532,7 +520,7 @@ Constraints is an expression that Traefik matches against the container's labels
|
|||
Default rule. (Default: ```Host(`{{ normalize .Name }}`)```)
|
||||
|
||||
`--providers.docker.endpoint`:
|
||||
Docker server endpoint. Can be a tcp or a unix socket endpoint. (Default: ```unix:///var/run/docker.sock```)
|
||||
Docker server endpoint. Can be a TCP or a Unix socket endpoint. (Default: ```unix:///var/run/docker.sock```)
|
||||
|
||||
`--providers.docker.exposedbydefault`:
|
||||
Expose containers by default. (Default: ```true```)
|
||||
|
@ -543,12 +531,6 @@ Client timeout for HTTP connections. (Default: ```0```)
|
|||
`--providers.docker.network`:
|
||||
Default Docker network used.
|
||||
|
||||
`--providers.docker.swarmmode`:
|
||||
Use Docker on Swarm Mode. (Default: ```false```)
|
||||
|
||||
`--providers.docker.swarmmoderefreshseconds`:
|
||||
Polling interval for swarm mode. (Default: ```15```)
|
||||
|
||||
`--providers.docker.tls.ca`:
|
||||
TLS CA
|
||||
|
||||
|
@ -855,6 +837,51 @@ Enable Rest backend with default settings. (Default: ```false```)
|
|||
`--providers.rest.insecure`:
|
||||
Activate REST Provider directly on the entryPoint named traefik. (Default: ```false```)
|
||||
|
||||
`--providers.swarm`:
|
||||
Enable Docker Swarm backend with default settings. (Default: ```false```)
|
||||
|
||||
`--providers.swarm.allowemptyservices`:
|
||||
Disregards the Docker containers health checks with respect to the creation or removal of the corresponding services. (Default: ```false```)
|
||||
|
||||
`--providers.swarm.constraints`:
|
||||
Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container.
|
||||
|
||||
`--providers.swarm.defaultrule`:
|
||||
Default rule. (Default: ```Host(`{{ normalize .Name }}`)```)
|
||||
|
||||
`--providers.swarm.endpoint`:
|
||||
Docker server endpoint. Can be a TCP or a Unix socket endpoint. (Default: ```unix:///var/run/docker.sock```)
|
||||
|
||||
`--providers.swarm.exposedbydefault`:
|
||||
Expose containers by default. (Default: ```true```)
|
||||
|
||||
`--providers.swarm.httpclienttimeout`:
|
||||
Client timeout for HTTP connections. (Default: ```0```)
|
||||
|
||||
`--providers.swarm.network`:
|
||||
Default Docker network used.
|
||||
|
||||
`--providers.swarm.refreshseconds`:
|
||||
Polling interval for swarm mode. (Default: ```15```)
|
||||
|
||||
`--providers.swarm.tls.ca`:
|
||||
TLS CA
|
||||
|
||||
`--providers.swarm.tls.cert`:
|
||||
TLS cert
|
||||
|
||||
`--providers.swarm.tls.insecureskipverify`:
|
||||
TLS insecure skip verify (Default: ```false```)
|
||||
|
||||
`--providers.swarm.tls.key`:
|
||||
TLS key
|
||||
|
||||
`--providers.swarm.usebindportip`:
|
||||
Use the ip address from the bound port, rather than from the inner network. (Default: ```false```)
|
||||
|
||||
`--providers.swarm.watch`:
|
||||
Watch Docker events. (Default: ```true```)
|
||||
|
||||
`--providers.zookeeper`:
|
||||
Enable ZooKeeper backend with default settings. (Default: ```false```)
|
||||
|
||||
|
|
|
@ -189,6 +189,9 @@ WriteTimeout is the maximum duration before timing out writes of the response. I
|
|||
`TRAEFIK_ENTRYPOINTS_<NAME>_UDP_TIMEOUT`:
|
||||
Timeout defines how long to wait on an idle session before releasing the related resources. (Default: ```3```)
|
||||
|
||||
`TRAEFIK_EXPERIMENTAL_HTTP3`:
|
||||
Enable HTTP3. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_EXPERIMENTAL_KUBERNETESGATEWAY`:
|
||||
Allow the Kubernetes gateway api provider usage. (Default: ```false```)
|
||||
|
||||
|
@ -222,21 +225,6 @@ resolv.conf used for DNS resolving (Default: ```/etc/resolv.conf```)
|
|||
`TRAEFIK_HOSTRESOLVER_RESOLVDEPTH`:
|
||||
The maximal depth of DNS recursive resolving (Default: ```5```)
|
||||
|
||||
`TRAEFIK_HUB`:
|
||||
Traefik Hub configuration. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_HUB_TLS_CA`:
|
||||
The certificate authority authenticates the Traefik Hub Agent certificate.
|
||||
|
||||
`TRAEFIK_HUB_TLS_CERT`:
|
||||
The TLS certificate for Traefik Proxy as a TLS client.
|
||||
|
||||
`TRAEFIK_HUB_TLS_INSECURE`:
|
||||
Enables an insecure TLS connection that uses default credentials, and which has no peer authentication between Traefik Proxy and the Traefik Hub Agent. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_HUB_TLS_KEY`:
|
||||
The TLS key for Traefik Proxy as a TLS client.
|
||||
|
||||
`TRAEFIK_LOG`:
|
||||
Traefik log settings. (Default: ```false```)
|
||||
|
||||
|
@ -532,7 +520,7 @@ Constraints is an expression that Traefik matches against the container's labels
|
|||
Default rule. (Default: ```Host(`{{ normalize .Name }}`)```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_DOCKER_ENDPOINT`:
|
||||
Docker server endpoint. Can be a tcp or a unix socket endpoint. (Default: ```unix:///var/run/docker.sock```)
|
||||
Docker server endpoint. Can be a TCP or a Unix socket endpoint. (Default: ```unix:///var/run/docker.sock```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_DOCKER_EXPOSEDBYDEFAULT`:
|
||||
Expose containers by default. (Default: ```true```)
|
||||
|
@ -543,12 +531,6 @@ Client timeout for HTTP connections. (Default: ```0```)
|
|||
`TRAEFIK_PROVIDERS_DOCKER_NETWORK`:
|
||||
Default Docker network used.
|
||||
|
||||
`TRAEFIK_PROVIDERS_DOCKER_SWARMMODE`:
|
||||
Use Docker on Swarm Mode. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_DOCKER_SWARMMODEREFRESHSECONDS`:
|
||||
Polling interval for swarm mode. (Default: ```15```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_DOCKER_TLS_CA`:
|
||||
TLS CA
|
||||
|
||||
|
@ -855,6 +837,51 @@ Enable Rest backend with default settings. (Default: ```false```)
|
|||
`TRAEFIK_PROVIDERS_REST_INSECURE`:
|
||||
Activate REST Provider directly on the entryPoint named traefik. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_SWARM`:
|
||||
Enable Docker Swarm backend with default settings. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_SWARM_ALLOWEMPTYSERVICES`:
|
||||
Disregards the Docker containers health checks with respect to the creation or removal of the corresponding services. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_SWARM_CONSTRAINTS`:
|
||||
Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container.
|
||||
|
||||
`TRAEFIK_PROVIDERS_SWARM_DEFAULTRULE`:
|
||||
Default rule. (Default: ```Host(`{{ normalize .Name }}`)```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_SWARM_ENDPOINT`:
|
||||
Docker server endpoint. Can be a TCP or a Unix socket endpoint. (Default: ```unix:///var/run/docker.sock```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_SWARM_EXPOSEDBYDEFAULT`:
|
||||
Expose containers by default. (Default: ```true```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_SWARM_HTTPCLIENTTIMEOUT`:
|
||||
Client timeout for HTTP connections. (Default: ```0```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_SWARM_NETWORK`:
|
||||
Default Docker network used.
|
||||
|
||||
`TRAEFIK_PROVIDERS_SWARM_REFRESHSECONDS`:
|
||||
Polling interval for swarm mode. (Default: ```15```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_SWARM_TLS_CA`:
|
||||
TLS CA
|
||||
|
||||
`TRAEFIK_PROVIDERS_SWARM_TLS_CERT`:
|
||||
TLS cert
|
||||
|
||||
`TRAEFIK_PROVIDERS_SWARM_TLS_INSECURESKIPVERIFY`:
|
||||
TLS insecure skip verify (Default: ```false```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_SWARM_TLS_KEY`:
|
||||
TLS key
|
||||
|
||||
`TRAEFIK_PROVIDERS_SWARM_USEBINDPORTIP`:
|
||||
Use the ip address from the bound port, rather than from the inner network. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_SWARM_WATCH`:
|
||||
Watch Docker events. (Default: ```true```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_ZOOKEEPER`:
|
||||
Enable ZooKeeper backend with default settings. (Default: ```false```)
|
||||
|
||||
|
|
|
@ -83,9 +83,7 @@
|
|||
defaultRule = "foobar"
|
||||
exposedByDefault = true
|
||||
useBindPortIP = true
|
||||
swarmMode = true
|
||||
network = "foobar"
|
||||
swarmModeRefreshSeconds = "42s"
|
||||
httpClientTimeout = "42s"
|
||||
allowEmptyServices = true
|
||||
[providers.docker.tls]
|
||||
|
@ -93,6 +91,22 @@
|
|||
cert = "foobar"
|
||||
key = "foobar"
|
||||
insecureSkipVerify = true
|
||||
[providers.swarm]
|
||||
constraints = "foobar"
|
||||
watch = true
|
||||
endpoint = "foobar"
|
||||
defaultRule = "foobar"
|
||||
exposedByDefault = true
|
||||
useBindPortIP = true
|
||||
network = "foobar"
|
||||
refreshSeconds = "42s"
|
||||
httpClientTimeout = "42s"
|
||||
allowEmptyServices = true
|
||||
[providers.swarm.tls]
|
||||
ca = "foobar"
|
||||
cert = "foobar"
|
||||
key = "foobar"
|
||||
insecureSkipVerify = true
|
||||
[providers.file]
|
||||
directory = "foobar"
|
||||
watch = true
|
||||
|
|
|
@ -95,9 +95,23 @@ providers:
|
|||
insecureSkipVerify: true
|
||||
exposedByDefault: true
|
||||
useBindPortIP: true
|
||||
swarmMode: true
|
||||
network: foobar
|
||||
swarmModeRefreshSeconds: 42s
|
||||
httpClientTimeout: 42s
|
||||
allowEmptyServices: true
|
||||
swarm:
|
||||
constraints: foobar
|
||||
watch: true
|
||||
endpoint: foobar
|
||||
defaultRule: foobar
|
||||
tls:
|
||||
ca: foobar
|
||||
cert: foobar
|
||||
key: foobar
|
||||
insecureSkipVerify: true
|
||||
exposedByDefault: true
|
||||
useBindPortIP: true
|
||||
network: foobar
|
||||
refreshSeconds: 42s
|
||||
httpClientTimeout: 42s
|
||||
allowEmptyServices: true
|
||||
file:
|
||||
|
|
|
@ -83,54 +83,6 @@ Attach labels to your containers and let Traefik do the rest!
|
|||
- traefik.http.services.admin-service.loadbalancer.server.port=9000
|
||||
```
|
||||
|
||||
??? example "Configuring Docker Swarm & Deploying / Exposing Services"
|
||||
|
||||
Enabling the docker provider (Swarm Mode)
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
docker:
|
||||
# swarm classic (1.12-)
|
||||
# endpoint: "tcp://127.0.0.1:2375"
|
||||
# docker swarm mode (1.12+)
|
||||
endpoint: "tcp://127.0.0.1:2377"
|
||||
swarmMode: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.docker]
|
||||
# swarm classic (1.12-)
|
||||
# endpoint = "tcp://127.0.0.1:2375"
|
||||
# docker swarm mode (1.12+)
|
||||
endpoint = "tcp://127.0.0.1:2377"
|
||||
swarmMode = true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
# swarm classic (1.12-)
|
||||
# --providers.docker.endpoint=tcp://127.0.0.1:2375
|
||||
# docker swarm mode (1.12+)
|
||||
--providers.docker.endpoint=tcp://127.0.0.1:2377
|
||||
--providers.docker.swarmMode=true
|
||||
```
|
||||
|
||||
Attach labels to services (not to containers) while in Swarm mode (in your docker compose file)
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
services:
|
||||
my-container:
|
||||
deploy:
|
||||
labels:
|
||||
- traefik.http.routers.my-container.rule=Host(`example.com`)
|
||||
- traefik.http.services.my-container-service.loadbalancer.server.port=8080
|
||||
```
|
||||
|
||||
!!! important "Labels in Docker Swarm Mode"
|
||||
While in Swarm Mode, Traefik uses labels found on services, not on individual containers.
|
||||
Therefore, if you use a compose file with Swarm Mode, labels should be defined in the `deploy` part of your service.
|
||||
This behavior is only enabled for docker-compose version 3+ ([Compose file reference](https://docs.docker.com/compose/compose-file/compose-file-v3/#labels-1)).
|
||||
|
||||
## Routing Configuration
|
||||
|
||||
!!! info "Labels"
|
||||
|
@ -275,9 +227,6 @@ you'd add the label `traefik.http.services.<name-of-your-choice>.loadbalancer.pa
|
|||
Registers a port.
|
||||
Useful when the container exposes multiples ports.
|
||||
|
||||
Mandatory for Docker Swarm (see the section ["Port Detection with Docker Swarm"](../../providers/docker.md#port-detection_1)).
|
||||
{: #port }
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.server.port=8080"
|
||||
```
|
||||
|
@ -675,14 +624,3 @@ otherwise it will randomly pick one (depending on how docker is returning them).
|
|||
|
||||
!!! warning
|
||||
When deploying a stack from a compose file `stack`, the networks defined are prefixed with `stack`.
|
||||
|
||||
#### `traefik.docker.lbswarm`
|
||||
|
||||
```yaml
|
||||
- "traefik.docker.lbswarm=true"
|
||||
```
|
||||
|
||||
Enables Swarm's inbuilt load balancer (only relevant in Swarm Mode).
|
||||
|
||||
If you enable this option, Traefik will use the virtual IP provided by docker swarm instead of the containers IPs.
|
||||
Which means that Traefik will not perform any kind of load balancing and will delegate this task to swarm.
|
||||
|
|
640
docs/content/routing/providers/swarm.md
Normal file
640
docs/content/routing/providers/swarm.md
Normal file
|
@ -0,0 +1,640 @@
|
|||
---
|
||||
title: "Traefik Docker Swarm Routing Documentation"
|
||||
description: "This guide will teach you how to attach labels to your containers, to route traffic and load balance with Traefik and Docker."
|
||||
---
|
||||
|
||||
# Traefik & Docker Swarm
|
||||
|
||||
A Story of Labels & Containers
|
||||
{: .subtitle }
|
||||
|
||||
![Swarm](../../assets/img/providers/docker.png)
|
||||
|
||||
Attach labels to your containers and let Traefik do the rest!
|
||||
|
||||
## Configuration Examples
|
||||
|
||||
??? example "Configuring Docker Swarm & Deploying / Exposing Services"
|
||||
|
||||
Enabling the docker provider (Swarm Mode)
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
swarm:
|
||||
# swarm classic (1.12-)
|
||||
# endpoint: "tcp://127.0.0.1:2375"
|
||||
# docker swarm mode (1.12+)
|
||||
endpoint: "tcp://127.0.0.1:2377"
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.swarm]
|
||||
# swarm classic (1.12-)
|
||||
# endpoint = "tcp://127.0.0.1:2375"
|
||||
# docker swarm mode (1.12+)
|
||||
endpoint = "tcp://127.0.0.1:2377"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
# swarm classic (1.12-)
|
||||
# --providers.swarm.endpoint=tcp://127.0.0.1:2375
|
||||
# docker swarm mode (1.12+)
|
||||
--providers.swarm.endpoint=tcp://127.0.0.1:2377
|
||||
```
|
||||
|
||||
Attach labels to services (not to containers) while in Swarm mode (in your docker compose file)
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
services:
|
||||
my-container:
|
||||
deploy:
|
||||
labels:
|
||||
- traefik.http.routers.my-container.rule=Host(`example.com`)
|
||||
- traefik.http.services.my-container-service.loadbalancer.server.port=8080
|
||||
```
|
||||
|
||||
!!! important "Labels in Docker Swarm Mode"
|
||||
While in Swarm Mode, Traefik uses labels found on services, not on individual containers.
|
||||
Therefore, if you use a compose file with Swarm Mode, labels should be defined in the `deploy` part of your service.
|
||||
This behavior is only enabled for docker-compose version 3+ ([Compose file reference](https://docs.docker.com/compose/compose-file/compose-file-v3/#labels-1)).
|
||||
|
||||
??? example "Specifying more than one router and service per container"
|
||||
|
||||
Forwarding requests to more than one port on a container requires referencing the service loadbalancer port definition using the service parameter on the router.
|
||||
|
||||
In this example, requests are forwarded for `http://example-a.com` to `http://<private IP of container>:8000` in addition to `http://example-b.com` forwarding to `http://<private IP of container>:9000`:
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
services:
|
||||
my-container:
|
||||
# ...
|
||||
deploy:
|
||||
labels:
|
||||
- traefik.http.routers.www-router.rule=Host(`example-a.com`)
|
||||
- traefik.http.routers.www-router.service=www-service
|
||||
- traefik.http.services.www-service.loadbalancer.server.port=8000
|
||||
- traefik.http.routers.admin-router.rule=Host(`example-b.com`)
|
||||
- traefik.http.routers.admin-router.service=admin-service
|
||||
- traefik.http.services.admin-service.loadbalancer.server.port=9000
|
||||
```
|
||||
|
||||
## Routing Configuration
|
||||
|
||||
!!! info "Labels"
|
||||
|
||||
- Labels are case insensitive.
|
||||
- The complete list of labels can be found in [the reference page](../../reference/dynamic-configuration/docker.md).
|
||||
|
||||
### General
|
||||
|
||||
Traefik creates, for each container, a corresponding [service](../services/index.md) and [router](../routers/index.md).
|
||||
|
||||
The Service automatically gets a server per instance of the container,
|
||||
and the router automatically gets a rule defined by `defaultRule` (if no rule for it was defined in labels).
|
||||
|
||||
#### Service definition
|
||||
|
||||
--8<-- "content/routing/providers/service-by-label.md"
|
||||
|
||||
??? example "Automatic service assignment with labels"
|
||||
|
||||
With labels in a compose file
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.http.routers.myproxy.rule=Host(`example.net`)"
|
||||
# service myservice gets automatically assigned to router myproxy
|
||||
- "traefik.http.services.myservice.loadbalancer.server.port=80"
|
||||
```
|
||||
|
||||
??? example "Automatic service creation and assignment with labels"
|
||||
|
||||
With labels in a compose file
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
# no service specified or defined and yet one gets automatically created
|
||||
# and assigned to router myproxy.
|
||||
- "traefik.http.routers.myproxy.rule=Host(`example.net`)"
|
||||
```
|
||||
|
||||
### Routers
|
||||
|
||||
To update the configuration of the Router automatically attached to the container,
|
||||
add labels starting with `traefik.http.routers.<name-of-your-choice>.` and followed by the option you want to change.
|
||||
|
||||
For example, to change the rule, you could add the label ```traefik.http.routers.my-container.rule=Host(`example.com`)```.
|
||||
|
||||
!!! warning "The character `@` is not authorized in the router name `<router_name>`."
|
||||
|
||||
??? info "`traefik.http.routers.<router_name>.rule`"
|
||||
|
||||
See [rule](../routers/index.md#rule) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.routers.myrouter.rule=Host(`example.com`)"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.routers.<router_name>.entrypoints`"
|
||||
|
||||
See [entry points](../routers/index.md#entrypoints) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.routers.myrouter.entrypoints=ep1,ep2"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.routers.<router_name>.middlewares`"
|
||||
|
||||
See [middlewares](../routers/index.md#middlewares) and [middlewares overview](../../middlewares/overview.md) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.routers.myrouter.middlewares=auth,prefix,cb"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.routers.<router_name>.service`"
|
||||
|
||||
See [service](../routers/index.md#service) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.routers.myrouter.service=myservice"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.routers.<router_name>.tls`"
|
||||
|
||||
See [tls](../routers/index.md#tls) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.routers.myrouter.tls=true"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.routers.<router_name>.tls.certresolver`"
|
||||
|
||||
See [certResolver](../routers/index.md#certresolver) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.routers.myrouter.tls.certresolver=myresolver"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.routers.<router_name>.tls.domains[n].main`"
|
||||
|
||||
See [domains](../routers/index.md#domains) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.routers.myrouter.tls.domains[0].main=example.org"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.routers.<router_name>.tls.domains[n].sans`"
|
||||
|
||||
See [domains](../routers/index.md#domains) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.routers.myrouter.tls.domains[0].sans=test.example.org,dev.example.org"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.routers.<router_name>.tls.options`"
|
||||
|
||||
See [options](../routers/index.md#options) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.routers.myrouter.tls.options=foobar"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.routers.<router_name>.priority`"
|
||||
|
||||
See [priority](../routers/index.md#priority) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.routers.myrouter.priority=42"
|
||||
```
|
||||
|
||||
### Services
|
||||
|
||||
To update the configuration of the Service automatically attached to the container,
|
||||
add labels starting with `traefik.http.services.<name-of-your-choice>.`, followed by the option you want to change.
|
||||
|
||||
For example, to change the `passHostHeader` behavior,
|
||||
you'd add the label `traefik.http.services.<name-of-your-choice>.loadbalancer.passhostheader=false`.
|
||||
|
||||
!!! warning "The character `@` is not authorized in the service name `<service_name>`."
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.server.port`"
|
||||
|
||||
Registers a port.
|
||||
Useful when the container exposes multiples ports.
|
||||
|
||||
Mandatory for Docker Swarm (see the section ["Port Detection with Docker Swarm"](../../providers/docker.md#port-detection)).
|
||||
{: #port }
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.server.port=8080"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.server.scheme`"
|
||||
|
||||
Overrides the default scheme.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.server.scheme=http"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.serverstransport`"
|
||||
|
||||
Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one.
|
||||
See [serverstransport](../services/index.md#serverstransport) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.<service_name>.loadbalancer.serverstransport=foobar@file"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.passhostheader`"
|
||||
|
||||
See [pass Host header](../services/index.md#pass-host-header) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.passhostheader=true"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.headers.<header_name>`"
|
||||
|
||||
See [health check](../services/index.md#health-check) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.healthcheck.headers.X-Foo=foobar"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.hostname`"
|
||||
|
||||
See [health check](../services/index.md#health-check) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.healthcheck.hostname=example.org"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.interval`"
|
||||
|
||||
See [health check](../services/index.md#health-check) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.healthcheck.interval=10s"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.path`"
|
||||
|
||||
See [health check](../services/index.md#health-check) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.healthcheck.path=/foo"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.method`"
|
||||
|
||||
See [health check](../services/index.md#health-check) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.healthcheck.method=foobar"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.status`"
|
||||
|
||||
See [health check](../services/index.md#health-check) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.healthcheck.status=42"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.port`"
|
||||
|
||||
See [health check](../services/index.md#health-check) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.healthcheck.port=42"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.scheme`"
|
||||
|
||||
See [health check](../services/index.md#health-check) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.healthcheck.scheme=http"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.timeout`"
|
||||
|
||||
See [health check](../services/index.md#health-check) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.healthcheck.timeout=10s"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.followredirects`"
|
||||
|
||||
See [health check](../services/index.md#health-check) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.healthcheck.followredirects=true"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.sticky.cookie`"
|
||||
|
||||
See [sticky sessions](../services/index.md#sticky-sessions) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.sticky.cookie=true"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.sticky.cookie.httponly`"
|
||||
|
||||
See [sticky sessions](../services/index.md#sticky-sessions) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.sticky.cookie.httponly=true"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.sticky.cookie.name`"
|
||||
|
||||
See [sticky sessions](../services/index.md#sticky-sessions) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.sticky.cookie.name=foobar"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.sticky.cookie.secure`"
|
||||
|
||||
See [sticky sessions](../services/index.md#sticky-sessions) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.sticky.cookie.secure=true"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.sticky.cookie.samesite`"
|
||||
|
||||
See [sticky sessions](../services/index.md#sticky-sessions) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.sticky.cookie.samesite=none"
|
||||
```
|
||||
|
||||
??? info "`traefik.http.services.<service_name>.loadbalancer.responseforwarding.flushinterval`"
|
||||
|
||||
See [response forwarding](../services/index.md#response-forwarding) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.http.services.myservice.loadbalancer.responseforwarding.flushinterval=10"
|
||||
```
|
||||
|
||||
### Middleware
|
||||
|
||||
You can declare pieces of middleware using labels starting with `traefik.http.middlewares.<name-of-your-choice>.`,
|
||||
followed by the middleware type/options.
|
||||
|
||||
For example, to declare a middleware [`redirectscheme`](../../middlewares/http/redirectscheme.md) named `my-redirect`,
|
||||
you'd write `traefik.http.middlewares.my-redirect.redirectscheme.scheme=https`.
|
||||
|
||||
More information about available middlewares in the dedicated [middlewares section](../../middlewares/overview.md).
|
||||
|
||||
!!! warning "The character `@` is not authorized in the middleware name."
|
||||
|
||||
??? example "Declaring and Referencing a Middleware"
|
||||
|
||||
```yaml
|
||||
services:
|
||||
my-container:
|
||||
# ...
|
||||
deploy:
|
||||
labels:
|
||||
# Declaring a middleware
|
||||
- traefik.http.middlewares.my-redirect.redirectscheme.scheme=https
|
||||
# Referencing a middleware
|
||||
- traefik.http.routers.my-container.middlewares=my-redirect
|
||||
```
|
||||
|
||||
!!! warning "Conflicts in Declaration"
|
||||
|
||||
If you declare multiple middleware with the same name but with different parameters, the middleware fails to be declared.
|
||||
|
||||
### TCP
|
||||
|
||||
You can declare TCP Routers and/or Services using labels.
|
||||
|
||||
??? example "Declaring TCP Routers and Services"
|
||||
|
||||
```yaml
|
||||
services:
|
||||
my-container:
|
||||
# ...
|
||||
deploy:
|
||||
labels:
|
||||
- "traefik.tcp.routers.my-router.rule=HostSNI(`example.com`)"
|
||||
- "traefik.tcp.routers.my-router.tls=true"
|
||||
- "traefik.tcp.services.my-service.loadbalancer.server.port=4123"
|
||||
```
|
||||
|
||||
!!! warning "TCP and HTTP"
|
||||
|
||||
If you declare a TCP Router/Service, it will prevent Traefik from automatically creating an HTTP Router/Service (like it does by default if no TCP Router/Service is defined).
|
||||
You can declare both a TCP Router/Service and an HTTP Router/Service for the same container (but you have to do so manually).
|
||||
|
||||
#### TCP Routers
|
||||
|
||||
??? info "`traefik.tcp.routers.<router_name>.entrypoints`"
|
||||
|
||||
See [entry points](../routers/index.md#entrypoints_1) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.tcp.routers.mytcprouter.entrypoints=ep1,ep2"
|
||||
```
|
||||
|
||||
??? info "`traefik.tcp.routers.<router_name>.rule`"
|
||||
|
||||
See [rule](../routers/index.md#rule_1) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.tcp.routers.mytcprouter.rule=HostSNI(`example.com`)"
|
||||
```
|
||||
|
||||
??? info "`traefik.tcp.routers.<router_name>.service`"
|
||||
|
||||
See [service](../routers/index.md#services) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.tcp.routers.mytcprouter.service=myservice"
|
||||
```
|
||||
|
||||
??? info "`traefik.tcp.routers.<router_name>.tls`"
|
||||
|
||||
See [TLS](../routers/index.md#tls_1) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.tcp.routers.mytcprouter.tls=true"
|
||||
```
|
||||
|
||||
??? info "`traefik.tcp.routers.<router_name>.tls.certresolver`"
|
||||
|
||||
See [certResolver](../routers/index.md#certresolver_1) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.tcp.routers.mytcprouter.tls.certresolver=myresolver"
|
||||
```
|
||||
|
||||
??? info "`traefik.tcp.routers.<router_name>.tls.domains[n].main`"
|
||||
|
||||
See [domains](../routers/index.md#domains_1) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.tcp.routers.mytcprouter.tls.domains[0].main=example.org"
|
||||
```
|
||||
|
||||
??? info "`traefik.tcp.routers.<router_name>.tls.domains[n].sans`"
|
||||
|
||||
See [domains](../routers/index.md#domains_1) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.tcp.routers.mytcprouter.tls.domains[0].sans=test.example.org,dev.example.org"
|
||||
```
|
||||
|
||||
??? info "`traefik.tcp.routers.<router_name>.tls.options`"
|
||||
|
||||
See [options](../routers/index.md#options_1) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.tcp.routers.mytcprouter.tls.options=mysoptions"
|
||||
```
|
||||
|
||||
??? info "`traefik.tcp.routers.<router_name>.tls.passthrough`"
|
||||
|
||||
See [TLS](../routers/index.md#tls_1) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.tcp.routers.mytcprouter.tls.passthrough=true"
|
||||
```
|
||||
|
||||
??? info "`traefik.tcp.routers.<router_name>.priority`"
|
||||
|
||||
See [priority](../routers/index.md#priority_1) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.tcp.routers.myrouter.priority=42"
|
||||
```
|
||||
|
||||
#### TCP Services
|
||||
|
||||
??? info "`traefik.tcp.services.<service_name>.loadbalancer.server.port`"
|
||||
|
||||
Registers a port of the application.
|
||||
|
||||
```yaml
|
||||
- "traefik.tcp.services.mytcpservice.loadbalancer.server.port=423"
|
||||
```
|
||||
|
||||
??? info "`traefik.tcp.services.<service_name>.loadbalancer.server.tls`"
|
||||
|
||||
Determines whether to use TLS when dialing with the backend.
|
||||
|
||||
```yaml
|
||||
- "traefik.tcp.services.mytcpservice.loadbalancer.server.tls=true"
|
||||
```
|
||||
|
||||
??? info "`traefik.tcp.services.<service_name>.loadbalancer.proxyprotocol.version`"
|
||||
|
||||
See [PROXY protocol](../services/index.md#proxy-protocol) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.tcp.services.mytcpservice.loadbalancer.proxyprotocol.version=1"
|
||||
```
|
||||
|
||||
??? info "`traefik.tcp.services.<service_name>.loadbalancer.serverstransport`"
|
||||
|
||||
Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one.
|
||||
See [serverstransport](../services/index.md#serverstransport_2) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.tcp.services.<service_name>.loadbalancer.serverstransport=foobar@file"
|
||||
```
|
||||
|
||||
### UDP
|
||||
|
||||
You can declare UDP Routers and/or Services using labels.
|
||||
|
||||
??? example "Declaring UDP Routers and Services"
|
||||
|
||||
```yaml
|
||||
services:
|
||||
my-container:
|
||||
# ...
|
||||
deploy:
|
||||
labels:
|
||||
- "traefik.udp.routers.my-router.entrypoints=udp"
|
||||
- "traefik.udp.services.my-service.loadbalancer.server.port=4123"
|
||||
```
|
||||
|
||||
!!! warning "UDP and HTTP"
|
||||
|
||||
If you declare a UDP Router/Service, it will prevent Traefik from automatically creating an HTTP Router/Service (like it does by default if no UDP Router/Service is defined).
|
||||
You can declare both a UDP Router/Service and an HTTP Router/Service for the same container (but you have to do so manually).
|
||||
|
||||
#### UDP Routers
|
||||
|
||||
??? info "`traefik.udp.routers.<router_name>.entrypoints`"
|
||||
|
||||
See [entry points](../routers/index.md#entrypoints_2) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.udp.routers.myudprouter.entrypoints=ep1,ep2"
|
||||
```
|
||||
|
||||
??? info "`traefik.udp.routers.<router_name>.service`"
|
||||
|
||||
See [service](../routers/index.md#services_1) for more information.
|
||||
|
||||
```yaml
|
||||
- "traefik.udp.routers.myudprouter.service=myservice"
|
||||
```
|
||||
|
||||
#### UDP Services
|
||||
|
||||
??? info "`traefik.udp.services.<service_name>.loadbalancer.server.port`"
|
||||
|
||||
Registers a port of the application.
|
||||
|
||||
```yaml
|
||||
- "traefik.udp.services.myudpservice.loadbalancer.server.port=423"
|
||||
```
|
||||
|
||||
### Specific Provider Options
|
||||
|
||||
#### `traefik.enable`
|
||||
|
||||
```yaml
|
||||
- "traefik.enable=true"
|
||||
```
|
||||
|
||||
You can tell Traefik to consider (or not) the container by setting `traefik.enable` to true or false.
|
||||
|
||||
This option overrides the value of `exposedByDefault`.
|
||||
|
||||
#### `traefik.docker.network`
|
||||
|
||||
```yaml
|
||||
- "traefik.docker.network=mynetwork"
|
||||
```
|
||||
|
||||
Overrides the default docker network to use for connections to the container.
|
||||
|
||||
If a container is linked to several networks, be sure to set the proper network name (you can check this with `docker inspect <container_id>`),
|
||||
otherwise it will randomly pick one (depending on how docker is returning them).
|
||||
|
||||
!!! warning
|
||||
When deploying a stack from a compose file `stack`, the networks defined are prefixed with `stack`.
|
||||
|
||||
#### `traefik.docker.lbswarm`
|
||||
|
||||
```yaml
|
||||
- "traefik.docker.lbswarm=true"
|
||||
```
|
||||
|
||||
Enables Swarm's inbuilt load balancer (only relevant in Swarm Mode).
|
||||
|
||||
If you enable this option, Traefik will use the virtual IP provided by docker swarm instead of the containers IPs.
|
||||
Which means that Traefik will not perform any kind of load balancing and will delegate this task to swarm.
|
|
@ -1,338 +0,0 @@
|
|||
# Traefik Hub
|
||||
|
||||
## Overview
|
||||
|
||||
Once the Traefik Hub feature is enabled in Traefik,
|
||||
Traefik and its local agent communicate together.
|
||||
|
||||
This agent can:
|
||||
|
||||
* get the Traefik metrics to display them in the Traefik Hub UI
|
||||
* secure the Traefik routers
|
||||
* provide ACME certificates to Traefik
|
||||
* transfer requests from the SaaS Platform to Traefik (and then avoid the users to expose directly their infrastructure on the internet)
|
||||
|
||||
!!! warning "Traefik Hub Entrypoints"
|
||||
|
||||
When the Traefik Hub feature is enabled, Traefik exposes some services meant for the Traefik Hub Agent on dedicated entrypoints (on ports `9900` and `9901` by default).
|
||||
Given their sensitive nature, those services should not be publicly exposed.
|
||||
Also those dedicated entrypoints, regardless of how they are created (default, or user-defined), should not be used by anything other than the Hub Agent.
|
||||
|
||||
!!! important "Learn More About Traefik Hub"
|
||||
|
||||
This section is intended only as a brief overview for Traefik users who are not familiar with Traefik Hub.
|
||||
To explore all that Traefik Hub has to offer, please consult the [Traefik Hub Documentation](https://doc.traefik.io/traefik-hub).
|
||||
|
||||
!!! Note "Prerequisites"
|
||||
|
||||
* Traefik Hub is compatible with Traefik Proxy 2.7 or later.
|
||||
* The Traefik Hub Agent must be installed to connect to the Traefik Hub platform.
|
||||
|
||||
!!! information "Configuration Discovery"
|
||||
|
||||
According to installation options, the Traefik Hub Agent listens to the Docker or Kubernetes API to discover containers/services.
|
||||
|
||||
It doesn't support the routers discovered by Traefik Proxy using other providers, e.g., using the File provider.
|
||||
|
||||
!!! example "Minimal Static Configuration to Activate Traefik Hub for Docker"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
hub:
|
||||
tls:
|
||||
insecure: true
|
||||
|
||||
metrics:
|
||||
prometheus:
|
||||
addRoutersLabels: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[hub]
|
||||
[hub.tls]
|
||||
insecure = true
|
||||
|
||||
[metrics]
|
||||
[metrics.prometheus]
|
||||
addRoutersLabels = true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--hub.tls.insecure
|
||||
--metrics.prometheus.addrouterslabels
|
||||
```
|
||||
|
||||
!!! example "Minimal Static Configuration to Activate Traefik Hub for Kubernetes"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
hub: {}
|
||||
|
||||
metrics:
|
||||
prometheus:
|
||||
addRoutersLabels: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[hub]
|
||||
|
||||
[metrics]
|
||||
[metrics.prometheus]
|
||||
addRoutersLabels = true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--hub
|
||||
--metrics.prometheus.addrouterslabels
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Entrypoints
|
||||
|
||||
#### `traefikhub-api`
|
||||
|
||||
This entrypoint is used to communicate between the Hub agent and Traefik.
|
||||
It allows the Hub agent to create routing.
|
||||
|
||||
This dedicated Traefik Hub entryPoint should not be used by anything other than Traefik Hub.
|
||||
|
||||
The default port is `9900`.
|
||||
To change the port, you have to define an entrypoint named `traefikhub-api`.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
entryPoints:
|
||||
traefikhub-api: ":8000"
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[entryPoints.traefikhub-api]
|
||||
address = ":8000"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--entrypoints.traefikhub-api.address=:8000
|
||||
```
|
||||
|
||||
#### `traefikhub-tunl`
|
||||
|
||||
This entrypoint is used to communicate between Traefik Hub and Traefik.
|
||||
It allows to create secured tunnels.
|
||||
|
||||
This dedicated Traefik Hub entryPoint should not be used by anything other than Traefik Hub.
|
||||
|
||||
The default port is `9901`.
|
||||
To change the port, you have to define an entrypoint named `traefikhub-tunl`.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
entryPoints:
|
||||
traefikhub-tunl: ":8000"
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[entryPoints.traefikhub-tunl]
|
||||
address = ":8000"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--entrypoints.traefikhub-tunl.address=:8000
|
||||
```
|
||||
|
||||
### `tls`
|
||||
|
||||
_Optional, Default=None_
|
||||
|
||||
This section is required when using the Hub agent for Docker.
|
||||
|
||||
This section allows configuring mutual TLS connection between Traefik Proxy and the Traefik Hub Agent.
|
||||
The key and the certificate are the credentials for Traefik Proxy as a TLS client.
|
||||
The certificate authority authenticates the Traefik Hub Agent certificate.
|
||||
|
||||
!!! note "Certificate Domain"
|
||||
|
||||
The certificate must be valid for the `proxy.traefik` domain.
|
||||
|
||||
!!! note "Certificates Definition"
|
||||
|
||||
Certificates can be defined either by their content or their path.
|
||||
|
||||
!!! note "Insecure Mode"
|
||||
|
||||
The `insecure` option is mutually exclusive with any other option.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
hub:
|
||||
tls:
|
||||
ca: /path/to/ca.pem
|
||||
cert: /path/to/cert.pem
|
||||
key: /path/to/key.pem
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[hub.tls]
|
||||
ca= "/path/to/ca.pem"
|
||||
cert= "/path/to/cert.pem"
|
||||
key= "/path/to/key.pem"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--hub.tls.ca=/path/to/ca.pem
|
||||
--hub.tls.cert=/path/to/cert.pem
|
||||
--hub.tls.key=/path/to/key.pem
|
||||
```
|
||||
|
||||
### `tls.ca`
|
||||
|
||||
The certificate authority authenticates the Traefik Hub Agent certificate.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
hub:
|
||||
tls:
|
||||
ca: |-
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBcjCCARegAwIBAgIQaewCzGdRz5iNnjAiEoO5AzAKBggqhkjOPQQDAjASMRAw
|
||||
DgYDVQQKEwdBY21lIENvMCAXDTIyMDMyMTE2MTY0NFoYDzIxMjIwMjI1MTYxNjQ0
|
||||
WjASMRAwDgYDVQQKEwdBY21lIENvMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
|
||||
ZaKYPj2G8Hnmju6jbHt+vODwKqNDVQMH5nxhtAgSUZS61mLWwZvvUhIYLNPwHz8a
|
||||
x8C7+cuihEC6Tzvn8DeGeKNNMEswDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG
|
||||
CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20w
|
||||
CgYIKoZIzj0EAwIDSQAwRgIhAO8sucDGY+JOrNgQg1a9ZqqYvbxPFnYsSZr7F/vz
|
||||
aUX2AiEAilZ+M5eX4RiMFc3nlm9qVs1LZhV3dZW/u80/mPQ/oaY=
|
||||
-----END CERTIFICATE-----
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[hub.tls]
|
||||
ca = """-----BEGIN CERTIFICATE-----
|
||||
MIIBcjCCARegAwIBAgIQaewCzGdRz5iNnjAiEoO5AzAKBggqhkjOPQQDAjASMRAw
|
||||
DgYDVQQKEwdBY21lIENvMCAXDTIyMDMyMTE2MTY0NFoYDzIxMjIwMjI1MTYxNjQ0
|
||||
WjASMRAwDgYDVQQKEwdBY21lIENvMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
|
||||
ZaKYPj2G8Hnmju6jbHt+vODwKqNDVQMH5nxhtAgSUZS61mLWwZvvUhIYLNPwHz8a
|
||||
x8C7+cuihEC6Tzvn8DeGeKNNMEswDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG
|
||||
CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20w
|
||||
CgYIKoZIzj0EAwIDSQAwRgIhAO8sucDGY+JOrNgQg1a9ZqqYvbxPFnYsSZr7F/vz
|
||||
aUX2AiEAilZ+M5eX4RiMFc3nlm9qVs1LZhV3dZW/u80/mPQ/oaY=
|
||||
-----END CERTIFICATE-----"""
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--hub.tls.ca=-----BEGIN CERTIFICATE-----
|
||||
MIIBcjCCARegAwIBAgIQaewCzGdRz5iNnjAiEoO5AzAKBggqhkjOPQQDAjASMRAw
|
||||
DgYDVQQKEwdBY21lIENvMCAXDTIyMDMyMTE2MTY0NFoYDzIxMjIwMjI1MTYxNjQ0
|
||||
WjASMRAwDgYDVQQKEwdBY21lIENvMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
|
||||
ZaKYPj2G8Hnmju6jbHt+vODwKqNDVQMH5nxhtAgSUZS61mLWwZvvUhIYLNPwHz8a
|
||||
x8C7+cuihEC6Tzvn8DeGeKNNMEswDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG
|
||||
CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20w
|
||||
CgYIKoZIzj0EAwIDSQAwRgIhAO8sucDGY+JOrNgQg1a9ZqqYvbxPFnYsSZr7F/vz
|
||||
aUX2AiEAilZ+M5eX4RiMFc3nlm9qVs1LZhV3dZW/u80/mPQ/oaY=
|
||||
-----END CERTIFICATE-----
|
||||
```
|
||||
|
||||
### `tls.cert`
|
||||
|
||||
The TLS certificate for Traefik Proxy as a TLS client.
|
||||
|
||||
!!! note "Certificate Domain"
|
||||
|
||||
The certificate must be valid for the `proxy.traefik` domain.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
hub:
|
||||
tls:
|
||||
cert: |-
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBcjCCARegAwIBAgIQaewCzGdRz5iNnjAiEoO5AzAKBggqhkjOPQQDAjASMRAw
|
||||
DgYDVQQKEwdBY21lIENvMCAXDTIyMDMyMTE2MTY0NFoYDzIxMjIwMjI1MTYxNjQ0
|
||||
WjASMRAwDgYDVQQKEwdBY21lIENvMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
|
||||
ZaKYPj2G8Hnmju6jbHt+vODwKqNDVQMH5nxhtAgSUZS61mLWwZvvUhIYLNPwHz8a
|
||||
x8C7+cuihEC6Tzvn8DeGeKNNMEswDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG
|
||||
CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20w
|
||||
CgYIKoZIzj0EAwIDSQAwRgIhAO8sucDGY+JOrNgQg1a9ZqqYvbxPFnYsSZr7F/vz
|
||||
aUX2AiEAilZ+M5eX4RiMFc3nlm9qVs1LZhV3dZW/u80/mPQ/oaY=
|
||||
-----END CERTIFICATE-----
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[hub.tls]
|
||||
cert = """-----BEGIN CERTIFICATE-----
|
||||
MIIBcjCCARegAwIBAgIQaewCzGdRz5iNnjAiEoO5AzAKBggqhkjOPQQDAjASMRAw
|
||||
DgYDVQQKEwdBY21lIENvMCAXDTIyMDMyMTE2MTY0NFoYDzIxMjIwMjI1MTYxNjQ0
|
||||
WjASMRAwDgYDVQQKEwdBY21lIENvMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
|
||||
ZaKYPj2G8Hnmju6jbHt+vODwKqNDVQMH5nxhtAgSUZS61mLWwZvvUhIYLNPwHz8a
|
||||
x8C7+cuihEC6Tzvn8DeGeKNNMEswDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG
|
||||
CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20w
|
||||
CgYIKoZIzj0EAwIDSQAwRgIhAO8sucDGY+JOrNgQg1a9ZqqYvbxPFnYsSZr7F/vz
|
||||
aUX2AiEAilZ+M5eX4RiMFc3nlm9qVs1LZhV3dZW/u80/mPQ/oaY=
|
||||
-----END CERTIFICATE-----"""
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--hub.tls.cert=-----BEGIN CERTIFICATE-----
|
||||
MIIBcjCCARegAwIBAgIQaewCzGdRz5iNnjAiEoO5AzAKBggqhkjOPQQDAjASMRAw
|
||||
DgYDVQQKEwdBY21lIENvMCAXDTIyMDMyMTE2MTY0NFoYDzIxMjIwMjI1MTYxNjQ0
|
||||
WjASMRAwDgYDVQQKEwdBY21lIENvMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
|
||||
ZaKYPj2G8Hnmju6jbHt+vODwKqNDVQMH5nxhtAgSUZS61mLWwZvvUhIYLNPwHz8a
|
||||
x8C7+cuihEC6Tzvn8DeGeKNNMEswDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG
|
||||
CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20w
|
||||
CgYIKoZIzj0EAwIDSQAwRgIhAO8sucDGY+JOrNgQg1a9ZqqYvbxPFnYsSZr7F/vz
|
||||
aUX2AiEAilZ+M5eX4RiMFc3nlm9qVs1LZhV3dZW/u80/mPQ/oaY=
|
||||
-----END CERTIFICATE-----
|
||||
```
|
||||
|
||||
### `tls.key`
|
||||
|
||||
The TLS key for Traefik Proxy as a TLS client.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
hub:
|
||||
tls:
|
||||
key: |-
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgm+XJ3LVrTbbirJea
|
||||
O+Crj2ADVsVHjMuiyd72VE3lgxihRANCAARlopg+PYbweeaO7qNse3684PAqo0NV
|
||||
AwfmfGG0CBJRlLrWYtbBm+9SEhgs0/AfPxrHwLv5y6KEQLpPO+fwN4Z4
|
||||
-----END PRIVATE KEY-----
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[hub.tls]
|
||||
key = """-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgm+XJ3LVrTbbirJea
|
||||
O+Crj2ADVsVHjMuiyd72VE3lgxihRANCAARlopg+PYbweeaO7qNse3684PAqo0NV
|
||||
AwfmfGG0CBJRlLrWYtbBm+9SEhgs0/AfPxrHwLv5y6KEQLpPO+fwN4Z4
|
||||
-----END PRIVATE KEY-----"""
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--hub.tls.key=-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgm+XJ3LVrTbbirJea
|
||||
O+Crj2ADVsVHjMuiyd72VE3lgxihRANCAARlopg+PYbweeaO7qNse3684PAqo0NV
|
||||
AwfmfGG0CBJRlLrWYtbBm+9SEhgs0/AfPxrHwLv5y6KEQLpPO+fwN4Z4
|
||||
-----END PRIVATE KEY-----
|
||||
```
|
||||
|
||||
### `tls.insecure`
|
||||
|
||||
_Optional, Default=false_
|
||||
|
||||
Enables an insecure TLS connection that uses default credentials,
|
||||
and which has no peer authentication between Traefik Proxy and the Traefik Hub Agent.
|
||||
The `insecure` option is mutually exclusive with any other option.
|
||||
|
||||
!!! warning "Security Consideration"
|
||||
|
||||
Do not use this setup in production.
|
||||
This option implies sensitive data can be exposed to potential malicious third-party programs.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
hub:
|
||||
tls:
|
||||
insecure: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[hub.tls]
|
||||
insecure = true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--hub.tls.insecure=true
|
||||
```
|
|
@ -75,6 +75,7 @@ nav:
|
|||
- 'Configuration Discovery':
|
||||
- 'Overview': 'providers/overview.md'
|
||||
- 'Docker': 'providers/docker.md'
|
||||
- 'Swarm': 'providers/swarm.md'
|
||||
- 'Kubernetes IngressRoute': 'providers/kubernetes-crd.md'
|
||||
- 'Kubernetes Ingress': 'providers/kubernetes-ingress.md'
|
||||
- 'Kubernetes Gateway API': 'providers/kubernetes-gateway.md'
|
||||
|
@ -94,6 +95,7 @@ nav:
|
|||
- 'Services': 'routing/services/index.md'
|
||||
- 'Providers':
|
||||
- 'Docker': 'routing/providers/docker.md'
|
||||
- 'Swarm': 'routing/providers/swarm.md'
|
||||
- 'Kubernetes IngressRoute': 'routing/providers/kubernetes-crd.md'
|
||||
- 'Kubernetes Ingress': 'routing/providers/kubernetes-ingress.md'
|
||||
- 'Kubernetes Gateway API': 'routing/providers/kubernetes-gateway.md'
|
||||
|
@ -138,7 +140,6 @@ nav:
|
|||
- 'Overview': 'middlewares/tcp/overview.md'
|
||||
- 'InFlightConn': 'middlewares/tcp/inflightconn.md'
|
||||
- 'IpAllowList': 'middlewares/tcp/ipallowlist.md'
|
||||
- 'Traefik Hub': 'traefik-hub/index.md'
|
||||
- 'Plugins & Plugin Catalog': 'plugins/index.md'
|
||||
- 'Operations':
|
||||
- 'CLI': 'operations/cli.md'
|
||||
|
@ -196,6 +197,7 @@ nav:
|
|||
- 'Dynamic Configuration':
|
||||
- 'File': 'reference/dynamic-configuration/file.md'
|
||||
- 'Docker': 'reference/dynamic-configuration/docker.md'
|
||||
- 'Swarm': 'reference/dynamic-configuration/swarm.md'
|
||||
- 'Kubernetes CRD': 'reference/dynamic-configuration/kubernetes-crd.md'
|
||||
- 'Kubernetes Gateway API': 'reference/dynamic-configuration/kubernetes-gateway.md'
|
||||
- 'Consul Catalog': 'reference/dynamic-configuration/consul-catalog.md'
|
||||
|
|
27
go.mod
27
go.mod
|
@ -19,7 +19,7 @@ require (
|
|||
github.com/docker/docker v20.10.21+incompatible
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/fatih/structs v1.1.0
|
||||
github.com/go-acme/lego/v4 v4.10.2
|
||||
github.com/go-acme/lego/v4 v4.11.0
|
||||
github.com/go-check/check v0.0.0-00010101000000-000000000000
|
||||
github.com/go-kit/kit v0.10.1-0.20200915143503-439c4d2ed3ea
|
||||
github.com/golang/protobuf v1.5.2
|
||||
|
@ -71,7 +71,7 @@ require (
|
|||
github.com/unrolled/render v1.0.2
|
||||
github.com/unrolled/secure v1.0.9
|
||||
github.com/vdemeester/shakers v0.1.0
|
||||
github.com/vulcand/oxy/v2 v2.0.0-20230417082832-03de175b3822
|
||||
github.com/vulcand/oxy/v2 v2.0.0-20230427132221-be5cf38f3c1c
|
||||
github.com/vulcand/predicate v1.2.0
|
||||
go.elastic.co/apm v1.13.1
|
||||
go.elastic.co/apm/module/apmot v1.13.1
|
||||
|
@ -93,7 +93,7 @@ require (
|
|||
golang.org/x/text v0.7.0
|
||||
golang.org/x/time v0.3.0
|
||||
golang.org/x/tools v0.2.0
|
||||
google.golang.org/grpc v1.51.0
|
||||
google.golang.org/grpc v1.53.0
|
||||
gopkg.in/DataDog/dd-trace-go.v1 v1.43.1
|
||||
gopkg.in/fsnotify.v1 v1.4.7
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
|
@ -107,7 +107,8 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute/metadata v0.2.0 // indirect
|
||||
cloud.google.com/go/compute v1.18.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
github.com/AlecAivazis/survey/v2 v2.2.3 // indirect
|
||||
github.com/Azure/azure-sdk-for-go v40.3.0+incompatible // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
|
@ -142,7 +143,7 @@ require (
|
|||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/buger/goterm v1.0.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible // indirect
|
||||
github.com/circonus-labs/circonusllhist v0.1.3 // indirect
|
||||
github.com/civo/civogo v0.3.11 // indirect
|
||||
|
@ -207,8 +208,8 @@ require (
|
|||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.7.0 // indirect
|
||||
github.com/gophercloud/gophercloud v1.0.0 // indirect
|
||||
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect
|
||||
github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf // indirect
|
||||
|
@ -286,6 +287,7 @@ require (
|
|||
github.com/nrdcg/freemyip v0.2.0 // indirect
|
||||
github.com/nrdcg/goinwx v0.8.1 // indirect
|
||||
github.com/nrdcg/namesilo v0.2.1 // indirect
|
||||
github.com/nrdcg/nodion v0.1.0 // indirect
|
||||
github.com/nrdcg/porkbun v0.1.1 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.4.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
|
@ -312,6 +314,7 @@ require (
|
|||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.9 // indirect
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/simplesurance/bunny-go v0.0.0-20221115111006-e11d9dc91f04 // indirect
|
||||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
|
||||
github.com/softlayer/softlayer-go v1.0.6 // indirect
|
||||
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
|
||||
|
@ -341,7 +344,7 @@ require (
|
|||
go.etcd.io/etcd/api/v3 v3.5.5 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.5 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.5.5 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.34.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
|
||||
|
@ -353,14 +356,14 @@ require (
|
|||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect
|
||||
golang.org/x/crypto v0.5.0 // indirect
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
|
||||
golang.org/x/oauth2 v0.4.0 // indirect
|
||||
golang.org/x/oauth2 v0.5.0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
golang.org/x/term v0.5.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
|
||||
google.golang.org/api v0.98.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/api v0.111.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230223222841-637eb2293923 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.66.6 // indirect
|
||||
|
|
181
go.sum
181
go.sum
|
@ -23,35 +23,20 @@ cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmW
|
|||
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
|
||||
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
|
||||
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
|
||||
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
|
||||
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
|
||||
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
|
||||
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
|
||||
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
|
||||
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
|
||||
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
|
||||
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
|
||||
cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
|
||||
cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=
|
||||
cloud.google.com/go v0.105.0 h1:DNtEKRBAAzeS4KyIory52wWHuClNaXJ5x1F7xa4q+5Y=
|
||||
cloud.google.com/go v0.107.0 h1:qkj22L7bgkl6vIeZDlOY2po43Mx/TIa2Wsa7VR+PEww=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
|
||||
cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
|
||||
cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
|
||||
cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
|
||||
cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
|
||||
cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
|
||||
cloud.google.com/go/compute/metadata v0.2.0 h1:nBbNSZyDpkNlo3DepaaLKVuO7ClyifSAmNloSCZrHnQ=
|
||||
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
cloud.google.com/go/compute v1.18.0 h1:FEigFqoDbys2cvFkZ9Fjq4gnHBP55anJ0yQyau2f9oY=
|
||||
cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||
cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
|
||||
cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
|
@ -62,7 +47,6 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
|
|||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/AlecAivazis/survey/v2 v2.2.3 h1:utJR2X4Ibp2fBxdjalQUiMFf3zfQNjA15YE8+ftlKEs=
|
||||
github.com/AlecAivazis/survey/v2 v2.2.3/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk=
|
||||
|
@ -302,8 +286,9 @@ github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEex
|
|||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
|
||||
github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
|
||||
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
|
||||
|
@ -682,8 +667,8 @@ github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwv
|
|||
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/go-acme/lego/v4 v4.10.2 h1:5eW3qmda5v/LP21v1Hj70edKY1jeFZQwO617tdkwp6Q=
|
||||
github.com/go-acme/lego/v4 v4.10.2/go.mod h1:EMbf0Jmqwv94nJ5WL9qWnSXIBZnvsS9gNypansHGc6U=
|
||||
github.com/go-acme/lego/v4 v4.11.0 h1:oIPoU7zBJoTfoVrbqk62+/2NsGCSgCVK1JtZSZZ28SU=
|
||||
github.com/go-acme/lego/v4 v4.11.0/go.mod h1:dENL0J3/WughN2NLy0T35otK5k1EWCmXTwCw0+X5ZaE=
|
||||
github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
|
||||
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
|
||||
|
@ -851,7 +836,6 @@ github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw
|
|||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
|
||||
github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
|
||||
|
@ -877,7 +861,6 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-containerregistry v0.0.0-20191015185424-71da34e4d9b3/go.mod h1:ZXFeSndFcK4vB1NR4voH1Zm38K7ViUNiYtfIBDxrwf0=
|
||||
|
@ -897,7 +880,6 @@ github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
|
|||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
|
@ -911,7 +893,6 @@ github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLe
|
|||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
|
@ -924,19 +905,12 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
|||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.0 h1:y8Yozv7SZtlU//QXbezB6QkpuE6jMD2/gfzk4AftXjs=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
||||
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
|
||||
github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
|
||||
github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
|
||||
github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
|
||||
github.com/googleapis/gax-go/v2 v2.6.0 h1:SXk3ABtQYDT/OH8jAyvEOQ58mgawq5C4o/4/89qN2ZU=
|
||||
github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY=
|
||||
github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ=
|
||||
github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8=
|
||||
github.com/googleapis/gnostic v0.0.0-20170426233943-68f4ded48ba9/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
|
@ -944,7 +918,6 @@ github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTV
|
|||
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
|
||||
github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
|
||||
github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
|
||||
github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
|
||||
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
|
||||
github.com/gophercloud/gophercloud v0.15.1-0.20210202035223-633d73521055/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4=
|
||||
github.com/gophercloud/gophercloud v1.0.0 h1:9nTGx0jizmHxDobe4mck89FyQHVyA3CaXLIUSGJjP9k=
|
||||
|
@ -1464,6 +1437,8 @@ github.com/nrdcg/goinwx v0.8.1 h1:20EQ/JaGFnSKwiDH2JzjIpicffl3cPk6imJBDqVBVtU=
|
|||
github.com/nrdcg/goinwx v0.8.1/go.mod h1:tILVc10gieBp/5PMvbcYeXM6pVQ+c9jxDZnpaR1UW7c=
|
||||
github.com/nrdcg/namesilo v0.2.1 h1:kLjCjsufdW/IlC+iSfAqj0iQGgKjlbUUeDJio5Y6eMg=
|
||||
github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw=
|
||||
github.com/nrdcg/nodion v0.1.0 h1:zLKaqTn2X0aDuBHHfyA1zFgeZfiCpmu/O9DM73okavw=
|
||||
github.com/nrdcg/nodion v0.1.0/go.mod h1:inbuh3neCtIWlMPZHtEpe43TmRXxHV6+hk97iCZicms=
|
||||
github.com/nrdcg/porkbun v0.1.1 h1:gxVzQYfFUGXhnBax/aVugoE3OIBAdHgrJgyMPyY5Sjo=
|
||||
github.com/nrdcg/porkbun v0.1.1/go.mod h1:JWl/WKnguWos4mjfp4YizvvToigk9qpQwrodOk+CPoA=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
|
@ -1725,6 +1700,8 @@ github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9Nz
|
|||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/simplesurance/bunny-go v0.0.0-20221115111006-e11d9dc91f04 h1:ZTzdx88+AcnjqUfJwnz89UBrMSBQ1NEysg9u5d+dU9c=
|
||||
github.com/simplesurance/bunny-go v0.0.0-20221115111006-e11d9dc91f04/go.mod h1:5KS21fpch8TIMyAUv/qQqTa3GZfBDYgjaZbd2KXKYfg=
|
||||
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
|
@ -1889,8 +1866,8 @@ github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6Ac
|
|||
github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
|
||||
github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
|
||||
github.com/vmware/govmomi v0.18.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU=
|
||||
github.com/vulcand/oxy/v2 v2.0.0-20230417082832-03de175b3822 h1:DXLWOIMPcQV+bxCFhBYSY5AIGP4DGvXH6qkwsg82YYY=
|
||||
github.com/vulcand/oxy/v2 v2.0.0-20230417082832-03de175b3822/go.mod h1:A2voDnpONyqdplUDK0lt5y4XHLiBXPBw7iQES8+ZWRw=
|
||||
github.com/vulcand/oxy/v2 v2.0.0-20230427132221-be5cf38f3c1c h1:Qt/YKpE8uAKNF4x2mwBZxmVo2WtgUL1WFDeXr1nlfpA=
|
||||
github.com/vulcand/oxy/v2 v2.0.0-20230427132221-be5cf38f3c1c/go.mod h1:A2voDnpONyqdplUDK0lt5y4XHLiBXPBw7iQES8+ZWRw=
|
||||
github.com/vulcand/predicate v1.2.0 h1:uFsW1gcnnR7R+QTID+FVcs0sSYlIGntoGOTb3rQJt50=
|
||||
github.com/vulcand/predicate v1.2.0/go.mod h1:VipoNYXny6c8N381zGUWkjuuNHiRbeAZhE7Qm9c+2GA=
|
||||
github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs=
|
||||
|
@ -1969,8 +1946,9 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/collector/pdata v0.64.1 h1:8E06uHr0nnenGftFwhwdenA88QhVnF4dJam+qVXgdVg=
|
||||
go.opentelemetry.io/collector/pdata v0.64.1/go.mod h1:IzvXUGQml2mrnvdb8zIlEW3qQs9oFLdD2hLwJdZ+pek=
|
||||
go.opentelemetry.io/otel v1.11.2 h1:YBZcQlsVekzFsFbjygXMOXSs6pialIZxcjfO/mBDmR0=
|
||||
|
@ -2182,7 +2160,6 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd
|
|||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
|
@ -2193,13 +2170,7 @@ golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
|
@ -2217,17 +2188,10 @@ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ
|
|||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
|
||||
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
|
||||
golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M=
|
||||
golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
|
||||
golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s=
|
||||
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -2239,7 +2203,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -2354,38 +2317,25 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
@ -2494,8 +2444,6 @@ golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4f
|
|||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
|
@ -2505,10 +2453,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
|||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0=
|
||||
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY=
|
||||
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
|
||||
gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM=
|
||||
|
@ -2540,26 +2486,8 @@ google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjR
|
|||
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
|
||||
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
|
||||
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
|
||||
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
|
||||
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
|
||||
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
|
||||
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
|
||||
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
|
||||
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
||||
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
||||
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
|
||||
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
|
||||
google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
|
||||
google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
|
||||
google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
|
||||
google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
|
||||
google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
|
||||
google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
|
||||
google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
|
||||
google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
|
||||
google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
|
||||
google.golang.org/api v0.98.0 h1:yxZrcxXESimy6r6mdL5Q6EnZwmewDJK2dVg3g75s5Dg=
|
||||
google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
|
||||
google.golang.org/api v0.111.0 h1:bwKi+z2BsdwYFRKrqwutM+axAlYLz83gt5pDSXCJT+0=
|
||||
google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
|
@ -2620,49 +2548,12 @@ google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6D
|
|||
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
||||
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
|
||||
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
||||
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
||||
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
||||
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
||||
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
|
||||
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211021150943-2b146023228c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
||||
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
||||
google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
||||
google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
||||
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
|
||||
google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||
google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||
google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||
google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||
google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||
google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
||||
google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
||||
google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
||||
google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
||||
google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
||||
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
||||
google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37 h1:jmIfw8+gSvXcZSgaFAGyInDXeWzUhvYH57G/5GKMn70=
|
||||
google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
|
||||
google.golang.org/genproto v0.0.0-20230223222841-637eb2293923 h1:znp6mq/drrY+6khTAlJUDNFFcDGV2ENLYKpMq8SyCds=
|
||||
google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw=
|
||||
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
|
@ -2692,23 +2583,13 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5
|
|||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
|
||||
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
||||
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U=
|
||||
google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
|
||||
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
|
||||
google.golang.org/grpc/examples v0.0.0-20201130180447-c456688b1860/go.mod h1:Ly7ZA/ARzg8fnPU9TyZIxoz33sEUuWX7txiqs8lPTgE=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
|
|
|
@ -26,7 +26,6 @@ type features struct {
|
|||
Tracing string `json:"tracing"`
|
||||
Metrics string `json:"metrics"`
|
||||
AccessLog bool `json:"accessLog"`
|
||||
Hub bool `json:"hub"`
|
||||
// TODO add certificates resolvers
|
||||
}
|
||||
|
||||
|
@ -248,7 +247,6 @@ func getFeatures(conf static.Configuration) features {
|
|||
Tracing: getTracing(conf),
|
||||
Metrics: getMetrics(conf),
|
||||
AccessLog: conf.AccessLog != nil,
|
||||
Hub: conf.Hub != nil,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@ import (
|
|||
"github.com/traefik/traefik/v3/pkg/config/static"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/docker"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/file"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/hub"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/ingress"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/rest"
|
||||
|
@ -235,6 +234,7 @@ func TestHandler_Overview(t *testing.T) {
|
|||
API: &static.API{},
|
||||
Providers: &static.Providers{
|
||||
Docker: &docker.Provider{},
|
||||
Swarm: &docker.SwarmProvider{},
|
||||
File: &file.Provider{},
|
||||
KubernetesIngress: &ingress.Provider{},
|
||||
KubernetesCRD: &crd.Provider{},
|
||||
|
@ -262,7 +262,6 @@ func TestHandler_Overview(t *testing.T) {
|
|||
Tracing: &static.Tracing{
|
||||
Jaeger: &jaeger.Config{},
|
||||
},
|
||||
Hub: &hub.Provider{},
|
||||
},
|
||||
confDyn: runtime.Configuration{},
|
||||
expected: expected{
|
||||
|
|
3
pkg/api/testdata/overview-dynamic.json
vendored
3
pkg/api/testdata/overview-dynamic.json
vendored
|
@ -2,8 +2,7 @@
|
|||
"features": {
|
||||
"accessLog": false,
|
||||
"metrics": "",
|
||||
"tracing": "",
|
||||
"hub": false
|
||||
"tracing": ""
|
||||
},
|
||||
"http": {
|
||||
"middlewares": {
|
||||
|
|
3
pkg/api/testdata/overview-empty.json
vendored
3
pkg/api/testdata/overview-empty.json
vendored
|
@ -2,8 +2,7 @@
|
|||
"features": {
|
||||
"accessLog": false,
|
||||
"metrics": "",
|
||||
"tracing": "",
|
||||
"hub": false
|
||||
"tracing": ""
|
||||
},
|
||||
"http": {
|
||||
"middlewares": {
|
||||
|
|
3
pkg/api/testdata/overview-features.json
vendored
3
pkg/api/testdata/overview-features.json
vendored
|
@ -2,8 +2,7 @@
|
|||
"features": {
|
||||
"accessLog": false,
|
||||
"metrics": "Prometheus",
|
||||
"tracing": "Jaeger",
|
||||
"hub": true
|
||||
"tracing": "Jaeger"
|
||||
},
|
||||
"http": {
|
||||
"middlewares": {
|
||||
|
|
4
pkg/api/testdata/overview-providers.json
vendored
4
pkg/api/testdata/overview-providers.json
vendored
|
@ -2,8 +2,7 @@
|
|||
"features": {
|
||||
"accessLog": false,
|
||||
"metrics": "",
|
||||
"tracing": "",
|
||||
"hub": false
|
||||
"tracing": ""
|
||||
},
|
||||
"http": {
|
||||
"middlewares": {
|
||||
|
@ -24,6 +23,7 @@
|
|||
},
|
||||
"providers": [
|
||||
"Docker",
|
||||
"Swarm",
|
||||
"File",
|
||||
"KubernetesIngress",
|
||||
"KubernetesCRD",
|
||||
|
|
|
@ -40,9 +40,7 @@
|
|||
defaultRule = "foobar"
|
||||
exposedByDefault = true
|
||||
useBindPortIP = true
|
||||
swarmMode = true
|
||||
network = "foobar"
|
||||
swarmModeRefreshSeconds = 42
|
||||
httpClientTimeout = 42
|
||||
[providers.docker.tls]
|
||||
ca = "foobar"
|
||||
|
|
|
@ -8,4 +8,5 @@ type Experimental struct {
|
|||
LocalPlugins map[string]plugins.LocalDescriptor `description:"Local plugins configuration." json:"localPlugins,omitempty" toml:"localPlugins,omitempty" yaml:"localPlugins,omitempty" export:"true"`
|
||||
|
||||
KubernetesGateway bool `description:"Allow the Kubernetes gateway api provider usage." json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty" export:"true"`
|
||||
HTTP3 bool `description:"Enable HTTP3." json:"http3,omitempty" toml:"http3,omitempty" yaml:"http3,omitempty" export:"true"`
|
||||
}
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
package static
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/logs"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/hub"
|
||||
)
|
||||
|
||||
func (c *Configuration) initHubProvider() error {
|
||||
if _, ok := c.EntryPoints[hub.TunnelEntrypoint]; !ok {
|
||||
var ep EntryPoint
|
||||
ep.SetDefaults()
|
||||
ep.Address = ":9901"
|
||||
c.EntryPoints[hub.TunnelEntrypoint] = &ep
|
||||
|
||||
log.Info().Str(logs.EntryPointName, hub.TunnelEntrypoint).
|
||||
Msg("The entryPoint is created on port 9901 to allow exposition of services.")
|
||||
}
|
||||
|
||||
if c.Hub.TLS == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.Hub.TLS.Insecure && (c.Hub.TLS.CA != "" || c.Hub.TLS.Cert != "" || c.Hub.TLS.Key != "") {
|
||||
return errors.New("mTLS configuration for Hub and insecure TLS for Hub are mutually exclusive")
|
||||
}
|
||||
|
||||
if !c.Hub.TLS.Insecure && (c.Hub.TLS.CA == "" || c.Hub.TLS.Cert == "" || c.Hub.TLS.Key == "") {
|
||||
return errors.New("incomplete mTLS configuration for Hub")
|
||||
}
|
||||
|
||||
if c.Hub.TLS.Insecure {
|
||||
log.Warn().Msg("Hub is in `insecure` mode. Do not run in production with this setup.")
|
||||
}
|
||||
|
||||
if _, ok := c.EntryPoints[hub.APIEntrypoint]; !ok {
|
||||
var ep EntryPoint
|
||||
ep.SetDefaults()
|
||||
ep.Address = ":9900"
|
||||
c.EntryPoints[hub.APIEntrypoint] = &ep
|
||||
|
||||
log.Info().Str(logs.EntryPointName, hub.APIEntrypoint).
|
||||
Msg("The entryPoint is created on port 9900 to allow Traefik to communicate with the Hub Agent for Traefik.")
|
||||
}
|
||||
|
||||
c.EntryPoints[hub.APIEntrypoint].HTTP.TLS = &TLSConfig{
|
||||
Options: "traefik-hub",
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -17,7 +17,6 @@ import (
|
|||
"github.com/traefik/traefik/v3/pkg/provider/ecs"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/file"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/http"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/hub"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/gateway"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/ingress"
|
||||
|
@ -78,8 +77,6 @@ type Configuration struct {
|
|||
|
||||
CertificatesResolvers map[string]CertificateResolver `description:"Certificates resolvers configuration." json:"certificatesResolvers,omitempty" toml:"certificatesResolvers,omitempty" yaml:"certificatesResolvers,omitempty" export:"true"`
|
||||
|
||||
Hub *hub.Provider `description:"Traefik Hub configuration." json:"hub,omitempty" toml:"hub,omitempty" yaml:"hub,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
|
||||
Experimental *Experimental `description:"experimental features." json:"experimental,omitempty" toml:"experimental,omitempty" yaml:"experimental,omitempty" export:"true"`
|
||||
|
||||
Spiffe *SpiffeClientConfig `description:"SPIFFE integration configuration." json:"spiffe,omitempty" toml:"spiffe,omitempty" yaml:"spiffe,omitempty" export:"true"`
|
||||
|
@ -210,7 +207,9 @@ func (t *Tracing) SetDefaults() {
|
|||
type Providers struct {
|
||||
ProvidersThrottleDuration ptypes.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time." json:"providersThrottleDuration,omitempty" toml:"providersThrottleDuration,omitempty" yaml:"providersThrottleDuration,omitempty" export:"true"`
|
||||
|
||||
Docker *docker.Provider `description:"Enable Docker backend with default settings." json:"docker,omitempty" toml:"docker,omitempty" yaml:"docker,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
Docker *docker.Provider `description:"Enable Docker backend with default settings." json:"docker,omitempty" toml:"docker,omitempty" yaml:"docker,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
Swarm *docker.SwarmProvider `description:"Enable Docker Swarm backend with default settings." json:"swarm,omitempty" toml:"swarm,omitempty" yaml:"swarm,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
|
||||
File *file.Provider `description:"Enable File backend with default settings." json:"file,omitempty" toml:"file,omitempty" yaml:"file,omitempty" export:"true"`
|
||||
KubernetesIngress *ingress.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesIngress,omitempty" toml:"kubernetesIngress,omitempty" yaml:"kubernetesIngress,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
KubernetesCRD *crd.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesCRD,omitempty" toml:"kubernetesCRD,omitempty" yaml:"kubernetesCRD,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
|
@ -219,12 +218,11 @@ type Providers struct {
|
|||
ConsulCatalog *consulcatalog.ProviderBuilder `description:"Enable ConsulCatalog backend with default settings." json:"consulCatalog,omitempty" toml:"consulCatalog,omitempty" yaml:"consulCatalog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
Nomad *nomad.ProviderBuilder `description:"Enable Nomad backend with default settings." json:"nomad,omitempty" toml:"nomad,omitempty" yaml:"nomad,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
Ecs *ecs.Provider `description:"Enable AWS ECS backend with default settings." json:"ecs,omitempty" toml:"ecs,omitempty" yaml:"ecs,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
|
||||
Consul *consul.ProviderBuilder `description:"Enable Consul backend with default settings." json:"consul,omitempty" toml:"consul,omitempty" yaml:"consul,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
Etcd *etcd.Provider `description:"Enable Etcd backend with default settings." json:"etcd,omitempty" toml:"etcd,omitempty" yaml:"etcd,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
ZooKeeper *zk.Provider `description:"Enable ZooKeeper backend with default settings." json:"zooKeeper,omitempty" toml:"zooKeeper,omitempty" yaml:"zooKeeper,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
Redis *redis.Provider `description:"Enable Redis backend with default settings." json:"redis,omitempty" toml:"redis,omitempty" yaml:"redis,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
HTTP *http.Provider `description:"Enable HTTP backend with default settings." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
Consul *consul.ProviderBuilder `description:"Enable Consul backend with default settings." json:"consul,omitempty" toml:"consul,omitempty" yaml:"consul,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
Etcd *etcd.Provider `description:"Enable Etcd backend with default settings." json:"etcd,omitempty" toml:"etcd,omitempty" yaml:"etcd,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
ZooKeeper *zk.Provider `description:"Enable ZooKeeper backend with default settings." json:"zooKeeper,omitempty" toml:"zooKeeper,omitempty" yaml:"zooKeeper,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
Redis *redis.Provider `description:"Enable Redis backend with default settings." json:"redis,omitempty" toml:"redis,omitempty" yaml:"redis,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
HTTP *http.Provider `description:"Enable HTTP backend with default settings." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
|
||||
Plugin map[string]PluginConf `description:"Plugins configuration." json:"plugin,omitempty" toml:"plugin,omitempty" yaml:"plugin,omitempty"`
|
||||
}
|
||||
|
@ -255,22 +253,19 @@ func (c *Configuration) SetEffectiveConfiguration() {
|
|||
}
|
||||
}
|
||||
|
||||
if c.Hub != nil {
|
||||
if err := c.initHubProvider(); err != nil {
|
||||
c.Hub = nil
|
||||
log.Error().Err(err).Msg("Unable to activate the Hub provider")
|
||||
} else {
|
||||
log.Debug().Msg("Hub provider has been activated")
|
||||
if c.Providers.Docker != nil {
|
||||
if c.Providers.Docker.HTTPClientTimeout < 0 {
|
||||
c.Providers.Docker.HTTPClientTimeout = 0
|
||||
}
|
||||
}
|
||||
|
||||
if c.Providers.Docker != nil {
|
||||
if c.Providers.Docker.SwarmModeRefreshSeconds <= 0 {
|
||||
c.Providers.Docker.SwarmModeRefreshSeconds = ptypes.Duration(15 * time.Second)
|
||||
if c.Providers.Swarm != nil {
|
||||
if c.Providers.Swarm.RefreshSeconds <= 0 {
|
||||
c.Providers.Swarm.RefreshSeconds = ptypes.Duration(15 * time.Second)
|
||||
}
|
||||
|
||||
if c.Providers.Docker.HTTPClientTimeout < 0 {
|
||||
c.Providers.Docker.HTTPClientTimeout = 0
|
||||
if c.Providers.Swarm.HTTPClientTimeout < 0 {
|
||||
c.Providers.Swarm.HTTPClientTimeout = 0
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -294,18 +289,7 @@ func (c *Configuration) SetEffectiveConfiguration() {
|
|||
}
|
||||
|
||||
func (c *Configuration) hasUserDefinedEntrypoint() bool {
|
||||
if len(c.EntryPoints) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
switch len(c.EntryPoints) {
|
||||
case 1:
|
||||
return c.EntryPoints[hub.TunnelEntrypoint] == nil
|
||||
case 2:
|
||||
return c.EntryPoints[hub.TunnelEntrypoint] == nil || c.EntryPoints[hub.APIEntrypoint] == nil
|
||||
default:
|
||||
return true
|
||||
}
|
||||
return len(c.EntryPoints) != 0
|
||||
}
|
||||
|
||||
func (c *Configuration) initACMEProvider() {
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/hub"
|
||||
)
|
||||
|
||||
func TestHasEntrypoint(t *testing.T) {
|
||||
|
@ -24,53 +23,6 @@ func TestHasEntrypoint(t *testing.T) {
|
|||
},
|
||||
assert: assert.True,
|
||||
},
|
||||
{
|
||||
desc: "user defined entryPoints + hub entryPoint (tunnel)",
|
||||
entryPoints: map[string]*EntryPoint{
|
||||
"foo": {},
|
||||
hub.TunnelEntrypoint: {},
|
||||
},
|
||||
assert: assert.True,
|
||||
},
|
||||
{
|
||||
desc: "hub entryPoint (tunnel)",
|
||||
entryPoints: map[string]*EntryPoint{
|
||||
hub.TunnelEntrypoint: {},
|
||||
},
|
||||
assert: assert.False,
|
||||
},
|
||||
{
|
||||
desc: "user defined entryPoints + hub entryPoint (api)",
|
||||
entryPoints: map[string]*EntryPoint{
|
||||
"foo": {},
|
||||
hub.APIEntrypoint: {},
|
||||
},
|
||||
assert: assert.True,
|
||||
},
|
||||
{
|
||||
desc: "hub entryPoint (api)",
|
||||
entryPoints: map[string]*EntryPoint{
|
||||
hub.APIEntrypoint: {},
|
||||
},
|
||||
assert: assert.True,
|
||||
},
|
||||
{
|
||||
desc: "user defined entryPoints + hub entryPoints (tunnel, api)",
|
||||
entryPoints: map[string]*EntryPoint{
|
||||
"foo": {},
|
||||
hub.TunnelEntrypoint: {},
|
||||
hub.APIEntrypoint: {},
|
||||
},
|
||||
assert: assert.True,
|
||||
},
|
||||
{
|
||||
desc: "hub entryPoints (tunnel, api)",
|
||||
entryPoints: map[string]*EntryPoint{
|
||||
hub.TunnelEntrypoint: {},
|
||||
hub.APIEntrypoint: {},
|
||||
},
|
||||
assert: assert.False,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
|
|
@ -930,14 +930,8 @@ func (p *Provider) sanitizeDomains(ctx context.Context, domain types.Domain) ([]
|
|||
|
||||
var cleanDomains []string
|
||||
for _, dom := range domains {
|
||||
if strings.HasPrefix(dom, "*") {
|
||||
if p.DNSChallenge == nil {
|
||||
return nil, fmt.Errorf("unable to generate a wildcard certificate in ACME provider for domain %q : ACME needs a DNSChallenge", strings.Join(domains, ","))
|
||||
}
|
||||
|
||||
if strings.HasPrefix(dom, "*.*") {
|
||||
return nil, fmt.Errorf("unable to generate a wildcard certificate in ACME provider for domain %q : ACME does not allow '*.*' wildcard domain", strings.Join(domains, ","))
|
||||
}
|
||||
if strings.HasPrefix(dom, "*.*") {
|
||||
return nil, fmt.Errorf("unable to generate a wildcard certificate in ACME provider for domain %q : ACME does not allow '*.*' wildcard domain", strings.Join(domains, ","))
|
||||
}
|
||||
|
||||
canonicalDomain := types.CanonicalDomain(dom)
|
||||
|
|
|
@ -217,13 +217,6 @@ func TestProvider_sanitizeDomains(t *testing.T) {
|
|||
expectedErr: "no domain was given",
|
||||
expectedDomains: nil,
|
||||
},
|
||||
{
|
||||
desc: "no DNSChallenge",
|
||||
domains: types.Domain{Main: "*.traefik.wtf", SANs: []string{"foo.traefik.wtf"}},
|
||||
dnsChallenge: nil,
|
||||
expectedErr: "unable to generate a wildcard certificate in ACME provider for domain \"*.traefik.wtf,foo.traefik.wtf\" : ACME needs a DNSChallenge",
|
||||
expectedDomains: nil,
|
||||
},
|
||||
{
|
||||
desc: "unauthorized wildcard with SAN",
|
||||
domains: types.Domain{Main: "*.*.traefik.wtf", SANs: []string{"foo.traefik.wtf"}},
|
||||
|
|
|
@ -80,6 +80,10 @@ func NewProviderAggregator(conf static.Providers) ProviderAggregator {
|
|||
p.quietAddProvider(conf.Docker)
|
||||
}
|
||||
|
||||
if conf.Swarm != nil {
|
||||
p.quietAddProvider(conf.Swarm)
|
||||
}
|
||||
|
||||
if conf.Rest != nil {
|
||||
p.quietAddProvider(conf.Rest)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"strings"
|
||||
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
|
@ -17,7 +18,16 @@ import (
|
|||
"github.com/traefik/traefik/v3/pkg/provider/constraints"
|
||||
)
|
||||
|
||||
func (p *Provider) buildConfiguration(ctx context.Context, containersInspected []dockerData) *dynamic.Configuration {
|
||||
type DynConfBuilder struct {
|
||||
Shared
|
||||
apiClient client.APIClient
|
||||
}
|
||||
|
||||
func NewDynConfBuilder(configuration Shared, apiClient client.APIClient) *DynConfBuilder {
|
||||
return &DynConfBuilder{Shared: configuration, apiClient: apiClient}
|
||||
}
|
||||
|
||||
func (p *DynConfBuilder) build(ctx context.Context, containersInspected []dockerData) *dynamic.Configuration {
|
||||
configurations := make(map[string]*dynamic.Configuration)
|
||||
|
||||
for _, container := range containersInspected {
|
||||
|
@ -92,7 +102,7 @@ func (p *Provider) buildConfiguration(ctx context.Context, containersInspected [
|
|||
return provider.Merge(ctx, configurations)
|
||||
}
|
||||
|
||||
func (p *Provider) buildTCPServiceConfiguration(ctx context.Context, container dockerData, configuration *dynamic.TCPConfiguration) error {
|
||||
func (p *DynConfBuilder) buildTCPServiceConfiguration(ctx context.Context, container dockerData, configuration *dynamic.TCPConfiguration) error {
|
||||
serviceName := getServiceName(container)
|
||||
|
||||
if len(configuration.Services) == 0 {
|
||||
|
@ -117,7 +127,7 @@ func (p *Provider) buildTCPServiceConfiguration(ctx context.Context, container d
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) buildUDPServiceConfiguration(ctx context.Context, container dockerData, configuration *dynamic.UDPConfiguration) error {
|
||||
func (p *DynConfBuilder) buildUDPServiceConfiguration(ctx context.Context, container dockerData, configuration *dynamic.UDPConfiguration) error {
|
||||
serviceName := getServiceName(container)
|
||||
|
||||
if len(configuration.Services) == 0 {
|
||||
|
@ -141,7 +151,7 @@ func (p *Provider) buildUDPServiceConfiguration(ctx context.Context, container d
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) buildServiceConfiguration(ctx context.Context, container dockerData, configuration *dynamic.HTTPConfiguration) error {
|
||||
func (p *DynConfBuilder) buildServiceConfiguration(ctx context.Context, container dockerData, configuration *dynamic.HTTPConfiguration) error {
|
||||
serviceName := getServiceName(container)
|
||||
|
||||
if len(configuration.Services) == 0 {
|
||||
|
@ -167,7 +177,7 @@ func (p *Provider) buildServiceConfiguration(ctx context.Context, container dock
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) keepContainer(ctx context.Context, container dockerData) bool {
|
||||
func (p *DynConfBuilder) keepContainer(ctx context.Context, container dockerData) bool {
|
||||
logger := log.Ctx(ctx)
|
||||
|
||||
if !container.ExtraConf.Enable {
|
||||
|
@ -193,7 +203,7 @@ func (p *Provider) keepContainer(ctx context.Context, container dockerData) bool
|
|||
return true
|
||||
}
|
||||
|
||||
func (p *Provider) addServerTCP(ctx context.Context, container dockerData, loadBalancer *dynamic.TCPServersLoadBalancer) error {
|
||||
func (p *DynConfBuilder) addServerTCP(ctx context.Context, container dockerData, loadBalancer *dynamic.TCPServersLoadBalancer) error {
|
||||
if loadBalancer == nil {
|
||||
return errors.New("load-balancer is not defined")
|
||||
}
|
||||
|
@ -219,7 +229,7 @@ func (p *Provider) addServerTCP(ctx context.Context, container dockerData, loadB
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) addServerUDP(ctx context.Context, container dockerData, loadBalancer *dynamic.UDPServersLoadBalancer) error {
|
||||
func (p *DynConfBuilder) addServerUDP(ctx context.Context, container dockerData, loadBalancer *dynamic.UDPServersLoadBalancer) error {
|
||||
if loadBalancer == nil {
|
||||
return errors.New("load-balancer is not defined")
|
||||
}
|
||||
|
@ -245,7 +255,7 @@ func (p *Provider) addServerUDP(ctx context.Context, container dockerData, loadB
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) addServer(ctx context.Context, container dockerData, loadBalancer *dynamic.ServersLoadBalancer) error {
|
||||
func (p *DynConfBuilder) addServer(ctx context.Context, container dockerData, loadBalancer *dynamic.ServersLoadBalancer) error {
|
||||
if loadBalancer == nil {
|
||||
return errors.New("load-balancer is not defined")
|
||||
}
|
||||
|
@ -275,7 +285,7 @@ func (p *Provider) addServer(ctx context.Context, container dockerData, loadBala
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) getIPPort(ctx context.Context, container dockerData, serverPort string) (string, string, error) {
|
||||
func (p *DynConfBuilder) getIPPort(ctx context.Context, container dockerData, serverPort string) (string, string, error) {
|
||||
logger := log.Ctx(ctx)
|
||||
|
||||
var ip, port string
|
||||
|
@ -307,7 +317,7 @@ func (p *Provider) getIPPort(ctx context.Context, container dockerData, serverPo
|
|||
return ip, port, nil
|
||||
}
|
||||
|
||||
func (p Provider) getIPAddress(ctx context.Context, container dockerData) string {
|
||||
func (p *DynConfBuilder) getIPAddress(ctx context.Context, container dockerData) string {
|
||||
logger := log.Ctx(ctx)
|
||||
|
||||
netNotFound := false
|
||||
|
@ -338,23 +348,17 @@ func (p Provider) getIPAddress(ctx context.Context, container dockerData) string
|
|||
}
|
||||
|
||||
if container.NetworkSettings.NetworkMode.IsContainer() {
|
||||
dockerClient, err := p.createClient()
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Msg("Unable to get IP address")
|
||||
return ""
|
||||
}
|
||||
|
||||
connectedContainer := container.NetworkSettings.NetworkMode.ConnectedContainer()
|
||||
containerInspected, err := dockerClient.ContainerInspect(context.Background(), connectedContainer)
|
||||
containerInspected, err := p.apiClient.ContainerInspect(context.Background(), connectedContainer)
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Msgf("Unable to get IP address for container %s: failed to inspect container ID %s", container.Name, connectedContainer)
|
||||
return ""
|
||||
}
|
||||
|
||||
// Check connected container for traefik.docker.network, falling back to
|
||||
// the network specified on the current container.
|
||||
// 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)
|
||||
extraConf, err := p.extractLabels(containerParsed)
|
||||
if err != nil {
|
||||
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)
|
||||
return ""
|
||||
|
@ -379,8 +383,9 @@ func (p Provider) getIPAddress(ctx context.Context, container dockerData) string
|
|||
return ""
|
||||
}
|
||||
|
||||
func (p *Provider) getPortBinding(container dockerData, serverPort string) (*nat.PortBinding, error) {
|
||||
func (p *DynConfBuilder) 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 {
|
||||
|
@ -391,36 +396,3 @@ func (p *Provider) getPortBinding(container dockerData, serverPort string) (*nat
|
|||
|
||||
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]
|
||||
}
|
||||
|
||||
return provider.Normalize(serviceName)
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
)
|
||||
|
||||
func TestDefaultRule(t *testing.T) {
|
||||
func TestDynConfBuilder_DefaultRule(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
containers []dockerData
|
||||
|
@ -376,8 +376,10 @@ func TestDefaultRule(t *testing.T) {
|
|||
t.Parallel()
|
||||
|
||||
p := Provider{
|
||||
ExposedByDefault: true,
|
||||
DefaultRule: test.defaultRule,
|
||||
Shared: Shared{
|
||||
ExposedByDefault: true,
|
||||
DefaultRule: test.defaultRule,
|
||||
},
|
||||
}
|
||||
|
||||
err := p.Init()
|
||||
|
@ -385,18 +387,20 @@ func TestDefaultRule(t *testing.T) {
|
|||
|
||||
for i := 0; i < len(test.containers); i++ {
|
||||
var err error
|
||||
test.containers[i].ExtraConf, err = p.getConfiguration(test.containers[i])
|
||||
test.containers[i].ExtraConf, err = p.extractLabels(test.containers[i])
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
configuration := p.buildConfiguration(context.Background(), test.containers)
|
||||
builder := NewDynConfBuilder(p.Shared, nil)
|
||||
|
||||
configuration := builder.build(context.Background(), test.containers)
|
||||
|
||||
assert.Equal(t, test.expected, configuration)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_buildConfiguration(t *testing.T) {
|
||||
func TestDynConfBuilder_build(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
containers []dockerData
|
||||
|
@ -3381,10 +3385,12 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
t.Parallel()
|
||||
|
||||
p := Provider{
|
||||
AllowEmptyServices: test.allowEmptyServices,
|
||||
DefaultRule: "Host(`{{ normalize .Name }}.traefik.wtf`)",
|
||||
ExposedByDefault: true,
|
||||
UseBindPortIP: test.useBindPortIP,
|
||||
Shared: Shared{
|
||||
AllowEmptyServices: test.allowEmptyServices,
|
||||
ExposedByDefault: true,
|
||||
UseBindPortIP: test.useBindPortIP,
|
||||
DefaultRule: "Host(`{{ normalize .Name }}.traefik.wtf`)",
|
||||
},
|
||||
}
|
||||
p.Constraints = test.constraints
|
||||
|
||||
|
@ -3393,18 +3399,20 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
|
||||
for i := 0; i < len(test.containers); i++ {
|
||||
var err error
|
||||
test.containers[i].ExtraConf, err = p.getConfiguration(test.containers[i])
|
||||
test.containers[i].ExtraConf, err = p.extractLabels(test.containers[i])
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
configuration := p.buildConfiguration(context.Background(), test.containers)
|
||||
builder := NewDynConfBuilder(p.Shared, nil)
|
||||
|
||||
configuration := builder.build(context.Background(), test.containers)
|
||||
|
||||
assert.Equal(t, test.expected, configuration)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerGetIPPort(t *testing.T) {
|
||||
func TestDynConfBuilder_getIPPort_docker(t *testing.T) {
|
||||
type expected struct {
|
||||
ip string
|
||||
port string
|
||||
|
@ -3565,12 +3573,12 @@ func TestDockerGetIPPort(t *testing.T) {
|
|||
|
||||
dData := parseContainer(test.container)
|
||||
|
||||
provider := &Provider{
|
||||
builder := NewDynConfBuilder(Shared{
|
||||
Network: "testnet",
|
||||
UseBindPortIP: true,
|
||||
}
|
||||
}, nil)
|
||||
|
||||
actualIP, actualPort, actualError := provider.getIPPort(context.Background(), dData, test.serverPort)
|
||||
actualIP, actualPort, actualError := builder.getIPPort(context.Background(), dData, test.serverPort)
|
||||
if test.expected.error {
|
||||
require.Error(t, actualError)
|
||||
} else {
|
||||
|
@ -3582,73 +3590,7 @@ func TestDockerGetIPPort(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDockerGetPort(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
container docker.ContainerJSON
|
||||
serverPort string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "no binding, no server port label",
|
||||
container: containerJSON(name("foo")),
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
desc: "binding, no server port label",
|
||||
container: containerJSON(ports(nat.PortMap{
|
||||
"80/tcp": {},
|
||||
})),
|
||||
expected: "80",
|
||||
},
|
||||
{
|
||||
desc: "binding, multiple ports, no server port label",
|
||||
container: containerJSON(ports(nat.PortMap{
|
||||
"80/tcp": {},
|
||||
"443/tcp": {},
|
||||
})),
|
||||
expected: "80",
|
||||
},
|
||||
{
|
||||
desc: "no binding, server port label",
|
||||
container: containerJSON(),
|
||||
serverPort: "8080",
|
||||
expected: "8080",
|
||||
},
|
||||
{
|
||||
desc: "binding, server port label",
|
||||
container: containerJSON(
|
||||
ports(nat.PortMap{
|
||||
"80/tcp": {},
|
||||
})),
|
||||
serverPort: "8080",
|
||||
expected: "8080",
|
||||
},
|
||||
{
|
||||
desc: "binding, multiple ports, server port label",
|
||||
container: containerJSON(ports(nat.PortMap{
|
||||
"8080/tcp": {},
|
||||
"80/tcp": {},
|
||||
})),
|
||||
serverPort: "8080",
|
||||
expected: "8080",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dData := parseContainer(test.container)
|
||||
|
||||
actual := getPort(dData, test.serverPort)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerGetIPAddress(t *testing.T) {
|
||||
func TestDynConfBuilder_getIPAddress_docker(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
container docker.ContainerJSON
|
||||
|
@ -3742,24 +3684,26 @@ func TestDockerGetIPAddress(t *testing.T) {
|
|||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
provider := &Provider{
|
||||
conf := Shared{
|
||||
Network: "webnet",
|
||||
}
|
||||
|
||||
dData := parseContainer(test.container)
|
||||
|
||||
dData.ExtraConf.Docker.Network = provider.Network
|
||||
dData.ExtraConf.Docker.Network = conf.Network
|
||||
if len(test.network) > 0 {
|
||||
dData.ExtraConf.Docker.Network = test.network
|
||||
}
|
||||
|
||||
actual := provider.getIPAddress(context.Background(), dData)
|
||||
builder := NewDynConfBuilder(conf, nil)
|
||||
|
||||
actual := builder.getIPAddress(context.Background(), dData)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwarmGetIPAddress(t *testing.T) {
|
||||
func TestDynConfBuilder_getIPAddress_swarm(t *testing.T) {
|
||||
testCases := []struct {
|
||||
service swarm.Service
|
||||
expected string
|
||||
|
@ -3810,47 +3754,13 @@ func TestSwarmGetIPAddress(t *testing.T) {
|
|||
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
provider := &Provider{
|
||||
SwarmMode: true,
|
||||
}
|
||||
|
||||
dData, err := provider.parseService(context.Background(), test.service, test.networks)
|
||||
require.NoError(t, err)
|
||||
|
||||
actual := provider.getIPAddress(context.Background(), dData)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwarmGetPort(t *testing.T) {
|
||||
testCases := []struct {
|
||||
service swarm.Service
|
||||
serverPort string
|
||||
networks map[string]*docker.NetworkResource
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
service: swarmService(
|
||||
withEndpointSpec(modeDNSSR),
|
||||
),
|
||||
networks: map[string]*docker.NetworkResource{},
|
||||
serverPort: "8080",
|
||||
expected: "8080",
|
||||
},
|
||||
}
|
||||
|
||||
for serviceID, test := range testCases {
|
||||
test := test
|
||||
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p := Provider{}
|
||||
p := &SwarmProvider{}
|
||||
|
||||
dData, err := p.parseService(context.Background(), test.service, test.networks)
|
||||
require.NoError(t, err)
|
||||
|
||||
actual := getPort(dData, test.serverPort)
|
||||
builder := NewDynConfBuilder(p.Shared, nil)
|
||||
actual := builder.getIPAddress(context.Background(), dData)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
|
|
35
pkg/provider/docker/data.go
Normal file
35
pkg/provider/docker/data.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
dockercontainertypes "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/go-connections/nat"
|
||||
)
|
||||
|
||||
// dockerData holds the need data to the provider.
|
||||
type dockerData struct {
|
||||
ID string
|
||||
ServiceName string
|
||||
Name string
|
||||
Labels map[string]string // List of labels set to container or service
|
||||
NetworkSettings networkSettings
|
||||
Health string
|
||||
Node *dockertypes.ContainerNode
|
||||
ExtraConf configuration
|
||||
}
|
||||
|
||||
// NetworkSettings holds the networks data to the provider.
|
||||
type networkSettings struct {
|
||||
NetworkMode dockercontainertypes.NetworkMode
|
||||
Ports nat.PortMap
|
||||
Networks map[string]*networkData
|
||||
}
|
||||
|
||||
// Network holds the network data to the provider.
|
||||
type networkData struct {
|
||||
Name string
|
||||
Addr string
|
||||
Port int
|
||||
Protocol string
|
||||
ID string
|
||||
}
|
|
@ -1,602 +0,0 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/docker/cli/cli/connhelper"
|
||||
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"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/docker/go-connections/sockets"
|
||||
"github.com/rs/zerolog/log"
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/job"
|
||||
"github.com/traefik/traefik/v3/pkg/logs"
|
||||
"github.com/traefik/traefik/v3/pkg/provider"
|
||||
"github.com/traefik/traefik/v3/pkg/safe"
|
||||
"github.com/traefik/traefik/v3/pkg/types"
|
||||
"github.com/traefik/traefik/v3/pkg/version"
|
||||
)
|
||||
|
||||
const (
|
||||
// DockerAPIVersion is a constant holding the version of the Provider API traefik will use.
|
||||
DockerAPIVersion = "1.24"
|
||||
|
||||
// SwarmAPIVersion is a constant holding the version of the Provider API traefik will use.
|
||||
SwarmAPIVersion = "1.24"
|
||||
)
|
||||
|
||||
// DefaultTemplateRule The default template for the default rule.
|
||||
const DefaultTemplateRule = "Host(`{{ normalize .Name }}`)"
|
||||
|
||||
var _ provider.Provider = (*Provider)(nil)
|
||||
|
||||
// Provider holds configurations of the provider.
|
||||
type Provider struct {
|
||||
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"`
|
||||
Watch bool `description:"Watch Docker events." json:"watch,omitempty" toml:"watch,omitempty" yaml:"watch,omitempty" export:"true"`
|
||||
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"`
|
||||
SwarmModeRefreshSeconds ptypes.Duration `description:"Polling interval for swarm mode." json:"swarmModeRefreshSeconds,omitempty" toml:"swarmModeRefreshSeconds,omitempty" yaml:"swarmModeRefreshSeconds,omitempty" export:"true"`
|
||||
HTTPClientTimeout ptypes.Duration `description:"Client timeout for HTTP connections." json:"httpClientTimeout,omitempty" toml:"httpClientTimeout,omitempty" yaml:"httpClientTimeout,omitempty" export:"true"`
|
||||
AllowEmptyServices bool `description:"Disregards the Docker containers health checks with respect to the creation or removal of the corresponding services." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"`
|
||||
defaultRuleTpl *template.Template
|
||||
}
|
||||
|
||||
// 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
|
||||
p.SwarmModeRefreshSeconds = ptypes.Duration(15 * time.Second)
|
||||
p.DefaultRule = DefaultTemplateRule
|
||||
}
|
||||
|
||||
// Init the provider.
|
||||
func (p *Provider) Init() error {
|
||||
defaultRuleTpl, err := provider.MakeDefaultRuleTemplate(p.DefaultRule, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while parsing default rule: %w", err)
|
||||
}
|
||||
|
||||
p.defaultRuleTpl = defaultRuleTpl
|
||||
return nil
|
||||
}
|
||||
|
||||
// dockerData holds the need data to the provider.
|
||||
type dockerData struct {
|
||||
ID string
|
||||
ServiceName string
|
||||
Name string
|
||||
Labels map[string]string // List of labels set to container or service
|
||||
NetworkSettings networkSettings
|
||||
Health string
|
||||
Node *dockertypes.ContainerNode
|
||||
ExtraConf configuration
|
||||
}
|
||||
|
||||
// NetworkSettings holds the networks data to the provider.
|
||||
type networkSettings struct {
|
||||
NetworkMode dockercontainertypes.NetworkMode
|
||||
Ports nat.PortMap
|
||||
Networks map[string]*networkData
|
||||
}
|
||||
|
||||
// Network holds the network data to the provider.
|
||||
type networkData struct {
|
||||
Name string
|
||||
Addr string
|
||||
Port int
|
||||
Protocol string
|
||||
ID string
|
||||
}
|
||||
|
||||
func (p *Provider) createClient() (client.APIClient, error) {
|
||||
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),
|
||||
client.WithTimeout(time.Duration(p.HTTPClientTimeout)),
|
||||
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),
|
||||
client.WithTimeout(time.Duration(p.HTTPClientTimeout)),
|
||||
}
|
||||
|
||||
if p.TLS != nil {
|
||||
ctx := log.With().Str(logs.ProviderName, "docker").Logger().WithContext(context.Background())
|
||||
|
||||
conf, err := p.TLS.CreateTLSConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create client TLS configuration: %w", err)
|
||||
}
|
||||
|
||||
hostURL, err := client.ParseHostURL(p.Endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: conf,
|
||||
}
|
||||
|
||||
if err := sockets.ConfigureTransport(tr, hostURL.Scheme, hostURL.Host); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opts = append(opts, client.WithHTTPClient(&http.Client{Transport: tr, Timeout: time.Duration(p.HTTPClientTimeout)}))
|
||||
}
|
||||
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
// Provide allows the docker provider to provide configurations to traefik using the given configuration channel.
|
||||
func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
|
||||
pool.GoCtx(func(routineCtx context.Context) {
|
||||
logger := log.Ctx(routineCtx).With().Str(logs.ProviderName, "docker").Logger()
|
||||
ctxLog := logger.WithContext(routineCtx)
|
||||
|
||||
operation := func() error {
|
||||
var err error
|
||||
ctx, cancel := context.WithCancel(ctxLog)
|
||||
defer cancel()
|
||||
ctx = log.Ctx(ctx).With().Str(logs.ProviderName, "docker").Logger().WithContext(ctx)
|
||||
|
||||
dockerClient, err := p.createClient()
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Failed to create a client for docker, error")
|
||||
return err
|
||||
}
|
||||
defer dockerClient.Close()
|
||||
|
||||
serverVersion, err := dockerClient.ServerVersion(ctx)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Failed to retrieve information of the docker client and server host")
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Debug().Msgf("Provider connection established with docker %s (API %s)", serverVersion.Version, serverVersion.APIVersion)
|
||||
|
||||
var dockerDataList []dockerData
|
||||
if p.SwarmMode {
|
||||
dockerDataList, err = p.listServices(ctx, dockerClient)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Failed to list services for docker swarm mode")
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
dockerDataList, err = p.listContainers(ctx, dockerClient)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Failed to list containers for docker")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
configuration := p.buildConfiguration(ctxLog, dockerDataList)
|
||||
configurationChan <- dynamic.Message{
|
||||
ProviderName: "docker",
|
||||
Configuration: configuration,
|
||||
}
|
||||
if p.Watch {
|
||||
if p.SwarmMode {
|
||||
errChan := make(chan error)
|
||||
|
||||
// TODO: This need to be change. Linked to Swarm events docker/docker#23827
|
||||
ticker := time.NewTicker(time.Duration(p.SwarmModeRefreshSeconds))
|
||||
|
||||
pool.GoCtx(func(ctx context.Context) {
|
||||
logger := log.Ctx(ctx).With().Str(logs.ProviderName, "docker").Logger()
|
||||
ctx = logger.WithContext(ctx)
|
||||
|
||||
defer close(errChan)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
services, err := p.listServices(ctx, dockerClient)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Failed to list services for docker swarm mode")
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
configuration := p.buildConfiguration(ctx, services)
|
||||
if configuration != nil {
|
||||
configurationChan <- dynamic.Message{
|
||||
ProviderName: "docker",
|
||||
Configuration: configuration,
|
||||
}
|
||||
}
|
||||
|
||||
case <-ctx.Done():
|
||||
ticker.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
if err, ok := <-errChan; ok {
|
||||
return err
|
||||
}
|
||||
// channel closed
|
||||
} else {
|
||||
f := filters.NewArgs()
|
||||
f.Add("type", "container")
|
||||
options := dockertypes.EventsOptions{
|
||||
Filters: f,
|
||||
}
|
||||
|
||||
startStopHandle := func(m eventtypes.Message) {
|
||||
logger.Debug().Msgf("Provider event received %+v", m)
|
||||
containers, err := p.listContainers(ctx, dockerClient)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Failed to list containers for docker")
|
||||
// Call cancel to get out of the monitor
|
||||
return
|
||||
}
|
||||
|
||||
configuration := p.buildConfiguration(ctx, containers)
|
||||
if configuration != nil {
|
||||
message := dynamic.Message{
|
||||
ProviderName: "docker",
|
||||
Configuration: configuration,
|
||||
}
|
||||
select {
|
||||
case configurationChan <- message:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eventsc, errc := dockerClient.Events(ctx, options)
|
||||
for {
|
||||
select {
|
||||
case event := <-eventsc:
|
||||
if event.Action == "start" ||
|
||||
event.Action == "die" ||
|
||||
strings.HasPrefix(event.Action, "health_status") {
|
||||
startStopHandle(event)
|
||||
}
|
||||
case err := <-errc:
|
||||
if errors.Is(err, io.EOF) {
|
||||
logger.Debug().Msg("Provider event stream closed")
|
||||
}
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
notify := func(err error, time time.Duration) {
|
||||
logger.Error().Err(err).Msgf("Provider error, retrying in %s", time)
|
||||
}
|
||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), ctxLog), notify)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Cannot retrieve data")
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) listContainers(ctx context.Context, dockerClient client.ContainerAPIClient) ([]dockerData, error) {
|
||||
containerList, err := dockerClient.ContainerList(ctx, dockertypes.ContainerListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var inspectedContainers []dockerData
|
||||
// get inspect containers
|
||||
for _, container := range containerList {
|
||||
dData := inspectContainers(ctx, dockerClient, container.ID)
|
||||
if len(dData.Name) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
extraConf, err := p.getConfiguration(dData)
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Error().Err(err).Msgf("Skip container %s", getServiceName(dData))
|
||||
continue
|
||||
}
|
||||
dData.ExtraConf = extraConf
|
||||
|
||||
inspectedContainers = append(inspectedContainers, dData)
|
||||
}
|
||||
return inspectedContainers, nil
|
||||
}
|
||||
|
||||
func inspectContainers(ctx context.Context, dockerClient client.ContainerAPIClient, containerID string) dockerData {
|
||||
containerInspected, err := dockerClient.ContainerInspect(ctx, containerID)
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Warn().Err(err).Msgf("Failed to inspect container %s", containerID)
|
||||
return dockerData{}
|
||||
}
|
||||
|
||||
// This condition is here to avoid to have empty IP https://github.com/traefik/traefik/issues/2459
|
||||
// We register only container which are running
|
||||
if containerInspected.ContainerJSONBase != nil && containerInspected.ContainerJSONBase.State != nil && containerInspected.ContainerJSONBase.State.Running {
|
||||
return parseContainer(containerInspected)
|
||||
}
|
||||
|
||||
return dockerData{}
|
||||
}
|
||||
|
||||
func parseContainer(container dockertypes.ContainerJSON) dockerData {
|
||||
dData := dockerData{
|
||||
NetworkSettings: networkSettings{},
|
||||
}
|
||||
|
||||
if container.ContainerJSONBase != nil {
|
||||
dData.ID = container.ContainerJSONBase.ID
|
||||
dData.Name = container.ContainerJSONBase.Name
|
||||
dData.ServiceName = dData.Name // Default ServiceName to be the container's Name.
|
||||
dData.Node = container.ContainerJSONBase.Node
|
||||
|
||||
if container.ContainerJSONBase.HostConfig != nil {
|
||||
dData.NetworkSettings.NetworkMode = container.ContainerJSONBase.HostConfig.NetworkMode
|
||||
}
|
||||
|
||||
if container.State != nil && container.State.Health != nil {
|
||||
dData.Health = container.State.Health.Status
|
||||
}
|
||||
}
|
||||
|
||||
if container.Config != nil && container.Config.Labels != nil {
|
||||
dData.Labels = container.Config.Labels
|
||||
}
|
||||
|
||||
if container.NetworkSettings != nil {
|
||||
if container.NetworkSettings.Ports != nil {
|
||||
dData.NetworkSettings.Ports = container.NetworkSettings.Ports
|
||||
}
|
||||
if container.NetworkSettings.Networks != nil {
|
||||
dData.NetworkSettings.Networks = make(map[string]*networkData)
|
||||
for name, containerNetwork := range container.NetworkSettings.Networks {
|
||||
addr := containerNetwork.IPAddress
|
||||
if addr == "" {
|
||||
addr = containerNetwork.GlobalIPv6Address
|
||||
}
|
||||
|
||||
dData.NetworkSettings.Networks[name] = &networkData{
|
||||
ID: containerNetwork.NetworkID,
|
||||
Name: name,
|
||||
Addr: addr,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return dData
|
||||
}
|
||||
|
||||
func (p *Provider) listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerData, error) {
|
||||
logger := log.Ctx(ctx)
|
||||
|
||||
serviceList, err := dockerClient.ServiceList(ctx, dockertypes.ServiceListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serverVersion, err := dockerClient.ServerVersion(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
networkListArgs := filters.NewArgs()
|
||||
// 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")
|
||||
}
|
||||
|
||||
networkList, err := dockerClient.NetworkList(ctx, dockertypes.NetworkListOptions{Filters: networkListArgs})
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Msg("Failed to network inspect on client for docker")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
networkMap := make(map[string]*dockertypes.NetworkResource)
|
||||
for _, network := range networkList {
|
||||
networkToAdd := network
|
||||
networkMap[network.ID] = &networkToAdd
|
||||
}
|
||||
|
||||
var dockerDataList []dockerData
|
||||
var dockerDataListTasks []dockerData
|
||||
|
||||
for _, service := range serviceList {
|
||||
dData, err := p.parseService(ctx, service, networkMap)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msgf("Skip container %s", getServiceName(dData))
|
||||
continue
|
||||
}
|
||||
|
||||
if dData.ExtraConf.Docker.LBSwarm {
|
||||
if len(dData.NetworkSettings.Networks) > 0 {
|
||||
dockerDataList = append(dockerDataList, dData)
|
||||
}
|
||||
} else {
|
||||
isGlobalSvc := service.Spec.Mode.Global != nil
|
||||
dockerDataListTasks, err = listTasks(ctx, dockerClient, service.ID, dData, networkMap, isGlobalSvc)
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Send()
|
||||
} else {
|
||||
dockerDataList = append(dockerDataList, dockerDataListTasks...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return dockerDataList, err
|
||||
}
|
||||
|
||||
func (p *Provider) parseService(ctx context.Context, service swarmtypes.Service, networkMap map[string]*dockertypes.NetworkResource) (dockerData, error) {
|
||||
logger := log.Ctx(ctx)
|
||||
|
||||
dData := dockerData{
|
||||
ID: service.ID,
|
||||
ServiceName: service.Spec.Annotations.Name,
|
||||
Name: service.Spec.Annotations.Name,
|
||||
Labels: service.Spec.Annotations.Labels,
|
||||
NetworkSettings: networkSettings{},
|
||||
}
|
||||
|
||||
extraConf, err := p.getConfiguration(dData)
|
||||
if err != nil {
|
||||
return dockerData{}, err
|
||||
}
|
||||
dData.ExtraConf = extraConf
|
||||
|
||||
if service.Spec.EndpointSpec != nil {
|
||||
if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeDNSRR {
|
||||
if dData.ExtraConf.Docker.LBSwarm {
|
||||
logger.Warn().Msgf("Ignored %s endpoint-mode not supported, service name: %s. Fallback to Traefik load balancing", swarmtypes.ResolutionModeDNSRR, service.Spec.Annotations.Name)
|
||||
}
|
||||
} else if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeVIP {
|
||||
dData.NetworkSettings.Networks = make(map[string]*networkData)
|
||||
for _, virtualIP := range service.Endpoint.VirtualIPs {
|
||||
networkService := networkMap[virtualIP.NetworkID]
|
||||
if networkService != nil {
|
||||
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 {
|
||||
logger.Debug().Msgf("No virtual IPs found in network %s", virtualIP.NetworkID)
|
||||
}
|
||||
} else {
|
||||
logger.Debug().Msgf("Network not found, id: %s", virtualIP.NetworkID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return dData, nil
|
||||
}
|
||||
|
||||
func listTasks(ctx context.Context, dockerClient client.APIClient, serviceID string,
|
||||
serviceDockerData dockerData, networkMap map[string]*dockertypes.NetworkResource, isGlobalSvc bool,
|
||||
) ([]dockerData, error) {
|
||||
serviceIDFilter := filters.NewArgs()
|
||||
serviceIDFilter.Add("service", serviceID)
|
||||
serviceIDFilter.Add("desired-state", "running")
|
||||
|
||||
taskList, err := dockerClient.TaskList(ctx, dockertypes.TaskListOptions{Filters: serviceIDFilter})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var dockerDataList []dockerData
|
||||
for _, task := range taskList {
|
||||
if task.Status.State != swarmtypes.TaskStateRunning {
|
||||
continue
|
||||
}
|
||||
dData := parseTasks(ctx, task, serviceDockerData, networkMap, isGlobalSvc)
|
||||
if len(dData.NetworkSettings.Networks) > 0 {
|
||||
dockerDataList = append(dockerDataList, dData)
|
||||
}
|
||||
}
|
||||
return dockerDataList, err
|
||||
}
|
||||
|
||||
func parseTasks(ctx context.Context, task swarmtypes.Task, serviceDockerData dockerData,
|
||||
networkMap map[string]*dockertypes.NetworkResource, isGlobalSvc bool,
|
||||
) dockerData {
|
||||
dData := dockerData{
|
||||
ID: task.ID,
|
||||
ServiceName: serviceDockerData.Name,
|
||||
Name: serviceDockerData.Name + "." + strconv.Itoa(task.Slot),
|
||||
Labels: serviceDockerData.Labels,
|
||||
ExtraConf: serviceDockerData.ExtraConf,
|
||||
NetworkSettings: networkSettings{},
|
||||
}
|
||||
|
||||
if isGlobalSvc {
|
||||
dData.Name = serviceDockerData.Name + "." + task.ID
|
||||
}
|
||||
|
||||
if task.NetworksAttachments != nil {
|
||||
dData.NetworkSettings.Networks = make(map[string]*networkData)
|
||||
for _, virtualIP := range task.NetworksAttachments {
|
||||
if networkService, present := networkMap[virtualIP.Network.ID]; present {
|
||||
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
|
||||
}
|
||||
} else {
|
||||
log.Ctx(ctx).Debug().Msgf("No IP addresses found for network %s", virtualIP.Network.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return dData
|
||||
}
|
193
pkg/provider/docker/pdocker.go
Normal file
193
pkg/provider/docker/pdocker.go
Normal file
|
@ -0,0 +1,193 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
eventtypes "github.com/docker/docker/api/types/events"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/job"
|
||||
"github.com/traefik/traefik/v3/pkg/logs"
|
||||
"github.com/traefik/traefik/v3/pkg/provider"
|
||||
"github.com/traefik/traefik/v3/pkg/safe"
|
||||
)
|
||||
|
||||
// DockerAPIVersion is a constant holding the version of the Provider API traefik will use.
|
||||
const DockerAPIVersion = "1.24"
|
||||
|
||||
const dockerName = "docker"
|
||||
|
||||
var _ provider.Provider = (*Provider)(nil)
|
||||
|
||||
// Provider holds configurations of the provider.
|
||||
type Provider struct {
|
||||
Shared `yaml:",inline" export:"true"`
|
||||
ClientConfig `yaml:",inline" export:"true"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (p *Provider) SetDefaults() {
|
||||
p.Watch = true
|
||||
p.ExposedByDefault = true
|
||||
p.Endpoint = "unix:///var/run/docker.sock"
|
||||
p.DefaultRule = DefaultTemplateRule
|
||||
}
|
||||
|
||||
// Init the provider.
|
||||
func (p *Provider) Init() error {
|
||||
defaultRuleTpl, err := provider.MakeDefaultRuleTemplate(p.DefaultRule, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while parsing default rule: %w", err)
|
||||
}
|
||||
|
||||
p.defaultRuleTpl = defaultRuleTpl
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) createClient(ctx context.Context) (*client.Client, error) {
|
||||
p.ClientConfig.apiVersion = DockerAPIVersion
|
||||
return createClient(ctx, p.ClientConfig)
|
||||
}
|
||||
|
||||
// Provide allows the docker provider to provide configurations to traefik using the given configuration channel.
|
||||
func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
|
||||
pool.GoCtx(func(routineCtx context.Context) {
|
||||
logger := log.Ctx(routineCtx).With().Str(logs.ProviderName, dockerName).Logger()
|
||||
ctxLog := logger.WithContext(routineCtx)
|
||||
|
||||
operation := func() error {
|
||||
var err error
|
||||
ctx, cancel := context.WithCancel(ctxLog)
|
||||
defer cancel()
|
||||
|
||||
ctx = log.Ctx(ctx).With().Str(logs.ProviderName, dockerName).Logger().WithContext(ctx)
|
||||
|
||||
dockerClient, err := p.createClient(ctxLog)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Failed to create Docker API client")
|
||||
return err
|
||||
}
|
||||
defer func() { _ = dockerClient.Close() }()
|
||||
|
||||
builder := NewDynConfBuilder(p.Shared, dockerClient)
|
||||
|
||||
serverVersion, err := dockerClient.ServerVersion(ctx)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Failed to retrieve information of the docker client and server host")
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Debug().Msgf("Provider connection established with docker %s (API %s)", serverVersion.Version, serverVersion.APIVersion)
|
||||
|
||||
dockerDataList, err := p.listContainers(ctx, dockerClient)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Failed to list containers for docker")
|
||||
return err
|
||||
}
|
||||
|
||||
configuration := builder.build(ctxLog, dockerDataList)
|
||||
configurationChan <- dynamic.Message{
|
||||
ProviderName: dockerName,
|
||||
Configuration: configuration,
|
||||
}
|
||||
|
||||
if p.Watch {
|
||||
f := filters.NewArgs()
|
||||
f.Add("type", "container")
|
||||
options := dockertypes.EventsOptions{
|
||||
Filters: f,
|
||||
}
|
||||
|
||||
startStopHandle := func(m eventtypes.Message) {
|
||||
logger.Debug().Msgf("Provider event received %+v", m)
|
||||
containers, err := p.listContainers(ctx, dockerClient)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Failed to list containers for docker")
|
||||
// Call cancel to get out of the monitor
|
||||
return
|
||||
}
|
||||
|
||||
configuration := builder.build(ctx, containers)
|
||||
if configuration != nil {
|
||||
message := dynamic.Message{
|
||||
ProviderName: dockerName,
|
||||
Configuration: configuration,
|
||||
}
|
||||
select {
|
||||
case configurationChan <- message:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eventsc, errc := dockerClient.Events(ctx, options)
|
||||
for {
|
||||
select {
|
||||
case event := <-eventsc:
|
||||
if event.Action == "start" ||
|
||||
event.Action == "die" ||
|
||||
strings.HasPrefix(event.Action, "health_status") {
|
||||
startStopHandle(event)
|
||||
}
|
||||
case err := <-errc:
|
||||
if errors.Is(err, io.EOF) {
|
||||
logger.Debug().Msg("Provider event stream closed")
|
||||
}
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
notify := func(err error, time time.Duration) {
|
||||
logger.Error().Err(err).Msgf("Provider error, retrying in %s", time)
|
||||
}
|
||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), ctxLog), notify)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Cannot retrieve data")
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) listContainers(ctx context.Context, dockerClient client.ContainerAPIClient) ([]dockerData, error) {
|
||||
containerList, err := dockerClient.ContainerList(ctx, dockertypes.ContainerListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var inspectedContainers []dockerData
|
||||
// get inspect containers
|
||||
for _, container := range containerList {
|
||||
dData := inspectContainers(ctx, dockerClient, container.ID)
|
||||
if len(dData.Name) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
extraConf, err := p.extractLabels(dData)
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Error().Err(err).Msgf("Skip container %s", getServiceName(dData))
|
||||
continue
|
||||
}
|
||||
|
||||
dData.ExtraConf = extraConf
|
||||
|
||||
inspectedContainers = append(inspectedContainers, dData)
|
||||
}
|
||||
|
||||
return inspectedContainers, nil
|
||||
}
|
332
pkg/provider/docker/pswarm.go
Normal file
332
pkg/provider/docker/pswarm.go
Normal file
|
@ -0,0 +1,332 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
swarmtypes "github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/rs/zerolog/log"
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/job"
|
||||
"github.com/traefik/traefik/v3/pkg/logs"
|
||||
"github.com/traefik/traefik/v3/pkg/provider"
|
||||
"github.com/traefik/traefik/v3/pkg/safe"
|
||||
)
|
||||
|
||||
// SwarmAPIVersion is a constant holding the version of the Provider API traefik will use.
|
||||
const SwarmAPIVersion = "1.24"
|
||||
|
||||
const swarmName = "swarm"
|
||||
|
||||
var _ provider.Provider = (*SwarmProvider)(nil)
|
||||
|
||||
// SwarmProvider holds configurations of the provider.
|
||||
type SwarmProvider struct {
|
||||
Shared `yaml:",inline" export:"true"`
|
||||
ClientConfig `yaml:",inline" export:"true"`
|
||||
|
||||
RefreshSeconds ptypes.Duration `description:"Polling interval for swarm mode." json:"refreshSeconds,omitempty" toml:"refreshSeconds,omitempty" yaml:"refreshSeconds,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (p *SwarmProvider) SetDefaults() {
|
||||
p.Watch = true
|
||||
p.ExposedByDefault = true
|
||||
p.Endpoint = "unix:///var/run/docker.sock"
|
||||
p.RefreshSeconds = ptypes.Duration(15 * time.Second)
|
||||
p.DefaultRule = DefaultTemplateRule
|
||||
}
|
||||
|
||||
// Init the provider.
|
||||
func (p *SwarmProvider) Init() error {
|
||||
defaultRuleTpl, err := provider.MakeDefaultRuleTemplate(p.DefaultRule, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while parsing default rule: %w", err)
|
||||
}
|
||||
|
||||
p.defaultRuleTpl = defaultRuleTpl
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *SwarmProvider) createClient(ctx context.Context) (*client.Client, error) {
|
||||
p.ClientConfig.apiVersion = SwarmAPIVersion
|
||||
return createClient(ctx, p.ClientConfig)
|
||||
}
|
||||
|
||||
// Provide allows the docker provider to provide configurations to traefik using the given configuration channel.
|
||||
func (p *SwarmProvider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
|
||||
pool.GoCtx(func(routineCtx context.Context) {
|
||||
logger := log.Ctx(routineCtx).With().Str(logs.ProviderName, swarmName).Logger()
|
||||
ctxLog := logger.WithContext(routineCtx)
|
||||
|
||||
operation := func() error {
|
||||
var err error
|
||||
ctx, cancel := context.WithCancel(ctxLog)
|
||||
defer cancel()
|
||||
|
||||
ctx = log.Ctx(ctx).With().Str(logs.ProviderName, swarmName).Logger().WithContext(ctx)
|
||||
|
||||
dockerClient, err := p.createClient(ctx)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Failed to create Docker API client")
|
||||
return err
|
||||
}
|
||||
defer func() { _ = dockerClient.Close() }()
|
||||
|
||||
builder := NewDynConfBuilder(p.Shared, dockerClient)
|
||||
|
||||
serverVersion, err := dockerClient.ServerVersion(ctx)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Failed to retrieve information of the docker client and server host")
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Debug().Msgf("Provider connection established with docker %s (API %s)", serverVersion.Version, serverVersion.APIVersion)
|
||||
|
||||
dockerDataList, err := p.listServices(ctx, dockerClient)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Failed to list services for docker swarm mode")
|
||||
return err
|
||||
}
|
||||
|
||||
configuration := builder.build(ctxLog, dockerDataList)
|
||||
configurationChan <- dynamic.Message{
|
||||
ProviderName: swarmName,
|
||||
Configuration: configuration,
|
||||
}
|
||||
if p.Watch {
|
||||
errChan := make(chan error)
|
||||
|
||||
// TODO: This need to be change. Linked to Swarm events docker/docker#23827
|
||||
ticker := time.NewTicker(time.Duration(p.RefreshSeconds))
|
||||
|
||||
pool.GoCtx(func(ctx context.Context) {
|
||||
logger := log.Ctx(ctx).With().Str(logs.ProviderName, swarmName).Logger()
|
||||
ctx = logger.WithContext(ctx)
|
||||
|
||||
defer close(errChan)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
services, err := p.listServices(ctx, dockerClient)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Failed to list services for docker swarm mode")
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
configuration := builder.build(ctx, services)
|
||||
if configuration != nil {
|
||||
configurationChan <- dynamic.Message{
|
||||
ProviderName: swarmName,
|
||||
Configuration: configuration,
|
||||
}
|
||||
}
|
||||
|
||||
case <-ctx.Done():
|
||||
ticker.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
if err, ok := <-errChan; ok {
|
||||
return err
|
||||
}
|
||||
// channel closed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
notify := func(err error, time time.Duration) {
|
||||
logger.Error().Err(err).Msgf("Provider error, retrying in %s", time)
|
||||
}
|
||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), ctxLog), notify)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Cannot retrieve data")
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *SwarmProvider) listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerData, error) {
|
||||
logger := log.Ctx(ctx)
|
||||
|
||||
serviceList, err := dockerClient.ServiceList(ctx, dockertypes.ServiceListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serverVersion, err := dockerClient.ServerVersion(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
networkListArgs := filters.NewArgs()
|
||||
// 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")
|
||||
}
|
||||
|
||||
networkList, err := dockerClient.NetworkList(ctx, dockertypes.NetworkListOptions{Filters: networkListArgs})
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Msg("Failed to network inspect on client for docker")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
networkMap := make(map[string]*dockertypes.NetworkResource)
|
||||
for _, network := range networkList {
|
||||
networkToAdd := network
|
||||
networkMap[network.ID] = &networkToAdd
|
||||
}
|
||||
|
||||
var dockerDataList []dockerData
|
||||
var dockerDataListTasks []dockerData
|
||||
|
||||
for _, service := range serviceList {
|
||||
dData, err := p.parseService(ctx, service, networkMap)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msgf("Skip container %s", getServiceName(dData))
|
||||
continue
|
||||
}
|
||||
|
||||
if dData.ExtraConf.Docker.LBSwarm {
|
||||
if len(dData.NetworkSettings.Networks) > 0 {
|
||||
dockerDataList = append(dockerDataList, dData)
|
||||
}
|
||||
} else {
|
||||
isGlobalSvc := service.Spec.Mode.Global != nil
|
||||
dockerDataListTasks, err = listTasks(ctx, dockerClient, service.ID, dData, networkMap, isGlobalSvc)
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Send()
|
||||
} else {
|
||||
dockerDataList = append(dockerDataList, dockerDataListTasks...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dockerDataList, err
|
||||
}
|
||||
|
||||
func (p *SwarmProvider) parseService(ctx context.Context, service swarmtypes.Service, networkMap map[string]*dockertypes.NetworkResource) (dockerData, error) {
|
||||
logger := log.Ctx(ctx)
|
||||
|
||||
dData := dockerData{
|
||||
ID: service.ID,
|
||||
ServiceName: service.Spec.Annotations.Name,
|
||||
Name: service.Spec.Annotations.Name,
|
||||
Labels: service.Spec.Annotations.Labels,
|
||||
NetworkSettings: networkSettings{},
|
||||
}
|
||||
|
||||
extraConf, err := p.extractLabels(dData)
|
||||
if err != nil {
|
||||
return dockerData{}, err
|
||||
}
|
||||
dData.ExtraConf = extraConf
|
||||
|
||||
if service.Spec.EndpointSpec != nil {
|
||||
if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeDNSRR {
|
||||
if dData.ExtraConf.Docker.LBSwarm {
|
||||
logger.Warn().Msgf("Ignored %s endpoint-mode not supported, service name: %s. Fallback to Traefik load balancing", swarmtypes.ResolutionModeDNSRR, service.Spec.Annotations.Name)
|
||||
}
|
||||
} else if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeVIP {
|
||||
dData.NetworkSettings.Networks = make(map[string]*networkData)
|
||||
for _, virtualIP := range service.Endpoint.VirtualIPs {
|
||||
networkService := networkMap[virtualIP.NetworkID]
|
||||
if networkService != nil {
|
||||
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 {
|
||||
logger.Debug().Msgf("No virtual IPs found in network %s", virtualIP.NetworkID)
|
||||
}
|
||||
} else {
|
||||
logger.Debug().Msgf("Network not found, id: %s", virtualIP.NetworkID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return dData, nil
|
||||
}
|
||||
|
||||
func listTasks(ctx context.Context, dockerClient client.APIClient, serviceID string,
|
||||
serviceDockerData dockerData, networkMap map[string]*dockertypes.NetworkResource, isGlobalSvc bool,
|
||||
) ([]dockerData, error) {
|
||||
serviceIDFilter := filters.NewArgs()
|
||||
serviceIDFilter.Add("service", serviceID)
|
||||
serviceIDFilter.Add("desired-state", "running")
|
||||
|
||||
taskList, err := dockerClient.TaskList(ctx, dockertypes.TaskListOptions{Filters: serviceIDFilter})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var dockerDataList []dockerData
|
||||
for _, task := range taskList {
|
||||
if task.Status.State != swarmtypes.TaskStateRunning {
|
||||
continue
|
||||
}
|
||||
dData := parseTasks(ctx, task, serviceDockerData, networkMap, isGlobalSvc)
|
||||
if len(dData.NetworkSettings.Networks) > 0 {
|
||||
dockerDataList = append(dockerDataList, dData)
|
||||
}
|
||||
}
|
||||
return dockerDataList, err
|
||||
}
|
||||
|
||||
func parseTasks(ctx context.Context, task swarmtypes.Task, serviceDockerData dockerData,
|
||||
networkMap map[string]*dockertypes.NetworkResource, isGlobalSvc bool,
|
||||
) dockerData {
|
||||
dData := dockerData{
|
||||
ID: task.ID,
|
||||
ServiceName: serviceDockerData.Name,
|
||||
Name: serviceDockerData.Name + "." + strconv.Itoa(task.Slot),
|
||||
Labels: serviceDockerData.Labels,
|
||||
ExtraConf: serviceDockerData.ExtraConf,
|
||||
NetworkSettings: networkSettings{},
|
||||
}
|
||||
|
||||
if isGlobalSvc {
|
||||
dData.Name = serviceDockerData.Name + "." + task.ID
|
||||
}
|
||||
|
||||
if task.NetworksAttachments != nil {
|
||||
dData.NetworkSettings.Networks = make(map[string]*networkData)
|
||||
for _, virtualIP := range task.NetworksAttachments {
|
||||
if networkService, present := networkMap[virtualIP.Network.ID]; present {
|
||||
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
|
||||
}
|
||||
} else {
|
||||
log.Ctx(ctx).Debug().Msgf("No IP addresses found for network %s", virtualIP.Network.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return dData
|
||||
}
|
49
pkg/provider/docker/pswarm_mock_test.go
Normal file
49
pkg/provider/docker/pswarm_mock_test.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
dockerclient "github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
type fakeTasksClient struct {
|
||||
dockerclient.APIClient
|
||||
tasks []swarm.Task
|
||||
container dockertypes.ContainerJSON
|
||||
err error
|
||||
}
|
||||
|
||||
func (c *fakeTasksClient) TaskList(ctx context.Context, options dockertypes.TaskListOptions) ([]swarm.Task, error) {
|
||||
return c.tasks, c.err
|
||||
}
|
||||
|
||||
func (c *fakeTasksClient) ContainerInspect(ctx context.Context, container string) (dockertypes.ContainerJSON, error) {
|
||||
return c.container, c.err
|
||||
}
|
||||
|
||||
type fakeServicesClient struct {
|
||||
dockerclient.APIClient
|
||||
dockerVersion string
|
||||
networks []dockertypes.NetworkResource
|
||||
services []swarm.Service
|
||||
tasks []swarm.Task
|
||||
err error
|
||||
}
|
||||
|
||||
func (c *fakeServicesClient) ServiceList(ctx context.Context, options dockertypes.ServiceListOptions) ([]swarm.Service, error) {
|
||||
return c.services, c.err
|
||||
}
|
||||
|
||||
func (c *fakeServicesClient) ServerVersion(ctx context.Context) (dockertypes.Version, error) {
|
||||
return dockertypes.Version{APIVersion: c.dockerVersion}, c.err
|
||||
}
|
||||
|
||||
func (c *fakeServicesClient) NetworkList(ctx context.Context, options dockertypes.NetworkListOptions) ([]dockertypes.NetworkResource, error) {
|
||||
return c.networks, c.err
|
||||
}
|
||||
|
||||
func (c *fakeServicesClient) TaskList(ctx context.Context, options dockertypes.TaskListOptions) ([]swarm.Task, error) {
|
||||
return c.tasks, c.err
|
||||
}
|
|
@ -9,26 +9,10 @@ import (
|
|||
"github.com/davecgh/go-spew/spew"
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
dockerclient "github.com/docker/docker/client"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type fakeTasksClient struct {
|
||||
dockerclient.APIClient
|
||||
tasks []swarm.Task
|
||||
container dockertypes.ContainerJSON
|
||||
err error
|
||||
}
|
||||
|
||||
func (c *fakeTasksClient) TaskList(ctx context.Context, options dockertypes.TaskListOptions) ([]swarm.Task, error) {
|
||||
return c.tasks, c.err
|
||||
}
|
||||
|
||||
func (c *fakeTasksClient) ContainerInspect(ctx context.Context, container string) (dockertypes.ContainerJSON, error) {
|
||||
return c.container, c.err
|
||||
}
|
||||
|
||||
func TestListTasks(t *testing.T) {
|
||||
testCases := []struct {
|
||||
service swarm.Service
|
||||
|
@ -83,7 +67,7 @@ func TestListTasks(t *testing.T) {
|
|||
t.Run(strconv.Itoa(caseID), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p := Provider{}
|
||||
p := SwarmProvider{}
|
||||
dockerData, err := p.parseService(context.Background(), test.service, test.networks)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -103,32 +87,7 @@ func TestListTasks(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
type fakeServicesClient struct {
|
||||
dockerclient.APIClient
|
||||
dockerVersion string
|
||||
networks []dockertypes.NetworkResource
|
||||
services []swarm.Service
|
||||
tasks []swarm.Task
|
||||
err error
|
||||
}
|
||||
|
||||
func (c *fakeServicesClient) ServiceList(ctx context.Context, options dockertypes.ServiceListOptions) ([]swarm.Service, error) {
|
||||
return c.services, c.err
|
||||
}
|
||||
|
||||
func (c *fakeServicesClient) ServerVersion(ctx context.Context) (dockertypes.Version, error) {
|
||||
return dockertypes.Version{APIVersion: c.dockerVersion}, c.err
|
||||
}
|
||||
|
||||
func (c *fakeServicesClient) NetworkList(ctx context.Context, options dockertypes.NetworkListOptions) ([]dockertypes.NetworkResource, error) {
|
||||
return c.networks, c.err
|
||||
}
|
||||
|
||||
func (c *fakeServicesClient) TaskList(ctx context.Context, options dockertypes.TaskListOptions) ([]swarm.Task, error) {
|
||||
return c.tasks, c.err
|
||||
}
|
||||
|
||||
func TestListServices(t *testing.T) {
|
||||
func TestSwarmProvider_listServices(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
services []swarm.Service
|
||||
|
@ -277,7 +236,7 @@ func TestListServices(t *testing.T) {
|
|||
|
||||
dockerClient := &fakeServicesClient{services: test.services, tasks: test.tasks, dockerVersion: test.dockerVersion, networks: test.networks}
|
||||
|
||||
p := Provider{}
|
||||
p := SwarmProvider{}
|
||||
|
||||
serviceDockerData, err := p.listServices(context.Background(), dockerClient)
|
||||
assert.NoError(t, err)
|
||||
|
@ -293,7 +252,7 @@ func TestListServices(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSwarmTaskParsing(t *testing.T) {
|
||||
func TestSwarmProvider_parseService_task(t *testing.T) {
|
||||
testCases := []struct {
|
||||
service swarm.Service
|
||||
tasks []swarm.Task
|
||||
|
@ -396,7 +355,7 @@ func TestSwarmTaskParsing(t *testing.T) {
|
|||
t.Run(strconv.Itoa(caseID), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p := Provider{}
|
||||
p := SwarmProvider{}
|
||||
|
||||
dData, err := p.parseService(context.Background(), test.service, test.networks)
|
||||
require.NoError(t, err)
|
211
pkg/provider/docker/shared.go
Normal file
211
pkg/provider/docker/shared.go
Normal file
|
@ -0,0 +1,211 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/cli/connhelper"
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/docker/go-connections/sockets"
|
||||
"github.com/rs/zerolog/log"
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v3/pkg/provider"
|
||||
"github.com/traefik/traefik/v3/pkg/types"
|
||||
"github.com/traefik/traefik/v3/pkg/version"
|
||||
)
|
||||
|
||||
// DefaultTemplateRule The default template for the default rule.
|
||||
const DefaultTemplateRule = "Host(`{{ normalize .Name }}`)"
|
||||
|
||||
type Shared struct {
|
||||
ExposedByDefault bool `description:"Expose containers by default." json:"exposedByDefault,omitempty" toml:"exposedByDefault,omitempty" yaml:"exposedByDefault,omitempty" export:"true"`
|
||||
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"`
|
||||
AllowEmptyServices bool `description:"Disregards the Docker containers health checks with respect to the creation or removal of the corresponding services." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"`
|
||||
Network string `description:"Default Docker network used." json:"network,omitempty" toml:"network,omitempty" yaml:"network,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"`
|
||||
|
||||
Watch bool `description:"Watch Docker events." json:"watch,omitempty" toml:"watch,omitempty" yaml:"watch,omitempty" export:"true"`
|
||||
DefaultRule string `description:"Default rule." json:"defaultRule,omitempty" toml:"defaultRule,omitempty" yaml:"defaultRule,omitempty"`
|
||||
|
||||
defaultRuleTpl *template.Template
|
||||
}
|
||||
|
||||
func inspectContainers(ctx context.Context, dockerClient client.ContainerAPIClient, containerID string) dockerData {
|
||||
containerInspected, err := dockerClient.ContainerInspect(ctx, containerID)
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Warn().Err(err).Msgf("Failed to inspect container %s", containerID)
|
||||
return dockerData{}
|
||||
}
|
||||
|
||||
// This condition is here to avoid to have empty IP https://github.com/traefik/traefik/issues/2459
|
||||
// We register only container which are running
|
||||
if containerInspected.ContainerJSONBase != nil && containerInspected.ContainerJSONBase.State != nil && containerInspected.ContainerJSONBase.State.Running {
|
||||
return parseContainer(containerInspected)
|
||||
}
|
||||
|
||||
return dockerData{}
|
||||
}
|
||||
|
||||
func parseContainer(container dockertypes.ContainerJSON) dockerData {
|
||||
dData := dockerData{
|
||||
NetworkSettings: networkSettings{},
|
||||
}
|
||||
|
||||
if container.ContainerJSONBase != nil {
|
||||
dData.ID = container.ContainerJSONBase.ID
|
||||
dData.Name = container.ContainerJSONBase.Name
|
||||
dData.ServiceName = dData.Name // Default ServiceName to be the container's Name.
|
||||
dData.Node = container.ContainerJSONBase.Node
|
||||
|
||||
if container.ContainerJSONBase.HostConfig != nil {
|
||||
dData.NetworkSettings.NetworkMode = container.ContainerJSONBase.HostConfig.NetworkMode
|
||||
}
|
||||
|
||||
if container.State != nil && container.State.Health != nil {
|
||||
dData.Health = container.State.Health.Status
|
||||
}
|
||||
}
|
||||
|
||||
if container.Config != nil && container.Config.Labels != nil {
|
||||
dData.Labels = container.Config.Labels
|
||||
}
|
||||
|
||||
if container.NetworkSettings != nil {
|
||||
if container.NetworkSettings.Ports != nil {
|
||||
dData.NetworkSettings.Ports = container.NetworkSettings.Ports
|
||||
}
|
||||
if container.NetworkSettings.Networks != nil {
|
||||
dData.NetworkSettings.Networks = make(map[string]*networkData)
|
||||
for name, containerNetwork := range container.NetworkSettings.Networks {
|
||||
addr := containerNetwork.IPAddress
|
||||
if addr == "" {
|
||||
addr = containerNetwork.GlobalIPv6Address
|
||||
}
|
||||
|
||||
dData.NetworkSettings.Networks[name] = &networkData{
|
||||
ID: containerNetwork.NetworkID,
|
||||
Name: name,
|
||||
Addr: addr,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return dData
|
||||
}
|
||||
|
||||
type ClientConfig struct {
|
||||
apiVersion string
|
||||
|
||||
Endpoint string `description:"Docker server endpoint. Can be a TCP or a Unix socket endpoint." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"`
|
||||
TLS *types.ClientTLS `description:"Enable Docker TLS support." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"`
|
||||
HTTPClientTimeout ptypes.Duration `description:"Client timeout for HTTP connections." json:"httpClientTimeout,omitempty" toml:"httpClientTimeout,omitempty" yaml:"httpClientTimeout,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
func createClient(ctx context.Context, cfg ClientConfig) (*client.Client, error) {
|
||||
opts, err := getClientOpts(ctx, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
httpHeaders := map[string]string{
|
||||
"User-Agent": "Traefik " + version.Version,
|
||||
}
|
||||
|
||||
opts = append(opts,
|
||||
client.WithHTTPHeaders(httpHeaders),
|
||||
client.WithVersion(cfg.apiVersion))
|
||||
|
||||
return client.NewClientWithOpts(opts...)
|
||||
}
|
||||
|
||||
func getClientOpts(ctx context.Context, cfg ClientConfig) ([]client.Opt, error) {
|
||||
helper, err := connhelper.GetConnectionHelper(cfg.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),
|
||||
client.WithTimeout(time.Duration(cfg.HTTPClientTimeout)),
|
||||
client.WithHost(helper.Host), // To avoid 400 Bad Request: malformed Host header daemon error
|
||||
client.WithDialContext(helper.Dialer),
|
||||
}, nil
|
||||
}
|
||||
|
||||
opts := []client.Opt{
|
||||
client.WithHost(cfg.Endpoint),
|
||||
client.WithTimeout(time.Duration(cfg.HTTPClientTimeout)),
|
||||
}
|
||||
|
||||
if cfg.TLS != nil {
|
||||
conf, err := cfg.TLS.CreateTLSConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create client TLS configuration: %w", err)
|
||||
}
|
||||
|
||||
hostURL, err := client.ParseHostURL(cfg.Endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: conf,
|
||||
}
|
||||
|
||||
if err := sockets.ConfigureTransport(tr, hostURL.Scheme, hostURL.Host); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opts = append(opts, client.WithHTTPClient(&http.Client{Transport: tr, Timeout: time.Duration(cfg.HTTPClientTimeout)}))
|
||||
}
|
||||
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
return provider.Normalize(serviceName)
|
||||
}
|
|
@ -23,7 +23,7 @@ type specificConfiguration struct {
|
|||
LBSwarm bool
|
||||
}
|
||||
|
||||
func (p *Provider) getConfiguration(container dockerData) (configuration, error) {
|
||||
func (p *Shared) extractLabels(container dockerData) (configuration, error) {
|
||||
conf := configuration{
|
||||
Enable: p.ExposedByDefault,
|
||||
Docker: specificConfiguration{
|
112
pkg/provider/docker/shared_test.go
Normal file
112
pkg/provider/docker/shared_test.go
Normal file
|
@ -0,0 +1,112 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
docker "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_getPort_docker(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
container docker.ContainerJSON
|
||||
serverPort string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "no binding, no server port label",
|
||||
container: containerJSON(name("foo")),
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
desc: "binding, no server port label",
|
||||
container: containerJSON(ports(nat.PortMap{
|
||||
"80/tcp": {},
|
||||
})),
|
||||
expected: "80",
|
||||
},
|
||||
{
|
||||
desc: "binding, multiple ports, no server port label",
|
||||
container: containerJSON(ports(nat.PortMap{
|
||||
"80/tcp": {},
|
||||
"443/tcp": {},
|
||||
})),
|
||||
expected: "80",
|
||||
},
|
||||
{
|
||||
desc: "no binding, server port label",
|
||||
container: containerJSON(),
|
||||
serverPort: "8080",
|
||||
expected: "8080",
|
||||
},
|
||||
{
|
||||
desc: "binding, server port label",
|
||||
container: containerJSON(
|
||||
ports(nat.PortMap{
|
||||
"80/tcp": {},
|
||||
})),
|
||||
serverPort: "8080",
|
||||
expected: "8080",
|
||||
},
|
||||
{
|
||||
desc: "binding, multiple ports, server port label",
|
||||
container: containerJSON(ports(nat.PortMap{
|
||||
"8080/tcp": {},
|
||||
"80/tcp": {},
|
||||
})),
|
||||
serverPort: "8080",
|
||||
expected: "8080",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dData := parseContainer(test.container)
|
||||
|
||||
actual := getPort(dData, test.serverPort)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getPort_swarm(t *testing.T) {
|
||||
testCases := []struct {
|
||||
service swarm.Service
|
||||
serverPort string
|
||||
networks map[string]*docker.NetworkResource
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
service: swarmService(
|
||||
withEndpointSpec(modeDNSSR),
|
||||
),
|
||||
networks: map[string]*docker.NetworkResource{},
|
||||
serverPort: "8080",
|
||||
expected: "8080",
|
||||
},
|
||||
}
|
||||
|
||||
for serviceID, test := range testCases {
|
||||
test := test
|
||||
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p := SwarmProvider{}
|
||||
|
||||
dData, err := p.parseService(context.Background(), test.service, test.networks)
|
||||
require.NoError(t, err)
|
||||
|
||||
actual := getPort(dData, test.serverPort)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,147 +0,0 @@
|
|||
package hub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
mux *http.ServeMux
|
||||
|
||||
client http.Client
|
||||
|
||||
entryPoint string
|
||||
port int
|
||||
tlsCfg *TLS
|
||||
|
||||
// Accessed atomically.
|
||||
lastCfgUnixNano int64
|
||||
|
||||
cfgChan chan<- dynamic.Message
|
||||
}
|
||||
|
||||
func newHandler(entryPoint string, port int, cfgChan chan<- dynamic.Message, tlsCfg *TLS, client http.Client) http.Handler {
|
||||
h := &handler{
|
||||
mux: http.NewServeMux(),
|
||||
entryPoint: entryPoint,
|
||||
port: port,
|
||||
cfgChan: cfgChan,
|
||||
tlsCfg: tlsCfg,
|
||||
client: client,
|
||||
}
|
||||
|
||||
h.mux.HandleFunc("/config", h.handleConfig)
|
||||
h.mux.HandleFunc("/discover-ip", h.handleDiscoverIP)
|
||||
h.mux.HandleFunc("/state", h.handleState)
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
type configRequest struct {
|
||||
UnixNano int64 `json:"unixNano"`
|
||||
Configuration *dynamic.Configuration `json:"configuration"`
|
||||
}
|
||||
|
||||
func (h *handler) handleConfig(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != http.MethodPost {
|
||||
http.Error(rw, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
payload := &configRequest{Configuration: emptyDynamicConfiguration()}
|
||||
if err := json.NewDecoder(req.Body).Decode(payload); err != nil {
|
||||
err = fmt.Errorf("decoding config request: %w", err)
|
||||
log.Error().Err(err).Msg("Handling config")
|
||||
http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
cfg := payload.Configuration
|
||||
patchDynamicConfiguration(cfg, h.entryPoint, h.port, h.tlsCfg)
|
||||
|
||||
// We can safely drop messages here if the other end is not ready to receive them
|
||||
// as the agent will re-apply the same configuration.
|
||||
select {
|
||||
case h.cfgChan <- dynamic.Message{ProviderName: "hub", Configuration: cfg}:
|
||||
atomic.StoreInt64(&h.lastCfgUnixNano, payload.UnixNano)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) handleDiscoverIP(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != http.MethodGet {
|
||||
http.Error(rw, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
xff := req.Header.Get("X-Forwarded-For")
|
||||
port := req.URL.Query().Get("port")
|
||||
nonce := req.URL.Query().Get("nonce")
|
||||
|
||||
if err := h.doDiscoveryReq(req.Context(), xff, port, nonce); err != nil {
|
||||
err = fmt.Errorf("doing discovery request: %w", err)
|
||||
log.Error().Err(err).Msg("Handling IP discovery")
|
||||
http.Error(rw, http.StatusText(http.StatusBadGateway), http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(rw).Encode(xff); err != nil {
|
||||
err = fmt.Errorf("encoding discover ip response: %w", err)
|
||||
log.Error().Err(err).Msg("Handling IP discovery")
|
||||
http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) doDiscoveryReq(ctx context.Context, ip, port, nonce string) error {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://%s", net.JoinHostPort(ip, port)), http.NoBody)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating request: %w", err)
|
||||
}
|
||||
|
||||
q := make(url.Values)
|
||||
q.Set("nonce", nonce)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
req.Host = "agent.traefik"
|
||||
|
||||
resp, err := h.client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("doing request: %w", err)
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type stateResponse struct {
|
||||
LastConfigUnixNano int64 `json:"lastConfigUnixNano"`
|
||||
}
|
||||
|
||||
func (h *handler) handleState(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != http.MethodGet {
|
||||
http.Error(rw, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
resp := stateResponse{
|
||||
LastConfigUnixNano: atomic.LoadInt64(&h.lastCfgUnixNano),
|
||||
}
|
||||
if err := json.NewEncoder(rw).Encode(resp); err != nil {
|
||||
err = fmt.Errorf("encoding last config received response: %w", err)
|
||||
log.Error().Err(err).Msg("Handling state")
|
||||
http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
h.mux.ServeHTTP(rw, req)
|
||||
}
|
|
@ -1,168 +0,0 @@
|
|||
package hub
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/tls/generate"
|
||||
)
|
||||
|
||||
func TestHandleConfig(t *testing.T) {
|
||||
cfgChan := make(chan dynamic.Message, 1)
|
||||
|
||||
client, err := createAgentClient(&TLS{Insecure: true})
|
||||
require.NoError(t, err)
|
||||
h := newHandler("traefik-hub-ep", 42, cfgChan, nil, client)
|
||||
|
||||
cfg := emptyDynamicConfiguration()
|
||||
cfg.HTTP.Routers["foo"] = &dynamic.Router{
|
||||
EntryPoints: []string{"ep"},
|
||||
Service: "bar",
|
||||
Rule: "Host(`foo.com`)",
|
||||
}
|
||||
|
||||
req := configRequest{Configuration: cfg}
|
||||
|
||||
b, err := json.Marshal(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
server := httptest.NewServer(h)
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
resp, err := http.Post(server.URL+"/config", "application/json", bytes.NewReader(b))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
select {
|
||||
case gotCfgRaw := <-cfgChan:
|
||||
patchDynamicConfiguration(cfg, "traefik-hub-ep", 42, nil)
|
||||
assert.Equal(t, cfg, gotCfgRaw.Configuration)
|
||||
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("Configuration not received")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandle_Config_MethodNotAllowed(t *testing.T) {
|
||||
cfgChan := make(chan dynamic.Message, 1)
|
||||
client, err := createAgentClient(&TLS{Insecure: true})
|
||||
require.NoError(t, err)
|
||||
h := newHandler("traefik-hub-ep", 42, cfgChan, nil, client)
|
||||
|
||||
server := httptest.NewServer(h)
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
resp, err := http.Get(server.URL + "/config")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestHandle_DiscoverIP(t *testing.T) {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
port := listener.Addr().(*net.TCPAddr).Port
|
||||
nonce := "XVlBzgbaiCMRAjWw"
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
||||
var handlerCallCount int
|
||||
mux.HandleFunc("/", func(_ http.ResponseWriter, req *http.Request) {
|
||||
handlerCallCount++
|
||||
assert.Equal(t, nonce, req.URL.Query().Get("nonce"))
|
||||
})
|
||||
|
||||
certificate, err := generate.DefaultCertificate()
|
||||
require.NoError(t, err)
|
||||
agentServer := &http.Server{
|
||||
Handler: mux,
|
||||
TLSConfig: &tls.Config{
|
||||
Certificates: []tls.Certificate{*certificate},
|
||||
InsecureSkipVerify: true,
|
||||
MinVersion: tls.VersionTLS13,
|
||||
},
|
||||
}
|
||||
t.Cleanup(func() { _ = agentServer.Close() })
|
||||
|
||||
rdy := make(chan struct{})
|
||||
|
||||
go func(s *http.Server) {
|
||||
close(rdy)
|
||||
if err = s.ServeTLS(listener, "", ""); errors.Is(err, http.ErrServerClosed) {
|
||||
return
|
||||
}
|
||||
}(agentServer)
|
||||
|
||||
<-rdy
|
||||
|
||||
cfgChan := make(chan dynamic.Message, 1)
|
||||
client, err := createAgentClient(&TLS{Insecure: true})
|
||||
require.NoError(t, err)
|
||||
h := newHandler("traefik-hub-ep", 42, cfgChan, nil, client)
|
||||
|
||||
traefikServer := httptest.NewServer(h)
|
||||
t.Cleanup(traefikServer.Close)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, traefikServer.URL+"/discover-ip", http.NoBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
q := make(url.Values)
|
||||
q.Set("port", strconv.Itoa(port))
|
||||
q.Set("nonce", nonce)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
// Simulate a call from behind different proxies.
|
||||
req.Header.Add("X-Forwarded-For", "127.0.0.1")
|
||||
req.Header.Add("X-Forwarded-For", "10.10.0.13")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
err = resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
assert.Equal(t, 1, handlerCallCount)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var ip string
|
||||
err = json.NewDecoder(resp.Body).Decode(&ip)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "127.0.0.1", ip)
|
||||
}
|
||||
|
||||
func TestHandle_DiscoverIP_MethodNotAllowed(t *testing.T) {
|
||||
cfgChan := make(chan dynamic.Message, 1)
|
||||
client, err := createAgentClient(&TLS{Insecure: true})
|
||||
require.NoError(t, err)
|
||||
h := newHandler("traefik-hub-ep", 42, cfgChan, nil, client)
|
||||
|
||||
server := httptest.NewServer(h)
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
resp, err := http.Post(server.URL+"/discover-ip", "", http.NoBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode)
|
||||
}
|
|
@ -1,219 +0,0 @@
|
|||
package hub
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/logs"
|
||||
"github.com/traefik/traefik/v3/pkg/provider"
|
||||
"github.com/traefik/traefik/v3/pkg/safe"
|
||||
ttls "github.com/traefik/traefik/v3/pkg/tls"
|
||||
)
|
||||
|
||||
var _ provider.Provider = (*Provider)(nil)
|
||||
|
||||
// Entrypoints created for Hub.
|
||||
const (
|
||||
APIEntrypoint = "traefikhub-api"
|
||||
TunnelEntrypoint = "traefikhub-tunl"
|
||||
)
|
||||
|
||||
// Provider holds configurations of the provider.
|
||||
type Provider struct {
|
||||
TLS *TLS `description:"TLS configuration for mTLS communication between Traefik and Hub Agent." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"`
|
||||
|
||||
server *http.Server
|
||||
}
|
||||
|
||||
// TLS configures the mTLS connection between Traefik Proxy and the Traefik Hub Agent.
|
||||
type TLS struct {
|
||||
Insecure bool `description:"Enables an insecure TLS connection that uses default credentials, and which has no peer authentication between Traefik Proxy and the Traefik Hub Agent." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"`
|
||||
CA ttls.FileOrContent `description:"The certificate authority authenticates the Traefik Hub Agent certificate." json:"ca,omitempty" toml:"ca,omitempty" yaml:"ca,omitempty" loggable:"false"`
|
||||
Cert ttls.FileOrContent `description:"The TLS certificate for Traefik Proxy as a TLS client." json:"cert,omitempty" toml:"cert,omitempty" yaml:"cert,omitempty" loggable:"false"`
|
||||
Key ttls.FileOrContent `description:"The TLS key for Traefik Proxy as a TLS client." json:"key,omitempty" toml:"key,omitempty" yaml:"key,omitempty" loggable:"false"`
|
||||
}
|
||||
|
||||
// Init the provider.
|
||||
func (p *Provider) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Provide allows the hub provider to provide configurations to traefik using the given configuration channel.
|
||||
func (p *Provider) Provide(configurationChan chan<- dynamic.Message, _ *safe.Pool) error {
|
||||
if p.TLS == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
return fmt.Errorf("listener: %w", err)
|
||||
}
|
||||
|
||||
port := listener.Addr().(*net.TCPAddr).Port
|
||||
|
||||
client, err := createAgentClient(p.TLS)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating Hub Agent HTTP client: %w", err)
|
||||
}
|
||||
|
||||
p.server = &http.Server{Handler: newHandler(APIEntrypoint, port, configurationChan, p.TLS, client)}
|
||||
|
||||
// TODO: this is going to be leaky (because no context to make it terminate)
|
||||
// if/when Provide lifecycle differs with Traefik lifecycle.
|
||||
go func() {
|
||||
if err = p.server.Serve(listener); err != nil {
|
||||
log.Error().Str(logs.ProviderName, "hub").Err(err).Msg("Unexpected error while running server")
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
exposeAPIAndMetrics(configurationChan, APIEntrypoint, port, p.TLS)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func exposeAPIAndMetrics(cfgChan chan<- dynamic.Message, ep string, port int, tlsCfg *TLS) {
|
||||
cfg := emptyDynamicConfiguration()
|
||||
|
||||
patchDynamicConfiguration(cfg, ep, port, tlsCfg)
|
||||
|
||||
cfgChan <- dynamic.Message{ProviderName: "hub", Configuration: cfg}
|
||||
}
|
||||
|
||||
func patchDynamicConfiguration(cfg *dynamic.Configuration, ep string, port int, tlsCfg *TLS) {
|
||||
cfg.HTTP.Routers["traefik-hub-agent-api"] = &dynamic.Router{
|
||||
EntryPoints: []string{ep},
|
||||
Service: "api@internal",
|
||||
Rule: "Host(`proxy.traefik`) && PathPrefix(`/api`)",
|
||||
}
|
||||
cfg.HTTP.Routers["traefik-hub-agent-metrics"] = &dynamic.Router{
|
||||
EntryPoints: []string{ep},
|
||||
Service: "prometheus@internal",
|
||||
Rule: "Host(`proxy.traefik`) && PathPrefix(`/metrics`)",
|
||||
}
|
||||
|
||||
cfg.HTTP.Routers["traefik-hub-agent-service"] = &dynamic.Router{
|
||||
EntryPoints: []string{ep},
|
||||
Service: "traefik-hub-agent-service",
|
||||
Rule: "Host(`proxy.traefik`) && (PathPrefix(`/config`) || PathPrefix(`/discover-ip`) || PathPrefix(`/state`))",
|
||||
}
|
||||
|
||||
cfg.HTTP.Services["traefik-hub-agent-service"] = &dynamic.Service{
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: fmt.Sprintf("http://127.0.0.1:%d", port),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if tlsCfg == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if tlsCfg.Insecure {
|
||||
cfg.TLS.Options["traefik-hub"] = ttls.Options{
|
||||
MinVersion: "VersionTLS13",
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
cfg.TLS.Options["traefik-hub"] = ttls.Options{
|
||||
ClientAuth: ttls.ClientAuth{
|
||||
CAFiles: []ttls.FileOrContent{tlsCfg.CA},
|
||||
ClientAuthType: "RequireAndVerifyClientCert",
|
||||
},
|
||||
SniStrict: true,
|
||||
MinVersion: "VersionTLS13",
|
||||
}
|
||||
|
||||
cfg.TLS.Certificates = append(cfg.TLS.Certificates, &ttls.CertAndStores{
|
||||
Certificate: ttls.Certificate{
|
||||
CertFile: tlsCfg.Cert,
|
||||
KeyFile: tlsCfg.Key,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func emptyDynamicConfiguration() *dynamic.Configuration {
|
||||
return &dynamic.Configuration{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: make(map[string]*dynamic.Router),
|
||||
Middlewares: make(map[string]*dynamic.Middleware),
|
||||
Services: make(map[string]*dynamic.Service),
|
||||
ServersTransports: make(map[string]*dynamic.ServersTransport),
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: make(map[string]*dynamic.TCPRouter),
|
||||
Services: make(map[string]*dynamic.TCPService),
|
||||
ServersTransports: make(map[string]*dynamic.TCPServersTransport),
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{
|
||||
Stores: make(map[string]ttls.Store),
|
||||
Options: make(map[string]ttls.Options),
|
||||
},
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: make(map[string]*dynamic.UDPRouter),
|
||||
Services: make(map[string]*dynamic.UDPService),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createAgentClient(tlsCfg *TLS) (http.Client, error) {
|
||||
var client http.Client
|
||||
if tlsCfg.Insecure {
|
||||
client.Transport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
MinVersion: tls.VersionTLS13,
|
||||
},
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
caContent, err := tlsCfg.CA.Read()
|
||||
if err != nil {
|
||||
return client, fmt.Errorf("reading CA: %w", err)
|
||||
}
|
||||
|
||||
roots := x509.NewCertPool()
|
||||
if ok := roots.AppendCertsFromPEM(caContent); !ok {
|
||||
return client, errors.New("appending CA error")
|
||||
}
|
||||
|
||||
certContent, err := tlsCfg.Cert.Read()
|
||||
if err != nil {
|
||||
return client, fmt.Errorf("reading Cert: %w", err)
|
||||
}
|
||||
keyContent, err := tlsCfg.Key.Read()
|
||||
if err != nil {
|
||||
return client, fmt.Errorf("reading Key: %w", err)
|
||||
}
|
||||
|
||||
certificate, err := tls.X509KeyPair(certContent, keyContent)
|
||||
if err != nil {
|
||||
return client, fmt.Errorf("creating key pair: %w", err)
|
||||
}
|
||||
|
||||
// mTLS
|
||||
client.Transport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
RootCAs: roots,
|
||||
Certificates: []tls.Certificate{certificate},
|
||||
ServerName: "agent.traefik",
|
||||
ClientAuth: tls.RequireAndVerifyClientCert,
|
||||
MinVersion: tls.VersionTLS13,
|
||||
},
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/traefik/traefik/v3/pkg/logs"
|
||||
"github.com/traefik/traefik/v3/pkg/provider"
|
||||
traefikv1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s"
|
||||
"github.com/traefik/traefik/v3/pkg/safe"
|
||||
"github.com/traefik/traefik/v3/pkg/tls"
|
||||
"github.com/traefik/traefik/v3/pkg/types"
|
||||
|
@ -57,7 +58,25 @@ type Provider struct {
|
|||
IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"`
|
||||
ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"`
|
||||
AllowEmptyServices bool `description:"Allow the creation of services without endpoints." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"`
|
||||
lastConfiguration safe.Safe
|
||||
|
||||
lastConfiguration safe.Safe
|
||||
|
||||
routerTransform k8s.RouterTransform
|
||||
}
|
||||
|
||||
func (p *Provider) SetRouterTransform(routerTransform k8s.RouterTransform) {
|
||||
p.routerTransform = routerTransform
|
||||
}
|
||||
|
||||
func (p *Provider) applyRouterTransform(ctx context.Context, rt *dynamic.Router, ingress *traefikv1alpha1.IngressRoute) {
|
||||
if p.routerTransform == nil {
|
||||
return
|
||||
}
|
||||
|
||||
err := p.routerTransform.Apply(ctx, rt, ingress.Annotations)
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Error().Err(err).Msg("Apply router transform")
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) {
|
||||
|
|
|
@ -148,6 +148,8 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
|
|||
}
|
||||
}
|
||||
|
||||
p.applyRouterTransform(ctx, r, ingressRoute)
|
||||
|
||||
conf.Routers[normalized] = r
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/traefik/traefik/v3/pkg/logs"
|
||||
"github.com/traefik/traefik/v3/pkg/provider"
|
||||
traefikv1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s"
|
||||
"github.com/traefik/traefik/v3/pkg/safe"
|
||||
"github.com/traefik/traefik/v3/pkg/tls"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
@ -55,6 +56,23 @@ type Provider struct {
|
|||
EntryPoints map[string]Entrypoint `json:"-" toml:"-" yaml:"-" label:"-" file:"-"`
|
||||
|
||||
lastConfiguration safe.Safe
|
||||
|
||||
routerTransform k8s.RouterTransform
|
||||
}
|
||||
|
||||
func (p *Provider) SetRouterTransform(routerTransform k8s.RouterTransform) {
|
||||
p.routerTransform = routerTransform
|
||||
}
|
||||
|
||||
func (p *Provider) applyRouterTransform(ctx context.Context, rt *dynamic.Router, route *gatev1alpha2.HTTPRoute) {
|
||||
if p.routerTransform == nil {
|
||||
return
|
||||
}
|
||||
|
||||
err := p.routerTransform.Apply(ctx, rt, route.Annotations)
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Error().Err(err).Msg("Apply router transform")
|
||||
}
|
||||
}
|
||||
|
||||
// Entrypoint defines the available entry points.
|
||||
|
@ -498,7 +516,7 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway *
|
|||
for _, routeKind := range routeKinds {
|
||||
switch routeKind.Kind {
|
||||
case kindHTTPRoute:
|
||||
listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, gatewayHTTPRouteToHTTPConf(ctx, ep, listener, gateway, client, conf)...)
|
||||
listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, p.gatewayHTTPRouteToHTTPConf(ctx, ep, listener, gateway, client, conf)...)
|
||||
case kindTCPRoute:
|
||||
listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, gatewayTCPRouteToTCPConf(ctx, ep, listener, gateway, client, conf)...)
|
||||
case kindTLSRoute:
|
||||
|
@ -657,7 +675,7 @@ func getAllowedRouteKinds(listener gatev1alpha2.Listener, supportedKinds []gatev
|
|||
return routeKinds, conditions
|
||||
}
|
||||
|
||||
func gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, listener gatev1alpha2.Listener, gateway *gatev1alpha2.Gateway, client Client, conf *dynamic.Configuration) []metav1.Condition {
|
||||
func (p *Provider) gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, listener gatev1alpha2.Listener, gateway *gatev1alpha2.Gateway, client Client, conf *dynamic.Configuration) []metav1.Condition {
|
||||
if listener.AllowedRoutes == nil {
|
||||
// Should not happen due to validation.
|
||||
return nil
|
||||
|
@ -810,8 +828,11 @@ func gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, listener gatev1a
|
|||
router.Service = serviceName
|
||||
}
|
||||
|
||||
rt := &router
|
||||
p.applyRouterTransform(ctx, rt, route)
|
||||
|
||||
routerKey = provider.Normalize(routerKey)
|
||||
conf.HTTP.Routers[routerKey] = &router
|
||||
conf.HTTP.Routers[routerKey] = rt
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/traefik/traefik/v3/pkg/job"
|
||||
"github.com/traefik/traefik/v3/pkg/logs"
|
||||
"github.com/traefik/traefik/v3/pkg/provider"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s"
|
||||
"github.com/traefik/traefik/v3/pkg/safe"
|
||||
"github.com/traefik/traefik/v3/pkg/tls"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
@ -49,7 +50,25 @@ type Provider struct {
|
|||
AllowEmptyServices bool `description:"Allow creation of services without endpoints." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"`
|
||||
AllowExternalNameServices bool `description:"Allow ExternalName services." json:"allowExternalNameServices,omitempty" toml:"allowExternalNameServices,omitempty" yaml:"allowExternalNameServices,omitempty" export:"true"`
|
||||
DisableIngressClassLookup bool `description:"Disables the lookup of IngressClasses." json:"disableIngressClassLookup,omitempty" toml:"disableIngressClassLookup,omitempty" yaml:"disableIngressClassLookup,omitempty" export:"true"`
|
||||
lastConfiguration safe.Safe
|
||||
|
||||
lastConfiguration safe.Safe
|
||||
|
||||
routerTransform k8s.RouterTransform
|
||||
}
|
||||
|
||||
func (p *Provider) SetRouterTransform(routerTransform k8s.RouterTransform) {
|
||||
p.routerTransform = routerTransform
|
||||
}
|
||||
|
||||
func (p *Provider) applyRouterTransform(ctx context.Context, rt *dynamic.Router, ingress *netv1.Ingress) {
|
||||
if p.routerTransform == nil {
|
||||
return
|
||||
}
|
||||
|
||||
err := p.routerTransform.Apply(ctx, rt, ingress.Annotations)
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Error().Err(err).Msg("Apply router transform")
|
||||
}
|
||||
}
|
||||
|
||||
// EndpointIngress holds the endpoint information for the Kubernetes provider.
|
||||
|
@ -268,6 +287,8 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
|
|||
rt.TLS = rtConfig.Router.TLS
|
||||
}
|
||||
|
||||
p.applyRouterTransform(ctx, rt, ingress)
|
||||
|
||||
conf.HTTP.Routers["default-router"] = rt
|
||||
conf.HTTP.Services["default-backend"] = service
|
||||
}
|
||||
|
@ -311,8 +332,13 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
|
|||
serviceName := provider.Normalize(ingress.Namespace + "-" + pa.Backend.Service.Name + "-" + portString)
|
||||
conf.HTTP.Services[serviceName] = service
|
||||
|
||||
rt := loadRouter(rule, pa, rtConfig, serviceName)
|
||||
|
||||
p.applyRouterTransform(ctx, rt, ingress)
|
||||
|
||||
routerKey := strings.TrimPrefix(provider.Normalize(ingress.Namespace+"-"+ingress.Name+"-"+rule.Host+pa.Path), "-")
|
||||
routers[routerKey] = append(routers[routerKey], loadRouter(rule, pa, rtConfig, serviceName))
|
||||
|
||||
routers[routerKey] = append(routers[routerKey], rt)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
11
pkg/provider/kubernetes/k8s/router_transform.go
Normal file
11
pkg/provider/kubernetes/k8s/router_transform.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
)
|
||||
|
||||
type RouterTransform interface {
|
||||
Apply(ctx context.Context, rt *dynamic.Router, annotations map[string]string) error
|
||||
}
|
|
@ -39,7 +39,7 @@ import (
|
|||
"github.com/traefik/traefik/v3/pkg/types"
|
||||
)
|
||||
|
||||
var updateExpected = flag.Bool("update_expected", true, "Update expected files in fixtures")
|
||||
var updateExpected = flag.Bool("update_expected", false, "Update expected files in fixtures")
|
||||
|
||||
var fullDynConf *dynamic.Configuration
|
||||
|
||||
|
@ -591,22 +591,46 @@ func TestDo_staticConfiguration(t *testing.T) {
|
|||
}
|
||||
|
||||
config.Providers.Docker = &docker.Provider{
|
||||
Constraints: `Label("foo", "bar")`,
|
||||
Watch: true,
|
||||
Endpoint: "MyEndPoint",
|
||||
DefaultRule: "PathPrefix(`/`)",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "myCa",
|
||||
Cert: "mycert.pem",
|
||||
Key: "mycert.key",
|
||||
InsecureSkipVerify: true,
|
||||
Shared: docker.Shared{
|
||||
ExposedByDefault: true,
|
||||
Constraints: `Label("foo", "bar")`,
|
||||
AllowEmptyServices: true,
|
||||
Network: "MyNetwork",
|
||||
UseBindPortIP: true,
|
||||
Watch: true,
|
||||
DefaultRule: "PathPrefix(`/`)",
|
||||
},
|
||||
ExposedByDefault: true,
|
||||
UseBindPortIP: true,
|
||||
SwarmMode: true,
|
||||
Network: "MyNetwork",
|
||||
SwarmModeRefreshSeconds: 42,
|
||||
HTTPClientTimeout: 42,
|
||||
ClientConfig: docker.ClientConfig{
|
||||
Endpoint: "MyEndPoint", TLS: &types.ClientTLS{
|
||||
CA: "myCa",
|
||||
Cert: "mycert.pem",
|
||||
Key: "mycert.key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
HTTPClientTimeout: 42,
|
||||
},
|
||||
}
|
||||
|
||||
config.Providers.Swarm = &docker.SwarmProvider{
|
||||
Shared: docker.Shared{
|
||||
ExposedByDefault: true,
|
||||
Constraints: `Label("foo", "bar")`,
|
||||
AllowEmptyServices: true,
|
||||
Network: "MyNetwork",
|
||||
UseBindPortIP: true,
|
||||
Watch: true,
|
||||
DefaultRule: "PathPrefix(`/`)",
|
||||
},
|
||||
ClientConfig: docker.ClientConfig{
|
||||
Endpoint: "MyEndPoint", TLS: &types.ClientTLS{
|
||||
CA: "myCa",
|
||||
Cert: "mycert.pem",
|
||||
Key: "mycert.key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
HTTPClientTimeout: 42,
|
||||
},
|
||||
RefreshSeconds: 42,
|
||||
}
|
||||
|
||||
config.Providers.KubernetesIngress = &ingress.Provider{
|
||||
|
|
|
@ -89,23 +89,40 @@
|
|||
"providers": {
|
||||
"providersThrottleDuration": "1m51s",
|
||||
"docker": {
|
||||
"exposedByDefault": true,
|
||||
"constraints": "Label(\"foo\", \"bar\")",
|
||||
"allowEmptyServices": true,
|
||||
"network": "MyNetwork",
|
||||
"useBindPortIP": true,
|
||||
"watch": true,
|
||||
"endpoint": "xxxx",
|
||||
"defaultRule": "xxxx",
|
||||
"endpoint": "xxxx",
|
||||
"tls": {
|
||||
"ca": "xxxx",
|
||||
"cert": "xxxx",
|
||||
"key": "xxxx",
|
||||
"insecureSkipVerify": true
|
||||
},
|
||||
"exposedByDefault": true,
|
||||
"useBindPortIP": true,
|
||||
"swarmMode": true,
|
||||
"network": "MyNetwork",
|
||||
"swarmModeRefreshSeconds": "42ns",
|
||||
"httpClientTimeout": "42ns"
|
||||
},
|
||||
"swarm": {
|
||||
"exposedByDefault": true,
|
||||
"constraints": "Label(\"foo\", \"bar\")",
|
||||
"allowEmptyServices": true,
|
||||
"network": "MyNetwork",
|
||||
"useBindPortIP": true,
|
||||
"watch": true,
|
||||
"defaultRule": "xxxx",
|
||||
"endpoint": "xxxx",
|
||||
"tls": {
|
||||
"ca": "xxxx",
|
||||
"cert": "xxxx",
|
||||
"key": "xxxx",
|
||||
"insecureSkipVerify": true
|
||||
},
|
||||
"httpClientTimeout": "42ns",
|
||||
"refreshSeconds": "42ns"
|
||||
},
|
||||
"file": {
|
||||
"directory": "file Directory",
|
||||
"watch": true,
|
||||
|
|
|
@ -4,11 +4,11 @@ RepositoryName = "traefik"
|
|||
OutputType = "file"
|
||||
FileName = "traefik_changelog.md"
|
||||
|
||||
# example new bugfix v2.9.9
|
||||
CurrentRef = "v2.9"
|
||||
PreviousRef = "v2.9.8"
|
||||
BaseBranch = "v2.9"
|
||||
FutureCurrentRefName = "v2.9.9"
|
||||
# example new bugfix v2.10.1
|
||||
CurrentRef = "v2.10"
|
||||
PreviousRef = "v2.10.0"
|
||||
BaseBranch = "v2.10"
|
||||
FutureCurrentRefName = "v2.10.1"
|
||||
|
||||
ThresholdPreviousRef = 10
|
||||
ThresholdCurrentRef = 10
|
||||
|
|
6
webui/src/statics/providers/swarm.svg
Normal file
6
webui/src/statics/providers/swarm.svg
Normal file
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="52" viewBox="0 0 52 52">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<circle cx="26" cy="26" r="26" fill="#007BFF"/>
|
||||
<path fill="#FFF" fill-rule="nonzero" d="M22.749 13v3.94h-8.125v3.939h-4.062v3.94H7.414a.785.785 0 0 0-.787.64 8.97 8.97 0 0 0-.127 1.526c0 .55.054 1.15.152 1.748.93-.215 2.634-.539 2.336-1.6 1.603 1.8 5.449 1.255 6.423.369 1.089 1.53 7.43.945 7.87-.246 1.365 1.55 5.596 1.55 6.957 0 .441 1.19 6.757 1.775 7.845.246.346.314 1.066.575 1.904.714.283-.52.543-1.043.787-1.6 5.16-.062 6.274-3.66 6.322-3.817.089-.301-.032-.612-.28-.812-.085-.071-1.99-1.57-5.077-1.059-.866-2.736-3.097-3.982-3.199-4.038a.826.826 0 0 0-.914.074c-.082.065-2.002 1.643-1.701 4.875.076.813.295 1.53.635 2.167-.667.36-1.812.812-3.656.812h-.406v-3.94h-4.063V13H22.75zm17.238 15.216c-.098.185-.225.363-.33.542h7.337c-.882-.216-2.777-.505-2.462-1.625-1.038 1.163-3.009 1.335-4.545 1.083zm-.33.542H6.652a12.07 12.07 0 0 0 1.219 3.545c4.411 1.006 9.041-.526 9.089-.542a.8.8 0 0 1 1.016.493.78.78 0 0 1-.483 1.01c-.155.052-2.945.96-6.372.96-.68 0-1.394-.037-2.108-.124 2.133 2.743 5.748 4.9 11.298 4.9 8.778 0 15.528-3.616 19.346-10.242zm-33.005 0c-.003-.013.004-.013 0-.025-.054.012-.104.012-.152.025h.152zm17.721-14.182h2.438v2.363h-2.438v-2.363zm-8.124 3.94h2.437v2.363H16.25v-2.364zm4.062 0h2.438v2.363H20.31v-2.364zm4.062 0h2.438v2.363h-2.438v-2.364zm-12.186 3.939h2.437v2.363h-2.437v-2.363zm4.062 0h2.437v2.363H16.25v-2.363zm4.062 0h2.438v2.363H20.31v-2.363zm4.062 0h2.438v2.363h-2.438v-2.363zm4.062 0h2.438v2.363h-2.438v-2.363zM19.5 30.333c.105 0 .212.016.304.05-.098.055-.177.15-.177.27 0 .179.146.345.33.345a.367.367 0 0 0 .304-.172.7.7 0 0 1 .051.295.802.802 0 0 1-.812.788.802.802 0 0 1-.813-.788c0-.434.365-.788.813-.788z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
Loading…
Reference in a new issue