VSCode×DevContainerでDockerコンテナ内にJava開発環境を作る


## 書いた背景
- 最近実務でJavaを使い始めた。
- Javaに関する開発等はEclipceで行う風潮があるが、Eclipceが使いやすいと思わない。少なくともプライベートではVSCodeで書きたい。
- EclipceのAll-in-OneにはJava実行環境等が含まれていたが、VSCodeではそれがないためローカルマシンに実行環境をインストールする必要がある。が、それをやりたくない。
- VSCode ×　DockerでJavaの開発環境を作ろう。それを手軽に実現してくれるのがDevContainerというプラグインらしい。


## 前提

- VSCodeのDevContainerにはそれ専用のDockerイメージがそれなりに用意されているので、手元にDockerfileを用意しなくてもすぐ始められるため、今回はその方法で進める。
- Dev Container向けに用意されているDockerイメージはLTS版が中心でJava21まで対応されている。
- ビルドツールにはgradleを使用し、バージョンは8.4.4
- 書いた人はJavaど素人なので、正直gradleの操作とか基本的なところまで初見っぽい書き方になっています。

## Dev Containerとは？ Remote Developmentとの違い

名前やアイコンの似たプラグインが複数あるので、関係性を整理。
特にDevContainerの旧名と名前が似ているRemote Developmentを比較。

- [Dev Container（旧Remote Container）](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
- [Remote Development](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack)

### DevContainer

元々はRemote Containerという名前だった。

拡張機能の説明を要約すると以下の通り。

- Dev ContainerとはDockerコンテナ内の環境を開発環境として利用するためのプラグインである。
- Dockerコンテナを開発環境として扱うことにより、チームメンバー間や開発・本番環境で一貫性を保つことができる。

### Remote Development

拡張機能の説明を要約すると以下の通り。

- Remote Containerとはコンテナやリモートマシン、WSL内の任意のフォルダを開いてVSCodeの機能を利用することができる。つまりリモート環境での開発ができる。
- リモート環境での開発が実現することによって、ローカルマシンにソースコードを置く必要がなくなったり、ローカルマシンよりも高いスペックの別マシンでソースコードを実行できる。
- この拡張機能をインストールすると以下の4つが含まれる。
  - Remote - SSH - SSH を使用してリモート マシン/VM 上のフォルダーを開いて、任意の場所にあるソース コードを操作する。
  - Remote - Tunnels - SSH の代わりにVS Code トンネルを使用してリモート マシン/VM 上のフォルダーを開いて、任意の場所にあるソース コードを操作する。
  - Dev Containers - コンテナ内またはコンテナ内にマウントされているフォルダを開いて、別のツールチェーンまたはコンテナ ベースのアプリケーションを操作する。
  - WSL - inux 用 Windows サブシステム内の任意のフォルダーを開いて、Windows の快適さから Linux を活用した開発エクスペリエンスを実現する。


つまり、Dev ContainerはRemote Developmentの1手法であり、開発環境をセットアップしたコンテナに接続を行うことによってローカルと開発環境を分離する。

## Dev Containerを使ってJavaアプリケーションを実行する

手元で作成したDockerイメージを作成することもできるが、MicrosoftがDevContainer用に用意したいくつかのイメージからセットアップすることもできるので今回はその方法を取る。
（その中にはLTSのJava21までのイメージが存在する。）

### コマンドパレットからDevContainerのコマンドを立ち上げる

![68747470733a2f2f73746f726167652e676f6f676c65617069732e636f6d2f7a656e6e2d757365722d75706c6f61642f3436393739626264396434322d32303234303231322e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982811/23065add-9283-55a9-d88b-cdc7e818be4b.png)

▲ F1キーを押すとコマンドパレットが立ち上がり、「dev container configuration file」まで入力して設定ファイルの新規作成項目をあぶり出す。

「Add Development Container Configuration Files...」をクリックすると以下のように設定ファイルに関するオプションをいくつか聞かれる。

![68747470733a2f2f73746f726167652e676f6f676c65617069732e636f6d2f7a656e6e2d757365722d75706c6f61642f3830373665383638656537612d32303234303231322e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982811/4bc1c2a5-b0a9-4836-67a0-66e4e715166e.png)
▲ 最初に定義された構成のうちどれを使用するか聞かれるのでJavaを選択。
Javaのプロジェクトを開いている場合はおそらく選択肢の中にJavaが表示される。

![68747470733a2f2f73746f726167652e676f6f676c65617069732e636f6d2f7a656e6e2d757365722d75706c6f61642f6638343539636663653032362d32303234303231322e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982811/aa275c76-5431-729b-8f7d-a98cc187661a.png)
▲ Javaが選択肢にないときは「すべての定義を表示」を開いて辿っていけばJavaを見つけられる。

![68747470733a2f2f73746f726167652e676f6f676c65617069732e636f6d2f7a656e6e2d757365722d75706c6f61642f3463323466336365396338612d32303234303231322e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982811/f31e42ea-aaed-c325-51cc-2418e6322e46.png)
▲ 次にJavaのバージョンを選択。今回は現時点のLTSで最新の21-bullseyeを選択。

![68747470733a2f2f73746f726167652e676f6f676c65617069732e636f6d2f7a656e6e2d757365722d75706c6f61642f6537343661366436653061632d32303234303231322e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982811/959eb531-2a42-43d4-cf2c-10cb109229f0.png)
▲ 今回はGradleのプロジェクトを用意したので、2つ目の選択肢を選ぶ。

![68747470733a2f2f73746f726167652e676f6f676c65617069732e636f6d2f7a656e6e2d757365722d75706c6f61642f6135623037383764623433632d32303234303231322e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982811/10ad035a-00d6-cc47-d2f2-79b3e3dfee05.png)
▲ イメージに追加する機能を聞かれるが、今回は特にないのでそのまま「OK」。

![スクリーンショット 2024-02-13 14.07.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982811/c0a17fee-0fc1-776b-f897-df8a3dd91d4a.png)
するとプロジェクトに.devcontainerというディレクトリが作成され、その中にdevcontainer.json（DevContainerの定義ファイル）が存在する。

### VSCodeをDockerコンテナに接続する

定義ファイルの作成が終わったら早速コンテナを立ち上げてみる。

![スクリーンショット 2024-02-13 14.09.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982811/1de3ad87-ad1d-0b1c-21d5-4ed61b301c0a.png)
▲ VSCode左下にある青い手裏剣みたいなアイコンをクリックする。

![スクリーンショット 2024-02-13 14.04.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982811/c2fcf137-1c8e-8dbb-afcd-f7cff0ca62da.png)
▲ リモート接続に関するメニューが表示されるので「コンテナで再度開く」をクリック。

![68747470733a2f2f73746f726167652e676f6f676c65617069732e636f6d2f7a656e6e2d757365722d75706c6f61642f6265633435373064313365652d32303234303231322e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982811/0862368b-cdc7-d344-2559-c78cc39fdb0f.png)
▲ このような画面が新しく開けばDevContainerで開けている。

## HelloWorldのサンプルコードをビルドする

まずプロジェクトにsrc/main/javaの階層でディレクトリを作成し、HelloWorld.javaを以下のように作成。

```java:src/main/javaHelloWorld.java
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}
```

次にgradleで上記ソースをビルド〜実行する。

Javaプログラムを実行する方法の1つとして、ビルドしたソースを.jar形式のファイルとしてアウトプットしてそれをjavaコマンドで直接指定して実行する方法がある。

gradleでビルドを行う場合はあらかじめ`build.gradle`というスクリプトファイルを定義しておく必要があるのだが、eclipceで新規gradleプロジェクトを作成したときには存在しないので手動で作成する。

```build.gradle
plugins {
    id 'java'
}

// JARファイルの作成
jar {
    // マニフェストにメインクラスを指定
    manifest {
        attributes 'Main-Class': 'HelloWorld'
    }
}
```
mavenがjava向けのビルドツールなのに対してGradleは必ずしもJava向けとは限らないので、そもそもjavaのプラグインを明示して使用する必要がある模様。

```bash
// Macの場合
./gradlew clean build 

// windowsの場合
gradlew.bat clean build
```

build.gradleを保存したらプロジェクトルートでgradleのビルドコマンドを実行する。
先ほど構成ファイル作成時にgradleをインストールしたはずだが、結局インストールされていないのでローカルでgradleを実行。

ビルドに成功すると今回の構成の場合は`build/libs`ディレクトリに`{アプリケーション名}.jar`ファイルが作成される。

これをjavaコマンドで実行すると、、、

![スクリーンショット 2024-02-13 14.04.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982811/18394724-432a-209e-c08a-b7d87ed06ebb.png)


▲ Hello World!が表示されるので、一旦VSCodeのDevContainerにJava開発環境を作成するミッションは成功したということになる。

## SpringBootアプリを立ち上げてみる

Eclipceで作成したプロジェクトそのまま開いて上記と同じ手順を実行しましたが、問題なく実行できて（ポート番号8080にアクセス可）手順もほぼ変わらないので省略します。

## エラー関連

### dev container mkdir: cannot create directory ‘/home/vscode/.vscode-server’: No space left on device のエラーが発生する場合

```
docker system prune
```
を実行すれば解決するっぽい。

https://zenn.dev/yufuworks/scraps/b17ec132c4d089


### ビルドに失敗する場合

```
./gradlew build
・
・
・
Welcome to Gradle 8.1.1!

Here are the highlights of this release:
 - Stable configuration cache
 - Experimental Kotlin DSL assignment syntax
 - Building with Java 20

For more details see https://docs.gradle.org/8.1.1/release-notes.html

Starting a Gradle Daemon (subsequent builds will be faster)

FAILURE: Build failed with an exception.

* What went wrong:
Could not open cp_settings generic class cache for settings file '/workspaces/gradle_development/settings.gradle' (/home/vscode/.gradle/caches/8.1.1/scripts/1en43omyaxmkkiczjvbzrftux).
> BUG! exception in phase 'semantic analysis' in source unit '_BuildScript_' Unsupported class file major version 65

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
````

上記のの場合はシンプルにJavaのバージョンがGradleの実行要件を満たしていない。
今回はEclipce内で空のGradleのプロジェクトを作成したが、依存するJavaのバージョンの設定等がなかったのでこのようなことになった。

gradle-wrapper.propertiesを開いて`distributionUrl`を書き換えれば良い。
```
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip // ここを8.1.1から8.4にした
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
```

https://qiita.com/zoeque/items/6b6ad6a8c842febd8368