Thiết kế kiến trúc ứng dụng tối ưu cho Kubernetes (Chuẩn Cloud-native)

Thiết kế kiến trúc ứng dụng tối ưu cho Kubernetes (Chuẩn Cloud-native)

Thiết kế và vận hành ứng dụng với mục tiêu mở rộng, di động và độ bền vững luôn là một thách thức, nhất là khi hệ thống ngày càng trở nên phức tạp. Kiến trúc của một ứng dụng hay hệ thống không chỉ quyết định cách thức vận hành mà còn định hình kỳ vọng từ môi trường chạy và mức độ tích hợp với các thành phần liên quan. Việc áp dụng các mẫu thiết kế cụ thể ngay từ giai đoạn lên ý tưởng và thực hiện các quy trình vận hành phù hợp sẽ giúp khắc phục những vấn đề phổ biến khi triển khai ứng dụng trong môi trường phân tán quy mô lớn.

Các công nghệ như Docker và Kubernetes đã trở thành công cụ then chốt, giúp các nhóm phát triển đóng gói phần mềm, phân phối, triển khai và mở rộng trên hạ tầng máy tính phân tán một cách hiệu quả. Nắm vững và tối ưu hóa sức mạnh của những công cụ này sẽ giúp bạn quản lý ứng dụng một cách linh hoạt, kiểm soát và phản ứng nhanh hơn với các thay đổi của hệ thống.

Sau đây, chúng ta sẽ cùng khám phá một số nguyên tắc và mẫu thiết kế quan trọng để mở rộng và quản lý khối lượng công việc trên Kubernetes. Mặc dù Kubernetes hỗ trợ chạy nhiều loại khối lượng công việc, nhưng sự lựa chọn phù hợp sẽ ảnh hưởng lớn đến tính dễ vận hành và khả năng sẵn sàng của hệ thống.

Thiết kế cho khả năng mở rộng của ứng dụng

Khi sản xuất phần mềm, nhiều yêu cầu ảnh hưởng đến mẫu thiết kế và kiến trúc mà bạn lựa chọn áp dụng. Với Kubernetes, một trong những yếu tố quan trọng nhất là khả năng mở rộng theo chiều ngang, tức là tăng số lượng bản sao giống hệt nhau của ứng dụng chạy song song để phân phối tải và tăng độ sẵn sàng. Đây là một lựa chọn thay thế cho việc mở rộng theo chiều dọc, thường liên quan đến việc tăng cường năng lực của một ngăn xếp ứng dụng đơn lẻ.

Đặc biệt, kiến trúc microservices là một mẫu thiết kế phần mềm hoạt động hiệu quả cho việc triển khai mở rộng trên các cụm máy chủ. Các nhà phát triển tạo ra những ứng dụng nhỏ, có thể kết hợp được và giao tiếp qua mạng thông qua các API được định nghĩa rõ ràng thay vì những chương trình khối lớn giao tiếp qua các cơ chế nội bộ. Việc tái cấu trúc các ứng dụng monolithic thành các thành phần riêng biệt phục vụ một mục đích giúp mở rộng từng chức năng một cách độc lập. Phần lớn sự phức tạp và chi phí phát sinh vốn thường tồn tại ở cấp độ ứng dụng được chuyển giao cho lĩnh vực vận hành, nơi có thể được quản lý bởi các nền tảng như Kubernetes.

Ngoài những mẫu thiết kế phần mềm cụ thể, các ứng dụng cloud native được xây dựng với một số cân nhắc bổ sung. Ứng dụng cloud native thường tuân theo kiến trúc microservices với khả năng chống chịu, khả năng quan sát và các tính năng quản trị tích hợp, nhằm tận dụng tối đa các nền tảng điện toán đám mây.

Ví dụ, các ứng dụng cloud native được xây dựng kèm theo các chỉ số báo cáo tình trạng sức khỏe nhằm cho phép nền tảng quản lý các sự kiện vòng đời nếu một instance trở nên không khỏe mạnh. Chúng tạo ra (và cung cấp để xuất khẩu) dữ liệu telemetry mạnh mẽ nhằm cảnh báo cho các nhà quản trị về các vấn đề và cho phép họ đưa ra quyết định thông minh. Ứng dụng được thiết kế để xử lý các lần khởi động lại và thất bại thường xuyên, thay đổi khả năng truy cập backend và tải cao mà không làm hỏng dữ liệu hay trở nên không phản hồi.

Tuân thủ triết lý 12 yếu tố của ứng dụng

Một phương pháp phổ biến có thể giúp bạn tập trung vào những đặc điểm quan trọng nhất khi tạo ra các ứng dụng web sẵn sàng cho điện toán đám mây là triết lý 12 Factor App. Ban đầu được viết ra nhằm giúp các nhà phát triển và đội ngũ vận hành hiểu rõ những phẩm chất cốt lõi chung của các dịch vụ web được thiết kế để chạy trên đám mây, các nguyên tắc này cũng rất phù hợp với phần mềm sẽ chạy trong môi trường cụm như Kubernetes. Mặc dù các ứng dụng monolithic có thể hưởng lợi từ việc tuân thủ những khuyến nghị này, kiến trúc microservices được thiết kế dựa trên các nguyên tắc này lại hoạt động đặc biệt hiệu quả.

Tóm tắt nhanh 12 yếu tố như sau:

  1. Mã nguồn: Quản lý toàn bộ mã nguồn trong hệ thống kiểm soát phiên bản (như Git hoặc Mercurial). Mã nguồn quyết định toàn diện những gì được triển khai.
  2. Phụ thuộc: Các phụ thuộc nên được quản lý hoàn toàn và rõ ràng bởi mã nguồn, hoặc được lưu trữ cùng với mã (vendored) hoặc được khóa phiên bản theo định dạng mà trình quản lý gói có thể cài đặt.
  3. Cấu hình: Tách các tham số cấu hình ra khỏi ứng dụng và xác định chúng trong môi trường triển khai thay vì nhúng trực tiếp vào ứng dụng.
  4. Dịch vụ hỗ trợ: Các dịch vụ cục bộ và từ xa đều được trừu tượng hóa thành các nguồn tài nguyên có thể truy cập qua mạng với thông tin kết nối được thiết lập trong cấu hình.
  5. Xây dựng, phát hành, chạy: Giai đoạn xây dựng của ứng dụng phải hoàn toàn tách biệt với quá trình phát hành và vận hành. Giai đoạn xây dựng tạo ra một artifact triển khai từ mã nguồn, giai đoạn phát hành kết hợp artifact và cấu hình, và giai đoạn chạy thực thi bản phát hành.
  6. Tiến trình: Ứng dụng được cài đặt dưới dạng các tiến trình không nên dựa vào lưu trữ trạng thái cục bộ. Trạng thái cần được chuyển giao cho các dịch vụ hỗ trợ như đã mô tả ở yếu tố thứ tư.
  7. Ràng buộc cổng: Ứng dụng nên tự động liên kết với một cổng và lắng nghe các kết nối. Việc định tuyến và chuyển tiếp yêu cầu nên được xử lý bên ngoài.
  8. Đồng thời: Ứng dụng nên dựa vào mô hình tiến trình để mở rộng. Việc chạy nhiều bản sao của một ứng dụng cùng lúc, có thể trên nhiều máy chủ, cho phép mở rộng mà không cần điều chỉnh mã ứng dụng.
  9. Khả năng loại bỏ: Các tiến trình cần có khả năng khởi động nhanh và dừng một cách nhẹ nhàng mà không gây ra hậu quả nghiêm trọng.
  10. Đồng nhất môi trường phát triển/sản xuất: Môi trường thử nghiệm, staging và sản xuất của bạn nên tương đồng và được đồng bộ chặt chẽ. Sự khác biệt giữa các môi trường có thể tạo điều kiện cho các điểm không tương thích và cấu hình chưa được kiểm tra.
  11. Ghi log: Ứng dụng nên truyền trực tiếp log ra standard output để các dịch vụ bên ngoài có thể quyết định cách xử lý tốt nhất.
  12. Tiến trình quản trị: Các tiến trình quản trị dùng một lần nên được chạy đối với những bản phát hành cụ thể và được đóng gói cùng với mã của tiến trình chính.

Bằng cách tuân theo các nguyên tắc của 12 yếu tố, bạn có thể tạo và vận hành các ứng dụng phù hợp với Kubernetes. Các nguyên tắc này khuyến khích các nhà phát triển tập trung vào mục đích chính của ứng dụng, cân nhắc các điều kiện hoạt động và giao diện giữa các thành phần, cũng như sử dụng đầu vào, đầu ra và các tính năng quản lý tiến trình tiêu chuẩn để vận hành một cách dự đoán được trên Kubernetes.

Đóng gói các thành phần ứng dụng vào container

Kubernetes sử dụng container để chạy các ứng dụng được đóng gói và cách ly trên các node của cụm.
Để chạy trên Kubernetes, ứng dụng của bạn phải được bao gói trong một hoặc nhiều image container và thực thi bằng một runtime container như Docker. Mặc dù việc container hóa các thành phần là yêu cầu bắt buộc với Kubernetes, nó cũng giúp củng cố nhiều nguyên tắc từ phương pháp 12 Factor App đã nêu ở trên, cho phép mở rộng và quản lý tốt hơn.

Ví dụ, container cung cấp sự cách ly giữa môi trường ứng dụng và hệ thống chủ bên ngoài. Chúng hỗ trợ mô hình giao tiếp qua mạng giữa các ứng dụng, thường nhận cấu hình thông qua biến môi trường và xuất log được ghi ra stdout và stderr. Các container khuyến khích mô hình tiến trình và giúp duy trì sự đồng nhất giữa môi trường phát triển và sản xuất bằng cách có thể mở rộng độc lập và đóng gói sẵn môi trường runtime của tiến trình. Những đặc tính này giúp bạn đóng gói ứng dụng sao cho chạy trơn tru trên Kubernetes.

Hướng dẫn tối ưu hóa container

Sự linh hoạt của công nghệ container cho phép có nhiều cách khác nhau để đóng gói một ứng dụng. Tuy nhiên, một số phương pháp hoạt động tốt hơn trong môi trường Kubernetes so với những cách khác. Phần lớn các best practices về container hóa ứng dụng liên quan đến việc xây dựng image, nơi bạn định nghĩa cách phần mềm của mình sẽ được thiết lập và chạy bên trong container. Nói chung, việc giữ cho kích thước image nhỏ gọn và đơn giản mang lại nhiều lợi ích.

Các image được tối ưu kích thước có thể giảm thời gian và tài nguyên cần thiết để khởi động một container mới trên cụm bằng cách tái sử dụng các lớp đã có sẵn giữa các lần cập nhật image, điều mà Docker và các framework container khác đã được thiết kế để làm tự động.

Một bước đầu tiên tốt khi tạo image container là cố gắng tách biệt các bước xây dựng (build) khỏi image cuối cùng sẽ được chạy trong môi trường sản xuất. Việc biên dịch hoặc đóng gói phần mềm thường đòi hỏi công cụ hỗ trợ thêm, tốn thời gian và tạo ra các artifact (ví dụ như các phụ thuộc đa nền tảng) có thể không đồng nhất giữa các container hoặc không cần thiết cho môi trường runtime cuối cùng. Một cách để tách biệt quá trình xây dựng ra khỏi môi trường runtime một cách rõ ràng là sử dụng Docker multi-stage builds. Cấu hình build đa giai đoạn cho phép bạn chỉ định một image làm nền tảng trong quá trình build và xác định một image khác để sử dụng khi chạy. Điều này cho phép xây dựng phần mềm bằng image có đầy đủ công cụ build và sau đó sao chép các artifact tạo ra sang một image gọn nhẹ, tối ưu cho lần chạy sau đó.

Với chức năng này, thường là ý tưởng tốt khi xây dựng image sản xuất dựa trên một image parent tối giản. Nếu bạn muốn hoàn toàn tránh sự thừa thãi có trong các lớp parent kiểu “distro” như ubuntu:20.04 (bao gồm toàn bộ môi trường máy chủ Ubuntu 20.04), bạn có thể xây dựng image với scratch — image parent tối thiểu nhất của Docker. Tuy nhiên, layer scratch không cung cấp quyền truy cập tới nhiều công cụ cốt lõi và thường sẽ làm hỏng một số giả định cơ bản về môi trường Linux.
Lựa chọn thay thế, image Alpine Linux (alpine) đã trở nên phổ biến nhờ cung cấp một môi trường Linux tối giản nhưng đầy đủ tính năng.

Đối với các ngôn ngữ thông dịch như Python hay Ruby, mô hình thay đổi nhẹ vì không có giai đoạn biên dịch và interpreter phải có sẵn để chạy mã trong môi trường sản xuất.

Tuy nhiên, do image slim vẫn là lựa chọn lý tưởng, nên có nhiều image tối ưu cho từng ngôn ngữ, được xây dựng trên nền tảng Alpine Linux, hiện có sẵn trên Docker Hub. Lợi ích của việc sử dụng image nhỏ đối với các ngôn ngữ thông dịch tương tự như đối với các ngôn ngữ được biên dịch: Kubernetes có thể nhanh chóng tải tất cả image container cần thiết lên các node mới để bắt đầu thực hiện công việc.

Xác định phạm vi cho container và pod

Mặc dù ứng dụng của bạn phải được container hóa để chạy trên cụm Kubernetes, nhưng pod là đơn vị trừu tượng nhỏ nhất mà Kubernetes có thể quản lý trực tiếp. Một pod là một đối tượng của Kubernetes bao gồm một hoặc nhiều container có liên kết chặt chẽ. Các container trong một pod chia sẻ vòng đời và được quản lý như một đơn vị duy nhất. Ví dụ, các container luôn được lập lịch (triển khai) trên cùng một node (máy chủ), được khởi động hoặc dừng cùng lúc, và chia sẻ các tài nguyên như hệ thống tập tin và địa chỉ IP.

Việc hiểu cách Kubernetes xử lý các thành phần này và mỗi lớp trừu tượng cung cấp những gì cho hệ thống của bạn là rất quan trọng. Một số cân nhắc có thể giúp bạn xác định các điểm bao gói tự nhiên cho ứng dụng của mình với mỗi lớp trừu tượng này.

Một cách để xác định phạm vi hiệu quả cho các container là nhìn vào các ranh giới phát triển tự nhiên. Nếu hệ thống của bạn vận hành theo kiến trúc microservices, các container được thiết kế tốt thường được xây dựng để đại diện cho các đơn vị chức năng riêng biệt có thể được sử dụng trong nhiều bối cảnh khác nhau. Mức độ trừu tượng này cho phép nhóm của bạn phát hành các thay đổi của image container và sau đó triển khai chức năng mới này tới bất kỳ môi trường nào đang sử dụng image đó. Các ứng dụng có thể được xây dựng bằng cách kết hợp các container riêng lẻ, mỗi container thực hiện một chức năng nhất định nhưng có thể không hoàn thành toàn bộ một quy trình một mình.

Ngược lại, các pod thường được xây dựng dựa trên suy nghĩ về những phần của hệ thống có thể hưởng lợi từ việc quản lý độc lập. Vì Kubernetes sử dụng pod như là lớp trừu tượng người dùng nhỏ nhất, nên đây là đơn vị sơ khai nhất mà các công cụ và API của Kubernetes có thể tương tác và kiểm soát trực tiếp. Bạn có thể khởi động, dừng và khởi động lại pod, hoặc sử dụng các đối tượng cấp cao hơn được xây dựng trên pod để giới thiệu các tính năng nhân bản và quản lý vòng đời. Kubernetes không cho phép bạn quản lý các container bên trong pod một cách độc lập, do đó bạn không nên gom nhóm các container mà có thể hưởng lợi từ việc quản trị riêng biệt.

Vì nhiều tính năng và trừu tượng của Kubernetes liên quan trực tiếp đến pod, nên hợp lý khi gom các thành phần cần mở rộng cùng nhau vào một pod và tách riêng những thành phần nên mở rộng độc lập. Ví dụ, tách riêng máy chủ web khỏi máy chủ ứng dụng trong các pod khác nhau cho phép bạn mở rộng từng tầng một cách độc lập khi cần thiết. Tuy nhiên, việc gom một máy chủ web và một bộ chuyển đổi cơ sở dữ liệu vào cùng một pod có thể hợp lý nếu bộ chuyển đổi cung cấp chức năng thiết yếu mà máy chủ web cần để hoạt động đúng cách.

Tăng cường chức năng của pod bằng cách kết hợp các container hỗ trợ

Với ý tưởng trên, ta đặt ra câu hỏi: nên gom nhóm những loại container nào vào cùng một pod? Thông thường, một container chính chịu trách nhiệm thực hiện các chức năng cốt lõi của pod, nhưng có thể định nghĩa thêm các container phụ trợ để mở rộng hoặc tăng cường chức năng của container chính, hoặc giúp nó kết nối với môi trường triển khai đặc thù.

Ví dụ, trong một pod máy chủ web, container Nginx có thể lắng nghe các yêu cầu và phục vụ nội dung trong khi một container liên quan cập nhật các tệp tĩnh khi có thay đổi trong repository. Có thể bạn thấy hấp dẫn khi gói cả hai thành phần này vào một container duy nhất, nhưng sẽ có những lợi ích đáng kể khi thực hiện chúng dưới dạng các container riêng biệt. Cả container máy chủ web và container lấy dữ liệu từ repository đều có thể được sử dụng độc lập trong các bối cảnh khác nhau. Chúng có thể được duy trì bởi các đội khác nhau và phát triển sao cho hành vi của chúng có thể tổng quát hóa để làm việc với các container đồng hành khác nhau.

Brendan Burns và David Oppenheimer đã xác định ba mẫu thiết kế chính để gom các container phụ trợ vào cùng một pod trong bài báo về các mẫu thiết kế cho hệ thống phân tán dựa trên container. Đây là một số trường hợp sử dụng phổ biến nhất khi đóng gói các container lại với nhau trong một pod:

● Container bên: Trong mẫu này, container phụ trợ mở rộng và tăng cường chức năng cốt lõi của container chính. Mẫu này bao gồm việc thực thi các chức năng không chuẩn hoặc tiện ích trong một container riêng biệt. Ví dụ, một container chuyển tiếp log hoặc theo dõi cập nhật giá trị cấu hình có thể gia tăng chức năng cho một pod mà không làm thay đổi trọng tâm chính của nó.

● Container đại sứ: Mẫu đại sứ sử dụng một container bổ sung để trừu tượng hóa các nguồn tài nguyên từ xa cho container chính. Container chính kết nối trực tiếp với container đại sứ, container này sau đó kết nối và trừu tượng hóa các nhóm nguồn tài nguyên từ bên ngoài có thể phức tạp, như một cụm Redis phân tán. Container chính không cần phải biết hay quan tâm đến môi trường triển khai thực sự để kết nối với các dịch vụ bên ngoài.

● Bộ chuyển đổi (Adaptor): Mẫu bộ chuyển đổi được sử dụng để chuyển đổi dữ liệu, giao thức hoặc giao diện của container chính để phù hợp với các tiêu chuẩn mà bên ngoài yêu cầu. Các container bộ chuyển đổi cho phép truy cập đồng nhất tới các dịch vụ tập trung ngay cả khi ứng dụng mà chúng phục vụ chỉ hỗ trợ giao diện không tương thích theo cách bản địa.

Trích xuất cấu hình vào ConfigMaps và Secrets

Mặc dù cấu hình ứng dụng có thể được nhúng trực tiếp vào image container, nhưng tốt nhất bạn nên làm cho các thành phần của mình có thể cấu hình được tại thời điểm chạy để hỗ trợ việc triển khai trong nhiều bối cảnh và cho phép quản trị linh hoạt hơn. Để quản lý các tham số cấu hình thời gian chạy, Kubernetes cung cấp hai loại đối tượng, gọi là ConfigMaps và Secrets.

ConfigMaps là một cơ chế dùng để lưu trữ dữ liệu có thể được expose tới pod và các đối tượng khác tại thời điểm chạy. Dữ liệu được lưu trữ trong ConfigMaps có thể được trình bày dưới dạng biến môi trường hoặc được gắn dưới dạng tệp trong pod. Bằng cách thiết kế ứng dụng của bạn để đọc từ các vị trí này, bạn có thể tiêm cấu hình vào thời gian chạy thông qua ConfigMaps và thay đổi hành vi của các thành phần mà không cần xây dựng lại image container.

Secrets là một loại đối tượng Kubernetes tương tự, được sử dụng để lưu trữ an toàn dữ liệu nhạy cảm và cho phép các pod và thành phần khác truy cập có chọn lọc vào nó khi cần. Secrets là cách thuận tiện để chuyển các thông tin nhạy cảm tới ứng dụng mà không lưu trữ chúng dưới dạng văn bản thuần túy ở những vị trí dễ truy cập trong cấu hình thông thường. Về cơ bản, chúng hoạt động giống như ConfigMaps, cho phép ứng dụng tiêu thụ dữ liệu từ ConfigMaps và Secrets bằng cùng một cơ chế.

ConfigMaps và Secrets giúp bạn tránh việc đưa các tham số cấu hình trực tiếp vào định nghĩa đối tượng Kubernetes. Bạn có thể ánh xạ khóa cấu hình thay vì giá trị, cho phép cập nhật cấu hình “on the fly” bằng cách thay đổi ConfigMap hoặc Secret. Điều này cho phép bạn điều chỉnh hành vi thời gian chạy của pod và các đối tượng Kubernetes mà không cần chỉnh sửa định nghĩa tài nguyên.

Triển khai probes kiểm tra sẵn sàng và sống

Kubernetes tích hợp nhiều tính năng sẵn có để quản lý vòng đời của các thành phần và đảm bảo rằng ứng dụng của bạn luôn khỏe mạnh và sẵn sàng phục vụ. Tuy nhiên, để tận dụng các tính năng này, Kubernetes cần hiểu cách thức giám sát và diễn giải tình trạng sức khỏe của ứng dụng của bạn. Để thực hiện điều này, Kubernetes cho phép bạn định nghĩa các probes về tính sẵn sàng (readiness) và tính sống (liveness).

Liveness probes cho phép Kubernetes xác định liệu một ứng dụng bên trong container có đang sống và hoạt động hay không. Kubernetes có thể định kỳ chạy các lệnh trong container để kiểm tra các hành vi cơ bản của ứng dụng hoặc gửi các yêu cầu HTTP hoặc TCP tới một vị trí xác định để xác nhận tiến trình có sẵn sàng và phản hồi theo đúng kỳ vọng. Nếu một liveness probe thất bại, Kubernetes sẽ khởi động lại container nhằm cố gắng khôi phục chức năng trong pod.

Readiness probes là công cụ tương tự được dùng để xác định liệu một pod đã sẵn sàng phục vụ lưu lượng truy cập hay chưa. Các ứng dụng bên trong container có thể cần thực hiện các thủ tục khởi tạo trước khi sẵn sàng tiếp nhận yêu cầu từ phía client hoặc có thể cần tải lại sau khi có sự thay đổi cấu hình. Khi một readiness probe thất bại, thay vì khởi động lại container, Kubernetes sẽ tạm thời ngừng chuyển tiếp yêu cầu đến pod. Điều này cho phép pod hoàn tất các quy trình khởi tạo hoặc bảo trì mà không ảnh hưởng đến sức khỏe chung của cụm.

Bằng cách kết hợp các liveness và readiness probes, bạn có thể hướng dẫn Kubernetes tự động khởi động lại pod hoặc loại bỏ chúng khỏi nhóm backend. Việc cấu hình hạ tầng để tận dụng các khả năng này cho phép Kubernetes quản lý độ sẵn sàng và tình trạng sức khỏe của ứng dụng mà không cần thêm công việc vận hành.

Sử dụng deployment để quản lý khả năng mở rộng và độ sẵn sàng

Trước đó, khi bàn về một số nguyên tắc thiết kế cơ bản của pod, chúng ta đã đề cập đến việc các đối tượng khác của Kubernetes xây dựng trên những nguyên tắc này để cung cấp các tính năng nâng cao hơn. Một đối tượng phức hợp, đó là deployment, có lẽ là đối tượng Kubernetes được định nghĩa và thao tác phổ biến nhất.

Deployments là các đối tượng phức hợp được xây dựng dựa trên các nguyên tắc cơ bản của Kubernetes để thêm các khả năng quản lý vòng đời cho các đối tượng trung gian gọi là ReplicaSets, chẳng hạn như khả năng cập nhật luân phiên (rolling updates), quay lại phiên bản trước và chuyển đổi giữa các trạng thái. Các ReplicaSets cho phép bạn định nghĩa template cho pod nhằm tạo ra và quản lý nhiều bản sao của một thiết kế pod đơn lẻ. Điều này giúp bạn dễ dàng mở rộng hạ tầng, đáp ứng các yêu cầu về độ sẵn sàng và tự động khởi động lại các pod khi có sự cố.

Những tính năng bổ sung này cung cấp một khuôn khổ quản trị và khả năng tự phục hồi cho lớp pod cơ bản. Mặc dù pod là đơn vị cuối cùng chạy các khối lượng công việc mà bạn định nghĩa, nhưng chúng không phải là đơn vị mà bạn nên thường xuyên dự trữ và quản lý. Thay vào đó, hãy nghĩ đến pod như một khối xây dựng có khả năng chạy ứng dụng một cách mạnh mẽ khi được cung cấp qua các đối tượng cấp cao hơn như deployment.

Tạo dịch vụ và quy tắc ingress để quản lý truy cập tới các tầng ứng dụng

Deployments cho phép bạn dự trữ và quản lý các nhóm pod có thể thay thế lẫn nhau để mở rộng ứng dụng và đáp ứng nhu cầu của người dùng. Tuy nhiên, việc định tuyến lưu lượng truy cập tới các pod được dự trữ là một vấn đề riêng biệt. Khi các pod được thay thế trong quá trình cập nhật luân phiên, khởi động lại hoặc di chuyển do lỗi máy chủ, địa chỉ mạng liên kết với nhóm đang chạy sẽ thay đổi. Các dịch vụ (services) của Kubernetes cho phép bạn quản lý sự phức tạp này bằng cách duy trì thông tin định tuyến cho các nhóm pod động và kiểm soát việc truy cập tới các tầng khác nhau của hạ tầng.

Trong Kubernetes, services là các cơ chế cụ thể kiểm soát cách thức lưu lượng truy cập được định tuyến tới các nhóm pod. Cho dù đó là chuyển tiếp lưu lượng từ các client bên ngoài hay quản lý kết nối giữa các thành phần nội bộ, services cho phép bạn kiểm soát cách thức lưu lượng truy cập phải di chuyển.

Kubernetes sau đó sẽ cập nhật và duy trì tất cả thông tin cần thiết để chuyển tiếp các kết nối tới các pod tương ứng, ngay cả khi môi trường thay đổi và địa chỉ mạng được cập nhật.

Truy cập dịch vụ nội bộ

Để sử dụng services một cách hiệu quả, trước tiên bạn phải xác định những người tiêu dùng dự kiến cho mỗi nhóm pod. Nếu dịch vụ của bạn chỉ được sử dụng bởi các ứng dụng khác được triển khai trong cụm Kubernetes, loại dịch vụ clusterIP cho phép bạn kết nối tới một nhóm pod sử dụng địa chỉ IP ổn định chỉ có thể định tuyến từ bên trong cụm. Bất kỳ đối tượng nào được triển khai trong cụm đều có thể giao tiếp với nhóm pod đã nhân bản bằng cách gửi lưu lượng truy cập trực tiếp tới địa chỉ IP của dịch vụ. Đây là loại dịch vụ đơn giản nhất, hoạt động tốt cho các tầng ứng dụng nội bộ.

Một addon DNS tùy chọn cho phép Kubernetes cung cấp tên DNS cho các dịch vụ. Điều này cho phép các pod và đối tượng khác giao tiếp với dịch vụ bằng tên thay vì địa chỉ IP. Cơ chế này không thay đổi đáng kể cách sử dụng dịch vụ, nhưng các định danh dựa trên tên có thể giúp việc kết nối các thành phần hoặc xác định các tương tác mà không cần biết trước địa chỉ IP của dịch vụ.

Công khai dịch vụ cho người dùng bên ngoài

Nếu giao diện của dịch vụ cần được truy cập công khai, lựa chọn tốt nhất thường là sử dụng loại dịch vụ load balancer. Loại này sử dụng API của nhà cung cấp đám mây cụ thể của bạn để dự trữ một load balancer, từ đó phục vụ lưu lượng truy cập tới các pod của dịch vụ thông qua một địa chỉ IP công khai. Điều này cho phép bạn định tuyến các yêu cầu từ bên ngoài tới các pod trong dịch vụ, cung cấp một kênh mạng kiểm soát tới mạng nội bộ của cụm.

Vì loại dịch vụ load balancer tạo ra một load balancer cho mỗi dịch vụ, nên phương pháp này có thể trở nên tốn kém nếu bạn muốn cho phép truy cập công khai cho các dịch vụ Kubernetes. Để khắc phục điều này, các đối tượng ingress của Kubernetes có thể được sử dụng để mô tả cách định tuyến các loại yêu cầu khác nhau tới các dịch vụ khác nhau dựa trên một tập hợp các quy tắc định sẵn. Ví dụ, các yêu cầu cho “example.com” có thể được chuyển đến dịch vụ A, trong khi các yêu cầu cho “sammytheshark.com” có thể được định tuyến tới dịch vụ B. Các đối tượng ingress cung cấp cách mô tả cách định tuyến hợp lý dòng yêu cầu hỗn hợp tới các dịch vụ mục tiêu dựa trên các mẫu đã định sẵn.

Các quy tắc ingress cần được diễn giải bởi một ingress controller — thường là một loại load balancer như Nginx — được triển khai trong cụm dưới dạng pod, thực thi các quy tắc ingress và chuyển tiếp lưu lượng truy cập tới các dịch vụ của Kubernetes theo đó. Các triển khai ingress có thể được sử dụng để giảm thiểu số lượng load balancer bên ngoài mà chủ cụm cần vận hành.

Sử dụng cú pháp khai báo để quản lý trạng thái Kubernetes

Kubernetes cung cấp rất nhiều sự linh hoạt trong việc định nghĩa và kiểm soát các tài nguyên được triển khai trên cụm của bạn. Sử dụng các công cụ như kubectl, bạn có thể định nghĩa các đối tượng một cách trực tiếp để triển khai ngay lập tức trên cụm. Mặc dù cách này có thể hữu ích để nhanh chóng triển khai tài nguyên khi mới làm quen với Kubernetes, nhưng có một số nhược điểm khiến nó không phù hợp cho quản trị sản xuất lâu dài.

Một trong những vấn đề lớn của quản trị theo cách trực tiếp là nó không để lại bất kỳ bản ghi nào về những thay đổi bạn đã triển khai trên cụm. Điều này khiến việc khôi phục sau sự cố hoặc theo dõi các thay đổi vận hành trở nên khó khăn hoặc thậm chí không thể.

May mắn thay, Kubernetes cung cấp một cú pháp khai báo thay thế cho phép bạn định nghĩa hoàn toàn các tài nguyên trong các tệp văn bản, sau đó sử dụng kubectl để áp dụng cấu hình hoặc thay đổi. Việc lưu trữ các tệp cấu hình này trong kho kiểm soát phiên bản là một cách tốt để theo dõi các thay đổi và tích hợp với các quy trình duyệt xét được áp dụng cho các phần khác của tổ chức bạn.

Quản trị dựa trên tệp cũng cho phép bạn thích ứng các mẫu hiện có cho các tài nguyên mới bằng cách sao chép và chỉnh sửa các định nghĩa có sẵn. Việc lưu trữ các định nghĩa đối tượng Kubernetes của bạn trong các thư mục có phiên bản giúp bạn duy trì một bản chụp trạng thái cụm mong muốn tại mỗi thời điểm. Điều này có thể vô cùng hữu ích trong các hoạt động khôi phục, di chuyển hoặc khi truy tìm nguyên nhân gốc rễ của các thay đổi không mong muốn trong hệ thống của bạn.

Kết luận

Quản lý hạ tầng ứng dụng và tận dụng tối đa các tính năng từ môi trường điều phối hiện đại đòi hỏi sự chuyên môn cao. Tuy nhiên, khi bạn áp dụng đúng các thực hành phát triển và vận hành phù hợp với nguyên tắc thiết kế của công cụ, những lợi ích từ Kubernetes và công nghệ container sẽ trở nên rõ ràng hơn. Xây dựng kiến trúc hệ thống dựa trên các mẫu thiết kế tối ưu của Kubernetes và hiểu cách sử dụng một số tính năng nhằm giảm thiểu thách thức trong các triển khai phức tạp sẽ giúp nâng cao hiệu suất cũng như cải thiện trải nghiệm quản trị của bạn trên nền tảng này.

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *