お疲れさまです。@naokiurです。
最近ElasticBeanstalkを用いて、
アプリケーションを構築・稼働させることが、たまにあります。
開発を続けていく上で、欲しい仕組みの一つとして、
継続的デプロイ(Continuous Deployment)があるかと存じます。
AWSの公式でも、クイックスタートとして
AWS CodePipeline を使用した AWS Elastic Beanstalk 環境へのデプロイが掲載されています。
(Continuous DeploymentとContinuous Deliveryは意味が異なる、ということを初めて認識しました…。)
これはとても素晴らしいものだと思うのですが、
局所的な例で恐縮ながら、以下があるとき、
この例を組み替えると、よりマッチしたもの
(いざリリースするときにできなくなったりしなかったり、
作成するリソースを減らすことができたり)
になるのでは、とおもい、検討致しました。
- 前提
- アプリケーションのリリースによるシステム閉塞(数分〜数十分)が認められる
- masterブランチにマージされ、リリース可能となってから、実際にリリースするまで1日以上のラグが発生する場合がある
- リリースフローに承認がいるとか
- そのリリースフローを改善したほうがよい、という話はさておき…
- Manual Approvalは7日間以内に承認・拒否する必要があるので、それ以上間が開くときとか
- リリースフローに承認がいるとか
内容
構成図
ビルドまでと、ビルド以降を、CodeBuildとCodePipelineに分けました。
エンジニアがmasterブランチにマージしたら、
CodeBuildによってS3に格納します。
その後、
CodePipelineの「変更をリリースする」クリックする
ことによって、
ElasticBeanstalkにデプロイされる、
という流れです。
最近個人的にAWS CDKが好みなのですが、
今回はCloudFormationで構築しました。
AWS CDKは、ElasticBeanstalkDeployAction
的なものがまだ実装されてないとお見受けしたため…。
https://github.com/aws/aws-cdk/issues/2516
ElasticBeanstalkサンプルアプリケーション
簡単Djangoアプリケーションを予め稼働させておきます。
* アプリケーション名
* MyApp
* 環境名
* MySampleEnvironment
/quickstart
にアクセスすると、
テキストが返ってくる、という単純なものです。
CodeBuild構築
ビルドしてS3に格納するCodeBuildです。
Github Access Tokenは予めSecrets Managerに入れておきます。
デプロイに使用する際、バージョニングが必要なので、
S3のバージョニングは true
に設定しておきます。
Resources:
# First: Build Source and Zip, Save to S3(Build Bucket).
## This Need
## * S3 Bucket for after Build.
## * This Bucket need versioning for deploy to ElasticBeanstalk.
## * CodeBuild Project
## * GithubCredential for CodeBuild Project
## * IAM Role for CodeBuild Project
DjangoBuildRole:
Type: AWS::IAM::Role
Properties:
RoleName: DjangoBuildRole
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: Allow
Principal:
Service:
- codebuild.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyName: DjangoBuildRoleRolePolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: S3FullAccess
Effect: Allow
Action:
s3:*
Resource:
- '*'
- Sid: CloudWatchLogAccess
Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource:
- '*'
SysopsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub "naokiur-sysops-bucket-${AWS::AccountId}"
VersioningConfiguration:
Status: Enabled
EBBuildGithubCredential:
Type: AWS::CodeBuild::SourceCredential
Properties:
AuthType: PERSONAL_ACCESS_TOKEN
ServerType: GITHUB
Token: '{{resolve:secretsmanager:personal-secrets:SecretString:github-access-token}}'
DjangoAppEBBuild:
Type: AWS::CodeBuild::Project
Properties:
Name: django-app-build-project
Source:
Auth:
Type: OAUTH
BuildSpec: buildspec.yml
GitCloneDepth: 1
Location: https://github.com/naokiur/django_app.git
Type: GITHUB
ServiceRole: !GetAtt DjangoBuildRole.Arn
Environment:
Type: LINUX_CONTAINER
ComputeType: BUILD_GENERAL1_SMALL
Image: aws/codebuild/standard:2.0
Artifacts:
Type: S3
Location: !Ref SysopsBucket
Path: /builds
Name: django_app.zip
Packaging: ZIP
Triggers:
FilterGroups:
- - Type: EVENT
Pattern: PULL_REQUEST_CREATED,PULL_REQUEST_UPDATED,PUSH
- Pattern: refs/heads/master
Type: HEAD_REF
Webhook: true
CodePipeline構築
上記のS3からファイルを取得し、
ElasticBeanstalkにデプロイするCodePipelineです。
CodePipelineのArtifactStore用のS3 Bucketも作成しました。
こっちはバージョニングがいらないと考えたためです。
(OutputArtifacts
で指定した内容を、S3に蓄積していくという動きをすると思うのですが、毎回別名で保存されるので)
# Second: Download Source Zip from S3, Deploy to ElasticBeanstalk.
## This Need
## * S3 Bucket
## * This Bucket is not same with 'First'. It is no need versioning.
## * CodePipeline
## * IAM Role for CodePipeline
CodepipelineBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub "naokiur-codepipeline-bucket-${AWS::AccountId}"
VersioningConfiguration:
Status: Enabled
EBDeployPipelineRole:
Type: AWS::IAM::Role
Properties:
RoleName: EBDeployPipelineRole
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: Allow
Principal:
Service:
- codepipeline.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyName: EBDeployPipelineRolePolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: S3FullAccess
Effect: Allow
Action:
s3:*
Resource:
- '*'
- Sid: EBFullAccess
Effect: Allow
Action:
- elasticbeanstalk:*
- cloudformation:*
- autoscaling:*
- elasticloadbalancing:*
Resource:
- '*'
EBDeployPipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
Name: EBDeployPipeline
RoleArn: !GetAtt EBDeployPipelineRole.Arn
ArtifactStore:
Type: S3
Location: !Ref CodepipelineBucket
Stages:
- Name: Source
Actions:
- Name: download-source
ActionTypeId:
Category: Source
Owner: AWS
Version: "1"
Provider: S3
Configuration:
S3Bucket: !Ref SysopsBucket
S3ObjectKey: builds/django_app.zip
PollForSourceChanges: false
OutputArtifacts:
- Name: SourceOutput
- Name: Deploy
Actions:
- InputArtifacts:
- Name: SourceOutput
Name: deploy
ActionTypeId:
Category: Deploy
Owner: AWS
Version: "1"
Provider: ElasticBeanstalk
Configuration:
ApplicationName: MyApp
EnvironmentName: MySampleEnvironment
トライ
Djangoアプリケーションの内容を変更し、masterへpushします。
CodeBuildがmasterブランチへのpushを検知し、動きます。
CodeBuild完了後、手動で、CodePipelineを実行します。
今後
ビジネスに応じて、参考になれば幸いです。
パラメータ化した方が使いやすくなる部分が結構あると思うので、
もし実務で使うときがあったら、そのようなテンプレートにしていけたらと思っています。
デプロイする際に、AWSコンソール上にログインして、ボタンを押さなければならない
というのがめんどうだな…と思うのですが…
機密性と利便性のトレードオフの部分かも…? と考えています(見当違いだったらすみません)
参考にさせて頂きました
- https://aws.amazon.com/jp/getting-started/tutorials/continuous-deployment-pipeline/
- https://aws.amazon.com/jp/quickstart/architecture/blue-green-deployment/
- https://www.atlassian.com/continuous-delivery/principles/continuous-integration-vs-delivery-vs-deployment
- https://dev.classmethod.jp/cloud/aws/codepipeline-approve-async-processing/