使用 Terraform 将 Teleport Database Service 部署到 AWS ECS:基于 IAM Join Token 的完整示例解析
【免费下载链接】teleportThe easiest, and most secure way to access and protect all of your infrastructure.项目地址: https://gitcode.com/gh_mirrors/tel/teleport
导读
本文围绕 integrations/terraform-modules/teleport/container-service/aws/examples/db-service 示例展开,讲解如何用 Terraform 把 Teleport 的数据库服务(Database Service)以容器方式部署到 AWS ECS Fargate,并通过IAM Join Token使其安全加入一个已有的 Teleport 集群。读完本文,你将掌握该示例的完整目录结构、每个 Terraform 文件的职责、teleport_config的写法要点、IAM 信任关系的自动推导逻辑,以及模块底层如何把配置渲染进 ECS 任务定义——并可以直接把这一套方案复制到自己的基础设施中。
示例概览:一键拉起一个 ECS 上的数据库代理
该示例的核心目标可以用一句话概括:用纯 Terraform 声明式地创建一个承载 Teleport Database Service 的 ECS 服务,并让这个容器代理通过 AWS IAM 身份认证(join_method = "iam")自动加入你已有的 Teleport 集群,从而为集群提供数据库访问能力,而无需在 ECS 上手工维护任何静态 Token。
从 Terraform 模块依赖看,整个示例只依赖两层:
| 模块 | Source | Version | 作用 |
|---|---|---|---|
teleport_database_service | ../..(即本仓库模块container-service/aws) | 当前仓库版本 | 创建 ECS 集群、服务、任务定义、IAM 角色与安全组 |
vpc | terraform-aws-modules/vpc/aws | 6.6.0 | 创建一个带 3 个可用区、公有/私有子网的示例 VPC |
它对外只暴露一个必填输入:
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
teleport_proxy_addr | Teleport Proxy Service 的地址,host:port形式 | string | n/a | yes |
以及一个输出teleport_database_service,完整导出模块创建的全部资源对象,方便调用方进一步引用(例如输出 ECS 集群 ARN、任务 IAM 角色 ARN 等)。
目录结构:示例由六个文件组成
该示例目录的完整清单如下(位于 integrations/terraform-modules/teleport/container-service/aws/examples/db-service):
db-service/ ├── README.md # 本示例说明(含自动生成的 TF-Docs 表格) ├── data.tf # 可用区数据源 ├── docs_description # TF-Docs 生成用的描述文本 ├── docs_title # TF-Docs 生成用的标题文本 ├── main.tf # VPC 与 Database Service 模块、IAM Join Token ├── outputs.tf # 导出 teleport_database_service ├── providers.tf # aws / teleport 两个 provider 配置 ├── variables.tf # teleport_proxy_addr 变量声明 └── versions.tf # Terraform 与 provider 版本约束其中docs_description、docs_title只是 terraform-docs 生成README.md中表格区的输入素材,真正的业务逻辑集中在main.tf、providers.tf、versions.tf三个文件里。
版本约束(versions.tf)
versions.tf 定义了整套环境的版本基线:
terraform { required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" version = "~> 6.0" } http = { source = "hashicorp/http" version = "~> 3.0" } teleport = { source = "terraform.releases.teleport.dev/gravitational/teleport" version = "~> 18.5" } } }几个值得注意的细节:
teleportprovider 的 source 是terraform.releases.teleport.dev/gravitational/teleport,不是hashicorp/命名空间下的官方 registry,需要在使用前执行terraform login或在 CLI 配置中信任该私有 registry;httpprovider(~> 3.0)不是直接在本示例中被声明使用,而是随teleport_database_service模块的Managed Updates功能传递引入的(见下文“版本管理”一节);- Terraform 版本要求
>= 1.5.7,与模块自身的required_version保持一致。
Provider 配置(providers.tf)
providers.tf 同时配置了两个 provider:
provider "aws" { region = "us-east-1" default_tags { tags = { env = "example" } } } provider "teleport" { addr = var.teleport_proxy_addr profile_name = replace(var.teleport_proxy_addr, "/:[0-9]+.*/", "") }awsprovider 固定使用us-east-1,并通过default_tags给所有 AWS 资源打上env = example标签——这些标签会与模块内apply_aws_tags传入的标签合并;teleportprovider 使用addr指向 Proxy 地址,profile_name则通过正则replace(..., "/:[0-9]+.*/", "")从host:port中剥离端口部分,得到纯主机名作为 tsh 配置文件名。这意味着使用该示例的前提是:本机已经通过tsh login登录过对应集群,Terraform 才能拿到创建teleport_provision_token所需的集群凭据。
核心编排(main.tf):从 VPC 到数据库代理
main.tf 是整个示例的灵魂,按自上而下顺序完成三件事:建 VPC → 起数据库服务模块 → 创建 IAM Join Token。
第一步:创建示例 VPC
locals { namespace = "example" } module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "6.6.0" azs = slice(data.aws_availability_zones.this.names, 0, 3) cidr = "10.0.0.0/16" name = "${local.namespace}-vpc" public_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] private_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"] }配合 data.tf 中的data "aws_availability_zones" "this" {},示例从当前区域真实可用的可用区中取前 3 个(slice(..., 0, 3))来放置子网。VPC 使用10.0.0.0/16,公有子网与私有子网各 3 个,符合 ECS 多 AZ 高可用部署的最小布局。
第二步:调用 teleport_database_service 模块
module "teleport_database_service" { source = "../.." # Enable managed updates managed_updates_enabled = true managed_updates_group = "default" apply_aws_tags = { "example" = "true" } assign_public_ip = true # must be true when using public subnets ecs_cluster_name = "${local.namespace}-cluster" ecs_service_name = "${local.namespace}-svc" ecs_service_subnets = module.vpc.public_subnets ecs_task_desired_count = 1 environment_vars = { EXAMPLE_VAR = "EXAMPLE_VALUE" } vpc_id = module.vpc.vpc_id teleport_config = { version = "v3" teleport = { join_params = { token_name = "${local.namespace}-iam" method = "iam" } proxy_server = var.teleport_proxy_addr log = { severity = "DEBUG" } } auth_service = { enabled = "no" } proxy_service = { enabled = "no" } ssh_service = { enabled = "no" } discovery_service = { enabled = "no" } db_service = { enabled = "yes" resources = [ { labels = { "env" = "example" } } ] } } }这段代码集中体现了本示例的三个设计要点:
teleport_config使用原生 Terraform 语法书写,最终会被模块渲染成teleport start --config-string所需的 YAML(见下文“任务定义里发生了什么”一节)。它等价于一段标准 Teleport 配置:version: v3,通过join_params.method = "iam"和token_name = "example-iam"指定 IAM 加入方式,proxy_server指向你的 Proxy 地址,日志级别设为DEBUG便于排障。- 仅开启
db_service:auth_service、proxy_service、ssh_service、discovery_service全部显式enabled = "no",这与“数据库代理”的定位完全吻合——ECS 上的容器只充当 Database Service,不承担集群控制面职责。db_service.resources[0].labels = { "env" = "example" }声明了该服务托管的数据库标签,Teleport 集群将依据这一标签把注册请求路由到本代理。 assign_public_ip = true与公有子网配套:由于示例 VPC 的公网子网直连 Internet Gateway,容器需要被分配公网 IP 才能反向连接 Teleport Proxy;模块 README 明确要求——assign_public_ip = true时子网必须是公有子网,false时则必须是走 NAT 网关的私有子网。
第三步:创建 IAM Join Token
resource "teleport_provision_token" "iam" { metadata = { name = "${local.namespace}-iam" description = "Allow the Teleport ECS agent to join the cluster using AWS IAM credentials." } spec = { allow = [{ aws_arn = module.teleport_database_service.teleport_provision_token_allow_aws_arn }] join_method = "iam" roles = ["Db"] } version = "v2" }这是整个示例最巧妙的部分:
- 它通过
teleportprovider 在你的 Teleport 集群里创建一个join_method = "iam"、roles = ["Db"]的 provisioning token,作用域精确到一条 ARN; - 该 ARN 不是手写的,而是直接引用模块输出
module.teleport_database_service.teleport_provision_token_allow_aws_arn。
查看模块 outputs.tf 可知其推导公式:
value = format( "arn:%v:sts::%v:assumed-role/%v/*", one(data.aws_partition.this[*].partition), one(data.aws_caller_identity.this[*].account_id), one(aws_iam_role.ecs_task[*].name), )即arn:aws:sts::<account>:assumed-role/<ecs_task_role_name>/*。换句话说:模块自动查询当前 AWS 账号 ID 与分区,加上它创建的 ECS 任务 IAM 角色名,拼出“该任务角色可被信任”的完整 ARN 通配表达式。容器启动后带着 ECS 任务角色的临时 STS 凭据去连接 Proxy,Teleport 侧用这条 token 的allow.aws_arn与之比对,通过即放行——全程无需静态 Token,天然具备自动轮换能力。
模块输入速查:哪些参数值得关注
teleport_database_service模块(即 container-service/aws)暴露了丰富的输入,除示例中已使用的之外,下面这些在实际生产中经常被用到:
| 输入 | 默认值 | 说明 |
|---|---|---|
ecs_task_cpu | 2048 | 任务 CPU 单元数(Fargate 计费单位) |
ecs_task_memory | 4096 | 任务内存(MiB) |
ecs_task_desired_count | 2 | 期望运行的任务副本数,示例中显式设为1 |
ecs_task_cloudwatch_log_group_retention_days | 30 | CloudWatch 日志保留天数 |
ecs_task_cloudwatch_log_group_kms_key_id | null | 日志组加密 KMS Key;为null时使用 CloudWatch 默认加密 |
create | true | 全局开关,置false可只取输出不创建资源 |
create_security_group | true | 是否创建任务安全组,可用security_group_ids追加既有安全组 |
security_group_ids | [] | 额外附加到任务的安全组 |
ecs_task_role_inline_policy | null | 合并进任务 IAM 角色内联策略的 JSON,常用于给数据库代理授予访问目标数据库的权限 |
ecs_task_role_self_assumption_allowed | true | 任务角色是否允许自我 assume(IAM Join 前置条件之一) |
teleport_container_image | public.ecr.aws/gravitational/teleport-ent-distroless | Teleport 容器镜像,注意默认是企业版 distroless 镜像 |
teleport_version | 19.0.0-prealpha.2 | 显式指定 Teleport 版本(一般交给 Managed Updates 决定) |
managed_updates_enabled/managed_updates_group | true/"default" | 是否从 v2 Managed Updates 端点解析推荐版本 |
模块的常用输出包括:ecs_cluster_arn、ecs_service_arn、ecs_task_definition_arn、ecs_execution_role_arn、ecs_task_role_arn、security_group_id、teleport_config以及上文的teleport_provision_token_allow_aws_arn,完整清单见 container-service/aws/README.md。
任务定义里发生了什么:从 Terraform 配置到容器启动
为了理解“模块如何把teleport_config变成运行中的代理”,需要看模块的 aws_ecs_task_definition.tf。其核心容器定义片段如下:
container_definitions = jsonencode([ { command = [ # rewrite SIGTERM (15) to SIGQUIT (3) so ECS stop signal triggers graceful Teleport shutdown "--rewrite", "15:3", "--", "teleport", "start", "--config-string", base64encode(yamlencode(var.teleport_config)), ] entryPoint = ["/usr/bin/dumb-init"] ... image = "${var.teleport_container_image}:${local.teleport_version}" logConfiguration = { ... awslogs ... } name = "teleport" } ])这段实现揭示了三个底层细节:
- 配置传递方式:
teleport_config先被yamlencode序列化成 YAML,再base64encode编码,通过teleport start --config-string注入容器——所以teleport_config里的键名(如db_service、join_params)与 Teleport 原生 YAML 配置一一对应。 - 优雅停机:
--rewrite 15:3通过 dumb-init 把 ECS 停止容器时发送的SIGTERM(15) 改写成SIGQUIT(3),从而触发 Teleport 的优雅关闭流程,避免代理被强杀导致会话中断。 - 版本来源:镜像 tag 由
local.teleport_version决定,其计算逻辑是——若启用了 Managed Updates,则从data.http.managed_updates拉取auto_update.agent_version,否则回退到var.teleport_version,再统一去掉前导v。
同时,任务定义上的precondition还强制约束:开启 Managed Updates 时,teleport_config.teleport.proxy_server必须非空(见 aws_ecs_task_definition.tf),这是模块保证能正确解析更新源的前提,也是示例中必须传teleport_proxy_addr的原因之一。
此外,任务使用network_mode = "awsvpc"与requires_compatibilities = ["FARGATE"],日志走awslogs驱动写入模块创建的 CloudWatch Log Group;任务与执行两个 IAM 角色分别承载“Teleport 自身运行所需权限”和“拉取镜像、写日志所需权限”。
部署步骤与验证
前置条件
- 已配置好 Teleport 集群与
tsh,并能以管理员身份登录(示例中创建 provisioning token 需要相应权限); - 本机已通过
tsh login <proxy>登录,且tsh配置文件与providers.tf中profile_name推导出的主机名一致; - 已配置 AWS 凭据(Access Key 或 SSO),且账号有创建 VPC、ECS、IAM 的权限;
- 已信任
terraform.releases.teleport.dev私有 registry(首次执行terraform init时按提示完成登录)。
操作步骤
# 1. 初始化,拉取模块与 provider terraform init # 2. 确认执行计划,重点检查 teleport_provision_token 与 ECS 资源 terraform plan -var="teleport_proxy_addr=proxy.example.com:443" # 3. 应用 terraform apply -var="teleport_proxy_addr=proxy.example.com:443"应用成功后,可以按以下顺序验证:
terraform output teleport_database_service查看模块导出的完整资源(ECS 集群/服务/任务 ARN、角色 ARN、安全组 ID、CloudWatch 日志组);- 在 Teleport 侧运行
tsh status确认集群连接正常,再用tsh db ls(或 Web UI 的 Databases 页面)查看是否出现env=example标签的数据库服务实例; - 登录 AWS 控制台查看 ECS 服务是否处于
RUNNING、任务是否通过健康检查,并到 CloudWatch Log Group 中检查teleport start的启动日志(示例配置了DEBUG级别,会输出 IAM Join 的完整握手过程)。
变更与清理
- 调整
ecs_task_desired_count即可水平扩缩容代理副本; - 需要数据库代理访问具体数据库时,通过
ecs_task_role_inline_policy给任务角色授予对应数据库的访问策略,并在db_service.resources中补充数据库标签或静态资源清单; - 不再需要时执行
terraform destroy即可回收全部资源(注意 provisioning token 是创建在 Teleport 集群内的资源,也会一并删除)。
小结
db-service示例是一个“麻雀虽小、五脏俱全”的 ECS 化 Teleport 代理模板:它用最少的外部依赖(一个 VPC 模块 + 一个自研模块),演示了从网络环境搭建、teleport_config编写、IAM Join Token 创建到版本托管的全流程。其核心可复用资产有三点:
- 纯声明式的
teleport_config写法,与 Teleport 原生 YAML 语义一致,可平移到任意容器平台; teleport_provision_token_allow_aws_arn自动推导,让“ECS 任务角色 ↔ Teleport IAM Join Token”的信任关系零手写、随模块自动闭环;- Managed Updates 机制,把 Teleport 版本选择权交给官方更新端点,配合
proxy_server校验前置条件,降低长期维护成本。
如果你正在把 Teleport 的数据库访问能力容器化落地到 AWS,可以直接以本示例为基线:先原样部署跑通链路,再按上文“模块输入速查”中的参数逐项替换成生产配置即可。
【免费下载链接】teleportThe easiest, and most secure way to access and protect all of your infrastructure.项目地址: https://gitcode.com/gh_mirrors/tel/teleport
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考