Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add example with terraform #556

Merged
merged 6 commits into from
May 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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