Azure App Service にデプロイした Spring Boot コンテナに SSH 接続する

# Azure App Service にデプロイした Spring Boot コンテナに SSH 接続する

## 目的

[Spring Boot WEBサービス](https://qiita.com/hiroxpepe/items/4a0f5e077155ce41e7ac)を [Azure App Service](https://learn.microsoft.com/ja-jp/azure/app-service/overview) 環境で起動して理解を深めます。  

## 実現すること

Microsoft Azure App Service に Spring Boot WEBアプリのカスタムコンテナに SSH 接続します。  

こちらの記事の続きになります。  

https://qiita.com/hiroxpepe/items/05f2858a8c20f0b0977b

:::note warn
この記事では、Docker Hub にプュシュした**パブリックアクセス**のカスタムコンテナイメージを使用しています。 実際のシステムを構築される際にはご注意ください。
:::

Microsoft Azure Container Registry (ACR) を使用した構成の例として、以下の記事もございます。  

https://qiita.com/hiroxpepe/items/1077942fcebad5136c87

## 技術背景

### SSH とは？  

<details>
<summary>こちらを展開してご覧いただけます。</summary>

### SSH (Secure Shell)

SSH は、ネットワークを介して安全な通信を行うためのプロトコルの一つです。 SSH は、通信経路を暗号化し、不正アクセスや盗聴などのセキュリティ上のリスクを低減することができます。SSH は、主にリモートアクセスやファイル転送などの目的で使用されます。

</details>

## 開発環境

+ Windows 11 Home 22H2 を使用しています。  
+ [WSL](https://learn.microsoft.com/ja-jp/windows/wsl/about) の Ubuntu を操作していきますので macOS の方も参考にして頂けます。  

<details>

WSL (Microsoft Store アプリ版)

```sh
> wsl --version
WSL バージョン: 1.0.3.0
カーネル バージョン: 5.15.79.1
WSLg バージョン: 1.0.47
```

Ubuntu

```sh
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.1 LTS
Release:        22.04
```

Java JDK [※ Java JDK の導入と Hello World!](https://qiita.com/hiroxpepe/items/8650664e466906a57996)

```sh
$ java -version
openjdk version "11.0.17" 2022-10-18
OpenJDK Runtime Environment (build 11.0.17+8-post-Ubuntu-1ubuntu222.04)
OpenJDK 64-Bit Server VM (build 11.0.17+8-post-Ubuntu-1ubuntu222.04, mixed mode, sharing)
```

Maven [※ Maven の導入と Hello World!](https://qiita.com/hiroxpepe/items/40f7f199b9be7f9e3b5b)

```sh
$ mvn -version
Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 11.0.17, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
```

Docker Desktop

```sh
Version 4.16.3 (96739)
```

```sh
$ docker --version
Docker version 20.10.22, build 3a2c30b
```

```sh
$ docker-compose --version
Docker Compose version v2.15.1
```

</details>

※ この記事では基本的に Ubuntu のターミナルで操作を行います。

## "Hello World" を表示する手順

### Spring Boot WEBサービスの作成

こちらを参照して頂けます。  

https://qiita.com/hiroxpepe/items/4a0f5e077155ce41e7ac

### プロジェクトフォルダに移動  

※ ~/tmp/hello-spring-boot をプロジェクトフォルダとします。  

```sh
$ cd ~/tmp/hello-spring-boot
```

### Java アプリをビルド(※参考)  

こちらを参照して頂けます。  

https://qiita.com/hiroxpepe/items/05f2858a8c20f0b0977b

### コンテナイメージを作成

※ コンテナに SSH 接続する為、この記事では spring-boot:build-image によるコンテナイメージのビルドではなく、独自に Dockerfile でカスタムコンテナイメージをビルドする例を紹介します。  

sshd_config ファイルを作成

```sh
$ vim sshd_config
```

ファイルの内容

```sh:sshd_config
Port                2222
ListenAddress       0.0.0.0
LoginGraceTime      180
X11Forwarding       yes
Ciphers aes128-cbc,3des-cbc,aes256-cbc,aes128-ctr,aes192-ctr,aes256-ctr
MACs hmac-sha1,hmac-sha1-96
StrictModes         yes
SyslogFacility      DAEMON
PasswordAuthentication  yes
PermitEmptyPasswords    no
PermitRootLogin     yes
Subsystem sftp internal-sftp
```

<details>
<summary>説明を開きます。</summary>

#### sshd_config ファイルの内容

このファイルは、OpenSSH デーモンの設定ファイルであり、Docker コンテナ内で実行される SSH サーバーの設定をカスタマイズするために使用されます。このファイルは、Dockerfile 内の COPY コマンドで "/etc/ssh/" ディレクトリにコピーされます。  

このファイルでは、以下のような設定が行われています。  

+ SSH サーバーが待ち受けるポート番号を 2222 に設定しています。
+ ログイン時に表示されるメッセージの表示時間を 180 秒に設定しています。
+ X11 転送を有効にしています。
+ 暗号アルゴリズムと MAC アルゴリズムを指定しています。
+ StrictModes を yes に設定することで、ユーザーが直接ホームディレクトリにアクセスすることを禁止しています。
+ パスワード認証を許可し、空のパスワードを許可しないように設定しています。
+ PermitRootLogin を yes に設定しています。これにより、ルートユーザーで SSH 接続できます。
+ Subsystem sftp internal-sftp を設定することで、SFTP サブシステムが有効になり、ファイルの転送が可能になります。

</details>

ssh_setup.sh ファイルを作成

```sh
$ vim ssh_setup.sh
```

```sh:ssh_setup.sh
#!/bin/sh

# generate ssh host keys.
ssh-keygen -A

# prepare run dir
if [ ! -d "/var/run/sshd" ]; then
    mkdir -p /var/run/sshd
fi
```

<details>
<summary>説明を開きます。</summary>

#### ssh_setup.sh ファイルの内容

このスクリプトは、OpenSSH のための鍵ペアを生成するために ssh-keygen -A コマンドを実行し、"/var/run/sshd" ディレクトリを作成するための mkdir コマンドを実行します。  

また、"/var/run/sshd" ディレクトリが存在しない場合は、それを作成します。このスクリプトは、Docker イメージをビルドするときに実行され、SSH サービスの起動に必要な準備をします。  

</details>

startup.sh ファイルを作成

```sh
$ vim startup.sh
```

```sh:startup.sh
#!/bin/sh

# startup openssh daemon.
/usr/sbin/sshd

# startup jvm app.
java -jar app.jar
```

<details>
<summary>説明を開きます。</summary>

#### startup.sh ファイルの内容

このスクリプトは、OpenSSH のデーモンを起動して、Java アプリケーションを実行することにより、コンテナの起動処理を行っています。

|コマンド|内容|
|:--|:--|
|#!/bin/sh|シェルスクリプトであることを示す宣言です。|
|/usr/sbin/sshd|OpenSSH のデーモンを起動します。これにより、コンテナ内へ SSH でアクセスできるようになります。|
|java -jar app.jar|Java アプリケーションを起動します。 java コマンドは、Java ランタイムを起動し、-jar オプションは、指定された JAR ファイルを実行することを示します。 app.jar は、起動するアプリケーションの JAR ファイルです。|

</details>

Dockerfile を編集  

```sh
$ vim Dockerfile
```

ファイルの内容

```sh:Dockerfile
# from the base image of a jdk 11 container on Ubuntu 20.04.
FROM adoptopenjdk/openjdk11:x86_64-ubuntu-jdk-11.0.18_10-slim

# create work dir.
WORKDIR /app

# update package list.
RUN apt-get -y update

# install openssh and set the password for root to "Docker!".
RUN apt-get install -y openssh-server \
     && echo "root:Docker!" | chpasswd 

# copy the sshd_config file to the /etc/ssh/ directory.
COPY sshd_config /etc/ssh/

# copy and configure the ssh_setup file.
COPY ssh_setup.sh .
RUN chmod +x ssh_setup.sh \
    && (sleep 1;/app/ssh_setup.sh 2>&1 > /dev/null)

# copy and configures the startup file.
COPY startup.sh .
RUN chmod +x startup.sh 

# copy the jvm app.
COPY target/*.jar app.jar

# open port 8080 for jvm app and port 2222 for ssh access.
EXPOSE 8080 2222

# exec the startup script.
ENTRYPOINT ["/app/startup.sh"]
```

<details>
<summary>説明を開きます。</summary>

#### Dockerfile の内容

この Dockerfile は、Ubuntu 20.04 をベースとする JVM コンテナの Docker イメージを構築しています。 具体的には、AdoptOpenJDK が提供する OpenJDK 11 をベースとしています。  

|コマンド|内容|
|:--|:--|
|FROM adoptopenjdk/openjdk11:x86_64-ubuntu-jdk-11.0.18_10-slim|AdoptOpenJDK が提供する OpenJDK 11 をベースとして、x86_64 アーキテクチャの Ubuntu 20.04 上で実行されるJREコンテナを指定しています。|
|WORKDIR /app|"/app" ディレクトリを作成し、以降の作業ディレクトリを設定しています。|
|RUN apt install openssh && echo "root:Docker!" \| chpasswd|OpenSSH をインストールし、root ユーザーのパスワードを Docker! に設定しています。|
|COPY sshd_config /etc/ssh/|sshd_config ファイルを "/etc/ssh/" ディレクトリにコピーしています。このファイルは、SSH デーモンの設定を定義するためのものです。|
|COPY ssh_setup.sh .|ssh_setup.sh ファイルをコンテナ内の作業ディレクトリにコピーしています。このスクリプトは、SSH 接続を設定するためのもので、コンテナ起動時に実行されます。|
|RUN chmod +x ssh_setup.sh && (sleep 1;/app/ssh_setup.sh 2>&1 > /dev/null)|ssh_setup.sh ファイルに実行権限を与え、SSH 接続を設定するために実行します。|
|COPY startup.sh .|startup.sh ファイルをコンテナ内の作業ディレクトリにコピーしています。このスクリプトは、コンテナ起動時に JVM アプリケーションを実行するためのものです。|
|RUN chmod +x startup.sh|startup.sh ファイルに実行権限を与えます。|
|COPY target/*.jar app.jar|ビルドされた JVM アプリケーションの Jar ファイルをコンテナ内の app.jar ファイルにコピーしています。|
|EXPOSE 8080 2222|コンテナのポート 8080 と 2222 を公開しています。|
|ENTRYPOINT ["/app/startup.sh"]|コンテナ起動時に startup.sh スクリプトを実行し、JVM アプリケーションを実行します。|

</details>

コンテナイメージをビルド  

※ ローカルの Docker 環境 (Docker Desktop) に app-hello-spring-boot Docker イメージが作成されます。  

```sh
$ docker build --no-cache -t app-hello-spring-boot .
```

コンテナイメージの確認

```sh
$ docker images | grep app-hello-spring-boot
app-hello-spring-boot   latest   10c6cdc26e44   52 seconds ago    544MB
```

ローカルでコンテナイメージを起動して確認

```sh
$ docker run --name app-local -p 8080:8080 app-hello-spring-boot
```

### Docker Hub にコンテナイメージを登録

※ $USER の部分はご自身のコンテナレジストリに読み替えて下さい。  

タグ付け

```sh
$ docker tag app-hello-spring-boot $USER/app-hello-spring-boot:latest
```

プュシュ

```sh
$ docker push $USER/app-hello-spring-boot:latest
```

Docker Hub に $USER/app-hello-spring-boot:latest としてカスタムコンテナイメージが公開出来ました。  

### Azure のアカウントを取得

公式 [Azure の無料アカウントを使ってクラウドで構築](https://azure.microsoft.com/ja-jp/free/)

### Azure CLI でサインイン

こちらを参照して頂けます。  

https://qiita.com/hiroxpepe/items/20aeb54a676f190d5c96

ログイン

```sh
$ az login
```

※ 最新バージョンに更新する場合

```sh
$ az upgrade
```

### Azure 環境

#### リソースグループ

リソースグループを作成

```sh:Microsoft.Resources/resourceGroups
$ az group create \
    --name rg-hello \
    --location japaneast
```

リソースグループ一覧表示

```sh
$ az group list
```

※ リソースグループを削除する場合

```sh
$ az group delete -n <group>
```

#### App Service プラン

App Service プランを作成

```sh:Microsoft.Web/serverfarms
$ az appservice plan create \
    --resource-group rg-hello \
    --name asp-hello \
    --sku Free \
    --is-linux
```

App Service プランの一覧表示

```sh
$ az appservice plan list
```

※ App Service プランを削除する場合

```sh
$ az appservice plan delete -n <plan> -g <group>
```

#### App Service WEB アプリ

※ Azure App Service に Spring Boot アプリ Docker イメージをデプロイする方法はいくつかありますが、今回は Azure CLI で Docker Hub 上のパブリック カスタムコンテナイメージをデプロイする方法を紹介します。  

Web アプリの作成とデプロイ  
※ $USER の部分はご自身のコンテナレジストリに読み替えて下さい。  

```sh:Microsoft.Web/sites
$ az webapp create \
    --resource-group rg-hello \
    --plan asp-hello \
    --name app-hello-spring-boot \
    --deployment-container-image-name $USER/app-hello-spring-boot:latest
```

Web アプリの作成の設定を構成  
※ App Service WEB アプリはデフォルトで 80 番ポートをリッスンする仕様です。  

```sh
$ az webapp config appsettings set \
    --resource-group rg-hello \
    --name app-hello-spring-boot \
    --settings WEBSITES_PORT=8080
```

Web アプリのログ記録の構成

```sh
$ az webapp log config \
    --resource-group rg-hello \
    --name app-hello-spring-boot \
    --docker-container-logging filesystem \
    --application-logging filesystem \
    --web-server-logging filesystem 
```

WEBブラウザーでアクセス

```sh
$ az webapp browse \
    --resource-group rg-hello \
    --name app-hello-spring-boot
```

※ 立ち上がりに少し時間が掛かりますがページが表示されます。 しかし表示されたページ "/" にはコンテンツを設定していません。

改めてWEBブラウザで確認  
※ URLは環境で読み替えて下さい。  

```sh
https://app-hello-spring-boot.azurewebsites.net/api/data
```

WEBブラウザに {"message":"Hello World!"} と表示され、JSON データを取得することが出来ました。  

コンテナに SSH で接続

```sh
$ az webapp ssh \
    --resource-group rg-hello \
    --name app-hello-spring-boot
```

コンテナ接続後

```
# pwd
/root
# cd /app
# ls -la
total 17256
drwxr-xr-x    2 root     root          4096 Feb 27 11:18 .
drwxr-xr-x   59 root     root          4096 Feb 27 11:34 ..
-rw-r--r--    1 root     root      17650443 Feb 27 07:17 app.jar
-rwxr-xr-x    1 root     root           107 Feb 27 10:30 ssh_setup.sh
-rwxr-xr-x    1 root     root            70 Feb 27 11:16 startup.sh
```

コンテナの情報  

```
# cat /etc/*-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=20.04
DISTRIB_CODENAME=focal
DISTRIB_DESCRIPTION="Ubuntu 20.04.5 LTS"
NAME="Ubuntu"
VERSION="20.04.5 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.5 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal
```

## まとめ

+ Azure App Service 環境で動作している Spring Boot WEBサービスのカスタムコンテナに SSH で接続することが出来ました。

## 個人的見解

+ コンテナに SSH 接続する為に、SSH 接続用の設定ファイルや、Dockerfile に専用の設定を加える方法に問題があると感じました。
    + それは、Azure のその他のコンテナイメージをデプロイ可能であるサービスにおいてはそれらは必要ないという観点からです。
    + これらの追加設定により、コンテナイメージが Azure App Service 専用のものとなる可能性があると思います。
    + その点を踏まえ、もはや Azure App Service は、素直に jar ファイル形式でビルドされたJVMアプリを運用する為の PaaS 環境として割り切った方が良いのかも知れません。

## 参考

##### [Microsoft Learn] Azure App Service のカスタム コンテナーを構成する

https://learn.microsoft.com/ja-jp/azure/app-service/configure-custom-container?tabs=debian&pivots=container-linux#enable-ssh

##### [Microsoft Learn] Azure App Service コマンド リファレンス

https://learn.microsoft.com/ja-jp/cli/azure/service-page/azure%20app%20service?view=azure-cli-latest