17
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

SpringBootで構築したアプリケーションがgitにpushされたらCodeDeployで自動デプロイされる仕組み

Last updated at Posted at 2016-08-09

SpringBootで構築したアプリケーションがgitにpushされたら自動でデプロイされる仕組みを構築した記録です。
APサーバはEC2を使用していますが、オンプレミスのサーバでもCodeDeploy Agentをインストールして適切なNW設定を行うことにより同様のことが可能です。

構成

  • Gitリポジトリ
  • GitLab(なんでも良い)
  • CI
  • Jenkins
  • Test & Build
  • Maven(Gradleでもそんなに変わらない)
  • APサーバ
  • SpringBoot 1.3.x on EC2
  • Lambda function
  • Java8(node.jsとかPythonでもそんなに変わらない)

大まかな流れ

開発者

  1. 任意の(ここでは例としてdevelop) branchにpush

Jenkins

  1. 5分毎にポーリングし、develop branchが更新されていればpull
  2. Test & Build(Maven)
  3. Test & Buildが成功したらS3にリビジョンをアップロード

Lambda

  1. リビジョンがアップロードされるとそれをトリガーとしてLambda functionが起動
  2. Lambda functionがCodeDeployにデプロイリクエストを投げる

CodeDeploy

  1. CodeDeployから各インスタンスにSpringBootアプリがdeployされ、新しいバージョンのアプリが起動する

図にするとこんな感じになります。
CI.png

問題点

SpringBootはアプリのjarファイル自体がAPサーバとして実行されるので、デプロイ時にサーバ(jar)のstop/sartが必要

Tomcatにwarをデプロイする場合だとそのままwarファイルを置けばいいし、restartする場合でもサービスとして起動しているのでコマンド一発で楽ちん。
それに対してSpringBootの組み込みサーバを利用した場合、実行されているjarファイルごと置き換えるので、プロセスを探して停止、新しいjarを起動みたいにしないといけない。

これはSpringBoot1.3以降で利用できるFully Executable Jarsを作成、SpringBootアプリをserviceとして起動することで解決しました。

必要な作業

  • アプリ
  • SpringBootアプリをservice起動するためにmavenもしくはgradle設定でFully Executable Jarsを作成できるように設定
  • CodeDeploy
  • APサーバにCodeDeploy Agentをインストール
  • EC2インスタンスにIAMロールを紐付ける
  • CodeDeploy設定
  • appspec.ymlの作成
  • 各フェーズで呼び出されるshell scriptの作成
  • Jenkins
  • gitポーリング設定
  • Test & Build
  • S3にリビジョンをアップロードするshell script
  • Lambda
  • CodeDeploy起動用Lambda function
  • 実行権限の設定
  • Triggerの設定

[アプリ]SpringBootアプリをservice起動するためにmavenもしくはgradle設定でFully Executable Jarsを作成できるように設定

SpringBoot1.3以降で利用できるFully Executable Jarsを作成します。
参照: Spring BootのFully Executable Warを試す

CodeDeploy

以下を参考にCodeDeployの設定を行います。
EC2デプロイのためにCodeDeployを導入する

appspec.ymlは今回の構成だと以下のような流れになります。

  1. 起動中のSpringBoot serviceを停止する
  2. Deploy("target/apps.jar"をlinuxサーバの"/var/src/"に配置する)
  3. SpringBoot serviceを起動
appspec.yml
version: 1.0.0
os: linux
files:
  - source: target/apps.jar
    destination: /var/src/
    overwrite: yes
hooks:
  ApplicationStop:
    - location: scripts/appstop.sh
      timeout: 300
      runas: root
  ApplicationStart:
    - location: scripts/appstart.sh
      timeout: 300
      runas: root

SpringBootアプリをserviceとして起動しているので、scripts/appstop.shおよびscripts/appstart.shはそれぞれ以下の様な内容になります。

appstop.sh
#!/bin/sh
service [:service名] stop
appstart.sh
#!/bin/sh
service [:service名] start

[Jenkins]gitポーリング設定

詳細はググればすぐ出てくるので割愛しますが、5分間隔でgitリポジトリをポーリングします。

[Jenkins]Test & Build

MavenなりGradleでTest & Buildします。

[Jenkins]S3にリビジョンをアップロードする

aws deploy pushでjarファイルをS3にアップロードします。
ビルドの設定でシェルの実行を選択し、以下のようにします。
ここではJenkinsサーバにIAMのキーペアを持たせたくなかったので環境変数に一時的にキーペアをセットしています。
application-nameとs3-locationを指定してdeploy。

#!/bin/bash
export AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXXX
export AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
export AWS_DEFAULT_REGION=ap-northeast-1
export AWS_DEFAULT_OUTPUT=json

cd ${WORKSPACE}

aws deploy push --application-name [:applicationName] --s3-location s3://[:bucketName]/[:key] --source ./ 

[Lambda]CodeDeploy起動用Lambda function(Java)

S3にリビジョンがアップロードされたらAPサーバにjar(zip)ファイルをデプロイするLambda functionを作成します。
以下、説明はソースコメントに記します。

CodeDeoloy.java
import com.amazonaws.regions.Region;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.codedeploy.AmazonCodeDeploy;
import com.amazonaws.services.codedeploy.AmazonCodeDeployClient;
import com.amazonaws.services.codedeploy.model.BundleType;
import com.amazonaws.services.codedeploy.model.CreateDeploymentRequest;
import com.amazonaws.services.codedeploy.model.RevisionLocation;
import com.amazonaws.services.codedeploy.model.RevisionLocationType;
import com.amazonaws.services.codedeploy.model.S3Location;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.S3Event;
import com.amazonaws.services.s3.event.S3EventNotification.S3Entity;
import com.amazonaws.services.s3.event.S3EventNotification.S3ObjectEntity;

/**
 * S3にリビジョンがアップロードされたらAPサーバにjar(zip)ファイルをデプロイする。<br />
 * TriggerはObjectCreatedByCompleteMultipartUpload(ObjectPutではない)。
 * 
 * @author ryosukehayashi
 *
 */
public class CodeDeoloy implements RequestHandler<S3Event, Object> {

  /**
   * handler
   */
  public Object handleRequest(S3Event event, Context context) {

    S3Entity s3Entity = event.getRecords().get(0).getS3();
    S3ObjectEntity objectEntity = s3Entity.getObject();

    // 任意。ここでは汎用的に使うため、S3のkeyからアプリケーション名を取得するようにしている
    String applicationName = getApplicationName(objectEntity.getKey());
    // 任意。ここではバケット名をデプロイメントグループ名に設定
    String deploymentGroupName = s3Entity.getBucket().getName();
    String description = String.format("Deployed by LambdaDeployFunction %s",
        DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss.SSSxxxxx zzz")
            .format(ZonedDateTime.now(ZoneId.of("JST", ZoneId.SHORT_IDS))));

    // リビジョンのアップロード先(S3)
    S3Location s3Location = new S3Location();
    s3Location.setBucket(s3Entity.getBucket().getName());
    s3Location.setKey(objectEntity.getKey());
    s3Location.setETag(objectEntity.geteTag());
    s3Location.setVersion(objectEntity.getVersionId());
    // jarファイルの場合は"zip"で良い
    s3Location.setBundleType(BundleType.Zip);

    RevisionLocation revision = new RevisionLocation();
    // リビジョンの種類を指定。S3の他にGitHubが指定できる。
    revision.setRevisionType(RevisionLocationType.S3);
    revision.setS3Location(s3Location);

    // デプロイリクエスト
    CreateDeploymentRequest createDeploymentRequest = new CreateDeploymentRequest();
    createDeploymentRequest.setApplicationName(applicationName);
    // CodeDeployDefault.OneAtATime: 一台ずつデプロイ
    // CodeDeployDefault.AllAtOnce: 一気に全台デプロイ
    // CodeDeployDefault.HalfAtATime: 半分ずつデプロイ
    createDeploymentRequest.setDeploymentConfigName("CodeDeployDefault.OneAtATime");
    createDeploymentRequest.setDeploymentGroupName(deploymentGroupName);
    createDeploymentRequest.setRevision(revision);
    createDeploymentRequest.setDescription(description);

    AmazonCodeDeploy codeDeploy = new AmazonCodeDeployClient();
    codeDeploy.setRegion(Region.getRegion(Regions.AP_NORTHEAST_1));
    // デプロイリクエストを投げる
    codeDeploy.createDeployment(createDeploymentRequest);

    return null;
  }
}

[Lambda]実行権限を設定

以下の様な内容のpolicyをattachしたroleを作成し、Lambda functionの"Existing role"に設定します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["logs:*"],
      "Resource": "arn:aws:logs:*:*:*"
    },
    {
      "Effect": "Allow",
      "Action": ["codedeploy:*"],
      "Resource": ["*"]
    }
  ]
}

[Lambda]Trigger設定

作成したLambda functionのTriggerを設定します。
注意点として、aws deploy pushでアップロードされた時のイベントはObjectPutではなくObjectCreatedByCompleteMultipartUploadになるので、Event typeに"ObjectCreatedByCompleteMultipartUpload"を設定します。


以上で、開発者が任意のブランチにpushすると複数サーバにローリングデプロイされる流れが完成です。

17
23
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
17
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?