当 ECS 中有多个服务仅命令不同时,如何避免 terraform 重复?
How to avoid duplication in terraform when having multiple services in ECS which differ only in the command?
我们通过定义 template_file 在 Terraform 中创建 ECS 服务,该 template_file 使用所有需要的变量填充任务定义 JSON 模板。然后使用呈现的 template_file 创建 aws_ecs_task_definition
。通过此任务定义,创建了 aws_ecs_service
:
data "template_file" "web" {
template = "${file("${path.module}/tasks/web.json")}"
vars {
...
}
}
resource "aws_ecs_task_definition" "web" {
container_definitions = "${data.template_file.web.rendered}"
requires_compatibilities = ["FARGATE"]
...
}
data "aws_ecs_task_definition" "web" {
task_definition = "${aws_ecs_task_definition.web.family}"
}
resource "aws_ecs_service" "web" {
name = "web"
task_definition = "${aws_ecs_task_definition.web.family}:${max("${aws_ecs_task_definition.web.revision}", "${data.aws_ecs_task_definition.web.revision}")}"
...
}
还有其他服务的任务定义与第一个服务几乎相同,只是与另一个命令有细微差别(例如启动 sidekiq 而不是 Web 应用程序)。
除了复制所有内容(JSON 模板,template_file
以及所有定义的变量,aws_ecs_task_definition
和 aws_ecs_service
),还有其他方法吗?
Modules 是在 Terraform 中解决这个问题的主要方法。
如果将现有代码移动到单个文件夹中,则可以定义允许自定义该模块的变量,例如要传递给 ECS 服务的命令。
所以在你的情况下你可能有这样的事情:
modules/foo-service/main.tf
data "template_file" "web" {
template = "${file("${path.module}/tasks/web.json")}"
vars {
# ...
command = "${var.command}"
}
}
resource "aws_ecs_task_definition" "web" {
container_definitions = "${data.template_file.web.rendered}"
requires_compatibilities = ["FARGATE"]
# ...
}
data "aws_ecs_task_definition" "web" {
task_definition = "${aws_ecs_task_definition.web.family}"
}
resource "aws_ecs_service" "web" {
name = "web"
task_definition = "${aws_ecs_task_definition.web.family}:${max("${aws_ecs_task_definition.web.revision}", "${data.aws_ecs_task_definition.web.revision}")}"
# ...
}
modules/foo-service/variables.tf
variable "command" {}
staging/main.tf
module "foo_service_web" {
source = "../modules/foo-service"
command = "bundle exec server"
}
module "foo_service_sidekiq" {
source = "../modules/foo-service"
command = "bundle exec sidekiq"
}
扩展已接受的答案以显示如何同时删除由 template_file
vars 块中定义的变量引起的重复(这些变量不会更改,因此必须在模块调用之间重复) .仅内联这些变量或使用默认值也不是解决方案,因为它们仍然会在项目之间发生变化,只是不在同一项目的服务内。我们可以使用局部变量来设置默认值并使用合并函数覆盖默认值:
main.tf
locals {
task_variables = {
image = "..."
# lots of other variables
command = "[\"nginx\", \"-g\", \"daemon off; error_log /dev/stdout info;\"]"
}
}
# first invocation of the module, overriding the command
module "sidekiq" {
source = "ecs_service"
...
task_variables = "${merge(
local.task_variables,
map(
"command", "[\"bash\", \"-c\", \"exec bundle exec sidekiq\"]",
)
)}"
}
# second invocation of the module, no overrides
module "web" {
source = "ecs_service"
task_variables = "${local.task_variables}"
}
模块ecs_service
variable "task_variables" {
type = "map"
}
data "template_file" "web_task" {
template = "${file("${path.module}/tasks/task_definition.json")}"
vars = "${var.task_variables}"
}
resource "aws_ecs_task_definition" "web" {
container_definitions = "${data.template_file.web_task.rendered}"
...
}
data "aws_ecs_task_definition" "web" {
task_definition = "${aws_ecs_task_definition.web.family}"
...
}
resource "aws_ecs_service" "web" {
task_definition = "${aws_ecs_task_definition.web.family}:${max("${aws_ecs_task_definition.web.revision}", "${data.aws_ecs_task_definition.web.revision}")}"
...
}
我们通过定义 template_file 在 Terraform 中创建 ECS 服务,该 template_file 使用所有需要的变量填充任务定义 JSON 模板。然后使用呈现的 template_file 创建 aws_ecs_task_definition
。通过此任务定义,创建了 aws_ecs_service
:
data "template_file" "web" {
template = "${file("${path.module}/tasks/web.json")}"
vars {
...
}
}
resource "aws_ecs_task_definition" "web" {
container_definitions = "${data.template_file.web.rendered}"
requires_compatibilities = ["FARGATE"]
...
}
data "aws_ecs_task_definition" "web" {
task_definition = "${aws_ecs_task_definition.web.family}"
}
resource "aws_ecs_service" "web" {
name = "web"
task_definition = "${aws_ecs_task_definition.web.family}:${max("${aws_ecs_task_definition.web.revision}", "${data.aws_ecs_task_definition.web.revision}")}"
...
}
还有其他服务的任务定义与第一个服务几乎相同,只是与另一个命令有细微差别(例如启动 sidekiq 而不是 Web 应用程序)。
除了复制所有内容(JSON 模板,template_file
以及所有定义的变量,aws_ecs_task_definition
和 aws_ecs_service
),还有其他方法吗?
Modules 是在 Terraform 中解决这个问题的主要方法。
如果将现有代码移动到单个文件夹中,则可以定义允许自定义该模块的变量,例如要传递给 ECS 服务的命令。
所以在你的情况下你可能有这样的事情:
modules/foo-service/main.tf
data "template_file" "web" {
template = "${file("${path.module}/tasks/web.json")}"
vars {
# ...
command = "${var.command}"
}
}
resource "aws_ecs_task_definition" "web" {
container_definitions = "${data.template_file.web.rendered}"
requires_compatibilities = ["FARGATE"]
# ...
}
data "aws_ecs_task_definition" "web" {
task_definition = "${aws_ecs_task_definition.web.family}"
}
resource "aws_ecs_service" "web" {
name = "web"
task_definition = "${aws_ecs_task_definition.web.family}:${max("${aws_ecs_task_definition.web.revision}", "${data.aws_ecs_task_definition.web.revision}")}"
# ...
}
modules/foo-service/variables.tf
variable "command" {}
staging/main.tf
module "foo_service_web" {
source = "../modules/foo-service"
command = "bundle exec server"
}
module "foo_service_sidekiq" {
source = "../modules/foo-service"
command = "bundle exec sidekiq"
}
扩展已接受的答案以显示如何同时删除由 template_file
vars 块中定义的变量引起的重复(这些变量不会更改,因此必须在模块调用之间重复) .仅内联这些变量或使用默认值也不是解决方案,因为它们仍然会在项目之间发生变化,只是不在同一项目的服务内。我们可以使用局部变量来设置默认值并使用合并函数覆盖默认值:
main.tf
locals {
task_variables = {
image = "..."
# lots of other variables
command = "[\"nginx\", \"-g\", \"daemon off; error_log /dev/stdout info;\"]"
}
}
# first invocation of the module, overriding the command
module "sidekiq" {
source = "ecs_service"
...
task_variables = "${merge(
local.task_variables,
map(
"command", "[\"bash\", \"-c\", \"exec bundle exec sidekiq\"]",
)
)}"
}
# second invocation of the module, no overrides
module "web" {
source = "ecs_service"
task_variables = "${local.task_variables}"
}
模块ecs_service
variable "task_variables" {
type = "map"
}
data "template_file" "web_task" {
template = "${file("${path.module}/tasks/task_definition.json")}"
vars = "${var.task_variables}"
}
resource "aws_ecs_task_definition" "web" {
container_definitions = "${data.template_file.web_task.rendered}"
...
}
data "aws_ecs_task_definition" "web" {
task_definition = "${aws_ecs_task_definition.web.family}"
...
}
resource "aws_ecs_service" "web" {
task_definition = "${aws_ecs_task_definition.web.family}:${max("${aws_ecs_task_definition.web.revision}", "${data.aws_ecs_task_definition.web.revision}")}"
...
}