Alternative Architecture DOJO

オルターブースのクラウドネイティブ特化型ブログです。

AWS Codeシリーズとは

こんにちは!オルターブースのいけだです! 熱い夏が続きますね!最近は夏バテを払拭しようと辛い食べ物を食べすぎて、お腹の調子が悪くなりました💪

さて、今回はAWSのCodeシリーズの簡単な解説と、 実際にCodeCommit、CodeBuild、CodePiplineを試してみました🙂

AWS Codeシリーズ

AWS Codeシリーズは、AWSが提供する開発者向けの一連のサービスで、 ソフトウェアの開発とデプロイを効率化するためのツール群です。 以下は代表的なサービスです。

また、以下の表は用途毎で分類した表になります。

主な用途 サービス名
ソースコード管理 AWS CodeCommit
IssueベースのProject管理 Amazon CodeCatalyst
デプロイパイプライン AWS CodeBuild / AWS CodeDeploy / AWS CodePipeline
コードスキャニング Amazon CodeGuru セキュリティ
AIによるサポート Amazon CodeWhisperer

目的によって複数のサービスを組み合わせて実現していくイメージでしょうか。

CodeCommit / CodeBuild / CodePipeline を試してみる。

さて、これらを早速試してみようと思います。

下記のCI/CD on AWS ワークショップが非常に分かりやすかったので、こちらに沿って進めます。

catalog.workshops.aws

因みにこちらのワークショップを進めるにあたり、AWS CDKが利用可能であることが前提です。

セットアップが済んでいない場合はCDKワークショップを参考に環境とCDKの使用感を確認しておくのが良いと思います。

また、CI/CDワークショップLab 6までありますが、今回はLab3までを割愛して記載しています。

大まかな手順としては以下の通りです。

  1. ローカルにReactアプリとDockerfileを準備。
  2. CDKでCodeCommitレポジトリを作成し、ソースコードをプッシュする。
  3. CodeBuildによるビルドとテスト、ECRへの継続的デリバリー。

1の手順については割愛し、2.の手順に入っていきます。

まずCDKでCodeCommitレポジトリを準備します。

#cdkプロジェクトの作成
cdk init app --language=typescript
#lib/pipeline-cdk-stack.ts

import { CfnOutput, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as codecommit from 'aws-cdk-lib/aws-codecommit';

export class PipelineCdkStack extends Stack {
  constructor(scope: Construct, id: string, props: StackProps) {
    super(scope, id, props);

    //CodeCommitレポジトリ作成
    const sourceRepo = new codecommit.Repository(this, 'CICD_Workshop', {
      repositoryName: 'CICD_Workshop',
      description: 'Repository for my application code and infrastructure',
    });
    //作成したレポジトリのURLを出力
    new CfnOutput(this, 'CodeCommitRepositoryUrl', { value: sourceRepo.repositoryCloneUrlHttp });
  }
}
#bin/app-cdk.ts

import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { AppCdkStack } from '../lib/app-cdk-stack';
import { PipelineCdkStack } from '../lib/pipeline-cdk-stack'

const app = new cdk.App();

new AppCdkStack(app, 'AppCdkStack', {});

new PipelineCdkStack(app, 'pipeline-stack', {});
#デプロイ
cdk deploy pipeline-stack

コンソールで確認すると、レポジトリができているのが確認できます。

今回、このレポジトリにはユーザID/パスワード による認証(HTTPS)で接続してみようと思います。

因みに、HTTPS(GRC)認証というIAMユーザーの権限設定のみで利用可能な方法もあるようです。 複数アカウント持っているならこの方法が良さそうです。

docs.aws.amazon.com

IAMに移動し、CodeCommitを利用するIAMユーザー(今回は自分の)に、AWSCodeCommitFullAccessを割り当てます。

ついでに、この後使うCodeBuildとCodePipelineについてもフルアクセスのポリシーを割り当てておきます。

docs.aws.amazon.com

docs.aws.amazon.com

docs.aws.amazon.com

次にIAMユーザーセキリュティ認証情報AWS CodeCommit の HTTPS Git 認証情報から認証情報を生成します。

ここで得られるユーザー名とパスワードがレポジトリへプッシュする際に必要になります。

レポジトリへアクセスする際にこのように表示される

プルリクエストなどを試してみた所感としては、CodeCommitの操作はシンプルで直感的にも分かりやすい印象を持ちました。

CodeBuildとCodePipeline

Lab3では、CodeBuild/CodePiplineでソースコードのビルド&テスト → Dockerfileのビルド → ECRにイメージをプッシュするところまで行います。

以下は最終的なCDKファイルと設定ファイルになります。

# lib/ecr-cdk-stack.ts
//ECR用のスタックを新しく作成

import { Stack, StackProps } from "aws-cdk-lib";
import { Construct } from "constructs";
import * as ecr from "aws-cdk-lib/aws-ecr";

export class EcrCdkStack extends Stack {
  public readonly repository: ecr.Repository;

  constructor(scope: Construct, id: string, props: StackProps) {
    super(scope, id, props);
    this.repository = new ecr.Repository(this, "my_app");
  }
}
# lib/pipeline-cdk-stack.ts

import { CfnOutput, Stack, StackProps } from "aws-cdk-lib";
import { Construct } from "constructs";
import * as codecommit from "aws-cdk-lib/aws-codecommit";
import * as codepipeline from "aws-cdk-lib/aws-codepipeline";
import * as codebuild from "aws-cdk-lib/aws-codebuild";
import * as codepipeline_actions from "aws-cdk-lib/aws-codepipeline-actions";
import * as ecr from "aws-cdk-lib/aws-ecr";
import * as iam from 'aws-cdk-lib/aws-iam';

interface ConsumerProps extends StackProps {
  ecrRepository: ecr.Repository;
}

export class PipelineCdkStack extends Stack {
  constructor(scope: Construct, id: string, props: ConsumerProps) {
    super(scope, id, props);
    
    //CodeCommit
    const sourceRepo = new codecommit.Repository(this, "CICD_Workshop", {
      repositoryName: "CICD_Workshop",
      description: "Repository for my application code and infrastructure",
    });

    //CodePipeline
    const pipeline = new codepipeline.Pipeline(this, "CICD_Pipeline", {
      pipelineName: "CICD_Pipeline",
      crossAccountKeys: false,
    });
   
   //CodeBuild(ソースコードをテスト)
    const codeQualityBuild = new codebuild.PipelineProject(
      this,
      "Code Quality",
      {
        environment: {
          buildImage: codebuild.LinuxBuildImage.STANDARD_5_0,
          privileged: true,
          computeType: codebuild.ComputeType.LARGE
        },
       // 設定ファイルはbuildspec_test.ymlを参照する
        buildSpec: codebuild.BuildSpec.fromSourceFilename("buildspec_test.yml"),
      }
    );

    //CodeBuild (DockerfileをビルドしてECRにプッシュする)
    const dockerBuildProject = new codebuild.PipelineProject(
      this,
      "DockerBuildProject",
      {
        environmentVariables: {
          IMAGE_TAG: { value: "latest" },
          IMAGE_REPO_URI: { value: props.ecrRepository.repositoryUri },
          AWS_DEFAULT_REGION: { value: process.env.CDK_DEFAULT_REGION },
        },
        environment: {
          buildImage: codebuild.LinuxBuildImage.STANDARD_5_0,
          privileged: true,
          computeType: codebuild.ComputeType.LARGE
        },
       //設定ファイルはbuildspec_docker.ymlを参照する
        buildSpec: codebuild.BuildSpec.fromSourceFilename(
          "buildspec_docker.yml"
        ),
      }
    );
   
    //ECRへのアクセスを許可するIAMポリシー
    const dockerBuildRolePolicy = new iam.PolicyStatement({
      effect: iam.Effect.ALLOW,
      resources: ["*"],
      actions: [
        "ecr:GetAuthorizationToken",
        "ecr:BatchCheckLayerAvailability",
        "ecr:GetDownloadUrlForLayer",
        "ecr:GetRepositoryPolicy",
        "ecr:DescribeRepositories",
        "ecr:ListImages",
        "ecr:DescribeImages",
        "ecr:BatchGetImage",
        "ecr:InitiateLayerUpload",
        "ecr:UploadLayerPart",
        "ecr:CompleteLayerUpload",
        "ecr:PutImage",
      ],
    });
    
   //作成したIAMポリシーをアタッチ
    dockerBuildProject.addToRolePolicy(dockerBuildRolePolicy);

   
    //各ステージで出力されるArtifact(ファイルのグループを用意)
    const sourceOutput = new codepipeline.Artifact();
    const unitTestOutput = new codepipeline.Artifact();
    const dockerBuildOutput = new codepipeline.Artifact();
   
    //CodeCommitのmainからソースコードを取得し、次のステージに渡す
    pipeline.addStage({
      stageName: "Source",
      actions: [
        new codepipeline_actions.CodeCommitSourceAction({
          actionName: "CodeCommit",
          repository: sourceRepo,
          output: sourceOutput,
          branch: "main",
        }),
      ],
    });
    
    //このステージではソースコードのテストを行う
    pipeline.addStage({
      stageName: "Code-Quality-Testing",
      actions: [
        new codepipeline_actions.CodeBuildAction({
          actionName: "Unit-Test",
          project: codeQualityBuild,
          input: sourceOutput,
          outputs: [unitTestOutput],
        }),
      ],
    });
    
    //このステージではDockerFileをビルドし、ECRへプッシュする
    pipeline.addStage({
      stageName: "Docker-Push-ECR",
      actions: [
        new codepipeline_actions.CodeBuildAction({
          actionName: "docker-build",
          project: dockerBuildProject,
          input: sourceOutput,
          outputs: [dockerBuildOutput],
        }),
      ],
    });


    new CfnOutput(this, "CodeCommitRepositoryUrl", {
      value: sourceRepo.repositoryCloneUrlHttp,
    });
  }
}
# buildspec_test.yml
# ソースコードのビルド仕様
version: 0.2

phases:
  install:
    runtime-versions:
      nodejs: 14
  pre_build:
    commands:
      - cd ./my-app
      - echo Installing npm packages... 
      - npm install
  build:
    commands:
      - echo run tests...
      - CI=true npm run test
# buildspec_docker.yml
# Dockerfileビルド仕様
version: 0.2

phases:
  install:
    runtime-versions:
      nodejs: 14
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $IMAGE_REPO_URI

  build:
    commands:
      - cd ./my-app
      - echo Build started on `date`
      - echo Building the Docker image...
      - docker build -t myapp:$IMAGE_TAG .
      - docker tag myapp:$IMAGE_TAG $IMAGE_REPO_URI:$IMAGE_TAG

  post_build:
    commands:
      - echo Build completed on `date`
      - echo Pushing the Docker image...
      - docker push $IMAGE_REPO_URI:$IMAGE_TAG

buildspec_docker.ymlで使用している環境変数ですが、 CodeBuildにはビルドコマンドで使用できる環境変数がいくつか用意されているようです。

docs.aws.amazon.com

#bin/app-cdk.ts 
import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { AppCdkStack } from "../lib/app-cdk-stack";
import { PipelineCdkStack } from "../lib/pipeline-cdk-stack";
import { EcrCdkStack } from "../lib/ecr-cdk-stack";

const app = new cdk.App();

const ecrCdkStack = new EcrCdkStack(app, "ecr-stack", {});

new AppCdkStack(app, "AppCdkStack", {});

const pipelineCdkStack = new PipelineCdkStack(app, "pipeline-stack", {
  ecrRepository: ecrCdkStack.repository,
});

CDKの変更をデプロイします。

cdk deploy pipeline-stack

新しく、ECRレポジトリ、CodeBuildアクション、CodePipelineが作成されます。

変更をCodeCommitへgit pushします。

各パイプラインが実行されました。

ECRへもプッシュできていますね。

さいごに

今回はワークショップの3章まで記載しましたが、本来はこの後ECSへのCI/CDまで続いています。

CDKでリソースを作成しているので、使い終わったら削除も簡単です!

次回はGitHubとCodeシリーズの組み合わせパターンなんかもやってみようと思います💪

拙い内容で恐縮ですが、ここまでご覧いただきありがとうございました!