docs: AWS ECS terraform deploy (#2980)

This commit is contained in:
Guillaume Bouvignies
2024-01-10 08:17:38 +01:00
committed by GitHub
parent 4147d1604a
commit 42b28822f3
18 changed files with 1073 additions and 0 deletions
@@ -0,0 +1,3 @@
.terraform/
.env
terraform.tfstate*
+25
View File
@@ -0,0 +1,25 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/aws" {
version = "4.67.0"
constraints = "~> 4.16"
hashes = [
"h1:5Zfo3GfRSWBaXs4TGQNOflr1XaYj6pRnVJLX5VAjFX4=",
"zh:0843017ecc24385f2b45f2c5fce79dc25b258e50d516877b3affee3bef34f060",
"zh:19876066cfa60de91834ec569a6448dab8c2518b8a71b5ca870b2444febddac6",
"zh:24995686b2ad88c1ffaa242e36eee791fc6070e6144f418048c4ce24d0ba5183",
"zh:4a002990b9f4d6d225d82cb2fb8805789ffef791999ee5d9cb1fef579aeff8f1",
"zh:559a2b5ace06b878c6de3ecf19b94fbae3512562f7a51e930674b16c2f606e29",
"zh:6a07da13b86b9753b95d4d8218f6dae874cf34699bca1470d6effbb4dee7f4b7",
"zh:768b3bfd126c3b77dc975c7c0e5db3207e4f9997cf41aa3385c63206242ba043",
"zh:7be5177e698d4b547083cc738b977742d70ed68487ce6f49ecd0c94dbf9d1362",
"zh:8b562a818915fb0d85959257095251a05c76f3467caa3ba95c583ba5fe043f9b",
"zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
"zh:9c385d03a958b54e2afd5279cd8c7cbdd2d6ca5c7d6a333e61092331f38af7cf",
"zh:b3ca45f2821a89af417787df8289cb4314b273d29555ad3b2a5ab98bb4816b3b",
"zh:da3c317f1db2469615ab40aa6baba63b5643bae7110ff855277a1fb9d8eb4f2c",
"zh:dc6430622a8dc5cdab359a8704aec81d3825ea1d305bbb3bbd032b1c6adfae0c",
"zh:fac0d2ddeadf9ec53da87922f666e1e73a603a611c57bcbc4b86ac2821619b1d",
]
}
@@ -0,0 +1,85 @@
Deploying Windmill on AWS ECS
=============================
This folder contains terraform files to deploy a modular Windmill stack on AWS ECS with just a few commands.
### Pre-requisite
##### AWS terraform user
An AWS user with the AWS managed `AdministratorAccess` policy (arn: `arn:aws:iam::aws:policy/AdministratorAccess`) is necessary. This will be the user terraform uses to deploy the components to AWS.
Generate an AWS access key ID and secret, and append the following block to your `~/.aws/credentials`:
```
[terraform]
aws_access_key_id = <ACCESS_KEY_ID>
aws_secret_access_key = <SECRET_ACCESS_KEY>
```
##### Creating an ecs task execution role
Terraform will reference the role `ecsTaskExecutionRole` to launch tasks in the ECS cluster. SInce it is a common role, this Terraform does not create it, it need to be present in your AWS account.
If it's not, just create it in you IAM console and attach it the following AWS managed policy: `AmazonECSTaskExecutionRolePolicy` (arn: `arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy`)
##### Database password
Windmill relies on a PostgreSQL database which will be created by Terraform. The only think you need to manually provide is a strong password. It should be set in [terraform.tfvars](./terraform.tfvars).
### Windmill stack
The following stack will be deployed by this terraform as it is provided:
- 2 Windmill servers (each requiring 1 CPU and 1.5GiB of Memory)
- 2 multi-purpose Windmill workers (each requiring 2 CPU and 3GiB of Memory)
- Windmill LSP (requiring 1 CPU and 1.5GiB of Memory)
- Windmill Multiplayer (requiring 1 CPU and 1.5GiB of Memory)
- 1 native Windmill worker (requiring 2 CPU and 3GiB of Memory)
- 1 high performance Windmill worker (requiring 4 CPU and 15GiB of Memory)
The ECS cluster will be composed of 1 autoscaling group composed of up-to 6 `t3.medium` instances (5 necessary for the entire load, and 1 more for rolling upgrades). This will host all the services except the high performance workers. Those will be deployed on a separate auto-scaling group composed of up-to 2 `t3.xlarge` instances.
Of course the above is provided as an example, it should be tuned for you own needs, both in terms of instance specs, but also in terms of service architecture. For example, you might not need high performance workers, in which case you won't need the second autoscaling group at all. Same for the native worker, multiplayer, or even LSP (though LSP is highly recommended for a better coding experience).
The only strictly required components are:
- At least 1 Windmill server
- At least 1 multi-purpose Windmill worker
This terraform has been assembled to easily remove components. Each component mentioned in the above list is entirely contained in a `.tf` file. If you don't want it, just remove the file. And if you want to tune it, you know where to look.
- _REQUIRED_ Windmill server -> [windmill_server.tf](./windmill_server.tf)
- _REQUIRED_ Multi-purpose windmill worker -> [windmill_worker_basic.tf](./windmill_worker_basic.tf)
- _OPTIONAL_ Windmill LSP -> [windmill_lsp.tf](./windmill_lsp.tf)
- _OPTIONAL_ Windmill Multiplayer -> [windmill_multiplayer.tf](./windmill_multiplayer.tf)
- _OPTIONAL_ Windmill Native worker -> [windmill_worker_native.tf](./windmill_worker_native.tf)
- _OPTIONAL_ Windmill High Performace worker -> [windmill_worker_high_performance.tf](./windmill_worker_high_performance.tf) - this one is a lot longer because it deploys a new auto scaling group
### Network
The network deployed here is a single VPC, composed on 4 subnets spread across 2 availability zones (1 public and 1 private subnet per zone). All the services are behind a load balancer routing the request to Windmill server, LSP or multiplayer.
A single security group is used, allowing HTTP traffic on port 80 (it is not deploying custom certificates and therefore does not use HTTPS).
All this is provided as an example. It can be refined depending on your needs. All the network components are defined in [vpc.tf](./vpc.tf), except the security group which is is [security_group.tf](./security_group.tf) and load balancer which is defined in [load_balancer.tf](./load_balancer.tf)
### RDS Database
Windmill heavily relies on PostgreSQL database. This terraform deploys a standalone RDS instance having 100GiB of storage, which can be scaled up to 1000GiB. For production use cases, we recommend deploying a Multi-AZ instance, or even a Multi-AZ DB cluster. The RDS definition is in [rds.tf](./rds.tf)
### Ready?
REMINDER: don't forget to edit [terraform.tfvars](./terraform.tfvars) before deploying the stack.
Once you're ready, simply run:
```bash
# for a dry-run
terraform plan
```
and then
```bash
terraform apply
```
![Windmill screenshot](./assets/windmill.png)
Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

@@ -0,0 +1,95 @@
resource "aws_launch_template" "windmill_cluster_lt" {
name = "windmill-cluster-lt"
image_id = "ami-09c0b8e7f21923ac0"
instance_type = "t3.medium"
# vpc_security_group_ids = [aws_security_group.windmill_cluster_sg.id]
block_device_mappings {
device_name = "/dev/xvda"
ebs {
volume_size = 100
}
}
iam_instance_profile {
name = "ecsInstanceRole"
}
network_interfaces {
device_index = 0
delete_on_termination = true
associate_public_ip_address = true
security_groups = [
aws_security_group.windmill_cluster_sg.id
]
}
user_data = filebase64("${path.module}/lt_init_script.sh")
tag_specifications {
resource_type = "instance"
tags = {
Name = "ECS Instance - windmill-cluster"
}
}
}
resource "aws_autoscaling_group" "windmill_cluster_asg" {
name = "windmill-cluster-asg"
max_size = 6
min_size = 1
vpc_zone_identifier = [
aws_subnet.windmill_cluster_subnet_public1.id,
aws_subnet.windmill_cluster_subnet_public2.id,
aws_subnet.windmill_cluster_subnet_private1.id,
aws_subnet.windmill_cluster_subnet_private2.id
]
launch_template {
id = aws_launch_template.windmill_cluster_lt.id
version = "$Latest"
}
health_check_type = "EC2"
tag {
key = "AmazonECSManaged"
value = true
propagate_at_launch = true
}
}
resource "aws_ecs_cluster" "windmill_cluster" {
name = "windmill-cluster"
}
resource "aws_ecs_capacity_provider" "windmill_cluster_capacity_provider" {
name = "windmill-cluster-capacity-provider"
auto_scaling_group_provider {
auto_scaling_group_arn = aws_autoscaling_group.windmill_cluster_asg.arn
managed_scaling {
maximum_scaling_step_size = 5
minimum_scaling_step_size = 1
status = "ENABLED"
}
}
}
resource "aws_ecs_cluster_capacity_providers" "windmill_cluster_cluster___capacity_provider" {
cluster_name = aws_ecs_cluster.windmill_cluster.name
capacity_providers = [
aws_ecs_capacity_provider.windmill_cluster_capacity_provider.name,
aws_ecs_capacity_provider.windmill_cluster_high_performance_capacity_provider.name # remove if not using high performance workers
]
default_capacity_provider_strategy {
base = 1
weight = 100
capacity_provider = aws_ecs_capacity_provider.windmill_cluster_capacity_provider.name
}
}
@@ -0,0 +1,81 @@
resource "aws_lb_target_group" "windmill_cluster_windmill_server_tg" {
name = "windmill-cluster-server-tg"
port = 8000
protocol = "HTTP"
target_type = "ip"
vpc_id = aws_vpc.windmill_cluster_vpc.id
}
resource "aws_lb_target_group" "windmill_cluster_windmill_lsp_tg" {
name = "windmill-cluster-lsp-tg"
port = 3001
protocol = "HTTP"
target_type = "ip"
vpc_id = aws_vpc.windmill_cluster_vpc.id
}
resource "aws_lb_target_group" "windmill_cluster_windmill_multiplayer_tg" {
name = "windmill-cluster-multiplayer-tg"
port = 3002
protocol = "HTTP"
target_type = "ip"
vpc_id = aws_vpc.windmill_cluster_vpc.id
}
resource "aws_lb" "windmill_cluster_alb" {
name = "windmill-cluster-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.windmill_cluster_sg.id]
subnets = [
aws_subnet.windmill_cluster_subnet_public1.id,
aws_subnet.windmill_cluster_subnet_public2.id,
]
tags = {
Name = "windmill-cluster-alb"
}
}
resource "aws_lb_listener" "windmill_cluster_alb_listener" {
load_balancer_arn = aws_lb.windmill_cluster_alb.arn
port = 80
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.windmill_cluster_windmill_server_tg.arn
}
}
resource "aws_lb_listener_rule" "windmill_cluster_alb_lsp_rule" {
listener_arn = aws_lb_listener.windmill_cluster_alb_listener.arn
priority = 100
action {
type = "forward"
target_group_arn = aws_lb_target_group.windmill_cluster_windmill_lsp_tg.arn
}
condition {
path_pattern {
values = ["/ws/*"]
}
}
}
resource "aws_lb_listener_rule" "windmill_cluster_alb_multiplayer_rule" {
listener_arn = aws_lb_listener.windmill_cluster_alb_listener.arn
priority = 50
action {
type = "forward"
target_group_arn = aws_lb_target_group.windmill_cluster_windmill_multiplayer_tg.arn
}
condition {
path_pattern {
values = ["/ws_mp/*"]
}
}
}
@@ -0,0 +1,2 @@
#!/bin/bash
echo ECS_CLUSTER=windmill-cluster >> /etc/ecs/ecs.config;
+28
View File
@@ -0,0 +1,28 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.16"
}
}
required_version = ">= 1.2.0"
}
provider "aws" {
region = "us-east-2"
profile = "terraform"
}
data "aws_region" "current" {}
data "aws_iam_role" "ecs_task_execution_role" {
# this needs to be creates in AWS with the attaching the AWS managed policy: 'AmazonECSTaskExecutionRolePolicy'
name = "ecsTaskExecutionRole"
}
variable "database_password" {
type = string
sensitive = true
description = "RDS Database password"
}
+44
View File
@@ -0,0 +1,44 @@
resource "aws_db_subnet_group" "windmill_cluster_rds_subnets" {
name = "windmill-cluster-rds-subnets"
subnet_ids = [
aws_subnet.windmill_cluster_subnet_public1.id,
aws_subnet.windmill_cluster_subnet_public2.id,
aws_subnet.windmill_cluster_subnet_private1.id,
aws_subnet.windmill_cluster_subnet_private2.id,
]
}
resource "aws_db_instance" "windmill_cluster_rds" {
identifier = "windmill-cluster-db"
availability_zone = "us-east-2a"
instance_class = "db.m5d.large"
allocated_storage = 100
max_allocated_storage = 1000
storage_type = "gp3"
# storage_throughput = 125
storage_encrypted = true
# iops = 3000
engine = "postgres"
engine_version = "16.1"
parameter_group_name = "default.postgres16"
license_model = "postgresql-license"
db_name = "windmill"
username = "postgres"
password = var.database_password
network_type = "IPV4"
port = 5432
db_subnet_group_name = aws_db_subnet_group.windmill_cluster_rds_subnets.name
vpc_security_group_ids = [aws_security_group.windmill_cluster_sg.id]
publicly_accessible = false
performance_insights_enabled = true
performance_insights_retention_period = 7
backup_retention_period = 7
skip_final_snapshot = true
deletion_protection = false
}
@@ -0,0 +1,35 @@
resource "aws_security_group" "windmill_cluster_sg" {
name = "windmill-cluster-sg"
description = "Windmill cluster security group"
vpc_id = aws_vpc.windmill_cluster_vpc.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 0
to_port = 0
protocol = "-1"
self = true
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
tags = {
Name = "windmill-cluster-sg"
}
lifecycle {
create_before_destroy = true
}
}
@@ -0,0 +1 @@
database_password = "changeme"
+143
View File
@@ -0,0 +1,143 @@
resource "aws_vpc" "windmill_cluster_vpc" {
cidr_block = "132.0.0.0/16"
instance_tenancy = "default"
tags = {
Name = "windmill-cluster-vpc"
}
}
resource "aws_subnet" "windmill_cluster_subnet_public1" {
vpc_id = aws_vpc.windmill_cluster_vpc.id
cidr_block = "132.0.0.0/20"
availability_zone = "us-east-2a"
map_public_ip_on_launch = true
tags = {
Name = "windmill-cluster-subnet-public1"
}
}
resource "aws_subnet" "windmill_cluster_subnet_public2" {
vpc_id = aws_vpc.windmill_cluster_vpc.id
cidr_block = "132.0.16.0/20"
availability_zone = "us-east-2b"
map_public_ip_on_launch = true
tags = {
Name = "windmill-cluster-subnet-public2"
}
}
resource "aws_subnet" "windmill_cluster_subnet_private1" {
vpc_id = aws_vpc.windmill_cluster_vpc.id
cidr_block = "132.0.128.0/20"
availability_zone = "us-east-2a"
map_public_ip_on_launch = false
tags = {
Name = "windmill-cluster-subnet-private1"
}
}
resource "aws_subnet" "windmill_cluster_subnet_private2" {
vpc_id = aws_vpc.windmill_cluster_vpc.id
cidr_block = "132.0.144.0/20"
availability_zone = "us-east-2b"
map_public_ip_on_launch = false
tags = {
Name = "windmill-cluster-subnet-private2"
}
}
resource "aws_internet_gateway" "windmill_cluster_internet_gateway" {
vpc_id = aws_vpc.windmill_cluster_vpc.id
tags = {
Name = "windmill-cluster-internet-gateway"
}
}
resource "aws_eip" "windmill_cluster_nat_gateway_public1_eip" {
vpc = true
}
resource "aws_nat_gateway" "windmill_cluster_nat_gateway_public1" {
allocation_id = aws_eip.windmill_cluster_nat_gateway_public1_eip.id
subnet_id = aws_subnet.windmill_cluster_subnet_public1.id
tags = {
"Name" = "windmill-cluster-nat-gateway-public1"
}
}
resource "aws_eip" "windmill_cluster_nat_gateway_public2_eip" {
vpc = true
}
resource "aws_nat_gateway" "windmill_cluster_nat_gateway_public2" {
allocation_id = aws_eip.windmill_cluster_nat_gateway_public2_eip.id
subnet_id = aws_subnet.windmill_cluster_subnet_public2.id
tags = {
"Name" = "windmill-cluster-nat-gateway-public2"
}
}
resource "aws_route_table" "windmill_cluster_rtb_public" {
vpc_id = aws_vpc.windmill_cluster_vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.windmill_cluster_internet_gateway.id
}
tags = {
Name = "windmill-cluster-rtb-public"
}
}
resource "aws_route_table_association" "windmill_cluster_subnet_public1__rtb_public" {
subnet_id = aws_subnet.windmill_cluster_subnet_public1.id
route_table_id = aws_route_table.windmill_cluster_rtb_public.id
}
resource "aws_route_table_association" "windmill_cluster_subnet_public2__rtb_public" {
subnet_id = aws_subnet.windmill_cluster_subnet_public2.id
route_table_id = aws_route_table.windmill_cluster_rtb_public.id
}
resource "aws_route_table" "windmill_cluster_rtb_private1" {
vpc_id = aws_vpc.windmill_cluster_vpc.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.windmill_cluster_nat_gateway_public1.id
}
tags = {
Name = "windmill-cluster-rtb-private1"
}
}
resource "aws_route_table_association" "windmill_cluster_subnet_private1__rtb_private1" {
subnet_id = aws_subnet.windmill_cluster_subnet_private1.id
route_table_id = aws_route_table.windmill_cluster_rtb_private1.id
}
resource "aws_route_table" "windmill_cluster_rtb_private2" {
vpc_id = aws_vpc.windmill_cluster_vpc.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.windmill_cluster_nat_gateway_public2.id
}
tags = {
Name = "windmill-cluster-rtb-private2"
}
}
resource "aws_route_table_association" "windmill_cluster_subnet_private2__rtb_private2" {
subnet_id = aws_subnet.windmill_cluster_subnet_private2.id
route_table_id = aws_route_table.windmill_cluster_rtb_private2.id
}
@@ -0,0 +1,76 @@
resource "aws_cloudwatch_log_group" "windmill_cluster_windmill_lsp_log_group" {
name = "/ecs/windmill-lsp"
}
resource "aws_ecs_task_definition" "windmill_cluster_windmill_lsp_td" {
family = "windmill-lsp"
network_mode = "awsvpc"
execution_role_arn = data.aws_iam_role.ecs_task_execution_role.arn
cpu = 1024
memory = 1536
runtime_platform {
operating_system_family = "LINUX"
cpu_architecture = "X86_64"
}
requires_compatibilities = ["EC2"]
container_definitions = jsonencode([
{
name = "windmill-lsp"
image = "ghcr.io/windmill-labs/windmill-lsp:latest"
cpu = 1024
memory = 1536
essential = true
portMappings = [
{
name = "http"
containerPort = 3001
hostPort = 3001
protocol = "tcp"
appProtocol = "http"
}
]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = aws_cloudwatch_log_group.windmill_cluster_windmill_lsp_log_group.name
"awslogs-region" = data.aws_region.current.name
"awslogs-stream-prefix" = "ecs"
}
}
}
])
}
resource "aws_ecs_service" "windmill_cluster_windmill_lsp_service" {
name = "windmill-lsp"
cluster = aws_ecs_cluster.windmill_cluster.id
task_definition = aws_ecs_task_definition.windmill_cluster_windmill_lsp_td.arn
desired_count = 1
network_configuration {
subnets = [
aws_subnet.windmill_cluster_subnet_public1.id,
aws_subnet.windmill_cluster_subnet_public2.id,
]
security_groups = [aws_security_group.windmill_cluster_sg.id]
}
force_new_deployment = true
placement_constraints {
type = "distinctInstance"
}
capacity_provider_strategy {
capacity_provider = aws_ecs_capacity_provider.windmill_cluster_capacity_provider.name
weight = 100
}
load_balancer {
target_group_arn = aws_lb_target_group.windmill_cluster_windmill_lsp_tg.arn
container_name = "windmill-lsp"
container_port = 3001
}
depends_on = [aws_autoscaling_group.windmill_cluster_asg]
}
@@ -0,0 +1,76 @@
resource "aws_cloudwatch_log_group" "windmill_cluster_windmill_multiplayer_log_group" {
name = "/ecs/windmill-multiplayer"
}
resource "aws_ecs_task_definition" "windmill_cluster_windmill_multiplayer_td" {
family = "windmill-multiplayer"
network_mode = "awsvpc"
execution_role_arn = data.aws_iam_role.ecs_task_execution_role.arn
cpu = 1024
memory = 1536
runtime_platform {
operating_system_family = "LINUX"
cpu_architecture = "X86_64"
}
requires_compatibilities = ["EC2"]
container_definitions = jsonencode([
{
name = "windmill-multiplayer"
image = "ghcr.io/windmill-labs/windmill-multiplayer:latest"
cpu = 1024
memory = 1536
essential = true
portMappings = [
{
name = "http"
containerPort = 3002
hostPort = 3002
protocol = "tcp"
appProtocol = "http"
}
]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = aws_cloudwatch_log_group.windmill_cluster_windmill_multiplayer_log_group.name
"awslogs-region" = data.aws_region.current.name
"awslogs-stream-prefix" = "ecs"
}
}
}
])
}
resource "aws_ecs_service" "windmill_cluster_windmill_multiplayer_service" {
name = "windmill-multiplayer"
cluster = aws_ecs_cluster.windmill_cluster.id
task_definition = aws_ecs_task_definition.windmill_cluster_windmill_multiplayer_td.arn
desired_count = 1
network_configuration {
subnets = [
aws_subnet.windmill_cluster_subnet_public1.id,
aws_subnet.windmill_cluster_subnet_public2.id,
]
security_groups = [aws_security_group.windmill_cluster_sg.id]
}
force_new_deployment = true
placement_constraints {
type = "distinctInstance"
}
capacity_provider_strategy {
capacity_provider = aws_ecs_capacity_provider.windmill_cluster_capacity_provider.name
weight = 100
}
load_balancer {
target_group_arn = aws_lb_target_group.windmill_cluster_windmill_multiplayer_tg.arn
container_name = "windmill-multiplayer"
container_port = 3002
}
depends_on = [aws_autoscaling_group.windmill_cluster_asg]
}
@@ -0,0 +1,89 @@
resource "aws_cloudwatch_log_group" "windmill_cluster_windmill_server_log_group" {
name = "/ecs/windmill-server"
}
resource "aws_ecs_task_definition" "windmill_cluster_windmill_server_td" {
family = "windmill-server"
network_mode = "awsvpc"
execution_role_arn = data.aws_iam_role.ecs_task_execution_role.arn
cpu = 1024
memory = 1536
runtime_platform {
operating_system_family = "LINUX"
cpu_architecture = "X86_64"
}
requires_compatibilities = ["EC2"]
container_definitions = jsonencode([
{
name = "windmill-server"
image = "ghcr.io/windmill-labs/windmill-ee:main"
cpu = 1024
memory = 1536
essential = true
portMappings = [
{
name = "http"
containerPort = 8000
hostPort = 8000
protocol = "tcp"
appProtocol = "http"
}
]
environment = [{
name = "DATABASE_URL"
value = "postgres://${aws_db_instance.windmill_cluster_rds.username}:${aws_db_instance.windmill_cluster_rds.password}@${aws_db_instance.windmill_cluster_rds.endpoint}/${aws_db_instance.windmill_cluster_rds.db_name}"
}, {
name = "MODE"
value = "server"
}]
healthCheck = {
command = ["CMD-SHELL", "curl -f http://localhost:8000/api/version || exit 1"]
interval = 10
timeout = 5
retries = 5
}
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = aws_cloudwatch_log_group.windmill_cluster_windmill_server_log_group.name
"awslogs-region" = data.aws_region.current.name
"awslogs-stream-prefix" = "ecs"
}
}
}
])
}
resource "aws_ecs_service" "windmill_cluster_windmill_server_service" {
name = "windmill-server"
cluster = aws_ecs_cluster.windmill_cluster.id
task_definition = aws_ecs_task_definition.windmill_cluster_windmill_server_td.arn
desired_count = 2
network_configuration {
subnets = [
aws_subnet.windmill_cluster_subnet_public1.id,
aws_subnet.windmill_cluster_subnet_public2.id,
]
security_groups = [aws_security_group.windmill_cluster_sg.id]
}
force_new_deployment = true
placement_constraints {
type = "distinctInstance"
}
capacity_provider_strategy {
capacity_provider = aws_ecs_capacity_provider.windmill_cluster_capacity_provider.name
weight = 100
}
load_balancer {
target_group_arn = aws_lb_target_group.windmill_cluster_windmill_server_tg.arn
container_name = "windmill-server"
container_port = 8000
}
depends_on = [aws_autoscaling_group.windmill_cluster_asg]
}
@@ -0,0 +1,71 @@
resource "aws_cloudwatch_log_group" "windmill_cluster_windmill_worker_log_group" {
name = "/ecs/windmill-worker"
}
resource "aws_ecs_task_definition" "windmill_cluster_windmill_worker_td" {
family = "windmill-worker"
network_mode = "awsvpc"
execution_role_arn = data.aws_iam_role.ecs_task_execution_role.arn
cpu = 2048
memory = 3072
runtime_platform {
operating_system_family = "LINUX"
cpu_architecture = "X86_64"
}
requires_compatibilities = ["EC2"]
container_definitions = jsonencode([
{
name = "windmill-worker"
image = "ghcr.io/windmill-labs/windmill-ee:main"
cpu = 2048
memory = 3072
essential = true
environment = [{
name = "DATABASE_URL"
value = "postgres://${aws_db_instance.windmill_cluster_rds.username}:${aws_db_instance.windmill_cluster_rds.password}@${aws_db_instance.windmill_cluster_rds.endpoint}/${aws_db_instance.windmill_cluster_rds.db_name}"
}, {
name = "MODE"
value = "worker"
}, {
name = "WORKER_GROUP"
value = "default"
}]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = aws_cloudwatch_log_group.windmill_cluster_windmill_worker_log_group.name
"awslogs-region" = data.aws_region.current.name
"awslogs-stream-prefix" = "ecs"
}
}
}
])
}
resource "aws_ecs_service" "windmill_cluster_windmill_worker_service" {
name = "windmill-worker"
cluster = aws_ecs_cluster.windmill_cluster.id
task_definition = aws_ecs_task_definition.windmill_cluster_windmill_worker_td.arn
desired_count = 2
network_configuration {
subnets = [
aws_subnet.windmill_cluster_subnet_private1.id,
aws_subnet.windmill_cluster_subnet_private2.id,
]
security_groups = [aws_security_group.windmill_cluster_sg.id]
}
force_new_deployment = true
placement_constraints {
type = "distinctInstance"
}
capacity_provider_strategy {
capacity_provider = aws_ecs_capacity_provider.windmill_cluster_capacity_provider.name
weight = 100
}
depends_on = [aws_autoscaling_group.windmill_cluster_asg]
}
@@ -0,0 +1,147 @@
resource "aws_launch_template" "windmill_cluster_high_performance_lt" {
name = "windmill-cluster-high-perf-lt"
image_id = "ami-09c0b8e7f21923ac0"
instance_type = "t3.xlarge"
# vpc_security_group_ids = [aws_security_group.windmill_cluster_sg.id]
block_device_mappings {
device_name = "/dev/xvda"
ebs {
volume_size = 100
}
}
iam_instance_profile {
name = "ecsInstanceRole"
}
network_interfaces {
device_index = 0
delete_on_termination = true
associate_public_ip_address = true
security_groups = [
aws_security_group.windmill_cluster_sg.id
]
}
user_data = filebase64("${path.module}/lt_init_script.sh")
tag_specifications {
resource_type = "instance"
tags = {
Name = "ECS Instance - windmill-cluster"
}
}
}
resource "aws_autoscaling_group" "windmill_cluster_high_performance_asg" {
name = "windmill-cluster-high-perf-asg"
max_size = 2
min_size = 0
vpc_zone_identifier = [
aws_subnet.windmill_cluster_subnet_private1.id,
aws_subnet.windmill_cluster_subnet_private2.id
]
launch_template {
id = aws_launch_template.windmill_cluster_high_performance_lt.id
version = "$Latest"
}
health_check_type = "EC2"
tag {
key = "AmazonECSManaged"
value = true
propagate_at_launch = true
}
}
resource "aws_ecs_capacity_provider" "windmill_cluster_high_performance_capacity_provider" {
name = "windmill-cluster-high-perf-cp"
auto_scaling_group_provider {
auto_scaling_group_arn = aws_autoscaling_group.windmill_cluster_high_performance_asg.arn
managed_scaling {
maximum_scaling_step_size = 1
minimum_scaling_step_size = 1
status = "ENABLED"
}
}
}
resource "aws_cloudwatch_log_group" "windmill_cluster_windmill_high_performance_worker_log_group" {
name = "/ecs/windmill-high-performance-worker"
}
resource "aws_ecs_task_definition" "windmill_cluster_windmill_high_performance_worker_td" {
family = "windmill-high-performance-worker"
network_mode = "awsvpc"
execution_role_arn = data.aws_iam_role.ecs_task_execution_role.arn
cpu = 4096
memory = 15360
runtime_platform {
operating_system_family = "LINUX"
cpu_architecture = "X86_64"
}
requires_compatibilities = ["EC2"]
container_definitions = jsonencode([
{
name = "windmill-worker"
image = "ghcr.io/windmill-labs/windmill-ee:main"
cpu = 4096
memory = 15360
essential = true
environment = [{
name = "DATABASE_URL"
value = "postgres://${aws_db_instance.windmill_cluster_rds.username}:${aws_db_instance.windmill_cluster_rds.password}@${aws_db_instance.windmill_cluster_rds.endpoint}/${aws_db_instance.windmill_cluster_rds.db_name}"
}, {
name = "MODE"
value = "worker"
}, {
name = "WORKER_GROUP"
value = "high_performance"
}]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = aws_cloudwatch_log_group.windmill_cluster_windmill_high_performance_worker_log_group.name
"awslogs-region" = data.aws_region.current.name
"awslogs-stream-prefix" = "ecs"
}
}
}
])
}
resource "aws_ecs_service" "windmill_cluster_windmill_high_performance_worker_service" {
name = "windmill-high-performance-worker"
cluster = aws_ecs_cluster.windmill_cluster.id
task_definition = aws_ecs_task_definition.windmill_cluster_windmill_high_performance_worker_td.arn
desired_count = 1
network_configuration {
subnets = [
aws_subnet.windmill_cluster_subnet_private1.id,
aws_subnet.windmill_cluster_subnet_private2.id,
]
security_groups = [aws_security_group.windmill_cluster_sg.id]
}
force_new_deployment = true
placement_constraints {
type = "distinctInstance"
}
capacity_provider_strategy {
capacity_provider = aws_ecs_capacity_provider.windmill_cluster_high_performance_capacity_provider.name
weight = 100
}
depends_on = [aws_autoscaling_group.windmill_cluster_high_performance_asg]
}
@@ -0,0 +1,72 @@
resource "aws_cloudwatch_log_group" "windmill_cluster_windmill_native_worker_log_group" {
name = "/ecs/windmill-native-worker"
}
resource "aws_ecs_task_definition" "windmill_cluster_windmill_native_worker_td" {
family = "windmill-native-worker"
network_mode = "awsvpc"
execution_role_arn = data.aws_iam_role.ecs_task_execution_role.arn
cpu = 2048
memory = 3072
runtime_platform {
operating_system_family = "LINUX"
cpu_architecture = "X86_64"
}
requires_compatibilities = ["EC2"]
container_definitions = jsonencode([
{
name = "windmill-native-worker"
image = "ghcr.io/windmill-labs/windmill-ee:main"
cpu = 2048
memory = 3072
essential = true
environment = [{
name = "DATABASE_URL"
value = "postgres://${aws_db_instance.windmill_cluster_rds.username}:${aws_db_instance.windmill_cluster_rds.password}@${aws_db_instance.windmill_cluster_rds.endpoint}/${aws_db_instance.windmill_cluster_rds.db_name}"
}, {
name = "MODE"
value = "worker"
}, {
name = "WORKER_GROUP"
value = "native"
}]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = aws_cloudwatch_log_group.windmill_cluster_windmill_native_worker_log_group.name
"awslogs-region" = data.aws_region.current.name
"awslogs-stream-prefix" = "ecs"
}
"secretOptions" : []
}
}
])
}
resource "aws_ecs_service" "windmill_cluster_windmill_native_worker_service" {
name = "windmill-native-worker"
cluster = aws_ecs_cluster.windmill_cluster.id
task_definition = aws_ecs_task_definition.windmill_cluster_windmill_native_worker_td.arn
desired_count = 1
network_configuration {
subnets = [
aws_subnet.windmill_cluster_subnet_private1.id,
aws_subnet.windmill_cluster_subnet_private2.id,
]
security_groups = [aws_security_group.windmill_cluster_sg.id]
}
force_new_deployment = true
placement_constraints {
type = "distinctInstance"
}
capacity_provider_strategy {
capacity_provider = aws_ecs_capacity_provider.windmill_cluster_capacity_provider.name
weight = 100
}
depends_on = [aws_autoscaling_group.windmill_cluster_asg]
}