Tech Hotoke Blog

IT観音とは私のことです。

Vue×SpringでSPA作成9 - 2【AWSにアプリケーションのデプロイ】

f:id:TechHotoke:20211226002024p:plain

まえがき

こちらの記事の続編です。

techhotoke.hatenablog.com

目的

VueとSpringで作成したプロジェクトの構築手順の備忘録。 備忘録のため、詳細な説明を省略している部分があります。

前提

  • 基本的なJavaの知識やSpring、Vueの知識があること。
  • AWSに関する基本的な知識があること
  • ネットワークに関する基本的な知識があること

環境

  • Java 11
  • Spring Boot2.5.6
  • Gradle 7.1.1
  • Vue2.6
  • IDESTS
  • AWS(EC2,RDS,ELBなど)
  • OS:Mac(M1)

構成

f:id:TechHotoke:20220130145022j:plain

やること

の続き

実装

ローカル環境でTomcatにデプロイしてみる

  • 前回は取り乱して終わったので、今回は冷静になってやっていきます。まず、ローカルのTomcatでもこの現象が再現するかを確認してみます。(SpringBoot組み込みのTomcatで正常に画面が表示されることは確認ずみという前提で進めます)

  • TomcatのHPから、今回インストールしたTomcatと同バージョンのものをダウンロードしてきます。

Apache Tomcat® - Welcome!

  • ダウンロードしたTomcatのフォルダを解凍します。
  • webapps配下にwarファイルを格納します。
  • Tomcatの場所までターミナルないしコマンドプロンプトで移動します。
  • chmod -R 755 ./[Tomcatのフォルダ名] コマンドなどで実行権限を与えてやりましょう。
  • その後に、binフォルダに移動し、sh startup.shコマンドでTomcatを起動します。
  • webapps配下にwarが解凍されていることを確認してください。 f:id:TechHotoke:20220129234434p:plain
  • http://localhost:8080/にアクセスし、Tomcatの画面が表示されることを確認してください。 f:id:TechHotoke:20220129234512p:plain
  • http://localhost:8080/TMS-0.0.1-SNAPSHOT/frontendにアクセスします
  • 残念ながら再現されてしまいました。。。 f:id:TechHotoke:20220129234735p:plain
  • 一応、tail -f ./logs/catalina.outでログを確認しときます。(問題なさそうです) f:id:TechHotoke:20220129235213p:plain

Tomcat内包のjarファイルなら動く?

  • build.gradleの依存関係を外して、jarを出力できる状態にします。
plugins {
    id 'org.springframework.boot' version '2.6.0'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
    id 'war'←これを削除
}
  • java -jarコマンドで実行し、URLにアクセスしてみます。 f:id:TechHotoke:20220113071938p:plain

  • こちらは問題なく動くようです。

Spring Boot 実行可能な Jar 形式 - リファレンス

build.gradleの依存関係が不足している?

  • 公式17.17.1. Create a Deployable War File曰く、下記のようにクラスを継承してメソッドをオーバーライドせよとのことなので、従います。
@SpringBootApplication
public class TmsApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(TmsApplication.class, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(TmsApplication.class);
    }
    
}
  • これによりサ ーブレットコンテナによって起動される際にアプリケーションを設定することができるようになるとのことです。

  • また、依存関係も不足していたようなのでこちらも追加

providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
  • ここまで行きましたらローカルからgit push、EC2上でgit pullしてソースを最新に更新します。

  • ビルドして、生成されたwarをTomcatのwebapps配下に移動します。

  • warが展開されたことを確認したのちに再度URLにアクセスしてみます。 f:id:TechHotoke:20220201234827p:plain
  • まだ動きません。(遠い目...)

    Javaのバージョンを確認する

  • こちらの記事にJavaのバージョン違いでハマったという内容が記載されているため確認。

qiita.com

  • EC2上でjava --versionコマンドを叩くと
openjdk 17.0.1 2021-10-19 LTS
OpenJDK Runtime Environment Corretto-17.0.1.12.3 (build 17.0.1+12-LTS)
OpenJDK 64-Bit Server VM Corretto-17.0.1.12.3 (build 17.0.1+12-LTS, mixed mode, sharing)
  • build.gradleは、
sourceCompatibility = '11'

これはいけそうな気がしてきました。TomcatJAVA_HOMEに設定されているJavaを読みに行くので、EC2にインストールしたTomcatはJava17で動いていると考えられます。

  • find . | grep jdk-11などのコマンドでJDKのパスを探し出して、コピーします。/usr/lib/jvm/java-11-openjdk-11.0.13.0.8-1.amzn2.0.3.x86_64
  • vi ~/.bash_profileを確認すると
PATH=$PATH:$HOME/.local/bin:$HOME/bin

export PATH

すでにPATHが設定されていました。下記のようにJAVA_HOMEを設定します。

export JAVA_HOME="/usr/lib/jvm/java-11-openjdk-11.0.13.0.8-1.amzn2.0.3.x86_64"
export PATH="${JAVA_HOME}:/usr/lib/jvm/java-11-openjdk-11.0.13.0.8-1.amzn2.0.3.x86_64/bin $PATH:$HOME/.local/bin:$HOME/bin"
  • source ~/.zshrcコマンドで更新した後にecho $JAVA_HOMEコマンドでJavaのバージョンが11に設定されていることを確認します。
[root@ip-10-0-10-54 jvm]# echo $JAVA_HOME
/usr/lib/jvm/java-11-openjdk-11.0.13.0.8-1.amzn2.0.3.x86_64
  • java -versionコマンドを実行したところ、JDK-17を参照していました。
  • /usr/lib/jvm/java-11-openjdk-17を削除して、javaコマンドを叩くと、usr/bin/java not foundとのこと
  • そこでそのディレクトリに移動して、ls -l | grep javaで調べてみると、java -> /etc/alternatives/java シンボリックリンクが貼られているようなので、リンク先に移動してls -l | grep javaを叩くと、
一部抜粋
lrwxrwxrwx 1 root root 52  1月 13 22:18 java -> /usr/lib/jvm/java-17-amazon-corretto.x86_64/bin/java

先ほど削除したJDKがリンク先となっていたようです。

  • alternatives --config javaコマンドを叩くと以下のように出てくるので、該当のJDKを指定します。 f:id:TechHotoke:20220203181756p:plain

  • java --versionコマンドを叩いて該当のJavaのバージョンが表示されていればOKです

openjdk 11.0.13 2021-10-19 LTS
OpenJDK Runtime Environment 18.9 (build 11.0.13+8-LTS)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.13+8-LTS, mixed mode, sharing)

はい。Damn it...

alternativesコマンドとは?

alternativesはCentOSAmazon Linux2は公表されていないがCentOSがベースになっている的な話を聞いたことがあります)に標準で入っているコマンドです。 シンボリックリンクを活用して、同様の機能を持つソフトウェアや、バージョン違いのソフトを切り替えられる」という優れもののコマンドです。 つまり、 Javaだけに限らず様々なコマンドで応用することが可能ということでした。

Tomcatのコンテキストパスを確認する

めげずにやっていきます。お次はTomcatのコンテキストパスを確認していきます。 その前にこれまでの対応でローカル環境ではどうなるか確認しておきます。

f:id:TechHotoke:20220203230450p:plain

  • 真っ白な画面が返ってきましたね。。。でもこれはTomcatが404を返していなさそうなので、ちょっと希望が見えてきました。

f:id:TechHotoke:20220203233907p:plain

  • デベロッパーツールのネットワークタブを確認すると/frontendのURLへのステータスコードは200で返却されており、バンドルされたcssやjsファイルが404で取得できていない模様です。

  • 一旦、Tomcatを停止して、Spring Bootを起動して同様のパスにポート8080でアクセスするとどうなるのか検証します。

f:id:TechHotoke:20220203234503p:plain

  • こちらは正常に返却されていることが分かります。

  • 一応Tomcatのバージョンに起因していないかを確認するために、Tomcat8.5系のものをインストールして同様にアプリケーションをデプロイして検証してみます。(今回は8.5.75をインストールしてきました) f:id:TechHotoke:20220204003041p:plain

…普通に404が返ってきました。分からん。

  • とりあえず、、、コンテキストパスの設定を変更してみます。(非推奨ですが)conf/server.xmlのHOSTタグの子要素としてContextタグを追加して、下記のように記載します。
  <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
          
          <Context path="/TMS-0.0.1-SNAPSHOT" />
 </Host>
  • これでTomcatを再起動して、URLにアクセスしてみますと。。。 f:id:TechHotoke:20220203230450p:plain

  • 状態は変わらずです。

一旦これは保留にして、内包Tomcatのjarファイルで構成することに方針を転換することにしました。

アプリケーションをサービス化して、EC2起動時に自動起動設定する

この前に、以下のポートフォワーディングが失敗する現象に見舞われたので対応。(結局EC2インスタンスを再起動することで解消できたので原因が謎のまま)

  • まず、sudo systemctl disable tomcat.serviceでEC2にインストールしたTomcat自動起動設定を解除しておきます。

  • systemctl is-enabled tomcat.serviceで状態を確認して下記のようになっていればOkです。

[ec2-user@ip-10-0-10-54 share]$ systemctl is-enabled tomcat.service
disabled
  • EC2起動時にjarを実行する形にするためにはサービスに登録する方法があるようなのでそちらが一番手取り早そうなのでその方法を採用しようと思います。(AWSへのデプロイ方法がたくさんあるのでこれができたら今度はCodeDeployとかに挑戦してみたい)

Deploying Spring Boot Applications

A fully executable jar can be executed like any other executable binary or it can be registered with init.d or systemd

  • fully executive jarという形式にするといけるそうで、いくつか注意点があるみたいです

Fully executable jars work by embedding an extra script at the front of the file. Currently, some tools do not accept this format, so you may not always be able to use this technique. For example, jar -xf may silently fail to extract a jar or war that has been made fully executable. It is recommended that you make your jar or war fully executable only if you intend to execute it directly, rather than running it with java -jar or deploying it to a servlet container.

  • 確実に実行される保証がないからサーブレットコンテナに展開するとか、java -jarコマンドで実行するなと言ってます。

A zip64-format jar file cannot be made fully executable. Attempting to do so will result in a jar file that is reported as corrupt when executed directly or with java -jar. A standard-format jar file that contains one or more zip64-format nested jars can be fully executable.

  • zip64形式のjarファイルは、完全に実行可能な状態にすることができないので、一つ以上ネストしたjarファイルにせよ的なことですかね。。。

  • 下記の記述をbuild.gradleに追加するだけで良いという簡単さ!

bootJar {
    launchScript()
}
  • この記述を加えることで./myapplication.jar(環境により可変)コマンドでアプリケーションの実行が可能になるとの事です。

You can then run your application by typing ./my-application.jar (where my-application is the name of your artifact).

  • 変更をプッシュしてここから先はEC2のWebサーバー上で行なっていきます。

  • アプリケーションをサービスに登録する事で容易に実行できるようになるそうです。

Spring Boot application can be easily started as Unix/Linux services by using either init.d or systemd.

  • systemdかinit.dのどちらにすべきか判断できないのですが、下記記事を参考に、init.dはスクリプトのため、挙動を柔軟に制御できる反面、スクリプトに問題があると気付きにくいという点がエラーの元になると嫌なので、systemdに登録していきます。

    • systemdではこれまでサービス起動スクリプトで定義されていたものがUnitという形で定義されますので、サービスの管理=Unitの管理

    • デーモン:OSにおいて動作するプロセス(プログラム)で、主にバックグラウンドで動作するプロセス

Linux の任意のスクリプトをサービス登録し OS 起動時に自動起動させる [init.d/SystemD 編] - Qiita

  • まずは/var/lib/app/TMS/currentこの場所にjarファイルを配置します。

  • /var/lib/app/TMS/current に以下のようなServiceファイルを作成します。

[Unit]
Description = This is TwmpleManagementSystem's daemon

[Service]
ExecStart = /home/ec2-user/TTM/build/libs/TMS-TMS.jar
User = ec2-user
Group = ec2-user
SuccessExitStatus = 143

[Install]
WantedBy = multi-user.target

Serviceファイルの書き方などはこちらを参考: systemd.unit

systemdの*.serviceファイルの書き方 - Qiita

  • sudo systemctl enable <自分で作成したファイル名>.serviceコマンドを実行

  • Failed to execute operation: Cannot send after transport endpoint shutdownこんなエラーが出たので対処

  • systemctl list-unit-files --type=service | grep <自分で作成したファイル名>コマンドでコマンドが登録されていることを確認

f:id:TechHotoke:20220214184009p:plain

  • ステータスがmaskedになっており、この状態だとサービスの起動ができないという状態のようなので、systemctl unmask myapp.serviceコマンドを実行します。

  • すると、linkが削除された旨のメッセージが出てきてsystemctl list-unit-files --type=service | grep <自分で作成したファイル名>でも参照できなくなりました。

  • Unitファイルの設定場所は/usr/lib/systemd/systemまたは/etc/systemd/systemであり、後者に上書き用のファイルなどを設置する事で解決できそうなので、mvコマンドなどでServiceファイルをそちらに移行します。

f:id:TechHotoke:20220214185640p:plain

  • 再度確認してみると、disabled状態ですがServiceファイルが登録されていることが確認できました。

f:id:TechHotoke:20220214185754p:plain

  • # systemctl enable TMS-TMS.serviceコマンドを実行した後に、# systemctl is-enabled TMS-TMS.serviceコマンドを実行し、enbledにステータスが変更されていることを確認できればOKです。

  • # systemctl start TMS-TMS.serviceを実行し、アプリケーションのURLにアクセスした際に画面が表示されれば完了です。

  • EC2のSSH接続を切った状態でもアプリケーションにアクセスできればEC2上でアプリケーションが常時起動しているということになるためこれで完了です。

長くなりましたがここまでお付き合い頂きましてありがとうございます! またの機会にWarファイルのデプロイを行なっていきたいと思います。。。

参考:

【SpringBoot】CentOS7でjar(Gradle)を自動起動させるまで - タイガー!タイガー!じれったいぞー!(SE編)

SpringBootで作ったjarをinit.dのサービスとして動かしてみた - Qiita

SpringBoot アプリをサービスとして動かす方法 - Qiita

Deploying Spring Boot Applications

【Linux】systemd:Unit定義ファイル(サービス)の自作とsystemctlによる登録 | OFFICE54