+1

Kiến trúc Serverless: Lợi ích, Thách thức và Hướng dẫn Triển khai

Kiến trúc Serverless là một mô hình điện toán đám mây, nơi nhà cung cấp đám mây quản lý linh hoạt việc phân bổ và mở rộng tài nguyên. Bài viết này sẽ đi sâu vào tìm hiểu kiến trúc Serverless, từ khái niệm, thành phần, ưu nhược điểm, đến các trường hợp sử dụng và hướng dẫn triển khai.

Khái niệm về Kiến trúc Serverless

Kiến trúc Serverless là một mô hình điện toán đám mây, nơi nhà cung cấp đám mây quản lý linh hoạt việc phân bổ và mở rộng tài nguyên. Các nhà phát triển tập trung vào việc xây dựng và triển khai mã ứng dụng mà không cần lo lắng về việc quản lý cơ sở hạ tầng bên dưới.

Các đặc điểm chính:

  • Hướng sự kiện (Event-Driven): Các hàm được kích hoạt bởi các sự kiện, chẳng hạn như yêu cầu HTTP, cập nhật cơ sở dữ liệu hoặc tải lên tệp.
  • Trả tiền theo mức sử dụng (Pay-as-You-Go): Chi phí được dựa trên mức sử dụng thực tế (ví dụ: thời gian thực thi và số lượng yêu cầu).
  • Cơ sở hạ tầng được quản lý (Managed Infrastructure): Nhà cung cấp đám mây xử lý việc cung cấp máy chủ, mở rộng quy mô và bảo trì.

Thành phần của Kiến trúc Serverless

  • Hàm Serverless (Serverless Functions): Ví dụ: AWS Lambda, Google Cloud Functions, Azure Functions. Chúng là các hàm không trạng thái và tồn tại trong thời gian ngắn, thực hiện một tác vụ duy nhất.
  • Backend-as-a-Service (BaaS): Các dịch vụ được quản lý cho cơ sở dữ liệu, xác thực và nhắn tin. Ví dụ về cơ sở dữ liệu: AWS DynamoDB, Firebase Firestore. Ví dụ về xác thực: AWS Cognito, Firebase Auth. Ví dụ về lưu trữ: AWS S3, Google Cloud Storage.
  • API Gateway: Được sử dụng để hiển thị các hàm serverless dưới dạng API RESTful. Ví dụ: AWS API Gateway, Azure API Management.
  • Nguồn sự kiện (Event Sources): Các trình kích hoạt gọi các hàm serverless. Ví dụ: tải lên tệp S3, luồng DynamoDB, yêu cầu HTTP.

Ưu điểm của Kiến trúc Serverless

  • Giảm chi phí vận hành (Reduced Operational Overhead): Không cần quản lý máy chủ, mở rộng quy mô hoặc vá lỗi.
  • Hiệu quả chi phí (Cost Efficiency): Chỉ trả tiền cho tài nguyên được sử dụng (không có chi phí nhàn rỗi).
  • Khả năng mở rộng (Scalability): Tự động mở rộng theo nhu cầu.
  • Thời gian đưa ra thị trường nhanh hơn (Faster Time to Market): Tập trung vào viết mã hơn là quản lý cơ sở hạ tầng.
  • Khả năng sẵn sàng cao (High Availability): Khả năng chịu lỗi và dự phòng tích hợp.

Những thách thức của Serverless

  • Độ trễ khởi động nguội (Cold Start Latency): Lần gọi ban đầu có thể gặp phải sự chậm trễ do khởi tạo vùng chứa.
  • Thời gian thực thi bị giới hạn (Limited Execution Time): Các hàm có thời lượng thực thi tối đa (ví dụ: 15 phút cho AWS Lambda).
  • Giám sát và gỡ lỗi (Monitoring and Debugging): Yêu cầu các công cụ chuyên dụng để theo dõi và gỡ lỗi các hệ thống phân tán.
  • Phụ thuộc vào nhà cung cấp (Vendor Lock-In): Phụ thuộc vào các dịch vụ của nhà cung cấp đám mây cụ thể.
  • Quản lý trạng thái (State Management): Bản chất không trạng thái yêu cầu các dịch vụ bên ngoài (ví dụ: cơ sở dữ liệu) để duy trì trạng thái.

Trường hợp nào nên sử dụng Serverless?

  • Backend web và di động (Web and Mobile Backends): Xử lý xác thực người dùng, xử lý API và lưu trữ.
  • Xử lý dữ liệu (Data Processing): Xử lý dữ liệu trực tuyến theo thời gian thực hoặc các tác vụ hàng loạt theo lịch trình.
  • Ứng dụng IoT (IoT Applications): Kích hoạt các hàm dựa trên các sự kiện của thiết bị IoT.
  • Chatbots: Sử dụng các hàm serverless để xử lý tin nhắn của người dùng.
  • Học máy (Machine Learning): Thực hiện suy luận bằng cách sử dụng các mô hình được đào tạo trước trong các hàm serverless.

Nền tảng Serverless phổ biến hiện nay

1. AWS

  • AWS Lambda
  • AWS DynamoDB
  • Cổng API AWS
  • AWS Step Functions (điều phối)

2. Google Cloud

  • Chức năng đám mây của Google
  • Căn cứ hỏa lực
  • Chạy trên mây

3. Microsoft Azure

Chức năng Azure Ứng dụng Logic Azure Lưới sự kiện

4. Nền tảng khác

  • OpenFaaS (mã nguồn mở)
  • Netlify (không có máy chủ cho các trang web tĩnh)

Best Practices cho Kiến trúc Serverless

  • Tối ưu hóa khởi động nguội (Optimize Cold Starts): Sử dụng môi trường runtime nhẹ (ví dụ: Node.js). Giữ cho các gói hàm nhỏ gọn.
  • Sử dụng các hàm Idempotent (Use Idempotent Functions): Đảm bảo các lần thực thi lặp lại tạo ra cùng một kết quả.
  • Tận dụng thiết kế hướng sự kiện (Leverage Event-Driven Design): Kích hoạt các hàm bằng cách sử dụng các sự kiện đám mây (ví dụ: cập nhật cơ sở dữ liệu, tải lên tệp).
  • Giám sát và ghi nhật ký (Monitor and Log): Sử dụng các công cụ như AWS CloudWatch, Azure Monitor hoặc các công cụ nguồn mở.
  • Bảo mật ứng dụng (Secure Applications): Hạn chế quyền của hàm (Nguyên tắc Ít đặc quyền nhất). Xác thực và làm sạch dữ liệu đầu vào.
  • Sắp xếp với Step Functions (Orchestrate with Step Functions): Sử dụng các công cụ sắp xếp cho các quy trình làm việc phức tạp.

Ví dụ thực tế: Tạo hàm AWS Lambda phức tạp với nhiều trình kích hoạt

Trong nhiệm vụ này, chúng ta sẽ tạo một hàm AWS Lambda xử lý nhiều trình kích hoạt. Đối với ví dụ này, hàm Lambda sẽ:

  • Xử lý yêu cầu HTTP thông qua API Gateway.
  • Phản hồi sự kiện tải lên vùng chứa S3.
  • Lắng nghe các thay đổi luồng DynamoDB và ghi lại các cập nhật.

Sơ đồ kiến trúc

                   +-------------------+
HTTP Request --->  | API Gateway       |
                   +-------------------+
                          |
                          v
                   +-------------------+
                   | AWS Lambda        |
                   | (Single Function) |
                   +-------------------+
                          |
      +-------------------+-------------------+
      |                   |                   |
      v                   v                   v
+-------------+      +-----------+      +-----------------+
| DynamoDB    | ---> | Lambda    | ---> | S3 Upload Event |
| Stream      |      | Processing|      | Processing       |
+-------------+      +-----------+      +-----------------+

Các bước thực hiện

Bước 1: Thiết lập hàm AWS Lambda

  • Tạo hàm Lambda (Create the Lambda Function): Truy cập AWS Lambda Console. Nhấp vào Create Function > Author from Scratch. Đặt tên hàm là ComplexTriggerHandler. Chọn một runtime, chẳng hạn như Node.js 18.x hoặc Python 3.9.
  • Thêm Quyền (Add Permissions): Đính kèm các chính sách IAM sau: AWSLambdaBasicExecutionRole, AmazonDynamoDBReadOnlyAccessAmazonS3ReadOnlyAccess.

Bước 2: Định cấu hình nhiều trình kích hoạt

  • Trình kích hoạt 1: API Gateway: Điều hướng đến API Gateway Console. Tạo REST API. Định nghĩa một tài nguyên (ví dụ: /process). Thiết lập phương thức POST và liên kết nó với hàm Lambda của bạn.
  • Trình kích hoạt 2: Sự kiện Vùng chứa S3: Truy cập S3 Console. Chọn một vùng chứa hoặc tạo một vùng chứa mới. Định cấu hình thông báo sự kiện: Loại sự kiện: PUT (Object Created). Đích: Hàm Lambda > ComplexTriggerHandler.
  • Trình kích hoạt 3: Luồng DynamoDB: Truy cập DynamoDB Console. Tạo hoặc chọn một bảng. Kích hoạt Luồng: Loại Chế độ xem Luồng: Hình ảnh Mới hoặc Hình ảnh Mới và Cũ. Thêm hàm Lambda làm trình kích hoạt luồng.

Bước 3: Viết mã hàm Lambda

Ví dụ về Node.js:

exports.handler = async (event) => {
    console.log("Event Received:", JSON.stringify(event, null, 2));

    if (event.requestContext) {
        // Handle API Gateway trigger
        console.log("API Gateway Triggered");
        return {
            statusCode: 200,
            body: JSON.stringify({ message: "Hello from API Gateway!" }),
        };
    } else if (event.Records && event.Records[0].eventSource === "aws:s3") {
        // Handle S3 trigger
        const bucketName = event.Records[0].s3.bucket.name;
        const objectKey = event.Records[0].s3.object.key;
        console.log(`S3 Event: Bucket - ${bucketName}, Key - ${objectKey}`);
    } else if (event.Records && event.Records[0].eventSource === "aws:dynamodb") {
        // Handle DynamoDB stream trigger
        const dynamoRecord = event.Records[0].dynamodb.NewImage;
        console.log("DynamoDB Record:", JSON.stringify(dynamoRecord));
    } else {
        console.log("Unknown Trigger");
    }

    return { statusCode: 200, body: "Processed Successfully" };
};

Bước 4: Kiểm tra hàm

  • Kiểm tra API Gateway: Sử dụng công cụ như Postman hoặc curl để gửi yêu cầu POST: curl -X POST https://<API_ENDPOINT>/process
  • Kiểm tra Tải lên S3: Tải tệp lên vùng chứa S3. Xác minh rằng hàm Lambda ghi lại tên tệp và chi tiết vùng chứa.
  • Kiểm tra Luồng DynamoDB: Thêm bản ghi mới vào bảng DynamoDB. Kiểm tra nhật ký Lambda để biết chi tiết bản ghi đã xử lý.

Bước 5: Giám sát và gỡ lỗi

  • Sử dụng AWS CloudWatch Logs để giám sát việc thực thi hàm Lambda và khắc phục sự cố.
  • Thiết lập báo động để thông báo cho bạn trong trường hợp xảy ra lỗi hoặc độ trễ cao.

Cải tiến

  • Xử lý lỗi: Thêm thử lại cho các lỗi tạm thời. Sử dụng Hàng đợi Thư Chết (DLQ) cho các lần gọi không thành công.
  • Ghi nhật ký tối ưu: Sử dụng ghi nhật ký có cấu trúc (ví dụ: JSON) để phân tích tốt hơn.
  • Tích hợp: Tích hợp với AWS Step Functions cho các quy trình làm việc nâng cao. Thêm SNS hoặc SES cho thông báo email về các sự kiện cụ thể.

Sau đây là một tập lệnh Terraform để tự động thiết lập chức năng AWS Lambda với nhiều kích hoạt: API Gateway, sự kiện thùng S3 và luồng DynamoDB.

Tập lệnh Terraform

# Define the AWS provider
provider "aws" {
  region = "us-east-1"  # Change this to your desired region
}

# IAM Role for Lambda Execution
resource "aws_iam_role" "lambda_role" {
  name = "lambda_execution_role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action    = "sts:AssumeRole"
        Effect    = "Allow"
        Principal = { Service = "lambda.amazonaws.com" }
      },
    ]
  })
}

# Attach policies to the IAM Role
resource "aws_iam_role_policy_attachment" "lambda_basic_policy" {
  role       = aws_iam_role.lambda_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

resource "aws_iam_role_policy_attachment" "lambda_dynamodb_policy" {
  role       = aws_iam_role.lambda_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonDynamoDBReadOnlyAccess"
}

resource "aws_iam_role_policy_attachment" "lambda_s3_policy" {
  role       = aws_iam_role.lambda_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
}

# Lambda Function
resource "aws_lambda_function" "lambda_function" {
  function_name    = "ComplexTriggerHandler"
  runtime          = "nodejs18.x"
  role             = aws_iam_role.lambda_role.arn
  handler          = "index.handler"
  filename         = "${path.module}/lambda_function.zip"

  source_code_hash = filebase64sha256("${path.module}/lambda_function.zip")

  # Environment variables (optional)
  environment {
    variables = {
      LOG_LEVEL = "INFO"
    }
  }
}

# API Gateway
resource "aws_api_gateway_rest_api" "api" {
  name        = "LambdaAPI"
  description = "API Gateway for Lambda function"
}

resource "aws_api_gateway_resource" "resource" {
  rest_api_id = aws_api_gateway_rest_api.api.id
  parent_id   = aws_api_gateway_rest_api.api.root_resource_id
  path_part   = "process"
}

resource "aws_api_gateway_method" "method" {
  rest_api_id   = aws_api_gateway_rest_api.api.id
  resource_id   = aws_api_gateway_resource.resource.id
  http_method   = "POST"
  authorization = "NONE"
}

resource "aws_api_gateway_integration" "integration" {
  rest_api_id             = aws_api_gateway_rest_api.api.id
  resource_id             = aws_api_gateway_resource.resource.id
  http_method             = aws_api_gateway_method.method.http_method
  integration_http_method = "POST"
  type                    = "AWS_PROXY"
  uri                     = aws_lambda_function.lambda_function.invoke_arn
}

resource "aws_lambda_permission" "api_gateway" {
  statement_id  = "AllowAPIGatewayInvoke"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.lambda_function.function_name
  principal     = "apigateway.amazonaws.com"
  source_arn    = "${aws_api_gateway_rest_api.api.execution_arn}/*/*"
}

# S3 Bucket and Event Trigger
resource "aws_s3_bucket" "s3_bucket" {
  bucket = "lambda-trigger-bucket-${random_id.bucket_suffix.hex}"
}

resource "random_id" "bucket_suffix" {
  byte_length = 8
}

resource "aws_s3_bucket_notification" "s3_notification" {
  bucket = aws_s3_bucket.s3_bucket.id

  lambda_function {
    lambda_function_arn = aws_lambda_function.lambda_function.arn
    events              = ["s3:ObjectCreated:*"]
  }
}

resource "aws_lambda_permission" "s3_permission" {
  statement_id  = "AllowS3BucketInvoke"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.lambda_function.function_name
  principal     = "s3.amazonaws.com"
  source_arn    = aws_s3_bucket.s3_bucket.arn
}

# DynamoDB Table and Stream
resource "aws_dynamodb_table" "dynamodb_table" {
  name           = "lambda-trigger-table"
  billing_mode   = "PAY_PER_REQUEST"
  hash_key       = "id"
  stream_enabled = true
  stream_view_type = "NEW_AND_OLD_IMAGES"

  attribute {
    name = "id"
    type = "S"
  }
}

resource "aws_lambda_event_source_mapping" "dynamodb_trigger" {
  event_source_arn = aws_dynamodb_table.dynamodb_table.stream_arn
  function_name    = aws_lambda_function.lambda_function.arn
  starting_position = "LATEST"
}

# Output API Gateway URL
output "api_gateway_url" {
  value = aws_api_gateway_rest_api.api.execution_arn
}

Các bước sử dụng kịch bản

  • Chuẩn bị mã Hàm Lambda: Viết mã Lambda (index.js) tương tự như ví dụ trong phần trước. Nén mã (lambda_function.zip) và đặt nó vào cùng thư mục với kịch bản Terraform.
  • Khởi tạo Terraform: terraform init
  • Lập kế hoạch Triển khai: terraform plan
  • Áp dụng Cấu hình: terraform apply
  • Kiểm tra các Trình kích hoạt: Sử dụng URL API Gateway đầu ra để gửi yêu cầu HTTP. Tải tệp lên vùng chứa S3. Thêm hoặc sửa đổi bản ghi trong bảng DynamoDB.

Dọn dẹp

Sau khi kiểm tra, hãy hủy các tài nguyên để tránh phát sinh chi phí không cần thiết: terraform destroy

Hy vọng bạn thấy bài viết hưu


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí