ĐẠI HỌC QUỐC GIA HÀ NỘI TRƯỜNG ĐẠI HỌC CÔNG NGHỆ
NGUYỄN TRỌNG HIẾU
XÂY DỰNG KIẾN TRÚC TRIỂN KHAI LIÊN TỤC CHO CÁC HỆ THỐNG DỰA TRÊN VI DỊCH VỤ
LUẬN VĂN THẠC SĨ KỸ THUẬT PHẦN MỀM
HÀ NỘI - 2018
ĐẠI HỌC QUỐC GIA HÀ NỘI TRƯỜNG ĐẠI HỌC CÔNG NGHỆ
NGUYỄN TRỌNG HIẾU
XÂY DỰNG KIẾN TRÚC TRIỂN KHAI LIÊN TỤC CHO CÁC HỆ THỐNG DỰA TRÊN VI DỊCH VỤ
Ngành: Công nghệ thông tin
Chuyên ngành: Kỹ thuật phần mềm Mã số: 60480103
LUẬN VĂN THẠC SĨ KỸ THUẬT PHẦN MỀM
HÀ NỘI - 2018
MỤC LỤC
MỞ ĐẦU --- 1
CHƯƠNG 1: KIẾN THỨC CƠ BẢN --- 4
1.1 Tính toán đám mây --- 4
1.1.1 Giới thiệu về tính toán đám mây --- 4
1.1.2 Sự phổ biến của ứng dụng dựa vào đám mây --- 4
1.2 Tích hợp, chuyển giao và triển khai liên tục --- 4
1.2.1 Tích hợp liên tục --- 4
1.2.2 Chuyển giao liên tục --- 5
1.2.3 Triển khai liên tục --- 5
1.3 Kiến trúc hệ thống --- 5
1.3.1 Kiến trúc một khối --- 5
1.3.2 Kiến trúc hướng dịch vụ (Service-Oriented Architecture) --- 6
1.3.3 Kiến trúc vi dịch vụ --- 6
1.4 Các mô hình triển khai --- 7
1.4.1 Mô hình triển khai tùy biến --- 7
1.4.2 Mô hình không tùy biến kết hợp với Reverse Proxy --- 8
1.4.3 Mô hình không tùy biến với ứng dụng vi dịch vụ --- 8
1.5 Khái niệm Container --- 8
1.6 Tổng kết --- 9
CHƯƠNG 2: YÊU CẦU CỦA HỆ THỐNG TRIỂN KHAI LIÊN TỤC --- 10
2.1 Yêu cầu về điều phối container --- 10
2.1.1 Khám phá dịch vụ --- 10
2.1.2 Bộ lập lịch --- 10
2.1.3 Điều phối container --- 10
2.2 Yêu cầu cho các nhà phát triển --- 11
2.2.1 Khả năng tự triển khai --- 11
2.2.2 Nhận được các thông tin về kết quả build --- 11
2.3 Yêu cầu về vận hành --- 11
2.3.1 Quản lý cấu hình --- 11
2.3.2 Thu thập và lưu trữ log --- 11
2.3.3 Giám sát và thông báo lỗi --- 12
2.3.4 Có khả năng đảo ngược phiên bản triển khai (rollback) --- 12
2.3.5 Triển khai không có thời gian ngừng hoạt động (no-downtime deployment) --- 12
2.3.6 Thiết kế mạng triển khai --- 12
CHƯƠNG 3: THIẾT KẾ --- 13
3.1 Luồng triển khai cơ bản --- 13
3.2 Phân tích và lựa chọn công nghệ --- 14
3.2.1 Hệ thống quản lý mã nguồn --- 14
3.2.2 Máy chủ CI và máy chủ triển khai --- 14
3.2.3 Công nghệ container --- 15
3.2.4 Nền tảng điều phối container --- 15
3.2.5 Kênh thông báo --- 15
3.2.6 Hệ thống thu thập và quản lý log --- 15
3.2.7 Công nghệ giám sát hệ thống và cảnh báo --- 16
3.2.8 Giải pháp quản lý cấu hình --- 16
3.3 Luồng triển khai liên tục hoàn thiện --- 17
CHƯƠNG 4: CÀI ĐẶT VÀ ĐÁNH GIÁ --- 20
4.1 Cài đặt --- 20
4.1.1 Hệ thống vi dịch vụ mẫu --- 20
4.1.2 Cách thức tổ chức mã nguồn và quá trình build ảnh docker --- 20
4.1.3 Cài đặt môi trường sản phẩm --- 21
4.2 Đánh giá kết quả --- 24
KẾT LUẬN --- 26
MỞ ĐẦU
Trong những thập kỉ qua, chúng ta đã chứng kiến nhiều sự thay đổi trong cách mà một tổ chức phát triển và chuyển giao phần mềm, đặc biệt là về tốc độ và chất lượng chuyển giao.
Trước đây, mô hình phát triển phần mềm phổ biến là mô hình thác nước, trong đó việc phát triển được chia làm nhiều giai đoạn từ việc lập kế hoạch, phân tích yêu cầu, thiết kế hệ thống, cài đặt, kiểm thử, triển khai và bảo trì. Tuy nhiên, tỉ lệ thành công của những dự án phát triển phần mềm theo mô hình thác nước là rất thấp. Một trong những lí do phổ biến đó là yêu cầu hệ thống được khảo sát và được sử dụng để xây dựng phần mềm từ đầu dự án về cơ bản không chính xác hoặc có thiếu sót: có những yêu cầu chỉ sinh ra khi người sử dụng thực sự dùng phần mềm.
Những phương pháp xây dựng phần mềm linh hoạt (agile) được phát minh để khắc phục những điểm yếu của mô hình thác nước đó. Thay vì để người dùng phải chờ đợi 6 tháng hay 1 năm cho mỗi lần phát hành, các tổ chức sẽ liên tục cập nhật phần mềm cho người sử dụng (thường là 1 tháng), thu thập phản hồi, sửa đổi hay bổ sung thêm yêu cầu để cải tiến phần mềm cho những lần cập nhật sau.
Vấn đề phát triển phần mềm đã được cải thiện đáng kể, tuy nhiên vấn đề mới lại nảy sinh: các công việc liên quan tới vận hành cũ không còn theo kịp tốc độ phát triển phần mềm: những nhà phát triển muốn phát hành bản cập nhật mới nhanh nhất có thể để có thể lấy phản hồi từ người sử dụng, nhưng qúa trình từ các bản build tới khi sản phẩm được triển khai thành công mất nhiều thời gian.
Các khái niệm tích hợp, chuyển giao và triển khai liên tục được giới thiệu với những kinh nghiệm thực tiễn giúp giải quyết vấn đề nêu trên. Tuy nhiên, do giới hạn về công nghệ, mục tiêu của triển khai liên tục chưa đạt được vào thời điểm mà nó được giới thiệu: triển khai đòi hỏi phần cứng, mà việc chuẩn bị phần cứng mất nhiều thời gian (mua bán, cài đặt, quản lý, ...).
Với sự phát triển của Internet và sự xuất hiện của tính toán đám mây, các ứng dụng không còn bị bó buộc vào cơ sở hạ tầng vật lý mà đã có thể được triển khai trên các nền tảng đám mây. Những ứng dụng có thể sử dụng tài nguyên co dãn của đám mây để mở rộng, thu hẹp tùy vào nhu cầu sử dụng. Những ứng dụng như vậy được gọi là những ứng dụng dựa vào đám mây.
Kiến trúc các hệ thống phần mềm cũng tiến hóa để sử dụng lợi ích mà nền tảng đám mây mang lại. Trước đây, những dụng máy chủ (server-side application) truyền thống thường được xây dựng bằng một kiến trúc gồm nhiều lớp, với mỗi lớp chứa các loại thành phần khác nhau, ví dụ như:
• Lớp giao diện: chịu trách nhiệm xử lý các yêu cầu HTTP và trả lại kết quả là giao diện HTML hoặc dữ liệu ở dạng JSON/XML tới người dùng.
• Lớp logic nghiệp vụ: chịu trách nhiệm tính toán và xử lý dữ liệu thô sang dữ liệu có ý nghĩa theo nghiệp vụ nhất định.
• Lớp truy xuất dữ liệu: là cửa ngõ để truy cập vào cơ sở dữ liệu của các lớp khác. Lớp truy xuất dữ liệu cung cấp giao diện (API) một khối cho các lớp khác, độc lập với cơ sở dữ liệu đang được sử dụng (MySQL, PostgresSQL, …) Những ứng dụng sử dụng kiến trúc này thường được đóng gói và triển khai như một khối thống nhất. Những ứng dụng như vậy gọi là ứng dụng một khối (monolith).
Ứng dụng một khối có ưu điểm là dễ phát triển, kiểm thử và triển khai; tuy nhiên do tất cả các thành phần (module) đều được đóng vào một gói, ứng dụng ngày càng trở nên lớn và phức tạp, khiến việc hiểu và sửa đổi ứng dụng khó khăn hơn. Kích cỡ của ứng dụng sẽ làm chậm quá trình khởi động. Mỗi khi có cập nhật gì thì toàn bộ ứng dụng cần phải được triển khai lại, dẫn đến việc triển khai liên tục trở nên khó khăn hơn. Việc scale ứng dụng cũng sẽ đòi hỏi nhiều tài nguyên và phức tạp. Ngoài ra, lỗi trong một module hoàn toàn có thể khiến cho cả ứng dụng một khối bị sập. Khi muốn thay đổi công nghệ mới thì có khi cả ứng dụng cần phải được phát triển lại, gây lãng phí thời gian và tiền bạc.
Kiến trúc vi dịch vụ (microservice) được sinh ra để giải quyết những vấn đề trên.
Ý tưởng của kiến trúc này là chia ứng dụng ra những dịch vụ nhỏ hơn, hoàn toàn riêng biệt với nhau và giao tiếp với nhau thông qua các API. Ví dụ: những dịch vụ liên quan tới nghiệp vụ được phát triển riêng, cung cấp các API cho các dịch vụ khác. Những thành phần giao diện người dùng cũng được tách ra như một dịch vụ, chúng giao tiếp với những dịch vụ nghiệp vụ để lấy dữ liệu cần thiết và hiển thị lên cho người dùng.
Kiến trúc vi dịch vụ giải quyết được những vấn đề mà những ứng dụng một khối gặp phải:
• Việc chia ứng dụng ra thành những dịch vụ nhỏ hơn cho phép các dịch vụ được phát triển và kiểm thử nhanh chóng, độc lập. Mỗi đội sẽ được giao phát triển một hoặc một vài dịch vụ trong ứng dụng, cách biệt với các dịch vụ khác. Điều này khiến mã nguồn trở nên nhỏ hơn, dễ hiểu và quản lý.
• Mỗi dịch vụ nhỏ trong ứng dụng có thể được triển khai độc lập bởi mỗi đội.
• Do các dịch vụ trong ứng dụng chỉ giao tiếp với nhau thông qua API, mỗi dịch vụ có thể sử dụng công nghệ riêng. Điều này giúp việc cập nhật công nghệ cho toàn bộ ứng dụng trở nên dễ dàng (cập nhật lần lượt).
• Tích hợp liên tục trở nên khả thi do việc triển khai mỗi dịch vụ là độc lập với các dịch vụ khác.
• Mở rộng (scale) ứng dụng trở nên đơn giản và nhanh chóng, do việc scale ứng dụng sẽ được phân nhỏ thành scale các dịch vụ.
Kiến trúc vi dịch vụ hiện nay thường được sử dụng cùng với công nghệ container. Mỗi ảnh container là một đóng gói của ứng dụng kèm những thư viện cần thiết để ứng dụng có thể chạy được. Container sử dụng nhân chung với nhân hệ điều hành nên nhẹ hơn nhiều so với công nghệ máy ảo. Ngoài ra, container giúp cho ứng dụng chạy như nhau trên mọi môi trường, do vậy hỗ trợ tốt trong việc triển khai các dịch vụ trong kiến trúc vi dịch vụ. Cùng với nền tảng đám mây, kiến trúc vi dịch vụ và công nghệ container giúp cho ý tưởng về việc triển khai liên tục trở nên ngày một thực tế hơn.
Tuy nhiên, vấn đề gặp phải của kiến trúc vi dịch vụ sử dụng container là khi số lượng dịch vụ tăng lên, việc quản lý và phối hợp những dịch vụ đó trở nên khó hơn nhiều lần so với các ứng dụng một khối. Ngoài ra, việc giám sát nhiều dịch vụ cũng phức tạp hơn so với cách triển khai truyền thống; khi có lỗi xảy ra thì việc xem log và sửa lỗi cũng gây những khó khăn nhất định cho đội phát triển.
Với thực trạng như vậy, luận văn sẽ đề xuất ra giải pháp để giải quyết những vấn đề trên, từ đó đảm bảo được việc tích hợp, triển khai liên tục cho quá trình phát triển phần mềm.
Luận văn bao gồm 04 chương với nội dung của các chương được phân bố như sau:
• Chương 1: Giới thiệu về các khái niệm cơ bản liên quan tới tính toán đám mây, tích hợp và triển khai liên tục, các kiến trúc hệ thống phần mềm và công nghệ container.
• Chương 2: Phân tích yêu cầu để xây dựng mô hình triển khai liên tục cho cho hệ thống vi dịch vụ sử dụng container
• Chương 3: Trình bày thiết kế
• Chương 4: Cài đặt và đánh giá
• Kết luận: Kết luận và hướng nghiên cứu dự định trong tương lai.
CHƯƠNG 1: KIẾN THỨC CƠ BẢN
1.1 Tính toán đám mây
1.1.1 Giới thiệu về tính toán đám mây
Tính toán đám mây là sự cung cấp, phân phối sức mạnh tính toán, hệ thống lưu trữ, ứng dụng hoặc các nguồn tài nguyên công nghệ thông tin khác thông qua nền tảng đám mây, sử dụng kênh truyền internet và giá cả được tính toán dựa vào khối lượng sử dụng. Sử dụng tính toán đám mây, một công ty hay tổ chức không cần đầu tư nhiều chi phí ban đầu vào việc chuẩn bị các thiết bị phần cứng, hay dành nhiều thời gian để quản lý chúng. Thay vào đó, công ty/tổ chức chỉ cần cung cấp đúng yêu cầu về chủng loại và kích thước nguồn tài nguyên tính toán cho nhà cung cấp dịch vụ, mọi thứ sẽ được cung ứng gần như ngay lập tức. Các dịch vụ đám mây gần như trong suốt với người dùng, và với giao diện người dùng đơn giản, hầu hết các dịch vụ đám mây đòi hỏi rất ít kiến thức về quản trị.
Tính toán đám mây được chia thành 3 mô hình cơ bản: cơ sở hạ tầng như một dịch vụ(Infrastructure as a service – IaaS), nền tảng như một dịch vụ(Platform as a service – PaaS), phần mềm như một dịch vụ(Software as a service – SaaS).
1.1.2 Sự phổ biến của ứng dụng dựa vào đám mây
Việc chuyển đổi sang đám mây là sự phát triển tự nhiên đối với việc cung ứng các dịch vụ công nghệ thông tin. Nền tảng đám mây cung cấp một môi trường với các tài nguyên đa dạng, từ tính toán, lưu trữ cho tới mạng, có khả năng co dãn, linh động theo nhu cầu sử dụng. Ngày càng nhiều công ty, tổ chức chuyển dần sang phát triển phần mềm, ứng dụng cho nền tảng đám mây thay vì cách phát triển truyền thống bởi các đặc điểm nổi bật của đám mây như: tốc độ triển khai nhanh, khả năng cô lập lỗi tốt, và tính co dãn (scale).
1.2 Tích hợp, chuyển giao và triển khai liên tục 1.2.1 Tích hợp liên tục
Tích hợp liên tục là quá trình tự động hóa việc dịch (build) và kiểm thử mã mỗi khi các thành viên trong đội cập nhật mã lên hệ thống quản lý phiên bản (tích hợp liên tục đòi hỏi những nhà phát triển phải tích hợp mã vào một kho chứa mã duy nhất).
Tích hợp liên tục khuyến khích các nhà phát triển chia sẻ mã và các bài kiểm thử đơn vị (unit test) bằng việc hợp nhất những thay đổi trên máy lên kho quản lý mã chung ngay khi hoàn thiện một tác vụ nhỏ nào. Từ yêu cầu hợp nhất, các máy chủ sẽ kích hoạt luồng (pipeline): lấy mã mới về máy chủ CI, chạy quá trình dịch, kiểm thử, … Nếu có lỗi xảy ra ở bất cứ phần nào trong luồng, sửa lỗi là việc được ưu tiên hàng đầu.
Với mỗi công ty, tổ chức, pipeline tích hợp được xác định tùy thuộc vào công cụ sử dụng, ngôn ngữ lập trình cũng như rất nhiều yếu tố khác, tuy nhiên luồng thực hiên cơ bản bao gồm:
1. Đẩy mã lên kho chứa mã chung 2. Phân tích tĩnh
3. Kiểm thử trước khi triển khai
4. Đóng gói và triển khai lên môi trường kiểm thử 5. Kiểm thử sau khi triển khai
1.2.2 Chuyển giao liên tục
Về cơ bản, một luồng chuyển giao liên tục bao gồm các bước giống như luồng tích hợp liên tục đã liệt kê ở phần trước đó. Điểm khác biệt lớn nhất đó là: bước "5.
Kiểm thử sau khi triển khai" trong tích hợp liên tục thường đòi hỏi việc can thiệp bằng tay. Các bản dịch không vượt qua được các bài kiểm tra ở bước này sẽ bị loại. Còn trong Chuyển giao liên tục, sẽ có các cơ chế để tự động hóa bước này. Kết quả cuối cùng là một bản dịch mà luôn sẵn sàng để triển khai trên môi trường sản phẩm thực tế.
1.2.3 Triển khai liên tục
Triển khai liên tục là mục tiêu cuối cùng trong chuỗi tự động hóa này. Bản dịch cuối cùng sau bước chuyển giao liên tục là bản dịch luôn sẵn sàng để triển khai vào môi trường thực tế, tuy nhiên việc triển khai hay không và chọn phiên bản nào để triển khai lại phụ thuộc vào quyết định của đội vận hành. Trong triển khai liên tục, bất cứ bản dịch nào vượt qua được tất cả các bài kiểm thử đều sẽ được triển khai tự động lên môi trường sản phẩm thực tế. Không có sự can thiệp của con người trong qúa trình này.
1.3 Kiến trúc hệ thống
Trong những năm gần đây, kiến trúc phần mềm web đã phát triển với một tốc độ nhanh chóng. Với sự phổ biến của các dịch vụ Internet, chúng ta có thể thấy sự chuyển dịch từ kiến trúc một khối theo tầng/lớp, sang kiến trúc hướng dịch vụ và như hiện nay là kiến trúc vi dịch vụ. Việc lựa chọn một kiến trúc phần mềm cụ thể phụ thuộc hoàn toàn vào những yêu cầu về: khả năng co dãn (scale), độ phức tạp, khả năng triển khai, ... Đối với ứng dụng web, khả năng scale là một trong những yếu tố quan trọng hàng đầu.
1.3.1 Kiến trúc một khối
Kiến trúc một khối thường sẽ gộp những tính năng thiết yếu lại, từ đó phát triển cũng như triển khai như một khối duy nhất. Trong kiến trúc một khối, cách thiết kế phổ biến là phân chia các thành phần theo dạng module hoặc theo lớp. Ví dụ: phân chia thành lớp giao diện người dùng, tới lớp logic nghiệp vụ, lớp truy cập cơ sở dữ liệu và cuối cùng là lớp tích hợp để tích hợp với các dịch vụ ngoài.
Ưu điểm của kiến trúc này là:
• Đơn giản cho việc phát triển
• Đơn giản cho việc kiểm thử.
• Đơn giản cho việc triển khai
• Đơn giản trong việc scale theo chiều ngang
Kiến trúc này về cơ bản vẫn hoạt động tốt đối với những giai đoạn đầu của dự án.
Nhiều hệ thống phần mềm lớn hiện nay vẫn đang sử dụng kiến trúc này. Tuy nhiên, kiến trúc một khối có nhiều điểm hạn chế như:
• Cách tiếp cận này có giới hạn về kích thước và độ phức tạp.
• Ứng dụng có thể trở nên quá lớn, phức tạp để có thể hiểu và tiếp tục phát triển.
• Kích cỡ của ứng dụng làm việc khởi động trở nên chậm hơn
• Mỗi khi có cập nhật gì thì đều cần triển khai lại toàn bộ ứng dụng
• Triển khai liên tục trở nên khó khăn
• Nếu việc phân chia dữ liệu khó khăn, ứng dụng khó có thể scale theo chiều ngang như miêu tả ở trên.
• Ứng dụng một khối có nhiều rào cản trong việc áp dụng các công nghệ mới, bởi việc thay đổi trong bộ khung hay ngôn ngữ lập trình có thể ảnh hưởng tới toàn bộ ứng dụng.
1.3.2 Kiến trúc hướng dịch vụ (Service-Oriented Architecture)
Kiến trúc hướng dịch vụ nổi lên như một cách để giải quyết vấn đề các thành phần phụ thuộc vào nhau quá nhiều trong kiến trúc một khối.
Kiến trúc hướng dịch vụ là mẫu kiến trúc phần mềm trong đó các thành phần ứng dụng cung cấp dịch vụ cho các thành phần khác thông qua giao thức giao tiếp qua mạng. Có hai vai trò chính trong kiến trúc hướng dịch vụ: vai trò thành phần cung cấp dịch vụ và vai trò thành phần tiêu thụ dịch vụ. Một phần mềm có thể đóng cả hai trò, tùy vào từng hoàn cảnh cụ thể. Lớp tiêu thụ là điểm mà những thành phần tiêu thụ (có thể là người dùng, dịch vụ khác hoặc các bên thứ ba) tương tác với kiến trúc hướng dịch vụ, và Lớp cung cấp là lớp chứa tất cả các dịch vụ được định nghĩa trong kiến trúc hướng dịch vụ.
Enterprise Service Bus (ESB) là một kiểu kiến trúc tích hợp cho phép giao tiếp được thực hiện thông qua một bus chứa nhiều kết nối điểm-tới-điểm (point-to-point) giữa thành phần cung cấp và tiêu thụ.
1.3.3 Kiến trúc vi dịch vụ
Kiến trúc vi dịch vụ là mẫu kiến trúc phần mềm trong đó các ứng dụng phức tạp được chia nhỏ ra thành các dịch vụ nhỏ hơn, độc lập và giao tiếp với nhau thông qua những API độc lập với ngôn ngữ lập trình. Mỗi một vi dịch vụ là một ứng dụng nhỏ có kiến trúc riêng, có thể chứa logic nghiệp vụ và nhiều loại thành phần khác. Một số vi dịch vụ cung cấp API ở dạng Rest API, dạng message hoặc RPC và hầu hết các vi dịch vụ sẽ dùng API được cung cấp bởi các vi dịch vụ khác [6].
Kiến trúc vi dịch vụ có ảnh hưởng lớn trong mối quan hệ giữa ứng dụng và cơ sở dữ liệu. Thay vì dùng chung một bảng dữ liệu với các dịch vụ khác, mỗi vi dịch vụ sẽ có bảng dữ liệu riêng. Điều quan trọng trong kiến trúc này đó là mỗi một vi dịch vụ phải là một thành phần có thể chạy và triển khai độc lập được, việc tắt một vi dịch vụ không thể gây ra hậu quả lớn tới toàn bộ vi dịch vụ khác.
Kiến trúc vi dịch vụ có nhiều ưu điểm, giúp xử lý các vấn đề mà kiến trúc một khối gặp phải như:
• Giải quyết được vấn đề ứng dụng phức tạp bằng cách chia nhỏ thành các dịch vụ nhỏ, dễ quản lý hơn (vi dịch vụ). Mỗi một vi dịch vụ sẽ được phát triển nhanh hơn, dễ hiểu và bảo trì.
• Mỗi một vi dịch vụ có thể được phát triển độc lập bởi một đội, miễn là đảm bảo API giao tiếp.
• Giúp cập nhật công nghệ dễ dàng do mỗi vi dịch vụ có thể sử dụng công nghệ hoàn toàn riêng biệt.
• Mỗi một vi dịch vụ có thể được triển khai một cách độc lập, do vậy giúp cho việc triển khai liên tục trở nên khả thi.
• Việc scale ứng dụng giờ được chuyển thành scale từng vi dịch vụ độc lập, giúp tiết kiệm tài nguyên, scale nhanh hơn, có thể scale-out do mỗi vi dịch vụ có bảng cơ sở dữ liệu riêng.
Tuy nhiên, kiến trúc vi dịch vụ có những hạn chế nhất định:
• Kiến trúc vi dịch vụ làm tăng sự phức tạp của dự án
• Việc áp dụng một vài thay đổi liên quan tới nhiều dịch vụ đòi hỏi việc triển khai cẩn thận, có cơ chế cập nhật từ từ (roll-out) ở từng dịch vụ.
• Triển khai ứng dụng vi dịch vụ là rất phức tạp
1.4 Các mô hình triển khai
Có nhiều mô hình triển khai trong thực tế tùy thuộc vào kiến trúc phần mềm, cơ sở hạ tầng có sẵn và chiến lược của từng công ty. Tuy nhiên, chương này sẽ chỉ liệt kê 3 mô hình triển khai cơ bản để phân biệt giữa ứng dụng một khối và vi dịch vụ, cách đóng gói các bản build sử dụng để triển khai và phương án triển khai có tùy biến hay không.
1.4.1 Mô hình triển khai tùy biến
Ngày nay, cách phổ biến nhất để build và triển khai ứng dụng vẫn là sử dụng mô hình triển khai tùy biến. Chúng ta dựng lên một máy chủ web với ứng dụng cần chạy và cập nhật ứng dụng mỗi khi có bản phát hành mới. Sự thay đổi có thể xảy ra trong cấu hình, bản build (các tệp JAR, WAR, ...) hay cơ sở dữ liệu trên máy chủ đó, miễn là ứng dụng hoạt động. Với mô hình triển khai tùy biến, ta không thể biết chính xác rằng cấu hình môi trường phát triển, kiểm thử và môi trường sản phẩm (production) có giống nhau hay không. Ngoài ra, khó có thể đảm bảo được tất cả các máy đều được cập nhật đúng một phiên bản. Mô hình triển khai này dù ban đầu khá đơn giản nhưng nó sẽ dần kết nối mọi thứ vào nhau, tăng độ phức tạp theo thời gian và tạo ra sự không nhất quán. Khi có bản phát hành mới, việc cập nhật lại máy chủ có thể tạo ra thời gian ngừng hoạt động. Với mô hình này thì triển khai liên tục hoàn toàn không khả thi.
1.4.2 Mô hình không tùy biến kết hợp với Reverse Proxy
Việc chuyển đổi mô hình triển khai sang mô hình không tùy biến sẽ giải quyết được vấn đề khác nhau nêu trên. Khi triển khai, thay vì sử dụng các bản build đơn thuần của ứng dụng, ta sử dụng các bản đóng gói máy ảo hoặc container chứa tất cả môi trường cần thiết để ứng dụng hoạt động. Cách làm này đảm bảo được tính đồng nhất giữa môi trường build, kiểm thử và môi trường sản phẩm.
Khi triển khai, để đảm bảo không có thời gian ngừng hoạt động, một reverse proxy có thể được sử dụng: tất cả kết nối tới máy chủ chạy ứng dụng đều thông qua reverse proxy; khi cần cập nhật ứng dụng, ta triển khai hoàn toàn bản phát hành mới trên một máy chủ khác, đảm bảo ứng dụng chạy tốt trên đó và cấu hình reverse proxy để trỏ vào máy chủ vừa triển khai.
1.4.3 Mô hình không tùy biến với ứng dụng vi dịch vụ
Trong mô hình triển khai không tùy biến, mỗi một bản đóng gói bao gồm toàn bộ ứng dụng, khiến cho việc kiểm thử, triển khai mất nhiều thời gian. Điều này phần nào hạn chế việc triển khai thường xuyên. Áp dụng mô hình không tùy biến với ứng dụng sử dụng kiến trúc vi dịch vụ giúp đơn giản hóa được quá trình này: mỗi bản đóng gói sẽ chỉ gồm một hoặc một vài dịch vụ, các bản đóng gói của các dịch vụ khác nhau có thể được triển khai trên các máy khác nhau, giao tiếp giữa các dịch vụ được thực hiện qua mạng.
Điểm khác biệt cơ bản giữa mô hình này với mô hình không tùy biến ở trên là:
do kích cỡ của các dịch vụ khá nhỏ, không phải lúc nào triển khai cũng cần thực hiện trên một máy chủ mới. Dịch vụ mới hoàn toàn có thể được triển khai trên máy đang chạy, cấu hình reverse proxy sẽ trỏ trực tiếp vào dịch vụ thay vì vào máy chủ.
1.5 Khái niệm Container
Container là phương pháp ảo hoá ở mức hệ điều hành, cho phép chạy một ứng dụng cùng các thư viện nó phụ thuộc trong các tiến trình có tài nguyên được cách li.
Container giúp đóng gói mã, công cụ hệ thống, các cấu hình ... của ứng dụng vào trong một khối duy nhất gọi là ảnh container. Công nghệ container và công nghệ máy ảo có nhiều điểm tương đồng, ví dụ như về lợi ích liên quan tới phân phối và cách li tài nguyên. Tuy nhiên, container là ảo hoá ở mức hệ điều hành còn máy ảo là công nghệ ảo hoá phần cứng. Container có tính di động cao và sử dụng tài nguyên hiệu quả hơn.
Container là một lớp trừu tượng nằm ở tầng ứng dụng, đóng gói mã và các lệ thuộc vào cùng nhau. Nhiều container có thể được chạy trên cùng một máy và chia sẻ nhân hệ điều hành với các container khác. Mỗi container sẽ chạy như những tiến trình được cách li trong user space (Trong Linux, để đảm bảo tính cách li, container thường sử dụng namespace và control group). Các container có kích thước nhẹ hơn, chiếm ít không gian bộ nhớ hơn các máy ảo, do vậy có khả năng khởi động gần như ngay tức thời. Ngược lại, máy ảo là một lớp trừu tượng của phần cứng vật lý, có khả năng biến một máy chủ thành nhiều máy chủ con. Tầng hypervisor cho phép nhiều máy ảo chạy trong cùng một máy thật. Mỗi một máy ảo gồm đầy đủ hệ điều hành, các ứng dụng đi
kèm, các thư viện cơ bản, do vậy thường chiếm tới hàng chục GB bộ nhớ. Các máy ảo thường mất nhiều thời gian để khởi động.
1.6 Tổng kết
Như vậy, qua chương này, luận văn đã giới thiệu hầu hết các công nghệ nổi bật nhất hiện nay: từ sự phổ biến của tính toán đám mây, ý tưởng về một luồng tích hợp, chuyển giao và triển khai liên tục, tới các mô hình triển khai không tùy biến, sự tiến hoá về kiến trúc phần mềm hay cuối cùng là sự xuất hiện của công nghệ container.
Đây là những công nghệ làm tiền đề cho việc xây dựng một hệ thống triển khai liên tục hoàn chỉnh. Tìm hiểu những công nghệ này sẽ giúp chúng ta hiểu rõ hơn về thiết kế hệ thống triển khai được giới thiệu trong những chương tiếp theo.
CHƯƠNG 2: YÊU CẦU CỦA HỆ THỐNG TRIỂN KHAI LIÊN TỤC
2.1 Yêu cầu về điều phối container 2.1.1 Khám phá dịch vụ
Với cách thức xem các máy như nhau khi triển khai, yêu cầu đặt ra là làm cách nào người vận hành biết được container nào được triển khai trên máy nào bởi bộ lập lịch. Các công cụ khám phá dịch vụ được giới thiệu để giúp giải quyết vấn đề này.
Đối với các Docker container, vấn đề được thu gọn lại thành làm thế nào để có thể ánh xạ giữa container đang chạy và vị trí của nó. Vị trí ở đây chính là địa chỉ IP và cổng trên máy mà container được triển khai. Việc ánh xạ này cần được thực hiện một cách nhanh chóng và chính xác, bởi các container có thể được tắt hay bật lại trên các máy khác nhau trong cluster tại bất cứ thời điểm nào tùy thuộc vào môi trường.
Một giải pháp khám phá dịch vụ cần cung cấp được hai chức năng chính:
• Đăng kí: thiết lập ánh xạ giữa container và vị trí chạy khi container được triển khai. Bởi chỉ có bộ lập lịch biết chính xác vị trí mà container chạy, bộ lập lịch được xem như là nguồn đáng tin cậy duy nhất về vị trí container.
• Tìm kiếm: Cho phép các container hay các dịch vụ ngoài tìm kiếm các ánh xạ của các container khác để có thể giao tiếp với chúng. Yêu cầu đối với việc tìm kiếm là thông tin cần chính xác và thời gian phản hồi nhanh.
2.1.2 Bộ lập lịch
Một bộ lập lịch cho hệ thống phân tán sẽ dựa vào yêu cầu của người dùng để xác định ứng dụng và triển khai nó trên một hoặc nhiều máy đang khả dụng.
Ví dụ: người dùng có thể gửi yêu cầu chạy nhiều bản sao của một ứng dụng nhất định, bộ lập lịch sẽ dựa vào thông tin về tài nguyên của các máy để phân bố triển khai ứng dụng một cách hợp lí. Đối với Docker, việc này đòi hỏi ảnh của ứng dụng phải có sẵn trên máy chuẩn bị triển khai và Docker phải được cài trên các máy đó.
Một ví dụ của bộ lập lịch: người dùng yêu cầu triển khai 3 bản sao của ứng dụng, bộ lập lịch sẽ quyết định máy cụ thể để triển khai dựa trên thông tin về trạng thái cluster, cách tận dụng tài nguyên các máy để đảm bảo bật đủ số lượng bản sao yêu cầu và làm thế nào để thỏa mãn các ràng buộc khác từ người dùng (Ví dụ: chỉ triển khai ứng dụng trên máy có sử dụng ổ chứa SSD). Ngoài ra, chất lượng dịch vụ cũng là một yếu tố để đưa ra quyết định lựa chọn máy triển khai.
2.1.3 Điều phối container
Bộ lập lịch và chức năng khám phá dịch vụ đóng vai trò quan trọng trong việc quản lý container. Trên thực tế, hai thành phần này gần như luôn được sử dụng cùng nhau và nằm trong một khái niệm rộng hơn gọi là điều phối container.
Các thành phần cơ bản của một nền tảng điều phối container gồm: thành phần kiểm tra sức khỏe, tự động scale, thành phần khám phá dịch vụ, bộ lập lịch hay thành
phần nâng cấp hệ thống. Nền tảng điều phối container sẽ thực hiện qúa trình khởi tạo, lập lịch, lựa chọn máy cho tới giám sát, cập nhật và đảm bảo giao tiếp của các container trong cluster.
2.2 Yêu cầu cho các nhà phát triển 2.2.1 Khả năng tự triển khai
Ngoài việc tự động triển khai khi có sự thay đổi mã nguồn trên kho mã chung, hệ thống triển khai liên tục cũng cần có cơ chế để cho phép các đội phát triển, kiểm thử tự triển khai ứng dụng lên các môi trường, phục vụ mục đích kiểm tra tính năng hoặc kiểm thử, tái hiện lỗi. Hệ thống phân quyền cần được cấu hình để đảm bảo quyền hạn khác nhau tùy theo vai trò của các đội.
2.2.2 Nhận được các thông tin về kết quả build
Quá trình triển khai liên tục thường gồm 2 giai đoạn: build và triển khai. Quá trình build thường mất thời gian và các lập trình viên thường bận rộn với việc phát triển tính năng mới cho sản phẩm, do vậy cần có cơ chế để thông báo trạng thái của các tác vụ build cho các đội thông qua các kênh giao tiếp của công ty như email, phần mềm chat, ...
2.3 Yêu cầu về vận hành 2.3.1 Quản lý cấu hình
Cơ sở hạ tầng được quản lý như mã (Infrastructure as code - IAC) là cách tiếp cận việc tự động hóa quản lý cơ sở hạ tầng dựa trên những kinh nghiệm từ quá trình phát triển phần mềm. IAC nhấn mạnh vào tính nhất quán, lặp đi lặp lại đối với việc cung ứng và thay đổi các hệ thống cũng như cấu hình của chúng. Ý tưởng của IAC là người dùng sẽ viết và thực thi mã để định nghĩa, triển khai và cập nhật cơ sở hạ tầng.
IAC thay đổi nhận thức của mọi người trong việc đối xử các hoạt động vận hành như là phần mềm, dù một số hoạt động thực tế liên quan tới phần cứng (ví dụ: cài đặt máy chủ triển khai).
Bằng việc ứng dụng IAC, không chỉ người quản trị, bất cứ ai cũng có thể đọc và hiểu về toàn bộ cấu hình hệ thống, có khả năng đảo ngược cấu hình về phiên bản chạy tốt bằng việc sử dụng hệ thống quản lí phiên bản. IAC cũng cho phép đánh giá, kiểm thử trước khi thực sự triển khai sự thay đổi về cơ sở hạ tầng.
2.3.2 Thu thập và lưu trữ log
Thu thập và lưu trữ log là yêu cầu cơ bản đối với bất cứ hệ thống phần mềm thực tế nào. Trong một hệ thống vi dịch vụ dựa vào container, các container sẽ được triển khai trong một cluster gồm nhiều máy. Với mỗi máy, ngoài các container còn có những dịch vụ khác đang chạy. Các ứng dụng sẽ sản sinh ra các tệp log với định dạng, vị trí khác nhau, khiến cho việc quản lý và sử dụng log trở nên khó khăn. Cần có cơ chế để thu thập toàn bộ log cho tất cả các ứng dụng hay container này, đồng thời cho phép người dùng sử dụng chúng một cách hiệu quả.
2.3.3 Giám sát và thông báo lỗi
Các nền tảng điều phối container có cơ chế giám sát để đảm bảo các dịch vụ hoạt động bình thường và có phương pháp sửa lỗi nếu phát hiện vấn đề. Tuy nhiên, cơ chế cung cấp bởi nền tảng điều phối container thường chỉ ở mức cơ bản. Trên thực tế, các dịch vụ thường chứa logic nghiệp vụ phức tạp, mỗi một dịch vụ lại có yêu cầu và các tham số giám sát riêng. Cần có cơ chế để các nhà phát triển có thể tùy biến các tham số cần giám sát, có công cụ trực quan để đội vận hành theo dõi và các phương thức tự động thông báo nếu có lỗi xảy ra.
2.3.4 Có khả năng đảo ngược phiên bản triển khai (rollback)
Hệ thống triển khai liên tục cần có khả năng đảo ngược phiên bản triển khai một cách dễ dàng. Điều này giúp giảm áp lực trong việc triển khai và khuyến khich mọi người triển khai thường xuyên hơn để đẩy tính năng mới người dùng một cách nhanh chóng.
2.3.5 Triển khai không có thời gian ngừng hoạt động (no-downtime deployment) Hệ thống phải cho phép cập nhật phiên bản mới của ứng dụng mà không có thời gian ngừng hoạt động để đảm bảo tính liên tục. Một kỹ thuật thường được sử dụng là triển khai dạng blue-green: chạy hai bản sao giống hệt nhau của ứng dụng. Tại thời điểm ban đầu, cả hai bản sao được gọi là blue. Khi cần cập nhật, một trong hai bản sao sẽ được cập nhật trước và được gọi là green. Sau khi đảm bảo phiên bản green hoạt động tốt, phiên bản blue còn lại sẽ được thay thế bằng phiên bản green. Ngược lại, nếu có vấn đề xảy ra, phiên bản green sẽ bị loại và phiên bản blue sẽ được triển khai lại. Ở lần cập nhật tiếp theo, một trong hai phiên bản green sẽ được thay bằng phiên bản mới được gọi là blue. Phương pháp này là một biến thể của mô hình triển khai không tùy biến có sử dụng reverse proxy đã giới thiệu trong chương 01.
2.3.6 Thiết kế mạng triển khai
Trong thực tế, các máy triển khai trong đám mây của một công ty/tổ chức thường nằm trong một VPC (Virtual Private Cloud) với dải mạng ảo riêng. Các máy trong VPC có thể kết nối ra ngoài Internet thông qua một thiết bị NAT hoặc Internet Gateway được cung cấp bởi nhà cung cấp dịch vụ đám mây. Tuy nhiên, chúng không thể được truy cập trực tiếp từ bên ngoài nếu không gắn các địa chỉ Public IP vào những máy này với những cấu hình định tuyến thích hợp, tạo liên kết VPN hay sử dụng các dạng cấu hình port-forwarding tại các router.
Máy chủ triển khai thường được cài đặt tại ngay trong mạng nội bộ của công ty, do vậy ở điều kiện bình thường sẽ không thể kết nối tới tất cả các máy trong VPC, gây khó khăn cho việc triển khai tự động. Những phương pháp nêu trên đều có thể được áp dụng để giải quyết vấn đề này. Tuy nhiên, khi số lượng các máy trong VPC tăng lên, hoặc khi công ty muốn tạo ra nhiều VPC (chẳng hạn tạo ra nhiều môi trường để kiểm thử, đánh giá, mỗi môi trường được triển khai trên một VPC) thì những cách kết nối này đều có các hạn chế nhất định. Do vậy, thiết kế hệ thống triển khai liên tục cần tìm ra giải pháp đề giải quyết được yêu cầu này.
CHƯƠNG 3: THIẾT KẾ 3.1 Luồng triển khai cơ bản
Hình 3.1: Luồng triển khai liên tục cơ bản
Hình 3.1 thể hiện thiết kế luồng triển khai liên tục cơ bản cho một hệ thống vi dịch vụ sử dụng container, trong đó hoạt động của các thành phần cụ thể như sau:
1. Lập trình viên đẩy mã lên kho chứa mã chung (Repository).
2. Kho chứa mã nhận biết có sự thay đổi về mã, kích hoạt máy chủ CI để build mã vừa được cập nhật. Khi mã được build xong, máy chủ sẽ đóng gói bản build theo dạng ảnh container. Các cơ chế kiểm thử tự động cũng được dùng trong quá trình này để đảm bảo mã không có lỗi.
3. Nếu build thất bại, toàn bộ quá trình sẽ bị dừng lại. Ngược lại, máy chủ sẽ đẩy bản build mới lên một kho chứa chung dùng để triển khai. Kết quả build thành công hay thất bại đều có thông báo gửi tới nhà phát triển thông qua các kênh giao tiếp chung của công ty.
4. Sau khi bản build được đẩy lên kho chứa, máy chủ CI sẽ kích hoạt máy chủ triển khai để triển khai bản build lên môi trường sản phẩm. Quá trình triển khai cần đảm bảo không có thời gian ngừng hoạt động và trong suốt với người sử dụng.
5. Việc lựa chọn máy để triển khai và giao tiếp giữa các container trong các máy sẽ được một nền tảng điều phối container quản lý.
6. Các bản build được triển khai trên môi trường sản phẩm sẽ được hệ thống giám sát kiểm tra thường xuyên. Nếu có vấn đề xảy ra, hệ thống giám sát sẽ gửi thông báo tới thành phần cảnh báo. Thành phần cảnh báo có trách nhiệm gửi thông báo lỗi tới nhà phát triển thông qua các kênh giao tiếp.
7. Hệ thống quản lý log sẽ liên tục thu thập log từ các dịch vụ được triển khai, lưu trữ log vào một hệ thống lưu trữ và cho phép người dùng truy cập, tìm kiếm thông tin log khi có lỗi xảy ra thông qua giao diện người dùng.
3.2 Phân tích và lựa chọn công nghệ 3.2.1 Hệ thống quản lý mã nguồn
Luận văn sẽ sử dụng Git làm hệ thống quản lý mã nguồn do đặc điểm phân tán, khả năng hỗ trợ rẽ nhánh, làm việc nhóm hiệu quả cùng sự phổ biến của nó.
Mô hình rẽ nhánh được sử dụng như sau: nhánh develop là nhánh chính được sử dụng cho việc phát hành. Các nhà phát triển sẽ tạo nhánh mới cho các tính năng mới, cho việc sửa lỗi hay các bản vá. Sau khi đảm bảo mã hoạt động tốt ở nhánh mới, nhà phát triển sẽ gửi một yêu cầu hợp nhất mã (merge request) vào nhánh develop. Ngay khi yêu cầu được tạo ra, Git sẽ sử dụng kỹ thuật Web Hook để kích hoạt quá trình build với mã mới (pre-merge build). Kết quả quá trình build và yêu cầu sẽ được gửi cho người quản lý kho mã nguồn. Người quản lý kho mã sẽ đánh giá yêu cầu và chấp nhận hợp nhất. Ngay sau khi người quản lý chấp nhận, một quá trình build khác sẽ được kích hoạt (post-merge build). Quá trình này sẽ đóng gói bản build mới vào ảnh container theo công nghệ container được chọn, thực hiện kiểm thử với ảnh đóng gói và đẩy ảnh lên kho lưu trữ container nếu không có lỗi xảy ra.
3.2.2 Máy chủ CI và máy chủ triển khai
Máy chủ CI được sử dụng trong bài là Jenkins, một phần mềm tự động hóa, mã nguồn mở và viết bằng Java. Jenkins hỗ trợ hầu hết các phần mềm quản lý mã nguồn hiện nay như Git, Subversion, Mercurial ... và hỗ trợ các mã lệnh của nhiều loại ngôn ngữ script như Bash, Python, Groovy, Gradle ...
3.2.3 Công nghệ container
Mã nguồn sau khi được hợp nhất và build hậu hợp nhất sẽ được đóng gói thành ảnh container và lưu trong kho ảnh. Luận văn sẽ sử dụng công nghệ container phổ biến nhất hiện nay là Docker.
Docker sử dụng Dockerfile để miêu tả các lớp ảnh, các câu lệnh cần thực hiện cho việc build ảnh container. Nhằm đảm bảo tính nhất quán trong việc tích hợp build ảnh container với mã nguồn, Dockerfile sẽ được lưu trữ và được quản lý trong cùng thư mục trên kho lưu trữ chung như mã nguồn.
3.2.4 Nền tảng điều phối container
Hiện nay, cùng với sự phổ biến của công nghệ container, số lượng các nền tảng điều phối container ngày càng tăng lên. Trong đó, các nền tảng phổ biến nhất là Amazon ECS, Azure Container Service, Goole Container Engine, Diego của Cloud Foundry, Kubernetes, Docker Swarm, Mesosphere Marathon. Tuy nhiên, hầu hết các nền tảng điều phối đều là bản thương mại được cung cấp bởi các công ty điện toán đám mây, ngoại trừ Docker swarm, Mesosphere Marathon và Kubernetes. Luận văn do vậy chỉ tập trung hướng tới phân tích ba nền tảng này.
Ba nền tảng điều phối đều cung cấp các chức năng cơ bản nhất để quản lý container, tuy nhiên độ phức tạp trong sử dụng, số lượng máy có thể quản lý và khả năng tùy biến của các nền tảng hoàn toàn khác nhau:
Nền tảng < 10 máy 10 - 100 máy
100 – 1000 máy
> 1000 máy
Khả năng tùy biến
Tính dễ sử dụng Docker
swarm ++ + ? ? + +++
Mesphere Marathon
+ ++ ++ ++ ++ +
Kubernetes ++ ++ + ? ++ ++
Bảng 3-1: So sánh Docker Swarm, Mesosphere Marathon và Kubernetes
Từ bảng so sánh trên, luận văn đề xuất sử dụng Kubernetes như nền tảng điều phối container chính cho hệ thống triển khai liên tục.
3.2.5 Kênh thông báo
Kênh thông báo giúp tất cả các bên liên quan luôn cập nhật được trạng thái của một luồng triển khai liên tục và thường được tích hợp vào chính kênh giao tiếp hiện tại của công ty như email, các phần mềm chat. Việc lựa chọn kênh thông báo nào tùy thuộc đặc điểm và nhu cầu của từng công ty. Trong luận văn, phần mềm Slack được xem như kênh giao tiếp chính và sử dụng cho mục đích thông báo này.
3.2.6 Hệ thống thu thập và quản lý log
Cách thức thu thập log phổ biến hiện nay cho các cluster là sử dụng các log collector trên các máy triển khai nhằm thực hiện nhiệm vụ phân giải nội dung log thành các trường thông tin và gửi về hệ thống lưu trữ. Để cung cấp khả năng tìm kiếm nhanh cho người sử dụng, log thường được lưu và đánh chỉ số trong elasticsearch.
Kibana, một công cụ khám phá trực quan và hỗ trợ phân tích dữ liệu thời gian thực cho elasticsearch, sẽ được sử dụng như giao diện tìm kiếm log cho người dùng.
Như vậy, các giải pháp quản lý log khác biệt cơ bản nhất ở thành phần log collector. Trong thế giới mã nguồn mở, hai log collector phổ biến nhất là Logstash và Fluentd, đại diện cho 2 stack công nghệ ELK và EFK.
Điểm khác biệt cơ bản của Logstash và Fluentd được thể hiện trong bảng sau:
Log collector Phương pháp định tuyến log
Tầng giao vận Hiệu năng Log stash Dựa vào các lệnh theo
thuật toán
Cần triển khai với Redis để đảm bảo độ tin cậy
Sử dụng nhiều bộ nhớ hơn. Sử dụng Elastic Beats cho các lá.
Fluentd Dựa vào các thẻ Độ tin cậy được tích
hợp sẵn nhưng khó cấu hình
Sử dụng ít bộ nhớ hơn.
Dùng Fluent Bit và Fluentd Forwarder cho các lá.
Bảng 3-2: So sánh Logstash và Fluentd
Với hệ thống vi dịch vụ sử dụng container, số lượng container là rất lớn với khối lượng và chủng loại log khổng lồ. Do vậy sử dụng Fluentd sẽ giúp người vận hành cấu hình được cách thức định tuyến log linh hoạt, đảm bảo độ tin cậy mà không cần cài đặt thêm các dịch vụ ngoài và tối ưu hóa tài nguyên cũng như hiệu năng sử dụng.
3.2.7 Công nghệ giám sát hệ thống và cảnh báo
Việc hiểu một ứng dụng hoạt động ra sao khi được triển khai là yếu tố quan trọng để scale ứng dụng và cung cấp các dịch vụ đáng tin cậy. Prometheus là công cụ giám sát hệ thống được phát triển bởi SoundCloud và đã được xuất bản như phần mềm mã nguồn mở. Prometheus hoạt động bằng cách thu thập (Prometheus sử dụng thuật ngữ scrape) các mục tiêu, là các endpoints ở dạng key-value. Prometheus sau đó sẽ lưu trữ thông tin thu thập được như một khung trong cơ sở dữ liệu theo thời gian (time series database), từ đó cho phép người dùng thực hiện truy vấn đồ thị và các chức năng khác như cảnh báo.
Prometheus cho phép nhà phát triển thu thập thông tin của từng thư viện, từng dịch vụ bằng cách tích hợp thư viện Prometheus vào trong mã nguồn để xây dựng các thông tin (metrics) tùy biến. Đối với các thư viện với cách thức cung cấp thông tin khác, Prometheus có các exporter để chuyển đổi các thông tin đấy sang dạng thông tin mà Prometheus có thể sử dụng.
Ngoài ra, bản thân mã nguồn Prometheus cũng cung cấp thành phần cảnh báo lỗi là AlertManager, giúp gửi thông tin tới các bên liên quan qua nhiều kênh giao tiếp khác nhau. Thông tin mà Prometheus cung cấp có thể được thể hiện trực quan qua các phần mềm bên thứ ba như Grafana.
Prometheus giúp đảm bảo mục đích đa dạng trong việc giám sát một hệ thống vi dịch vụ gồm nhiều container.
3.2.8 Giải pháp quản lý cấu hình
Có nhiều giải pháp giúp quản lý cấu hình như Chef, Puppet hay Ansible. Các giải pháp này đều hỗ trợ việc tự động hóa quá trình cài đặt, cấu hình cho máy triển khai và
phù hợp để tích hợp vào luồng triển khai liên tục. Luận văn sẽ sử dụng Ansible do sự đơn giản trong việc cài đặt và cú pháp dễ hiểu, dễ viết của nó.
3.3 Luồng triển khai liên tục hoàn thiện
Với các công nghệ đã chọn được nêu trong phần trước, luận văn đề xuất xây dựng hệ thống triển khai liên tục hoàn thiện như sau (Hình 3.2):
Như vậy, luồng triển khai liên tục hoàn thiện hoạt động chi tiết như sau:
1. Lập trình viên gửi yêu cầu hợp nhất từ nhánh phụ sang nhánh develop trên Gitlab
2. Dựa vào WebHooks, Gitlab kích hoạt quá trình build tiền hợp nhất trong Jenkins. Kết quả build được gửi qua Slack cho tất cả các bên liên quan.
3. Người quản lý mã nguồn lựa chọn chấp nhận hợp nhất mã. WebHooks trên Gitlab kích hoạt quá trình build hậu hợp nhất trên Jenkins. Mã nguồn sau khi build sẽ được đóng gói vào ảnh Docker và lưu trên máy Jenkins.
4. Người quản lý mã nguồn lựa chọn chấp nhận hợp nhất mã. WebHooks trên Gitlab kích hoạt quá trình build hậu hợp nhất trên Jenkins. Mã nguồn sau khi build sẽ được đóng gói vào ảnh Docker và lưu trên máy Jenkins.
5. Các bài kiểm thử cho ảnh Docker sẽ được thực hiện trên máy Jenkins. Nếu tất cả các bài kiểm thử thành công, ảnh Docker sẽ được đẩy lên Docker Registry riêng của công ty. Đồng thời, tác vụ triển khai trong Jenkins cũng được kích hoạt.
6. Jenkins sẽ dựa vào Ansible để triển khai ảnh Docker vào Kubernetes cluster.
Việc lựa chọn máy triển khai phụ thuộc vào bộ lập lịch trong Kubernetes Master.
7. Mã nguồn được tích hợp với thư viện prometheus để tùy biến các metrics cần thiết cho việc giám sát. Ngoài ra, các exporter trong prometheus cũng được sử dụng để chuyển đổi thông tin metrics được cung cấp từ các thư viện khác hoặc của môi trường.
8. Prometheus Alert Manager được sử dụng làm máy chủ để gửi cảnh báo dựa vào thông tin nhận được từ máy chủ promethus và các nguyên tắc cảnh báo viết bởi người vận hành. Tất cả cảnh báo sẽ được gửi tới Slack như các thông báo lỗi khác.
9. Grafana được sử dụng để cung cấp giao diện trực quan cho người dùng.
10. Fluentd được lựa chọn để làm log collector cho các ảnh container. Cách thức thu thập log như sau: các tệp log trong container sẽ được ánh xạ ra máy triển khai. Fluentd sẽ dựa vào plugin tail để đọc nội dung log, phân giải các trường thông tin, xây dựng luồng sự kiện và gửi tới một máy đóng vai trò Log Aggregator. Từ máy log aggregator, log sẽ được chuyển vào lưu trữ trong Elasticsearch. Tại đây, log được lưu trữ và đánh chỉ số để phục vụ cho việc tìm kiếm nhanh.
11. Một số plugin sẽ được sử dụng để cung cấp giao diện và khả năng truy vấn đối với dữ liệu trong Elasticsearch. Ngoài ra, Kibana sẽ được cài đặt và liên kết với Elasticsearch để cung cấp giao diện linh hoạt cho người sử dụng.
Hình 3.2: Hệ thống triển khai liên tục hoàn thiện
Đối với phần kết nối mạng để triển khai (trong trường hợp các máy triển khai được cài đặt trong một VPC riêng trên nền tảng đám mây), luận văn đề xuất phương án kết nối như sau:
Hình 3.3: Kết nối mạng triển khai
Như vậy, một máy trong VPC sẽ đóng vai trò là work-terminal, chứa các kịch bản triển khai Ansible. Máy work-terminal có thể truy cập tới tất cả các máy khác trong VPC thông qua kết nối SSH. Máy work-terminal sẽ được gắn với một địa chỉ Public IP và được định tuyến để có thể truy cập được từ các máy ngoài mạng.
CHƯƠNG 4: CÀI ĐẶT VÀ ĐÁNH GIÁ
4.1 Cài đặt
4.1.1 Hệ thống vi dịch vụ mẫu
Để kiểm tra tính hiệu quả của thiết kế luồng triển khai liên tục được đề xuất trong chương trước, luận văn sẽ xây dựng một ứng dụng gồm hai vi dịch vụ cơ bản như sau:
• Forex-service: vi dịch vụ cung cấp chức năng tra cứu tỷ giá tiền tệ.
• Ccs-service: vi dịch vụ cung cấp chức năng chuyển đổi một lượng tiền nhất định từ một đơn vị tiền tệ sang đơn vị tiền tệ khác. Vi dịch vụ ccs-service sử dụng forex-service để lấy thông tin về tỷ giá.
Khi triển khai, mỗi vi dịch vụ nêu trên có thể có nhiều bản sao cùng chạy. Luận văn sử dụng cơ chế load-blance cho các bản sao này bằng Service, được cung cấp bởi Kubernetes, để đảm bảo định tuyến các yêu cầu tới đúng các bản sao một cách lần lượt (round-robin).
Hai vi dịch vụ được lập trình bằng ngôn ngữ Java, sử dụng bộ khung Spring Boot.
4.1.2 Cách thức tổ chức mã nguồn và quá trình build ảnh docker
• Sử dụng cách thức tổ chức thư mục như quy ước của các dựa án Maven
• Tạo thư mục docker-build ở cùng mức với thư mục mã nguồn src, chứa Dockerfile dùng để build ảnh docker, các tệp script để sử dụng như tài nguyên cho ảnh. Ngoài ra, các script để tự động hóa quá trình build và đẩy ảnh lên kho chứa ảnh cũng được đặt trong thư mục này.
Để có toàn quyền cấu hình, luận văn cài đặt hệ thống quản lý mã Gitlab và máy chủ CI Jenkins như các ứng dụng nội bộ. Jenkins cần sử dụng các plugin để cung cấp các chức năng tự động hóa cụ thể. Trong bài, scm-plugin được sử dụng để tải mã từ gitlab về máy chủ Jenkins và gitlab-plugin sẽ được sử dụng để giúp Jenkins nhận được thông báo về các sự kiện thay đổi mã trên Gitlab, từ đó kích hoạt quá trình build và cập nhật kết qủa lên chính commit đã kích hoạt đó.
Có hai quá trình build được sử dụng trong luồng triển khai được cài đặt: build tiền hợp nhất và build hậu hợp nhất.
Slack được sử dụng trong luận văn như kênh giao tiếp chính. Slack-plugin được cài đặt trong Jenkins để gửi thông báo tới Slack dựa vào trạng thái của quá trình build.
Tất cả trạng thái của các quá trình build này sẽ được cập nhật trực tiếp lên Gitlab, cung cấp thông tin trực quan cho nhà phát triển.
Sau khi build tiền hợp nhất thực hiện thành công, luận văn sử dụng API được cung cấp bởi Jenkins để kích hoạt tiếp quá trình triển khai, từ đó cập nhật ứng dụng vào môi trường sản phẩm (production environment).
Hầu hết các tác vụ trong Jenkins đều được cấu hình thông qua mã Groovy DSL một cách tự động. Điều này giúp cho việc cấu hình trở nên đơn giản, nhanh chóng và quan trọng nhất là có thể kiểm thử được từ trước khi thực sự chạy.
4.1.3 Cài đặt môi trường sản phẩm
4.1.3.1 Cài đặt nền tảng điều phối container
Một kubernetes cluster cần có ít nhất các thành phần sau:
• Một master node: chứa các thành phần api-server, controller-manager, bộ lập lịch và thành phần setup. Tất các các thành phần này đều được cài đặt bằng ảnh Docker hypercube được cung cấp bởi Google.
• Các worker node: chứa ít nhất 2 thành phần kubelet, kube-proxy. Các thành phần này cũng được cài đặt bằng ảnh hypercube với các tham số phù hợp.
• Etcd: cung cấp chức năng lưu trữ phân tán các giá trị dạng key-value.
Kubernetes được cấu hình để kết nối và lưu trữ các dữ liệu trạng thái trong etcd cluster. Etcd trong bài được cài đặt như các dịch vụ hệ thống dạng systemd.
• Cách thức kết nối mạng multihost: luận văn sử dụng dịch vụ flannel để đảm bảo kết nối mạng multihost cho các máy khác nhau. Kết nối mạng chi tiết được thể hiện trong Hình 4.1.
Hình 4.1: Cách thức kết nối mạng trong kubernetes
• Giải pháp khám phá dịch vụ: SkyDNS được sử dụng làm giải phát khám phá dịch vụ trong Kubernetes. Các container chạy trong các POD của kubernetes có thể tương tác với các container trong POD khác bằng DNS name của Service đại diện các POD.
Các vi dịch vụ forex-service và ccs-service trong luận văn được triển khai vào các worker node trong kubernetes cluster. Đối với một tổ chức, thông thường các máy triển khai môi trường sản phẩm được cài đặt trong một VPC với cấu hình mạng cách ly với môi trường bên ngoài. Do vậy, cần có cách thức để cho phép các yêu cầu gửi từ bên ngoài được chuyển vào đúng các vi dịch vụ đang chạy trong lòng VPC. Luận văn đề xuất cách thức cài đặt như Hình 4.2:
Hình 4.2: Các máy trong Kubernetes cluster
4.1.3.2 Cài đặt cách thức thu thập và quản lý log
Các thức cài đặt hệ thống thu thập và quản lý log thể hiện trong Hình 4.3 dưới đây.
Hình 4.3: Cách thức cài đặt và quản lý log
Khi các vi dịch vụ chạy trong docker container, log của chúng được cấu hình để ghi ra tệp log tại các vị trí được xác định theo mẫu sau:
/var/log/msa/${TÊN_VI_DỊCH_VỤ}/${TÊN_POD}/
Các tệp log trong container sẽ được mount vào thư mục /var/log/msa trên chính các máy mà container chạy. Kubernetes sẽ tự động sinh ra tên pod ngẫu nhiên dựa vào tên dịch vụ được cung cấp, do vậy tên pod có thể thay đổi theo thời gian. Việc quy định như trên đảm bảo các tệp log của pod cũ vẫn còn tồn tại trên các node để phục vụ mục đích thu thập. Các fluentd agent được cài đặt trên tất cả các máy và được cấu hình
để đọc (tail) các tệp log ở các vị trí xác định, phân giải các trường thông tin trong log và tạo ra các luồng log để gửi tới một máy tập trung gọi là fluentd-aggregator. Về cơ bản, fluentd-aggregator cũng là một máy chạy fluentd bình thường; tuy nhiên, fluentd được cấu hình để lắng nghe sự kiện log gửi tới (tại một port xác định). Trên máy fluentd-aggregator, các sự kiện log sẽ được lọc, thêm các trường thông tin cần thiết, gửi tới hệ thống lưu trữ và đánh chỉ số Elasticsearch. Phần mềm Kibana được cấu hình trên một máy khác, kết nối tới Elastichsearch để truy vấn dữ liệu, từ đó hiển thị thông tin cho người dùng. Elasticsearch và Kibana được cài đặt như các dịch vụ hệ thống systemd.
4.1.3.3 Cài đặt hệ thống giám sát
Hình 4.4: Cách thức cài đặt Prometheus
Prometheus là thành phần quan trọng nhất trong hệ thống giám sát, có nhiệm vụ thu thập các thông tin về trạng thái của tất cả các máy và ứng dụng. Các thông tin cần thu thập bao gồm:
• Tài nguyên sử dụng trên các máy triển khai
• Trạng thái các đối tượng trong Kubernetes cluster
• Các thông tin khác được sinh ra bởi bản thân các ứng dụng
Prometheus được cài đặt ngay trong Kubernetes cluster, sử dụng docker container. Đối với thông tin tài nguyên sử dụng trên máy triển khai, luận văn sử dụng
dịch vụ node-exporter, cài đặt trên tất cả các máy. Các thông tin sau khi chuyển đổi sẽ được cung cấp tại port 9100 trên chính các máy đó, với endpoint là /metrics.
Đối với trạng thái các đối tượng trong Kubernetes cluster (như thông tin về pod, container, service), luận văn sử dụng kube-state-metrics, được cài đặt bằng cách sử dụng docker container trong Kubernetes cluster.
Đối với các trường thông tin khác sinh ra bởi ứng dụng, luận văn sử dụng cơ chế gắn các chú thích vào các docker container. Ví dụ: thước đo msa_request_count được tích hợp vào trong forex-service và ccs-service với 2 nhãn success, error để tính số lượng các yêu cầu thành công hay thất bại gửi tới chúng.
Prometheus được cấu hình để gửi thông tin cảnh báo tới Alert Manager khi trạng thái của hệ thống vượt qua ngưỡng nhất định. Từ Alert Manager, cảnh báo sẽ được gửi đi thông qua các kênh thông báo khác nhau (trong bài sử dụng Slack). Alert Manager trong luận văn được chạy bằng docker, tuy nhiên nằm riêng biệt với Kubernetes cluster.
ALERT high_error_request
IF msa_request_count{msa_request_count="error"} > 3 FOR 1m
LABELS { severity = "slack" } ANNOTATIONS {
summary = "Number of error request error is too high",
description = "Receives lots of error request%. Reported by instance {{
$labels.instance }} of job {{ $labels.job }}.", }
Trong luận văn, tất các các quá trình cài đặt hệ thống phức tạp này đều được viết bằng Ansible. Việc cài đặt trên một máy hay một nhóm các máy có thể được thực hiện nhanh chóng thông qua cấu hình trên một tệp duy nhất.
4.2 Đánh giá kết quả
Qua việc sử dụng luồng triển khai liên tục cho hệ thống phần mềm dựa vào vi dịch vụ cụ thể trong phần trước, ta có thể thấy một số lợi ích rõ rệt mà nó mang lại như:
• Giảm chu kỳ phát hành sản phẩm (Cycle Time)
• Tăng năng suất lao động
• Thỏa mãn được các yêu cầu cho đội phát triển
Với việc sử dụng mô hình triển khai không tùy biến, không có nhiều khác biệt giữa môi trường sản phẩm và môi trường local (tất cả các thư viện, các yêu cầu để một dịch vụ chạy đều đã được đóng gói vào trong ảnh container), do vậy các nhà phát triển hoàn toàn có thể tự tái hiện và kiểm tra lỗi trên chính máy đang sử dụng.
Ngoài ra, tất cả cấu hình máy triển khai được khai báo trong các tệp kịch bản Ansible, được lưu và quản lý như mã nguồn trên kho quản lý mã chung mà các nhà phát triển đều có thể truy cập. Bằng việc tích hợp EFK stack và sử dụng các công cụ
giám sát với giao diện thể hiện trực quan, những thông tin trước đây cần phải truy cập trực tiếp vào máy mới xem được đều hiện hữu trên giao diện người dùng.
Thiết kế hệ thống triển khai liên tục trong luận văn thực tế đã đáp ứng gần như tất cả yêu cầu mà bên phát triển mong muốn, đồng thời đã hạn chế được những rủi ro mà những phương pháp làm việc trước đó gây ra.