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

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

こんにちは、@studio_meowtoon です。今回は、Azure App Service 環境でコンテナとして起動する Spring Boot Web アプリケーションに SSH 接続する方法を紹介します。
![spring-boot_on_azure-app-service.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/402db632-eeca-97a9-a86b-69403d97f9a1.png)

## 目的

Windows 11 の Linux でクラウド開発します。

こちらから記事の一覧がご覧いただけます。

https://qiita.com/studio_meowtoon/items/6119ab05d8b13527efd8

## 実現すること

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

こちらの関連記事の続編になります。

https://qiita.com/studio_meowtoon/items/29b6d9dadcece2d723e2

:::note alert
この記事では、**Docker Hub** に公開する**パブリックアクセス可能なカスタムコンテナイメージ**を使用しています。実際のシステム開発では、どのレジストリからコンテナイメージを取得するかは異なる場合がありますので、適宜ご確認ください。

また、Azure には完全にマネージドされたレジストリサービスである **Azure Container Registry** が利用できます。Azure CLI を使用しての操作方法もありますので、別の機会にご紹介したいと思います。
:::

## 技術トピック

### SSH とは？  

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

### SSH (Secure Shell)

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

</details>

## 開発環境

+ Windows 11 Home 22H2 を使用しています。  

:::note info
[WSL](https://learn.microsoft.com/ja-jp/windows/wsl/about) の Ubuntu を操作していきますので macOS の方も参考にして頂けます。  
:::

<details>

WSL (Microsoft Store アプリ版) [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/2aaa6579cb8e4fa1d874)

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

Ubuntu [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/2aaa6579cb8e4fa1d874)

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

Java JDK [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/4d11e94a2389758759cd)

```sh
$ java -version
openjdk version "11.0.18" 2023-01-17
OpenJDK Runtime Environment (build 11.0.18+10-post-Ubuntu-0ubuntu122.04)
OpenJDK 64-Bit Server VM (build 11.0.18+10-post-Ubuntu-0ubuntu122.04, mixed mode, sharing)
```

Maven [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/f4cdc4039158153365fb)

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

Docker [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/3bceee25ac67cc73ebbe)

```sh
$ docker --version
Docker version 23.0.1, build a5ee5b1
```

</details>

:::note warn
この記事では基本的に **Ubuntu のターミナルで操作**を行います。**Vim を使用してコピペする方法**を初めて学ぶ人のために、以下の記事で手順を紹介しています。ぜひ挑戦してみてください。
:::

https://qiita.com/studio_meowtoon/items/7771ad48676926e41463

## Hello World を表示する手順 

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

こちらの関連記事の続きから手順を説明する記事となります。

https://qiita.com/studio_meowtoon/items/37ac0082a3228e4de570

### Spring Boot アプリのプロジェクトフォルダに移動

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

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

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

:::note alert
**コンテナに 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 ファイルの内容

:::note info
このファイルは、OpenSSH デーモンの設定ファイルであり、コンテナ内で実行される 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 an ssh host keys.
ssh-keygen -A

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

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

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

:::note info

|内容|
|:--|
|このスクリプトは、OpenSSH のための鍵ペアを生成するために ssh-keygen -A コマンドを実行し、/var/run/sshd ディレクトリを作成するための mkdir コマンドを実行します。|
|また、/var/run/sshd ディレクトリが存在しない場合は、それを作成します。このスクリプトは、コンテナイメージをビルドするときに実行され、SSH サービスの起動に必要な準備をします。|
:::

</details>

startup.sh ファイルを作成します。

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

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

# startup openssh daemon.
/usr/sbin/sshd

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

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

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

:::note info
このスクリプトは、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 a work dir.
WORKDIR /app

# update the 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/ dir.
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 の内容

:::note info
この Dockerfile は、Ubuntu 20.04 をベースとする JRE コンテナイメージを構築しています。 具体的には、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 環境を確認します。

```sh
$ docker --version
Docker version 23.0.1, build a5ee5b1
```

:::note warn
Docker 環境をお持ちでない場合は、以下の関連記事から Docker Engine のインストール手順をご確認いただけます。
:::

https://qiita.com/studio_meowtoon/items/3bceee25ac67cc73ebbe

コンテナイメージをビルドします。 

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

コンテナイメージを確認します。

```sh
$ docker images | grep app-hello-spring-boot-ssh
app-hello-spring-boot-ssh   latest    4a94045f5846   8 seconds ago   545MB
```

### コンテナイメージを起動

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

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

ローカルのコンテナを停止します。  

```sh
$ docker stop app-local
```

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

Docker Hub のアカウントを作成します。  

https://hub.docker.com/signup

:::note warn
Docker Hub は、コンテナの共有、保存、および管理のためのクラウドベースのレジストリです。Docker Hub には、コミュニティによって作成された公式およびコミュニティによるイメージが格納されています。また、**ユーザーがカスタムコンテナイメージをアップロードおよび共有する**こともできます。
:::

Docker Hub にログインします。  

```sh
$ docker login
Login Succeeded
```

コンテナイメージにタグを付けます。
※ $USER の部分はご自身のコンテナリポジトリに読み替えて下さい。 

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

Docker Hub にコンテナイメージをプュシュします。

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

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

### Azure 環境にサインイン

こちらの関連記事の続きから手順を説明する記事となります。 

https://qiita.com/studio_meowtoon/items/9721041f9e6e3e987cf0

Azure CLI でログインします。  

```sh
$ az login
```

### Azure 環境

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

リソースグループを作成します。

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

#### 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 Web アプリ

Web アプリの作成とデプロイを行います。  
※ $USER の部分はご自身のコンテナリポジトリに読み替えて下さい。
 
```sh:Microsoft.Web/sites
$ az webapp create \
    --resource-group rg-hello \
    --plan asp-hello \
    --name app-hello-spring-boot-ssh \
    --deployment-container-image-name $USER/app-hello-spring-boot-ssh:latest
```

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

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

### Web アプリ動作の確認

以下のコマンドを実行すると、WEBブラウザーが立ち上がります。  

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

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

WEBブラウザには以下のように表示されます。  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/816fe3e4-eceb-0b3d-e63e-1acbc1d3c44c.png)
改めてWEBブラウザで確認します。    
※ URLは環境で読み替えて下さい。  

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

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

※ 別ターミナルから curl コマンドで確認してみます。  

```
$ curl -v https://app-hello-spring-boot-ssh.awesomewebsites.net/api/data
```

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

:::note info 
Azure App Service 自体が SSL/TLS 対応および HTTP/2 プロトコルをサポートしています。
:::

### コンテナに接続

コンテナに SSH で接続します。

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

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

:::note info
このコマンドは、Azure App Service Web アプリに対して SSH 接続を確立します。

|コマンド|内容|
|:--|:--|
|az webapp ssh|App Service Web アプリに SSH 接続を確立するためのコマンドです。|

|オプション|値|内容|
|:--|:--|:--|
|--resource-group|rg-hello|App Service Web アプリを含むリソースグループの名前を指定します。|
|--name|app-hello-spring-boot-ssh|App Service Web アプリの名前を指定します。|
:::

</details>

コンテナに接続後にディレクトリを確認します。 
※ コンテナから出るときは ctrl + D を押します。  

```
# pwd
/root
# cd /app
# ls -la
total 17256
drwxr-xr-x    2 root     root          4096 Mar  8 11:18 .
drwxr-xr-x   59 root     root          4096 Mar  8 11:34 ..
-rw-r--r--    1 root     root      17650443 Mar  8 07:17 app.jar
-rwxr-xr-x    1 root     root           107 Mar  8 10:30 ssh_setup.sh
-rwxr-xr-x    1 root     root            70 Mar  8 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
```

:::note warn
このコンテナは Ubuntu をベースに作成されています。つまり、Ubuntu と同じように扱うことができます。
:::

## Azure Portal

Azure Portal の App Service Web アプリ の SSH の項目からも確認できます。  
![ssh_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/3b6fac02-7dd9-de6f-a3d9-51f30e0e9129.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/aed49249-aebd-894a-0418-6d841500ae0c.png)
Web ブラウザからコンテナに SSH 接続できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/7ef08e61-3296-349e-80e1-54cb2175a367.png)

## 注意点

Microsoft は、カスタムコンテナで SSH サポートを使用する方法を公開しています。

https://learn.microsoft.com/ja-jp/azure/app-service/configure-linux-open-ssh-session

:::note warn
ただし、SSH デーモンを実行する設定をコンテナイメージ作成時に特別に付け加えることは、コンテナのポータビリティに懸念があるため、実際のシステム開発で避けることが推奨されます。**Azure** では、**Container Apps** というカスタムコンテナイメージのデプロイ手法も提供されています。
:::

こちらの記事では、Azure Container Apps を使用した方法について説明しています。

https://qiita.com/studio_meowtoon/items/cbcfe1433885bae514db

## まとめ

Azure App Service 環境で、Dockerfile からビルドした Spring Boot Web サービスのカスタムコンテナに SSH 接続することができました。

:::note warn
**Maven**、**Docker**、**Azure CLI** を使って、Spring Boot アプリの開発からコンテナイメージの作成、Azure 環境へのデプロイまで、**すべてをターミナルから行うことができます**。このように、クラウドでのシステム開発に必要なスキルや理解を深めることができます。初めての人でも簡単に手順を追うことができるので、ぜひ挑戦してみてください。
:::

どうでしたか？ 検証目的として、WSL Ubuntu で、Spring Boot Web アプリケーションを Azure App Service 環境にデプロイし、コンテナに手軽に SSH 接続することができます。ぜひお試しください。今後も Azure の開発環境などを紹介していきますので、ぜひお楽しみにしてください。

## 推奨コンテンツ

https://qiita.com/studio_meowtoon/items/cbcfe1433885bae514db