Рубрики
Uncategorized

CI/CD Pipeline для Amazon ECS/Fargate с Terraform

ОПИСАНИЕ В этом посте я собираюсь объяснить, как построить инфраструктуру на AWS с … Tagged с Terraform, AWS, DevOps, Cloud.

ОПИСАНИЕ

В этом посте я собираюсь объяснить, как построить инфраструктуру на AWS с Terraform для реализации конвейера CI/CD для ECS/Fargate. Архитектура состоит из VPC с двумя публичными подсетями в различных зонах доступности. Желаемые задачи 2, и каждая задача развернута в каждой публичной подсети с FARGATE, и каждая задача принадлежит одному и тому же сервису ECS. Балансировщик нагрузки приложения используется для сбалансировки нагрузки между двумя задачами. В этом случае основная цель состоит в том, чтобы реализовать контейнер Docker, который содержит простой HTTP -сервер, построенный с Golang. Этот HTTP -сервер позволяет получить частную IP -адреса каждой задачи. Если я введу новые изменения в репозиторий CodeCommit, CodePipeline обнаруживает эти изменения, запускает трубопровод и создает новое изображение Docker, а затем развертывает его в службе ECS для обновления задач.

АРХИТЕКТУРА

РЕСУРСЫ

https://github.com/erozedguy/CICD-Pipeline-for-Amazon-ECS-Fargate

Шаги

Шаг 01 — Создайте роль IAM и учетные данные CodeCommit

  • Создайте роль услуги для задачи службы Elastic Container ( Позволяет ECS -задачам вызовать службы AWS от вашего имени.)

  • Прикрепить Awscodecommitpoweruser Политика моему ПОЛЬЗОВАТЕЛЬ

  • Генерировать HTTPS GIT учетные данные для AWS Codecommit к клон , push , тянуть к Кодекс -репозиторий

Шаг 02: Сценарии терраформ для строительства инфраструктуры

Поставщики

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.51"
    }
  }
}
provider "aws" {
  profile = "default"
  region  = "us-east-1"
}

VPC

VPC Script имеет VPC , Подсети и Интернет -шлюз Ресурсы.

resource "aws_vpc" "ecs-vpc" {
  cidr_block = "${var.cidr}"

  tags = {
    Name = "ecs-vpc"
  }
}

# PUBLIC SUBNETS
resource "aws_subnet" "pub-subnets" {
  count                   = length(var.azs)
  vpc_id                  = "${aws_vpc.ecs-vpc.id}"
  availability_zone       = "${var.azs[count.index]}"
  cidr_block              = "${var.subnets-ip[count.index]}"
  map_public_ip_on_launch = true

  tags = {
    Name = "pub-subnets"
  }
}

# INTERNET GATEWAY
resource "aws_internet_gateway" "i-gateway" {
  vpc_id = "${aws_vpc.ecs-vpc.id}"

  tags = {
    Name = "ecs-igtw"
  }
}

Переменные для VPC

variable "cidr" {
  type    = string
  default = "145.0.0.0/16"
}

variable "azs" {
  type = list(string)
  default = [
    "us-east-1a",
    "us-east-1b"
  ]
}

variable "subnets-ip" {
  type = list(string)
  default = [
    "145.0.1.0/24",
    "145.0.2.0/24"
  ]

}

IAM Роли и политика

Для Кодовая строительство необходим для создания роли и политики IAM, чтобы обеспечить доступ к ECR Чтобы толкнуть и тянуть Docker Images в репозитории ECR. Кроме того, необходимо разрешение на доступ к ведро S3 для хранения Артефакты Анкет

resource "aws_iam_role" "codebuild-role" {
  name = "codebuild-role"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "codebuild.amazonaws.com"
        }
      },
    ]
  })
}

resource "aws_iam_role_policy" "codebuild-policy" {
  role = "${aws_iam_role.codebuild-role.name}"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action   = ["codecommit:GitPull"]
        Effect   = "Allow"
        Resource = "*"
      },
      {
        Action = [
          "ecr:BatchCheckLayerAvailability",
          "ecr:GetDownloadUrlForLayer",
          "ecr:BatchGetImage",
          "ecr:CompleteLayerUpload",
          "ecr:GetAuthorizationToken",
          "ecr:InitiateLayerUpload",
          "ecr:PutImage",
        "ecr:UploadLayerPart"]
        Effect   = "Allow"
        Resource = "*"
      },
      {
        Action = [
          "logs:CreateLogGroup",
          "logs:CreateLogStream",
        "logs:PutLogEvents"]
        Effect   = "Allow"
        Resource = "*"
      },
      {
        Action = [
          "s3:PutObject",
          "s3:GetObject",
          "s3:GetObjectVersion",
          "s3:GetBucketAcl",
        "s3:GetBucketLocation"]
        Effect   = "Allow"
        Resource = "*"
      }
    ]


  })

}

Маршрутные столы

Одиночный Таблица маршрутов Для обеих публичных подсет

# TABLE FOR PUBLIC SUBNETS
resource "aws_route_table" "pub-table" {
  vpc_id = "${aws_vpc.ecs-vpc.id}"
}

resource "aws_route" "pub-route" {
  route_table_id         = "${aws_route_table.pub-table.id}"
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = "${aws_internet_gateway.i-gateway.id}"
}

resource "aws_route_table_association" "as-pub" {
  count          = length(var.azs)
  route_table_id = "${aws_route_table.pub-table.id}"
  subnet_id      = "${aws_subnet.pub-subnets[count.index].id}"
}

Группы безопасности

Первая группа секунды для Служба ECS

resource "aws_security_group" "sg1" {
  name        = "golang-server"
  description = "Port 5000"
  vpc_id      = aws_vpc.ecs-vpc.id

  ingress {
    description      = "Allow Port 5000"
    from_port        = 5000
    to_port          = 5000
    protocol         = "tcp"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }

  egress {
    description = "Allow all ip and ports outboun"
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

Вторая секундовая группа для Приложение нагрузка балансировщика

resource "aws_security_group" "sg2" {
  name        = "golang-server-alb"
  description = "Port 80"
  vpc_id      = aws_vpc.ecs-vpc.id

  ingress {
    description      = "Allow Port 80"
    from_port        = 80
    to_port          = 80
    protocol         = "tcp"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }

  egress {
    description = "Allow all ip and ports outboun"
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

Приложение нагрузка балансировщика

resource "aws_lb" "app-lb" {
  name               = "app-lb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.sg2.id]
  subnets            = ["${aws_subnet.pub-subnets[0].id}", "${aws_subnet.pub-subnets[1].id}"]

}

Порт № 5000 используется в Целевая группа Потому что этот порт используется для контейнера

resource "aws_lb_target_group" "tg-group" {
  name        = "tg-group"
  port        = "5000"
  protocol    = "HTTP"
  vpc_id      = "${aws_vpc.ecs-vpc.id}"
  target_type = "ip"

}

Порт № 80 используется для Слушатель

resource "aws_lb_listener" "lb-listener" {
  load_balancer_arn = "${aws_lb.app-lb.arn}"
  port              = "80"
  protocol          = "HTTP"

  default_action {
    type             = "forward"
    target_group_arn = "${aws_lb_target_group.tg-group.arn}"
  }
}

ECS и ECR

Репозиторий ECR
resource "aws_ecr_repository" "ecr-repo" {
  name = "ecr-repo"
}
ECS Cluster
resource "aws_ecs_cluster" "ecs-cluster" {
  name = "clusterDev"
}
Определение задачи
  • В этой части важно указать Контейнерпорт
  • Создайте Env var: Export tf_var_uri_repo = .dkr.ecr.
resource "aws_ecs_task_definition" "task" {
  family                   = "HTTPserver"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = 256
  memory                   = 512
  execution_role_arn       = data.aws_iam_role.ecs-task.arn

  container_definitions = jsonencode([
    {
      name   = "golang-container"
      image  = "${var.uri_repo}:latest" #URI
      cpu    = 256
      memory = 512
      portMappings = [
        {
          containerPort = 5000
        }
      ]
    }
  ])
}
Служба ECS

Укажите балансировщик нагрузки блокировать

resource "aws_ecs_service" "svc" {
  name            = "golang-Service"
  cluster         = "${aws_ecs_cluster.ecs-cluster.id}"
  task_definition = "${aws_ecs_task_definition.task.id}"
  desired_count   = 2
  launch_type     = "FARGATE"


  network_configuration {
    subnets          = ["${aws_subnet.pub-subnets[0].id}", "${aws_subnet.pub-subnets[1].id}"]
    security_groups  = ["${aws_security_group.sg1.id}"]
    assign_public_ip = true
  }

  load_balancer {
    target_group_arn = "${aws_lb_target_group.tg-group.arn}"
    container_name   = "golang-container"
    container_port   = "5000"
  }
}

CI/CD Pipeline

Кодекс -репозиторий
resource "aws_codecommit_repository" "repo" {
  repository_name = var.repo_name
}
Проект CodeBuild
resource "aws_codebuild_project" "repo-project" {
  name         = "${var.build_project}"
  service_role = "${aws_iam_role.codebuild-role.arn}"

  artifacts {
    type = "NO_ARTIFACTS"
  }

  source {
    type     = "CODECOMMIT"
    location = "${aws_codecommit_repository.repo.clone_url_http}"
  }

  environment {
    compute_type    = "BUILD_GENERAL1_SMALL"
    image           = "aws/codebuild/standard:5.0"
    type            = "LINUX_CONTAINER"
    privileged_mode = true
  }
}
BUILDSPEC.YML
  • Этот файл очень важен для создания изображения Docker и привлечь его к ECR репозиторий
  • Для обновления службы ECS важно указать СОДЕРЖАНИЕ ИМЯ и Imageuri В файле JSON с именем Imagedefinitions.json . Этот файл является артефактом
  • Этот файл должен быть в репозитории CODECOMMIT
version: 0.2

phases:
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - echo $AWS_DEFAULT_REGION
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin 940401905947.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
      - REPOSITORY_NAME="ecr-repo"      
      - REPOSITORY_URI=940401905947.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$REPOSITORY_NAME
      - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
      - IMAGE_TAG=${COMMIT_HASH:=latest}
  build:
    commands:
      - echo Building the Docker image...
      - docker build -t $REPOSITORY_NAME:latest .
      - docker tag $REPOSITORY_NAME:latest $REPOSITORY_URI:latest
      - docker tag $REPOSITORY_NAME:latest $REPOSITORY_URI:$IMAGE_TAG
  post_build:
    commands:
      - docker push $REPOSITORY_URI:latest
      - docker push $REPOSITORY_URI:$IMAGE_TAG
      - printf '[{"name":"golang-container","imageUri":"%s"}]' $REPOSITORY_URI:$IMAGE_TAG > imagedefinitions.json

artifacts:
  files: imagedefinitions.json
S3 Bucket для хранения артефактов
resource "aws_s3_bucket" "bucket-artifact" {
  bucket = "eroz-artifactory-bucket"
  acl    = "private"
}
Codepipeline

Укажите Источник , Сборка В Развернуть Стадии

Примечание : кодировать стадии Проверьте официальную документацию https://docs.aws.amazon.com/codepipeline/latest/userguide/action-reference.html

resource "aws_codepipeline" "pipeline" {
  name     = "pipeline"
  role_arn = "${data.aws_iam_role.pipeline_role.arn}"

  artifact_store {
    location = "${aws_s3_bucket.bucket-artifact.bucket}"
    type     = "S3"
  }
  # SOURCE
  stage {
    name = "Source"
    action {
      name             = "Source"
      category         = "Source"
      owner            = "AWS"
      provider         = "CodeCommit"
      version          = "1"
      output_artifacts = ["source_output"]

      configuration = {
        RepositoryName = "${var.repo_name}"
        BranchName     = "${var.branch_name}"
      }
    }
  }
  # BUILD
  stage {
    name = "Build"
    action {
      name             = "Build"
      category         = "Build"
      owner            = "AWS"
      provider         = "CodeBuild"
      version          = "1"
      input_artifacts  = ["source_output"]
      output_artifacts = ["build_output"]

      configuration = {
        ProjectName = "${var.build_project}"
      }
    }
  }
  # DEPLOY
  stage {
    name = "Deploy"
    action {
      name            = "Deploy"
      category        = "Deploy"
      owner           = "AWS"
      provider        = "ECS"
      version         = "1"
      input_artifacts = ["build_output"]

      configuration = {
        ClusterName = "clusterDev"
        ServiceName = "golang-Service"
        FileName    = "imagedefinitions.json"
      }
    }
  }
}

ДАННЫЕ

Этот раздел предназначен для использования созданных ролей IAM

data "aws_iam_role" "pipeline_role" {
  name = "codepipeline-role"
}

data "aws_iam_role" "ecs-task" {
  name = "ecsTaskExecutionRole"
}

Выходы

Чтобы получить DNS ALB и URL -адрес кодового репозитория

output "repo_url" {
  value = aws_codecommit_repository.repo.clone_url_http
}

output "alb_dns" {
  value = aws_lb.app-lb.dns_name
}

Дополнительные переменные

variable "repo_name" {
  type    = string
  default = "dev-repo"
}

variable "branch_name" {
  type    = string
  default = "master"
}

variable "build_project" {
  type    = string
  default = "dev-build-repo"
}

variable "uri_repo" {
  type = string
  #The URI_REPO value is in a TF_VAR in my PC
}

Шаг 03: HTTP Simple Server с Golang

Этот код полезен для получения частного IP -адресов ECS -задач

package main

import (
    "fmt"
    "log"
    "net"
    "net/http"
)

func main() {
    log.Print("HTTPserver: Enter main()")
    http.HandleFunc("/", handler)
    log.Fatal(http.ListenAndServe("0.0.0.0:5000", nil))
}

// printing request headers/params
func handler(w http.ResponseWriter, r *http.Request) {

    log.Print("request from address: %q\n", r.RemoteAddr)
    fmt.Fprintf(w, "%s %s %s\n", r.Method, r.URL, r.Proto)

    fmt.Fprintf(w, "Host = %q\n", r.Host)
    fmt.Fprintf(w, "RemoteAddr = %q\n", r.RemoteAddr)
    if err := r.ParseForm(); err != nil {
        log.Print(err)
    }
    for k, v := range r.Form {
        fmt.Fprintf(w, "Form[%q] = %q\n", k, v)
    }
    fmt.Fprintf(w, "\n===> local IP: %q\n\n", GetOutboundIP())
}

func GetOutboundIP() net.IP {
    conn, err := net.Dial("udp", "8.8.8.8:80")
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

    localAddr := conn.LocalAddr().(*net.UDPAddr)

    return localAddr.IP
}

Шаг 04: Dockerfile

  • Этот DockerFile создайте изображение с HTTP -сервером с Golang
  • Этот файл должен быть в репозитории CODECOMMIT
FROM golang:alpine AS builder

ENV GO111MODULE=on \
    CGO_ENABLED=0 \
    GOOS=linux \
    GOARCH=amd64
WORKDIR /build
COPY ./HTTPserver.go .

# Build the application
RUN go build -o HTTPserver ./HTTPserver.go

WORKDIR /dist
RUN cp /build/HTTPserver .

# Build a small image
FROM scratch
COPY --from=builder /dist/HTTPserver /
EXPOSE 5000
ENTRYPOINT ["/HTTPserver"]

Шаг 05: Создать TF_VAR

Шаг 06: Создайте инфраструктуру

Команды

  • Terraform Init
  • Terraform Palidate
  • Терраформский план
  • Terraform Apply -Aut -Aprove

Когда творение закончено, мы получаем выходы

Шаг 07: Загрузите DockerFile, код и сборка файлов в репозиторий CodeCommit

  • Клонировать репозиторий

  • Копия BuildSpect.yml , Dockerfile и Golang Code к клонированной папке хранилища и Тогда сделай совершить

  • Git push к репозиторию кодекса

Шаг 08: Проверьте трубопровод

  • Когда стадия «сборка» будет выполнена, проверьте изображение Docker в репозитории ECR

Шаг 09: Проверьте службу ECS

Когда будет выполнена этап «развертывания», проверьте задачи в службе ECS

Шаг 10: Проверьте целевую группу

Шаг 11: Проверьте работу балансировщика нагрузки приложения

Последний шаг: удалить инфраструктуру

Terraform уничтожит -увеса

Оригинал: «https://dev.to/erozedguy/ci-cd-pipeline-for-amazon-ecs-fargate-with-terraform-33na»