Azure Beginner Guide

Azure for beginnersAzure สำหรับผู้เริ่มต้น

Azure Beginner Guide

Microsoft's cloud, tried with Microsoft's own emulatorsคลาวด์ของ Microsoft ทดลองด้วย emulator ของ Microsoft เอง

Microsoft Azure is the cloud many companies already use with Microsoft 365. This guide covers the services a typical app starts with: Blob, Queue and Table storage, the Cosmos DB database, serverless Azure Functions, Bicep for infrastructure as code, and the SDK, plus how Entra ID and RBAC control access. Microsoft publishes emulators for its storage, Cosmos DB and Functions so developers can work offline; this guide uses them, so you need no subscription. Microsoft Azure คือคลาวด์ที่หลายบริษัทใช้อยู่แล้วคู่กับ Microsoft 365 คู่มือนี้ครอบคลุมบริการที่แอปทั่วไปเริ่มใช้ ได้แก่ storage แบบ Blob, Queue และ Table ฐานข้อมูล Cosmos DB Azure Functions แบบ serverless Bicep สำหรับ infrastructure as code และ SDK รวมถึงการควบคุมสิทธิ์ด้วย Entra ID และ RBAC Microsoft เผยแพร่ emulator ของ storage, Cosmos DB และ Functions ให้นักพัฒนาทำงานแบบออฟไลน์ได้ คู่มือนี้ใช้ emulator เหล่านั้น จึงไม่ต้องมี subscription

Every command marked with a terminal on this page was run for real against Azurite and the Cosmos DB emulator, and the output is what it printed. Commands that need a real subscription are labelled "not run". The ideas behind regions and IAM are in Cloud Fundamentals. ทุกคำสั่งที่อยู่ในเทอร์มินัลในหน้านี้รันจริงกับ Azurite และ emulator ของ Cosmos DB และผลลัพธ์คือสิ่งที่มันพิมพ์ออกมา คำสั่งที่ต้องใช้ subscription จริงจะมีป้าย "ไม่ได้รัน" แนวคิดเรื่อง region และ IAM อยู่ใน Cloud Fundamentals

  • Run withรันด้วย Azure CLI 2.90
  • Azurite 3.37
  • Functions Core Tools 4.15
  • Bicep 0.47

Lesson 1 · Setupบทที่ 1 · เตรียมพร้อม

Azure and the labAzure และแล็บ

Azure organises everything in a tree. A tenant is your organisation's directory in Entra ID (users, groups, apps). Under it are subscriptions, the unit of billing and limits. Each subscription holds resource groups, folders that keep the resources of one app or environment together; delete the group and everything in it goes. Every resource (a storage account, a database, a function app) lives in one group and one region, such as Southeast Asia (Singapore). Azure จัดทุกอย่างเป็นโครงสร้างต้นไม้ tenant คือไดเรกทอรีขององค์กรเราใน Entra ID (ผู้ใช้ กลุ่ม แอป) ใต้ tenant มี subscription ซึ่งเป็นหน่วยของการคิดเงินและขีดจำกัด แต่ละ subscription มี resource group ซึ่งเป็นเหมือนโฟลเดอร์ที่รวมทรัพยากรของแอปหรือสภาพแวดล้อมหนึ่งไว้ด้วยกัน ลบ group แล้วทุกอย่างข้างในจะหายไปด้วย ทรัพยากร (resource) ทุกตัว (บัญชี storage ฐานข้อมูล function app) อยู่ใน group หนึ่งและ region หนึ่ง เช่น Southeast Asia (สิงคโปร์)

The lab: Azurite emulates Blob, Queue and Table storage; the Cosmos DB emulator runs the database; Azure Functions Core Tools (func) runs functions locally; the Azure CLI (az) talks to all of it. A storage account is reached with a connection string; Azurite's is published by Microsoft, with a fixed development account and key. แล็บประกอบด้วย Azurite ที่จำลอง Blob, Queue และ Table storage, emulator ของ Cosmos DB ที่รันฐานข้อมูล, Azure Functions Core Tools (func) ที่รันฟังก์ชันในเครื่อง และ Azure CLI (az) ที่คุยกับทุกส่วน บัญชี storage เข้าถึงด้วย connection string ซึ่งของ Azurite ถูกเผยแพร่โดย Microsoft พร้อมบัญชีและกุญแจสำหรับพัฒนาที่ตายตัว

Terminal, in ~/azure-labเทอร์มินัล ใน ~/azure-labAzure CLI 2.90 · Azurite 3.37
$ docker run -d --name azurite -p 10000:10000 -p 10001:10001 -p 10002:10002 mcr.microsoft.com/azure-storage/azurite
bacd76c5b559a8b39af8fa9063a5e33d17c7571bafb9a092986a4969f86c0a02

$ docker run -d --name cosmos -p 8081:8081 -p 1234:1234 mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:vnext-preview
d6146649f414ad13da82aecb34480b5757ed1cbf87fb2d6ff73f8ccbccb085ba

$ docker logs azurite
Azurite Blob service is starting at http://0.0.0.0:10000
Azurite Blob service is successfully listening at http://0.0.0.0:10000
Azurite Queue service is starting at http://0.0.0.0:10001
Azurite Queue service is successfully listening at http://0.0.0.0:10001
Azurite Table service is starting at http://0.0.0.0:10002
Azurite Table service is successfully listening at http://0.0.0.0:10002

$ az version --query '"azure-cli"' -o tsv
2.90.0

$ func --version
4.15.2

Each storage service has its own port: Blob 10000, Queue 10001, Table 10002. Core Tools comes from npm (npm install -g azure-functions-core-tools@4) or Homebrew.บริการ storage แต่ละตัวมีพอร์ตของตัวเอง Blob 10000, Queue 10001 และ Table 10002 ส่วน Core Tools ติดตั้งจาก npm (npm install -g azure-functions-core-tools@4) หรือ Homebrew

Real AzureAzure จริง

With a subscription you sign in with az login and create resources in a group: az group create --name shop-dev --location southeastasia, then az storage account create. In application code, don't use connection strings with keys at all: give the app a managed identity (Lesson 7).ถ้ามี subscription ให้ล็อกอินด้วย az login แล้วสร้างทรัพยากรใน group: az group create --name shop-dev --location southeastasia แล้ว az storage account create ในโค้ดของแอปอย่าใช้ connection string ที่มีกุญแจเลย ให้ใช้ managed identity กับแอปแทน (บทที่ 7)

Lesson 2 · Storageบทที่ 2 · Storage

Blob Storage: filesBlob Storage: ไฟล์

Azure's object storage. A storage account holds containers, and containers hold blobs (files). Each blob has an access tier: Hot for files in use, Cool and Cold for files read rarely, Archive for files kept for years. Colder tiers cost less to store and more to read. object storage ของ Azure บัญชี storage มี container และ container มี blob (ไฟล์) blob แต่ละตัวมี access tier คือ Hot สำหรับไฟล์ที่ใช้อยู่ Cool และ Cold สำหรับไฟล์ที่อ่านไม่บ่อย และ Archive สำหรับไฟล์ที่เก็บไว้หลายปี tier ที่เย็นกว่าเก็บได้ถูกกว่าแต่อ่านแพงกว่า

Terminal, in ~/azure-labเทอร์มินัล ใน ~/azure-labAzure CLI 2.90 · Azurite 3.37
$ az storage container create --name photos
{
  "created": true
}

$ az storage blob upload --container-name photos --name 2026/cat.jpg --file cat.jpg --no-progress --query etag -o tsv
"0x20646382B712DA0"

$ az storage blob list --container-name photos --query '[].{name:name, size:properties.contentLength, type:properties.contentSettings.contentType, tier:properties.blobTier}' -o table
Name          Size    Tier
------------  ------  ------
2026/cat.jpg  33      Hot

$ az storage blob download --container-name photos --name 2026/cat.jpg --file copy.jpg --no-progress -o none && cat copy.jpg
pretend this is a photo of a cat

--query uses JMESPath to pick fields, like the AWS CLI; -o table prints them as a table and dropped the empty type column. Like S3 keys, the 2026/ in the name is just part of the name.--query ใช้ JMESPath เลือกฟิลด์ เหมือน AWS CLI และ -o table พิมพ์เป็นตาราง โดยตัดคอลัมน์ type ที่ว่างออก 2026/ ในชื่อเป็นแค่ส่วนหนึ่งของชื่อ เหมือน key ของ S3

Containers are private: anonymous requests get 403. To share one file, generate a SAS (shared access signature): a URL with permissions and an expiry, signed with the account key. container เป็นแบบส่วนตัว คำขอแบบไม่ระบุตัวตนจะได้ 403 ถ้าต้องการแชร์ไฟล์หนึ่ง ให้สร้าง SAS (shared access signature) ซึ่งเป็น URL ที่มีสิทธิ์และวันหมดอายุ ลงนามด้วยกุญแจของบัญชี

Terminal, in ~/azure-labเทอร์มินัล ใน ~/azure-labAzure CLI 2.90 · Azurite 3.37
$ curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:10000/devstoreaccount1/photos/2026/cat.jpg
403

$ SAS_URL=$(az storage blob generate-sas --container-name photos --name 2026/cat.jpg --permissions r --expiry 2026-12-31T00:00Z --full-uri -o tsv)
$ echo "$SAS_URL" | cut -c1-90
$ curl -s "$SAS_URL"
http://127.0.0.1:10000/devstoreaccount1/photos/2026/cat.jpg?se=2026-12-31T00%3A00Z&sp=r&sv
pretend this is a photo of a cat

sp=r is read-only and se= is the expiry; the signature (cut off here) makes the URL impossible to change. Prefer a user delegation SAS, signed with Entra ID instead of the account key, and keep expiry times short.sp=r คือสิทธิ์อ่านอย่างเดียว และ se= คือวันหมดอายุ ลายเซ็น (ที่ถูกตัดไว้ตรงนี้) ทำให้แก้ URL ไม่ได้ ควรใช้ user delegation SAS ที่ลงนามด้วย Entra ID แทนกุญแจของบัญชี และตั้งเวลาหมดอายุให้สั้น

Lesson 3 · Storageบทที่ 3 · Storage

Queue Storage: messagesQueue Storage: ข้อความ

A simple, cheap queue in the same storage account. As with any queue, a consumer gets a message (which hides it for a while), does the work, then deletes it with the pop receipt from that get. Azure also has Service Bus, a full message broker with topics, sessions and transactions, for heavier needs. The CLI marks the queue commands "preview" with a warning on every run; --only-show-errors hides it. คิวที่เรียบง่ายและราคาถูกในบัญชี storage เดียวกัน เหมือนคิวทั่วไป ผู้รับ ดึง (get) ข้อความ (ซึ่งจะซ่อนข้อความไว้ชั่วคราว) ทำงาน แล้ว ลบ ด้วย pop receipt จากการดึงครั้งนั้น Azure ยังมี Service Bus ซึ่งเป็น message broker เต็มรูปแบบที่มี topic, session และ transaction สำหรับงานที่หนักกว่า CLI ระบุว่าคำสั่งคิวยังเป็น "preview" และพิมพ์คำเตือนทุกครั้ง --only-show-errors ซ่อนคำเตือนนั้น

Terminalเทอร์มินัลAzure CLI 2.90 · Azurite 3.37
$ az storage queue create --name thumbnails --only-show-errors
{
  "created": true
}

$ az storage message put --queue-name thumbnails --content "photo-42.jpg" --only-show-errors --query '{id:id, expires:expirationTime}' -o json
{
  "expires": "2026-10-12T19:32:19+00:00",
  "id": "7595c1e2-4571-47e4-9661-cb00cfd0a4a3"
}

$ az storage message peek --queue-name thumbnails --only-show-errors --query '[].{content:content, dequeueCount:dequeueCount}' -o table
Content       DequeueCount
------------  --------------
photo-42.jpg  0

$ { read ID; read RECEIPT; read CONTENT; } < <(az storage message get --queue-name thumbnails --only-show-errors --query '[0].[id,popReceipt,content]' -o tsv)
$ echo "working on $CONTENT"
$ az storage message delete --queue-name thumbnails --id "$ID" --pop-receipt "$RECEIPT" --only-show-errors -o none && echo deleted
$ az storage message peek --queue-name thumbnails --only-show-errors -o tsv
working on photo-42.jpg
deleted

A message lives for 7 days by default (see expires). peek looks without hiding the message. After the delete, the last peek found nothing.ข้อความมีอายุ 7 วันโดยค่าเริ่มต้น (ดู expires) peek ดูข้อความโดยไม่ซ่อนมัน หลังลบแล้ว peek ครั้งสุดท้ายไม่พบอะไร

Lesson 4 · Storageบทที่ 4 · Storage

Table Storage: simple recordsTable Storage: ระเบียนแบบง่าย

A cheap key-value store for structured records. Each entity is found by a PartitionKey plus a RowKey, the same idea as DynamoDB's partition and sort keys. Values are strings unless you give a type with @odata.type. ที่เก็บแบบ key-value ราคาถูกสำหรับระเบียนที่มีโครงสร้าง entity แต่ละตัวถูกหาด้วย PartitionKey กับ RowKey ซึ่งเป็นแนวคิดเดียวกับ partition key และ sort key ของ DynamoDB ค่าต่างๆ เป็น string เว้นแต่จะระบุชนิดด้วย @odata.type

Terminalเทอร์มินัลAzure CLI 2.90 · Azurite 3.37
$ az storage table create --name orders
{
  "created": true
}

$ az storage entity insert --table-name orders --entity PartitionKey=mya RowKey=1001 total=120 total@odata.type=Edm.Int32 -o none
$ az storage entity insert --table-name orders --entity PartitionKey=mya RowKey=1002 total=45 total@odata.type=Edm.Int32 -o none
$ az storage entity insert --table-name orders --entity PartitionKey=somchai RowKey=1001 total=300 total@odata.type=Edm.Int32 -o none && echo "3 entities inserted"
3 entities inserted

$ az storage entity query --table-name orders --filter "PartitionKey eq 'mya'" --query 'items[].{customer:PartitionKey, order:RowKey, total:total}' -o table
Customer    Order    Total
----------  -------  -------
mya         1001     120
mya         1002     45

$ az storage entity show --table-name orders --partition-key somchai --row-key 1001 --query '{customer:PartitionKey, order:RowKey, total:total, saved:Timestamp}' -o json
{
  "customer": "somchai",
  "order": "1001",
  "saved": "2026-10-05T19:31:15.909143+00:00",
  "total": 300
}

Filters use OData syntax: eq, gt, and. A filter on PartitionKey reads one partition; a filter on anything else scans the table. Every entity gets a Timestamp automatically.ตัวกรองใช้ไวยากรณ์ OData เช่น eq, gt, and การกรองด้วย PartitionKey อ่าน partition เดียว ส่วนการกรองด้วยฟิลด์อื่นจะสแกนทั้งตาราง entity ทุกตัวได้ Timestamp อัตโนมัติ

Lesson 5 · Storageบทที่ 5 · Storage

Cosmos DB: a global document databaseCosmos DB: ฐานข้อมูลเอกสารระดับโลก

Cosmos DB stores JSON documents and can copy them to several regions. A container is divided by a partition key (here /customer), and you query it with a SQL-like language. Every operation costs request units (RU): a point read by id and partition key is the cheapest, and a query across all partitions costs the most. The emulator answers the same API, though it doesn't calculate real RU charges. Cosmos DB เก็บเอกสาร JSON และคัดลอกไปหลาย region ได้ container ถูกแบ่งด้วย partition key (ในที่นี้คือ /customer) และคิวรีด้วยภาษาที่คล้าย SQL ทุกการทำงานมีค่าใช้จ่ายเป็น request unit (RU) การอ่านแบบ point read ด้วย id และ partition key ถูกที่สุด ส่วนคิวรีข้ามทุก partition แพงที่สุด emulator ตอบ API แบบเดียวกัน แต่ไม่ได้คำนวณค่า RU จริง

Terminal, in ~/azure-labเทอร์มินัล ใน ~/azure-labAzure CLI 2.90 · Azurite 3.37
$ cd sdk && node cosmos.mjs
point read: {
  id: '1002',
  customer: 'mya',
  total: 45,
  items: [ 'coffee' ],
  _rid: 'AgAAAIIAAAAFAAAAHYcAAAAAAAA=',
  _self: 'dbs/shop/colls/orders/docs/1002/',
  _etag: '985cad8c-0bbf-4a47-a5cb-9b9d835c95a4',
  _attachments: 'attachments/',
  _ts: 1791228741
}
mya's orders: [ { total: 120, id: '1001' }, { total: 45, id: '1002' } ]
orders over 100: [ '1001', '1003' ]

Cosmos DB adds system fields to every document: _rid and _self (internal ids), _etag (for safe updates) and _ts (last change, in Unix seconds). SELECT VALUE returns plain values instead of objects.Cosmos DB เพิ่มฟิลด์ของระบบให้ทุกเอกสาร ได้แก่ _rid และ _self (id ภายใน), _etag (สำหรับการอัปเดตอย่างปลอดภัย) และ _ts (เวลาเปลี่ยนแปลงล่าสุดเป็นวินาทีแบบ Unix) ส่วน SELECT VALUE คืนค่าธรรมดาแทนออบเจกต์

Lesson 6 · Serverlessบทที่ 6 · Serverless

Azure FunctionsAzure Functions

A function app holds functions, and each function has a trigger: an HTTP request, a queue message, a timer, a new blob. Core Tools creates a project and runs the real Functions host on your computer, using Azurite for its own storage. function app เก็บฟังก์ชันต่างๆ และแต่ละฟังก์ชันมี trigger เช่น คำขอ HTTP ข้อความในคิว ตัวจับเวลา หรือ blob ใหม่ Core Tools สร้างโปรเจกต์และรัน Functions host ตัวจริงบนเครื่องเรา โดยใช้ Azurite เป็นที่เก็บข้อมูลของมันเอง

Terminal, in ~/azure-labเทอร์มินัล ใน ~/azure-labAzure CLI 2.90 · Azurite 3.37
$ func init shopfn --worker-runtime node --language javascript
The new Node.js programming model is generally available. Learn more at https://aka.ms/AzFuncNodeV4
Writing package.json
Writing .funcignore
Writing .gitignore
Writing host.json
Writing local.settings.json
Writing ~/azure-lab/shopfn/.vscode/extensions.json
Running 'npm install'...

$ cd shopfn
$ func new --name hello --template "HTTP trigger" --authlevel anonymous
$ func new --name resize --template "Azure Queue Storage trigger"
Select a number for template:HTTP trigger
Function name: [httpTrigger] The function "hello" was created successfully from the "HTTP trigger" template.
Select a number for template:Azure Queue Storage trigger
Function name: [storageQueueTrigger] The function "resize" was created successfully from the "Azure Queue Storage trigger" template.

In the queue function I changed the template's queue name to thumbnails, named the connection setting, and shortened the log line: ในฟังก์ชันคิว ผมเปลี่ยนชื่อคิวจากเทมเพลตเป็น thumbnails ระบุชื่อการตั้งค่าการเชื่อมต่อ และย่อบรรทัด log

Terminal, in ~/azure-lab/shopfnเทอร์มินัล ใน ~/azure-lab/shopfnAzure CLI 2.90 · Azurite 3.37
$ az storage queue create --name thumbnails --only-show-errors
{
  "created": true
}

$ curl -s "http://localhost:7071/api/hello?name=Mya"; echo
Hello, Mya!

$ az storage message put --queue-name thumbnails --content "$(printf 'photo-42.jpg' | base64)" --only-show-errors --query id -o tsv
ce9c5e65-0198-4668-ba69-7de1574739da
A second terminal, in ~/azure-lab/shopfnเทอร์มินัลที่สอง ใน ~/azure-lab/shopfnAzure CLI 2.90 · Azurite 3.37
$ func start
Azure Functions Core Tools
Core Tools Version:       4.15.2+007886fd6e869a96595c7fd7788d90397588725b (64-bit)
Function Runtime Version: 4.1053.200.26403

[2026-10-05T19:32:29.243Z] Node.js v22 will reach EOL on 2027-04. Consider upgrading: https://aka.ms/functions-nodejs-supported-versions
[2026-10-05T19:32:29.422Z] Worker process started and initialized.

Functions:

	hello: [GET,POST] http://localhost:7071/api/hello

	resize: queueTrigger

For detailed output, run func with --verbose flag.
[2026-10-05T19:32:34.492Z] Host lock lease acquired by instance ID '000000000000000000000000BCEF689E'.
[2026-10-05T19:32:37.355Z] Executing 'Functions.hello' (Reason='This function was programmatically called via the host APIs.', Id=5120fe68-bbca-4a9a-8d26-00989aedafb0)
[2026-10-05T19:32:37.396Z] Http function processed request for url "http://localhost:7071/api/hello?name=Mya"
[2026-10-05T19:32:37.406Z] Executed 'Functions.hello' (Succeeded, Id=5120fe68-bbca-4a9a-8d26-00989aedafb0, Duration=61ms)
[2026-10-05T19:32:39.459Z] Executing 'Functions.resize' (Reason='New queue message detected on 'thumbnails'.', Id=f5c0b964-5740-47e2-8152-f381706967e7)
[2026-10-05T19:32:39.459Z] Trigger Details: MessageId: ce9c5e65-0198-4668-ba69-7de1574739da, DequeueCount: 1, InsertedOn: 2026-10-05T19:32:38.000+00:00
[2026-10-05T19:32:39.470Z] resizing photo-42.jpg
[2026-10-05T19:32:39.470Z] Executed 'Functions.resize' (Succeeded, Id=f5c0b964-5740-47e2-8152-f381706967e7, Duration=15ms)
[2026-10-05T19:32:40.628Z] Message has reached MaxDequeueCount of 5. Moving message to queue 'thumbnails-poison'.
⋯ 2 more lines ⋯⋯ อีก 2 บรรทัด ⋯

The host found both functions, answered the HTTP call, and picked up the queue message within a second. The queue function wants the message base64-encoded, hence base64 above; the last line is what happens without it (Mistakes). On Azure, the same project is published with func azure functionapp publish <app-name>.host พบทั้งสองฟังก์ชัน ตอบคำขอ HTTP และรับข้อความจากคิวภายในหนึ่งวินาที ฟังก์ชันคิวต้องการข้อความที่ เข้ารหัสแบบ base64 จึงต้องใช้ base64 ข้างบน บรรทัดสุดท้ายคือสิ่งที่เกิดขึ้นถ้าไม่เข้ารหัส (ข้อผิดพลาด) บน Azure โปรเจกต์เดียวกันจะถูกเผยแพร่ด้วย func azure functionapp publish <app-name>

Lesson 7 · Controlบทที่ 7 · การควบคุม

Entra ID and RBACEntra ID และ RBAC

Microsoft Entra ID (formerly Azure Active Directory) holds the identities: people, groups, and apps. Azure RBAC gives an identity a role at a scope: a subscription, a resource group or one resource. Permissions set at a scope apply to everything below it. There are hundreds of built-in roles; three are general (Reader, Contributor, Owner) and the rest are specific, like Storage Blob Data Reader. Microsoft Entra ID (ชื่อเดิม Azure Active Directory) เก็บตัวตนต่างๆ ทั้งคน กลุ่ม และแอป Azure RBAC มอบ role ให้ตัวตนหนึ่งที่ scope หนึ่ง ซึ่งอาจเป็น subscription, resource group หรือทรัพยากรตัวเดียว สิทธิ์ที่ตั้งไว้ที่ scope หนึ่งใช้กับทุกอย่างที่อยู่ข้างใต้ มี role สำเร็จรูปหลายร้อยตัว สามตัวเป็นแบบทั่วไป (Reader, Contributor, Owner) ที่เหลือเป็นแบบเฉพาะ เช่น Storage Blob Data Reader

Apps running in Azure get a managed identity: an identity Azure creates and rotates for them, so there's no secret to store at all. You grant that identity a role, and in code DefaultAzureCredential picks it up automatically (and uses your az login on your laptop). These commands need a real subscription, so they were not run for this guide: แอปที่รันใน Azure ได้ managed identity คือตัวตนที่ Azure สร้างและหมุนเวียนกุญแจให้ จึงไม่มีความลับให้เก็บเลย เรามอบ role ให้ตัวตนนั้น และในโค้ด DefaultAzureCredential จะหยิบมาใช้เองอัตโนมัติ (และใช้ az login ของเราเมื่ออยู่บนโน้ตบุ๊ก) คำสั่งเหล่านี้ต้องใช้ subscription จริง จึงไม่ได้รันในคู่มือนี้

Not run: needs a subscriptionไม่ได้รัน: ต้องใช้ subscription

Key Vault

For secrets that must exist (a third-party API key, a certificate), use Key Vault, and let the app read it with its managed identity. Code and configuration then hold only the vault's name.สำหรับความลับที่จำเป็นต้องมี (API key ของบริการภายนอก หรือ certificate) ให้ใช้ Key Vault และให้แอปอ่านด้วย managed identity ของมัน โค้ดและการตั้งค่าจะเก็บเพียงชื่อของ vault

Lesson 8 · Controlบทที่ 8 · การควบคุม

Bicep: infrastructure as codeBicep: infrastructure as code

Azure deploys resources from ARM templates, long JSON files. Bicep is a shorter language that compiles to them. This file describes a storage account with public access turned off and a private photos container. uniqueString() builds a stable, unique name from the resource group's id, because storage account names are global. Azure deploy ทรัพยากรจาก ARM template ซึ่งเป็นไฟล์ JSON ที่ยาว Bicep เป็นภาษาที่สั้นกว่าและคอมไพล์ออกมาเป็น ARM template ไฟล์นี้อธิบายบัญชี storage ที่ปิดการเข้าถึงแบบสาธารณะ และ container ส่วนตัวชื่อ photos uniqueString() สร้างชื่อที่ไม่ซ้ำและคงที่จาก id ของ resource group เพราะชื่อบัญชี storage ต้องไม่ซ้ำกันทั้งโลก

Terminal, in ~/azure-labเทอร์มินัล ใน ~/azure-labAzure CLI 2.90 · Azurite 3.37
$ az bicep version
Bicep CLI version 0.47.16 (3f73e1a234)

$ az bicep build --file main.bicep --stdout
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
⋯ 21 more lines ⋯⋯ อีก 21 บรรทัด ⋯
  "resources": [
    {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2023-05-01",
      "name": "[take(variables('accountName'), 24)]",
      "location": "[parameters('location')]",
      "sku": {
        "name": "Standard_ZRS"
      },
      "kind": "StorageV2",
      "properties": {
        "allowBlobPublicAccess": false,
        "minimumTlsVersion": "TLS1_2"
      }
    },
    {
      "type": "Microsoft.Storage/storageAccounts/blobServices",
      "apiVersion": "2023-05-01",
      "name": "[format('{0}/{1}', take(variables('accountName'), 24), 'default')]",
      "dependsOn": [
        "[resourceId('Microsoft.Storage/storageAccounts', take(variables('accountName'), 24))]"
      ]
    },
⋯ 8 more lines ⋯⋯ อีก 8 บรรทัด ⋯
  ],
  "outputs": {
    "accountName": {
      "type": "string",
      "value": "[take(variables('accountName'), 24)]"
    }
  }
}

Compiling needs no subscription: Bicep checks the file and writes the ARM template. Notice the dependsOn it added on its own, from parent:, so Azure creates the account before the container.การคอมไพล์ไม่ต้องใช้ subscription Bicep ตรวจไฟล์แล้วเขียน ARM template ออกมา สังเกต dependsOn ที่มันเพิ่มเองจาก parent: เพื่อให้ Azure สร้างบัญชีก่อน container

Not run: needs a subscriptionไม่ได้รัน: ต้องใช้ subscription

Lesson 9 · Controlบทที่ 9 · การควบคุม

The SDK in codeSDK ในโค้ด

The Azure SDK for JavaScript has one package per service. This script uploads a receipt, lists the container and reads the file back: Azure SDK สำหรับ JavaScript มีแพ็กเกจแยกตามบริการ สคริปต์นี้อัปโหลดใบเสร็จ แสดงรายการใน container และอ่านไฟล์กลับมา

Terminal, in ~/azure-labเทอร์มินัล ใน ~/azure-labAzure CLI 2.90 · Azurite 3.37
$ cd sdk && node blob.mjs 2>&1 | grep -m 1 "^RestError"
RestError: The API version 2026-10-06 is not supported by Azurite. Please upgrade Azurite to latest version and retry. If you are using Azurite in Visual Studio, please check you have installed latest Visual Studio patch. Azurite command line parameter "--skipApiVersionCheck" or Visual Studio Code configuration "Skip Api Version Check" can skip this error. 

A real snag: the newest SDK speaks storage API version 2026-10-06, newer than this Azurite release knows. Updating Azurite fixes it; so does the flag the message suggests.ปัญหาที่เจอจริง SDK ตัวใหม่ล่าสุดใช้ API ของ storage เวอร์ชัน 2026-10-06 ซึ่งใหม่กว่าที่ Azurite รุ่นนี้รู้จัก การอัปเดต Azurite แก้ได้ และตัวเลือกที่ข้อความแนะนำก็แก้ได้เช่นกัน

Terminal, in ~/azure-labเทอร์มินัล ใน ~/azure-labAzure CLI 2.90 · Azurite 3.37
$ docker rm -f azurite && docker run -d --name azurite -p 10000:10000 -p 10001:10001 -p 10002:10002 mcr.microsoft.com/azure-storage/azurite azurite --blobHost 0.0.0.0 --queueHost 0.0.0.0 --tableHost 0.0.0.0 --skipApiVersionCheck
azurite
7972b0da81c5a185291cc48b78281534796376923e92514e82b16cd2c1a873a8

$ cd sdk && node blob.mjs
2026/1003.txt 19 bytes text/plain
Order 1003: 2 x tea

Lesson 10 · Your turnบทที่ 10 · ถึงตาคุณแล้ว

Practiceแบบฝึกหัด

Three tasks against the emulators. Each solution was run; the output is under it. งานสามข้อกับ emulator ทุกเฉลยรันจริงแล้ว ผลลัพธ์อยู่ด้านล่าง

1. One year of photos1. รูปภาพของหนึ่งปี

Upload three photos, two of them under 2026/, then list only the 2026 ones.อัปโหลดรูปภาพสามรูป สองรูปอยู่ใต้ 2026/ แล้วแสดงเฉพาะรูปของปี 2026

Show solutionดูเฉลย
Terminal, in ~/azure-labเทอร์มินัล ใน ~/azure-labAzure CLI 2.90 · Azurite 3.37
$ az storage container create --name photos -o none
$ for f in 2025/dog.jpg 2026/cat.jpg 2026/bird.jpg; do echo "$f" > photo.jpg; az storage blob upload --container-name photos --name "$f" --file photo.jpg --no-progress -o none; done
$ az storage blob list --container-name photos --prefix 2026/ --query '[].name' -o tsv
2026/bird.jpg
2026/cat.jpg

2. Big orders2. คำสั่งซื้อใหญ่

In the orders table, find mya's orders with a total over 100.ในตาราง orders หาคำสั่งซื้อของ mya ที่มียอดรวมเกิน 100

Show solutionดูเฉลย
Terminalเทอร์มินัลAzure CLI 2.90 · Azurite 3.37
$ az storage table create --name orders -o none
$ az storage entity insert --table-name orders --entity PartitionKey=mya RowKey=1001 total=120 total@odata.type=Edm.Int32 -o none
$ az storage entity insert --table-name orders --entity PartitionKey=mya RowKey=1002 total=45 total@odata.type=Edm.Int32 -o none
$ az storage entity query --table-name orders --filter "PartitionKey eq 'mya' and total gt 100" --query 'items[].RowKey' -o tsv
1001

Without @odata.type=Edm.Int32, total would be a string, and gt 100 would compare text, not numbers.ถ้าไม่มี @odata.type=Edm.Int32, total จะเป็น string และ gt 100 จะเปรียบเทียบเป็นข้อความ ไม่ใช่ตัวเลข

3. Who bought tea?3. ใครซื้อชา

Query Cosmos DB for the orders whose items array contains "tea".คิวรี Cosmos DB หาคำสั่งซื้อที่อาร์เรย์ items มี "tea"

Show solutionดูเฉลย
Terminal, in ~/azure-labเทอร์มินัล ใน ~/azure-labAzure CLI 2.90 · Azurite 3.37
$ cd sdk && node tea.mjs
[ { customer: 'mya', id: '1001' } ]

Lesson 11 · Avoiding troubleบทที่ 11 · หลีกเลี่ยงปัญหา

Common mistakesข้อผิดพลาดที่พบบ่อย

A plain-text message for a queue-triggered functionส่งข้อความธรรมดาให้ฟังก์ชันที่ทำงานจากคิว

The Functions queue trigger expects base64 by default. A plain-text message fails to decode, is retried 5 times, and ends up in a poison queue (<queue>-poison), quietly. Encode messages to base64 when you send them, or set "messageEncoding": "none" for queues in host.json.queue trigger ของ Functions คาดหวัง base64 โดยค่าเริ่มต้น ข้อความธรรมดาจะถอดรหัสไม่ได้ ถูกลองซ้ำ 5 ครั้ง แล้วไปอยู่ใน poison queue (<queue>-poison) อย่างเงียบๆ ให้เข้ารหัสข้อความเป็น base64 ตอนส่ง หรือตั้ง "messageEncoding": "none" สำหรับคิวใน host.json

Terminal, with func start runningเทอร์มินัล ขณะที่ func start ทำงานอยู่Azure CLI 2.90 · Azurite 3.37
$ az storage message put --queue-name thumbnails --content "photo-43.jpg" --only-show-errors --query id -o tsv
83fc4be5-6711-4a9a-9b56-f6062c70140d

$ az storage queue list --only-show-errors --query '[].name' -o tsv
thumbnails
thumbnails-poison

And in the func start log: Message has reached MaxDequeueCount of 5. Moving message to queue 'thumbnails-poison'. Always watch your poison queues.และใน log ของ func start: Message has reached MaxDequeueCount of 5. Moving message to queue 'thumbnails-poison'. ควรเฝ้าดู poison queue อยู่เสมอ

Sending to a queue that doesn't existส่งข้อความไปยังคิวที่ไม่มีอยู่

Queues and containers aren't created on first use. Create them in your Bicep template (or createIfNotExists() at start-up), not by hand.คิวและ container ไม่ถูกสร้างเองเมื่อใช้ครั้งแรก ให้สร้างในเทมเพลต Bicep (หรือ createIfNotExists() ตอนเริ่มทำงาน) ไม่ใช่สร้างด้วยมือ

Terminalเทอร์มินัลAzure CLI 2.90 · Azurite 3.37
$ az storage message put --queue-name orders --content "order 1001" --only-show-errors
ERROR: The specified queue does not exist.
RequestId:DefaultID
Time:2026-10-05T19:31:49.198Z
ErrorCode:QueueNotFound

Connection strings with account keys in the appconnection string ที่มีกุญแจของบัญชีอยู่ในแอป

An account key is full control over every container, queue and table in the account, and it never expires on its own. Use a managed identity with a narrow role (Lesson 7), and disable shared-key access on the account when you can.กุญแจของบัญชีคือการควบคุมเต็มรูปแบบเหนือทุก container คิว และตารางในบัญชี และไม่หมดอายุเอง ให้ใช้ managed identity ที่มี role แคบๆ (บทที่ 7) และปิดการเข้าถึงด้วย shared key ของบัญชีเมื่อทำได้

✗ Wrong✗ ผิด
✓ Right✓ ถูก

Making a container public to share a fileเปิด container เป็นสาธารณะเพื่อแชร์ไฟล์

Anonymous access was refused in Lesson 2 (403), and that's the right default. Share single files with a short-lived SAS, and keep allowBlobPublicAccess: false on the account as in the Bicep file.การเข้าถึงแบบไม่ระบุตัวตนถูกปฏิเสธในบทที่ 2 (403) ซึ่งเป็นค่าเริ่มต้นที่ถูกต้อง ให้แชร์ไฟล์เดี่ยวด้วย SAS อายุสั้น และตั้ง allowBlobPublicAccess: false ที่บัญชีเหมือนในไฟล์ Bicep

✗ Wrong✗ ผิด
✓ Right✓ ถูก

Lesson 12 · Referenceบทที่ 12 · อ้างอิง

Glossaryคำศัพท์

EnglishไทยMeaningความหมาย
tenantเทแนนต์An organisation's directory in Entra IDไดเรกทอรีขององค์กรใน Entra ID
subscriptionสมาชิก (subscription)The unit of billing and limitsหน่วยของการคิดเงินและขีดจำกัด
resource groupresource groupA folder for the resources of one app or environmentโฟลเดอร์สำหรับทรัพยากรของแอปหรือสภาพแวดล้อมหนึ่ง
storage accountบัญชี storageHolds blobs, queues, tables and files under one nameเก็บ blob คิว ตาราง และไฟล์ภายใต้ชื่อเดียว
blob / containerblob / containerA stored file / the folder-like group it lives inไฟล์ที่เก็บไว้ / กลุ่มคล้ายโฟลเดอร์ที่ไฟล์อยู่
SASSASShared access signature: a signed, expiring URLShared access signature: URL ที่ลงนามและมีวันหมดอายุ
pop receiptpop receiptThe token needed to delete a message you receivedโทเค็นที่ต้องใช้ลบข้อความที่เรารับมา
poison queuepoison queueWhere Functions moves messages that keep failingที่ที่ Functions ย้ายข้อความที่ล้มเหลวซ้ำๆ ไปไว้
PartitionKey / RowKeyPartitionKey / RowKeyThe two parts of a Table Storage keyสองส่วนของ key ใน Table Storage
request unit (RU)request unit (RU)Cosmos DB's measure of the cost of an operationหน่วยวัดค่าใช้จ่ายของการทำงานใน Cosmos DB
trigger / bindingtrigger / bindingWhat starts a function / a declared input or outputสิ่งที่เริ่มฟังก์ชัน / อินพุตหรือเอาต์พุตที่ประกาศไว้
RBAC / role / scopeRBAC / role / scopeAccess control: what an identity may do, and whereการควบคุมสิทธิ์: ตัวตนหนึ่งทำอะไรได้ และที่ไหน
managed identitymanaged identityAn identity Azure gives a resource, with no secret to manageตัวตนที่ Azure มอบให้ทรัพยากร โดยไม่มีความลับให้ต้องจัดการ
Bicep / ARM templateBicep / ARM templateAzure's infrastructure-as-code language / the JSON it compiles toภาษา infrastructure as code ของ Azure / JSON ที่ได้จากการคอมไพล์