0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

今回の勉強内容
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」を追加すると不要になります。

host.json
{
  "version": "2.0",
  "extensions": {
    "http": {
      "routePrefix": ""
    }
  }
}

Azure Functions (ローカルDevOpsからデプロイ)
パイプラインは、以下の感じで処理されるようです。

azure-pipelines.yml
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です=笑)
image.png
その他、どうしても無料で使えないことから、余り真剣に見てませんが、全てデフォルトです。コツは「HOT」を選択する事でしょうか?Functionsの稼働ログが保管される関係で、HOTが無難なのだろうと思われます。

Azure Functions の登録
ホスティングオプションで、どれ選ぶ? となりそうですが、WebAPI として利用する本番環境なら「App Service」でしょうか? 理由としては、Functions は利用されないと、SLEEP するようで、SLEEP 後の初回アクセスの初動が遅れるようです。これを防ぐとすると、決められたリソースの割り当てられそうな、「App Service」なのだろうと…?無料枠を使う時は、迷わず「従量課金」です。使い過ぎなければ、無料枠が適用されます。
image.png
全てデフォルトでは、有料まっしぐらです。Insightは、コストが上がるので止めておきます。(エラーログが見えないのは辛いですが…)
image.png
リージョンだったり、OSだったりで、作成時にエラーが出るようです。
このあたり、Copilot で確認すると、リソースグループを別にする必要がある等、少々手間が発生するようでした。

Azure Functions の実行
セキュリティ対策として、httpのヘッダー情報(又はGetパラメータ)を必須にすることが可能です。
パラメータの値は、「関数」の「アプリキー」に準備されています。
image.png
これらの使い方を、以下に列挙してみます。

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

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 というキーで、「・・・接続文字列とか・・・」を設定しています)
image.png
この環境変数を、プログラムで利用する場合は以下の処理が必要です。

C_Sharp
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の設定と同じようなスケジュールが可能のようです。

local.settings.json
{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
    "TimerSchedule": "0 */1 * * * *",   // 毎分実行 
    "DbConnStr": "Server=myserver;Database=mydb;User Id=xxx;Password=yyy;(接続文字列)"
  }
}

パッケージの追加も必要です。

csprojファイル
+    <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Timer" />

C# の実装において、以下のような制御となるようです。

GetJsonTimer.cs
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とパスワードだけで、少し手軽に(古臭く)実装してみたいと思います。
まず、パッケージの追加です。

csprojファイル
+        <PackageReference Include="Microsoft.Data.SqlClient" />

SQLデータベースへの接続文字列を設定する必要があります。

local.settings.json(Valuesに追加)
+    "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トリガのプログラムです。

GetUsers.cs
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;
        }
    }
}

最後に
なかなか、無料で勉強するのも難しいですねぇ…。
呑みに行く場合と比較すると安くて、身になる投資と思えば納得も出来るのですが、支払いを考えると、無料で勉強する私にとっては敗北感多々です。
以上、お疲れさまでした~。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?