From 04cc267e86b77f8f6f0cd2923c8a8d0232bd6edd Mon Sep 17 00:00:00 2001 From: Edward Viaene Date: Thu, 20 Feb 2020 21:43:59 +0100 Subject: [PATCH] codepipeline (#30) * codepipeline --- codepipeline-demo/app/config/appspec.yaml | 10 +++ codepipeline-demo/app/config/buildspec.yml | 21 +++++ .../app/scripts/create-new-task-def.sh | 18 ++++ .../app/scripts/set-network-configuration.sh | 11 +++ codepipeline-demo/codedeploy.tf | 53 ++++++++++++ codepipeline-demo/codepipeline.tf | 16 ++-- codepipeline-demo/fargate-service.tf | 56 ++++++++---- codepipeline-demo/iam-codebuild.tf | 11 +++ codepipeline-demo/iam-codedeploy.tf | 86 +++++++++++++++++++ codepipeline-demo/iam-codepipeline.tf | 15 ++-- codepipeline-demo/lb.tf | 33 +++++-- codepipeline-demo/vpc.tf | 2 +- 12 files changed, 293 insertions(+), 39 deletions(-) create mode 100644 codepipeline-demo/app/config/appspec.yaml create mode 100644 codepipeline-demo/app/config/buildspec.yml create mode 100755 codepipeline-demo/app/scripts/create-new-task-def.sh create mode 100755 codepipeline-demo/app/scripts/set-network-configuration.sh create mode 100644 codepipeline-demo/codedeploy.tf create mode 100644 codepipeline-demo/iam-codedeploy.tf diff --git a/codepipeline-demo/app/config/appspec.yaml b/codepipeline-demo/app/config/appspec.yaml new file mode 100644 index 0000000..da97f97 --- /dev/null +++ b/codepipeline-demo/app/config/appspec.yaml @@ -0,0 +1,10 @@ +version: 0.0 +Resources: + - TargetService: + Type: AWS::ECS::Service + Properties: + # will be replaced by codedeploy when the pipeline runs + TaskDefinition: "" + LoadBalancerInfo: + ContainerName: demo + ContainerPort: 3000 diff --git a/codepipeline-demo/app/config/buildspec.yml b/codepipeline-demo/app/config/buildspec.yml new file mode 100644 index 0000000..5b4e740 --- /dev/null +++ b/codepipeline-demo/app/config/buildspec.yml @@ -0,0 +1,21 @@ +version: 0.2 + +phases: + pre_build: + commands: + - $(aws ecr get-login --no-include-email) + build: + commands: + - docker build -t $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$CODEBUILD_RESOLVED_SOURCE_VERSION . + post_build: + commands: + # push + - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$CODEBUILD_RESOLVED_SOURCE_VERSION + # create new task definition + - ./create-new-task-def.sh $IMAGE_REPO_NAME + - ./set-network-configuration.sh $IMAGE_REPO_NAME + +artifacts: + files: + - 'appspec.yaml' + - 'taskdef.json' diff --git a/codepipeline-demo/app/scripts/create-new-task-def.sh b/codepipeline-demo/app/scripts/create-new-task-def.sh new file mode 100755 index 0000000..8d4ede2 --- /dev/null +++ b/codepipeline-demo/app/scripts/create-new-task-def.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -ex +SERVICE_NAME=$1 +IMAGE_URI="$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$CODEBUILD_RESOLVED_SOURCE_VERSION" +TASKDEF_NAME=$(aws ecs list-task-definitions |jq --raw-output '.taskDefinitionArns[] | select(contains("'${SERVICE_NAME}'"))' | tail -n1) +CURRENT_TASKDEF=`aws ecs describe-task-definition --task-definition $TASKDEF_NAME` +CURRENT_TASKDEF_CONTAINERDEF=`echo $CURRENT_TASKDEF| jq --raw-output ".taskDefinition.containerDefinitions"` +TASKDEF_ROLE_ARN=`echo $CURRENT_TASKDEF| jq --raw-output ".taskDefinition.taskRoleArn"` +EXECUTION_ROLE_ARN=`echo $CURRENT_TASKDEF| jq --raw-output ".taskDefinition.executionRoleArn"` +TASKDEF=`echo $CURRENT_TASKDEF_CONTAINERDEF | jq ' [ .[] | .image = "'${IMAGE_URI}'" ]'` +CPU=$(echo $CURRENT_TASKDEF |jq -r '.taskDefinition.cpu') +MEMORY=$(echo $CURRENT_TASKDEF |jq -r '.taskDefinition.memory') +NETWORK_MODE=$(echo $CURRENT_TASKDEF |jq -r '.taskDefinition.networkMode') +REQUIRES_COMPATIBILITIES=$(echo $CURRENT_TASKDEF |jq '.taskDefinition.requiresCompatibilities[]' | tr '\n' ',' | sed 's/.$//') +echo '{"family": "'${SERVICE_NAME}'", "taskRoleArn": "'${TASKDEF_ROLE_ARN}'", "executionRoleArn": "'${EXECUTION_ROLE_ARN}'", "containerDefinitions": '$TASKDEF', "cpu": "'$CPU'", "memory": "'$MEMORY'", "requiresCompatibilities": ['$REQUIRES_COMPATIBILITIES'], "networkMode": "'${NETWORK_MODE}'" }' > taskdef.json +#aws ecs register-task-definition --cli-input-json file://task-def-template.json.new > task-def-template.json.out +#NEW_TASKDEF_ARN=`cat task-def-template.json.out |jq -r '.taskDefinition.taskDefinitionArn'` +sed -i 's#$TASKDEF#'$NEW_TASKDEF_ARN'#' appspec.yaml diff --git a/codepipeline-demo/app/scripts/set-network-configuration.sh b/codepipeline-demo/app/scripts/set-network-configuration.sh new file mode 100755 index 0000000..2d87a08 --- /dev/null +++ b/codepipeline-demo/app/scripts/set-network-configuration.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# set subnet, security groups, public ip +SERVICE_NAME=$1 +CLUSTER_NAME=$1 +NETWORK_CONFIGURATION=$(aws ecs describe-services --services $SERVICE_NAME --cluster $CLUSTER_NAME |jq -r '.services[].networkConfiguration') +SUBNETS=$(echo $NETWORK_CONFIGURATION |jq '.awsvpcConfiguration.subnets[]' | tr '\n' ',' | sed 's/.$//') +SECURITY_GROUPS=$(echo $NETWORK_CONFIGURATION |jq '.awsvpcConfiguration.securityGroups[]' | tr '\n' ',' | sed 's/.$//') +PUBLIC_IP=$(echo $NETWORK_CONFIGURATION |jq '.awsvpcConfiguration.assignPublicIp') +sed -i 's/$SUBNETS/'$SUBNETS'/' appspec.yaml +sed -i 's/$SECURITY_GROUPS/'$SECURITY_GROUPS'/' appspec.yaml +sed -i 's/$PUBLIC_IP/'$PUBLIC_IP'/' appspec.yaml diff --git a/codepipeline-demo/codedeploy.tf b/codepipeline-demo/codedeploy.tf new file mode 100644 index 0000000..a94eb8e --- /dev/null +++ b/codepipeline-demo/codedeploy.tf @@ -0,0 +1,53 @@ +resource "aws_codedeploy_app" "demo" { + compute_platform = "ECS" + name = "demo" +} + +resource "aws_codedeploy_deployment_group" "demo" { + app_name = aws_codedeploy_app.demo.name + deployment_config_name = "CodeDeployDefault.ECSAllAtOnce" + deployment_group_name = "demo" + service_role_arn = aws_iam_role.demo-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" + termination_wait_time_in_minutes = 5 + } + } + + deployment_style { + deployment_option = "WITH_TRAFFIC_CONTROL" + deployment_type = "BLUE_GREEN" + } + + ecs_service { + cluster_name = aws_ecs_cluster.demo.name + service_name = aws_ecs_service.demo.name + } + + load_balancer_info { + target_group_pair_info { + prod_traffic_route { + listener_arns = [aws_lb_listener.demo.arn] + } + + target_group { + name = aws_lb_target_group.demo-blue.name + } + + target_group { + name = aws_lb_target_group.demo-green.name + } + } + } +} diff --git a/codepipeline-demo/codepipeline.tf b/codepipeline-demo/codepipeline.tf index 872c335..ff44604 100644 --- a/codepipeline-demo/codepipeline.tf +++ b/codepipeline-demo/codepipeline.tf @@ -26,8 +26,8 @@ resource "aws_codepipeline" "demo" { output_artifacts = ["demo-docker-source"] configuration = { - RepositoryName = aws_codecommit_repository.demo.repository_name - BranchName = "master" + RepositoryName = aws_codecommit_repository.demo.repository_name + BranchName = "master" } } } @@ -54,19 +54,19 @@ resource "aws_codepipeline" "demo" { name = "Deploy" action { - name = "Deploy" + name = "DeployToECS" category = "Deploy" owner = "AWS" - provider = "ECS" + provider = "CodeDeployToECS" input_artifacts = ["demo-docker-build"] version = "1" configuration = { - ClusterName = "demo" # name of cluster - ServiceName = "demo" # name of service + ApplicationName = aws_codedeploy_app.demo.name + DeploymentGroupName = aws_codedeploy_deployment_group.demo.deployment_group_name + TaskDefinitionTemplateArtifact = "demo-docker-build" + AppSpecTemplateArtifact = "demo-docker-build" } - - role_arn = aws_iam_role.demo-codepipeline.arn } } } diff --git a/codepipeline-demo/fargate-service.tf b/codepipeline-demo/fargate-service.tf index 38eb16a..23ca755 100644 --- a/codepipeline-demo/fargate-service.tf +++ b/codepipeline-demo/fargate-service.tf @@ -1,10 +1,10 @@ resource "aws_ecs_task_definition" "demo" { - family = "demo" + family = "demo" execution_role_arn = aws_iam_role.ecs-task-execution-role.arn - task_role_arn = aws_iam_role.ecs-demo-task-role.arn - cpu = 256 - memory = 512 - network_mode = "awsvpc" + task_role_arn = aws_iam_role.ecs-demo-task-role.arn + cpu = 256 + memory = 512 + network_mode = "awsvpc" requires_compatibilities = [ "FARGATE" ] @@ -39,42 +39,60 @@ DEFINITION } resource "aws_ecs_service" "demo" { - name = "demo" - cluster = aws_ecs_cluster.demo.id - desired_count = 1 + name = "demo" + cluster = aws_ecs_cluster.demo.id + desired_count = 1 task_definition = aws_ecs_task_definition.demo.arn - launch_type = "FARGATE" + launch_type = "FARGATE" + depends_on = [aws_lb_listener.demo] + + deployment_controller { + type = "CODE_DEPLOY" + } network_configuration { - subnets = slice(module.vpc.public_subnets, 1, 2) - security_groups = [aws_security_group.ecs-demo.id] + subnets = slice(module.vpc.public_subnets, 1, 2) + security_groups = [aws_security_group.ecs-demo.id] assign_public_ip = true } load_balancer { - target_group_arn = aws_lb_target_group.demo.id - container_name = "demo" - container_port = "3000" + target_group_arn = aws_lb_target_group.demo-blue.id + container_name = "demo" + container_port = "3000" } lifecycle { ignore_changes = [ - task_definition + task_definition, + load_balancer ] } } # security group resource "aws_security_group" "ecs-demo" { - name = "ECS demo" - vpc_id = module.vpc.vpc_id + name = "ECS demo" + vpc_id = module.vpc.vpc_id description = "ECS demo" + ingress { + from_port = 3000 + to_port = 3000 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + egress { from_port = 0 - to_port = 0 - protocol = "-1" + to_port = 0 + protocol = "-1" cidr_blocks = [ "0.0.0.0/0" ] } } + +# logs +resource "aws_cloudwatch_log_group" "demo" { + name = "demo" +} diff --git a/codepipeline-demo/iam-codebuild.tf b/codepipeline-demo/iam-codebuild.tf index d5b7034..ae2de53 100644 --- a/codepipeline-demo/iam-codebuild.tf +++ b/codepipeline-demo/iam-codebuild.tf @@ -114,6 +114,17 @@ resource "aws_iam_role_policy" "demo-codebuild" { "*" ] }, + { + "Sid": "ECS", + "Effect": "Allow", + "Action": [ + "ecs:List*", + "ecs:Describe*" + ], + "Resource": [ + "*" + ] + }, { "Effect": "Allow", "Action": [ diff --git a/codepipeline-demo/iam-codedeploy.tf b/codepipeline-demo/iam-codedeploy.tf new file mode 100644 index 0000000..6c0f3a3 --- /dev/null +++ b/codepipeline-demo/iam-codedeploy.tf @@ -0,0 +1,86 @@ +resource "aws_iam_role" "demo-codedeploy" { + name = "demo-codedeploy" + + assume_role_policy = <