AWS

CodePipelineでイメージをビルドしてFargateにデプロイする

スポンサーリンク
CodePipelineでビルドしたイメージをFargateにデプロイする AWS
スポンサーリンク
スポンサーリンク

CodePipelineでイメージをビルドしてFargateにデプロイする

以下のページで、CodePipelineを使ってパイプラインを構成して、CodeCommitとCodeBuildを連携させて、ECRにイメージをプッシュする方法をご紹介しました。

今回はCodePipelineにデプロイステージを作成して、ビルドしたイメージをFargateにデプロイすることを目指します。

構築する環境

Diagram of use CodePipeline to build and deploy images to Fargate.

CodePipelineを構成し、2つのリソースを連携させます。

1つ目はCodeCommitです。
CodeCommitはCodePipelineのソースステージを担当します。
Gitリポジトリとして使用します。

2つ目はCodeBuildです。
CodeBuildはCodePipelineのビルドステージを担当します。
CodeCommitにプッシュされたコードから、Dockerイメージをビルドします。
ビルドしたイメージをECRにプッシュします。

CodePipelineにデプロイステージを作成します。
後述のFargateにデプロイするように設定します。

SSMパラメータストアにDockerHubアカウント情報を保存します。
DockerBuildでイメージを生成する際に、DockerHubにサインインした上でベースイメージをプルするために、これらを使用します。

CodePipelineが開始されるきっかけですが、CodeCommitへのプッシュを条件とします。
具体的には、EventBridgeに上記を満たすルールを用意します。

プライベートサブネットに、FargateタイプのECSを作成します。

ECRにプッシュしたイメージを取得するために、ECR用およびS3用VPCエンドポイントを作成します。

EC2インスタンスを作成します。
Fargate上に作成されたコンテナにアクセスするためのクライアントして使用します。

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。

awstut-fa/076 at main · awstut-an-r/awstut-fa
Contribute to awstut-an-r/awstut-fa development by creating an account on GitHub.

テンプレートファイルのポイント解説

基本的な構成は冒頭にご紹介したページと同様です。
本ページは、CodePipelineにおいて、CodeBuildでビルドしたDockerイメージを、Fargateにデプロイする方法を中心に取り上げます。

プライベートサブネットにFargateを構築する方法については、以下のページをご確認ください。

CodeBuild

Resources: CodeBuildProject: Type: AWS::CodeBuild::Project Properties: Artifacts: Type: CODEPIPELINE Cache: Type: NO_CACHE Environment: ComputeType: !Ref ProjectEnvironmentComputeType EnvironmentVariables: - Name: CONTAINER_NAME Type: PLAINTEXT Value: !Ref ContainerName - Name: DOCKERHUB_PASSWORD Type: PARAMETER_STORE Value: !Ref SSMParameterDockerHubPassword - Name: DOCKERHUB_USERNAME Type: PARAMETER_STORE Value: !Ref SSMParameterDockerHubUsername - Name: IMAGE_DEFINITION Type: PLAINTEXT Value: !Ref ImageDefinitionFileName Image: !Ref ProjectEnvironmentImage ImagePullCredentialsType: CODEBUILD Type: !Ref ProjectEnvironmentType PrivilegedMode: true LogsConfig: CloudWatchLogs: Status: DISABLED S3Logs: Status: DISABLED Name: !Ref Prefix ServiceRole: !GetAtt CodeBuildRole.Arn Source: Type: CODEPIPELINE BuildSpec: !Sub | version: 0.2 phases: pre_build: commands: - echo Logging in to Amazon ECR... - aws --version - aws ecr get-login-password --region ${AWS::Region} | docker login --username AWS --password-stdin ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com - REPOSITORY_URI=${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ECRRepositoryName} - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7) - IMAGE_TAG=${!COMMIT_HASH:=latest} - echo Logging in to Docker Hub... - echo $DOCKERHUB_PASSWORD | docker login -u $DOCKERHUB_USERNAME --password-stdin build: commands: - echo Build started on `date` - echo Building the Docker image... - docker build -t $REPOSITORY_URI:latest . - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG post_build: commands: - echo Build completed on `date` - echo Pushing the Docker images... - docker push $REPOSITORY_URI:latest - docker push $REPOSITORY_URI:$IMAGE_TAG - echo Writing image definitions file... - printf '[{"name":"%s","imageUri":"%s"}]' $CONTAINER_NAME $REPOSITORY_URI:$IMAGE_TAG > $IMAGE_DEFINITION artifacts: files: $IMAGE_DEFINITION Visibility: PRIVATE
Code language: YAML (yaml)

BuildSpecプロパティがポイントです。
今回はCloudFormationテンプレートにbuildspec.ymlの内容を直接記載しています。

CodeBuildでビルドしたイメージをECS(Fargate)にデプロイする上でのポイントは、post_buildフェーズです。
AWS公式ページによりますと、imagedefinition.jsonというファイルを作成する必要があるとされています。

Amazon ECS サービスのコンテナ名およびイメージとタグがあるビルドのルートに imagedefinitions.json という名前のファイルを作成します。CD パイプラインのデプロイステージでこの情報を使用してサービスのタスク定義の新しいリビジョンを作成し、新しいタスク定義を使用してサービスを更新します。imagedefinitions.jsonファイルは ECS ジョブワーカーに必須です。

チュートリアル: CodePipeline を使用した Amazon ECS 標準デプロイ

今回は環境変数IMAGE_DEFINITIONにimagedefinitions.jsonという文字列を格納し、2箇所で参照しています。

CodePipeline

Resources: Pipeline: Type: AWS::CodePipeline::Pipeline Properties: ArtifactStore: Location: !Ref BucketName Type: S3 Name: !Ref Prefix RoleArn: !GetAtt CodePipelineRole.Arn Stages: - Actions: - ActionTypeId: Category: Source Owner: AWS Provider: CodeCommit Version: 1 Configuration: BranchName: !Ref BranchName OutputArtifactFormat: CODE_ZIP PollForSourceChanges: false RepositoryName: !GetAtt CodeCommitRepository.Name Name: SourceAction OutputArtifacts: - Name: !Ref PipelineSourceArtifact Region: !Ref AWS::Region RunOrder: 1 Name: Source - Actions: - ActionTypeId: Category: Build Owner: AWS Provider: CodeBuild Version: 1 Configuration: ProjectName: !Ref CodeBuildProject InputArtifacts: - Name: !Ref PipelineSourceArtifact Name: Build OutputArtifacts: - Name: !Ref PipelineBuildArtifact Region: !Ref AWS::Region RunOrder: 1 Name: Build - Actions: - ActionTypeId: Category: Deploy Owner: AWS Provider: ECS Version: 1 Configuration: ClusterName: !Ref ECSClusterName FileName: !Ref ImageDefinitionFileName ServiceName: !Ref ECSServiceName InputArtifacts: - Name: !Ref PipelineBuildArtifact Name: Deploy Region: !Ref AWS::Region RunOrder: 1 Name: Deploy
Code language: YAML (yaml)

Stagesプロパティがポイントです。
本プロパティの3つ目の要素が、ECS(Fargate)にデプロイするステージです。

ActionTypeIdプロパティで、ECS(Fargate)にデプロイするように設定します。
Configurationプロパティで、デプロイするイメージやデプロイ先を設定します。FileNameプロパティで先述のimagedefinitions.jsonファイルのファイル名を指定します。ClusterNameおよびServiceNameプロパティでデプロイ先のECSクラスター・サービスを指定します。
InputArtifactsプロパティは、本ステージで使用するアーティファクトを設定します。ポイントはビルドステージで指定したOutputArtifactsプロパティの値と同一にする必要があるという点です。ビルドステージで生成されたアーティファクトを使ってデプロイするためです。

CodePipeline用のIAMロールを確認します。

Resources: CodePipelineRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - codepipeline.amazonaws.com Action: - sts:AssumeRole Policies: - PolicyName: PipelinePolicy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - codecommit:CancelUploadArchive - codecommit:GetBranch - codecommit:GetCommit - codecommit:GetRepository - codecommit:GetUploadArchiveStatus - codecommit:UploadArchive Resource: - !GetAtt CodeCommitRepository.Arn - Effect: Allow Action: - codebuild:BatchGetBuilds - codebuild:StartBuild - codebuild:BatchGetBuildBatches - codebuild:StartBuildBatch Resource: - !GetAtt CodeBuildProject.Arn - Effect: Allow Action: - s3:PutObject - s3:GetObject - s3:GetObjectVersion - s3:GetBucketAcl - s3:GetBucketLocation Resource: - !Sub "arn:aws:s3:::${BucketName}" - !Sub "arn:aws:s3:::${BucketName}/*" - Effect: Allow Action: - ecs:* Resource: "*" - Effect: Allow Action: - iam:PassRole Resource: "*" Condition: StringLike: iam:PassedToService: - ecs-tasks.amazonaws.com
Code language: YAML (yaml)

ポイントはECSに関する権限と、iam:PassRoleです。
これらの権限はECSにイメージをデプロイするために必要となります。

このIAMロールはPipelineのRoleArnプロパティで参照されますが、本プロパティは以下の通り説明されています。

The Amazon Resource Name (ARN) for CodePipeline to use to either perform actions with no actionRoleArn, or to use to assume roles for actions with an actionRoleArn.

AWS::CodePipeline::Pipeline

つまり今回のデプロイステージにおいては、このIAMロールを使用してECSにイメージをデプロイすることになります。

このIAMロールの信頼ポリシーを確認します。
プリンシパルが「codepipeline.amazonaws.com」とありますので、CodePipelineが本IAMロールを使って、例えばECSの全アクションを実行することができることなります。

ただし今回のDockerイメージをデプロイするというアクションは、ECSタスクが実行することになります。
ですから本IAMロールにiam:PassRoleの権限を与え、ECSタスクに本IAMロールをパスすることを許可します。
この挙動はResourceに「*」を、Conditionでパスする先が「ecs-tasks.amazonaws.com」サービスであることを条件とすることで実装します。

(参考)ECSサービス

Resources: Service: Type: AWS::ECS::Service Properties: Cluster: !Ref Cluster DesiredCount: 0 LaunchType: FARGATE NetworkConfiguration: AwsvpcConfiguration: SecurityGroups: - !Ref ContainerSecurityGroup Subnets: - !Ref ContainerSubnet ServiceName: !Sub "${Prefix}-service" TaskDefinition: !Ref TaskDefinition
Code language: YAML (yaml)

特別な設定は行いません。

1点ポイントがあります。
DesiredCountプロパティで、ECSサービス上に作成するタスク数を0にします。
この値が1以上の場合は、CloudFormationスタック作成時に、タスク作成を試みることになります。
しかし初期構築時はECRにイメージがプッシュされていないため、タスク作成に失敗します。
ですから本プロパティを0とすることで、タスクを生成しないように設定します。

(参考)アプリコンテナ

Dockerfile

FROM amazonlinux RUN yum update -y && yum install python3 python3-pip -y RUN pip3 install bottle COPY main.py ./ CMD ["python3", "main.py"] EXPOSE 8080
Code language: YAML (yaml)

冒頭でご紹介したページと同様です。

Amazon Linux 2をベースとして、PythonおよびWebフレームワークBottleをインストールします。
アプリロジックを記載したPythonスクリプト(main.py)をコピーし、これを実行するように設定します。
アプリは8080/tcpでHTTPリクエストを待ち受けますので、このポートを公開します。

main.py

from bottle import route, run @route('/') def hello(): return 'Hello CodePipeline.' if __name__ == '__main__': run(host='0.0.0.0', port=8080)
Code language: YAML (yaml)

Bottleを使用して、簡易的なWebサーバを構築します。
8080/tcpでHTTPリクエストを待ち受け、「Hello CodePipeline.」を返すという単純な構成です。

環境構築

CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。

CloudFormationスタックを作成し、スタック内のリソースを確認する

CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。

各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。

  • ECSクラスター:fa-076-cluster
  • ECSサービス:fa-076-service
  • ECR:fa-076
  • CodeCommit:fa-076
  • CodeBuildプロジェクト:fa-076
  • CodePipeline:fa-076
  • SSMパラメータのパラメータ名1:fa-076-DcokerHubUsername
  • SSMパラメータのパラメータ名2:fa-076-DcokerHubPassword
  • EC2インスタンス:i-0024a483b8e4a8778

作成されたリソースをAWS Management Consoleから確認します。
ECS(Fargate)を確認します。

Detail of ECS 1.
Detail of ECS 2.

正常にECSクラスター・サービス・タスクが作成されています。
Desired Countが0であり、タスクが生成されていないことがわかります。

ECRを確認します。

Detail of ECR 1.

空です。
パイプラインが実行されることによって、ここにイメージがプッシュされることになります。

CodeCommitを確認します。

Detail of CodeCommit 1.

こちらも空です。
ここにコードをプッシュすることによって、パイプラインが実行されることになります。

CodeBuildを確認します。

Detail of CodeBuild 1.
Detail of CodeBuild 2.

CloudFormationテンプレートで指定した通りに、CodeBuildが作成されています。

CodePipelineを確認します。

Detail of CodePipeline 1.

パイプラインの実行に失敗しています。
これはCloudFormationスタック作成時に、CodeCommitおよびその内部にブランチが作成されたことがきっかけとして、パイプラインが実行されたためです。
現時点ではCodeCommitにコードをプッシュしていないため、パイプライン実行の過程でエラーが発生したということです。

動作確認

パイプライン実行 1回目

準備が整いましたので、CodeCommitにコードをプッシュします。

まずCodeCommitをプルします。

$ git clone https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/fa-076 Cloning into 'fa-076'warning: You appear to have cloned an empty repository.
Code language: Bash (bash)

空のリポジトリがプルされました。

リポジトリにDockerfileおよびmain.pyを加えます。

$ ls -al total 8 drwxrwxr-x 3 ec2-user ec2-user 51 Aug 14 11:56 . drwxrwxr-x 3 ec2-user ec2-user 20 Aug 14 11:56 .. -rw-rw-r-- 1 ec2-user ec2-user 187 Aug 12 11:01 Dockerfile drwxrwxr-x 7 ec2-user ec2-user 119 Aug 14 11:56 .git -rw-rw-r-- 1 ec2-user ec2-user 681 Aug 13 12:30 main.py
Code language: Bash (bash)

2ファイルをCodeCommitにプッシュします。

$ git add . $ git commit -m "first commit" [master (root-commit) 738b02d2] first commit ... 2 files changed, 39 insertions(+) create mode 100644 Dockerfile create mode 100644 main.py $ git push ... * [new branch] master -> master
Code language: Bash (bash)

正常にプッシュできました。

改めてCodeCommitを確認します。

Detail of CodeCommit 2.

確かに2ファイルがプッシュされています。

CodePipelineが実行が開始されました。
しばらく待機します。

Detail of CodePipeline 2.

パイプラインの実行が正常に完了しました。

ECRを確認します。

Detail of ECR 2.

イメージがプッシュされています。
CodeBuildによってビルドされたイメージが、ECRにプッシュされたということです。

ECSサービスの設定変更を行います。

Detail of ECS 33.

Desired Countを0から1に更新します。

ECSサービスを確認します。

Detail of ECS 5.

1つタスクが作成されました。

作成されたタスクの詳細を確認します。

Detail of ECS 4.

自動的に割り当てられたプライベートアドレスが「10.0.2.246」であることがわかります。

コンテナにHTTPリクエストを行うために、EC2インスタンスにアクセスします。
インスタンスへのアクセスはSSM Session Managerを使用します。

% aws ssm start-session --target i-0024a483b8e4a8778 Starting session with SessionId: root-00be6118d01682117 sh-4.2$
Code language: Bash (bash)

SSM Session Managerの詳細につきましては、以下のページをご確認ください。

curlコマンドを使って、タスク内のコンテナにアクセスします。

sh-4.2$ curl http://10.0.2.246:8080 Hello CodePipeline.
Code language: Bash (bash)

コンテナから応答がありました。
確かにBottleアプリで設定した文字列です。
このことから、CodePipelineで作成したパイプラインを実行することよって、イメージがビルドされて、このイメージからECSタスクが生成されたことがわかります。

パイプライン実行 2回目

改めてパイプラインを実行します。

main.pyのコードを少し変更した上で、CodeComitにプッシュします。

$ cat main.py from bottle import route, run @route('/') def hello(): return 'Hello CodePipeline. Update!' if __name__ == '__main__': run(host='0.0.0.0', port=8080)
Code language: Bash (bash)
$ git add . $ git commit -m "second commit" [master e3dc93e] second commit Committer: EC2 Default User <ec2-user@ip-172-31-26-46.ap-northeast-1.compute.internal> ... 1 file changed, 1 insertion(+), 1 deletion(-) $ git push ... To https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/fa-076 738b02d..e3dc93e master -> master
Code language: Bash (bash)

CodePipelineを確認します。

Detail of CodePipeline 3.

再びパイプラインが実行されました。
しばらく待つと、パイプラインが完了します。

改めてECRを確認します。

Detail of ECR 3.

新たなイメージがビルドされプッシュされました。
今後はこちらのイメージを使って、ECSタスクが生成されるはずです。

デプロイ途中のECSサービスの挙動を確認します。

Detail of ECS 7.

ECSデプロイ途中は2つのタスクが動作していることが確認できます。
1つは元々動作していたタスクです。このタスクは古いイメージから生成されたものです。
もう1つは新たに生成されたタスクです。このタスクは新しいイメージから生成されたものです。
このようにデプロイが始まると、新たなタスクが生成され、新旧タスクが併存するタイミングがあります。

デプロイが完了した後に、改めてECSサービスを確認します。

Detail of ECS 8.

新しいタスクだけが動作しています。
古いタスクは停止しました。
このようにデプロイが完了すると、新しいイメージから生成されたタスクだけが動作します。

新たに生成されたタスクの詳細を確認します。

Detail of ECS 9.

新たに生成されたタスクに割り当てられたプライベートアドレスは「10.0.2.224」でした。

改めてEC2インスタンスからECSタスクにアクセスします。

sh-4.2$ curl http://10.0.2.224:8080/ Hello CodePipeline. Update!
Code language: Bash (bash)

コンテナから応答がありました。
更新したコードを反映した文字列です。
このことから、CodePipelineで作成したパイプラインを実行することよって、最新のイメージをECS(Fargate)にデプロイできたことがわかります。

まとめ

今回はCodePipelineにデプロイステージを作成して、ビルドしたイメージをFargateにデプロイすることができました。

タイトルとURLをコピーしました