IaaS/퍼블릭클라우드

(AWS) CodeBuild 란?

armyost 2021. 12. 30. 06:42
728x90

AWS CodeBuild는 

• Fully managed build service

완전관리형 빌드 서비스이며

 

• Alternative to other build tools such as Jenkins

Jenkins와 같은 빌드툴의 대체재 이며

 

• Continuous scaling (no servers to manage or provision – no build queue)

프로비저닝이 필요없이 지속적으로 스케일링 관릭 되며

 

• Pay for usage: the time it takes to complete the builds

빌드 수행 시간 만큼 과금하며

 

• Secure: Integration with KMS for encryption of build artifacts, IAM for build permissions, and VPC for network security, CloudTrail for API calls logging

KMS, IAM, VPC의 보안세팅을 이용할 수 있는 향상된 보안

 


CodeBuild 활용방안

 

• Source Code from GitHub / CodeCommit / CodePipeline / S3…

GitHub, CodeCommit 등과 같은 다양한 레파지토리 또는 저장장치를 Source로 지정할 수 있다.

 

• Build instructions can be defined in code (buildspec.yml file)

Buildspec.yml 파일을 이용해 빌드 설명을 정의할 수 있음

 

• Output logs to Amazon S3 & AWS CloudWatch Logs

• Metrics to monitor CodeBuild statistics

• Use CloudWatch Events to detect failed builds and trigger notifications

• Use CloudWatch Alarms to notify if you need “thresholds” for failures

• CloudWatch Events / AWS Lambda as a Glue

• SNS notification

CloudWatch와 AWS SNS를 이용해 Notification이 용이

 


CodeBuild 사용법

 

1. [CodeBuild]-[빌드프로젝트] 에서 빌드프로젝트 생성 클릭

2. 기본정보를 입력하고 '소스'란에 나의 레포지토리 정보와 동일하게 세팅

(필자의 경우 CodeCommit을 레파지토리로 사용하였고, 브랜치를 기준으로 빌드를 수행코자합니다.)

 

3. CodeBuild 실행하는 데 사용하는 운영 체제, 프로그래밍 언어 실행 시간 및 도구의 조합을 나타냅니다. 

 

4. 가장 중요한 부분으로 Buildspec.yml 을 Read토록 한다. (디폴트로 buildspec.yml로 설정되어 있으니 혹시 이름/경로가 다른경우 입력하길)

5. 그리고 빌드 수행하면 시작됩니다.


BuildSpec 사용법

 

공식 Doc

https://docs.aws.amazon.com/ko_kr/codebuild/latest/userguide/build-spec-ref.html

 

CodeBuild의 빌드 사양 참조 - AWS CodeBuild

지정한 경우runtime-versions섹션에서 Ubuntu 표준 이미지 2.0 이상 또는 Amazon Linux 2 (AL2) 표준 이미지 1.0 이상 외의 이미지를 사용하는 경우, 빌드에서” 라는 경고가 발생합니다.Skipping install of runtimes.

docs.aws.amazon.com

 

buildspec 파일은 YAML 형식으로 표시해야 합니다.

 

version(필수)

buildspec 버전을 나타냅니다. 0.2를 사용할 것을 권장합니다.

 

 

run-as(옵션)

선택적 시퀀스. Linux 사용자만 사용할 수 있습니다. 이 buildspec 파일에서 명령을 실행하는 Linux 사용자를 지정합니다.run-as는 지정된 사용자에게 읽기 및 실행 권한을 부여합니다. buildspec 파일 처음에 run-as를 지정할 경우 모든 명령에 전역적으로 적용됩니다. 모든 buildspec 파일 명령에 대한 사용자를 지정하지 않으려는 경우 phases 블록 중 하나에 run-as를 사용하여 단계에 명령에 대한 사용자를 지정할 수 있습니다. run-as를 지정하지 않으면 모든 명령이 루트 사용자로 실행됩니다.

 

env(옵션, 중요)

선택적 시퀀스. 하나 이상의 사용자 지정 환경 변수에 대한 정보를 나타냅니다.

 

env/shell

선택적 시퀀스. Linux 또는 Windows 운영 체제에 대해 지원되는 셸을 지정합니다.

 

env/variables

env가 지정된 경우 및 사용자 지정 환경 변수를 일반 텍스트로 정의하려고 할 때 필요합니다. 

 

env/parameter-store

Amazon EC2 Systems Manager 파라미터 스토어에 저장된 단일 사용자 지정 환경 변수를 나타냅니다.

 

env/secrets-manager

AWS Secrets Manager에 저장된 사용자 지정 환경 변수를 검색하려고 할 때 필요합니다.

<key>: <secret-id>:<json-key>:<version-stage>|<version-id>

 

env/exported-variables

내보낼 환경 변수를 나열하는 데 사용됩니다. 내보낼 변수는 빌드 중에 컨테이너에서 사용할 수 있어야 합니다. 

 

env/git-credential-helper

CodeBuild 가 Git 자격 증명 헬퍼를 사용하여 Git 자격 증명을 제공하는지를 나타내는 데 사용됩니다

 

 

 

 

proxy(옵션)

명시적 프록시 서버에서 빌드를 실행할 경우 설정을 나타내는 데 사용됩니다.

 

phases(필수)

빌드의 각 단계 동안 CodeBuild가 실행하는 명령을 나타냅니다.

 

phases/*/run-as

해당 명령을 실행하는 Linux 사용자를 지정

 

phases/*/실패 시

단계 중에 오류가 발생할 경우 수행할 작업을 지정합니다. .

 

phases/*/finally

명령 후에 실행 되는commands블록을 설정합니다. JAVA의 try/catch/finally 와 같은 역할

 

phases/install

빌드 환경에서 패키지를 설치하는 경우에만 install 단계를 사용하는 것이 좋습니다.

 

phases/install/runtime-versions

빌드 사양 파일에 런타임을 지정하지 않은 경우 CodeBuild가 사용하는 이미지에서 사용할 수 있는 기본 런타임을 선택합니다. 하나 이상의 런타임을 지정하는 경우 CodeBuild 는 해당 런타임만 사용합니다.

 

phases/install/commands

스칼라 시퀀스를 포함하며, 각 스칼라는 설치 중에 CodeBuild 가 실행하는 단일 명령을 나타냅니다. CodeBuild 는 처음부터 끝까지 한 번에 하나씩 나열된 순서로 각 명령을 실행합니다.

 

phases/pre_build

빌드 전에 CodeBuild가 실행하는 명령을 나타냅니다

 

phases/build

빌드 중에 CodeBuild가 실행하는 명령을 나타냅니다

 

phases/post_build

빌드 후에 CodeBuild가 실행하는 명령을 나타냅니다

 

phases/build(pre_, post_ 동일)/commands

xx_build를 지정한 경우 필수 시퀀스입니다. CodeBuild 는 처음부터 끝까지 한 번에 하나씩 나열된 순서로 각 명령을 실행합니다.

 

reports(필수)

report-group-name-or-arn

보고서를 전송할 보고서 그룹을 지정합니다.

 

reports/<report-group>/files

보고서에 의해 생성된 테스트 결과의 원시 데이터가 포함된 위치를 나타냅니다. 

 

reports/<report-group>/file-format

보고서 파일 형식을 나타냅니다. 지정하지 않으면 JUNITXML가 사용됩니다. 

 

reports/<report-group>/base-directory

빌드 위치에 상대적인 하나 이상의 최상위 디렉터리를 나타냅니다. 

 

reports/<report-group>/discard-paths

보고서 파일 디렉터리가 출력시 /로 압축되는지 여부를 지정합니다.(보안을 위해)

 

artifacts(옵션, 중요)

CodeBuild 출력을 찾을 수 있는 위치 및 S3 출력 버킷에 업로드하기 위해 빌드 출력을 준비하는 방식에 대한 정보를 나타냅니다. Docker 이미지를 빌드하여 Amazon ECR에 푸시하는 경우 또는 소스 코드에 단위 테스트를 실행하고 빌드는 하지 않는 경우 등에는 이 시퀀스가 필요하지 않습니다.

 

artifacts/files

빌드 환경의 빌드 출력 결과물을 포함하는 위치를 나타냅니다.

 

artifacts/name

빌드 결과물의 이름을 지정합니다. 

 

artifacts/discard-paths

빌드 아티팩트 디렉터리가 출력에서 평면화(/로 퉁치는)되는지 여부를 지정합니다.

 

artifacts/base-directory

CodeBuild Build가 빌드 출력 결과물에 포함할 파일 및 하위 디렉터리를 결정할 때 사용하는 원래 빌드 위치를 기준으로 하나 이상의 최상위 디렉터리를 나타냅니다. 유

 

artifacts/exclude-paths

출력시 제외할 경로

 

artifacts/enable-symlinks

내부 심볼 링크가 출력되는 ZIP 파일에 유지되는지 여부를 지정합니다. yes로 설정하면 소스의 모든 내부 심볼 링크가 아티팩트 ZIP 파일에 보존됩니다.

 

artifacts/s3-prefix

S3버킷 출력경로의 prefix를 정할때 사용. 객체가 Amazon S3 버킷으로 출력되고 네임스페이스 유형이 BUILD_ID.일때 버킷의 출력 경로는<s3-prefix>/<build-id>/<name>.zip  이다.

 

artifacts/secondary-artifacts

선택적 시퀀스. 1개 이상의 아티팩트 정의를 아티팩트 식별자와 아티팩트 정의를 연결하는 매핑으로 나타냅니다. 

 

cache(옵션)

CodeBuild 가 S3 캐시 버킷을 사용하여 업로드할때 준비 정보를 나타냅니다. 

 

cache/paths

캐시의 위치를 나타냅니다. 

 

↓ buildspec.yml기본 구성

version: 0.2

run-as: Linux-user-name

env:
  shell: shell-tag
  variables:
    key: "value"
    key: "value"
  parameter-store:
    key: "value"
    key: "value"
  exported-variables:
    - variable
    - variable
  secrets-manager:
    key: secret-id:json-key:version-stage:version-id
  git-credential-helper: no | yes

proxy:
  upload-artifacts: no | yes
  logs: no | yes

batch:
  fast-fail: false | true
  # build-list:
  # build-matrix:
  # build-graph:
        
phases:
  install:
    run-as: Linux-user-name
    on-failure: ABORT | CONTINUE
    runtime-versions:
      runtime: version
      runtime: version
    commands:
      - command
      - command
    finally:
      - command
      - command
  pre_build:
    run-as: Linux-user-name
    on-failure: ABORT | CONTINUE
    commands:
      - command
      - command
    finally:
      - command
      - command
  build:
    run-as: Linux-user-name
    on-failure: ABORT | CONTINUE
    commands:
      - command
      - command
    finally:
      - command
      - command
  post_build:
    run-as: Linux-user-name
    on-failure: ABORT | CONTINUE
    commands:
      - command
      - command
    finally:
      - command
      - command
reports:
  report-group-name-or-arn:
    files:
      - location
      - location
    base-directory: location
    discard-paths: no | yes
    file-format: report-format
artifacts:
  files:
    - location
    - location
  name: artifact-name
  discard-paths: no | yes
  base-directory: location
  exclude-paths: excluded paths
  enable-symlinks: no | yes
  s3-prefix: prefix
  secondary-artifacts:
    artifactIdentifier:
      files:
        - location
        - location
      name: secondary-artifact-name
      discard-paths: no | yes
      base-directory: location
    artifactIdentifier:
      files:
        - location
        - location
      discard-paths: no | yes
      base-directory: location
cache:
  paths:
    - path
    - path

AWS에서 Merge요청등으로 발생한 Pull request를 승인하는 프로세스

 

준비사항

Lambda Functions, Codebuild, Cloudwatch Events, IAM Roles, CodeCommit 

 

Architecture

  1. PullRequest is created 또는 existing PullRequest is updated 발생
  2. Cloudwatch의 레파지토리 모니터링 event 활성화됨
  3. event가 lambda function에 관련 데이터 전송
  4. 마지막 Commit버전에 대한 CodeBuild의 프로젝트를 유발해서 Test를 Run한다.
  5. PullRequest의 코멘트 섹션에 빌드시작을 알리고 Timestamp와 빌드로그의 링크를 게시하는 등 커스텀 메시지를 남긴다.
  6. CodeBuild가 완료되면 Cloudewatch가 빌드결과를 Lambda function에 보냄
  7. 이 Lambda function은 PullRequest에 결과를 코멘트 할 것이다.

 

 

 

 

 

아래는 Lambda Function 예시이다.

const AWS = require('aws-sdk');
const codecommit = new AWS.CodeCommit();
const codebuild = new AWS.CodeBuild();

exports.handler = async (event) => {
    try {
        console.log('Received Event: ', event);
        const { destinationCommit } = event.detail;
        const { sourceCommit } = event.detail;
        const { pullRequestId } = event.detail;
        const pullRequestName = event.detail.title;
        const sourceBranch = event.detail.sourceReference.split('/').pop();
        const triggerCodeBuildParameters = {
            sourceBranch, sourceCommit, destinationCommit, pullRequestId, pullRequestName
        };
        const codeBuildResult = await triggerCodebuild(triggerCodeBuildParameters);
        
        const buildId = codeBuildResult.build.id;
        const postBuildStartedCommentOnPRParameters = {
            sourceCommit, destinationCommit, pullRequestId, buildId
        }
        
        await postBuildStartedCommentOnPR(postBuildStartedCommentOnPRParameters);
        
        return {
            statusCode: 200
        };
    }
    catch (error) {
        console.log('An Error Occured', error);
        return { 
            error
        };
    }
};

async function postBuildStartedCommentOnPR(postBuildStartedCommentOnPRParameters) {
    const { sourceCommit, destinationCommit, pullRequestId, buildId } = postBuildStartedCommentOnPRParameters;
    const logLink = `https://${process.env.REGION}.console.aws.amazon.com/codesuite/codebuild/projects/ValidatePullRequest/build/${buildId}`;
    const parameters = {
        afterCommitId: sourceCommit,
        beforeCommitId: destinationCommit,
        content: `Build For Validating The Pull Request has been started.   
        Timestamp: **${Date.now()}**   
        Check [CodeBuild Logs](${logLink})`,
        pullRequestId,
        repositoryName: process.env.REPOSITORY_NAME
    };

    const request = await codecommit.postCommentForPullRequest(parameters);
    const promise = request.promise();
    return promise.then(
        (data) => data,
        (error) => {
            console.log('Error In Commenting To Pull Request', error);
            throw new Error(error);
        }
    );
}

async function triggerCodebuild(triggerCodeBuildParameters) {
    const { sourceBranch, sourceCommit, destinationCommit, pullRequestId, pullRequestName } = triggerCodeBuildParameters;
    console.log(`Triggering Codebuild, Branch: ${sourceBranch}`);
    const parameters = {
        projectName: process.env.CODEBUILD_PROJECT,
        sourceVersion: `refs/heads/${sourceBranch}^{${sourceCommit}}`,
        environmentVariablesOverride: [
            {
                name: 'pullRequestId',
                value: pullRequestId,
                type: 'PLAINTEXT'
            },
            {
                name: 'sourceCommit',
                value: sourceCommit,
                type: 'PLAINTEXT'
            },
            {
                name: 'destinationCommit',
                value: destinationCommit,
                type: 'PLAINTEXT'
            },
            {
                name: 'pullRequestName',
                value: pullRequestName,
                type: 'PLAINTEXT'
            }
        ]
    };
    const request = await codebuild.startBuild(parameters);
    const promise = request.promise();
    return promise.then(
        (data) => data,
        (error) => {
            console.log('Error In Starting Codebuild', error);
            throw new Error(error);
        }
    );
}

[TriggerCodeBuildStart-index.js]

 

 

const AWS = require('aws-sdk');
const codecommit = new AWS.CodeCommit();
exports.handler = async (event) => {
    try {
        console.log('Event', event);
        const parameters = await getParameters(event);
        console.log('Parameters For Comment:', parameters);
        await commentCodeBuildResultOnPR(parameters);
        return { statusCode: 200 };
    }
    catch (error) {
        console.log('An Error Occured', error);
        return { error };
    }
};

async function getParameters(event) {
    try {
        const buildId = event.detail['build-id'].split('/')[1];
        const buildStatus = event.detail['build-status'];
        const environmentVariableList = event.detail['additional-information'].environment['environment-variables'];
        let afterCommitId, beforeCommitId, content, pullRequestId;
        for (element of environmentVariableList) {
            if (element.name === 'pullRequestId') pullRequestId = element.value;
            if (element.name === 'sourceCommit') afterCommitId = element.value;
            if (element.name === 'destinationCommit') beforeCommitId = element.value;
            if (element.name === 'pullRequestName') pullRequestName = element.value;
        }

        const logLink = `https://${process.env.REGION}.console.aws.amazon.com/codesuite/codebuild/projects/ValidatePullRequest/build/${buildId}`;
        content = `Build Result: **${buildStatus}**   
        Timestamp: **${Date.now()}**   
        Check [CodeBuild Logs](${logLink})`;

        return {
            afterCommitId,
            beforeCommitId,
            content,
            pullRequestId,
            repositoryName: process.env.REPOSITORY_NAME
        };
    } catch (error) {
        throw error;
    }
}

async function commentCodeBuildResultOnPR(parameters) {
    const request = await codecommit.postCommentForPullRequest(parameters);
    const promise = request.promise();
    return promise.then(
        (data) => data,
        (error) => {
            console.log('Error In Commenting To Pull Request', error);
            throw new Error(error);
        }
    );
}

[TriggerCodeBuildResult - index.js]

 

 


 

buildspec에 관한 use-case는 아래 링크에서 확인할 수 있다.

 

https://docs.aws.amazon.com/ko_kr/ko_kr/codebuild/latest/userguide/use-case-based-samples.html

 

CodeBuild 사용 사례 기반 샘플 - AWS CodeBuild

이 페이지에 작업이 필요하다는 점을 알려 주셔서 감사합니다. 실망시켜 드려 죄송합니다. 잠깐 시간을 내어 설명서를 향상시킬 수 있는 방법에 대해 말씀해 주십시오.

docs.aws.amazon.com

 

 

이중에 특히 Docker 관련 샘플을 보자

version: 0.2

phases:
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
  build:
    commands:
      - echo Build started on `date`
      - echo Building the Docker image...          
      - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
      - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG      
  post_build:
    commands:
      - echo Build completed on `date`
      - echo Pushing the Docker image...
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG

여기서 주목할 만한점은 왜 ECR로그인을 Pre-build에 하는가 이다. 

 

왜냐하면, Build가 무지오래 걸릴 수가 있기때문에 오류가능성이 있는 것을 Pre-build에 선 검증하고 빌드해야 똑똑한 CICD인것..

 

 


빌드시 사용하는 환경변수는 아래 링크에서 확인할 수 있다.

 

https://docs.aws.amazon.com/ko_kr/ko_kr/codebuild/latest/userguide/build-env-ref-env-vars.html

 

빌드 환경의 환경 변수 - AWS CodeBuild

Webhook pull 요청 이벤트에서 트리거하는 GitHub 또는 GitHub Enterprise Server 빌드의 경우 pr/pull-request-number입니다.

docs.aws.amazon.com

 

환경변수를 직접 커스텀 하고 싶다면, 이전에 배운 Buildspec의 env를 이용하거나,

 

CodeBuild에서 Build 수정을 통해 변수를 커스터 마이징한다.

간혹 도커빌드를 하다보면 DB_PASSWORD나 DB_HOST등을 환경변수로 빼는 경우가 많은데 이런경우 유용하게 사용할 수 있다.