首页
/ Traefik AWS ECS 提供商配置指南:服务发现、约束表达式与凭证解析

Traefik AWS ECS 提供商配置指南:服务发现、约束表达式与凭证解析

2026-09-07 11:12:50作者:田桥桑Industrious

导读

本文是 Traefik(The Cloud Native Application Proxy)官方参考文档中 AWS ECS 提供商配置部分的技术详解。它面向在 AWS 上部署 ECS/Fargate/ECS Anywhere、希望由 Traefik 自动发现弹性容器并生成路由与负载均衡配置的开发者。读完本文,你将掌握 ECS 提供商的启用方式、全部静态配置参数、基于标签的 constraints 过滤语法、defaultRule 默认路由模板的写法、AWS 凭证/区域解析顺序,以及 Traefik 运行所需的最小 IAM 策略。

ECS 提供商在 Traefik 中的角色

Traefik 将“路由配置来源”抽象为一个个 Provider(提供商)。AWS ECS 提供商属于 标签型(Label-based)编排器提供商:Traefik 周期性查询 AWS ECS API,读取每个服务/任务定义上挂载的 Docker 标签,依据这些标签自动生成动态路由配置,并在检测到变化时热更新路由(参见服务发现范围控制与 Provider 总览)。

从源码看,ECS 提供商定义在 pkg/provider/ecs/ecs.go 中,通过 ProviderName = "ecs"pkg/provider/ecs/ecs.go#L33)注册;它实现了 provider.Provider 接口,并在其 Provide 方法中启动一个常驻 goroutine:先加载一次配置,然后按 RefreshSeconds 定时轮询刷新(pkg/provider/ecs/ecs.go#L110-L154),遇到瞬时错误时使用指数退避自动重试。

启用 ECS 提供商

以下三种方式等价,任选其一即可在静态配置中启用 ECS 提供商。

providers:
  ecs: {}
[providers.ecs]
--providers.ecs=true

配置选项总表

下表是 ECS 提供商支持的完整静态配置项(字段名、默认值与必填性均以官方文档与源码为准)。其中 providers.providersThrottleDuration 属于所有 Provider 共享的全局节流选项,无法按单个 Provider 单独设置,但节流算法会独立作用于每个 Provider。

Field Description Default Required
providers.providersThrottleDuration 在一次配置重新加载之后,等待的最小时长,在此之前不处理任何新的配置刷新事件。若该时间段内发生多次事件,只采纳最近一次,其余全部丢弃。该选项无法按 Provider 单独设置,但节流算法会独立作用于每个 Provider。 2s No
providers.ecs.autoDiscoverClusters 是否在集群列表范围内搜索服务。设为 true 时对所有集群启用服务发现。 false No
providers.ecs.ecsAnywhere 是否启用 ECS Anywhere 支持。 false No
providers.ecs.clusters 在集群列表中搜索服务。当 autoDiscoverClusterstrue 时该选项被忽略。 ["default"] No
providers.ecs.exposedByDefault 是否默认暴露 ECS 服务。设为 false 时,没有 traefik.enable=true 标签的容器将被忽略,不进入生成的路由配置。详见限制服务发现范围 true No
providers.ecs.constraints 定义一条表达式,Traefik 将其与容器标签匹配,以决定是否为该容器创建任何路由。详见下文 constraints 一节。 "" No
providers.ecs.healthyTasksOnly 是否仅发现健康任务(HEALTHY healthStatus)。 false No
providers.ecs.defaultRule 适用于所有服务的默认 Host 规则。详见下文 defaultRule 一节。 "Host(`{{ normalize .Name }}`)" No
providers.ecs.refreshSeconds 轮询间隔(秒)。 15 No
providers.ecs.region ECS 实例所在区域。详见下文凭证解析一节。 "" No
providers.ecs.accessKeyID ECS 实例的 Access Key ID。详见下文凭证解析一节。 "" No
providers.ecs.secretAccessKey ECS 实例的 Secret Access Key。详见下文凭证解析一节。 "" No

这些字段在源码的 Provider 结构体中逐一声明并带 toml/yaml/json/export 标签(pkg/provider/ecs/ecs.go#L36-L51)。值得注意的是 accessKeyIDsecretAccessKey 两个字段带有 loggable:"false" 标签,意味着它们在任何日志输出中都会被脱敏,不会明文泄漏。默认值由 SetDefaults 统一设置(pkg/provider/ecs/ecs.go#L90-L97):clusters 默认仅含 "default"refreshSeconds 默认 15 秒、exposedByDefault 默认开启、默认规则为 "Host(`{{ normalize .Name }}`)",与上表完全一致。

关于 throttling 与刷新节流

providers.providersThrottleDuration(默认 2s)约束的是“配置刷新事件”的最小处理间隔,而不是 ECS API 轮询间隔。ECS 轮询节奏由 refreshSeconds 决定:每次轮询都会调用 AWS API 拉取任务列表、任务定义与 EC2 实例信息,再通过 channel 向 Traefik 推送一条 dynamic.Message(见 pkg/provider/ecs/ecs.go#L191-L203)。节流选项则是为了避免配置频繁变动时,Traefik 因过度重建路由而不堪重负。

控制服务发现的集群范围

clustersautoDiscoverClusters 共同决定 Traefik 会扫描哪些 ECS 集群:

  • 默认(autoDiscoverClusters=falseclusters=["default"])只扫描名为 default 的集群,可显式配置为多个集群名;
  • autoDiscoverClusters=true 时,Traefik 通过 ecs.ListClusters API 分页枚举账号下全部集群并逐一扫描,此时 clusters 配置被忽略。

从源码实现看,扫描逻辑位于 listInstancespkg/provider/ecs/ecs.go#L207-L388):对每个集群调用 ListTasks(限定 DesiredStatus: RUNNING)并分页拉取,随后用 DescribeTasks 补齐任务详情;在 healthyTasksOnly=true 时,HealthStatus 不等于 HEALTHY 的任务会被直接跳过(pkg/provider/ecs/ecs.go#L254-L257)。

由于 AWS API 单次请求参数个数上限为 100,源码通过 chunkIDs 把所有 ARN 列表切分成不超过 100 个元素的批次再调用 Describe* 接口(pkg/provider/ecs/ecs.go#L539-L543)。

网络模式与任务 IP 解析差异

listInstances 内部区分两种网络模式(源码见 pkg/provider/ecs/ecs.go#L308-L365):

  • awsvpc 网络模式(典型为 Fargate):取任务 Attachment 中容器第一个网络接口的私有 IPv4(为空时退回 IPv6)作为后端地址,端口来自任务定义的 PortMappings
  • bridge/host 网络模式(典型为 EC2 启动类型):后端地址取自运行任务的 EC2 容器实例的私有 IP,端口来自任务的 NetworkBindings(即宿主机映射端口)。

ECS Anywhere

ecsAnywhere=true 启用对外部(非 EC2 托管)实例的支持。此时除了常规的 ec2.DescribeInstances 查找外,Traefik 还会调用 Systems Manager 的 DescribeInstanceInformation,把前缀为 mi- 的外部托管实例纳入发现(pkg/provider/ecs/ecs.go#L390-L452)。这也是后面 IAM 策略中需要 ssm:DescribeInstanceInformation 权限的原因。需要特别说明:在 ECS Anywhere 场景下,该权限是必需的,否则外部实例无法被发现。

健康状态过滤与不可见实例

即使不开启 healthyTasksOnlyfilterInstancepkg/provider/ecs/config.go#L159-L198)也会无条件丢弃以下实例:healthStatusUNHEALTHY 的任务、机器状态非 RUNNING、没有私有 IP、或 traefik.enable 未开启的实例。

exposedByDefault 与 traefik.enable

与 Docker/Swarm/Nomad/Consul Catalog 等标签型 Provider 一致,ECS Provider 支持两阶段“开白名单”式的范围收缩(见限制服务发现范围):

  1. 全局层面:将 providers.ecs.exposedByDefault 设为 false
  2. 应用层面:只给希望暴露的服务挂上 traefik.enable=true 标签。

该标签的解析逻辑位于 pkg/provider/ecs/label.gogetConfiguration 先把 Enable 初始化为 ExposedByDefault 的值,再通过 label.Decode(instance.Labels, &conf, "traefik.ecs.", "traefik.enable") 读取标签完成覆盖——traefik.enable 标签优先级高于 exposedByDefault。与约束(constraints)的机制不同,traefik.* 是 Traefik 配置的保留标签命名空间,其值会直接参与路由的生成。

在 ECS 任务定义中,标签位于容器定义(container definition)的 labels 字段,例如:

{
  "family": "my-service",
  "containerDefinitions": [
    {
      "name": "my-container",
      "image": "my-image:latest",
      "labels": {
        "traefik.enable": "true",
        "traefik.http.routers.my-container.rule": "Host(`example.com`)"
      }
    }
  ]
}

constraints:按标签表达式裁剪服务发现

constraints 是一个比 exposedByDefault 更细粒度的过滤器。它是一条表达式,Traefik 会把它与每个任务的容器标签匹配,决定是否为该容器生成路由。若没有任何标签命中表达式则不创建路由;表达式为空则包含所有被发现的容器。

表达式的语法基于 Label("key", "value")LabelRegex("key", "value") 两个函数,并支持常规布尔逻辑(&&||! 及括号)。官方文档给出的示例如下:

# 仅包含拥有 key 为 a.label.name、value 为 foo 的标签的容器
constraints = "Label(`a.label.name`, `foo`)"
# 排除拥有 key 为 a.label.name、value 为 value 的任意标签的容器
constraints = "!Label(`a.label.name`, `value`)"
# 逻辑与
constraints = "Label(`a.label.name`, `valueA`) && Label(`another.label.name`, `valueB`)"
# 逻辑或
constraints = "Label(`a.label.name`, `valueA`) || Label(`another.label.name`, `valueB`)"
# 逻辑与和逻辑或混用,括号控制优先级
constraints = "Label(`a.label.name`, `valueA`) && (Label(`another.label.name`, `valueB`) || Label(`yet.another.label.name`, `valueC`))"
# 仅包含拥有 key 为 a.label.name 且 value 能匹配正则 a.+ 的容器
constraints = "LabelRegex(`a.label.name`, `a.+`)"

约束关键点:traefik.* 是保留的标签命名空间,专用于 Traefik 配置,不能作为自定义约束的 key 使用。

在静态配置文件中设置约束:

providers:
  ecs:
    constraints: "Label(`a.label.name`,`foo`)"
    # ...
[providers.ecs]
  constraints = "Label(`a.label.name`,`foo`)"
  # ...
--providers.ecs.constraints="Label(`a.label.name`,`foo`)"
# ...

补充说明:本文档只承接 provider 侧静态配置;有关如何通过 exposedByDefaulttraefik.enable 收缩服务发现范围,可进一步参考 Provider 总览中的相关章节

constraints 的底层求值实现

表达式的解析与求值并非 ECS Provider 自研,而是复用 pkg/provider/constraints/constraints_labels.go 中基于 github.com/vulcand/predicate 的实现(pkg/provider/constraints/constraints_labels.go#L16-L46):

  • 注册了 LabelLabelRegex 两个函数与 AND/OR/NOT 运算符;
  • Label(name, value) 实际等价于精确比较 labels[name] == value
  • LabelRegex(name, expr) 使用 Go 的 regexp.MatchString 对标签值做正则匹配;
  • 表达式为空字符串时直接返回 true,即不限制。

在 ECS Provider 中,该函数被 filterInstance 调用(pkg/provider/ecs/config.go#L187-L195):约束不匹配或表达式解析失败的容器都会被剔除并记录调试日志。

defaultRule:默认路由规则模板

defaultRule 定义了当某个容器没有通过标签显式声明路由规则时,Traefik 为它应用的路由规则。

它必须是合法的 Go template,并可使用 sprig 模板函数。模板可通过标识符 Name 访问 ECS 服务名,也可访问定义在该容器上的全部标签。

providers:
  ecs:
    defaultRule: "Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)"
    # ...
[providers.ecs]
  defaultRule = "Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)"
  # ...
--providers.ecs.defaultRule='Host(`{{ .Name }}.{{ index .Labels "customLabel"}}`)'
# ...

默认规则背后的实现细节

  • 默认值为 "Host(`{{ normalize .Name }}`)"。其中 normalize 并非 sprig 内置函数,而是 Traefik 注入的自定义函数:它把字符串中的全部非字母数字字符替换成 -pkg/provider/configuration.go#L125-L131),从而把带空格/冒号/斜杠的 ECS 服务组名转成合法的 Host 片段。
  • 模板由 MakeDefaultRuleTemplate 编译:其函数表 = sprig 全部文本函数 + normalizepkg/provider/configuration.go#L18-L26);ECS Provider 在 Init 阶段就完成模板解析,解析失败会直接报错并中止启动(pkg/provider/ecs/ecs.go#L100-L108)。
  • 实际生成路由时,模板的渲染模型包含 Name(服务名)与 Labels(容器标签字典),见 pkg/provider/ecs/config.go#L79-L87
  • 若通过标签显式给出了路由 rule,则优先使用标签值;只有当 rule 为空时才会套用 defaultRulepkg/provider/configuration.go#L91-L108)。

默认规则与 Traefik 服务本身的“自指”防护

需要注意:当 Traefik 容器自身也被 ECS 服务发现纳入暴露范围时,默认规则机制可能让 Traefik 创建出指向自身的路由,形成无限循环。为防止这种情况,Traefik 会给走默认规则的路由注入一个内部中间件(denyRouterRecursion),拒绝来自同一路由的递归请求。相应地,pkg/provider/configuration.go#L106-L107 中会把套用了默认规则的路由标记为 router.DefaultRule = true,作为下游识别这类路由的依据。

凭证与区域解析(Credentials)

如果未显式配置 regionaccessKeyIDsecretAccessKey,Traefik 会遵循 AWS SDK 的标准默认链完成解析。

区域(region)解析规则:

  • 若未提供 region,EC2 启动类型的任务会从 EC2 元数据端点(EC2 Metadata endpoint)解析区域;
  • 在 FARGATE 环境中,则从 AWS_REGION 环境变量解析。

凭证解析优先级(当未提供 accessKeyIDsecretAccessKey 时,按以下顺序):

  1. 环境变量 AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYAWS_SESSION_TOKEN
  2. 共享凭证文件,位置由 AWS_PROFILEAWS_SHARED_CREDENTIALS_FILE 决定,默认 profile 为 default、默认文件为 ~/.aws/credentials
  3. EC2 实例角色(Instance Role)或 ECS 任务角色(Task Role)。

显式配置示例:

providers:
  ecs:
    region: us-east-1
    accessKeyID: "abc"
    secretAccessKey: "123"
    # ...
[providers.ecs]
  region = "us-east-1"
  accessKeyID = "abc"
  secretAccessKey = "123"
--providers.ecs.region="us-east-1"
--providers.ecs.accessKeyID="abc"
--providers.ecs.secretAccessKey="123"
# ...

源码层面的凭证组装逻辑(pkg/provider/ecs/ecs.go#L156-L189):

  • 未提供 region 时,通过 config.WithEC2IMDSRegion() 从 EC2 元数据服务解析区域,并记录一条 INFO 日志;
  • 同时提供了 accessKeyIDsecretAccessKey 时,显式构造静态凭证 Provider(aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(...)))并作为最高优先级传入 config.LoadDefaultConfig——依据 AWS SDK 的规则,一旦显式提供凭证,SDK 只使用该凭证;两者皆空时则由 SDK 默认链接管;
  • 最终客户端同时构建 ecsec2ssm 三个 AWS 服务客户端,分别用于容器任务、EC2 实例与(Anywhere 场景下的)托管实例发现。

运行所需 IAM 策略(Policy)

Traefik 需要以下权限才能读取 ECS 信息:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "TraefikECSReadAccess",
            "Effect": "Allow",
            "Action": [
                "ecs:ListClusters",
                "ecs:DescribeClusters",
                "ecs:ListTasks",
                "ecs:DescribeTasks",
                "ecs:DescribeContainerInstances",
                "ecs:DescribeTaskDefinition",
                "ec2:DescribeInstances",
                "ssm:DescribeInstanceInformation"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

需要特别指出的是:ssm:DescribeInstanceInformation 动作是 ECS Anywhere 实例发现所必需的。而各 API 的具体用途,可以在源码的扫描主流程中找到对应关系——ListClusters/ListTasks 分页枚举集群与任务(pkg/provider/ecs/ecs.go#L212-L263),DescribeTasks 补齐任务元数据,DescribeContainerInstances + ec2:DescribeInstances 获取 EC2 启动类型实例的地址信息(pkg/provider/ecs/ecs.go#L454-L514),DescribeTaskDefinition 则用于读取容器定义上的 Docker 标签(pkg/provider/ecs/ecs.go#L516-L537)。

用任务定义标签声明路由与端口

ECS Provider 的最终目标是把标签翻译成可路由的动态配置。核心转换在 buildConfiguration 中完成(pkg/provider/ecs/config.go#L23-L93):它对每个实例先解析 traefik.* 标签得到候选的动态配置,随后为每个服务(traefik.http.services.<name>.loadbalancer.server.port 等标签声明的负载均衡器)注入真实的后端 server(地址 = 容器/实例私有 IP,端口取标签指定值),最后按服务名生成并挂接 router。

以下三个标签的示例非常常见,用于指定容器内部要暴露的自定义端口——当没有指定该标签时,Traefik 默认使用容器暴露的第一个端口

{
  "family": "my-service",
  "containerDefinitions": [
    {
      "name": "my-container",
      "image": "my-image:latest",
      "labels": {
        "traefik.http.routers.my-container.rule": "Host(`example.com`)",
        "traefik.http.routers.my-container.service": "my-service",
        "traefik.http.services.my-service.loadbalancer.server.port": "12345"
      }
    }
  ]
}

若应用实际监听端口并非容器暴露的第一个端口却未配置该标签,就会出现经典的 HTTP/502 Gateway Error。端口选择逻辑在 getPortpkg/provider/ecs/config.go#L303-L340):标签指定的 server.port 会先与容器端口映射逐一比对,命中则返回对应的宿主机端口;未命中时回退使用各映射端口按数值排序后的最小值。

在 ECS 场景中,标签被写在任务定义的容器级 labels 字段里,而非单独的“service 定义”。注意标签大小写不敏感;router、service、middleware 名称中不允许出现 @ 字符。若需要完整掌握每个 HTTP/TCP/UDP router、service、middleware 标签及其默认值,可查阅ECS 标签参考文档(例如 traefik.http.routers.<name>.ruletraefik.tcp.routers.<name>.rule=HostSNI(\...`)traefik.udp.services..loadbalancer.server.port` 等)。由于标签里可能会携带证书、密钥等敏感数据,官方建议敏感数据应存放于更安全的存储(如 secrets 文件、云厂商密钥服务),而不要写在标签中。

小结与进一步阅读

要让 Traefik 正确接管 AWS ECS 流量的完整步骤是:

  1. 在静态配置中启用 providers.ecs(YAML/TOML/CLI 三选一);
  2. 依据网络模式与是否使用 ECS Anywhere,选择性地设置 clustersautoDiscoverClustersecsAnywhere
  3. 视需要配置 region/accessKeyID/secretAccessKey(或依赖默认凭证链);
  4. 按需裁剪暴露范围:exposedByDefault + traefik.enable 标签,或更细粒度的 constraints 表达式;
  5. 把承载路由/中间件/服务配置的 traefik.* 标签写入 ECS 任务定义的容器 labels;
  6. 为 Traefik 运行角色附加本文给出的 IAM 策略(ECS Anywhere 场景必须含 ssm:DescribeInstanceInformation)。

如需继续深入,推荐按以下路径在仓库中阅读:

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
docsdocs
暂无描述
Markdown
899
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
925
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
601
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
525
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
395