やりたいこと
- ソース管理は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
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を利用すべきだと思うが、面倒臭くなったので省略。
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"]
}
}
```