Azure Learn AZ-204 The Hard Way | 實作 AZ-204 Lab 01 App Services


  1. Lab01 - Web App
    1. Storage Account
    2. Web App API
    3. Web App Web Site
  2. App Service 相關知識
    1. Auto Scaling
    2. Deployment Slots
    3. Authentication
    4. Logging
  3. 實戰 App Service 心得

實作 AZ-204 證照測驗準備的實驗課程,從實驗課程中精熟 AZ-204 的測驗重點以及工具操作。

本次要實驗的是如何使用 Azure App Service,取代傳統使用作業系統搭配 IIS 的部署方式,App Service 是 Fully Managed 的 Platform as a service 服務,不再需要管理作業系統更新、網路機制等問題,對於開發者而言需要煩惱的事情減到最少的部署方式。

logo

Lab01 - Web App

詳細的實驗流程可以參考微軟在 GitHub上的專案

圖片來源:微軟

Storage Account

建立 Storage Account

取得 API Key 以及 ConnectionString 提供後續 Web App 使用

建立 Storage Container (Blob)

測試上傳圖片

Web App API

建立 Web App - API


設定 AppSettings


瀏覽 Web App

建立好的 Web App 初始畫面如附圖

將微軟提供的範例程式部署至 Web App

Source Code | GitHub

ImagesController.cs

[ApiController]
[Route("/")]
public class ImagesController : ControllerBase
{
    private HttpClient _httpClient;
    private Options _options;

    public ImagesController(HttpClient httpClient, Options options)
    {
        _httpClient = httpClient;
        _options = options;
    }

    private async Task<BlobContainerClient> GetCloudBlobContainer(string containerName)
    {
        BlobServiceClient serviceClient = 
          new BlobServiceClient(_options.StorageConnectionString);
        BlobContainerClient containerClient = 
          serviceClient.GetBlobContainerClient(containerName);
        await containerClient.CreateIfNotExistsAsync();
        return containerClient;
    }

    [Route("/")]
    [HttpGet]
    public async Task<ActionResult<IEnumerable<string>>> Get()
    {
        BlobContainerClient containerClient = 
          await GetCloudBlobContainer(_options.FullImageContainerName);
        List<string> results = new List<string>();
        await foreach (BlobItem blobItem in containerClient.GetBlobsAsync())
        {
            results.Add(
                Flurl.Url.Combine(
                    containerClient.Uri.AbsoluteUri,
                    blobItem.Name
                )
            );
        }
        Console.Out.WriteLine("Got Images");
        return Ok(results);
    }

    [Route("/")]
    [HttpPost]
    public async Task<ActionResult> Post()
    {
        Stream image = Request.Body;
        BlobContainerClient containerClient = 
          await GetCloudBlobContainer(_options.FullImageContainerName);
        string blobName = Guid.NewGuid().ToString().ToLower().Replace("-", String.Empty);
        BlobClient blobClient = containerClient.GetBlobClient(blobName);
        await blobClient.UploadAsync(image);
        return Created(blobClient.Uri, null);
    }
}

Program.cs

public class Program
{
    public static async Task Main(string[] args) =>
        await Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            })
            .Build()
            .RunAsync();
}

public class Startup
{
    private IConfiguration _configuration { get; }

    public Startup(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<Options>(_configuration.Get<Options>());
        services.AddSingleton<HttpClient>(new HttpClient());
        services.AddControllers();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseDeveloperExceptionPage();
        app.UseRouting();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

使用 az cli 來進行部署作業

az webapp deployment source config-zip 
  --resource-group ManagedPlatform 
  --src api.zip 
  --name imgapics

部署完成後,再次瀏覽可以看到目前 Web App API 就是將 Storage Container 當中的 Blod Images 以陣列的方式將 Imgeas URL 回應。

除了使用 az cli 進行部署作業外,也可以從 Visual Studio Code 安裝 Azure App Service Extensions 來進行部署作業。


Web App Web Site

建立 Web App - Web Site

設定 AppSettings,介接到先前完成部署的 Web App API

將微軟提供的程式更新部署至 Web App

Source Code | GitHub

Index.cshtml.cs

public class IndexModel : PageModel
{
    private HttpClient _httpClient;
    private Options _options;

    public IndexModel(HttpClient httpClient, Options options)
    {
        _httpClient = httpClient;
        _options = options;
    }

    [BindProperty]
    public List<string> ImageList { get; private set; }

    [BindProperty]
    public IFormFile Upload { get; set; }

    public async Task OnGetAsync()
    {
        var imagesUrl = _options.ApiUrl;

        string imagesJson = await _httpClient.GetStringAsync(imagesUrl);

        IEnumerable<string> imagesList = 
          JsonConvert.DeserializeObject<IEnumerable<string>>(imagesJson);

        this.ImageList = imagesList.ToList<string>();
    }

    public async Task<IActionResult> OnPostAsync()
    {
        if (Upload != null && Upload.Length > 0)
        {
            var imagesUrl = _options.ApiUrl;

            using (var image = new StreamContent(Upload.OpenReadStream()))
            {
                image.Headers.ContentType = 
                  new MediaTypeHeaderValue(Upload.ContentType);
                var response = await _httpClient.PostAsync(imagesUrl, image);
            }
        }
        return RedirectToPage("/Index");
    }
}

Program.cs

 public class Program
{
    public static async Task Main(string[] args) =>
        await Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            })
            .Build()
            .RunAsync();
}

public class Startup
{
    private IConfiguration _configuration { get; }

    public Startup(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<Options>(_configuration.Get<Options>());
        services.AddSingleton<HttpClient>(new HttpClient());
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseDeveloperExceptionPage();
        app.UseStaticFiles();
        app.UseRouting();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapRazorPages();
        });
    }
}

使用 az cli 來進行部署作業

az webapp deployment source config-zip 
  --resource-group ManagedPlatform 
  --src web.zip 
  --name imgwebcs

完成部署後可以瀏覽成果:

測試使用上傳圖片的功能:

App Service 相關知識

Auto Scaling

設定 Scaling Rule
觀察 Instance 的變化

Deployment Slots


Slots 可以交換
Slots 用於分流

Authentication


Logging

實戰 App Service 心得

App Service 提供了 Free Tier 免費使用,雖然有限制每天的 CPU 運算時間以及儲存空間,但對於測試性質的網站服務而言應該相當足夠,值得馬上進駐使用。

此外 App Service 提供了 MySQL In App (可惜不是 SQL Server Express,相關知識無法發揮 😗),更包含 phyMyAdmin 管理工具,讓基本的網站服務可以搭配資料庫進行應用,非常方便。此外也可以考慮使用 sqlite 的方式,設計資料庫類型的網站服務,在 Free Tier 下就有如此豐富的資源可以使用,對於開發者而言,實在是福音 😍

進階管理的工具包含 Kudu 可以用 GUI Explorer 的方式瀏覽 App Service 的目錄結構以及檔案,此外目前 Preview 的 App Service Editor 可以直接用於在 App Service 上檔案的編輯,讓 PAAS 的站台管理體驗更好。

實際應用部署作業上,除了可以使用 Visual Studio 直接部署到 App Service,也可以透過 Azure DevOps 以 Continuous Deployment 的方式部署,或者是 App Service 提供的 Private Git 位址,直接由 Visual Studio 推送到 App Service 專案上,部署的方式非常方便,幾乎與發佈到 IIS 無異。

使用 Azure Service PAAS 平台的最大好處在於可以省去許多繁複的作業系統管理、網站伺服器管理等設定,專注在功能的實現 😄