diff --git a/README.md b/README.md index d44141c..d4abb08 100644 --- a/README.md +++ b/README.md @@ -2,29 +2,43 @@ ## What is this -This module contains reusable terraform modules to reduce boilerplate code. Mainly for M3 company but anyone can use this under [LICENSE](./LICENSE). +Reusable terraform modules. They are made for M3,Inc.'s internal use. But, anyone able to use them under [LICENSE](./LICENSE). -For example, you can setup ECS cluster + AutoScailing with [ecs_ec2_cluster_template](./ecs_ec2_cluster_template), no need to write many terraform for every applications. +## Modules -## Documents +**Setting up services**: -Look `README.md` and `variables.tf` of each module to know it's detail. For example, [ecs_ec2_cluster_template/README.md](./ecs_ec2_cluster_template/README.md) and [ecs_ec2_cluster_template/variables.tf](./ecs_ec2_cluster_template/variables.tf). +| Module | Description | +| -------------: | :------------- | +| [alb_template](./alb_template) | ALB + SSL certificate. | +| [ecr_repository_template](./ecr_repository_template) | ECR repository with Lifecycle policy. | +| [ecs_ec2_cluster_template](./ecs_ec2_cluster_template) | ECS nodes with EC2 + AutoScaling. | +| [ecs_web_service_template](./ecs_web_service_template) | ECS service + ALB target/listener + Route53 record.| -## How to use +**Monitoring**: -You can load this module from [GitHub registry](https://www.terraform.io/docs/modules/sources.html#github). +| Module | Description | +| -------------: | :------------- | +| [guardduty_slack](./guardduty_slack) | GuardDuty alerts in Slack. | +| [lambda_monitoring](./lambda_monitoring) | CloudWatch monitoring (alarm) for AWS lambda. | -Only what you need to do is to write following: +See `README.md` and `variables.tf` of each module for detail. + +## Install + +We can use modules with `module` blocks: ``` module "esc_ec2_cluster_template" { source = "github.com/m3dev/m3-terraform-modules//ecs_ec2_cluster_template?ref=495ff58" - // ... set input variables, see `variables.tf` of the module. + // ... set input variables, see `variables.tf` of the mo. } ``` -Note that there are some key points in the `source` URL (see [official document for detail](https://www.terraform.io/docs/modules/sources.html)): +Note: + +- You have to use double slash (`//`) to split repository URL and path from repository root. +- You should specify tag/branch/revision with `ref`, or your code might be broken when the `master` branch changed. -- Use double slash (`//`) to split repository URL and path from repository root -- Use `ref` to specify tag/branch/revision to use +See [the official document](https://www.terraform.io/docs/modules/sources.html)) for detail. diff --git a/alb_template/README.md b/alb_template/README.md index 2afa3ab..4e4e87f 100644 --- a/alb_template/README.md +++ b/alb_template/README.md @@ -1,6 +1,6 @@ # AWS ALB template -Create ALB + SSL certificate. +ALB + SSL certificate. This module also setup [DNS record](aws_route53_record.tf) and [aws_acm_certificate_validation](aws_acm_certificate_validation.tf) with DNS validation. So that this module requires Route53 hosted zone. diff --git a/ecr_ecs_deploy/README.md b/ecr_ecs_deploy/README.md new file mode 100644 index 0000000..c80a2de --- /dev/null +++ b/ecr_ecs_deploy/README.md @@ -0,0 +1,19 @@ +# ecr_ecs_deploy + +Updates ECS service to force a new deployment when new Docker image is pushed to ECR repository. + +It must be useful for a case that ECS task definition refers docker image with ":latest" (or other fixed) tag. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:-----:| +| docker\_image\_tags | Docker image tag to watch. | `string` | `"latest"` | no | +| ecr\_repository\_name | Name of ECR repository to watch. | `string` | n/a | yes | +| ecs\_cluster\_name | Name of ECS cluster which the ECS service belongs to | `string` | n/a | yes | +| ecs\_service\_name | Arn of ECS service to update | `string` | n/a | yes | +| name\_prefix | Prefix which is added for some resources. | `string` | `""` | no | + +## Outputs + +No output. diff --git a/ecr_ecs_deploy/aws_cloudwatch_event_rule.tf b/ecr_ecs_deploy/aws_cloudwatch_event_rule.tf new file mode 100644 index 0000000..600db95 --- /dev/null +++ b/ecr_ecs_deploy/aws_cloudwatch_event_rule.tf @@ -0,0 +1,28 @@ +resource "aws_cloudwatch_event_rule" "ecr_deploy" { + name_prefix = "${var.name_prefix}ecr-put-image" + description = "ECR PutImage" + + event_pattern = jsonencode({ + source = [ + "aws.ecr" + ] + "detail" = { + eventName = [ + "PutImage" + ] + "requestParameters": { + "repositoryName": [ + var.ecr_repository_name + ], + "imageTag": [ + var.docker_image_tags + ] + } + } + }) +} + +resource "aws_cloudwatch_event_target" "slack" { + rule = aws_cloudwatch_event_rule.ecr_deploy.name + arn = aws_lambda_function.update_ecs_service.arn +} diff --git a/ecr_ecs_deploy/aws_iam_role.tf b/ecr_ecs_deploy/aws_iam_role.tf new file mode 100644 index 0000000..5b6af4f --- /dev/null +++ b/ecr_ecs_deploy/aws_iam_role.tf @@ -0,0 +1,50 @@ +resource "aws_iam_role" "lambda_execution" { + name_prefix = "${var.name_prefix}lambda_execution" + assume_role_policy = data.aws_iam_policy_document.lambda_execution_assume_role_policy.json +} + +data "aws_iam_policy_document" "lambda_execution_assume_role_policy" { + statement { + actions = [ + "sts:AssumeRole" + ] + principals { + type = "Service" + identifiers = [ + "lambda.amazonaws.com" + ] + } + } +} + + +resource "aws_iam_role_policy_attachment" "lambda_execution" { + role = aws_iam_role.lambda_execution.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" +} + +resource "aws_iam_role_policy_attachment" "update_ecs_service" { + role = aws_iam_role.lambda_execution.name + policy_arn = aws_iam_policy.update_ecs_service.arn +} + +resource "aws_iam_policy" "update_ecs_service" { + name = "${var.name_prefix}update_ecs_service" + policy = data.aws_iam_policy_document.update_ecs_service.json +} + +resource "random_id" "sid" { + byte_length = 8 +} + +data "aws_iam_policy_document" "update_ecs_service" { + statement { + sid = random_id.sid.hex + actions = [ + "ecs:UpdateService" + ] + resources = [ + var.ecs_service_arn + ] + } +} diff --git a/ecr_ecs_deploy/aws_lambda_function.tf b/ecr_ecs_deploy/aws_lambda_function.tf new file mode 100644 index 0000000..a626341 --- /dev/null +++ b/ecr_ecs_deploy/aws_lambda_function.tf @@ -0,0 +1,42 @@ +resource "random_id" "suffix" { + byte_length = 8 +} + +data "aws_ecs_cluster" "cluster" { + cluster_name = var.ecs_cluster_name +} + +data "aws_ecs_service" "service" { + cluster_arn = data.aws_ecs_cluster.cluster.arn + service_name = var.ecs_service_name +} + +resource "aws_lambda_function" "update_ecs_service" { + filename = data.archive_file.lambda.output_path + function_name = "${var.name_prefix}-update_ecs_service-${random_id.suffix.hex}" + role = aws_iam_role.lambda_execution.arn + handler = "main.lambda_handler" + source_code_hash = data.archive_file.lambda.output_base64sha256 + runtime = "python3.8" + memory_size = 128 + timeout = 30 + + environment { + variables = { + "EcsServiceArn" = data.aws_ecs_service.service.arn + "EcsClusterName" = var.ecs_cluster_name + } + } + + lifecycle { + ignore_changes = [ + filename, + ] + } +} + +data "archive_file" "lambda" { + type = "zip" + source_dir = "${abspath(path.module)}/lambda" + output_path = "${abspath(path.module)}/.upload/slack.zip" +} diff --git a/ecr_ecs_deploy/aws_lambda_permission.tf b/ecr_ecs_deploy/aws_lambda_permission.tf new file mode 100644 index 0000000..4de1978 --- /dev/null +++ b/ecr_ecs_deploy/aws_lambda_permission.tf @@ -0,0 +1,6 @@ +resource "aws_lambda_permission" "ecr_deploy" { + source_arn = aws_cloudwatch_event_rule.ecr_deploy.arn + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.update_ecs_service.function_name + principal = "events.amazonaws.com" +} diff --git a/ecr_ecs_deploy/lambda/main.py b/ecr_ecs_deploy/lambda/main.py new file mode 100644 index 0000000..0a2bc5d --- /dev/null +++ b/ecr_ecs_deploy/lambda/main.py @@ -0,0 +1,32 @@ +import json +import logging +import os + +import boto3 + + +class JsonFormatter: + def format(self, record): + return json.dumps(record.__dict__) + + +logging.basicConfig() +logging.getLogger().handlers[0].setFormatter(JsonFormatter()) +logger = logging.getLogger(__name__) +logger.setLevel(os.environ.get('LOG_LEVEL', 'INFO')) + + +def lambda_handler(event, context): + try: + service = os.environ['EcsServiceArn'] + cluster = os.environ['EcsClusterName'] + except KeyError as e: + logger.exception(f'Environment variable not set: {e}') + raise + + client = boto3.client("ecs") + response = client.update_service( + cluster=cluster, + service=service, + forceNewDeployment=True, + ) diff --git a/ecr_ecs_deploy/variables.tf b/ecr_ecs_deploy/variables.tf new file mode 100644 index 0000000..d1c9b6b --- /dev/null +++ b/ecr_ecs_deploy/variables.tf @@ -0,0 +1,27 @@ +variable "ecr_repository_name" { + type = string + description = "Name of ECR repository to watch." +} + +variable "ecs_cluster_name" { + type = string + description = "Name of ECS cluster which the ECS service belongs to" +} + +variable "ecs_service_name" { + type = string + description = "Arn of ECS service to update" +} + +variable "docker_image_tags" { + type = string + description = "Docker image tag to watch." + default = "latest" +} + +variable "name_prefix" { + type = string + description = "Prefix which is added for some resources." + default = "" +} + diff --git a/ecr_repository_template/README.md b/ecr_repository_template/README.md index 6ecf0a1..606df6e 100644 --- a/ecr_repository_template/README.md +++ b/ecr_repository_template/README.md @@ -1,6 +1,6 @@ # ecr_repository_template -Create ECR repository with Lifecycle policy. +ECR repository with Lifecycle policy. ## Variables diff --git a/ecs_ec2_cluster_template/README.md b/ecs_ec2_cluster_template/README.md index e571fd2..f8a61de 100644 --- a/ecs_ec2_cluster_template/README.md +++ b/ecs_ec2_cluster_template/README.md @@ -1,6 +1,6 @@ # ecs_ec2_cluster_template -Setup ECS nodes with EC2 + AutoScaling. +ECS nodes with EC2 + AutoScaling. Also enable [SSM](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ec2-run-command.html) so that you can manage instance with it. diff --git a/ecs_web_service_template/README.md b/ecs_web_service_template/README.md index d3bfd7f..b3248be 100644 --- a/ecs_web_service_template/README.md +++ b/ecs_web_service_template/README.md @@ -1,6 +1,6 @@ # ecs_service_template -Create ECS service + ALB target/listener + Route53 record. +ECS service + ALB target/listener + Route53 record. ## Prerequisite diff --git a/guardduty_slack/README.md b/guardduty_slack/README.md index b638ab7..2ff05eb 100644 --- a/guardduty_slack/README.md +++ b/guardduty_slack/README.md @@ -1,5 +1,7 @@ # guardduty_slack +Notification of GuardDuty for Slack. + This module setups: - AWS GuardDuty