Javaで開発したWebアプリケーションをJenkinsを使って自動デプロイする[準備編]

CI/CDを実践するため、JenkinsでArtifactsを生成してアプリケーションサーバーにデプロイする所まで設定してみました。

この記事では*準備編*として、Javaで書いたWebアプリケーションプロジェクトをGithubで管理し、Jenkinsと連携してビルドするまでの設定までとなります。

# Artifacts

デプロイの方法には、一般的に"warでデプロイ"と"jarでデプロイ"の２通りがあると思いますので、どちらのパターンでも試すことにしました。

すなわち
**1. warファイルを出力し、アプリケーションサーバーにデプロイします。**
**2. jarファイルを出力し、`java`コマンドで実行します。**
今回は、前者はアプリケーションサーバーにTomcatを利用し、後者はSpring Bootで組み込みTomcatを使って、jarを実行します。

# 環境

- Java: OpenJDK 11.0.2
- Tomcat:　9.0.14
- Maven: 3.6
- Spring framework: 5.1.3
- Thymeleaf: 3.0.11
- Spring-boot: 2.1.2
- Jenkins: 2.161
- VirtualBox: 6.0.2
- Vagrant: 2.2.3
- CentOS: 7

# Javaプロジェクトの作成

warファイルとjarファイルの２種類のArtifactsを用意するため、SpringFrameworkを利用したJavaのWebアプリケーションプロジェクトを2つ用意しました。

GithubのRepositoryは[こちら](https://github.com/kazokmr/JavaWebAppDeploySample)

### パターン１： warファイルを出力するプロジェクト

#### Spring MVCを利用したWebアプリケーションを作成

ベースはSpringのHPにある[Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/)を元に、Mavenプロジェクトを用意しようとしましたが、SpringBootを利用していてパッケージングもjarでしたので、以前買った[Spring徹底入門](https://www.amazon.co.jp/dp/B01IEWNLBU/ref=cm_sw_em_r_mt_dp_U_YXQrCb5C6MTSR)と多田さんがJJUG CCCで行なったハンズオンの資料[Spring 5 & Spring Boot 2ハンズオン準備手順](https://qiita.com/suke_masa/items/44463518fdbbc13e0087)も参考にさせていただきました。

IntelliJ IDEAの[File > Project Structure > Artifacts]で、warファイルが登録されていることを確認し、無ければ追加しておく。

#### Maven Wrapperの作成

Maven Wrapperを用意して、JenkinsサーバーにMavenをインストールせずにmvnコマンドを実行できるようにしました。
`mvn -N io.takari:maven:wrapper -Dmaven=3.6.0`
Maven Wrapper(mvnw)の作り方は[こちら](https://kazuhira-r.hatenablog.com/entry/20180121/1516542120)を参照しました。

#### 開発マシンのIntelliJ　IDEAからTomcatを起動する

開発環境でTomcatを立ち上げてブラウザで確認するまでに手こずったのでメモ

1. まず開発PCにTomcatをインストールしていなかったので、homebrewでインストールした。`brew install tomcat`
2. IntelliJ IDEAの[Run > Edit Configurations > Add New Configuration]から[Tomcat Server > local]を指定して、設定画面を表示する
3. [Application Server]のPathは`/usr/local/Cellar/tomcat/9.0.14/libexec`を指定する。
4. [URL]は`http://localhost:8080/web-spring-mvc/greeting`と入力する。
5. Deploymentタブの[Deploy at the server startup]でArtifactsとして指定できるwarから`war　exploded`を追加する
6. Deploymentタブの[Application Context]で、`/web-spring-mvc`と指定する。(4のURLのContextPathと合わせる)

IntelliJ IDEAからTomcatを実行すると、http://localhost:8080/web-sprig-mvc/greeting にアクセスして画面が表示される。

### パターン２： jarファイルを出力するプロジェクト

Spring Bootアプリケーションで開発しjarで出力したArtifactsを`java`コマンドで実行します。
サンプルプロジェクトは[Building an Application with Spring Boot](https://spring.io/guides/gs/spring-boot/)を元にMavenプロジェクトを作成します。
こちらのサンプルも出力がRestAPIだったのでhtml(Thymeleaf)を出力するように編集しました。

まずはSpring Initializerを使ってプロジェクトを作成します。

#### MavenのDependency設定

以下、手こずった点について

- `spring-boot-starter-thymeleaf`を追加しないと、Thymeleafが利用できないので画面が出力されない。
- JUnit5を使う場合、spring-boot-starter-testの依存関係から`junit`を除く(どうも依存先のjunitから存在しないバージョンを指定しようとしてエラーが出る。)
- 出力したjarファイルは、JVMサーバーでサービス起動するが、[実行可能なjar](https://docs.spring.io/spring-boot/docs/current/reference/html/deployment-install.html#deployment-script-customization-when-it-runs)にするために、spring-boot-maven-pluginのexecutableオプションをtrueにしておくこと。

# サーバーの作成

以下の3つの仮想環境をローカルマシン上に構築しました。

1. Jenkinsサーバー
2. アプリケーションサーバー(Tomcat)
3. アプリケーションサーバー（Javaのみ）

構築は、[Vagrant](https://gist.github.com/kazokmr/bbe465c63c4205c61ed81759350f88df)で複数の環境を同時に起動し、[ansible-playbook](https://github.com/kazokmr/ansible-java-deploy-env)を利用して必要なソフトをインストールしています。

### Jenkinsサーバーの構築

主なインストールソフトウェア

- Git
- OpenJDK 8 JRE
- OpenJDK 11 JDK
- Jenkins

JRE 8はJenkins動作用に実行環境として利用し、OpenJDK 11はアプリケーションのビルド用に利用します。

Jenkinsのインストールは、[Jenkins Wiki](https://wiki.jenkins.io/display/JENKINS/Installing+Jenkins+on+Red+Hat+distributions)を参照しました。

### Tomcatサーバーの構築

主なインストールソフトウェア

- OpenJDK 11 JDK
- Tomcat 9

Ansible-playbookの書き方は、[ansible本家のサンプル](https://github.com/ansible/ansible-examples/tree/master/tomcat-standalone)を参考にしました。

firewall設定でつまづいたが、「Tomcatのサービス起動をstartedからrestartedに変更する」と「firewalldのポート指定の前に、firewalldを起動する」ことで、接続できました。

```yml:/roles/tomcat/tasks/main.yml(抜粋)
- name: Start Tomcat
  service: name=tomcat state=restarted enabled=yes

- name: running firewall daemon
  service: name=firewalld state=restarted
```

ローカルPCからTomcatのmanager画面(/manager/html)にアクセスすると「403 access denied」と表示されてしまうのでエラー画面や[こちら](http://blog.enjoyxstudy.com/entry/2016/10/30/000000)を見たところ、manager画面へのアクセスはTomcatをインストールしたマシンからのみできるように初期値が設定されいているとのことだったので、 /webapps/manager/META-INF/context.xml を編集して、仮想環境のネットワーク内（192.168.33.0/24）からアクセスできるように変更しました。

また、tomcatマネージャーをコマンドから実行できるようにするため、tomcat-users.xmlには、実行ユーザーにmanager-sciprtロールを付与しておきます。[参考](http://yoneyore.hatenablog.com/entry/2017/06/04/190754)

### JVMサーバーの構築

SpringBootアプリケーションを実行するための設定を行います。

1. OpenJDK 11をインストールして、javaコマンドが実行できるようにします
2. アプリケーションを実行するjvmユーザーを作成します。
3. SpringBootアプリケーションを実行するサービスを作成します。

尚、作成時点ではjarファイルはまだ存在していないので、サービスを起動してもステータスはFailになります。

## Vagrantの起動

`vagrant up`を実行すると、3つの環境が順番に起動し、Ansibleを実行して必要なソフトや設定がインストールされます。
インストールが完了した後、仮想環境に`vagrant ssh [ホスト名]`でアクセスできます。

#### Jenkins

http://192.168.33.10:8080/ にアクセスするとJenkinsにログインできます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/160107/1ea3417c-2401-c439-3af9-b018244480ef.png)

後は、画面上から初期セットアップを実施し、インストールしたJDKとGitのパスを設定します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/160107/bc05d212-8943-75f4-2d0d-014647a34685.png)

#### Tomcat

http://192.168.33.30:8080/ にアクセスするとTomcatコンソールにアクセスできます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/160107/41600735-e685-3fa3-1527-064aec38803b.png)

サンプル含まれるroles/tomcat/vars/main.ymlのユーザーを使うと、管理画面やScriptが利用できます。

#### JVM

`vagrant ssh jvm`でアクセスして`java -version`を実行すると**openjdk version**が表示されることを確認します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/160107/6cfc92a4-696a-4899-1b66-dbc210b486ad.png)

また、`# systemctl status app`を実行するとSpringBootアプリケーションサービスの実行ステータスが確認できますが、初回はjarファイルが無いのでエラーとなります。(/var/log/messageを見るとわかります。)

## JVMサーバーにSSHでアクセスするための設定

JenkinsサーバーからJVMサーバーにjarファイルを転送してアプリケーションのサービスを実行するためにSSH接続を利用します。
今回のサンプルではvagrantで複数環境を同時に起動した都合から、あらかじめ公開鍵を作らなかったので以下のようにコマンドで鍵の受け渡しを行いました。
(実際は公開鍵をあらかじめ用意しておいて、ansibleのauthorized_keyモジュールで登録すると良いと思います。)

**JVMサーバーで一時的にパスワード認証でアクセスを有効にする** 
1. `# vi /etc/ssh/sshd_config` を開き、"PasswordAuthentication"をyesに切り替える
2. `# systemctl restart sshd`でsshを再起動する。
3. `# passwd jvm`でjvmユーザーのパスワードを登録する 

**JenkinsサーバーのJenkinsユーザーでSSH鍵を作りJVMサーバーに公開鍵を送る**
`$ sudo su -s /bin/bash jenkins`を実行するとJenkinsユーザーでコマンド実行できるようになるので、SSH鍵を作成するコマンドを実行する

```bash
1. jenkinｓユーザーのホームディレクトリ[/var/lib/jenkins]に移動
$ cd ~

2. ssh鍵を作る　
$ ssh-keygen -b 2048 -t rsa -f /var/lib/jenkins/.ssh/id_rsa -N ""

3. 公開鍵をJVMサーバーに送る(送るときにjvmユーザーのパスワードを聞かれる)
$ ssh-copy-id -i .ssh/id_rsa.pub jvm@192.168.33.20

4. 接続テスト
$ ssh -i .ssh/id_rsa jvm@192.168.33.20

5. 終了
$ exit
```

**sshアクセスできたらJVMサーバーのパスワード認証を無効に戻すことを忘れないように**
ちなみにJVMサーバーに作成したjvmユーザーのホームディレクトリは、`/var/jvm`にしているので、SSHの接続情報は`/var/jvm/.ssh`に格納されます。

# Jenkinsでビルドとデプロイを実行する準備が完了しました。

**Githubで管理するMavenプロジェクト(Java + Spring)**と**Jenkinsサーバー**と**デプロイ環境**が準備できました！
この後のビルドとデプロイを実行する設定方法は[Tomcatアプリケーション](https://qiita.com/kazokmr/items/853098ba49889cc18abf)と[SpringBootアプリケーション](https://qiita.com/kazokmr/items/7decab715fdda083482e)に分けてまとめましたのでこちらを参照してください。