Azure Functions で Selenium を実行する

本記事は、[Microsoft Azure Tech Advent Calendar 2022 10 日目](https://qiita.com/advent-calendar/2022/microsoft-azure-tech) の投稿です。

## はじめに
Azure Functions はイベント駆動型で何かしらの処理を行うことに適したサービスとなっております。例えば、HTTP リクエストが到達した際（HTTP）、ある時刻に到達した際（タイマー）や キューにメッセージが入った際（Queue, EventHubs etc.）のイベントに対しての処理が必要となります。イベントに対してどのような処理内容を実施したいのかについては、アプリケーションコードとして記載することができるため、比較的自由度が高いと考えております。
今回は、何かしらのイベントが来たら、`ブラウザ操作`をしたいというモチベーションのもと、やってみた系の記事としてご紹介できればと思います。

## Selenium と Azure Functions
ブラウザを自動的に操作をするライブラリとしてひとつ有名なのは、[Selenium](https://www.selenium.dev/) になります。多くのプログラミング言語にて利用することができ、Web アプリケーションの UI テストの自動化を実装することが出来ます。Selenium を Azure Functions で利用する際に、Azure Functions で提供されている Windows や Linux のランタイムでは使用することができず、Google Chrome 自体や Selenium を動作するための Chrome Driver といったライブラリ、バイナリをインストールする必要があるため工夫が必要となります。
以下の Microsot Q&A サイトでは同じ悩みを持ったユーザからの投稿があり、Selenium を Azure Functions で使用する方法としては、カスタムコンテナを使用することになります。

https://learn.microsoft.com/en-us/answers/questions/227623/run-selenium-on-azure-functions.html
> This should indeed be possible with a Custom Container since you can install all required dependencies and not run into limitations of the shared environment where functions usually run on.


## [余談] 実現しているユーザがいないか探してみる
個人的には車輪の再発明というよりかは、あるものから改善をしていきたかったので、Azure Functions で Selenium を動作してブラウザ操作を行っているユーザを探したところ、お近くの方がカスタムコンテナで実現しているリポジトリを発見しました。しかしながら、クローンしたリポジトリ内のサンプルコードは正常に動作しなかったので若干のデバッグ作業をしておりました。

https://github.com/rebremer/azure-function-selenium

紆余曲折した結果、単純に Selenium のバージョンを上げると使われていない構文が存在していたため修正とプルリクエストを送りました。現在は Azure Functions ランタイムバージョン 4 の Python 3.10 として動作を確認済みです。
https://github.com/rebremer/azure-function-selenium/pull/5
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/2d0d9809-3a37-b91f-16e0-e0c6935048c5.png)


## 実際にやってみた
開発者の方が公開している Azure CLI ベースでの検証方法の記事もありますためこちらもご参照ください。以下の検証方法は主に Azure Portal を使用しております。

https://towardsdatascience.com/how-to-create-a-selenium-web-scraper-in-azure-functions-f156fd074503

### Azure リソース作成
#### Azure Function（関数アプリ）リソース作成
 Azure Portal 画面より Azure Functions のリソース作成を行います。関数アプリ > +作成 移動すると以下のスクリーンショットが表示されますので、公開は `Docker コンテナー` とオペレーティングシステムは `Linux` と指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/01e53378-15f9-1cd3-2788-ae3abe06d0fe.png)

Azure Functions のカスタムコンテナで使用できるプランは、App Service プラン または Elastic Premium プランとなり、今回はイベントドリブンでスケールは必要はないと考えているので、App Service プラン（Premium V3）として、Azure Functions のリソースを作成いたしました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/9910f3c9-0e8a-ca8b-c765-83182b88298b.png)


#### Azure Container Registry (ACR)リソース作成
カスタムコンテナのイメージを格納する Azure Container Registry のリソースを作成します。リソースグループの指定とレジストリ名、リージョン（場所）を指定し作成ボタンを押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/4c516b1c-b722-1289-ba42-8138fc74db6b.png)

#### Azure Storage アカウントリソース作成
カスタムコンテナ内の HTTP トリガーやタイマートリガーの関数コードアプリケーション内の処理では、Selenium にて取得した内容を Azure Storage アカウント内の Blob ストレージへ保存を行います。そのため新規にリソース作成を行い、Azure Functions のアプリケーション設定へ指定をするため、Azure Storage アカウント名と Blob ストレージ名の値をクリップボード等にて保存します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/9a23312f-f88e-4d42-6f69-03f71a11eec9.png)


### ローカルでの検証
Selenium をインストールする設定が記載している Azure Functions のカスタムコンテナ(Dockerfile) プロジェクトをローカル環境にて動作するために、以下の git clone コマンドを使用します。

```
$ git clone git@github.com:rebremer/azure-function-selenium.git
```

あらかじめ、Docker CLI をインストールしていただき、Dockerfile が存在するファイルのパスにて docker build コマンドを実行し Docker イメージの作成を行います。Google Chrome、Chrome Driver や Python の pip ライブラリのインストールに時間がかかりますため数分間待ちます。

```
$ docker build --tag <registry-name>.azurecr.io/<image-name>:latest .
```

作成をした Docker イメージをローカル環境にて実行するために、以下の docker run コマンドを実行します。
```
$ docker run -p 8080:80 -it <registry-name>.azurecr.io/<image-name>:latest
```

ブラウザにて `localhost:8080` アドレスへ接続を行い、以下のスクリーンショットの通り表示されている場合には正常に Azure Functions ホストが起動しております。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/f69c0069-67cb-3e37-0ccf-7de0612aff5a.png)


### Azure Container Registry (ACR) へ Docker イメージを保存
az acr show コマンドを実行して、レジストリの資格情報を取得します。このコマンドの JSON 出力からは、レジストリのユーザー名と共に 2 つのパスワードが得られます。

```
$ az acr credential show --resource-group <myResourceGroup> --name <registry-name>
```

レジストリのユーザー名とパスワードの値をコピーを行い、docker login コマンドを使用して、コンテナー レジストリにサインインします。

```
$ docker login <registry-name>.azurecr.io --username <registry-username>
```

docker push コマンドを使用してレジストリにイメージをプッシュ（保存）します。

```
$ docker push <registry-name>.azurecr.io/<image-name>:latest
```

az acr repository list コマンドを使用して Docker イメージのプッシュが成功したことを確認します。

```
$ az acr repository list -n <registry-name>
```


### Azue Functions での設定

ACR へプッシュした Docker コンテナを Azure Functions にて動作をするために Azure Portal 画面の左ブレードメニューの デプロイセンターより、レジストリソースを Azure コンテナレジストリとし、レジストリ、イメージ、タグを選択して、上部メニューの保存ボタンを押下します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/22701d29-fc4f-6a1a-6b30-cc539ed541bc.png)

サンプルコードは Selenium にて取得方法した結果を Azure Storage アカウントの Blob ストレージへ保存を行うため、クリップボード等にてコピーしていた値を Azure Portal 左ブレードメニューの 構成 > アプリケーション設定 > + 新しいアプリケーション設定 から追加をします。

```
par_storage_account_name : Azure Storage アカウント名
par_storage_container_name : Blob ストレージ名
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/80e74e38-eec0-9efc-7460-ddf019bacdad.png)

Azure Functions から Blob ストレージへ保存を行うには、マネージド ID を有効にする必要があるため、Azure Portal 画面の左ブレードメニューの ID より状態を オン にして保存ボタンを押下します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/37bf4c86-45b3-a17c-4875-fd37f0824295.png)

次に有効にしたマネージド ID を Azure Storage アカウントへ接続をするための割り当てを実施します。上記のスクリーンショットより、Azure ロールの割り当て ボタンを押下し、+ ロールの割り当ての追加 から、スコープを ストレージ として対象リソースを選択、役割を ストレージ BLOB データ共同作成者 を指定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/e33ec9af-fdfa-9607-4ee6-4f40d52b573f.png)



## 実行結果
Azure Portal 画面の 関数 > HttpTrigger >  コードとテスト > テストと実行　から実行ボタンを押下していただくことで HTTP リクエストを送信することが出来ます。HTTP 応答のコンテンツでは、http://www.ubuntu.com/ へ接続した際の HTTP レスポンスメッセージが返却されていることを確認することが出来ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/725ddad9-29e4-c829-6e5b-f57d9136703d.png)

HTTP トリガーやタイマートリガーの関数コードの実行結果は、Azure Functions のアプリケーション設定にて指定した Azure Storage アカウント内の Blob ストレージへ txt ファイルとして保存されていることを確認することが出来ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/18c474f8-d3ec-7593-d1ec-e9013d4189ce.png)


## まとめ
今回は Azure Functiosn を使用して、ブラウザ操作をしてみたいというモチベーションからカスタムコンテナを使用して検証してみました。カスタムコンテナを使用することで Azure Functions が提供していないライブラリやバイナリを入れることができるため比較的に自由度は高くなったという印象でした。HTTP トリガーやタイマートリガーのイベントは Azure Functions ホスト側で用意されているため、Selenium の環境構築とアプリケーション処理の記述になるため、ブラウザ操作にも集中出来そうかと考えております。