あえて継続的にデプロイしないElasticBeanstalkへのデプロイ環境を構築しました。

お疲れさまです。@naokiurです。

最近ElasticBeanstalkを用いて、
アプリケーションを構築・稼働させることが、たまにあります。

開発を続けていく上で、欲しい仕組みの一つとして、
継続的デプロイ（Continuous Deployment）があるかと存じます。
AWSの公式でも、クイックスタートとして
[AWS CodePipeline を使用した AWS Elastic Beanstalk 環境へのデプロイ](https://aws.amazon.com/jp/quickstart/architecture/blue-green-deployment/)が掲載されています。
（Continuous DeploymentとContinuous Deliveryは意味が異なる、ということを初めて認識しました…。）

これはとても素晴らしいものだと思うのですが、
局所的な例で恐縮ながら、以下があるとき、
この例を組み替えると、よりマッチしたもの
（いざリリースするときにできなくなったりしなかったり、
作成するリソースを減らすことができたり）
になるのでは、とおもい、検討致しました。

* 前提
    * アプリケーションのリリースによるシステム閉塞（数分〜数十分）が認められる
    * masterブランチにマージされ、リリース可能となってから、実際にリリースするまで1日以上のラグが発生する場合がある
        * リリースフローに承認がいるとか
            * そのリリースフローを改善したほうがよい、という話はさておき…
        * Manual Approvalは7日間以内に承認・拒否する必要があるので、それ以上間が開くときとか


# 内容
## 構成図
<img width="232" alt="structure.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129313/a6edb898-4d8c-2a00-baac-9a0d3581164a.png">


ビルドまでと、ビルド以降を、CodeBuildとCodePipelineに分けました。
エンジニアがmasterブランチにマージしたら、
CodeBuildによってS3に格納します。

その後、
 `CodePipelineの「変更をリリースする」クリックする`ことによって、
ElasticBeanstalkにデプロイされる、
という流れです。
<img width="201" alt="button_for_release.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129313/914743db-61f8-625b-7c9a-c2a6aae7362f.png">


最近個人的にAWS CDKが好みなのですが、
今回はCloudFormationで構築しました。
AWS CDKは、`ElasticBeanstalkDeployAction`的なものがまだ実装されてないとお見受けしたため…。
https://github.com/aws/aws-cdk/issues/2516

## ElasticBeanstalkサンプルアプリケーション
簡単Djangoアプリケーションを予め稼働させておきます。
* アプリケーション名
    * MyApp
* 環境名
    * MySampleEnvironment

`/quickstart`にアクセスすると、
テキストが返ってくる、という単純なものです。
<img width="567" alt="quickstart_before.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129313/39ab2eaa-bbfb-dcff-8828-f48ef931b138.png">


## CodeBuild構築
ビルドしてS3に格納するCodeBuildです。
Github Access Tokenは予めSecrets Managerに入れておきます。
デプロイに使用する際、バージョニングが必要なので、
S3のバージョニングは `true`に設定しておきます。

```yml
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に蓄積していくという動きをすると思うのですが、毎回別名で保存されるので）

```yml

    # 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します。
<img width="537" alt="django_modify.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129313/2f6fe11c-edfa-fdaa-075a-3c520c908f2d.png">

CodeBuildがmasterブランチへのpushを検知し、動きます。
<img width="456" alt="codebuild_begin_build.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129313/e3107b52-3b3f-da6d-1a9b-68444ab74b4f.png">


CodeBuild完了後、手動で、CodePipelineを実行します。
<img width="833" alt="codepipeline_start_source.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129313/6691ef48-ff1c-438b-a065-86983be3bf47.png">


返却されるテキストが変わりました！
<img width="591" alt="quickstart_after.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129313/8f7d6d99-41e0-76e0-7abe-6140ad63b594.png">


# 今後
ビジネスに応じて、参考になれば幸いです。
パラメータ化した方が使いやすくなる部分が結構あると思うので、
もし実務で使うときがあったら、そのようなテンプレートにしていけたらと思っています。

`デプロイする際に、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/