Skip to content

Commit

Permalink
Merge pull request #556 from kayac/example-with-terraform
Browse files Browse the repository at this point in the history
Add example with terraform
  • Loading branch information
fujiwara committed May 7, 2023
2 parents 7e116c8 + 093ad34 commit 5452757
Show file tree
Hide file tree
Showing 14 changed files with 606 additions and 0 deletions.
2 changes: 2 additions & 0 deletions tests/terraform/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
terraform.tfstate*
.terraform*
85 changes: 85 additions & 0 deletions tests/terraform/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
## An example of deployments by ecspresso and Terraform

This example shows how to deploy an ECS service by ecspresso and Terraform.

### Prerequisites

- [Terraform](https://www.terraform.io/) >= v1.4.0
- [ecspresso](https://github.com/kayac/ecspresso) >= v2.0.0

#### Environment variables

- `AWS_REGION` for AWS region. (e.g. `ap-northeast-1`)
- `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`, or `AWS_PROFILE` for AWS credentials.
- `AWS_SDK_LOAD_CONFIG=true` may be required if you use `AWS_PROFILE` and `~/.aws/config`.

### Usage

Terraform creates AWS resources (VPC, Subnets, ALB, IAM roles, ECS cluster, etc.) for ECS service working with ALB. And ecspresso deploys the service into the ECS cluster.

```console
$ terraform init
$ terraform apply
$ ecspresso verify
$ ecspresso deploy
```

After completing the deployment, you can access the service via ALB.

```console
$ curl -s "http://$(terraform output -raw alb_dns_name)/"
```

Note: This example contains availability zone named a, b, and d. If your AWS account does not have some of them, you can change the resources for them from [vpc.tf](./vpc.tf), [alb.tf](./alb.tf) and [ecs-service-def.jsonnet](./ecs-service-def.jsonnet).

### Usage with AWS CodeDeploy

At first, you must delete an ECS service that having "ECS" deployment controller.

```console
$ ecspresso delete --force --terminate
```

Note: After the ECS service is deleted, wait a few minutes until the ECS service is completely removed. While `ecspresso status` reports `DRAINING`, you cannot create a new ECS service with the same name. After `ecspresso status` reports `is INACTIVE`, you can continue to the next step.

Edit `ecs-service-def.jsonnet`. Remove `deploymentCircuitBreaker` block and change `deployment_controller` to `CODE_DEPLOY`.

```diff
{
deploymentConfiguration: {
- deploymentCircuitBreaker: {
- enable: false,
- rollback: false,
- },
maximumPercent: 200,
minimumHealthyPercent: 100,
},
deploymentController: {
- type: 'ECS',
+ type: 'CODE_DEPLOY',
},
```

Then deploy the service again.

```console
$ ecspresso deploy
```

After completing the deployment, you have to create a CodeDeploy application and deployment group.
Uncomment [codedeploy.tf](./codedeploy.tf) and run `terraform apply` again.

Now you can deploy the service by ecspresso using CodeDeploy!

```console
$ ecspresso deploy
```

### Cleanup

You must delete the ECS service and tasks first. And then, you can delete the resources created by Terraform.

```console
$ ecspresso delete --terminate
$ terraform destroy
```
63 changes: 63 additions & 0 deletions tests/terraform/alb.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
resource "aws_lb" "main" {
name = var.project
internal = false
load_balancer_type = "application"
security_groups = [
aws_security_group.alb.id,
aws_security_group.default.id,
]
subnets = [
aws_subnet.public-a.id,
aws_subnet.public-c.id,
aws_subnet.public-d.id,
]
tags = {
Name = var.project
}
}

resource "aws_lb_target_group" "http" {
for_each = toset(["alpha", "beta"])
name = "${var.project}-${each.key}"
port = 80
target_type = "ip"
vpc_id = aws_vpc.main.id
protocol = "HTTP"
deregistration_delay = 5

health_check {
path = "/"
port = "traffic-port"
protocol = "HTTP"
healthy_threshold = 2
unhealthy_threshold = 10
timeout = 5
interval = 6
}
tags = {
Name = "${var.project}-${each.key}"
}

lifecycle {
create_before_destroy = true
}
}

resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.main.arn
port = 80
protocol = "HTTP"

default_action {
type = "forward"
target_group_arn = aws_lb_target_group.http["alpha"].arn
}

tags = {
Name = "${var.project}-http"
}
}

output "alb_dns_name" {
value = aws_lb.main.dns_name
}
79 changes: 79 additions & 0 deletions tests/terraform/codedeploy.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
resource "aws_codedeploy_app" "main" {
name = var.project
compute_platform = "ECS"
}
resource "aws_codedeploy_deployment_group" "main" {
app_name = aws_codedeploy_app.main.name
deployment_config_name = "CodeDeployDefault.ECSAllAtOnce"
deployment_group_name = var.project
service_role_arn = aws_iam_role.codedeploy.arn
auto_rollback_configuration {
enabled = true
events = ["DEPLOYMENT_FAILURE"]
}
blue_green_deployment_config {
deployment_ready_option {
action_on_timeout = "CONTINUE_DEPLOYMENT"
}
terminate_blue_instances_on_deployment_success {
action = "TERMINATE"
}
}
deployment_style {
deployment_option = "WITH_TRAFFIC_CONTROL"
deployment_type = "BLUE_GREEN"
}
ecs_service {
cluster_name = aws_ecs_cluster.main.name
service_name = var.project
}
load_balancer_info {
target_group_pair_info {
prod_traffic_route {
listener_arns = [aws_lb_listener.http.arn]
}
target_group {
name = aws_lb_target_group.http["alpha"].name
}
target_group {
name = aws_lb_target_group.http["beta"].name
}
}
}
}
resource "aws_iam_role" "codedeploy" {
name = "${var.project}-codedeploy"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Principal = {
Service = "codedeploy.amazonaws.com"
}
Effect = "Allow"
Sid = ""
}
]
})
}
data "aws_iam_policy" "codedeploy" {
arn = "arn:aws:iam::aws:policy/AWSCodeDeployRoleForECS"
}
resource "aws_iam_role_policy_attachment" "codedeploy" {
policy_arn = data.aws_iam_policy.codedeploy.arn
role = aws_iam_role.codedeploy.name
}
*/
27 changes: 27 additions & 0 deletions tests/terraform/config.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
variable "project" {
type = string
default = "ecspresso"
}

provider "aws" {
region = "ap-northeast-1"
default_tags {
tags = {
"env" = "${var.project}"
}
}
}

terraform {
required_version = ">= 1.4.0"

required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 4.65.0"
}
}
}

data "aws_caller_identity" "current" {
}
49 changes: 49 additions & 0 deletions tests/terraform/ecs-service-def.jsonnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
deploymentConfiguration: {
// remove deploymentCircuitBreaker when deployment controller is CODE_DEPLOY
deploymentCircuitBreaker: {
enable: false,
rollback: false,
},
maximumPercent: 200,
minimumHealthyPercent: 100,
},
deploymentController: {
type: 'ECS', // ECS or CODE_DEPLOY
},
desiredCount: 1,
enableECSManagedTags: false,
enableExecuteCommand: true,
healthCheckGracePeriodSeconds: 0,
launchType: 'FARGATE',
loadBalancers: [
{
containerName: 'nginx',
containerPort: 80,
targetGroupArn: "{{ tfstate `aws_lb_target_group.http['alpha'].arn` }}",
},
],
networkConfiguration: {
awsvpcConfiguration: {
assignPublicIp: 'ENABLED',
securityGroups: [
'{{ tfstate `aws_security_group.default.id` }}',
],
subnets: [
'{{ tfstate `aws_subnet.public-a.id` }}',
'{{ tfstate `aws_subnet.public-c.id` }}',
'{{ tfstate `aws_subnet.public-d.id` }}',
],
},
},
platformFamily: 'Linux',
platformVersion: '1.4.0',
propagateTags: 'SERVICE',
schedulingStrategy: 'REPLICA',
tags: [
{
key: 'env',
value: 'ecspresso',
},
],
}
79 changes: 79 additions & 0 deletions tests/terraform/ecs-task-def.jsonnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
{
containerDefinitions: [
{
cpu: 0,
essential: true,
image: 'nginx:latest',
logConfiguration: {
logDriver: 'awslogs',
options: {
'awslogs-create-group': 'true',
'awslogs-group': '{{tfstate `aws_cloudwatch_log_group.main.name`}}',
'awslogs-region': '{{ must_env `AWS_REGION` }}',
'awslogs-stream-prefix': 'nginx',
},
},
name: 'nginx',
portMappings: [
{
appProtocol: '',
containerPort: 80,
hostPort: 80,
protocol: 'tcp',
},
],
},
{
command: [
'tail',
'-f',
'/dev/null',
],
cpu: 0,
essential: true,
image: 'debian:bullseye-slim',
logConfiguration: {
logDriver: 'awslogs',
options: {
'awslogs-create-group': 'true',
'awslogs-group': '{{tfstate `aws_cloudwatch_log_group.main.name`}}',
'awslogs-region': '{{ must_env `AWS_REGION` }}',
'awslogs-stream-prefix': 'bash',
},
},
name: 'bash',
secrets: [
{
name: 'FOO',
valueFrom: '{{tfstate `aws_ssm_parameter.foo.name`}}'
},
{
name: 'BAR',
valueFrom: '{{tfstate `aws_secretsmanager_secret.bar.arn`}}'
},
{
name: 'JSON_KEY',
valueFrom: '{{tfstate `aws_secretsmanager_secret.json.arn`}}:key::'
},
],
},
],
cpu: '256',
ephemeralStorage: {
sizeInGiB: 30,
},
executionRoleArn: '{{tfstate `aws_iam_role.ecs-task.arn`}}',
family: 'ecspresso',
memory: '512',
networkMode: 'awsvpc',
requiresCompatibilities: [
'FARGATE',
],
tags: [
{
key: 'env',
value: 'ecspresso',
},
],
taskRoleArn: '{{tfstate `aws_iam_role.ecs-task.arn`}}',
}
6 changes: 6 additions & 0 deletions tests/terraform/ecs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
resource "aws_ecs_cluster" "main" {
name = var.project
tags = {
Name = var.project
}
}
Loading

0 comments on commit 5452757

Please sign in to comment.