Azure Functionsを用いたサーバーレスなTwitter botを開発してみた（その１、環境構築でPowerShellの実行ポリシーではまったお話）

# このページの目的

Azure Functionsの開発をするための開発環境をWindows 10PC上に構築します。このページで構築した後にTwitter botサービスを作る予定です。

簡単に環境構築できると思ったのですが、PowerShellスクリプトのポリシーですごくはまったので記事を書きました。

# 開発環境

## 開発PCの環境

* Windows ホストPC
  * エディション：Windows 10 Home
  * バージョン：20H2
  * OS ビルド：19042.867
  * エクスペリエンス：Windows Feature Experience Pack 120.2212.551.0

## Azure Functions開発のための環境

初めてAzure Functionsの開発をする場合はこちらのページを参考に環境を構築します。

https://docs.microsoft.com/ja-jp/azure/azure-functions/create-first-function-vs-code-python

必要なのは次の通りです。（上記のリンクから抜粋）

* アクティブなサブスクリプションが含まれる Azure アカウント。 
* Azure Functions Core Tools バージョン 3.x。
* Azure Functions でサポートされているバージョンの Python
* サポートされているプラットフォームのいずれかにインストールされた Visual Studio Code。
* Visual Studio Code 用の Python 拡張機能。
* Visual Studio Code 用 Azure Functions 拡張機能。

# Azure Functionプロジェクトの作成

## テンプレート・プロジェクトの作成

前節で紹介した公式ページの「ローカル プロジェクトを作成する」節を参考にしてテンプレートのプロジェクトを作成します。

https://docs.microsoft.com/ja-jp/azure/azure-functions/create-first-function-vs-code-python#create-your-local-project

上記のページではHTTPトリガーのAzure Functionsプロジェクトを作る例が書かれていますが、今回はTimerTriggerのプロジェクトを作成します。cornの設定はこちらのページを参照してください。

https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-timer?tabs=csharp

関数名は「TimerTriggerTweet」にしました。

## プロジェクトの中身

テンプレートから作成したAzure Functionsプロジェクトのフォルダ・ファイルを抜粋します。（venvのincludeやLibは多くのフォルダ・ファイルがあるため省略しています。）

```
SIMPLE-TWITTER-BOT
│  .funcignore
│  .gitignore
│  host.json
│  local.settings.json
│  proxies.json
│  requirements.txt
│  
├─.venv
│  ├─Include
│  ├─Lib
│  │  └─site-packages
│  │      ├─azure
│  │      ├─azure_functions-1.7.0.dist-info
│  │      └─pip
│  └─Scripts
│          activate
│          activate.bat
│          Activate.ps1
│          deactivate.bat
│          easy_install-3.7.exe
│          easy_install.exe
│          pip.exe
│          pip3.7.exe
│          pip3.exe
│          python.exe
│          pythonw.exe
├─.vscode
│      extensions.json
│      launch.json
│      settings.json
│      tasks.json
│      
└─TimerTriggerTweet
       function.json
       readme.md
       sample.dat
       __init__.py
       
```

### プロジェクトのルート
Azure Functions全体の設定ファイルが保存されています。簡単にファイルの意味をまとめます。

| ファイル名 | 概要 |
|:-----------|:-----------|
| .funcignore       |  |
| .gitignore | Gitリポジトリにコミットしないファイルを記述 |
| host.json | Azure Functionsの設定 |
| local.settings.json | ローカル実行するときの設定 |
| proxies.json | プロキシサーバー(L7 ファイヤーフォール)の設定 |
| requirements.txt | 動作させるのに必要なPythonパッケージを記述 |

### .venv

Pythonの仮想環境venvフォルダ。ホストにpipコマンドでモジュールをインストールするとすべてのPythonスクリプトで共用することになりますが、プロジェクトごとにvenvで仮想的な実行環境を構築すると、プロジェクトごとに必要なモジュールが明確になります。また、モジュールのバージョンも指定できるので他の環境にPythonスクリプトをデプロイするのが楽になります。

Azure Functionsではローカルの開発環境とAzureの実行環境をそろえるためにデフォルトでvenvを使っているのだと思います。

https://docs.python.org/ja/3/library/venv.html

### .vscode

Visual Studio Codeプロジェクトの設定ファイルの保存場所。

# ローカルでデバッグ実行

公式ページの「関数をローカルで実行する」を試します。

https://docs.microsoft.com/ja-jp/azure/azure-functions/create-first-function-vs-code-python#run-the-function-locally

## セキュリティーエラー発生

当方の環境では次のエラーが発生して実行できませんでした。

```bash
> Executing task: .venv\Scripts\python -m pip install -r requirements.txt <

Requirement already satisfied: azure-functions in \simple-twitter-bot\.venv\lib\site-packages (from -r requirements.txt (line 5)) (1.7.0)
WARNING: You are using pip version 20.1.1; however, version 21.1.1 is available.
You should consider upgrading via the 'simple-twitter-bot\.venv\Scripts\python.exe -m pip install --upgrade pip' command.

Terminal will be reused by tasks, press any key to close it.

> Executing task: .venv\Scripts\activate ; func host start <

.venv\Scripts\activate : このシステムではスクリプトの実行が無効になっているため、ファイル simple-twitter-bot\.venv\Scripts\Activate.ps1 を読み込むことができません。
詳細については、「about_Execution_Policies」(https://go.microsoft.com/fwlink/?LinkID=135170) を参照してください。
発生場所 行:1 文字:1
+ .venv\Scripts\activate ; func host start
+ ~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : セキュリティ エラー: (: ) []、PSSecurityException
    + FullyQualifiedErrorId : UnauthorizedAccess
Found Python version 3.7.9 (python3).

Azure Functions Core Tools
Core Tools Version:       3.0.3477 Commit hash: 5fbb9a76fc00e4168f2cc90d6ff0afe5373afc6d  (64-bit)
Function Runtime Version: 3.0.15584.0

[2021-05-16T06:15:46.376Z] File 'C:\Program Files\dotnet\dotnet.exe' is not found, 'dotnet' invocation will rely on the PATH environment variable.
[2021-05-16T06:16:00.802Z] File 'C:\Program Files\dotnet\dotnet.exe' is not found, 'dotnet' invocation will rely on the PATH environment variable.
[2021-05-16T06:16:01.231Z] File 'C:\Program Files\dotnet\dotnet.exe' is not found, 'dotnet' invocation will rely on the PATH environment variable.

Functions:

        TimerTriggerTweet: timerTrigger

For detailed output, run func with --verbose flag.
[2021-05-16T06:16:06.376Z] Worker process started and initialized.
```

### PSSecurityExceptionエラーの原因

Visual Studio CodeのTerminalはWindows版ではPower Shellが使われています。Azure Functionsのデバッグ環境を立ち上げるとまず、venvが起動します。venvの起動は`.venv/Scripts/Activate.ps1`というPower Shellスクリプトを実行することで行われますが、Power Shellのスクリプト実行ポリシーがスクリプトの実行を許可していないためエラーになっているようです。

私の環境のPower Shellスクリプトの実行ポリシーを`Get-ExecutionPolicy`コマンド（`-List`オプション付き）で調べたところすべてのポリシーが`Undifined`になっていました。

```powershell
PS > Get-ExecutionPolicy -List

        Scope ExecutionPolicy
        ----- ---------------
MachinePolicy       Undefined
   UserPolicy       Undefined
      Process       Undefined
  CurrentUser       Undefined
 LocalMachine       Undefined


PS >
```

`Undefined`はMicrosoftのドキュメントによると`現在のスコープには実行ポリシーが設定されていません。すべてのスコープの実行ポリシーがの場合 Undefined 、有効な実行ポリシーは windows クライアントの場合は、 Restricted windows Server の場合は RemoteSigned です。`とのことです。

https://docs.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_execution_policies?view=powershell-7.1#undefined

実行ポリシーには優先度があり、先ほどのリンクによると次の順番とのことです。上のほうが優先度が高いようです。

```
Group Policy: MachinePolicy
Group Policy: UserPolicy
Execution Policy: Process (or pwsh.exe -ExecutionPolicy)
Execution Policy: CurrentUser
Execution Policy: LocalMachine
```

`Get-ExecutionPolicy`コマンドを`-List`オプションなしで実行すると現在適用されるポリシーを表示してくれます。試したところ、`Restricted`になっていました。

```powershell
PS > Get-ExecutionPolicy      
Restricted
PS >
```

私のホストはWindows 10なので`Restricted`と同様の実行ポリシーになっているようです。上記のページから抜粋すると`Windows クライアントコンピューターの既定の実行ポリシー。個々のコマンドを許可しますが、スクリプトは許可しません。書式設定ファイル、構成ファイル () .ps1xml 、モジュールスクリプトファイル ()、PowerShell プロファイル () など、すべてのスクリプトファイルの実行を禁止 .psm1 .ps1 します。`のようです。エラーになるわけです。

### PSSecurityExceptionエラーの解決

`Set-ExecutionPolicy`コマンドでポリシーを変えればいいのですが、それは最後の手段だと思います。アクセス権は必要最低限のスコープに対して設定すべきです。この場合の必要最低限はVisual Studio Codeが実行するAzure Functionsプロジェクトのデバッグ実行ができること、なので実行ポリシーのスコープは`Process`にすることが望ましいです。設定するポリシーは`RemoteSigned`にします。これはWindows serverの規定なので今回はこれにしましたが、読者さんの環境に応じたポリシーを設定してください。

実行ポリシーのスコープ`Process`に対して`RemoteSigned`ポリシーを設定するには次のコマンドを実行するとよいようです。

``` powershell
PS > Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned
```

今回はVSCodeからの実行でVisual Studio Code 用 Azure Functions 拡張機能が起動し、機能拡張がvenvのactivate psスクリプトをキックしていると考えられます。ですのでこのコマンドをVSCodeのターミナルからたたいてもスコープがそのターミナルに閉じられてしまします。

先ほどのMicrosoftの公式ページによると、`スコープは、 Process 現在の PowerShell セッションにのみ影響します。 実行ポリシーは、レジストリではなく、環境変数に保存され $env:PSExecutionPolicyPreference ます。`とあります。日本語がおかしいのでわかりにくいですが`Process`スコープのポリシーは環境変数`PSExecutionPolicyPreference`に書き込まれるようです。そのため、今回はVSCodeの`settings.json`に次の設定を記述することにしました。（`settings.json`は`.vscode/settings.json`に保存されています。）

```json
  "terminal.integrated.env.windows": {
    "PSExecutionPolicyPreference": "RemoteSigned"
  }
```

これはVSCodeでvenvを使うときによくある設定のようです。この`settings.json`はVSCodeが起動するときに読み込まれるので、この記述をした場合は一度VSCodeを終了してから再度起動してください。（僕はこれにめちゃくちゃはまりました(´・ω・｀)）

## ローカルでデバッグ実行成功

VSCodeを再起動したら無事起動できました。

```powershell
> Executing task: .venv\Scripts\python -m pip install -r requirements.txt <

Requirement already satisfied: azure-functions in simple-twitter-bot\.venv\lib\site-packages (from -r requirements.txt (line 5)) (1.7.0)
WARNING: You are using pip version 20.1.1; however, version 21.1.2 is available.
You should consider upgrading via the 'simple-twitter-bot\.venv\Scripts\python.exe -m pip install --upgrade pip' command.

Terminal will be reused by tasks, press any key to close it.

> Executing task: .venv\Scripts\activate ; func host start <

Found Python version 3.7.9 (python3).

Azure Functions Core Tools
Core Tools Version:       3.0.3477 Commit hash: 5fbb9a76fc00e4168f2cc90d6ff0afe5373afc6d  (64-bit)
Function Runtime Version: 3.0.15584.0

[2021-06-13T15:25:45.582Z] File 'C:\Program Files\dotnet\dotnet.exe' is not found, 'dotnet' invocation will rely on the PATH environment variable.
[2021-06-13T15:25:46.423Z] File 'C:\Program Files\dotnet\dotnet.exe' is not found, 'dotnet' invocation will rely on the PATH environment variable.
[2021-06-13T15:25:46.632Z] File 'C:\Program Files\dotnet\dotnet.exe' is not found, 'dotnet' invocation will rely on the PATH environment variable.

Functions:

        TimerTriggerTweet: timerTrigger

For detailed output, run func with --verbose flag.
[2021-06-13T15:25:48.910Z] Worker process started and initialized.
```

## ローカル実行のシーケンスを想像する

最初に、`.vscode/tasks.json`が実行されます。pythonの`pip`コマンドを使って仮想環境`venv`に対して必要なライブラリをインストールするようです。必要なライブラリはプロジェクトルートにある`requirements.txt`に記述するようです。デフォルトでは`azure-functions`をインストールするようです。


# まとめ

もともとはTwitter botサービスを作る、という趣旨で記事を書いていたのを無理やりタイトル変えたのでおかしいところがあればご指摘ください。次回からいよいよサーバレスアーキテクチャによるサービス開始を開始します。