VSCodeで始めるDockerコンテナ上にJava開発環境を構築（②Dockerコンテナ編）

# 目的
前回「①WSL環境準備」の続きとして、Dockerコンテナ上にJava開発環境（複数バージョン）を構築します。
本記事では、VSCodeのDev Containers拡張機能を利用し、作成したJava開発環境にアクセスできるところまで確認する予定です。
[前回の記事はこちら（①WSL環境準備編）](https://qiita.com/peanuts2013/items/bca1ca6856c2a45fa9ea)

# 第2回「②Dockerコンテナ編」における記載範囲
* VSCodeでDev Containersを利用する準備として、「devcontainer.json」の作成
* Dockerイメージの元になる「Dockerfile」の作成
* 複数コンテナ管理を見据え、「docker-compose.yml」の作成　※1
* VSCodeのDev Containersを利用し、コンテナ（Java開発環境）にアクセス

※1
今回の記事では1つのコンテナのため、複数のコンテナを管理する想定はありません。
続編としてJenkins環境構築に関する記事を公開予定のため、本記事でも「docker-dompose.yml」を利用する方法としています。

# 1. 事前準備
* VSCodeの拡張機能（Extensions）に「Dev Containers」を導入しておきます。

【VSCode手順】
1. VSCodeの左側ペインにて「EXTENSIONS」（拡張機能）を選択、または「Ctrl + Shift + X」を押下
1. 検索ボックスにて「Dev Containers」を入力
1. 「Dev Containers」を選択し、Installを実行

* WSL起動時デフォルトユーザーを「app01」に変更しておきます。
以下、サイトを参考にさせて頂きました。
[WSL の起動時デフォルト ユーザ変更](https://minettyo.hatenablog.com/entry/wsl_defaultuser#gsc.tab=0)

# 2. Dockerコンテナ上に構築する環境
- OpenJDK、Mavenを導入した開発環境
- SDKMAN（※2）を導入し、複数のJavaバージョン（Java17、21）を切り替え可能
- proxy設定を行い、企業内の環境で利用する想定（※3）

※2
SDKMAN（Software Development Kit Manager）は、JavaやGroovy、ScalaなどのJVM言語やmaven、gradleといったビルドツールのインストールや管理を簡単にし、特定のバージョンへの切り替えを容易にする。

※3
環境によってはproxy設定は不要になるため、別記事で試した内容は投稿したいと思います。

# 3. Dev Container準備
## 3.1. VS Code上のディレクトリ構成
VSCodeからは、下記の『ホスト側のディレクトリ構成』の"."位置がカレントディレクトリとなるようにフォルダを開きます。

【VSCode手順】
1. VSCodeの左側ペインにてExplorer（Ctrl + Shift + E）から「Open Folder」を選択し、カレントディレクトリを指定

![Open Folder.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32811/590d89cf-eae3-bc52-3812-cee1c5c34fa8.png)


### ホスト側のディレクトリ構成
```
/home/
└── ubuntu/
    ├── .devcontainer/
    │   └── devcontainer.json
    ├── development/
    │   ├── Dockerfile
    │   └── settings.xml
    └── docker-compose.yml
/etc/
├── docker/
│   └── daemon.json
└── systemd/
    └── system/
        └── docker.service.d/
            └── http-proxy.conf
~/.docker/
└── config.json
```

### コンテナ側のディレクトリ構成
```
/opt/
└── docker/
    └── project/
        ├── .m2/
        │   └── repository/
        └── settings.xml
/home/
└── app01/
    ├── .sdkman/
    │   └── candidates/
    │       ├── java/
    │       │   ├── 17.0.12-tem/
    │       │   │   └── bin/
    │       │   └── 21.0.4-tem/
    │       │       └── bin/
    │       └── maven/
    │           └── 3.9.8/
    └── .bashrc
```

## 3.2. devcontainer.jsonの作成
.devcontainerディレクトリ配下に「devcontainer.json」を作成します。

```json:devcontainer.json
{
    "name": "Java Development Environment",
    // devcontainer.jsonを起点としたdocker-compose.ymlのディレクトリパス
    "dockerComposeFile": "../docker-compose.yml",
    "forwardPorts": [8080],
    "service": "app",
    // コンテナログイン時のカレントディレクトリ（存在するディレクトリを指定）
    "workspaceFolder": "/home/app01",
    
    "customizations": {
        "vscode": {
            "settings": {
                "java.configuration.runtimes" : [
                    {
                        "name": "JavaSE-17",
                        "path": "/home/app01/.sdkman/candidates/java/17.0.12-tem",
                        "default": true
                    },
                    {
                        "name": "JavaSE-21",
                        "path": "/home/app01/.sdkman/candidates/java/21.0.4-tem"
                    }
                ],
                "java.jdt.ls.vmargs": "-Xmx2G -XX:+UseG1GC -XX:+useStringDeduplication"
            },
            "extensions": [
                "redhat.java",
                "vscjava.vscode-java-debug",
                "vscjava.vscode-java-test"
            ]
        }
    }
}
```

# 4. Dockerfileの作成
アプリケーション環境としてSDKMAN（※2）を導入し、複数のJavaバージョンを管理できるように`Dockerfile`を作成します。
```dockerfile:Dockerfile
FROM ubuntu:22.04

USER root

# 作業ディレクトリを設定
WORKDIR /opt/docker/project

# Mavenローカルリポジトリとなるディレクトリを作成
RUN mkdir -p /opt/docker/tools \
    && mkdir -p /opt/docker/project/.m2

# 必要なパッケージをインストール
RUN apt-get update && apt-get install -y \
    locales \
    subversion \
    unzip \
    curl zip unzip

# ユーザー「app01」を作成し、ディレクトリの所有者を「app01」に変更
RUN useradd -ms /bin/bash app01 && \
    chown -R app01:app01 /opt/docker

# app01ユーザーに切り替え
USER app01

# バージョン管理のためSDKMANをインストール
ARG JAVA17_VERSION=17.0.12-tem
ARG JAVA21_VERSION=21.0.4-tem
ARG MAVEN_VERSION=3.9.8

# SDKMANのインストール、sdkman-init.shの実行権限を付与
RUN curl -s "https://get.sdkman.io" | bash && \
    chmod +x $HOME/.sdkman/bin/sdkman-init.sh && \
    bash -c "source $HOME/.sdkman/bin/sdkman-init.sh && \
        sdk install java ${JAVA17_VERSION} && \
        sdk install java ${JAVA21_VERSION} && \
        sdk install maven ${MAVEN_VERSION}"

# ローカルリポジトリを設定したsettings.xmlをコピー
COPY ./development/settings.xml /opt/docker/project
```

# 5. docker-compose.ymlの作成
appサービスを定義した`docker-compose.yml`を作成します。
```yml:docker-compose.yml
version: '3.3'

services:
  app:
    build:
      # docker-compose.ymlの配置パスがカレント
      context: .
      dockerfile: ./development/Dockerfile
    environment:
      - TZ=Asia/Tokyo
    ports:
      - "8080:8080"
    stdin_open: true
    tty: true
```

# 6. settings.xml
コンテナ内のワークスペース毎にMavenローカルリポジトリを分けて使う想定とし、デフォルトでないディレクトリを設定した`settings.xml`を準備しておきます。
本記事ではMavenの使い方については割愛しますが、Mavenコマンド実行時にオプションでコピーした`settings.xml`を指定する形で利用する想定です。
```bash:例
$ mvn install -s settings.xml
```

```xml:settings.xml
<?xml version="1.0" encoding="UTF-8"?>
<settings>
    <localRepsitory>/opt/docker/project/.m2/localRepsitory</localRepsitory>
(省略)
</settings>
```

# 7. VSCodeからコンテナを開く
## 7.1. VSCodeでF1キー（Ctrl + Shift + P）を押下してコマンドパレットを開き、「Reopen in Container」を選択する。
![ReOpen in Container.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32811/7b1c1806-6a2c-122a-7b94-88e8a3f0e964.png)

## 7.2. コンテナが起動すると、「devcontainer.json」で指定するワークディレクトリを開いた状態となる。
![ReOpen in Container_直後.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32811/2d1163f7-13a7-5f86-7483-3aafca82fbc8.png)

## 7.3. sdkコマンドでバージョンを確認する。（SDKMANが正しくインストールされたか確認）
![SDKMANのコマンドでバージョン確認.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32811/91d676f1-cfbe-4493-ad90-a807e63a8de9.png)

## 7.4. Javaバージョンを変更してみる。（Java21に設定された状態）
```bash:Java17に変更する例
$ sdk use java 17.0.12-tem
```

実行ログ
```bash
app01@xxx:~$ sdk use java 17.0.12-tem
app01@xxx:~$ sdk current        

Using:

java: 17.0.12-tem
maven: 3.9.8

Using java version 17.0.12-tem in this shell.
```

# おわりに
記事の途中でも少し触れましたが、続編としてJenkinsコンテナ環境を構築する記事も書きたいと思っています。
`docker-compose.yml`で複数サービスを定義し、Dev Containerによるコンテナ起動ではなく、`docker-compose`コマンドを使用した内容となる予定です。