Bạn chắc chắn đã nghe qua từ “Cloud-Native” và “Microservices” trước đây, nhưng bạn có thực sự hiểu về các phương pháp tốt nhất khi cấu hình hoặc di chuyển ứng dụng từ kiến trúc Monolithic sang Microservices không?

Hiện nay, microservices là thuật ngữ rất phổ biến đối với bất kỳ nhà phát triển nào đang xây dựng ứng dụng, hoặc kỹ sư Cloud/DevOps/SRE quản lý hoặc triển khai ứng dụng.

“Các kỹ thuật này cho phép các hệ thống loose-coupling, Fault tolerance, dễ quản lý và dễ quan sát. Kết hợp với tự động hóa mạnh mẽ, chúng cho phép kỹ sư thực hiện các thay đổi có ảnh hưởng cao thường xuyên và dễ dàng dự đoán với ít công việc cần làm.”

Kiến trúc microservices đã cách mạng hóa cách chúng ta xây dựng và triển khai các ứng dụng hiện đại. Nó mang lại những lợi ích như khả năng mở rộng, tính chịu lỗi và linh hoạt. Tuy nhiên, để tận dụng hết tiềm năng của microservices, cần tuân thủ các phương pháp tốt đã được chứng minh. Một bộ nguyên tắc mà nhận được sự công nhận rộng rãi chính là 12 Nguyên tắc Factor.

Các Nguyên tắc 12 Factors là một bộ nguyên tắc tốt nhất cho việc xây dựng các ứng dụng cloud-native dựa trên microservices. Các ứng dụng này có tính module, có khả năng mở rộng và linh hoạt. Chúng được thiết kế để hoạt động ở quy mô web và cung cấp tính chịu lỗi cao. Các nguyên tắc này được áp dụng cho các ứng dụng cloud-native và không phụ thuộc vào ngôn ngữ lập trình hoặc nền tảng cụ thể. 12 Factors đã được công bố vào năm 2012 bởi Adam Wiggins, người sáng lập Heroku. Phương pháp này trình bày một quá trình tư duy về cách phát triển ứng dụng và bổ sung cho các phương pháp và quy trình tư duy khác như reactive manifesto.

Các 12 nguyên tắc bao gồm:

  1. Codebase – Một codebase được theo dõi trong hệ thống quản lý phiên bản, nhiều phiên bản triển khai.
  2. Dependencies – Tuyên bố rõ và cô lập các dependency.
  3. Configuration – Lưu trữ cấu hình trong môi trường.
  4. Backing Services – Xem xử lý backing services như là tài nguyên đính kèm.
  5. Build, release, run – Tách biệt nghiêm ngặt giai đoạn build và chạy.
  6. Processes – Thực thi ứng dụng dưới dạng một hoặc nhiều quy trình không lưu trạng thái.
  7. Port binding – Xuất dịch vụ qua việc ràng buộc cổng.
  8. Concurrency – Mở rộng qua mô hình quy trình.
  9. Disposability – Tối đa hóa tính ổn định với khởi động nhanh và tắt dễ dàng.
  10. Dev/prod parity – Giữ sự tương đồng giữa môi trường phát triển, sân khấu và sản xuất càng giống nhau càng tốt.
  11. Logs – Xem xử lý logs như là luồng sự kiện.
  12. Admin processes – Chạy tác vụ quản trị/ quản lý như các quy trình một lần.

Codebase

A twelve-factor app is always tracked in a version control system… A codebase is any single repo (in a centralized revision control system like Subversion), or any set of repos who share a root commit (in a decentralized revision control system like Git).

Nguyên tắc này ủng hộ việc sử dụng một codebase duy nhất được theo dõi trong hệ thống quản lý phiên bản với nhiều triển khai trên nhiều môi trường khác nhau. Mỗi microservice chỉ có thể có một codebase. Codebase phải được quản lý bởi hệ thống quản lý phiên bản. Các triển khai khác nhau được tạo ra từ codebase này, mỗi cái cho một môi trường khác nhau như phát triển, sân khấu, sản xuất và có thể là các môi trường khác. Dù có vẻ quá đơn giản và không có ý nghĩa nhưng nếu suy nghĩ về thế giới của SOA và microservices, chúng ta cần suy nghĩ kỹ lưỡng về chiến lược quản lý phiên bản của mình. Mỗi dịch vụ nên được duy trì như là codebase riêng của mình và phải được quản lý phiên bản độc lập.

Mỗi nhà phát triển sao chép bản sao của codebase để thực hiện thay đổi hoặc chạy cục bộ. Nền tảng sẽ kéo mã nguồn từ một kho lưu trữ duy nhất và sử dụng mã này để xây dựng một đơn vị triển khai duy nhất. Các nhánh hoặc tags cụ thể được sử dụng dựa trên chiến lược nhánh và phát hành. Tuy nhiên, không có một chiến lược codebase phù hợp cho tất cả và các nhóm thường chuyển từ Mono repo sang Multi repos dựa trên các yếu tố khác nhau. Mono Repo là nơi tất cả Microservices được đặt trong một kho lưu trữ duy nhất. Trong multi repo, mỗi codebase microservice được theo dõi trong kho lưu trữ độc lập của nó.

Dependencies – Những phụ thuộc

“A twelve-factor app never relies on implicit existence of system-wide packages.”

Các phụ thuộc Các phụ thuộc được sử dụng bởi một dự án và các phiên bản của chúng phải được tuyên bố rõ ràng và cách ly khỏi mã nguồn. Việc tường minh phiên bản dẫn đến việc giảm vấn đề tương thích trên các môi trường. Điều này cũng dẫn đến khả năng tái tạo vấn đề xảy ra trong các kết hợp phiên bản cụ thể tốt hơn. Các phụ thuộc không chỉ bao gồm các gói mà còn bao gồm các nền tảng, SDK vv. Các phụ thuộc gói có thể được quản lý bằng các công cụ quản lý gói như Nuget, NPM vv. Công nghệ container đơn giản hóa việc này hơn bằng cách tuyên bố rõ ràng tất cả các bước cài đặt. Nó cũng chỉ định các phiên bản của hình ảnh cơ bản, để việc xây dựng hình ảnh là idempotent. .Net core cung cấp file dự án như một container để tuyên bố tất cả các phụ thuộc. Nó sử dụng nuget làm trình quản lý gói để tải về các gói cần thiết. Dưới đây là một ví dụ về một tập tin dự án dotnet tuyên bố tất cả các phụ thuộc cần thiết.

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.2" />
    <PackageReference Include="RabbitMQ.Client" Version="6.2.4" />
    <PackageReference Include="AutoMapper" Version="6.1.1" />
    <PackageReference Include="xunit" Version="2.2.0" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
  </ItemGroup>

</Project>

Trong thế giới Java, Maven sử dụng tập tin pom.xml để xác định thông tin thư viện, framework và các phụ thuộc khác. Dưới đây là một đoạn trích từ tập tin pom xác định các phụ thuộc cần thiết cho dự án.

<dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-core</artifactId>
      <version>3.6.3.Final</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate</artifactId>
      <version>3.2.5.ga</version>
    </dependency>
    <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>5.1.14</version>
      </dependency>
</dependencies>

Application configuration

“Apps sometimes store config as constants in the code. This is a violation of twelve-factor, which requires strict separation of config from code. Config varies substantially across deploys, code does not.”

Cấu hình ứng dụng, khác nhau qua các môi trường như phụ thuộc bên ngoài, cơ sở dữ liệu, thông tin xác thực, cổng và cổng khác chỉ được thể hiện khi chạy. Cấu hình này không nên được mã hóa cứng trong mã nguồn mà nên được đưa ra bên ngoài và có thể thay đổi động từ bên ngoài ứng dụng. Không nên có thông tin xác thực được mã hóa cứng và không có cấu hình nào trong mã nguồn. Điều này đảm bảo rằng ứng dụng không bị sửa đổi để cập nhật cấu hình trước khi triển khai qua các môi trường và hoàn toàn không phụ thuộc vào môi trường. Điều này cũng đảm bảo rằng thông tin nhạy cảm không bị trộn lẫn với mã nguồn. Việc sử dụng biến môi trường có thể được tiêm khi triển khai ứng dụng trong môi trường cụ thể được khuyến khích cao. Điều này đảm bảo rằng nhà phát triển có thể tập trung vào mã nguồn mà có đảm bảo rằng cấu hình và thông tin xác thực cần thiết có sẵn một cách nhất quán trên tất cả các môi trường. Ngoài biến môi trường, các công cụ như Vault cho phép lưu trữ cấu hình một cách an toàn qua các môi trường.

Backing Services – Các dịch vụ hậu cần

“The code for a twelve-factor app makes no distinction between local and third-party services…Each distinct backing service is a resource.”

Các cơ sở dữ liệu, API và các hệ thống bên ngoài khác được truy cập từ ứng dụng được gọi là tài nguyên. Ứng dụng nên được tách rời khỏi các tài nguyên bên ngoài của nó. Những tài nguyên này nên được kết nối với ứng dụng một cách lỏng lẻo. Dịch vụ hậu cần nên được trừu tượng hóa thành các thành phần cá thể với giao diện sạch sẽ. Chúng nên có thể thay thế bằng các phiên bản khác mà không ảnh hưởng đến ứng dụng sử dụng nguyên tắc cấu hình ứng dụng như đã nói ở trên. Việc triển khai chung cho phép các dịch vụ hậu cần được kết nối và tách ra theo ý muốn. Quản trị viên nên có khả năng kết nối hoặc tách các dịch vụ hậu cần này để nhanh chóng thay thế các dịch vụ gặp sự cố mà không cần phải thay đổi mã hoặc triển khai. Các mẫu khác như bộ ngắt mạch, thử lại và dự phòng cũng được khuyến nghị khi sử dụng các dịch vụ hậu cần này.

Build, Release, Run

“The code for a twelve-factor app makes no distinction between local and third-party services…Each distinct backing service is a resource.”

Nguyên tắc này chặt chẽ liên kết với các nguyên tắc trước đó. Một mã nguồn duy nhất được đưa qua quá trình xây dựng để tạo ra một sản phẩm đã được biên dịch. Kết quả của giai đoạn xây dựng được kết hợp với thông tin cấu hình cụ thể cho môi trường để tạo ra một sản phẩm không thể thay đổi khác, một phiên bản phát hành. Mỗi phiên bản phát hành được gắn nhãn một cách duy nhất. Phiên bản phát hành không thể thay đổi này sau đó được chuyển đến một môi trường (phát triển, chạy thử, sản xuất, vv.) và chạy. Nếu có vấn đề xảy ra, điều này giúp chúng ta có khả năng kiểm tra phiên bản cụ thể và quay trở lại một phiên bản đã hoạt động trước đó. Tất cả các bước này nên được thực hiện bởi các công cụ CI/CD được cung cấp bởi nền tảng. Điều này đảm bảo rằng các giai đoạn xây dựng, phát hành và chạy được thực hiện một cách nhất quán trên tất cả môi trường.

Processes

“Twelve-factor processes are stateless and share-nothing.”

Tất cả các Processes và thành phần của ứng dụng phải là không lưu trữ trạng thái và không chia sẻ gì với nhau. Một ứng dụng có thể tạo và tiêu thụ trạng thái tạm thời khi xử lý yêu cầu hoặc xử lý giao dịch, nhưng trạng thái đó nên biến mất khi khách hàng đã nhận được phản hồi. Tất cả trạng thái kéo dài phải được đặt bên ngoài ứng dụng và được cung cấp bởi các dịch vụ hậu cần. Các Processes xuất hiện và biến mất, mở rộng theo chiều ngang và theo chiều dọc, và có thể bị loại bỏ một cách dễ dàng. Điều này có nghĩa là bất cứ điều gì được chia sẻ giữa các Processes cũng có thể biến mất, tiềm ẩn nguy cơ gây ra sự cố lan rộng. Nguyên tắc này là chìa khóa cho các đặc tính như khả năng chống lỗi, độ bền, khả năng mở rộng và sẵn có.

Port Binding

“The twelve-factor app is completely self-contained and does not rely on runtime injection of a webserver into the execution environment to create a web-facing service.”

Một ứng dụng theo 12 nguyên tắc là hoàn toàn tự chứa và không phụ thuộc vào bất kỳ runtime nào như máy chủ ứng dụng, máy chủ web vv. để sẵn có như một dịch vụ. Nó là tự chứa và tiết lộ chức năng của mình qua một giao thức phù hợp nhất như HTTP, MQTT, AMQP vv. Một ứng dụng theo 12 nguyên tắc phải xuất dịch vụ bằng cách port-binding, có nghĩa là ứng dụng cũng tương tác với thế giới thông qua một điểm cuối. Việc port-binding có thể được xuất và cấu hình bằng nguyên tắc cấu hình đã nêu ở trên. Một ứng dụng sử dụng HTTP như giao thức có thể chạy như http://localhost:5001 trên máy làm việc của một nhà phát triển và trong QA nó có thể chạy như http://164.132.1.10:5000, và trong sản xuất như http://service.company.com. Một ứng dụng được phát triển với việc port-binding này hỗ trợ việc port-binding cụ thể cho mỗi môi trường mà không cần phải thay đổi bất kỳ mã nào.

Concurrency – Xử lý đồng thời

“In the twelve-factor app, processes are a first class citizen…The process model truly shines when it comes time to scale out.”

Ứng dụng nên mở rộng bằng cách sử dụng mô hình quy trình. Khả năng mở rộng linh hoạt có thể được đạt được bằng cách mở rộng theo chiều ngang. Các quy tắc có thể được thiết lập để tự động mở rộng số lượng phiên bản của ứng dụng/dịch vụ dựa trên tải hoặc thông số đo runtime khác. Các quy trình không lưu trữ trạng thái, không chia sẻ gì được đặt ở vị trí lý tưởng để tận dụng đầy đủ khả năng mở rộng theo chiều ngang và chạy nhiều phiên bản đồng thời.

Disposability

“The twelve-factor app’s processes are disposable, meaning they can be started or stopped at a moment’s notice.”

Các quy trình được tạo ra và kết thúc liên tục theo yêu cầu. Các quy trình của một ứng dụng nên là dễ xử lý và cho phép nó được khởi động hoặc dừng nhanh chóng. Một ứng dụng không thể mở rộng, triển khai, phát hành hoặc khôi phục nhanh chóng nếu nó không thể khởi động nhanh chóng và tắt một cách trơn tru. Việc tắt một cách trơn tru ngụ ý việc lưu trạng thái nếu cần thiết và giải phóng tài nguyên máy tính đã được cấp phát. Điều này là yêu cầu chính do tính tạm thời của các ứng dụng cloud native.

Dev/Prod Parity

“The twelve-factor app is designed for continuous deployment by keeping the gap between development and production small.”

Tất cả các môi trường nên được duy trì sao cho giống nhau càng nhiều càng tốt. Điều này đảm bảo rằng bất kỳ vấn đề cụ thể của môi trường nào cũng được xác định càng sớm càng tốt.

Logs

” A twelve-factor app never concerns itself with routing or storage of its output stream.”

Log nên được xem xét như là chuỗi sự kiện. Log là một chuỗi các sự kiện được phát ra từ một ứng dụng theo thứ tự thời gian. Một ứng dụng cloud-native ghi tất cả mục nhập Log của mình vào stdout và stderr. Bạn có thể sử dụng các công cụ như ELK stack (ElasticSearch, Logstash, và Kibana), Splunk vv để bắt và phân tích các phát thải Log của bạn. Ứng dụng nên được tách rời khỏi việc lưu trữ, xử lý và phân tích Log. Log có thể được điều hướng đến bất kỳ đâu. Ví dụ, chúng có thể được điều hướng đến một cơ sở dữ liệu NoSQL, đến một dịch vụ khác, đến một tệp trong kho lưu trữ, đến một hệ thống chỉ mục và phân tích nhật ký, hoặc đến một hệ thống lưu trữ dữ liệu.

Admin Processes

“Run admin/management tasks as one-off processes.”

Các nhiệm vụ bảo trì, như thực thi tập lệnh cho di cư dữ liệu, khởi tạo dữ liệu ban đầu và làm nóng bộ nhớ đệm nên được tự động hóa và thực hiện đúng thời hạn. Chúng được thực thi trong môi trường chạy và nên được gói với bản phát hành cho mã nguồn và cấu hình cụ thể. Điều này đảm bảo rằng các nhiệm vụ bảo trì được thực hiện trên cùng một môi trường mà ứng dụng đang chạy. Nguyên tắc này là chìa khóa cho khả năng gói ứng dụng với các nhiệm vụ bảo trì.

Kết luận

Các nguyên tắc trên là chìa khóa cho việc thiết kế một ứng dụng theo 12 nguyên tắc. Những nguyên tắc này cũng là chìa khóa cho việc thiết kế một kiến trúc dịch vụ micro scalable, chịu lỗi và mạnh mẽ.