Chuyển đến nội dung chính

Terraform: Nâng cấp ASP.Core với SQL Server - Part 5

Trong bài viết này, chúng ta sẽ tạo Docker Image cho SQL Server, tích hợp thư viện AspNetCore.HealthChecks.SqlServer vào ứng dụng ASP.NET Core, sau đó dùng Terraform để triển khai cả hai container (ASP.NET Core và SQL Server) cùng lúc lên Azure Container Instances (ACI).

Cấu trúc dự án

Chúng ta cần một folder riêng cho Dockerfile của SQL Server để tránh nhầm lẫn và một folder mới cho Terraform.
tf-aci-fullstack/
├── main.tf
├── variables.tf
└── outputs.tf
sql-db-image/
└── Dockerfile
AspNetCoreGettingStarted/
├── *.*
├── Dockerfile
└── appsettings.json

SQL Server

Chúng ta sẽ sử dụng image chính thức của Microsoft.
# Use the official Microsoft SQL Server image for Linux
FROM mcr.microsoft.com/mssql/server:2019-latest

# Set environment variables for SQL Server configuration
ENV ACCEPT_EULA=Y
ENV SA_PASSWORD=YourStrongPassword123!
ENV MSSQL_PID=Developer

# Expose the default SQL Server port
EXPOSE 1433

ASP.NET Core

Tạo ứng dụng ASP.NET Core. Thêm Health Check vào project ASP.NET Core
dotnet add package AspNetCore.HealthChecks.SqlServer
Cập nhật appsettings.json
{
  "ConnectionStrings": {
    "DefaultConnection": "Server=sql-db,1433;Initial Catalog=master;Persist Security Info=False;User ID=sa;Password=YourStrongPassword123!;Encrypt=True;TrustServerCertificate=True;MultipleActiveResultSets=False;Connection Timeout=30;"
  }
}
Cập nhật file program.cs
builder.Services.AddControllersWithViews();

builder.Services.AddHealthChecks()
    .AddSqlServer(
        connectionString: builder.Configuration.GetConnectionString("DefaultConnection"),
        healthQuery: "SELECT 1", 
        name: "sqlserver",
        failureStatus: HealthStatus.Unhealthy,
        tags: new[] { "db", "sql" }
    );

var app = builder.Build();
//...
app.MapHealthChecks("/health", new HealthCheckOptions
{
    ResponseWriter = async (context, report) =>
    {
        context.Response.ContentType = "application/json";
        await context.Response.WriteAsync(System.Text.Json.JsonSerializer.Serialize(new
        {
            status = report.Status.ToString(),
            checks = report.Entries.Select(e => new {
                name = e.Key,
                status = e.Value.Status.ToString(),
                duration = e.Value.Duration.ToString()
            })
        }));
    }
});
//...
Cập nhật Dockerfile
# syntax=docker/dockerfile:1
FROM mcr.microsoft.com/dotnet/sdk:9.0-bookworm-slim AS build-env
WORKDIR /app

COPY *.csproj ./
RUN dotnet restore

COPY . ./
RUN dotnet publish -c Release -o out

# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:9.0-bookworm-slim
WORKDIR /app
COPY --from=build-env /app/out .
ENV ASPNETCORE_URLS=http://+:80
EXPOSE 80
ENTRYPOINT ["dotnet", "AspNetCoreGettingStarted.dll"]

Local Test với docker

Để mô phỏng môi trường ACI (nơi các container có thể giao tiếp qua tên service), chúng ta cần tạo một Docker Network.
# 1. Create a custom bridge network
docker network create my-aci-network

# 2. Run SQL Server container (hostname is its name: sql-db)
docker run -d --name sql-db --network my-aci-network yourdockerhubuser/mssql-dev:v1

# 3. Run ASP.NET Core container, using the SQL container name as the server name
# We override the Server name (SQL_SERVER_NAME) and Password (SA_PASSWORD) via ENV vars.
docker run -d --name aspnet-web --network my-aci-network \    
    -p 8080:80 \
    yourdockerhubuser/aspnet-app:v1

# 4. Check Health Status
# After a few seconds for SQL Server to start, check the health endpoint:
curl http://localhost:8080/health

Lưu ý về Network và Service Discovery:

  • Lệnh docker network create tạo một mạng ảo nội bộ (Custom Bridge Network).
  • Khi bạn chạy containers và gắn chúng vào cùng một network (--network aci-local-network), Docker sẽ tự động sử dụng **tên container** (sql-db) làm **hostname/DNS name** để container Web App có thể kết nối. Đây chính xác là cách ACI và Kubernetes hoạt động (Service Discovery).

Terraform

Trong phần này, chúng ta sẽ đẩy (upload) hai Docker images lên Azure Container Registry (ACR), sau đó triển khai (deploy) chúng lên Azure Container Instances (ACI).
graph TD subgraph "Azure Container Registry (ACR)" I1[Image: aspnet-app:v3] I2[Image: mssql-dev:v3] end subgraph "Azure Container Instances (ACI)" A[Container Group: aci-fullstack-web-sql] subgraph Internal ACI Network B(Container: aspnet-web) C(Container: sql-db) B -->|Hostname: localhost, Port 1433| C end end D[Public Internet] -->|Port 80| A A -->|Pulls Image via Credentials| I1 A -->|Pulls Image via Credentials| I2

Azure Container Registry (ACR)

Azure Container Registry (ACR) là dịch vụ Registry Docker riêng tư được Azure quản lý hoàn toàn, cho phép chúng ta lưu trữ, quản lý và phân phối các container image một cách an toàn.

Trong quy trình DevOps này, ACR đóng vai trò là trung tâm lưu trữ container image — nơi chúng ta:

  • Build Docker image cho ứng dụng (ví dụ: ASP.NET và SQL Server) trên máy local hoặc trong CI/CD pipeline.
  • Push (đẩy) các image đó lên ACR bằng lệnh docker push hoặc thông qua Terraform / Azure CLI.
  • Terraform sau đó sẽ lấy image từ ACR để triển khai container lên Azure Container Instances (ACI). 

Tạo Instance Azure Container Registry

Trên Azure Portal => Create a resource => Chọn Container Registries.

Gõ Registry Name, ví dụ: nhatkyhoctap.azurecr.io

Sau khi tạo xong, bạn vào Container Registry => Settings => Access keys, sau đó enable Admin user. Bạn sẽ dùng settings này để deploy image lên App Service

Mở Visual Studio lên, chọn Terminal => Git Bash
$ docker login nhatkyhoctap.azurecr.io
Authenticating with existing credentials...
Login did not succeed, error: Error response from daemon: Get "https://whizregistry.azurecr.io/v2/": unauthorized: authentication required, visit https://aka.ms/acr/authorization for more information.
Username (nhatkyhoctap): nhatkyhoctap
Password:
Login Succeeded
Trong Visual Studio Code, nhấp chọn biểu tượng Docker, chọn image => Push
Executing task: docker image push nhatkyhoctap.azurecr.io/demo:latest 

The push refers to repository [nhatkyhoctap.azurecr.io/nhatkyhoctap]
7a6051a3589b: Preparing
a63d75c8cef3: Preparing
Trường hợp local image của bạn không phải là nhatkyhoctap.azurecr.io, bạn cần phải đổi tag lại, đảm bảo khi push image lên, image sẽ được push lên Azure Container Registries:
docker tag anbinhtrong/aspnet-app:v1 nhatkyhoctap.azurecr.io/aspnet-app:v1
Để xem images được push được Azure Container Registries, bạn vào: Container Registries > Pannel > Services > Repositories

Push Images lên Azure Container Registry (ACR)

Docker cần biết đích đến của image trước khi push. Giả sử ACR của bạn có địa chỉ url: anbinhtrong.azurecr.io

Chúng ta sẽ gắn lại thẻ (tag) cho image local (ví dụ: ananh/...) để trỏ về ACR của mình (anbinhtrong.azurecr.io/...). Sử dụng tag :v3 cho các image đã fix lỗi (Globalization, SQL Batching)

docker tag ananh/aspnet-app:v3 anbinhtrong.azurecr.io/aspnet-app:v3
# Push Web App Image into ACR
docker push anbinhtrong.azurecr.io/aspnet-app:v3
docker tag ananh/mssql-dev:v3 anbinhtrong.azurecr.io/mssql-dev:v3
# Push SQL DB Image into ACR
docker push sd2294.azurecr.io/mssql-dev:v3
Lưu ý: Việc tách biệt tên image giữa local (ananh/...) và Registry (anbinhtrong.azurecr.io/...) là bắt buộc trong Docker. Khi push, Docker dựa vào địa chỉ Registry để biết nơi upload image.

Implement Terraform

File: variables.tf: Khai báo các thông tin cơ bản: Resource Group, Region, ACR, Image tags và mật khẩu SQL Server.
# Declare required Azure provider
terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
  }
}

# General Azure Variables
variable "resource_group_name" {
  description = "Name of the resource group to create"
  type        = string
  default     = "nhatkyhoctap"
}

variable "location" {
  description = "Azure region to deploy resources"
  type        = string
  default     = "Southeast Asia"
}

# Azure Container Registry (ACR) Variables
variable "acr_name" {
  description = "Globally unique name for the Azure Container Registry"
  type        = string
  default     = "nhatkyhoctap"
}

# Container Image Tags
variable "app_image_tag" {
  description = "Tag for the ASP.NET Core application image in ACR"
  type        = string
  default     = "v1" 
}

variable "db_image_tag" {
  description = "Tag for the SQL Server database image in ACR"
  type        = string
  default     = "v2" 
}

# Credentials & Secrets (For SQL Server)
variable "sa_password" {
  description = "SA Password for SQL Server. Use a complex one!"
  type        = string
  sensitive   = true 
  default     = "YourStrongPassword123!" 
}
File main.tf: Đây là file chính, nơi chúng ta kết hợp Web App + SQL Server trong cùng một Azure Container Group
# Configure the Azure Provider
provider "azurerm" {
  features {}
}

# 1. Create a Resource Group
data "azurerm_resource_group" "rg_existing" {
  name = var.resource_group_name 
}

# 2. Data Source: Reference the existing Azure Container Registry
# This resource is required to store the images we pushed in Step 1.1
data "azurerm_container_registry" "acr_existing" {
  name                = var.acr_name
  resource_group_name = data.azurerm_resource_group.rg_existing.name
}

# 3. Deploy Azure Container Group (ACI) with 2 containers
resource "azurerm_container_group" "aci_fullstack" {
  name                = "aci-fullstack-web-sql"
  location            = var.location
  resource_group_name = data.azurerm_resource_group.rg_existing.name
  ip_address_type     = "Public"
  # Create a unique DNS name label based on the resource group name
  dns_name_label      = "${var.resource_group_name}-${substr(uuid(), 0, 8)}"
  os_type             = "Linux"

  # CRUCIAL: ACI needs credentials to pull images from the private ACR
  image_registry_credential {
    server   = data.azurerm_container_registry.acr_existing.login_server
    username = data.azurerm_container_registry.acr_existing.admin_username
    password = data.azurerm_container_registry.acr_existing.admin_password
  }

  # Container 1: ASP.NET Core Web App
  container {
    name   = "aspnet-web"
    # Image path uses ACR login server and the defined image tag
    image  = "${data.azurerm_container_registry.acr_existing.login_server}/aspnet-app:${var.app_image_tag}"
    cpu    = 0.5
    memory = 1.5

    ports {
        port     = 80
        protocol = "TCP"
    }

    # CRUCIAL: Pass environment variables (using fixed Connection String)
    environment_variables = {
      # The App will connect to the SQL Server container using its name localhost as the hostname
      "ConnectionStrings__DefaultConnection" = "Server=localhost,1433;Initial Catalog=master;User Id=sa;Password=${var.sa_password};Encrypt=False;TrustServerCertificate=False;MultipleActiveResultSets=False;Connection Timeout=30;"
    }
  }

  # Container 2: SQL Server Database
  container {
    name   = "sql-db" # This name is used as the hostname by the web app
    # Image path uses ACR login server and the defined image tag
    image  = "${data.azurerm_container_registry.acr_existing.login_server}/mssql-dev:${var.db_image_tag}"
    cpu    = 2.0
    memory = 4.0 

    # CRUCIAL: Environment variables for SQL Server setup
    environment_variables = {
      "ACCEPT_EULA" = "Y"
      "SA_PASSWORD" = var.sa_password
      "MSSQL_PID"   = "Developer"
    }
  }

  # Expose the web app publicly on port 80
  exposed_port {
    port     = 80
    protocol = "TCP"
  }
}
File output.tf
# Output the public URL for the application
output "web_app_url" {
  description = "The public URL for the ASP.NET Core application"
  value       = "http://${azurerm_container_group.aci_fullstack.fqdn}"
}

# Output the public Health Check endpoint URL
output "health_check_url" {
  description = "The public Health Check endpoint URL"
  value       = "http://${azurerm_container_group.aci_fullstack.fqdn}/health"
}

# Output the ACR Login Server for reference
output "acr_login_server" {
  description = "The login server name for Azure Container Registry"
  value       = data.azurerm_container_registry.acr_existing.login_server
}
Thực hiện lệnh
terraform init
terraform apply -auto-approve

Terraform sẽ:

  • Kéo image aspnet-app:v3 và mssql-dev:v3 từ ACR.
  • Tạo Container Group aci-fullstack-web-sql trên Azure.
  • Xuất ra public URL của ứng dụng và endpoint /health

Kết luận

ACR là kho trung tâm giúp quản lý và bảo mật image nội bộ.

Terraform tự động hóa toàn bộ quy trình deploy container lên Azure.

Cấu trúc này có thể mở rộng cho môi trường thực tế — chỉ cần thêm volume Azure Files, subnet, hoặc scale-out bằng ACI/AKS.

Nhận xét

Bài đăng phổ biến từ blog này

[ASP.NET MVC] Authentication và Authorize

Một trong những vấn đề bảo mật cơ bản nhất là đảm bảo những người dùng hợp lệ truy cập vào hệ thống. ASP.NET đưa ra 2 khái niệm: Authentication và Authorize Authentication xác nhận bạn là ai. Ví dụ: Bạn có thể đăng nhập vào hệ thống bằng username và password hoặc bằng ssh. Authorization xác nhận những gì bạn có thể làm. Ví dụ: Bạn được phép truy cập vào website, đăng thông tin lên diễn đàn nhưng bạn không được phép truy cập vào trang mod và admin.

ASP.NET MVC: Cơ bản về Validation

Validation (chứng thực) là một tính năng quan trọng trong ASP.NET MVC và được phát triển trong một thời gian dài. Validation vắng mặt trong phiên bản đầu tiên của asp.net mvc và thật khó để tích hợp 1 framework validation của một bên thứ 3 vì không có khả năng mở rộng. ASP.NET MVC2 đã hỗ trợ framework validation do Microsoft phát triển, tên là Data Annotations. Và trong phiên bản 3, framework validation đã hỗ trợ tốt hơn việc xác thực phía máy khách, và đây là một xu hướng của việc phát triển ứng dụng web ngày nay.

Tổng hợp một số kiến thức lập trình về Amibroker

Giới thiệu về Amibroker Amibroker theo developer Tomasz Janeczko được xây dựng dựa trên ngôn ngữ C. Vì vậy bộ code Amibroker Formula Language sử dụng có syntax khá tương đồng với C, ví dụ như câu lệnh #include để import hay cách gói các object, hàm trong các block {} và kết thúc câu lệnh bằng dấu “;”. AFL trong Amibroker là ngôn ngữ xử lý mảng (an array processing language). Nó hoạt động dựa trên các mảng (các dòng/vector) số liệu, khá giống với cách hoạt động của spreadsheet trên excel.