diff --git a/main.tf b/main.tf index f90a00e..b145df5 100644 --- a/main.tf +++ b/main.tf @@ -1,114 +1,151 @@ ################################ -# IAM Roles # +# Master IAM Roles # ################################ -data "aws_iam_policy_document" "assume_role" { +data "aws_iam_policy_document" "master" { statement { actions = ["sts:AssumeRole"] principals { type = "Service" - identifiers = ["ecs-tasks.amazonaws.com"] + identifiers = ["eks.amazonaws.com"] } } } -resource "aws_iam_role" "execution" { - name = "${var.identifier}-ServiceRoleForECSExecution" - assume_role_policy = data.aws_iam_policy_document.assume_role.json +resource "aws_iam_role" "master" { + name = "${var.identifier}-ServiceRoleForEKSMaster" + assume_role_policy = data.aws_iam_policy_document.master.json tags = var.tags } -resource "aws_iam_role_policy_attachment" "execution" { - role = aws_iam_role.execution.name - policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role" +resource "aws_iam_role_policy_attachment" "cluster" { + policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy" + role = aws_iam_role.master.name } -resource "aws_iam_role" "task" { - name = "${var.identifier}-ServiceRoleForECSTask" - assume_role_policy = data.aws_iam_policy_document.assume_role.json - - tags = var.tags +resource "aws_iam_role_policy_attachment" "service" { + policy_arn = "arn:aws:iam::aws:policy/AmazonEKSServicePolicy" + role = aws_iam_role.master.name } -resource "aws_iam_role_policy_attachment" "task" { - count = length(var.policies) - role = aws_iam_role.task.name - policy_arn = var.policies[count.index] +resource "aws_iam_role_policy_attachment" "controller" { + policy_arn = "arn:aws:iam::aws:policy/AmazonEKSVPCResourceController" + role = aws_iam_role.master.name } ################################ -# CloudWatch # +# Worker IAM Roles # ################################ -resource "aws_cloudwatch_log_group" "main" { - name = "${var.identifier}-fargate" - retention_in_days = try(var.log_config["retention_in_days"], null) +data "aws_iam_policy_document" "worker" { + statement { + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["ec2.amazonaws.com"] + } + } +} + +resource "aws_iam_role" "worker" { + name = "${var.identifier}-ServiceRoleForEKSWorker" + assume_role_policy = data.aws_iam_policy_document.worker.json tags = var.tags } -################################ -# ECR Repository # -################################ +data "aws_iam_policy_document" "autoscaling" { + statement { + effect = "Allow" + + actions = [ + "autoscaling:DescribeAutoScalingGroups", + "autoscaling:DescribeAutoScalingInstances", + "autoscaling:DescribeTags", + "autoscaling:DescribeLaunchConfigurations", + "autoscaling:SetDesiredCapacity", + "autoscaling:TerminateInstanceInAutoScalingGroup", + "ec2:DescribeLaunchTemplateVersions" + ] + + resources = ["*"] + } +} -resource "aws_ecr_repository" "main" { - count = var.image == null ? 1 : 0 - name = "${var.identifier}-cluster" - image_tag_mutability = "MUTABLE" - force_delete = true +resource "aws_iam_policy" "autoscaling" { + name = "ed-eks-autoscaler-policy" + policy = data.aws_iam_policy_document.autoscaling.json tags = var.tags } -################################ -# ECS Cluster # -################################ +resource "aws_iam_role_policy_attachment" "autoscaling" { + policy_arn = aws_iam_policy.autoscaling.arn + role = aws_iam_role.worker.name +} -resource "aws_ecs_cluster" "main" { - name = var.identifier +resource "aws_iam_role_policy_attachment" "worker_node" { + policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy" + role = aws_iam_role.worker.name +} - tags = var.tags +resource "aws_iam_role_policy_attachment" "network_interface" { + policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy" + role = aws_iam_role.worker.name } -resource "aws_ecs_task_definition" "main" { - family = var.identifier - requires_compatibilities = ["FARGATE"] - execution_role_arn = aws_iam_role.execution.arn - task_role_arn = aws_iam_role.task.arn - network_mode = "awsvpc" - cpu = var.cpu - memory = var.memory - container_definitions = jsonencode([{ - name = var.identifier - image = var.image == null ? "${aws_ecr_repository.main[0].repository_url}:latest" : try(var.image["uri"], null) - environment = [for k, v in var.env_variables : {name = k, value = v}] - logConfiguration = { - logDriver = "awslogs" - options = { - awslogs-group = aws_cloudwatch_log_group.main.id - awslogs-region = try(var.log_config["region"], null) - awslogs-stream-prefix = "cluster" - } - } - }]) +resource "aws_iam_role_policy_attachment" "ecr" { + policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" + role = aws_iam_role.worker.name +} + +resource "aws_iam_role_policy_attachment" "ssm" { + policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" + role = aws_iam_role.worker.name +} + +resource "aws_iam_role_policy_attachment" "xray" { + policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess" + role = aws_iam_role.worker.name +} + +# TODO give IAM permission to read ECR registries and S3 buckets + +################################ +# EKS Cluster # +################################ + +resource "aws_eks_cluster" "main" { + name = var.identifier + role_arn = aws_iam_role.master.arn + + vpc_config { + subnet_ids = var.subnets + } tags = var.tags } -resource "aws_ecs_service" "main" { - name = var.identifier - cluster = aws_ecs_cluster.main.id - task_definition = aws_ecs_task_definition.main.arn - launch_type = "FARGATE" - desired_count = var.desired_task_count - force_new_deployment = true +resource "aws_eks_node_group" "main" { + cluster_name = aws_eks_cluster.main.name + node_group_name = var.identifier + node_role_arn = aws_iam_role.worker.arn + subnet_ids = var.subnets + capacity_type = "ON_DEMAND" + disk_size = var.disk_size + instance_types = var.instance_types + + scaling_config { + desired_size = var.desired_size + max_size = var.max_size + min_size = var.min_size + } - network_configuration { - subnets = try(var.network_config["subnets"], null) - assign_public_ip = false - security_groups = var.security_groups + update_config { + max_unavailable = 1 } tags = var.tags diff --git a/outputs.tf b/outputs.tf index 6c9dd56..e69de29 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,12 +0,0 @@ -output "ecr_repository" { - description = "Object of the created ECR repository if created." - value = { - uri = try(aws_ecr_repository.main[0].repository_url, null) - arn = try(aws_ecr_repository.main[0].arn, null) - } -} - -output "log_arn" { - description = "ARN of the created CloudWatch log group." - value = try(aws_cloudwatch_log_group.main.arn, null) -} diff --git a/tests/cluster.tftest.hcl b/tests/cluster.tftest.hcl deleted file mode 100644 index ae9951f..0000000 --- a/tests/cluster.tftest.hcl +++ /dev/null @@ -1,127 +0,0 @@ -provider "aws" { - region = "eu-central-1" - default_tags { - tags = { - Environment = "Test" - } - } -} - -run "invalid_identifier" { - command = plan - - variables { - identifier = "ab" - - network_config = { - vpc = "vpc-01234567890abcdef" - subnets = ["subnet-1242421", "subnet-2344898"] - } - - log_config = { - region = "eu-central-1" - retention_in_days = 7 - } - } - - expect_failures = [var.identifier] -} - -run "invalid_vpc" { - command = plan - - variables { - identifier = "abc" - - network_config = { - vpc = "abc-01234567890abcdef" - subnets = ["subnet-1242421", "subnet-2344898"] - } - - log_config = { - region = "eu-central-1" - retention_in_days = 7 - } - } - - expect_failures = [var.network_config] -} - -run "invalid_subnets" { - command = plan - - variables { - identifier = "abc" - - network_config = { - vpc = "vpc-01234567890abcdef" - subnets = ["subnet-1242421", "net-2344898"] - } - - log_config = { - region = "eu-central-1" - retention_in_days = 7 - } - } - - expect_failures = [var.network_config] -} - -run "valid_configuration" { - command = plan - - variables { - identifier = "abc" - - network_config = { - vpc = "vpc-01234567890abcdef" - subnets = ["subnet-1242421", "subnet-2344898"] - } - - log_config = { - region = "eu-central-1" - retention_in_days = 7 - } - } -} - -run "invalid_security_groups" { - command = plan - - variables { - identifier = "abc" - security_groups = ["sg-we32558632", "s23423423432", "sg-893hgo23hg23"] - - network_config = { - vpc = "vpc-01234567890abcdef" - subnets = ["subnet-1242421", "subnet-2344898"] - } - - log_config = { - region = "eu-central-1" - retention_in_days = 7 - } - } - - expect_failures = [var.security_groups] -} - -run "invalid_retention_in_days" { - command = plan - - variables { - identifier = "abc" - - network_config = { - vpc = "vpc-01234567890abcdef" - subnets = ["subnet-1242421", "subnet-2344898"] - } - - log_config = { - region = "eu-central-1" - retention_in_days = 6 - } - } - - expect_failures = [var.log_config] -} diff --git a/tests/ecr.tftest.hcl b/tests/ecr.tftest.hcl deleted file mode 100644 index c181b9f..0000000 --- a/tests/ecr.tftest.hcl +++ /dev/null @@ -1,60 +0,0 @@ -provider "aws" { - region = "eu-central-1" - default_tags { - tags = { - Environment = "Test" - } - } -} - -run "without_repository" { - command = plan - - variables { - identifier = "abc" - - image = null - - network_config = { - vpc = "vpc-01234567890abcdef" - subnets = ["subnet-1242421", "subnet-2344898"] - } - - log_config = { - region = "eu-central-1" - retention_in_days = 7 - } - } - - assert { - condition = length(aws_ecr_repository.main) == 1 - error_message = "ECR repository was not created" - } -} - -run "with_repository" { - command = plan - - variables { - identifier = "abc" - - image = { - uri = "registry.test:latest" - } - - network_config = { - vpc = "vpc-01234567890abcdef" - subnets = ["subnet-1242421", "subnet-2344898"] - } - - log_config = { - region = "eu-central-1" - retention_in_days = 7 - } - } - - assert { - condition = length(aws_ecr_repository.main) == 0 - error_message = "ECR repository was created unexpectedly" - } -} diff --git a/variables.tf b/variables.tf index 4005793..85630f9 100644 --- a/variables.tf +++ b/variables.tf @@ -7,85 +7,54 @@ variable "identifier" { } } -variable "policies" { - description = "List of IAM policy ARNs for the Fargate task's IAM role." - type = list(string) - default = [] -} - -variable "log_config" { - description = "Object to define logging configuration for the Fargate tasks to CloudWatch." - type = object({ - region = string - retention_in_days = number - }) +variable "vpc" { + description = "ID of the subnets' VPC." + type = string validation { - condition = try(var.log_config["retention_in_days"], 1) == 1 || ( - try(var.log_config["retention_in_days"], 3) == 3) || ( - try(var.log_config["retention_in_days"], 5) == 5) || ( - try(var.log_config["retention_in_days"], 7) == 7) || ( - try(var.log_config["retention_in_days"], 14) == 14) || ( - try(var.log_config["retention_in_days"], 30) == 30) || ( - try(var.log_config["retention_in_days"], 365) == 365) || ( - try(var.log_config["retention_in_days"], 0) == 0) - error_message = "Retention in days must be one of these values: 0, 1, 3, 5, 7, 14, 30, 365" + condition = startswith(var.vpc, "vpc-") + error_message = "Must be valid VPC ID" } } -variable "image" { - description = "Object of the image which will be pulled by the Fargate tasks to execute." - type = object({ - uri = string - }) - default = null -} - -variable "security_groups" { - description = "List of security group IDs the ECS service will hold." +variable "subnets" { + description = "A list of IDs of subnets for the subnet group and potentially the RDS proxy." type = list(string) - default = [] - validation { - condition = !contains([for v in var.security_groups : startswith(v, "sg-")], false) - error_message = "Elements must be valid security group IDs" - } -} - -variable "network_config" { - description = "Object of definition for the network configuration of the ECS service." - type = object({ - vpc = string - subnets = list(string) - }) validation { - condition = startswith(try(var.network_config["vpc"], null), "vpc-") - error_message = "Must be valid VPC ID" + condition = length(var.subnets) > 1 + error_message = "List of subnets must contain at least 2 elements" } validation { - condition = !contains([for v in var.network_config["subnets"] : startswith(v, "subnet-")], false) - error_message = "Elements in task subnets must be valid subnet IDs" + condition = !contains([for v in var.subnets : startswith(v, "subnet-")], false) + error_message = "Elements must be valid subnet IDs" } } -variable "env_variables" { - description = "A map of environment variables for the Fargate task at runtime." - type = map(string) - default = {} +variable "disk_size" { + description = "Disk size in GiB of the node group." + type = number + default = 20 } -variable "memory" { - description = "Amount of memory in MiB used by each Fargate tasks." +variable "instance_types" { + description = "Types of the instances in the node group." + type = list(string) + default = ["t3.small"] +} + +variable "desired_size" { + description = "Desired amount of nodes in the node group." type = number - default = 512 + default = 1 } -variable "cpu" { - description = "Number of CPU units used by each Fargate tasks." +variable "min_size" { + description = "Minimum amount of nodes in the node group." type = number - default = 256 + default = 1 } -variable "desired_task_count" { - description = "Preferred number of task that shall run." +variable "max_size" { + description = "Maximum amount of nodes in the node group." type = number default = 1 }