Tag: aws

  • Cách Grab sử dụng DynamoDB để xử lý hàng triệu đơn hàng mỗi ngày

    Cách Grab sử dụng DynamoDB để xử lý hàng triệu đơn hàng mỗi ngày

    Nội dung

    Giới thiệu

    Trong thực tế, sau khi một khách hàng đặt một đơn hàng GrabFood từ ứng dụng Grab, đối tác bán hàng sẽ chuẩn bị đơn hàng. Một đối tác tài xế (Grab Bike) sẽ tiếp nhận đơn hàng và vận chuyển cho khách hàng. Làm cách nào mà nền tảng đặt hàng (Order Platform) có thể xử lý hàng triệu đơn hàng như vậy mỗi ngày !?

    Nhìn chung, có thể phân loại các truy vấn mà Order Platform cần xử lý làm 2 loại: transactional queries vs analytical queries. Các truy vấn transactional ví dụ như: Create đơn hàng, Update đơn hàng, Get đơn hàng theo ID, Get các đơn hàng đang được xử lý của một khách hàng,… Những truy vấn này là những truy vấn quan trọng cần xử lý chính xác, hiệu quả. Các truy vấn analytical ví dụ như: Get lịch sử đơn hàng, Get các metrics thống kê về đơn hàng,…

    → Order Platform cần được thiết kế để có thể xử lý một lượng lớn dữ liệu transaction hàng tháng.

    Sau khi phân loại các mẫu truy vấn như trên, nhóm Kỹ sư của Grab đã đặt ra 3 mục tiêu thiết kế hệ thống Database để lưu trữ và xử lý như sau:

    • Stability (Tính ổn định): Giải pháp cơ sở dữ liệu phải đảm bảo phục vụ được khả năng đọc/ghi với thông lượng cao (Queries per Second, hay QPS). Xử lý đơn hàng trực tuyến phải có tính sẵn sàng cao.
    • Scalability and cost (Khả năng mở rộng và chi phí): Giải pháp phải đáp ứng được sự phát triển nhanh chóng về mặt business, ngoài ra giải pháp phải hiệu quả về chi phí trên quy mô lớn.
    • Consistency (Tính nhất quán): strong consistency cho các đối với các transactional querieseventually consistency với các analytical queries.

    Với các mục tiêu đó, Grab team đã quyết định thiết kế giải pháp cơ sở dữ liệu sử dụng các loại Database khác nhau, một để xử lý các transactional queries (OLTP) và một để xử lý các analytical queries (OLAP).

    Hình 1: Tổng quan giải pháp cơ sở dữ liệu cho Order Platform

    Vì các mục đích sử dụng khác nhau nên các cơ sở dữ liệu OLTP và OLAP sẽ lưu trữ data theo các cách khác nhau, với cùng một nguồn data nhưng OLTP sẽ lưu trữ chúng trong thời gian ngắn, ngược lại thì OLAP sẽ lưu trữ data trong thời gian dài hơn nhằm truy xuất các thông tin lịch sử và thống kê với dữ liệu. Trong Hình 1, Grab team sử dụng một Data Ingestion pipeline để có thể lưu trữ giữ liệu nhất quán trong cả 2 cơ sở dữ liệu. Cần lưu ý rằng, pipeline này sẽ hoạt động bằng cách sử dụng các phương pháp xử lý asynchronous (bất đồng bộ) vì chúng ta không cần phải xử lý các truy vấn với OLAP trực tuyến, real-time như OLTP.

    Trong bài viết này, tôi sẽ tập trung vào phân tích cách Grab xử lý hàng triệu đơn hàng mỗi ngày, chính vì vậy mà trong các phần tiếp theo, tôi sẽ không đề cập đến cơ sở dữ liệu OLAP. Các bạn nếu có hứng thú, có thể tìm đọc thêm các tài liệu từ Grab Tech Blog.

    Với các truy vấn OLTP, Grab team chọn DynamoDB là công nghệ lưu trữ và xử lý dữ liệu. Trong phần 2, tôi sẽ phân tích tại sao DynamoDB lại phù hợp. Phần 3, tôi sẽ phân tích Grab sử dụng DynamoDB như thế nào trong thiết kế mô hình dữ liệu hiệu quả.

    Tại sao lại là DynamoDB?

    Để trả lời câu hỏi này, tôi sẽ phân tích tính phù hợp của DynamoDB đối với việc đáp ứng các mục tiêu thiết kế được nêu ở phần trước.

    Điều đầu tiên để nói về DynamoDB, nó là một cơ sở dữ liệu NoSQL, fully-managed được cung cấp bởi AWS, có tính khả dụng cao (high availability), hiệu năng cao (high performance) với hiệu suất chỉ 1 phần nghìn giây (single-digit millisecond performance), khả năng mở rộng quy mô vô hạn mà không giảm hiệu suất (infinite scaling with no performance degradation) → Stability.

    High availability

    DynamoDB tự động phân phối dữ liệu và lưu lượng truy cập qua một số lượng máy chủ vừa đủ để xử lý các yêu cầu lưu trữ và thông lượng trong khi vẫn đảm bảo hiệu suất nhanh chóng và nhất quán. Tất cả dữ liệu được lưu trữ sử dụng ổ đĩa SSD và tự động được replicated qua các AZs khác nhau → high availability & high durability. Hơn nữa, chúng ta có thể sử dụng Global Tables để đồng bộ dữ liệu qua các Regions.

    Data models

    Trước khi nói về hiệu suất, tôi đề cập một chút về các mô hình dữ liệu mà DynamoDB hỗ trợ. Trong DynamoDB, có 2 mô hình dữ liệu đó là key-value store (sử dụng hash table) → cho phép hiệu suất truy vấn O(1) và wide-column store (sử dụng B-tree) → cho phép hiệu suất truy vấn O(log(n)), n là kích thước của 1 collections các phần tử có chung partition key.

    DynamoDB cho phép truy cập dữ liệu theo nhiều cách (access patterns) khác nhau với hiệu suất cao → điều quan trọng với DynamoDB đó là chúng ta phải thiết kế mô hình dữ liệu hiệu quả, tôi sẽ tiếp tục đề cập đến điều này trong phần 3 – Grab đã sử dụng DynamoDB thế nào?

    Infinite scaling with no performance degradation

    DynamoDB có khả năng scale vô hạn là nhờ cơ chế sharding dữ liệu qua nhiều server instances. Partitions là đơn vị lưu trữ cốt lõi của các DynamoDB tables. Khi một HTTP request (ứng dụng giao tiếp với DynamoDB qua HTTP connection thay vì TCP connection như các cơ sở dữ liệu truyền thống khác) tới DynamoDB, request router sẽ lấy partition key trong request đó và áp dụng một hàm băm (hash function) đối với partition key này. Kết quả của hàm băm sẽ chỉ định server nơi mà dữ liệu được lưu trữ, request sau đó sẽ được chuyển tiếp đến server nơi lưu trữ dữ liệu để thực hiện read/write với dữ liệu. Thiết kế này giúp cho DynamoDB có thể bổ sung thêm các storage nodes vô hạn khi dữ liệu scale up.

    Trong các phiên bản sớm hơn của DynamoDB, thông lượng được chia sẻ đều giữa các partitions → có thể dẫn đến mất cân bằng truy cập dữ liệu (có partition thì cần truy cập nhiều → không đủ tài nguyên, có partition thì nhu cầu truy cập ít hơn → thừa tài nguyên). Tuy nhiên, DynamoDB team hiện nay đã cho phép adaptive capacity, có nghĩa là thông lượng sẽ tự động được đáp ứng đủ cho các items cần truy cập nhiều, DynamoDB tự động cung cấp capacity cao hơn cho các partitions có nhiều lưu lượng truy cập hơn → DynamoDB có thể đáp ứng thông lượng lên đến 1000 WCUs (write capacity units) và 3000 RCUs (read capacity units). Hay nói một cách khác, DynamoDB có thể đáp ứng đến tối đa 3000 read Queries per second1000 write Queries per Second → hiệu suất cao.

    Ngoài ra, AWS còn cung cấp một tính năng nữa cho DynamoDB, gọi là DynamoDB Accelerator (DAX) – một fully-managed in-memory cache cho bảng DynamoDB. Mặc dù hiệu suất của DAX có thể không thực sự mạnh như Redis nhưng DAX có thể là một giải pháp caching phù hợp cho phép tốc độ đủ nhanh, ít bảo trì và tiết kiệm chi phí hơn so với các bộ đệm khác.

    Tóm lại, với DynamoDB, chúng ta vẫn có thể đảm bảo single-digit millisecond performance khi dữ liệu scale up. Chúng ta có thể mở rộng dữ liệu lên đến 10TB mà vẫn không suy giảm hiệu suất như sử dụng với 10GB. Điều mà không thể được đáp ứng trong các cơ sở dữ liệu quan hệ – ở đó, hiệu suất giảm dần khi dữ liệu scale up.

    Consistency

    Khi nói về các databases và các hệ thống phân tán, một trong những thuộc tính cần xem xết đó là các cơ chế consistency mà nó chúng hỗ trợ.

    Với DynamoDB, có 2 tùy chọn consistency mà cơ sở dữ liệu này hỗ trợ:

    • Strong consistency
    • Eventual consistency

    Với strong consistency, bất kỳ item nào chúng ta đọc từ DynamoDB sẽ phản ánh tất cả các yêu cầu writes đã xảy ra trước đó, trước khi yêu cầu đọc được thực thi. Ngược lại, với eventual consistency thì các yêu cầu đọc items có thể không phản ánh tất cả các yêu cầu writes xảy ra trước đó.

    → Các transactional queries trong Order Platform hoàn toàn có thể được sử dụng với strong consistency trong DynamoDB.

    Cost effective

    DynamoDB cung cấp một mô hình định giá linh hoạt. Với hầu hết các cơ sở dữ liệu, chi phí dựa trên 1 lượng capacities nhất định cho máy chủ bao gồm CPU, RAM, Hard Disks,… → điều này không tối ưu vì chúng ta sẽ phải trả tiền cho tài nguyên thay vì trả cho workload ta sử dụng (bao nhiêu truy vấn mỗi giây/queries per second)

    Ngược lại, DynamoDB được định giá trực tiếp dựa trên workload mà ứng dụng cần, chúng ta chỉ định thông lượng dựa trên WCU (write capacity units) và RCU (read capacity units). Một RCU cung cấp 1 strongly-consistent read per second hoặc 2 eventually-consistent reads per second, lên đến 4KB kích thước. Một WCU cho phép 1 write per second, lên đến 1KB kích thước → Chi phí cho Read và Write là riêng biệt → Có thể tối ưu 1 trong 2 độc lập dựa trên nhu cầu ứng dụng.

    Điều thú vị với DynamoDB là chúng ta có thể điều chỉnh thông lượng WCU và RCU nếu cần. Ví dụ như vào ban đêm hoặc cuối tuần, chúng ta có thể giảm thông lượng để tiết kiệm chi phí.

    Nếu chúng ta không estimate được thông lượng của ứng dụng → DynamoDB cung cấp tùy chọn On-Demand capacity mode (chi phí cao hơn Provisioned capacity mode) → Có thể sử dụng On-Demand capacity mode trong quá trình phát triển, thực thi load testing để đưa ra Provisioned capacity phù hợp → tiết kiệm chi phí.

    Năm 2017, AWS công bố một tính năng gọi là Time-to-live (TTL) cho DynamoDB, cho phép DynamoDB tự động xóa các item khi chúng ta chỉ muốn lưu trữ chúng cho 1 thời gian ngắn → điều này phù hợp với nhu cầu sử dụng DynamoDB của Grab, khi mà Grab team đã thiết kế một Data Ingestion Pipeline để lưu trữ dữ liệu lâu dài trong cơ sở dữ liệu OLAP. Việc lưu trữ dữ liệu trong DynamoDB trong thời gian ngắn còn cho phép hiệu quả chi phí cũng như đảm bảo hiệu suất luôn ổn định cao → đáp ứng được các transactional queries hàng ngày.

    Sử dụng DynamoDB như thế nào?

    Single-table design

    Khi mô hình hóa dữ liệu với DynamoDB, chúng ta nên sử dụng càng ít bảng càng tốt, lý tưởng nhất là chỉ sử dụng 1 bảng cho toàn bộ ứng dụng → single-table design. Tại sao lại như vậy !?

    Với các cơ sở dữ liệu quan hệ, thông thường chúng ta sẽ tạo 1 bảng ứng với mỗi entity (thực thể) trong ứng dụng. Các bảng có thể liên kết với nhau thông qua foreign keys → qua đó dữ liệu có thể được truy vấn qua các bảng sử dụng các phép toán joins. Các phép joins này khá tốn kém, khi mà nó phải scan nhiều bảng, so sánh các giá trị,… và trả về các kết quả.

    DynamoDB được xây dựng cho các use cases với hiệu suất truy vấn cao, quy mô dữ liệu lớn, như Order Platform của Grab → Các phép toán joinskhông phù hợp với các use cases như vậy. Thay vì cải thiện các phép toán joins trên quy mô dữ liệu lớn, DynamoDB giải quyết vấn đề bằng cách loại bỏ hoàn toàn việc sử dụng joins trong truy vấn. Loại bỏ bằng cách nào !?

    Cơ chế pre-join trong DynamoDB có thể cho phép liên kết dữ liệu tương tự như các cơ sở dữ liệu quan hệ. Cơ chế này sử dụng các item collections. Một item collection là tập các items trong bảng chia sẻ chung partition key. Như vậy, nhờ pre-join mà DynamoDB cho phép thiết kế cơ sở dữ liệu ứng dụng chỉ với 1 bảng. Tất nhiên, có những hạn chế nhất định với single-table design, tuy nhiên trong phạm vi bài viết này tôi sẽ không đi sâu vào phân tích những hạn chế đó. Trong các phần sau, tôi muốn làm nổi bật thiết kế của Grab sử dụng single-table design như thế nào để xử lý các transactional queries với các đơn hàng.

    Data modeling

    Trong DynamoDB, khi thiết kế mô hình dữ liệu, chúng ta phải xác định Primary keys của bảng. DynamoDB cung cấp 2 loại Primary keys khác nhau:

    • Simple primary keys: chỉ bao gồm một thuộc tính, gọi là partition key.
    • Composite primary keys: là sự kết hợp của partition key và một thuộc tính khác, gọi là sort key. Đôi khi, chúng ta có thể gọi partition key là hash key, sort key là range key.

    Để thiết kế cơ sở dữ liệu hiệu quả, trước hết chúng ta cần hình dung về mô hình dữ liệu của Order Platform. Order Platform cần thực hiện các truy vấn với đơn hàng (order), các đơn hàng liên quan đến khách hàng hay người đặt hàng, mỗi đơn hàng sẽ có 1 trong 3 trạng thái: ongoing, completed, hoặc canceled → Khá đơn giản và phù hợp với single-table design trong DynamoDB. Hình dưới đây cho thấy thiết kế này của Grab.

    Hình 2: Minh họa bảng DynamoDB lưu trữ các đơn hàng của Order Platform

    Trong hình 2 ở trên, Grab team đã đưa ra một minh họa về bảng DynamoDB nơi mà lưu trữ các đơn hàng, với Partition keymã đơn hàng (order_id) (Lưu ý rằng hình trên chỉ mang tính chất minh họa, không phải là toàn bộ thiết kế của Order Platform).

    Thiết kế này cho phép các transactional queries với đơn hàng như Get, Create, Update được thực hiện với hiệu suất cao, strong consistency. Bởi cấu trúc dữ liệu sử dụng hash table → các truy vấn sử dụng order_id sẽ có độ phức tạp thời gian là O(1).

    Với các truy vấn liên quan đến 1 đơn hàng cụ thể, chúng ta có thể sử dụng order_id (partition key). Vậy với các truy vấn liên quan đến 1 khách hàng, ví dụ như lấy danh sách các đơn hàng ở trạng thái ongoing của khách hàng Alice như trong Hình 2, chúng ta có thể tiếp tục sử dụng mô hình dữ liệu trên không !?

    → Câu trả lời là có. Đến đây, chúng ta phải sử dụng một tính năng hữu ích khác được cung cấp bởi DynamoDB, đó là Global secondary index.

    Secondary indexes

    Primary keys có thể giới hạn các access patterns, như trong trường hợp của Grab, khi sử dụng primary keys với partition keyorder_id thì chúng ta không thể tiếp tục truy vấn với 1 khách hàng cụ thể trong bảng đơn hàng. Để giải quyết hạn chế này, DynamoDB đưa ra một khái niệm gọi là secondary indexes. Secondary indexes cho phép chúng ta re-shape lại dữ liệu sang 1 cấu trúc khác để phục vụ các truy vấn với access pattern khác so với thiết kế ban đầu.

    Khi tạo secondary index, chúng ta cần chỉ định key schema của index, key schema này tương tự như Primary keys của bảng, có thể chỉ cần partition key hoặc kết hợp partition key và sort key. Có 2 loại secondary indexes trong DynamoDB:

    • Local secondary indexes (LSI)
    • Global secondary indexes (GSI)

    Một LSI sử dụng chung partition key như primary key của bảng nhưng với một sort key khác đi → điều này cho phép các truy vấn theo range khác với access pattern ban đầu.

    Ngược lại, với GSI, chúng ta có thể chọn bất cứ thuộc tính nào làm partition key và sort key.

    → Trong trường hợp của Grab, với thiết kế ban đầu như Hình 2, bởi LSI không thay đổi partition key → không phù hợp với các truy vấn theo 1 khách hàng cụ thể → Grab team đã sử dụng GSI để giải quyết vấn đề này.

    Ví dụ với một truy vấn Get tất cả các đơn hàng trong trạng thái Ongoing của khách hàng có pax_idAlice. Nếu chúng ta sử dụng GSI chỉ với partition keypax_id thì việc truy vấn dựa trên pax_id (trường hợp này là Alice) sẽ trả về tất cả các đơn hàng của Alice → Filter trên các kết quả đó để trích xuất ra các đơn hàng của Alice có trạng thái Ongoing. Như vậy, nếu như Alice có nhiều đơn hàng thì việc Filter như vậy sẽ ảnh hưởng đến độ trễ truy vấn nói riêng và hiệu năng tổng thể của hệ thống nói chung. Giải quyết bằng cách nào !?

    → Sử dụng Sparse indexes

    Hình 3: GSI cho bảng đơn hàng

    GSI được Grab team thiết kế sử dụng ID của khách hàng (pax_id_gsi) làm partition key và thời gian tạo đơn hàng (created_at) làm sort key, sparse index cho phép tại bất kỳ thời điểm nào, bảng GSI chỉ lưu trữ các đơn hàng với trạng thái Ongoing (Khi một đơn hàng chuyển từ Ongoing → Completed thì nó sẽ tự động bị xóa khỏi GSI) → Cho phép độ trễ truy vấn thấp, hiệu suất tốt hơn và hiệu quả chi phí.

    Một vài lưu ý khi sử dụng GSI với DynamoDB:

    • Với GSI, chúng ta cần provision thêm throughput. RCU và WCU được sử dụng riêng biệt so với bảng ban đầu.
    • Dữ liệu được replicated từ bảng ban đầu sang GSI theo cơ chế asynchronous → chỉ cho phép eventual consistency.

    Time to live (TTL)

    Vì mục đích chỉ lưu trữ giữ liệu trong DynamoDB trong một thời gian ngắn (chúng ta có thể thấy điều này trên ứng dụng Grab Food, các đơn hàng sau một thời gian ~ 3 tháng sẽ không còn thấy trong lịch sử nữa :v) → Grab team sử dụng tính năng TTL được cung cấp bởi DynamoDB → cho phép tự động xóa các items khi expired. Để sử dụng TTL hiệu quả, Grab team chỉ thêm TTL với các items mới được add vào bảng, xóa các items không có thuộc tính TTL theo cách thủ công và chạy các script để xóa các items có TTL đã out of date khá lâu → cho phép tính năng TTL trên bảng với kích thước đủ nhỏ → hiệu quả khi DynamoDB tự động thực hiện scan bảng và xóa các items đã expired.

    Data ingestion pipeline

    Hình 4: Data ingestion pipeline

    Grab team sử dụng Kafka để đồng bộ dữ liệu giữa 2 cơ sở dữ liệu OLTP và OLAP. Sử dụng Kafka như thế nào, các bạn có thể đọc thêm từ Grab tech blog như trong tài liệu tham khảo [1]. Trong phạm vi bài viết này, tôi không phân tích về lựa chọn này. Tuy nhiên, nếu trong tương lai có thể, tôi muốn phân tích về tính khả thi của giải pháp sử dụng DynamoDB Streams để xử lý và đồng bộ dữ liệu giữa các cơ sở dữ liệu OLTP và OLAP.

    Kết luận

    Trong bài viết này, tôi đã dựa trên một case study là Order Platform của Grab Food để phân tích một số tính năng, nguyên lý của DynamoDB phù hợp với các ứng dụng cần OLTP. DynamoDB đã cho phép Order Platform đạt được tính ổn định, tính khả dụng cao, hiệu suất cao cùng với khả năng mở rộng quy mô vô hạn. Ngoài ra, tôi cũng giới thiệu các cơ chế để cải thiện hiệu suất, chi phí hiệu quả mà Grab team sử dụng như GSI, TTL. Data ingestion pipeline được giới thiệu như là 1 biện pháp mà Grab sử dụng để đồng bộ dữ liệu sang cơ sở dữ liệu OLAP phục vụ các truy vấn thống kê cũng như đảm bảo khả năng mở rộng nghiệp vụ ứng dụng về sau.

    Tài liệu tham khảo

    [1] Xi Chen, Siliang Cao, “How we store and process millions of orders daily”, https://engineering.grab.com/how-we-store-millions-orders, Accessed: 2022-03-31

    [2] Alex DeBrie, “The DynamoDB Book”

    [3] Amazon DynamoDB, https://disaster-recovery.workshop.aws/en/services/databases/dynamodb.html, Accessed: 2022-03-31

  • Giả lập môi trường phát triển AWS với LocalStack

    Giả lập môi trường phát triển AWS với LocalStack

    Khi chúng ta xây dựng các ứng dụng với AWS, chúng ta truy cập các dịch vụ AWS khác nhau cho nhiều mục đích: lưu trữ tệp trong S3, lưu một số dữ liệu trong DynamoDB, gửi tin nhắn tới SQS, viết trình xử lý sự kiện bằng các hàm lambda và nhiều mục đích khác.

    Tuy nhiên, trong những ngày đầu phát triển, chúng ta thích tập trung vào việc viết mã ứng dụng thay vì dành thời gian cho việc thiết lập môi trường để truy cập các dịch vụ AWS. Việc thiết lập môi trường phát triển để sử dụng các dịch vụ này tốn nhiều thời gian và phát sinh chi phí không mong muốn với AWS.

    Để tránh bị sa lầy bởi những tác vụ thông thường này, chúng ta có thể sử dụng LocalStack để phát triển và kiểm tra các ứng dụng của mình với các triển khai mô phỏng của các dịch vụ này.


    Tại sao nên dùng LocalStack?

    Chúng ta thường hay sử dụng phương thức dummy như mock, fake, proxy object để chạy Unit test cho ứng dụng với việc sử dụng các liên kết bên ngoài như database, aws, …

    Với LocalStack, chúng ta sẽ giả lâp môi trường trường AWS phía local. LocalStack hỗ trợ:

    • Chạy các ứng dụng tương tác với các dịch vụ AWS ở phía local mà không cần kết nối môi trường thật.

    • Tránh sự phức tạp của cấu hình AWS và tập trung vào phát triển.

    • Tích hợp chạy thử nghiệm với CI / CD pipeline.

    • Cấu hình và thử nghiệm các kịch bản lỗi.


    Cách sử dụng LocalStack?

    Việc sử dụng LocalStack của chúng tôi tập trung vào hai nhiệm vụ:

    • Chạy LocalStack.
    • Ghi đè URL điểm cuối AWS bằng URL của LocalStack.

    LocalStack thường chạy bên trong vùng chứa Docker, nhưng thay vào đó chúng ta cũng có thể chạy nó dưới dạng ứng dụng Python.


    Chạy Localstack với Python

    Trước tiên, chúng ta cài đặt LocalStack bằng pip:

    pip install localstack
    

    Sau đó, chúng ta bắt đầu localstack bằng lệnh start:

    localstack start
    

    Thao tác này sẽ khởi động LocalStack bên trong vùng chứa Docker.


    Chạy Localstack với Docker

    Bạn cũng có thể sử dụng tệp docker-compose.yml từ Github và sử dụng lệnh này (hiện yêu cầu docker-compile phiên bản 1.9.0+):

    docker-compose up
    

    Tùy chỉnh LocalStack

    Mặc định của LocalStack là tạo ra tất cả các dịch vụ được hỗ trợ với mỗi dịch vụ trong số chúng đang lắng nghe trên cổng 4566. Chúng ta có thể ghi đè hành vi này của LocalStack bằng cách thiết lập một vài biến môi trường.

    Port mặc định 4566 có thể được ghi đè bằng cách đặt biến môi trường EDGE_PORT. Chúng tôi cũng có thể định cấu hình LocalStack để tạo ra một nhóm dịch vụ hạn chế bằng cách đặt danh sách tên dịch vụ được phân tách bằng dấu phẩy làm giá trị cho biến môi trường SERVICES:

    version: '2.1'
    
    services:
      localstack:
        container_name: "${LOCALSTACK_DOCKER_NAME-localstack_main}"
        image: localstack/localstack
        ports:
          - "4566-4599:4566-4599"
          - "${PORT_WEB_UI-8080}:${PORT_WEB_UI-8080}"
        environment:
          - SERVICES=s3,dynamodb
    

    Trong file docker-compose.yml này, chúng ta đặt biến môi trường SERVICES thành tên của dịch vụ chúng ta muốn sử dụng trong ứng dụng của mình (S3 và DynamoDB).


    Kết nối với LocalStack

    Chúng ta truy cập các dịch vụ AWS thông qua AWS CLI hoặc từ các ứng dụng của chúng ta bằng AWS SDK.

    AWS SDK và CLI là một phần không thể thiếu trong bộ công cụ của chúng ta để xây dựng ứng dụng với các dịch vụ AWS. SDK cung cấp các thư viện máy khách bằng tất cả các ngôn ngữ lập trình phổ biến như Java, Node js hoặc Python để truy cập các dịch vụ AWS khác nhau.

    Cả AWS SDK và CLI đều cung cấp tùy chọn ghi đè URL của AWS API. Chúng ta thường sử dụng điều này để chỉ định URL của máy chủ proxy của chúng ta khi kết nối với các dịch vụ AWS từ phía sau máy chủ proxy của công ty. Chúng ta sẽ sử dụng tính năng tương tự này trong môi trường local của chúng tôi để kết nối với LocalStack.

    Chúng ta thực hiện việc này trong AWS CLI bằng các lệnh như sau:

    aws --endpointurl http://localhost:4566 s3 ls
    

    Việc thực thi lệnh này sẽ gửi các yêu cầu đến URL của LocalStack được chỉ định làm giá trị của tham số dòng lệnh URL điểm cuối (localhost trên port 4566) thay vì đến AWS thật.

    Chúng ta sử dụng cách tiếp cận tương tự khi sử dụng SDK:

    URI endpointOverride = new URI("http://localhost:4566");
    S3Client s3 = S3Client.builder()
      .endpointOverride(endpointOverride )  // <-- Overriding the endpoint
      .region(region)
      .build();
    

    Ở đây, chúng ta đã ghi đè điểm cuối AWS của S3 bằng cách cung cấp giá trị của URL của LocalStack làm tham số cho phương thức endpointOverride trong lớp S3ClientBuilder.

    Toàn bộ bài viết là giới thiệu về framework LocalStack. Trong bài viết sau, mình sẽ hướng dẫn mọi người cách tích hợp nó vào source code như nào nhé.

  • Upload file dung lượng lớn tới S3 với Multipart và Presign-url

    Upload file dung lượng lớn tới S3 với Multipart và Presign-url

    Nếu như bình thường ta thực hiện single upload tới s3 thì sẽ có 2 cách sau:

    • Upload file lên s3 qua server của chúng ta: Cái này thì reject vì chúng ta phải tốn thời gian upload lên server của mình rồi từ đó mới upload lên S3. Với large file thì gần như là không nên.

    • Upload từ client bằng cách sử dụng presign-url. Thay vì phải qua server trung gian thì chúng ta upload thẳng lên S3. Tốc độ cải thiện rất nhiều vì cách 1 đa phần thời gian tốn ở khâu upload lên server của chúng ta.

    Nhưng nói gì thì nói single upload to S3 thì sẽ có những hạn chế sau dưới Maximum size chỉ là 5GB . Điều này thực sự hạn chế. Hơn nữa AWS cũng suggest chúng ta là file >100MB thì nên sử dụng Multipart Upload . Vậy ưu điểm chính của nó là:

    • Maximum size là 5TB
    • Tốc độ upload tốt hơn

    Điều đó mình lựa chọn multipart và presign-url cho bài toán upload file dung lượng lớn. Server ở đây mình sử dụng là python. Client thì dùng angular.

    part1

    Server

    Phần server này mình sử dụng kiến trúc serverless để triển khai với ngôn ngữ Python Flask trên môi trường AWS Cloud

    Các bạn tham khảo project trên Github của mình để hiểu cách setup nhé.

    Phần Server chủ yếu sẽ có 3 api chính:

    • Đầu tiên là api start-upload, với logic là request tới s3 để lấy uploadId

      @app.route("/start-upload", methods=["GET"])
      def start_upload():
          file_name = request.args.get('file_name')
          response = s3.create_multipart_upload(
              Bucket=BUCKET_NAME, 
              Key=file_name
          )
      
          return jsonify({
              'upload_id': response['UploadId']
          })
      
    • Tiếp theo là api get-upload-url để lấy presignurl cho từng part của file khi upload

      @app.route("/get-upload-url", methods=["GET"])
      def get_upload_url():
           file_name = request.args.get('file_name')
           upload_id = request.args.get('upload_id')
           part_no = request.args.get('part_no')
           signed_url = s3.generate_presigned_url(
               ClientMethod ='upload_part',
               Params = {
                   'Bucket': BUCKET_NAME,
                   'Key': file_name, 
                   'UploadId': upload_id, 
                   'PartNumber': int(part_no)
               }
           )
      
           return jsonify({
               'upload_signed_url': signed_url
           })
      
    • Cuối cùng đây là api để kiểm tra xem việc upload đã hoàn thành chưa.

       @app.route("/complete-upload", methods=["POST"])
       def complete_upload():
           file_name = request.json.get('file_name')
           upload_id = request.json.get('upload_id')
           print(request.json)
           parts = request.json.get('parts')
           response = s3.complete_multipart_upload(
               Bucket = BUCKET_NAME,
               Key = file_name,
               MultipartUpload = {'Parts': parts},
               UploadId= upload_id
           )
           
           return jsonify({
               'data': response
           })
      

    Client

    Phần client này mình dùng Angular để triển khai 1 trang web upload file đơn giản.

    Các bạn tham khảo project trên Github của mình để hiểu cách setup nhé.

    Khi có action upload đầu tiên ta sẽ gọi tới function uploadMultipartFile. Function uploadMultipartFile có chức năng với các step sau:

    • Call api start-upload để lấy được uploadId.

      const uploadStartResponse = await this.startUpload({
          fileName: file.name,
          fileType: file.type
      });
      
    • Split file upload thành các chunks, ở đây chia thành 10MB/chunk nhé. Ở đây mình chia là 10MB vì minimum size của Multipart Upload là 10MB.

    • Thực hiện call api get-upload-url để lấy preSignurl cho từng chunk ở trên.

    • Upload các chunks bằng signurl.

    • Khi tất cả các chunks upload xong sẽ thực hiện call api complete-upload để xác nhận với S3 mình đã upload đầy đủ các part. Done.

      try {
          const FILE_CHUNK_SIZE = 10000000; // 10MB
          const fileSize = file.size;
          const NUM_CHUNKS = Math.floor(fileSize / FILE_CHUNK_SIZE) + 1;
          let start, end, blob;
      
          let uploadPartsArray = [];
          let countParts = 0;
      
          let orderData = [];
      
          for (let index = 1; index < NUM_CHUNKS + 1; index++) {
            start = (index - 1) * FILE_CHUNK_SIZE;
            end = (index) * FILE_CHUNK_SIZE;
            blob = (index < NUM_CHUNKS) ? file.slice(start, end) : file.slice(start);
      
            // (1) Generate presigned URL for each part
            const uploadUrlPresigned = await this.getPresignUrl({
              fileName: file.name,
              fileType: file.type,
              partNo: index.toString(),
              uploadId: uploadStartResponse.upload_id
            });
      
            // (2) Puts each file part into the storage server
      
            orderData.push({
              presignedUrl: uploadUrlPresigned.upload_signed_url,
              index: index
            });
      
            const req = new HttpRequest('PUT', uploadUrlPresigned.upload_signed_url, blob, {
              reportProgress: true
            });
      
            this.httpClient
              .request(req)
              .subscribe((event: HttpEvent<any>) => {
                switch (event.type) {
                  case HttpEventType.UploadProgress:
                    const percentDone = Math.round(100 * event.loaded / FILE_CHUNK_SIZE);
                    this.uploadProgress$.emit({
                      progress: file.size < FILE_CHUNK_SIZE ? 100 : percentDone,
                      token: tokenEmit
                    });
                    break;
                  case HttpEventType.Response:
                    console.log('Done!');
                }
      
                // (3) Calls the CompleteMultipartUpload endpoint in the backend server
      
                if (event instanceof HttpResponse) {
                  const currentPresigned = orderData.find(item => item.presignedUrl === event.url);
      
                  countParts++;
                  uploadPartsArray.push({
                    ETag: event.headers.get('ETag').replace(/[|&;$%@"<>()+,]/g, ''),
                    PartNumber: currentPresigned.index
                  });
                  if (uploadPartsArray.length === NUM_CHUNKS) {
                    console.log(file.name)
                    console.log(uploadPartsArray)
                    console.log(uploadStartResponse.upload_id)
                    this.httpClient.post(`${this.url}/complete-upload`, {
                      file_name: encodeURIComponent(file.name),
                      parts: uploadPartsArray.sort((a, b) => {
                        return a.PartNumber - b.PartNumber;
                      }),
                      upload_id: uploadStartResponse.upload_id
                    }).toPromise()
                      .then(res => {
                        this.finishedProgress$.emit({
                          data: res
                        });
                      });
                  }
                }
              });
          }
        } catch (e) {
          console.log('error: ', e);
        }
      

    Có những chú ý sau.

    • Minimum size của Multipart Upload là 10MB. Maximum là 5TB
    • Được upload tối đã 10000 chunks cho 1 file thôi. Ví dụ ở đây mình chia 1 chunk là 10MB thì mình chỉ upload tối đa 200GB. Tương tự 1 chunk là 5GB => upload tối đa là 5TB.

    Demo

    • Cài đặt server với serverless. Vào project serverless và gõ lệnh

      sls deploy
      

      Có những chú ý sau:

      • Sau khi cài đặt NodeJS thì cài Serverless framework

        npm i -g serverless
        
      • Cần có account AWS

      • Config ở file serverless đang sử dụng aws profile mà mình đã setup ở local(khanhcd92). Nếu bạn dùng default hoặc cấu hình profile riêng thì hãy thay đổi nó nhé. Cách cấu hình aws profile.

        provider:
          name: aws
          runtime: python3.6
          stage: dev
          region: ap-southeast-1
          profile: khanhcd92
        
    • Thay đường dẫn API endpoint từ output của cài đặt server cho url trong class UploadService của project web

    • Cài đặt thư viện trong project web

      npm i
      
    • Chạy web angular ở local với lệnh

      npm start
      

      part2

    • Kéo file cần upload vào trình duyệt. Chỗ này mình sẽ dùng 1 file có dụng lượng khoảng 75M để upload.

      part3

    • Kiểm tra kết quả trên S3 part4

    Chi tiết source code các bạn xem ở đây nhé.

  • Thiết kế khả năng phục hồi cho một trang web tĩnh trên AWS Cloud

    Thiết kế khả năng phục hồi cho một trang web tĩnh trên AWS Cloud

    Khi bạn thiết kế một hệ thống thì bạn nên có suy nghĩ rằng không hệ thống nào có thể đảm bảo vận hành trơn chu mà không có vấn đề gì xảy ra. Hệ thống có thể sập vì rất nhiều lý do. Chúng ta luôn phải có phương án có thể khôi phục lại ngay tức thì để độ tổn thất cho khách hàng và công ty ở mức thấp nhất. Đặc biệt là các hệ thống trên Cloud.

    Werner Vogels, CTO & VP, Amazon says:

    Everything fails all the time.

    Bài viết hôm nay mình sẽ giới thiệu về cách thiết kế 1 trang web tĩnh trên S3 mà đảm bảo khả năng hồi phục(Resiliency).

    Khả năng phục hồi (Resiliency) là gì?

    Khả năng phục hồi là khả năng hệ thống phục hồi sau lỗi do hệ thống quá tải, bị tấn công, các lỗi liên quan phần cứng hoặc phần mềm.

    Amazon S3 rất khả dụng và có khả năng phục hồi cao đối với sự cố theo vùng nhưng liệu trang web có khả năng chống chịu với thảm họa khu vực như nguồn điện, thời tiết.,? đám mây có những cách tốt hơn để giúp cung cấp tính liên tục cho hoạt động kinh doanh và đây là một trong những cách.

    Thiết kế khả năng phục hồi cho một trang web tĩnh trên AWS Cloud

    s3dr

    Các dịch vụ sử dụng

    • Route 53
    • CloudFront
    • S3
    • IAM

    Triển khai

    Do chưa có domain có sẵn nên bước triển khai này mình sẽ chỉ sử dụng các dịch vụ:

    • CloudFront
    • S3
    • IAM

    Bước 1: Tạo S3 bucket name static-web-sample-s3 với region Singapore và enable tính năng Versioning. Chi tiết

    Bước 2: Tạo S3 bucket name static-web-sample-dr-s3 với region Tokyo và enable tính năng Versioning Chi tiết

    Ghi chú: Ở hướng dẫn này, web chính của mình triển khai lưu trên S3 với region Singapore, và web khôi phục khi xảy ra thảm hoạ sẽ được lưu trên S3 với region Tokyo.

    Bước 3: Cấu hình replication giữa 2 bucket name

    • Ở bucket name static-web-sample-s3 chọn Managemet tab. Tiếp theo, ở phần Replication rules chọn Create replication rule.

      • Nhập rule name rep1

      • Phần rule scope thì chọn option thứ 2 rep2

      • Ấn Browse S3 xong chọn đến bucket name static-web-sample-dr-s3 rep3

      • Phần IAM role, chọn Create new role rep4

      • Phần Additional replication options thì không chọn gì cả rồi ấn Save rep5

      • Kết quả sau khi cấu hình xong rep6

    Bước4: Tạo 1 trang page index.html đơn giản rồi upload nó lên S3 bucket name

    <!DOCTYPE html>
    <html lang='en'>
    
    <head>
        <meta charset='UTF-8'>
        <title>Static HTML</title>
    </head>
    
    <body>
        <div class="container">
            <h1>Hey there</h1>
        </div>
    </body>
    
    </html>
    

    Bước 5: Tạo CloudFront liên kết với S3 bucket name static-web-sample-s3. Chi tiết

    origin

    Ghi chú: Sử dụng OAI cho CloudFront access đến S3 và chọn Yes, update the bucket policy

    Bước 6: Tạo thêm origin của CloudFront liên kết tới S3 bucket name static-web-sample-dr-s3 dro2

    Ghi chú: Sử dụng OAI cho CloudFront access đến S3 và chọn Yes, update the bucket policy

    Bước 7: Tạo Origin Group của CloudFront. Nhập thông như hình bên dưới.

    • Chọn Create origin group og1
    • Chọn các origin cần sử dụng rồi thêm vào. Chú ý origin web chính sẽ được đặt lên đầu tiên. og2

    Bước 8: Cập nhật Behavior default của CloudFront Vì behavior default của CloudFront đang liên kết với origin của S3 bucket name static-web-sample-s3, nên chúng ta sẽ cần cập nhật sang sử dụng origin group.

    • Chọn Behavior Default rồi chọn Edit beha1
    • Ở màn hình Edit, cập nhật origin sang origin group (DR-Group) beha2

    Bước 9: Đợi CloudFront Deploy re1

    Bước 10: Sau khi CloudFront đã deploy xong, thì chúng ta vào brownser và nhập DNS link của CloudFront. web

    Bước 11: Xoá bucket name static-web-sample-s3 hoặc chỉ xoá file index.html trong bucket.

    Bước 12: Bạn vào lại trình duyệt reload lại trang web nhé. Bạn sẽ thấy trang web vẫn hoạt động bình thường dù đã xoá file hoặc S3 bucket chưa source chính. web

  • Triển khai angular web với AWS CDK

    Triển khai angular web với AWS CDK

    Ở phần đầu của series, mình đã giới thiệu AWS CDK là gì. Nay để hiểu rõ nó hơn mình sẽ giới thiệu cách triển khai 1 web angular với AWS CDK.

    Chuẩn bị

    • Chuẩn bị sẵn account AWS
    • Cài đặt NodeJS
    • Cài đặt Typescript: npm i -g typescript
    • Cài đặt AWS CLI
    • Cấu hình AWS client: aws configure. Bạn sẽ cần nhập thông tin access key ID, secret access key và aws region. Chi tiết cấu hình bạn có tìm ở đây
    • Cài đẳt AWS CDK: npm install -g aws-cdk

    Thực hiện

    • Tạo một thư mục static_web_cdk.

    • Trong thư mục này tạo 2 thư mục con, 1 cái angular-sample-web cho website và 1 cái infrastructure cho phần infra.

    Build source code Angular

    • Di chuyển đến thư mục ngular-sample-web. Trong thư mục này mình sẽ download sample từ trên trang chủ Angular về để sử dung Xem chi tiết ở đây.

    web1

    • Di chuyển đến thư mục infrastructure

    • Khơi tạo source code infrastructure: cdk init app --language typescript. Lệnh này sẽ gen ra sample code cho chúng ta.

    web2

    • Mở file bin/infrastructure.ts, thêm stackName và uncomment phần env. Phần env này chúng sẽ sử dụng mặc định như bên dưới.
    #!/usr/bin/env node
    import 'source-map-support/register';
    import * as cdk from '@aws-cdk/core';
    import { InfrastructureStack } from '../lib/infrastructure-stack';
    
    const app = new cdk.App();
    new InfrastructureStack(app, 'InfrastructureStack', {
      /* If you don't specify 'env', this stack will be environment-agnostic.
       * Account/Region-dependent features and context lookups will not work,
       * but a single synthesized template can be deployed anywhere. */
      stackName: 'web-static-stack',
      /* Uncomment the next line to specialize this stack for the AWS Account
       * and Region that are implied by the current CLI configuration. */
      env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
    
      /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
    });
    
    • Bây giờ mở file /lib/infrastructure-stack.ts nơi chúng ta sẽ khai báo các dịch vụ của stack.

    Khai báo các thành phần của Stack

    Để triển khai Angular web static mình sử dụng các dịch vụ CloudFront và S3. CloudFront sẽ access vào private S3 thông qua OAI (Origin Access Identity). Vậy thì Stack cần phải code những gì mọi người theo dõi bên dưới nhé.

    • Cài đặt thư viện @aws-cdk/aws-s3 để sử dụng được dịch vụ s3.
    • Cài đặt thư viện @aws-cdk/aws-cloudfront để sử dụng dịch vụ cloudfront.
    • Cài đặt thư viện @aws-cdk/aws-iam để sử dụng khai báo Policy cho S3
    • Trong file lib/infrastructure-stack.ts, chúng ta sẽ cần import các thư viện phía trên vào. Sau đó ở sau ta sẽ cần khai báo các dịch vụ bên duới dòng comment //The code that defines your stack goes here:
    import * as cdk from '@aws-cdk/core';
    import { BlockPublicAccess, Bucket, BucketEncryption } from '@aws-cdk/aws-s3';
    import { CloudFrontWebDistribution, OriginAccessIdentity } from '@aws-cdk/aws-cloudfront';
    import { PolicyStatement } from '@aws-cdk/aws-iam';
    
    export class InfrastructureStack extends cdk.Stack {
      constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
        super(scope, id, props);
    
        // The code that defines your stack goes here
        // Khai báo S3
        const myBucket = new Bucket(this, "static-website-bucket", {
          bucketName: 'static-web-sample-s3',
          versioned: true,
          encryption: BucketEncryption.S3_MANAGED,
          blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
          websiteIndexDocument: "index.html"
        });
    
        // Khai báo OAI
        const oia = new OriginAccessIdentity(this, 'OIA', {
          comment: "Created by CDK"
        });
    
        // Khai báo Policy cho S3
        const policyStatement = new PolicyStatement();
        policyStatement.addActions('s3:GetObject');
        policyStatement.addResources(`${myBucket.bucketArn}/*`);
        policyStatement.addCanonicalUserPrincipal(oia.cloudFrontOriginAccessIdentityS3CanonicalUserId);
        myBucket.addToResourcePolicy(policyStatement);
    
        // Khai báo CloudFront
        new CloudFrontWebDistribution(this, 'static-website-cf', {
          originConfigs: [
            {
              s3OriginSource: {
                s3BucketSource: myBucket,
                originAccessIdentity: oia
              },
              behaviors: [
                { isDefaultBehavior: true }
              ]
            }
          ]
        });
    
        // Output giá trị DNS của CloudFront
        new CfnOutput(this, 'CloudFrontDNS', {
          value: cfWebStatic.distributionDomainName
        })
      }
    }
    
    
    • Tiêp theo chúng ta sẽ phần khai báo deploy. Bạn sẽ cần cài thư viện @aws-cdk/aws-s3-deployment và thêm import * as s3Deployment from '@aws-cdk/aws-s3-deployment' vào file stack và khai báo deploy như bên dưới:
    const deployment = new s3Deployment.BucketDeployment(this, "deployStaticWebsite", {
          sources: [s3Deployment.Source.asset("../angular-sample-web/dist")],
          destinationBucket: myBucket,
        });
    
    • Thuộc tính s3Deployment.Source.asset("../angular-sample-web/dist") là nơi mọi người sẽ cần update tới đúng vị trị source code mình đang dùng.

    Triển khai

    Bây giờ chúng ta sẽ cần triển khai stack này tới AWS:

    • Di chuyển đến thư mục angular-sample-web

    • Cài đặt các thư viện npm i

    • Build Angular app ng build --prod. Lúc này bundle source code sẽ được gen ra trong thư mục dist

    • Sau khi build xong Angular app thì di chuyển đến thư mục infrastructure

    • Trên cửa sổ command gõ lệnh: cdk boostrap. Thư mục cdk.out sẽ được tạo ra. Nó sẽ bao gồm code phần website và infrastructure template.

    The first time you deploy an AWS CDK app into an environment (account/region), you’ll need to install a “bootstrap stack”. This stack includes resources that are needed for the toolkit’s operation. For example, the stack includes an S3 bucket that is used to store templates and assets during the deployment process.

    • Chạy lệnh cdk synth. Nó sẽ tổng hợp lại CloudFormation template cho stack.
    • Chạy lệnh cdk deploy
    • Nhập y để tiếp tục triển khai
    • Sau khi đã hoàn thành, chúng ta tới console của dịch vụ CloudFormation. Chúng ta sẽ thấy có 2 stack được tạo ra là CDKToolkit và web-static-stack

    web4

    • Bây giờ vào tiếp stack web-static-stack để lấy link web.

    web4

    • Click vào link CloudFrontDNS trên tab Output của màn hình stack. Bạn sẽ thấy kết quả như bên dưới.

    web5

    NOTE:

    • Khi bạn không dùng nữa thì có thể chạy lệnh cdk destroy để xoá stack.

    • Ví dụ bên trên là dùng cài đặt AWS client với giá trị default. Khi bạn đã thiết lập profile cho AWS thì tham khảo các câu lệnh để chạy cdk bên dưới đây nhé.

      cdk boostrap --profile profile_name
      cdk synth --profile profile_name
      cdk deploy --profile profile_name
      cdk destroy --profile profile_name
      

    Bạn có thể tham khao code ở Github

    Tôi hy vọng nó sẽ có ích. Bạn cũng có thể thay thế Angular với ReactJS hoặc VueJS. Xin vui lòng cho tôi biết phản hồi của bạn.

  • AWS CDK là gì ?

    AWS CDK là gì ?

    Infrastructure as Code

    Khái niệm này đã có rất nhiều bài viết nói về. Nên mình chỉ trích dẫn nội dung chính như bên dưới.

    Infrastructure as code (IaC) is the process of managing and provisioning computer data centers through machine-readable definition files, rather than physical hardware configuration or interactive configuration tools. The IT infrastructure managed by this process comprises both physical equipment, such as bare-metal servers, as well as virtual machines, and associated configuration resources. The definitions may be in a version control system. It can use either scripts or declarative definitions, rather than manual processes, but the term is more often used to promote declarative approaches.

    Để hiểu rõ hơn về IaC, mọi người đọc link bên dưới nhé. https://magz.techover.io/2021/08/19/infrastructure-as-code/

    AWS CDK

    • AWS (AWS CDK) là một framework phát triển phần mềm mã nguồn mở để xác định cơ sở hạ tầng đám mây trong mã và cung cấp nó thông qua AWS CloudFormation.

    • Nó cung cấp tính trừu tượng hướng đối tượng mức cao để xác định tài nguyên AWS một cách phân cấp bằng cách sử dụng sức mạnh của các ngôn ngữ lập trình hiện đại. Sử dụng thư viện cấu trúc cơ sở hạ tầng của CDK, bạn có thể dễ dàng đóng gói các phương pháp hay nhất của AWS vào định nghĩa cơ sở hạ tầng của mình và chia sẻ nó mà không cần lo lắng về logic bản soạn sẵn.

    • AWS CDK có hỗ trợ hạng nhất cho TypeScript, JavaScript, Python, Java và C #.

    cdk

    Tại sao nên sử dụng AWS CDK ?

    • AWS CDK cho phép phát triển hạ tầng với code và triển khai nó lên AWS qua CloudFormation. Bạn sẽ nhận được toàn bộ lợi ích của CloudFormation.

    • Sử dụng các ngôn ngữ lập trình, công cụ và quy trình làm việc quen thuộc – AWS CDK cho phép bạn phát triển cơ sở hạ tầng bằng TypeScript, Python, Java và .NET.

    • Triển khai cơ sở hạ tầng và runtime code cùng nhau – AWS CDK cho phép bạn tham chiếu các runtime code của mình trong cùng một dự án với cùng một ngôn ngữ lập trình

    • Sử dụng logic (câu lệnh if, vòng lặp for, v.v.) khi xác định cơ sở hạ tầng của bạn

    • Sử dụng các kỹ thuật hướng đối tượng để tạo mô hình hệ thống của bạn

    • Xác định các nội dung tóm tắt ở cấp độ cao, chia sẻ và xuất bản chúng cho nhóm, công ty hoặc cộng đồng của bạn

    • Tổ chức dự án của bạn thành các mô-đun hợp lý

    • Chia sẻ và sử dụng lại cơ sở hạ tầng của bạn làm thư viện

    • Kiểm tra mã cơ sở hạ tầng của bạn bằng các giao thức tiêu chuẩn ngành

    cdk

    Cấu trúc CDK Code

    Apps

    • Chạy chương trình
    • Render and triển khai cfn templates

    Stacks

    • Đơn vị triển khai
    • Gồm thông tin region và account id

    Constructs

    • Đại diện các tài nguyên AWS
    • Có thể hình thành cấu trúc cây phân cấp

    cdk

    • Chúng là các tệp bao gồm mọi thứ cần thiết để triển khai ứng dụng của bạn lên môi trường đám mây.

    • Đơn vị triển khai trong AWS CDK được gọi là stack. Tất cả các tài nguyên AWS được xác định trong phạm vi của một stack, trực tiếp hoặc gián tiếp, đều được cung cấp như một đơn vị duy nhất. Vì AWS CDK stack được triển khai thông qua AWS CloudFormation stack nên chúng có những hạn chế giống như trong AWS CloudFormation.

    • Construct là các khối xây dựng cơ bản của ứng dụng AWS CDK. Một cấu trúc đại diện cho một "thành phần đám mây" và đóng gói mọi thứ AWS CloudFormation cần để tạo thành phần đó.

    CDK Lifecycle

    • CDK (với sự trợ giúp của AWS CLI) biên dịch App của nó (App sẽ chạy chương trình có chứa các StackConstruct)
    • CDK tổng hợp mã để chuyển nó thành mẫu CloudFormation. Khi bạn chạy cdk, hãy triển khai mã mà chúng tôi đã đặt trong lib / <code_name> .ts được phân tích cú pháp bởi CDK Framework, sau đó tạo một mẫu CloudFormation nằm trong thư mục cdk.out và cuối cùng triển khai nó vào CloudFormation.
    • Mẫu CloudFormation được triển khai trong tài khoản AWS và chạy để tạo tất cả các thành phần AWS

    cdk

    AWS workflow

    cdk

    Đây chỉ là phần giới thiệu về AWS CDK trong chuỗi bài viết về AWS CDK, mình sẽ làm demo ở bài viết sau nhé. Mong mọi người ủng hộ.

    Tham khảo: https://dev.to/aws-builders/everything-about-aws-cdk-489m

  • AWS Certificate Manager là gì ?

    AWS Certificate Manager là gì ?

    Chứng chỉ SSL miễn phí

    Khi nói đến chứng chỉ SSL miễn phí, chắc hẳn bạn sẽ nghĩ ngay đến Let’s Encrypt. Nhưng thật ra thì, AWS Certificate Manager cũng cung cấp chứng chỉ SSL miễn phí. Bản chất ACM là một dịch vụ sẵn có miễn phí, cung cấp chức năng quản lý chứng chỉ SSL. Bạn không cần phải trả thêm gì khác ngoài chi phí cho các tài nguyên AWS mà bạn tạo ra để chạy ứng dụng.

    AWS Certificate Manager

    Với AWS Certificate Manager, bạn có thể nhanh chóng yêu cầu một chứng chỉ và triển khai chứng chỉ đó trên các tài nguyên AWS được tích hợp ACM( chẳng hạn như các dịch vụ Elastic Load Balancer, Amazon CloudFront và API Gateway,…) đồng thời cho phép AWS Certificate Manager xử lý các yêu cầu gia hạn chứng chỉ. Dịch vụ này còn cho phép bạn tạo các chứng chỉ riêng cho các tài nguyên nội bộ trong hệ thống mạng của mình và quản lý vòng đời của chứng chỉ một cách tập trung.

    acm

    Cách cấu hình

    Ở phần này mình sẽ không nói cách cấu hình như nào vì các bạn có thể tham khảo trên tài liệu của AWS đã có hướng dẫn rất đầy đủ. Mình sẽ tập trung nói về các điểm lợi điểm hại của các cách cấu hình.Với ACM chúng ta sẽ có 3 cách cấu hình chính:

    Cách 1: Import certificate

    Cách này chỉ dùng khi chúng ta đăng kí domain từ bên thứ 3 khác không phải là AWS. Với domain mà bạn tự mua thì có thể dễ dàng sẽ lấy được certificate và key chain. Nhưng nếu domain của khách hàng của bạn thì họ sẽ rất khó để cung cấp chúng cho bạn. Đôi khi họ cũng chỉ là nguời mới không lưu trữ cái đó chả hạn. Đặc biệt khi domain hết hạn cần gia hạn lại thì họ cũng phải cung cấp lại các thông tin certificate mới của domain để mình import lại. Nó sẽ làm tốn thêm công sức nhiều. Cho nên cách này mình không khuyến cáo sử dụng.

    case1

    • Certificate body: Certificate issued for a particular domain name
    • Certificate private key: Unencrypted (created without a password) private key.
    • Certificate chain: CA bundle of the issuing Certificate Authority

    Cách cấu hình: https://docs.aws.amazon.com/acm/latest/userguide/import-certificate-api-cli.html

    Cách 2: Request a certificate with email

    Cách này chúng ta sẽ cần điền thông tin email mà mình hoặc khách hàng đã dùng để đăng kí domain từ bên thứ 3 khác. Đôi khi khách hàng họ sẽ quên mất email đăng kí domain là email nào. Hoặc đơn giản họ chỉ là người mới tiếp quản công việc không thể nào biết được. Đặc biệt cách này cũng cần xử lý khi domain hết hạn mà gia hạn lại. Cho nên cách này mình không khuyến cáo sử dụng.

    case1

    Cách cấu hình: https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-request-public.html

    Cách 3: Request a certificate with DNS validation

    Sau khi đã hoàn thành các bước thì chúng ta sẽ cần export DNS từ phần đăng ký ACM. Chúng ta cần thêm thông tin này trên trang quản trị domain. Với cách này sẽ đảm bảo khách hàng dễ xử lý với việc thêm nó và an toàn cho thông tin certificate domain của khách hàng.

    case1

    Ví dụ: Hệ thống khách hàng xây dựng trên AWS, nhưng lại đăng kí domain với Godday. Chúng ta có thể vào GoDaddy thêm 1 record với type là CNAME. Sau đó lấy các thông tin export từ ACM để điền vào. Hoàn thành bước này thì chúng ta đã đảm bảo phần có thể active được ACM. Ngoài ra với cách này chúng ta sẽ không cần lo lắng về việc khi domain hết hạn và gia hạn lại. Mình sẽ không phải làm gì thêm từ phía ACM. Mình khuyến khích nên sử dụng cách này. Nó sẽ vừa dễ dàng cho mình và cho khách có thể xử lý được mà không bị phụ thuộc certificate của domain hoặc email đã đăng kí.

    godady

    Cách cấu hình: https://docs.aws.amazon.com/acm/latest/userguide/dns-validation.html

  • Sử dụng Nginx để truy cập tới một Private S3 Bucket

    Sử dụng Nginx để truy cập tới một Private S3 Bucket

    Bài toán

    Mình được giao nhiệm vụ phát triển một ứng dụng quản lý chi tiêu cho khách hàng. Thông tin các hoá đơn và ảnh trong ứng dụng sẽ được lưu trên S3. Khách hàng có yêu cầu có thể xem được ảnh lưu trên S3 bằng domain của họ thay vì bằng đường link trực tiếp từ S3. Hệ thống Backend của họ đang sử dụng Nginx với Python Flask và mong muốn không sử dụng đến dịch vụ CloudFront.

    Giải pháp

    Sau hồi nguyên cứu mình đã tìm ra giải pháp là có thể dùng Nginx để truy cập tới một private S3 bucket.

    Để có thể demo được giải pháp nên mình sẽ lược bỏ hệ thống cho nó đơn giản hơn so với hệ thống của khách hàng. Đầu tiên giả sử hệ thống trên AWS đã được cấu hình như bên dưới:

    • VPC(10.0.0.0/16) với public subnet và private subnet
    • VPC Endpoint cho s3
    • A static web on ECS Fargate with Nginx
    • 1 trang web tĩnh được triển khai trên dịch vụ ECS Fargate với việc dùng Nginx
    • 1 private S3 bucket để lưu ảnh của trang web.

    Alt Text

    Kết quả sau khi cấu hình xong chúng ta sẽ có 1 trang web như bên dưới. Nhưng hình ảnh mọi nguời thấy là hình ảnh được tải từ trên mạng. Alt Text

    Bây giờ mình sẽ cần thay thể ảnh trên mạng này với ảnh trên S3. Để làm được điều đó các bạn theo dõi tiếp bên dưới nhé. Alt Text

    Thiết lập Nginx và S3

    1. Thay đổi S3 bucket policy để chỉ cho phép lấy dữ liệu từ VPC Endpoint.

    {
        "Version": "2008-10-17",
        "Id": "PolicyForCloudFrontPrivateContent",
        "Statement": [
            {
                "Sid": "Access-to-specific-VPCE-only",
                "Effect": "Allow",
                "Principal": "*",
                "Action": "s3:GetObject",
                "Resource": "arn:aws:s3:::demo-static-s3/*",
                "Condition": {
                    "StringEquals": {
                        "aws:sourceVpce": "vpce-0d92e50f230bc8070"
                    }
                }
            }
        ]
    }
    

    2. Tiếp theo là việc quan trọng nhất, chúng ta sẽ cần cấu hình Nginx để có thể tải ảnh từ private S3

    location ~^/image/(.+)$ {
            resolver 10.0.0.2;
            proxy_pass http://s3-ap-southeast-1.amazonaws.com/demo-static-s3/image/$1; 
    }
    
    • resolver: Địa chỉ IP của DNS server trong VPC mà mình đã tạo.

      Ví dụ, VPC của mình tạo có CIRD là 10.0.0.0/16. AWS sẽ dùng 5 IPs bên dưới cho các mục đích bên của họ và mình sẽ không thể sử dụng các IP đó.

      • 10.0.0.0: Network address.
      • 10.0.0.1: Reserved by AWS for the VPC router.
      • 10.0.0.2: Reserved by AWS. The IP address of the DNS server is the base of the VPC network range plus two. For VPCs with multiple CIDR blocks, the IP address of the DNS server is located in the primary CIDR. We also reserve the base of each subnet range plus two for all CIDR blocks in the VPC. For more information, see Amazon DNS server.
      • 10.0.0.3: Reserved by AWS for future use.
      • 10.0.0.255: Network broadcast address. We do not support broadcast in a VPC, therefore we reserve this address.

      => Resolver sẽ có giá trị là 10.0.0.2

      Tham khảo: https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Subnets.html

    • proxy_pass: s3 link

      Định dạng: http://s3-ap-southeast-1.amazonaws.com/[main_bucket]/[sub_bucket]/$1

    3. Thay đổi dường dẫn của tải ảnh của trang web

    <div class="container">
        <h1>Hey there</h1>
        <img src="https://cleandevs.com/image/668c1d479e27bb8750823655c83a6c9bd90263f9_hq.jpg"/>
      </div>
    

    4. Cuối cùng mình sẽ deploy lại trang web lên dịch vụ ECS Fargate. Kết quả sẽ như hình bên dưới. Trang web đã hiển thị ảnh được tải từ private S3 bucket

    Alt Text

  • Case study ứng dụng serverless trên AWS phục vụ Olympic Tokyo

    Case study ứng dụng serverless trên AWS phục vụ Olympic Tokyo

    Lời nói đầu

    Ở đâu đó có thể các bạn đã nghe thấy khái niệm serverless hay chạy ứng dụng không mà không cần sử dụng một server nào (non-server). Hiện nay với sự phát triển mạnh mẽ của các nền tảng public cloud như AWS, Azure, Alibaba.., khái niệm serverless đang dần trở nên thân thuộc hơn với những lập trình viên. Tuy nhiên bạn đã bao giờ tự tay xây dựng một hệ thống API mà không phải sử dụng server bao giờ chưa? Theo mình thấy thì hiện tại sự trải nghiệm của các dev với serverless thực sự chưa nhiều, một phần có lẽ do người ta vẫn tin tưởng ở server truyền thống hơn(Cái gì sờ thấy được cũng chắc chắn hơn). Ở loạt bài này, mình sẽ trình bày về một dự án team mình xây dựngAPI 100% sử dụng serverless . Mình sẽ tập trung vào kiến trúc hệ thống, giải thích các thành phần và framework hỗ trợ deploy serverless nhé!

    Nội dung

    Bối cảnh

    Khách hàng của mình đã xây dựng hệ thống trên môi trường AWS, sử dụng EC2 làm server, ngôn ngữ là Java và sử dụng framework là Struts . Hệ thống hiện tại chi phí đang quá lớn (Bao gồm cả chi phí AWS cũng như các chi phí liên quan khác), thời gian sử dụng và chạy job trong ngày là không cố định(do nghiệp vụ), nhiều khi không có người sử dụng cũng như không có job nào chạy nhưng cũng phải trả tiền cho 1 server API và 1 server Job. Khách hàng đã yêu cầu chuyển hệ thống cũ sang serverless và phát triển thêm tính năng dựa trên kiến trúc mới này. Hệ thống này mình xây dựng hoàn toàn trên Amazon Web Service, nên các dịch vụ cứ mặc định là của AWS nhé!

    Mô hình hệ thống Serverless

    Có lẽ nhiều người cũng đã nhìn qua kiến trúc serverless như thế này:



    Đúng, nó là 1 kiến trúc chung thường thấy của 1 serverless system triển khai trên AWS. Flow sẽ là:

    • App call API qua API Gateway
    • API Gateway trigger lambda
    • Lambda query data từ DB, trả về kết quả
    • API Gateway response data cho client

    Bla…Bla..

    Tuy nhiên, để ứng dụng nó vào 1 dự án cụ thể cần nhiều hơn thế này, theo dõi phần tiếp theo nhé!

    Hệ thống Serverless trong thực tế

    Mỗi hệ thống sẽ có những điểm giống và khác nhau tuỳ thuộc vào bài toán cần giải quyết. Không loằng ngoằng mình sẽ đưa ra kiến trúc mình đã xây dựng luôn (Đã lược bỏ một số chi tiết, tập trung chính vào phần serverless)

    Overview hệ thống này nhé:

    • Phần màu đỏ là hosting cho Frontend(được viết bằng Angular xxx). Phần FrontEnd sẽ bao gồm một S3 Bucket được setting làm static web, 1 Cloudfront Distribution để cache lại các resource tĩnh GLOBAL.
    • Phần màu xanh là hệ thống API serverless. Chúng ta sẽ quan tâm đến phần này nhiều hơn vì nó là trọng tâm của bài viết này. Nó bao gồm những dịch vụ gì, đi lần lượt nhé:
      • WAF: Web Application Firewall – Đây được coi là bức tường lửa đầu tiên để bảo vệ web. Nhiêm vụ của nó là bảo vệ app qua rule do người dùng thiết lập, ví dụ Whitelist IP, Blacklist IP… Quan trọng hơn là nó có thể phát hiện và chặn những request có dấu hiệu tấn công như XSS, SQL Injection….
      • API Gateway: Điểm nhận tất cả các request từ phía client. AWS cho phép route từng path của request đến những handler tương ứng.
      • Cognito: Dịch vụ này cung cấp phương thức xác thực, phân quyền và quản lý người dùng.
      • Lambda (Authenticate): Vì app của mình có tính năng authen hơi đặc biệt, do vậy mình phải dùng lambda function này để add thêm 1 số feature mà Cognito không đáp ứng đủ. Lambda function này sẽ được đính trực tiếp vào API Gateway, đóng vai trò tương tự như 1 middleware, cũng đặt trong private subnet nhé, nhưng vẽ như thế để tránh rối
      • VPC, Public subnet và private subnet: Cái này nếu ai đã làm qua với AWS và network của nó thì có thể nắm được rồi. Public subnet thì có thể internet facing, private subet là nơi đặt các server EC2, RDS, Lambda là private. Không thể truy cập trực tiếp từ internet vào các dịch vụ được đặt trong private subnet.
      • InternetGateway cho phép VPC có thể truy cập Internet, VPC Endpoint cho phép kết nối đến các dịch vụ khác của AWS mà ko qua đường truyền internet
      • Squid Proxy Server: Đóng vai trò là proxy cho phép các resource từ private subet kết nối ra ngoài Internet(Nhiều người sẽ dùng NAT Gateway hoặc NAT Instance).
      • Lambda (Đặt trong private subnet): Đây chính là linh hồn của Serverless, đóng vai trò tương tự 1 server. Mỗi path của API Gateway sẽ được xử lý bởi 1 lambda function. Lambda sẽ nhận request từ API Gateway, xử lý, trả response về API Gateway -> Response về Client
      • S3: Nếu ko có server thì file được lưu trữ ở đâu, up/down thế nào? Thông thường nếu hệ thống sử dụng autoscale thì cũng cần 1 nơi lưu trữ file chung (EFS hoặc S3 ….). Với Lambda cũng vậy, mình chọn S3 để lưu trữ file. Nhưng làm thế nào để upload và download file qua lambda nhỉ. Câu trả lời là sẽ không up/download file qua lambda, lambda chỉ là trung gian, generate Pre-signed URL để client thực hiện upload và download trực tiếp với S3.
      • DynamoDB: Đây là 1 database dạng NoSQL do AWS phát triển. Lưu data dạng Key-Value. Nếu cần thiết phải sử dụng CSDL quan hệ, mình khuyến khích dùng AWS Aurora serverless(MySQL hoặc PostgreSQL), hỗ trợ tốt nếu sử dụng serverless
      • CloudWatch: Phần này có 1 số dịch vụ nhỏ hơn. Tuy nhiên có 2 service chính là Logs và Rules. Logs là nơi xem, truy vấn log mà Lambda function đã ghi ra trong quá trình chạy, Rules được sử dụng để lập lịch cho 1 số job chạy cố định hàng ngày, khi đến thời gian nó sẽ gọi lambda function tương ứng.
      • SQS: Queue được dùng cho sử dụng cho những job muốn chạy ngay lập tức. SQS trigger đến Lambda function(Job) mỗi khi có message mới được đẩy vào queue.
      • X-Ray: Service này khá hay, nó giúp monitor ứng dụng một cách chi tiết hơn, visualize nó lên trên dashboard AWS, giúp gỡ lỗi ứng dụng, phán đoán lỗi cũng như cải tiến ứng dụng tốt hơn. Ví dụ: Thời gian query data từ DynamoDb, thời gian upload file S3…….
      • SNS: Gửi notification.

    Serverless framework

    Nếu đã từng làm việc với lambda, mọi người sẽ biết được rằng mỗi Lambda function là độc lập với nhau, source code vì vậy cũng hoàn toàn riêng biệt. Vậy với 1 project lớn bao gồm hàng trăm API, làm thế nào chúng ta quản lý source code và deploy, không thể build và upload bản build cho từng lambda function được. Vì vậy team đã quyết định sử dụng framework là serverless(https://www.serverless.com)

    Serverless framework là fw hỗ trợ nhiều cloud provider phổ biến như AWS, Azure, GPC, Alibaba… Nó cung cấp cho chúntg ta 1 công cụ để quản lý full life cycle cho ứng dụng serverless. Serverless framework cũng hỗ trợ nhiều ngôn ngữ như Java, Nodejs, Go, Python …

    Cấu hình serverless:
    Tư tưởng của framework hiểu đơn giản là chúng ta cần mapping Path của API Gateway với class, file xử lý logic cho function tương ứng.

    Đây là 1 file cấu hình sample của project serverless. Một số thành phần quan trọng bao gồm:

    • provider
      • name: Tên cloud provider(aws, gpc, azure)
      • runtime: Môi trường thực thi(java8, java11, nodejs12..)
    • package:
      • artifact: Đường dẫn trỏ đến file build
    • functions: List API của project
      • Tên lambda function:
        • handler: class xử lý logic cho API
        • events:
          • http: (nếu là HTTP thì lambda function này sẽ được trigger từ API Gateway)
            • path: Đường dẫn API
            • method: HTTP method(get, post …)

    Mình chỉ giới thiệu qua cấu hình cơ bản của serverless. Lợi ích của nó là giúp chúng ta dễ dàng quản lý life cycle của project serverless, sử dụng các architype có sẵn khi tạo project.

    Tổng kết lại

    Túm lại, để nói về 1 serverless system thì 1 bài viết là không đủ. Ở phần này mình chỉ overview hệ thống, các dịch vụ và vai trò của nó . Mong rằng qua bài viết này các bạn có thể nắm được cơ bản về kiến trúc 1 hệ thống không sử dụng server truyền thống nó như thế nào, đánh giá xem có thể apply trực tiếp vào dự án tiếp theo được không. Mong rằng sẽ có nhiều hơn dự án sử dụng serverless trong đơn vị để mọi người có thêm những trải nghiệm mới.

  • Cost-effective, High Availability Cassandra with AWS EKS and EC2 Spot instance.

    Cost-effective, High Availability Cassandra with AWS EKS and EC2 Spot instance.

    Mở đầu

    Cassandra hay Apache Cassandra, là một hệ thống quản lý cơ sở dữ liệu NoSQL, mã nguồn mở, miễn phí, phân tán dựa trên mạng ngang hàng P2P, hiện tại thường dùng dễ lưu trữ dữ liệu dưới dạng timeseries.

    Bản thân Cassandra đã có khả năng High availability với thiết kế no single point of failure và bản thân Cassandra cũng hỗ trợ việc mở rộng node một cách dễ dàng, vậy tại sao không mang sức mạnh của EC2 Spot Instance (chi phí rẻ cho khả năng tính toán lớn).

    Chúng ta lợi dụng một số tính năng sau của Cassandra để xử lý:

    • Data Center

      image

      • Trong đó data center sẽ đóng vai trò như một cụm node, Cassandra có thể live backups giữa các data center, data sẽ tự động copy async sang DC khác, khi một DC down các DC khác vẫn hoạt động bình thường
    • Seed nodes: Seed nodes sẽ là nơi các node mới connect và thông báo về việc chúng join cluster Seed node hoạt động như các điểm chung chuyển, các node sẽ trao đổi với các node seeds hơn các node khác, và các node này thường sẽ có các thông tin mới nhất và đầy đủ nhất, nhưng chúng sẽ gặp vấn đề overhead nên đừng sử dụng mọi node làm seeds.

    • Data replication: Cassandra lưu trữ dữ liệu trên nhiều node để đảm bảo tính toàn vẹn và fault tolerance (mình khá không thích dịch tiếng việt tự này, có thể dịch là khả năng chịu lỗi). Có 2 strategy: Simple và NetworkTopology, vì chúng ta dự định sử dụng data center nên hãy chọn NetworkTopology

    Như vậy về mặt lý thuyết chúng ta có thể xử lý được toàn bộ vấn đề, hãy mapping chúng với, K8S và AWS thậm chí hoàn toàn có thể xử lý được với trường hợp sử dụng Spot Instance.

    image

    Chúng ta sẽ sử dụng luôn khái niệm Availability Zone của AWS cho tương ứng với data center. Như vậy sẽ có 1 Statefulset cài đặt Cassandra, 1 Service để expose với mỗi DC.

    Cài đặt nào

    Thực ra script đã được chuẩn bị ở đây rồi.

    Mình sẽ giải thích một vài điểm cần chú ý

    Chúng ta add label cho các pod, việc này để các service có thể chọn được các pod của cassandra

      template:
        metadata:
          labels:
            app: cassandra
            interface: cassandraa
    

    Chọn node để cài đặt cassandra, chúng ta có thể dùng các key khác nhưng để cho tiện thì mình dùng tạm key này, việc này đảm bảo node của DC được cài đặt theo AZ của AWS đúng tinh thần High availability

          affinity:
            nodeAffinity:
              requiredDuringSchedulingIgnoredDuringExecution:
                nodeSelectorTerms:
                - matchExpressions:
                  - key: topology.kubernetes.io/zone
                    operator: In
                    values:
                    - ap-southeast-1a
    

    Cassandra seeds node, node đầu tiên của các statefulset được chọn làm seed, ở đây mình xử lý dùng 1 service cassandra thay vì dùng 3 service cho 3 AZ (một điểm nho nhỏ khác biệt), việc này không ảnh hưởng lắm.

      - name: CASSANDRA_SEEDS
        value: cassandraa-0.cassandra.thingsboard.svc.cluster.local,cassandrab-0.cassandra.thingsboard.svc.cluster.local,cassandrac-0.cassandra.thingsboard.svc.cluster.local
    

    Chúng ta không cần tất cả các seed cùng một lúc, nên ngay cả khi seed down thì node vẫn hoạt động bình thường.

    Ở các pod sử dụng cassandra này thì cần chỉ rõ DC nào của Cassandra để kết nối đến, ở đây mình đang cài đặt Thingsboard nên sẽ thêm environment variable sau, tất nhiên sẽ phải xử lý tách application của bạn ra 3 statefulset hoặc deployment khác nhau:

      - name: CASSANDRA_LOCAL_DATACENTER
        value: ap-southeast-1a
    

    Tada

    2021-08-17 18:08:36
    2021-08-17 11:08:36,628 [main] INFO  o.s.o.j.LocalContainerEntityManagerFactoryBean - Initialized JPA EntityManagerFactory for persistence unit 'default'
    2021-08-17 18:08:52
    2021-08-17 11:08:52,732 [main] INFO  c.d.o.d.internal.core.ContactPoints - Contact point cassandra:9042 resolves to multiple addresses, will use them all ([cassandra/10.0.1.112, cassandra/10.0.2.149, cassandra/10.0.1.150])
    2021-08-17 18:08:53
    2021-08-17 11:08:53,734 [main] INFO  c.d.o.d.i.c.DefaultMavenCoordinates - DataStax Java driver for Apache Cassandra(R) (com.datastax.oss:java-driver-core) version 4.10.0
    2021-08-17 18:08:54
    2021-08-17 11:08:54,956 [Thingsboard Cluster-admin-0] INFO  c.d.o.d.internal.core.time.Clock - Could not access native clock (see debug logs for details), falling back to Java system clock
    2021-08-17 18:08:56
    2021-08-17 11:08:56,621 [Thingsboard Cluster-admin-0] WARN  c.d.o.d.i.c.l.h.OptionalLocalDcHelper - [Thingsboard Cluster|default] You specified ap-southeast-1b as the local DC, but some contact points are from a different DC: Node(endPoint=cassandra/10.0.1.112:9042, hostId=a56560a6-1274-43f9-b72e-d8b1e7b33bf8, hashCode=6a745db0)=ap-southeast-1a, Node(endPoint=cassandra/10.0.1.150:9042, hostId=c7f9bc1c-c066-40d6-9def-7fbe58af90bb, hashCode=2c4f4fec)=ap-southeast-1a; please provide the correct local DC, or check your contact points
    

    Nếu dòng log cuối gây confuse thì hãy sử dụng các sevice riêng biệt nhé

    Happy Coding