Azure App Service の Linux カスタム コンテナに SSH 接続する

# はじめに
<a href="https://qiita.com/advent-calendar/2022/microsoft-azure-tech">Microsoft Azure Tech | Advent Calendar 2022</a> の 2日目の記事です。
本稿では Azure App Service の Linux カスタム コンテナに SSH 接続する方法を説明します。  

<a href="https://learn.microsoft.com/ja-jp/azure/app-service/">Azure App Service</a> は、Web アプリケーションの実行環境を提供する PaaS サービスです。  
<a href="https://azure.microsoft.com/ja-jp/products/app-service/web/">Web Apps</a> と呼ばれる Windows あるいは Linux の規定の構成による実行環境と、<a href="https://azure.microsoft.com/ja-jp/products/app-service/containers/#overview">Web App for Containers</a> と呼ばれる カスタム Docker イメージの実行環境とがあります。大きく分けると以下の３種類となります。[^1]  

| 種類 | 説明 |
|:--|:--|
| Web Apps on Linux | .NET、Java、Ruby、Node.js、PHP、Python の定義済みの実行環境を提供。 |
| Web Apps on Windows | .NET、NET Core、Java、Node.js の定義済みの実行環境を提供。 |
| Web App for Containers | カスタム Docker イメージの実行が可能。Linux コンテナ、Windows コンテナ、いずれも対応。 |

上記の表の通り、Linux 環境は 「Web Apps on Linux」 あるいは 「Web App for Containers の Linux コンテナ」 のいずれかとなります。  
「Web Apps on Linux」は組み込みの Linux コンテナで、SSH がサポートされています。Azure ポータルからブラウザベースで簡単に SSH セッションを開くことができます。  
一方、「Web App for Containers の Linux コンテナ」 の場合、ユーザー自身で SSH に必要な設定をコンテナに追加する必要があります。  

SSH 設定の追加方法は <a href="https://learn.microsoft.com/ja-jp/azure/app-service/configure-custom-container?pivots=container-linux#enable-ssh">App Service の公式ドキュメント</a>で説明されており、基本的にはこの説明に従えば問題ないのですが、この設定をサポートする機会が何度かあったため、この記事では公式サイトの方法をベースにより具体的な設定手順をご紹介します。  


# App Service にカスタム コンテナをデプロイする

事前準備として、サンプルアプリケーションを組み込んだ Linux のカスタム コンテナを App Service にデプロイします。  
はじめは SSH 設定を追加していない状態で、挙動を確認してみようと思います。

App Service 公式ドキュメントの「<a href="https://learn.microsoft.com/ja-jp/azure/app-service/quickstart-custom-container?tabs=java&pivots=container-linux-vscode">Azure でカスタム コンテナーを実行する</a>」チュートリアルに従って、Java アプリケーションのコンテナをビルドし、App Serice にデプロイします。

```docker:Dockerfile
FROM mcr.microsoft.com/azure-app-service/java:11-java11_stable

ENV PORT 80
EXPOSE 80

ENTRYPOINT ["java", "-Dserver.port=80", "-jar", "/tmp/appservice/parkingpage.jar"]
```

App Service へのデプロイが完了したら、Azure ポータルにアクセスして [開発ツール] > [SSH] で SSH セッションを開いてみます。  
SSH 設定を行っていないため、接続できずに以下のような画面になります。<font color="red">[SSH CONN CLOSE]</font> というエラーが表示されています。

![qiita_azuretech2022_ms_img01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2323437/756ecad4-c610-1329-ea46-f4883324d537.png)


# カスタム コンテナに SSH 設定を追加する

ここからが本題です。カスタム コンテナに設定を追加して SSH を有効にしましょう。  
現在のフォルダ構成は以下の通りです。

```plaintext:フォルダ構成
-/path-to-project
 |- Dockerfile
```

## 1. sshd_config ファイルを追加

Dockerfile と同じ階層に sshd_config というファイルを追加して、以下内容を記載します。  
このファイルは OpenSSH を構成するものです。ポートは 2222 である必要があります。

```conf:sshd_config
# This is ssh server systemwide configuration file.

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
```

## 2. ssh_setup スクリプト ファイルを追加

Dockerfile と同じ階層に ssh_setup.sh というファイルを追加して、以下内容を記載します。  
ssh-keygen を使用して SSH キーを作成するためのスクリプトとなります。

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

ssh-keygen -A

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

## 3. Dockerfile にコマンドを追加
OpenSSH をインストールして root パスワードを設定します。以下は Alpine Linux ベースのイメージの場合のコマンドとなります。
```docker:Dockerfile
# Install OpenSSH and set the password for root to "Docker!".
RUN apk add openssh \
     && echo "root:Docker!" | chpasswd 
```
手順 1 で追加したローカルファイル sshd_config を、コンテナ内のパス /etc/ssh/ にコピーします。
```docker:Dockerfile
# Copy the sshd_config file to the /etc/ssh/ directory
COPY sshd_config /etc/ssh/
```
手順 2 で追加したローカルファイル ssh_setup.sh を、コンテナ内に新規作成したディレクトリ /tmp にコピーして実行権限を付与したうえで、スクリプトを実行します。
```docker:Dockerfile
# Copy and configure the ssh_setup file
RUN mkdir -p /tmp
COPY ssh_setup.sh /tmp
RUN chmod +x /tmp/ssh_setup.sh \
    && (sleep 1;/tmp/ssh_setup.sh 2>&1 > /dev/null)
```
SSH 用のポート 2222 を追加します。ポート 2222 は プライベート仮想ネットワークのブリッジ ネットワーク内でのみアクセスでき、インターネット上の攻撃者からはアクセスできないので安心してください。  
なお、以下の例では TCP ポートには 80 を指定していますが、自分の構成に応じて変更してください。  
```docker:Dockerfile
# Open port 2222 for SSH access
EXPOSE 80 2222
```

## 4. コンテナのスタートアップ スクリプトで SSH サーバー を起動

コンテナの起動時に OpenSSH デーモン (sshd) を起動させる必要があります。起動コマンドは以下となります。

```shell:Bash
/usr/sbin/sshd
```

今回用意した Dockerfile では、ENTRYPOINT で jar ファイルの実行を指定しています。これに sshd 起動コマンドの実行も追加したいため、スタートアップスクリプトを用意して両者を実行させることにします。
Dockerfile と同じ階層に docker-entrypoint.sh というファイルを追加して、以下内容を記載します。  

```shell:docker-entrypoint.sh
#!/bin/sh

# Startup OpenSSH daemon
/usr/sbin/sshd

# Run the JAR file
java -Dserver.port=80 -jar /tmp/appservice/parkingpage.jar
```

これに伴い、Dockerfile に修正が必要です。  
追加したローカルファイル docker-entrypoint.sh を、コンテナ内のパス /tmp にコピーして実行権限を付与します。/tmp ディレクトリの新規作成コマンド (RUN mkdir -p /tmp) の記述箇所以降に追加してください。

```docker:Dockerfile
# Copy and configure the docker-entrypoint file
COPY docker-entrypoint.sh /tmp
RUN chmod +x /tmp/docker-entrypoint.sh 
```
ENTRYPOINT を修正します。
```docker:Dockerfile
ENTRYPOINT ["/tmp/docker-entrypoint.sh"]
```

## 5. 完成した Dockerfile
手順 1 ～ 4 を行った後のフォルダ構成と Dockerfile の内容は、以下の通りです。

```plaintext:フォルダ構成
-/path-to-project
 |--docker-entrypoint.sh
 |--Dockerfile
 |--ssh_setup.sh
 |--sshd_config
```

```docker:Dockerfile
FROM mcr.microsoft.com/azure-app-service/java:11-java11_stable

# Install OpenSSH and set the password for root to "Docker!".
RUN apk add openssh \
     && 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
RUN mkdir -p /tmp
COPY ssh_setup.sh /tmp
RUN chmod +x /tmp/ssh_setup.sh \
    && (sleep 1;/tmp/ssh_setup.sh 2>&1 > /dev/null)

# Copy and configure the docker-entrypoint file
COPY docker-entrypoint.sh /tmp
RUN chmod +x /tmp/docker-entrypoint.sh 

# Open port 2222 for SSH access
ENV PORT 80
EXPOSE 80 2222

ENTRYPOINT ["/tmp/docker-entrypoint.sh"]
```

フォルダ構成についてはサブディレクトリを追加するなどの変更が可能です。その場合は、Dockerfile 内の COPY でローカルファイルのパスを変更する必要がありますのでご注意ください。


# SSH 設定を追加したカスタム コンテナを再デプロイ

修正後の Dockerfile でコンテナをビルドして、App Service に再デプロイします。Azure ポータルの [開発ツール] > [SSH] で SSH セッションを開いてみると…  

![qiita_azuretech2022_ms_img02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2323437/0bcea4f5-6f44-4f03-7088-65d17f9f7045.png)

SSH 接続に成功して、Alpine Linux のコマンドライン ターミナルが表示されました。接続成功時は <font color="green">[SSH_CONNECTION_ESTABLISHED]</font> というメッセージが表示されます。  
これでアプリケーション コンテナ内でのコマンド実行が可能となりました。  

開発中も運用後も、実行中プロセスの確認やトレースログの採取など、管理コマンドの実行が必要なシーンは多々あるかと思います。  
Web App for Containers でカスタム コンテナを使用する場合にはマストの設定となりますので、ここでご紹介した手順が少しでもお役に立てば幸いです。


# 参考リンク
* [クイック スタート:App Service でカスタム コンテナーを実行する | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/app-service/quickstart-custom-container?tabs=java&pivots=container-linux-vscode)

* [Azure App Service のカスタム コンテナーを構成する | SSH を有効にする | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/app-service/configure-custom-container?pivots=container-linux#enable-ssh)



[^1]: 本記事の執筆時点のものとなります。