アマゾンECS/ファルゲートのためのCi/CDパイプライン


説明


このポストでは、私はECS/FargateのためにCI/CDパイプラインを実装するためにTerraformでAWSの上で基盤を構築する方法を説明するつもりです.
アーキテクチャは異なる可用性ゾーンの2公共サブネットを持つVPCから成ります.望ましいタスクは2であり、各タスクはFargateと各パブリックサブネットで展開され、各タスクは同じECSサービスに属します.
アプリケーションロードバランサは、2つのタスク間の負荷をバランスさせるために使用されます.
この場合、主な目標はGolangで構築された簡単なHTTPサーバを含むDockerコンテナを実装することです.このHTTPサーバーを使用すると、各タスクのプライベートIPを取得することができます.
CODERMITリポジトリに新しい変更をプッシュすると、CodePipelineはそれらの変更を検出し、パイプラインをトリガし、新しいDockerイメージを作成し、タスクを更新するためにECSサービスに配備します.

建築



資源


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

ステップ


ステップ01 - IAMロールを作成し、CodeMmit資格情報を作成する

  • エラスティックコンテナサービスタスクのサービスロールを作成する
    ECSタスクを使用してAWSサービスを呼び出すことができます.
  • 取り付けAWSCodeCommitPowerUser 私の政策USER
  • 生成するHTTPS Git credentials for AWS CodeCommit to clone , push , pullCodeCommit Repository

  • ステップ02:インフラストラクチャを構築するTerraformスクリプト


    プロバイダ


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

    VPC


    The vpc script has VPC , SUBNETS and INTERNET GATEWAY リソース.
    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"
      ]
    
    }
    

    役割と方針


    のためにCodeBuild へのアクセスを許可するIAMロール&ポリシーを作成する必要がありますECR 押すDocker images ECRリポジトリで.また、S 3のバケツにアクセスする許可が必要ですartifacts .
    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 = "*"
          }
        ]
    
    
      })
    
    }
    

    路線表


    シングルRoute Table 両方の公共サブネット
    # 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}"
    }
    
    

    セキュリティグループ


    最初のSECグループはECS Service
    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"]
      }
    }
    
    
    番目のSECグループはApplication Load Balancer
    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}"]
    
    }
    
    ポート・・・5000Target Group そのポートはコンテナに使われているので
    resource "aws_lb_target_group" "tg-group" {
      name        = "tg-group"
      port        = "5000"
      protocol    = "HTTP"
      vpc_id      = "${aws_vpc.ecs-vpc.id}"
      target_type = "ip"
    
    }
    
    ポート・・・80Listener
    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クラスター
    resource "aws_ecs_cluster" "ecs-cluster" {
      name = "clusterDev"
    }
    

    タスク定義
  • この部分では、containerPort
  • env varを作成します.export TF_VAR_uri_repo = <ID_ACCOUNT>.dkr.ecr.<REGION>.amazonaws.com/<ECR_REPOSITORY_NAME>
  • 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サービス
    指定するload balancer ブロック
    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パイプライン



    CodeMmitリポジトリ
    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
      }
    }
    

    ビルド気象研
  • このファイルはDockerイメージを作成し、ECRリポジトリに引き込むことが非常に重要です
  • ECSサービスを更新するにはcontainerName and imageUri 名前のJSONファイルでimagedefinitions.json . このファイルはアーティファクトです
  • このファイルはCODELMITリポジトリになければなりません
  • 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
    

    s 3を保存するバケツartifacts
    resource "aws_s3_bucket" "bucket-artifact" {
      bucket = "eroz-artifactory-bucket"
      acl    = "private"
    }
    

    コペイライン
    指定Source , Build , Deploy ステージ
    注:stages 公式文書をチェックする
    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"
    }
    

    出力


    を取得するには
    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シンプルサーバー


    このコードはECSタスクのプライベートIPを取得するのに便利です
    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

  • このdockfileはゴング付きHTTPサーバでイメージを作成する
  • このファイルはCODELMITリポジトリになければなりません
  • 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 : TfHunvarを作成する



    ステップ06 :インフラストラクチャの作成


    コマンド
  • terraform init
  • terraform validate
  • terraform plan
  • terraform apply -auto-approve
  • 創造が終わるとき、我々は出力を得ます

    ステップ07 : CodeCommitリポジトリにDockerFile、コード、およびbuildSpectファイルをアップロードする

  • リポジトリをクローンする
  • コピーbuildspect.yml , Dockerfile and Golang Code クローン化したリポジトリフォルダにcommit
  • gitはcodedemitリポジトリにプッシュする
  • ステップ08:パイプラインチェック


  • 「ビルド」ステージが終了すると、ECRリポジトリ内のDocker画像をチェックします


  • ステップ09 : ECSサービスのチェック


    「展開」ステージが完了すると、ECSサービスのタスクをチェックします



    ステップ10 :ターゲットグループをチェックする



    ステップ11:アプリケーション負荷バランサの操作をチェックする




    インフラストラクチャの削除

    terraform destroy -auto-approve