Codebuild 연동하기

Codebuild with jenkins!

Jenkins와 Codebuild

Jenkins와 Codebuild를 연동하게 되면, 빌드 또는 배포 job 은 jenkins에서 생성하고 실제 작업은 Codebuild를 통해서 진행하실 수 있습니다. Jenkins에서 Job이 실행되면 AWS 상에 독립적인 codebuild job이 생성 됩니다.

장점

  • 각 Job을 독립적인 환경에서 빌드/배포할 수 있습니다.

  • AWS Codebuild 특성상 특정 VPC내에서 실행하실 수 있기 때문에, 접근제어 또는 다른 서비스와의 연동 시 용이합니다.

  • Codebuild의 모든 로그를 Amazon CloudWatch Logs를 통해서 보실 수 있고, 기존의 빌드 및 배포 이력을 손쉽게 찾아보실 수 있습니다.

본 실습에서는 artp(prod) 계정에서 동일 환경에 배포해보겠습니다.

사전 준비 사항

  • Jenkins와 Codebuild에 연동할 Github 토큰

  • Jenkins에서 사용할 AWS IAM User 및 Credentials

    • AWS_ACCESS_KEY_ID

    • AWS_SECRET_ACCESS_KEY

Github 토큰

  • Github 에서 Settings --> Developer settings --> Personal access tokens 를 들어갑니다.

  • Generate new token 을 클릭합니다.

  • 이름을 넣고 reporead:org권한을 추가한 후에 토큰을 발급합니다.

  • 발급받은 토큰은 임시로 안전한 곳에 저장해두시기 바랍니다.

IAM User 생성

  • Jenkins를 배포할 계정의 IAM directory를 찾아서 들어갑니다.

    • 예시 : terraform/iam/art-prod

    • 없으신 경우에는 이전 IAM 환경 구성을 참조하셔서 먼저 환경 세팅을 해주시기 바랍니다.

  • GetLogEventsCodebuild:*에 관한 권한은 반드시 필요합니다. 다른 권한은 필요에 따라 추가/삭제하셔도 됩니다.

  • 아래와 같이 테라폼 코드를 작성하시고 나서, terraform apply 를 통해 배포하시면 콘솔에서 유저를 찾으실 수 있습니다.

# Jenkins User
resource "aws_iam_user" "jenkins_codebuild" {
  name = "jenkins-codebuild"
}

# Permissions that jenkins needs to create codebuild job
# You can change s3 bucket below if you have any bucket that jenkins use for retrieving/uploading artifact
resource "aws_iam_user_policy" "jenkins_codebuild" {
  name   = "jenkins-codebuild"
  user   = aws_iam_user.jenkins_codebuild.name
  policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Resource": ["arn:aws:logs:*:*:log-group:/aws/codebuild/*"],
            "Action": ["logs:GetLogEvents"]
        },
        {
            "Effect": "Allow",
            "Resource": ["arn:aws:s3:::art-deploy"],
            "Action": ["s3:GetBucketVersioning"]
        },
        {
            "Effect": "Allow",
            "Resource": ["arn:aws:s3:::art-deploy/*"],
            "Action": ["s3:PutObject"]
        },
        {
            "Effect": "Allow",
            "Resource": ["arn:aws:s3:::art-deploy/*"],
            "Action": ["s3:GetObject"]
        },
        {
            "Effect": "Allow",
            "Resource": ["arn:aws:codebuild:*:*:*"],
            "Action": [
                "codebuild:StartBuild",
                "codebuild:BatchGetBuilds",
                "codebuild:BatchGetProjects",
                "codebuild:StopBuild"
            ]
        }
	]
  }
EOF

}

생성이 완료되었으면 아래와 같이 Access Key를 발급 받습니다. 발급받으신 키는 반드시 안전한 곳에 보관하시기 바랍니다.

CodeBuild 생성

이제 연동할 Codebuild 프로젝트를 생성하도록 하겠습니다. Codebuild 프로젝트를 위해서는 아래의 리소스가 필요합니다.

  • Codebuild에서 사용할 IAM 역할

  • Codebuild의 기본 베이스가 되는 Docker image

  • Codebuild에서 사용할 보안그룹

  • Codebuild 프로젝트

IAM 역할 생성

  • Codebuild에서 사용할 역할이므로, 실제 빌드/배포에 필요한 권한을 가지고 있어야 합니다.

  • Codebuild를 생성할 계정의 IAM 디렉토리로 가서 생성을 진행합니다. (terraform/iam/art-prod)

vim terraform/iam/art-prod/codebuild-deployment.tf
resource "aws_iam_role" "codebuild_deployment" {
  name               = "codebuild-deployment"
  path               = "/service-role/" assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "codebuild.amazonaws.com"
      },

      "Action": "sts:AssumeRole"

    }
  ]
}
EOF
}

resource "aws_iam_role_policy" "codebuild_deployment_operation" {
  name   = "codebuild-deployment-operation-access"
  role   = aws_iam_role.codebuild_deployment.id
  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DeploymentSecurityGroupAccess",
      "Action": [
        "ec2:CreateSecurityGroup",
        "ec2:DescribeSecurityGroups",
        "ec2:AuthorizeSecurityGroupIngress",
        "ec2:RevokeSecurityGroupIngress"
      ],
      "Effect": "Allow",
      "Resource": ["*"]
    },
    {
      "Sid": "DeploymentAMIAccess",
      "Action": [
        "ec2:RegisterImage",
        "ec2:DescribeImages"
      ],
      "Effect": "Allow",
      "Resource": ["*"]
    },
    {
      "Sid": "DeploymentSnapshotAccess",
      "Action": [
        "ec2:CreateSnapshot",
        "ec2:DeleteSnaphot",
        "ec2:DescribeSnapshots"
      ],
      "Effect": "Allow",
      "Resource": ["*"]
    },
    {
      "Sid": "DeploymentInstanceAccess",
      "Action": [
        "ec2:RunInstances",
        "ec2:StartInstances",
        "ec2:StopInstances",
        "ec2:RebootInstances",
        "ec2:TerminateInstances",
        "ec2:DescribeInstances",
        "ec2:CreateTags",
        "ec2:DescribeTags",
        "ec2:ModifyInstanceAttribute"
      ],
      "Effect": "Allow",
      "Resource": ["*"]
    },
    {
      "Sid": "DeploymentKeyPairAccess",
      "Action": [
        "ec2:DescribeKeyPairs"
      ],
      "Effect": "Allow",
      "Resource": ["*"]
    },
    {
      "Sid": "LaunchTemplates",
      "Action": [
        "ec2:DeleteLaunchTemplate",
        "ec2:CreateLaunchTemplate",
        "ec2:GetLaunchTemplateData",
        "ec2:DescribeLaunchTemplates",
        "ec2:DescribeLaunchTemplateVersions",
        "ec2:ModifyLaunchTemplate",
        "ec2:DeleteLaunchTemplateVersions",
        "ec2:CreateLaunchTemplateVersion"
      ],
      "Effect": "Allow",
      "Resource": ["*"]
    },
    {
      "Sid": "DeploymentVolumeAccess",
      "Action": [
        "ec2:AttachVolume",
        "ec2:CreateVolume",
        "ec2:DeleteVolume",
        "ec2:DescribeVolume*",
        "ec2:DetachVolume"
      ],
      "Effect": "Allow",
      "Resource": ["*"]
    },
    {
      "Sid": "DeploymentASGAccess",
      "Action": [
        "autoscaling:*"
      ],
      "Effect": "Allow",
      "Resource": ["*"]
    },
    {
      "Sid": "DeploymentVPCAccess",
      "Action": [
        "ec2:CreateNetworkInterface",
        "ec2:DescribeDhcpOptions",
        "ec2:DescribeNetworkInterfaces",
        "ec2:DeleteNetworkInterface",
        "ec2:DescribeSubnets",
        "ec2:DescribeSecurityGroups",
        "ec2:DescribeVpcs"
      ],
      "Effect": "Allow",
      "Resource": ["*"]
    },
    {
      "Effect": "Allow",
      "Action": [
        "ec2:CreateNetworkInterfacePermission"
      ],
        "Resource": "arn:aws:ec2:ap-northeast-2:${var.account_id}:network-interface/*"
    },
    {
      "Sid": "DeploymentIAMAccess",
      "Action": [
        "iam:PassRole"
      ],
      "Effect": "Allow",
      "Resource": ["*"]
    },
    {
      "Sid": "DeploymentELBAccess",
      "Action": [
        "elasticloadbalancing:DescribeLoadBalancerAttributes",
        "elasticloadbalancing:DescribeLoadBalancers",
        "elasticloadbalancing:DescribeTargetGroupAttributes",
        "elasticloadbalancing:DescribeTargetHealth",
        "elasticloadbalancing:DescribeTargetGroups",
        "elasticloadbalancing:DescribeListeners",
        "elasticloadbalancing:DescribeTags",
        "elasticloadbalancing:DescribeRules",
        "elasticloadbalancing:DescribeInstanceHealth"
      ],
      "Effect": "Allow",
      "Resource": ["*"]
    },
    {
      "Sid": "CloudwatchAccess",
      "Action": [
        "cloudwatch:PutMetricAlarm"
      ],
      "Effect": "Allow",
      "Resource": ["*"]
    },
    {
      "Sid": "SSMSendCommand",
      "Action": [
        "ssm:SendCommand",
        "ssm:ListCommandInvocations"
      ],
      "Effect": "Allow",
      "Resource": ["*"]
    }
  ]
}
EOF
}

resource "aws_iam_role_policy" "codebuild_deployment_ecr" {
  name   = "codebuild-deployment-ecr"
  role   = aws_iam_role.codebuild_deployment.id
  policy = <<EOF
{
  "Statement": [
    {
      "Sid": "AllowGetAuthTokenAccess",
      "Effect": "Allow",
      "Action": [
        "ecr:GetAuthorizationToken"
      ],
      "Resource": "*"
    },
    {
      "Sid": "AllowReadECRAccess",
      "Effect": "Allow",
        "Action": [
          "ecr:BatchCheckLayerAvailability",
          "ecr:GetDownloadUrlForLayer",
          "ecr:BatchGetImage",
          "ecr:PutImage",
          "ecr:InitiateLayerUpload",
          "ecr:UploadLayerPart",
          "ecr:CompleteLayerUpload"
        ],
        "Resource": "*"
    }

  ]
}
EOF
}

# If codebuild needs to deploy to another account
# then, it should have assume permission.
#resource "aws_iam_role_policy" "codebuild_deployment_assume_deploy" {
#  name   = "codebuild-deployment-assume-deploy"
#  role   = aws_iam_role.codebuild_deployment.id
#  policy = <<EOF
#{
#  "Statement": [
#    {
#      "Sid": "AllowAnsibleHeadDeployBucketAccess",
#      "Action": [
#        "sts:AssumeRole"
#      ],
#      "Resource": [
#        "arn:aws:iam::<target account id>:role/deployment"
#      ],
#      "Effect": "Allow"
#    }
#  ]
#}
#EOF
#}

# If you want to store secret data to AWS Parameter Store, 
# then you should give permission so that codebuild can retrieve those values for build and deployment
resource "aws_iam_role_policy" "codebuild_deployment_kms" {
  name   = "codebuild-deployment-kms-decryption"
  role   = aws_iam_role.codebuild_deployment.id
  policy = <<EOF
{
  "Statement": [
    {
      "Sid": "AllowSsmParameterAccess",
      "Action": [
        "ssm:GetParameter",
        "ssm:GetParameters"
      ],
      "Effect": "Allow",
      "Resource": [
        "arn:aws:ssm:ap-northeast-2:${var.account_id}:parameter/CodeBuild/*"
      ]
    }
  ]
}
EOF
}

resource "aws_iam_role_policy" "codebuild_deployment_cloudwatch" {
  name   = "codebuild-deployment-cloudwatch"
  role   = aws_iam_role.codebuild_deployment.id
  policy = <<EOF
{
  "Statement": [
    {
      "Sid": "AllowCloudWatchAccess",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": [
        "arn:aws:logs:*:${var.account_id}:log-group:/aws/codebuild/*",
        "arn:aws:logs:*:${var.account_id}:log-group:/aws/codebuild/*:*",
        "arn:aws:logs:*:${var.account_id}:log-group:/*",
        "arn:aws:logs:*:${var.account_id}:log-group:/*:*",
        "arn:aws:logs:*:${var.account_id}:log-group:/*:*:*",
        "arn:aws:logs:*:${var.account_id}:log-group:*:*:*/*"
      ],
      "Effect": "Allow"
    }
  ]
}
EOF
}

resource "aws_iam_instance_profile" "codebuild_deployment" {
  name = "codebuild-deployment-profile"
  role = aws_iam_role.codebuild_deployment.name
}

resource "aws_iam_role_policy_attachment" "codebuild_deployment_attach" {
  role       = aws_iam_role.codebuild_deployment.name
  policy_arn = aws_iam_policy.app_universal.arn
}

output "codebuild_deployment_instance_profile" {
  value = aws_iam_instance_profile.codebuild_deployment.arn
}


output "codebuild_deployment_arn" {
  value = aws_iam_role.codebuild_deployment.arn
}

Docker Image 생성(optional)

  • Codebuild는 컨테이너로 생성되므로 어떤 이미지를 사용하여 Job을 돌릴지 정해주어야 합니다.

  • 본 가이드에서는 필요한 이미지를 빌드해서 ECR에 저장하도록 하겠습니다.

  • ECR 레포지토리를 생성하는 디렉토리에 리소스를 추가합니다.

    • terraform/ecr/art-prod/prod_apnortheast2

    • Policy 에 Account ID는 자신의 계정 번호를 넣어주시면 됩니다.

vim terraform/ecr/art-prod/prod_apnortheast2/art_build.tf
resource "aws_ecr_repository" "art_build" {
  name = "art-build"
}

resource "aws_ecr_repository_policy" "art_build" {
  repository = aws_ecr_repository.art_build.name

  policy = <<EOF
{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Sid": "new policy",
            "Effect": "Allow",
            "Principal": {
              "AWS": [
                "arn:aws:iam::<your account id>:root"
              ]
            },
            "Action": [
                "ecr:GetDownloadUrlForLayer",
                "ecr:BatchGetImage",
                "ecr:BatchCheckLayerAvailability",
                "ecr:DescribeRepositories",
                "ecr:GetRepositoryPolicy",
                "ecr:ListImages"
            ]
        }
    ]
}
EOF
}

  • ECR 레포지토리 생성이 완료되면, 각자 원하는 Docker Image를 생성해서 해당 Repository에 푸시합니다. (샘플 이미지는 올려놓았습니다!)

    • terraform/ecr/art-prod/prod_apnortheast2/Dockerfile-art-build-amazon

보안그룹 생성

  • VPC를 생성했던 곳으로 가서 Security Group을 생성합니다. (terraform/vpc/artp_apnortheast2 )

  • 만약 다른 곳에서 관리하고 계신 경우에는 해당 디렉토리에서 생성하시면 됩니다.

  • 생성한 Security Group을 외부에서 참조할 수 있도록 output에도 추가합니다.

vim terraform/vpc/artp_apnortheast2/codebuild_sg.tf
# Codebuild Default Security Group
resource "aws_security_group" "codebuild_default" {
  name        = "codebuild-default-${var.vpc_name}"
  description = "codebuild-default group for ${var.vpc_name}"
  vpc_id      = aws_vpc.default.id

  ingress {
    from_port = 0
    to_port   = 65535
    protocol  = "tcp"
    cidr_blocks = [
      "0.0.0.0/0",
    ]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
vim terraform/vpc/artp_apnortheast2/outputs.tf
output "aws_security_group_codebuild_default_id" {
  value = aws_security_group.codebuild_default.id
}

Codebuild에 github 인증하기

  • AWS CodeBuild 콘솔로 들어갑니다.

  • Create build project 를 클릭합니다.

  • Connect using OAuth 를 클릭합니다. (Personal Access Token으로 하셔도 무방합니다)

  • 인증을 마치신 후에는 Cancel 누르고 빠져나오셔도 됩니다. 실제 프로젝트는 terraform 코드를 이용해서 생성할 예정입니다.

Codebuild Project 생성

  • Jenkins에서 사용할 Codebuild 프로젝트를 생성합니다.

  • Codebuild 프로젝트와 관련된 전체 코드는 아래 디렉토리에 module 형태로 올려놓았으니 참조해주시기 바랍니다.

    • terraform/codebuild/

  • 변경이 필요한 부분은 아래 주석으로 표시해 놓았습니다.

본 가이드에서는 goployer 오픈소스를 활용하여 EC2를 배포할 예정입니다. goployer를 사용하고 싶으신 분들은 아래 링크를 참조해주시기 바랍니다.

https://github.com/DevopsArtFactory/goployer.git

vim terraform/codebuild/artp-apnortheast2/deployment.tf
module "deployment" {
  source                = "../_modules/deployment"
  service_name          = "deployment-prod"
  shard_id              = data.terraform_remote_state.vpc.outputs.shard_id
  public_subnets        = data.terraform_remote_state.vpc.outputs.public_subnets
  private_subnets       = data.terraform_remote_state.vpc.outputs.private_subnets
  aws_region            = data.terraform_remote_state.vpc.outputs.aws_region
  target_vpc            = data.terraform_remote_state.vpc.outputs.vpc_id
  vpc_name              = data.terraform_remote_state.vpc.outputs.vpc_name
  vpc_cidr_numeral      = data.terraform_remote_state.vpc.outputs.cidr_numeral
  billing_tag           = data.terraform_remote_state.vpc.outputs.billing_tag
  env_suffix            = data.terraform_remote_state.vpc.outputs.env_suffix

  image_credentials_type = "SERVICE_ROLE"

  # Change this to your security group ID
  deployment_default_sg = data.terraform_remote_state.vpc.outputs.aws_security_group_codebuild_default_id

  # Change this to your IAM role
  service_role          = "arn:aws:iam::816736805842:role/service-role/codebuild-deployment"

  # Change this repository which you want to pull code from.
  # In this repository, it should have buildspec file that you specify below
  github_repo            = "https://github.com/DevopsArtFactory/goployer.git"

  # buildspec file name you want to use
  buildspec              = "buildspec-deploy-prod.yml"

  # Change this to your docker image that you want to use for codebuild
  build_image            = "816736805842.dkr.ecr.ap-northeast-2.amazonaws.com/art-build:latest"
}

Jenkins 설정

이제 Codebuild 연동을 위해서 Jenkins 세팅을 진행하도록 하겠습니. 본 세팅은 따로 코드로 정의하지 않았고, 아래 가이드를 통해서 한 번 설정하시고 나면 특별한 일이 없는 이상은 바꾸실 일이 거의 없습니다.

플러그인 설치

  • Manage Jenkins -> Manage Plugins 를 클릭합니다.

  • Available 항목을 가셔서 codebuild를 검색해보시면 AWS CodBuild 플러그인이 보이실 겁니다. 이걸 클릭하신 후에 Install now without restart 를 클릭합니다.

설치가 진행되면 Jenkins를 재실행 할 수 있도록 옵션을 클릭해주고 기다립니다. Jenkins가 설치를 마치면 알아서 재시작합니다.

Github 연동

  • Manage Jenkins -> Configure System 를 클릭합니다.

  • Github 란으로 가셔서 Add Github Server 를 클릭합니다. 이름을 입력하신 후에, Credentials 쪽에 Add 버튼을 클릭합니다.

  • Kind로 Secret text 를 선택하시고, Secret란에 이전에 발급받으신 github 토큰을 입력합니다. ID는 편하신대로 입력하셔도 됩니다. 완료되었으면 Add 버튼을 클릭합니다.

  • 생성하신 Credentials를 선택하고 Test Connection을 클릭하셔서 정상적으로 연결되었는지 확인합니다.

  • 정상적으로 테스트가 끝나면 Save 버튼을 누르셔서 현재 세팅을 저장합니다.

Codebuild 연동

  • 메인화면에서 New Item 을 선택합니다.

  • Freestyle project 를 선택하시고, 이름을 입력하신 후 ok를 클릭합니다.

    • 저는 hello production 어플리케이션을 배포할 예정입니다.

  • Build 란에서 AWS Codebuild를 선택합니다.

    • AWS Codebuild가 안보이시는 경우에는 위에서 플러그인이 제대로 설치되었는지 확인해주시기 바랍니다.

  • Select credentials from jenkins 를 선택하시고 Add 를 클릭해서 새로운 인증키를 생성합니다.

  • 사전에 발급받은 Jenkins User의 AWS Access Key와 AWS Secret Access Key를 입력합니다.

  • Region ID를 입력하시고, 이전에 생성했던 Codebuild 이름을 입력합니다. 이후에 Use Project Source 를 선택합니다. 따로 값은 입력하지 않으셔도 됩니다.

  • (Optional) 파라미터가 필요하신 경우에는 파라미터를 (원하시는만큼)입력합니다. 해당 파라미터는 codebuild로 전달하실 수 있습니다. 참고로 맨 위쪽에서 세팅하시면 됩니다.

  • 위의 파라미터를 Codebuild로 전달합니다. Codebuild 설정했던 부분의 Build Configuration 부분에 있습니다.

  • 설정이 완료되었으면 save 버튼을 누릅니다.

Codebuild 사용하기

  • Build with Parameters 를 클릭하셔서 Codebuild job을 실행합니다.

  • 빌드 번호를 선택하시고 Console Output 을 보시면 Codebuild의 로그를 보실 수 있습니다.

  • Codebuild를 확인해보시면 아래와 같이 실행되고 있는 Job을 보실 수 있습니다.

Last updated