LoginSignup
4
7

More than 3 years have passed since last update.

【CodePipeline×ElasticBeanstalk】JavaアプリケーションをCodePipelineでElasticBeanstalkにCI/CDする その1

Last updated at Posted at 2020-08-06

Javaアプリケーション(データベースから値を取得し、JSON形式で結果を返す)をCodePipelineでElasticBeanstalkにCI/CDするハンズオンです。

長いので、

にの3記事に内容を分割しています。
作業時間は事前準備を除いて、トータル3時間前後を想定。各記事1時間程度が目安。

環境

  • OS:Windows10
  • IDE:Eclipse 2020-03
  • JDK:Amazon Correto 8
  • フレームワーク:Spring Boot
  • AWS
    • CodePipeline
      • CodeCommit
      • CodeBuild
      • CodeDeploy
    • Elastic Beantalk
      • Java SE(Java 8 バージョン 2.10.8)
        → 異なるプラットフォームブランチ(Corretto 11など)でも同手順でできます。
      • RDS:MySQL Community Edition(バージョン8.0.17)

イメージ図

本手順でできる内容のざっくりとしたイメージ図です。
ローカルで編集したコードをコミット → CodeCommitにプッシュすると、CodePipelineにより、アプリケーション実行環境であるElastic Beanstalkまで自動でビルド、デプロイを行ってくれるCI/CDの仕組みを構築します。
image.png

事前準備

①Eclipseが未インストールの場合は、
Pleiades All in One Eclipse ダウンロード(リリース 2020-06) | MergeDoc Project
より最新版(2020年8月3日時点)をダウンロードしてください。STS と Lombok が設定済みのため、Spring Bootによる開発にすぐ着手できます。

②JDKはAmazon Correto 8を想定しています。
Amazon Corretto 8 のダウンロード | aws
より、ダウンロード→インストールし、Eclipseへの設定を済ませておいてください。

③CodeCommitが使えるよう、
AWSのCodeCommit使い方。アクセスキー作るところからなど。| Qiita
を参考に、事前準備を行っておいてください。

手順

1. Java(Spring Boot)アプリケーションの作成

まず、EclipseでJava(Spring Boot)アプリケーションを作成します。
店舗IDをもとに、店舗詳細情報を取得する至ってシンプルな内容になっています。
パッケージの構成は以下の通り。

sample-eb-java
     |
    src/main/java
     |----jp.co.sample_eb_java
     |               |---- app
     |               |     |---- controller
     |               |     |---- response
     |               |
     |               |---- domain
     |               |      |---- service
     |               |      |---- repository
     |               |      |---- model
     |               |  
     |               |---- exception
     |               
     |
    src/main/resources
     |----application.yml
     |
    Buildfile
    Procfile
    build.gradle
    .ebextensions

・・・以下、省略

[1] プロジェクトの作成

(1) Eclipseを開いたら、「ファイル」(①)> 「新規」(②) > 「プロジェクト」(③)の順でクリック。
image.png

(2) ウィザードが立ち上がったら、「Springスターター・プロジェクト」を選択し(①)、「次へ」(②)をクリック。
image.png

(3) 以下のキャプチャのように設定し(①)、「次へ」(②)をクリック。
image.png

(4)Lombok, MySQL Driver, Spring Data JPA, Spring Webにチェックを入れ(候補に出ていない場合は検索ボックスより検索してください)、「次へ」(②)を押下
image.png

[2] 設定ファイルの変更・作成

次に、設定ファイルの変更と作成を行います。

(1)application.propertiesをapplication.ymlに名前変更し、以下のように記述します。
※application.propertiesのままでもいいのですが、最近の潮流(?)を踏まえてそうしました。
image.png

application.yml
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://${DB_HOST:ホスト名}:${DB_PORT:ポート番号}/${DB_NAME:データベース名}?serverTimezone=JST
    username: ${DB_USERNAME:ユーザー名}
    password: ${DB_PASSWORD:パスワード}
  jpa:
    database: MYSQL
    hibernate.ddl-auto: none
server:
  port: ${SERVER_PORT:サーバーポート番号}

日本語の箇所は後で変更するので、いったんこのままで。

(2)ルートディレクトリにBuildfileを作成し、以下の通り記述。

Buildfile
build: ./gradlew assemble

(3)ルートディレクトリにProcfileを作成し、以下の通り記述。

Procfile
web: java -jar build/libs/sample-eb-java.jar

(4) build.gradleに追記します。追記前の箇所を探し、「bootJar.archiveName = "sample-eb-java.jar"」を追記してください。

build.gradle(追記前)
configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}
build.gradle(追記後)
configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
    bootJar.archiveName = "sample-eb-java.jar"
}

[3]ソースコードの作成

続いて、ソースコードの作成です。
パッケージ構成はこの章(1. Java(Spring Boot)アプリケーションの作成)の冒頭の通りですが、上まで戻るのも面倒かと思うので、キャプチャを貼っておきます。
image.png

(1)まずはEntityクラス。
後から出てきますが、データベースに作成するshop_informationsテーブルに対応する内容となっています。

ShopInformation.java
package jp.co.sample_eb_java.domain.model;

import java.io.Serializable;
import javax.persistence.*;

import lombok.Getter;
import lombok.Setter;


/**
 * 店舗情報テーブルに紐づくEntityクラス
 * 
 * @author CHI-3
 *
 */
@Entity
@Getter
@Setter
@Table(name="shop_informations")
@NamedQuery(name="ShopInformation.findAll", query="SELECT s FROM ShopInformation s")
public class ShopInformation implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="shop_id")
    private Integer shopId;

    private String access;

    private String address;

    @Column(name="business_hour")
    private String businessHour;

    @Column(name="regular_holiday")
    private String regularHoliday;

    @Column(name="shop_name")
    private String shopName;

    private String tel;

    @Column(name="zip_code")
    private String zipCode;

    public ShopInformation() {
    }

}

(2)続いて、Repositoryインターフェース。テーブル操作に使用するインターフェースです。

ShopInformationRepository.java
package jp.co.sample_eb_java.domain.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import jp.co.sample_eb_java.domain.model.ShopInformation;

/**
 * 店舗情報を扱うリポジトリインターフェース
 *
 * @author CHI-3
 *
 */
@Repository
public interface ShopInformationRepository extends JpaRepository<ShopInformation, Integer>{

    /**
     * 店舗IDに紐づく店舗情報を取得
     *
     * @param shopId 店舗ID
     * @return 店舗情報
     */
    public ShopInformation findByShopId(Integer shopId);

}

(3) 続いて、Serviceクラス。ロジックを提供します。

ShopInformationService.java
package jp.co.sample_eb_java.domain.service;

import java.util.Optional;

import org.springframework.stereotype.Service;

import jp.co.sample_eb_java.domain.model.ShopInformation;
import jp.co.sample_eb_java.domain.repository.ShopInformationRepository;
import lombok.RequiredArgsConstructor;

/**
 * 店舗情報を取得するサービスクラス
 *
 * @author CHI-3
 *
 */
@Service
@RequiredArgsConstructor
public class ShopInformationService {

    private final ShopInformationRepository shopInformationRepository;

    /**
     * 店舗情報を取得
     *
     * @param shopId 店舗ID
     * @return 店舗情報
     * @throws Exception
     */
    public ShopInformation getShopInformation(Integer shopId) throws Exception{
        // 店舗情報を取得:対象店舗が存在しなければ例外をThrow
        ShopInformation shopInformation = Optional.ofNullable(shopInformationRepository.findByShopId(shopId)).orElseThrow(Exception::new);
        return shopInformation;
    }

}

(4)次に、Responseクラス。返却する値の成形を行います。

ShopInformationResponse.java
package jp.co.sample_eb_java.app.response;

import jp.co.sample_eb_java.domain.model.ShopInformation;
import lombok.Builder;
import lombok.Getter;

/**
 * 店舗情報取得用レスポンスクラス
 * @author CHI-3
 *
 */
@Getter
@Builder
public class ShopInformationResponse {
    /** 店舗情報 */
    private ShopInformation shopInformation;
}

(5)お次は、Controllerクラス。JSON形式で値を返却するため、@RestControllerを使っています。

ShopInformationController.java
package jp.co.sample_eb_java.app.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import jp.co.sample_eb_java.app.response.ShopInformationResponse;
import jp.co.sample_eb_java.domain.model.ShopInformation;
import jp.co.sample_eb_java.domain.service.ShopInformationService;
import lombok.RequiredArgsConstructor;

/**
 * 店舗情報を取得するAPI
 * 
 * @author CHI-3
 *
 */
@RestController
@RequiredArgsConstructor
public class ShopInformationController {

    private final ShopInformationService shopInformationService;

    /**
     * 店舗情報を取得
     *
     * @param shopId 店舗ID
     * @return レスポンスエンティティ(店舗情報)
     * @throws Exception
     */
    @GetMapping("/shop-information/{shopId}")
    public ResponseEntity<ShopInformationResponse> getShopInformation(@PathVariable("shopId") Integer shopId) throws Exception{
        ShopInformation shopInformation = shopInformationService.getShopInformation(shopId);
        ShopInformationResponse shopInformationResponse = ShopInformationResponse.builder().shopInformation(shopInformation).build();
        return new ResponseEntity<>(shopInformationResponse, HttpStatus.OK);
    }

}

(6) 最後に、ExceptionHandler。エラーハンドリングしてくれるクラスです。
今回は、存在しない店舗IDをリクエストした際などに、400エラー(Bad Request)を返却する内容になっています。

ExceptionHandler.java
package jp.co.sample_eb_java.exception;


import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import lombok.extern.slf4j.Slf4j;

/**
 * 例外ハンドラー
 *
 * @author CHI-3
 *
 */
@ControllerAdvice
@Slf4j
public class ExceptionHandler {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @org.springframework.web.bind.annotation.ExceptionHandler({Exception.class})
    public @ResponseBody
    ResponseEntity<Object> handleError(final Exception e) {
        log.info("call ExceptionHandler", e);
        return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
    }
}

[4] jarファイルの作成

(1) Elastic Beantalkの環境作成時用にjarファイルを作成します。
プロジェクトのルートディレクトリに移動し、ビルドコマンドを実行。
配置が以下のような場合は下記のようになります。
image.png

実行コマンド
> cd C:\pleiades-2020-03\workspace\sample-eb-java
> gradlew build

結果が「BUILD SUCCESSFUL」となればOK!

(2) build/libs配下にjarファイルが作成されていることを確認しましょう。
(これを環境作成時に使用します。)
image.png

2. Gitリポジトリの作成

手順1で作成したプロジェクトをGit管理できるようにします。
手順1で作成したプロジェクト上でローカルリポジトリを作成し、リモートリポジトリと同期する流れで行います。

[1] ローカルリポジトリの作成

(1) コマンドプロンプト、またはGitBashを開き、1で作成したプロジェクトのルートディレクトリに移動(cd)した後、リポジトリの初期化を行います(以下のコマンドを実行)。

> git init

プロジェクトのルートディレクトリに.gitフォルダができればOK。
image.png

(2)コミットする前に、 .gitignoreの書き換えを行います。
デフォルトの記述では、STSなど、必要な機能がコミット対象外となってしまい、後々動作しないなど、不具合の原因になりかねません。
以下の通り記述を変更します。
classファイルやlockファイルなど、コミットの必要のないものが対象外となります。

.gitignore
bin/*
.lock

(3) 編集内容をコミットします。
以下のコマンドを順に実行すればOK。

> git add .
> git commit -m "first commit"

「first commit」の部分(コミットメッセージ)は任意の内容でOKですが、編集内容がわかるメッセージにしましょう(以降同様。)

(4) 再度、.gitignoreを編集します。
機能としては必要だけれども、ローカルからのコミット対象外となるものを記述。

.gitignore

# Created by https://www.toptal.com/developers/gitignore/api/java,gradle,eclipse
# Edit at https://www.toptal.com/developers/gitignore?templates=java,gradle,eclipse

### Eclipse ###
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.settings/
.loadpath
.recommenders

# External tool builders
.externalToolBuilders/

# Locally stored "Eclipse launch configurations"
*.launch

# PyDev specific (Python IDE for Eclipse)
*.pydevproject

# CDT-specific (C/C++ Development Tooling)
.cproject

# CDT- autotools
.autotools

# Java annotation processor (APT)
.factorypath

# PDT-specific (PHP Development Tools)
.buildpath

# sbteclipse plugin
.target

# Tern plugin
.tern-project

# TeXlipse plugin
.texlipse

# STS (Spring Tool Suite)
.springBeans

# Code Recommenders
.recommenders/

# Annotation Processing
.apt_generated/
.apt_generated_test/

# Scala IDE specific (Scala & Java development for Eclipse)
.cache-main
.scala_dependencies
.worksheet

# Uncomment this line if you wish to ignore the project description file.
# Typically, this file would be tracked if it contains build/dependency configurations:
#.project

### Eclipse Patch ###
# Spring Boot Tooling
.sts4-cache/

### Java ###
# Compiled class file
*.class

# Log file
*.log

# BlueJ files
*.ctxt

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

### Gradle ###
.gradle
build/

# Ignore Gradle GUI config
gradle-app.setting

# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar

# Cache of project
.gradletasknamecache

# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
# gradle/wrapper/gradle-wrapper.properties

### Gradle Patch ###
**/build/

# End of https://www.toptal.com/developers/gitignore/api/java,gradle,eclipse

※上記はgitignore.ioで、

  • Java
  • Gradle
  • Eclipse

を条件として作成しました。

変更が完了したら、コミットしておきましょう。

> git add .gitignore
> git commit -m "fix .gitignore"

(5) gradlewの権限変更を行います。
【重要】この手順を飛ばしてしまうと、ビルド、デプロイ時にエラーになるので、忘れず実施してください。

まず、gradlewの権限を確認します。

> git ls-files -s gradlew
100644 fbd7c515832dab7b01092e80db76e5e03fe32d29 0       gradlew

上記のままだと、読み取り権限しかないので、以下のコマンドを実行して、実行権限を与えます。

> git update-index --add --chmod=+x gradlew

実行が完了したら、再度権限確認を行いましょう。
以下の通り、最初の6桁の数字の下3桁が755になっていればOK。

> git ls-files -s gradlew
100755 fbd7c515832dab7b01092e80db76e5e03fe32d29 0       gradlew

変更内容をコミットします。

> git add gradlew
> git commit -m "fix permission of gradlew"

[2] リモートリポジトリの作成

(1) AWSマネジメントコンソールにログインし、「サービス」(①)からCodeCommitを探してクリック(②)。
image.png

(2) CodeCommitのページに遷移したら、「リポジトリを作成」をクリック。
image.png

(3)手順1で作成したプロジェクトと同名のリポジトリ名をつけ(①)、「作成」をクリック。
image.png

[3] ローカルリポジトリとリモートリポジトリを同期

(1)リモートリポジトリの作成が完了すると、以下のようなページに遷移するので、「URLのクローン」(①) > 「HTTPSのクローン」(②)をクリック。
image.png

(2)コマンドプロンプト or GitBashを開き、プロジェクトのルートディレクトリにて、以下のコマンドを実行。

> git remote add origin (1)の「HTTPSのクローン」でコピーされたURI

(3) 内容をリモートリポジトリのmasterにプッシュ。

> git push -u origin master

(4)コンソールにて、リモートリポジトリにローカルリポジトリの内容が反映されていることを確認しましょう。
image.png

続編について

続きは、

にて。
3. Elastic Beanstalk環境の作成, 4. データベースの接続設定を行います。

更新履歴

  • 2020/08/13:手順「1. Java(Spring Boot)アプリケーションの作成」に「[4] jarファイルの作成」を追記

参考

環境

手順

1. Java(Spring Boot)アプリケーションの作成

2. Gitリポジトリの作成

4
7
1

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
4
7