今回の勉強内容
Azureを利用したWebシステムを勉強したいと思い…、
・SQLサーバを利用する
・BLOBストレージを利用する
・FunctionsにWebAPIを用意する
・AppServiceでreactを稼働させフロントにする
ぐらいを目標に試行錯誤したものの、Functionsは無料で構築はできない様子…
必ずStrageが必要になるのですが、そちらで数円ながら、コストが発生するのが不可避と理解しました。さらに調べた結果、Node.jsを利用し、「func start」とする事で、単体テスト環境になるそうです。<ローカル実行なので無料です> とは言え、AppServiceのサーバ処理として勉強を継続することは不可能ですし、コストを掛けずに勉強するのは諦めた次第です。
今回苦しめられた事
Azure SQLserver を無料枠で利用していたのですが、接続エラーが頻発する事が判明。
本番環境用だと、そんな事は無いのでしょうか? 少し不安に感じる程でした。
Azure Functions (WebApi:dotnet) 開発環境の準備
プロジェクトテンプレート(Azure Functions = func)を手に入れます
dotnet new install Microsoft.Azure.Functions.Worker.ProjectTemplates
アイテムテンプレート(http)を手に入れます
dotnet new install Microsoft.Azure.Functions.Worker.ItemTemplates
プロジェクトを作成します
dotnet new func -n webapi3
カレントをプロジェクトの中に移動させて、テンプレートを導入します
dotnet new http -n GetJsonApi
Azure Functions (フォルダ構成とURL)
名前空間(namaspace)とフォルダ構成を揃える必要はありません。(結構自由です)
デフォルトのURLでは「/api/」が必要ですが、以下の「routePrefix」を追加すると不要になります。
{
"version": "2.0",
"extensions": {
"http": {
"routePrefix": ""
}
}
}
Azure Functions (ローカルDevOpsからデプロイ)
パイプラインは、以下の感じで処理されるようです。
trigger:
- master
pool:
name: default
steps:
- task: UseDotNet@2
inputs:
packageType: 'sdk'
version: '9.0.x'
- task: DotNetCoreCLI@2
inputs:
command: 'publish'
publishWebProjects: false
arguments: '--configuration Release --output $(Build.ArtifactStagingDirectory)'
zipAfterPublish: true
- task: AzureFunctionApp@2
inputs:
azureSubscription: 'devops_func2' ← DevOpsのサービス接続名です
appType: 'functionApp'
appName: 'Azure Functions の名前'
package: '$(Build.ArtifactStagingDirectory)/**/*.zip'
Azure Strage の登録
Functions の登録にあたり、Strageが必須となるようです。
とりあえず、勉強したいだけなので、極力無料枠で進めます。(余り、気合が入ってない方を選択したらOKです=笑)

その他、どうしても無料で使えないことから、余り真剣に見てませんが、全てデフォルトです。コツは「HOT」を選択する事でしょうか?Functionsの稼働ログが保管される関係で、HOTが無難なのだろうと思われます。
Azure Functions の登録
ホスティングオプションで、どれ選ぶ? となりそうですが、WebAPI として利用する本番環境なら「App Service」でしょうか? 理由としては、Functions は利用されないと、SLEEP するようで、SLEEP 後の初回アクセスの初動が遅れるようです。これを防ぐとすると、決められたリソースの割り当てられそうな、「App Service」なのだろうと…?無料枠を使う時は、迷わず「従量課金」です。使い過ぎなければ、無料枠が適用されます。

全てデフォルトでは、有料まっしぐらです。Insightは、コストが上がるので止めておきます。(エラーログが見えないのは辛いですが…)

リージョンだったり、OSだったりで、作成時にエラーが出るようです。
このあたり、Copilot で確認すると、リソースグループを別にする必要がある等、少々手間が発生するようでした。
Azure Functions の実行
セキュリティ対策として、httpのヘッダー情報(又はGetパラメータ)を必須にすることが可能です。
パラメータの値は、「関数」の「アプリキー」に準備されています。

これらの使い方を、以下に列挙してみます。
PowerShellで実行
Invoke-WebRequest -Method GET -Uri "https://(Function名).azurewebsites.net/api/GetJsonApi" -Headers @{"x-functions-key"="アプリキーの値"} ''
Getパラメータの指定よりブラウザで実行
https://(Function名).azurewebsites.net/api/GetJsonApi?code=アプリキーの値
プログラムを修正( Function ⇒ Anonymous )してURLだけで実行
セキュリティを無視して、誰でも使えるようにする場合の対応です。
https://(Function名).azurewebsites.net/api/GetJsonApi
[Function("GetJsonApi")]
- public IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequest req)
+ public IActionResult Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req)
Azure Functions (環境変数を利用する)
開発環境と本番環境で、別の Functions を契約する場合、DBの接続先も、別になるでしょう。でも、プログラムは同じにしたいですよね。
そんな時に、Functions の「設定」から「環境変数」を設定し、利用することが可能です。
(以下は、DbConnStr というキーで、「・・・接続文字列とか・・・」を設定しています)

この環境変数を、プログラムで利用する場合は以下の処理が必要です。
string kankyo = Environment.GetEnvironmentVariable("DbConnStr");
この環境変数の機能は、Functions だけの機能ではなく、AppServiceでも同じような機能が用意されているようです。
ローカルでの環境構築(func start)
費用が掛かるので、こちらの対応を取りました。
今回、dotnet SDK と、Nodejs は事前に準備済のパソコンを利用しておりました。
なので、以下のコマンドを実行するだけで、「func start」が実行可能でした。
npm install -g azure-functions-core-tools@4 --unsafe-perm true
Functions と言えば、Httpトリガだけでなく、タイマー起動も可能です。
タイマー起動では、ストレージが必須になるので、Azuriteも準備しましょう。
npm install -g azurite
Azurite の実行は、空っぽのディレクトリをルートにして、実行しましょう。
(色々とファイルが作成されます)
azurite
準備ができたら、プロジェクトのルートで実行します。
(Azurite とは別のプロセスが必要です)
func start
Functionsで定義した環境変数については、「local.settings.json」で定義します
タイマーの利用
cronの設定と同じようなスケジュールが可能のようです。
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"TimerSchedule": "0 */1 * * * *", // 毎分実行
"DbConnStr": "Server=myserver;Database=mydb;User Id=xxx;Password=yyy;(接続文字列)"
}
}
パッケージの追加も必要です。
+ <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Timer" />
C# の実装において、以下のような制御となるようです。
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Extensions.Timer;
using Microsoft.Extensions.Logging;
namespace Company;
public class GetJsonTimer
{
private readonly ILogger<GetJsonTimer> _logger;
public GetJsonTimer(ILogger<GetJsonTimer> logger)
{
_logger = logger;
}
[Function("GetJsonTimer")]
public void Run([TimerTrigger("%TimerSchedule%")] TimerInfo myTimer)
{
string kankyo = Environment.GetEnvironmentVariable("DbConnStr");
_logger.LogInformation($"Timer function executed at: {DateTime.Now}");
_logger.LogInformation($"Connection string: {kankyo}");
}
}
Azure Functions データベース連携
以前、AppService で、SQLサーバ接続をやりました。
今回は、IDとパスワードだけで、少し手軽に(古臭く)実装してみたいと思います。
まず、パッケージの追加です。
+ <PackageReference Include="Microsoft.Data.SqlClient" />
SQLデータベースへの接続文字列を設定する必要があります。
+ "SqlConnectionString": "Server=tcp:SQLサーバ名.database.windows.net,1433;Initial Catalog=データベース名;Persist Security Info=False;User ID=ユーザID;Password=パスワード;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
最後に、httpトリガのプログラムです。
using System.Data;
using Microsoft.Data.SqlClient;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using System.Text.Json;
namespace MyFunctionProj {
public class GetUsers {
private readonly string _connectionString;
public GetUsers() {
_connectionString = Environment.GetEnvironmentVariable("SqlConnectionString");
}
[Function("GetUsers")]
public async Task<HttpResponseData> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "users")] HttpRequestData req) {
var users = new List<object>();
using (var conn = new SqlConnection(_connectionString)) {
await conn.OpenAsync();
var cmd = new SqlCommand("SELECT UserID, FirstName, LastName, Email FROM tUser", conn);
using (var reader = await cmd.ExecuteReaderAsync()) {
while (await reader.ReadAsync()) {
users.Add(new {
UserID = reader["UserID"],
FirstName = reader["FirstName"],
LastName = reader["LastName"],
Email = reader["Email"]
});
}
}
}
var response = req.CreateResponse(System.Net.HttpStatusCode.OK);
response.Headers.Add("Content-Type", "application/json; charset=utf-8");
await response.WriteStringAsync(JsonSerializer.Serialize(users));
return response;
}
}
}
最後に
なかなか、無料で勉強するのも難しいですねぇ…。
呑みに行く場合と比較すると安くて、身になる投資と思えば納得も出来るのですが、支払いを考えると、無料で勉強する私にとっては敗北感多々です。
以上、お疲れさまでした~。