LoginSignup
5
3

More than 3 years have passed since last update.

GitHubActionsとCodePipelineを連携させてDeployする。

Last updated at Posted at 2021-04-22

やりたいこと

  • ソース管理はGitHubを利用する。
  • GitHub flow
  • 最終的にはAWS上のEC2に対してDeployを行う。
  • master push時には承認なしにDevelop環境にDeployしたい。
  • tag push時にはStaging〜Production環境にDeployしたいが、各環境Deploy時に承認が必要。

前提

  • 諸々の事情で、AWS側でGitHubをSourceArtifactsとして動作することができない。
  • 承認は、これも諸事情で、AWS側で実施したい。
  • 本来はProduction環境はAWS上別アカウントにした方がベターなのでは?とは思うが、小さいシステムなのと、まだ最終的なプロダクトではないということで、同一アカウントのまま。別アカウントの場合は、クロスアカウントの考慮が必要。

結論

  • BuildはGitHub Actionsで実施する。
  • Build後、Zip化してS3にPushし、そこからCodePipelineを動作させることとする。
  • CodePipeline側でApproveを追加し、承認行為をさせる。そこからはCodeDeployでDeployする。

実際の内容。

  • master pushとtag pushでGitHub Actionsのymlを分けてしまう。(場合わけ利用するのが面倒になった。)
  • CodePipelineも二つに分離する。

とても安易なので、他でも参考になるかどうかは不明。

GitHub Actionsのyml

PRの際にはBuildのみ動作するようにする。S3 Pushは今回手間を省くために以下を利用した。
https://github.com/opspresso/action-s3-sync

AdoptOpenJDKを利用したかったので、setup-java@v2を利用した。
https://github.com/actions/setup-java

zip化する時に、zip -jしないとCodeDeployが動作しないので注意が必要。
これはzip直下にAppSpec.ymlが必要なため
https://infraya.work/posts/codedeploy_appspec_not_found_error/

またAWSのKEY IDとかSECRETSは事前にGitHub側に登録が必要。当たり前だけど、yml内に直書きしちゃダメ。アタックされるよ。
https://docs.github.com/ja/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-a-repository

/.github/workflows/build.yml
name: Java CI with Maven

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Set up JDK 1.15
      uses: actions/setup-java@v2
      with:
        java-version: '15'
        distribution: 'adopt'
    - name: Build with Maven
      run: mvn -B verify --file pom.xml
      env:
        TZ: 'Asia/Tokyo'
    - name: App Packaging
      run: |
        mkdir ./publish
        zip -j ./publish/foo.zip ./target/product.jar ./script/stop.sh ./script/start.sh ./appspec.yml
    - name: Publish to AWS S3
      # この処理はMaster Push時のみに動かしたいので。
      if: github.ref == 'refs/heads/master'
      uses: opspresso/action-s3-sync@master
      env:
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        AWS_REGION: "ap-northeast-1"
        FROM_PATH: "./publish"
        DEST_PATH: "s3://artifact/"

tag pushの際にはVersion_から始まるtagのみを対象とする。本来はこの際にBuildさせるわけではなく、上記Master push時のartifactsを利用すべきだと思うが、面倒臭くなったので省略。

/.github/workflows/tag-push.yml
name: Release Tag Push Event

on:
  push:
    tags: 
      - Version_*

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Set up JDK 1.15
      uses: actions/setup-java@v2
      with:
        java-version: '15'
        distribution: 'adopt'
    - name: Build with Maven
      run: mvn -B verify --file pom.xml
      env:
        TZ: 'Asia/Tokyo'
    - name: App Packaging
      run: |
        mkdir ./publish
        zip -j ./publish/foo-prd.zip ./target/product.jar ./script/stop.sh ./script/start.sh ./appspec.yml
    - name: Publish to AWS S3
      uses: opspresso/action-s3-sync@master
      env:
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        AWS_REGION: "ap-northeast-1"
        FROM_PATH: "./publish"
        DEST_PATH: "s3://artifact/"

S3の状態

こうすると、S3には以下のような状態になる。

s3 - artifact - foo.zip
              - foo-prd.zip

それぞれのzipファイル格納時に反映させるように、CodePipelineを組むことにする。なお、S3はVersioning有効化しておく必要があるので、注意が必要。
https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/tutorials-simple-s3.html

Develop環境用CodePipeline

Terraformで作る。
IAMとかS3とかも色々あるんだけど、細かい箇所は省略。
本来はSource変更検知はEventBridgeでちゃんと作った方がいいのだが、PollForSourceChangesでやってしまう。そんなにDeploy頻度が多くなかったので。
https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/update-change-detection.html

codepipeline.tf

################################################################################
# CodePipeline for Build Account (Develop)                                     #
################################################################################
resource "aws_codepipeline" "cop-dev" {

  name     = "develop-pipeline"
  role_arn = "${aws_iam_role.iam-cop.arn}"

  artifact_store {
    type     = "S3"
    location = "${aws_s3_bucket.s3-artifact.bucket}"
  }

  stage {
    name = "Source"

    action {
      run_order        = 1
      name             = "Source"
      category         = "Source"
      owner            = "AWS"
      provider         = "S3"
      version          = "1"
      output_artifacts = ["SourceArtifact"]

      configuration = {
        S3Bucket       = "${aws_s3_bucket.s3-artifact.bucket}"
        S3ObjectKey    = "foo.zip"
        PollForSourceChanges = true

      }
    }
  }

  stage {
    name = "Deploy"

    action {
      run_order        = 2
      name             = "Deploy"
      category         = "Deploy"
      owner            = "AWS"
      provider         = "CodeDeploy"
      version          = "1"
      input_artifacts  = ["SourceArtifact"]

      configuration = {
        ApplicationName = "ec2-deploy"
        DeploymentGroupName = "ec2-deploy-group"
      }
    }
  }
}

Production用Pipeline

一連の動作となるように、Staging承認〜Staging Deploy〜Production承認〜ProductionDeployとする。
StageのNameは同名は不可。なので例えばApproveで言うと、StagingApproveとProductionApproveと分けて作成している。また空白は許容されていないのでStaging ApproveみたいなNameはつけられない。

codepipeline.tf
################################################################################
# CodePipeline for Build Account (Production)                                  #
################################################################################
resource "aws_codepipeline" "cop-prd" {

  name     = "production-pipeline"
  role_arn = "${aws_iam_role.iam-cop.arn}"

  artifact_store {
    type     = "S3"
    location = "${aws_s3_bucket.s3-artifact.bucket}"
  }

  stage {
    name = "Source"

    action {
      run_order        = 1
      name             = "Source"
      category         = "Source"
      owner            = "AWS"
      provider         = "S3"
      version          = "1"
      output_artifacts = ["SourceArtifact"]

      configuration = {
        S3Bucket       = "${aws_s3_bucket.s3-artifact.bucket}"
        S3ObjectKey    = "foo-prd.zip"
        PollForSourceChanges = true

      }
    }
  }
    stage {
    name = "StagingApprove"

    action {
      run_order        = 2
      name             = "Approval"
      category         = "Approval"
      owner            = "AWS"
      provider         = "Manual"
      version          = "1"

      configuration = {
        CustomData = "StagingReleaseの承認をお願いします"
      }
    }
  }

  stage {
    name = "StagingDeploy"

    action {
      run_order        = 3
      name             = "Deploy"
      category         = "Deploy"
      owner            = "AWS"
      provider         = "CodeDeploy"
      version          = "1"
      input_artifacts  = ["SourceArtifact"]

      configuration = {
        ApplicationName = "ec2-deploy-stg"
        DeploymentGroupName = "ec2-deploy-stg-group"
      }
    }
  }

  stage {
    name = "ProductionApprove"

    action {
      run_order        = 4
      name             = "Approval"
      category         = "Approval"
      owner            = "AWS"
      provider         = "Manual"
      version          = "1"

      configuration = {
        CustomData = "ProductionReleaseの承認をお願いします"
      }
    }
  }

  stage {
    name = "ProductionDeploy"

    action {
      run_order        = 5
      name             = "Deploy"
      category         = "Deploy"
      owner            = "AWS"
      provider         = "CodeDeploy"
      version          = "1"
      input_artifacts  = ["SourceArtifact"]

      configuration = {
        ApplicationName = "ec2-deploy-prd"
        DeploymentGroupName = "ec2-deploy-prd-group"
      }
    }
  }
}

CodeDeploy

単純なEC2に対するIn-place Deploy
Fargate起動やBlue/Green Deployとかは考えてない。

また、これはDevelop環境用なので、Staging用、Production用はこれをコピーして作っている。本来は、Module化して、必要な箇所だけ変数化するべきだが、3つだし、今後増える可能性もあまりなかったので、コピー。こんなのが多すぎるな。

codedeploy.tf
#--------------------------------------------------
# デプロイプロジェクト
#--------------------------------------------------
resource "aws_codedeploy_app" "cod-app" {
    compute_platform = "Server"
    name = "ec2-deploy"
}
# リリース時に参照するconfiguration
# 設定項目はリリースの際にどの程度のホストが正常起動している必要があるかのみ
resource "aws_codedeploy_deployment_config" "cod-config" {
  deployment_config_name = "ec2-deploy-config"

  minimum_healthy_hosts {
    type  = "HOST_COUNT"
    value = 0
  }
}

# リリース対象のサーバを識別するための設定
# `"application":"deploy"` というタグがついたEC2インスタンスを対象としている
resource "aws_codedeploy_deployment_group" "cod-group" {
  app_name               = "${aws_codedeploy_app.cod-app.name}"
  deployment_group_name  = "ec2-deploy-group"
  service_role_arn       = "${aws_iam_role.iam-cod.arn}"
  deployment_config_name = "${aws_codedeploy_deployment_config.cod-config.id}"

  ec2_tag_set {
    ec2_tag_filter {
      type  = "KEY_AND_VALUE"
      key   = "application"
      value = "deploy"
    }
  }

  auto_rollback_configuration {
    enabled = true
    events  = ["DEPLOYMENT_FAILURE"]
  }
}
5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3