Tag: CICD

  • Sách dịch: Building Microservices: Designing Fine-Grained Systems – Chương 3 Mô hình hóa các dịch vụ

    Sách dịch: Building Microservices: Designing Fine-Grained Systems – Chương 3 Mô hình hóa các dịch vụ

    CHƯƠNG 3 Mô hình hóa các dịch vụ

    Lập luận của đối thủ khiến tôi nhớ đến một kẻ ngoại đạo, người, khi được hỏi về thế giới đang đứng trên cái gfi, đã trả lời: "Trên một con rùa." Nhưng con rùa đứng trên cái gì? "Trên một con rùa khác."

    —Joseph Barker (1854)

    Vì vậy, bạn biết microservices là gì và hy vọng hiểu được những lợi ích chính của chúng. Bây giờ có lẽ bạn đang háo hức muốn đi và bắt đầu tạo ra chúng, phải không? Nhưng bắt đầu từ đâu? Trong chương này, chúng ta sẽ xem xét cách suy nghĩ về ranh giới của các microservice của bạn, hy vọng sẽ tối đa hóa những ưu điểm và tránh một số nhược điểm tiềm ẩn. Nhưng trước tiên, chúng ta cần một cái gì đó để làm việc cùng nó.

    Giới thiệu về MusicCorp

    Sách về ý tưởng hoạt động tốt hơn với các ví dụ. Nếu có thể, tôi sẽ chia sẻ những câu chuyện từ các tình huống thực tế, nhưng tôi nhận thấy rằng việc có một lĩnh vực hư cấu để hoạt động cũng rất hữu ích. Xuyên suốt cuốn sách, chúng ta sẽ quay trở lại lĩnh vực này, xem khái niệm microservices hoạt động như thế nào trong thế giới này.

    Vì vậy, chúng ta hãy chuyển sự chú ý của chúng ta đến một trong những nhà bán lẻ trực tuyến tiên tiến nhất hiện nay – MusicCorp. MusicCorp gần đây là một nhà bán lẻ truyền thống, nhưng sau khi công ty từ bỏ mảng kinh doanh máy hát, mảng kinh doanh nền tảng của MusicCorp, họ ngày càng tập trung nhiều hơn vào việc kinh doanh trực tuyến. Công ty có một trang web, nhưng cảm thấy rằng bây giờ là lúc để tăng gấp đôi doanh số trên các nền tảng trực tuyến. Xét cho cùng, những chiếc iPod đó chỉ là một mốt nhất thời (rõ ràng là Zunes tốt hơn nhiều) và những người hâm mộ âm nhạc đang khá vui khi chờ đĩa CD được chuyển tới nhà của họ. Chất lượng bao giờ cũng hơn sự tiện lợi, phải không? Và trong khi chúng ta đang ở đó, điều mà mọi người vẫn tiếp tục về Spotify này là gì — một nền tảng hướng đến đối tượng thanh thiếu niên?

    Mặc dù đi sau một chút so với phần còn lai, nhưng MusicCorp lại có tham vọng lớn. May mắn thay, họ đã quyết định rằng cơ hội tốt nhất để chiếm lấy thị phần là đảm bảo rằng nó có thể thực hiện những thay đổi dễ dàng nhất có thể. Microservices giành chiến thắng!

    Điều gì tạo nên một dịch vụ tốt?

    Trước khi nhóm từ MusicCorp thay đổi chính mình, tạo ra dịch vụ này đến dịch vụ khác trong nỗ lực cung cấp loại băng 8 bản nhạc cho tất cả mọi người và một vài thứ khác, hãy bắt đầu và nói một chút về ý tưởng cơ bản quan trọng nhất mà chúng ta cần ghi nhớ. Điều gì tạo nên một dịch vụ tốt? Nếu bạn vẫn sống sót sau một lần deploy SOA không thành công, bạn có thể có một số ý tưởng về nơi tiếp theo mà tôi nói đến. Nhưng đề phòng trong trường hợp bạn không phải là kẻ may mắn hoặc giả sử bạn chính là kẻ may mắn, tôi muốn bạn tập trung vào hai khái niệm chính: liên kết lỏng lẻotính liên kết cao (loose coupling and high cohesion). Chúng tôi sẽ nói chi tiết trong suốt cuốn sách về những ý tưởng và thực tiễn khác, nhưng chúng đều vô ích nếu chúng tôi làm sai hai điều này.

    Mặc dù thực tế là hai thuật ngữ này được sử dụng rất nhiều, đặc biệt là trong ngữ cảnh của các hệ thống hướng đối tượng, nhưng điều đáng bàn về ý nghĩa của chúng đối với microservices là gì.

    Liên kết lỏng lẻo

    Khi các dịch vụ được kết hợp một cách lỏng lẻo, một sự thay đổi đối với một dịch vụ không yêu cầu thay đổi một dịch vụ khác. Toàn bộ quan điểm của microservice là có thể thực hiện thay đổi đối với một dịch vụ và deploy nó mà không cần thay đổi bất kỳ phần nào khác của hệ thống. Điều này thực sự khá quan trọng.

    Những thứ gì gây ra sự kết hợp chặt chẽ? Một sai lầm cổ điển là chọn một phong cách tích hợp liên kết chặt chẽ giữa dịch vụ này với dịch vụ khác, khiến những thay đổi bên trong dịch vụ đòi hỏi nhưng nơi sử dụng nó phải thay đổi. Chúng ta sẽ thảo luận sâu hơn về cách tránh điều này trong Chương 4.

    Một dịch vụ được kết hợp một cách lỏng lẻo cần biết rất ít về những dịch vụ mà nó cộng tác cùng. Điều này cũng có nghĩa là chúng tôi có thể muốn giới hạn số lượng các loại yêu cầu/gọi chức năng khác nhau từ dịch vụ này sang dịch vụ khác, bởi vì ngoài vấn đề tiềm tàng về hiệu suất, giao tiếp kiểu này có thể dẫn đến kết nối chặt chẽ.

    Độ gắn kết cao

    Chúng tôi muốn hành vi liên quan thi đi cùng nhau và hành vi không liên quan nên ở nơi khác. Tại sao? Vâng, nếu chúng ta muốn thay đổi hành vi, chúng ta muốn có thể thay đổi nó ở một nơi và giải phóng thay đổi đó càng sớm càng tốt. Nếu chúng tôi phải thay đổi hành vi đó ở nhiều nơi khác nhau, chúng tôi sẽ phải release nhiều dịch vụ khác nhau (có thể cùng một lúc) để thực hiện thay đổi đó. Việc thực hiện các thay đổi ở nhiều nơi khác nhau sẽ chậm hơn và việc deploy nhiều dịch vụ cùng một lúc là rất rủi ro — cả hai điều này chúng tôi đều muốn tránh.

    Vì vậy, chúng tôi muốn tìm các ranh giới của các vấn đề trong lĩnh vực của mình để giúp đảm bảo rằng hành vi liên quan sẽ ở cùng một nơi và giao tiếp với các ranh giới khác một cách lỏng lẻo nhất có thể.

    Bối cảnh có giới hạn

    Cuốn sách của Eric Evans về Thiết kế theo hướng lĩnh vực – Domain-Driven Design (Addison-Wesley) tập trung vào cách tạo hệ thống mô hình hóa các lĩnh vực trong thế giới thực. Cuốn sách chứa đầy những ý tưởng tuyệt vời như sử dụng ngôn ngữ phổ biến, sự trừu tượng của kho lưu trữ, và những thứ tương tự, nhưng có một khái niệm rất quan trọng mà Evans giới thiệu lúc đầu đã hoàn toàn lướt qua tôi: bối cảnh có giới hạn. Ý tưởng ở đây là bất kỳ lĩnh vực cụ thể nào đều bao gồm nhiều bối cảnh có giới hạn và nằm trong mỗi bối cảnh là những thứ (Eric sử dụng mô hình – model từ rất nhiều, có lẽ tốt hơn là những thứ – things) không cần giao tiếp với bên ngoài cũng như những thứ được chia sẻ ra bên ngoài với các bối cảnh có giới hạn khác. Mỗi bối cảnh có giới hạn có một giao diện rõ ràng, nơi nó quyết định những mô hình nào sẽ chia sẻ với các bối cảnh khác.

    Một định nghĩa khác về bối cảnh có giới hạn mà tôi thích là "một trách nhiệm cụ thể được thực thi bởi các ranh giới rõ ràng." Nếu bạn muốn thông tin từ bối cảnh có giới hạn hoặc muốn đưa ra các yêu cầu về chức năng trong bối cảnh có giới hạn, bạn giao tiếp với các ranh giới rõ ràng của nó bằng cách sử dụng các mô hình. Trong cuốn sách của mình, Evans sử dụng một định nghĩa tương tự của các tế bào, nơi mà "[c]ells (tế bào) có thể tồn tại bởi vì màng của chúng xác định những gì ra/vào và xác định những gì có thể đi qua."

    Hãy quay lại một chút với công việc kinh doanh của MusicCorp. Lĩnh vực của chúng tôi là toàn bộ hoạt động kinh doanh mà chúng tôi có. Nó bao gồm tất cả mọi thứ từ nhà kho đến bàn tiếp tân, từ tài chính đến đặt hàng. Chúng tôi có thể hoặc không thể mô hình hóa tất cả những điều đó trong phần mềm của mình, nhưng đó vẫn là thứ mà chúng tôi đang điều hành. Chúng ta hãy nghĩ về các phần của lĩnh vực đó trông giống như bối cảnh có giới hạn mà Evans đề cập đến. Tại MusicCorp, kho hàng của chúng tôi là một tổ hợp nhiều hoạt động — quản lý các đơn hàng được chuyển đi (và trả lại), nhận hàng mới, tổ chức các cuộc đua xe nâng, v.v. Ở những nơi khác, bộ phận tài chính có lẽ ít thú vị hơn, nhưng vẫn có một chức năng rất quan trọng trong tổ chức của chúng tôi. Những nhân viên này quản lý bảng lương, giữ các tài khoản của công ty và đưa ra các báo cáo quan trọng. Rất nhiều báo cáo. Chúng có lẽ cũng có những món đồ chơi trên bàn thú vị.

    Các mô hình được chia sẻ và mô hình ẩn

    Đối với MusicCorp, chúng ta có thể coi bộ phận tài chính và kho hàng là hai bối cảnh có giới hạn riêng biệt. Cả hai đều có giao diện rõ ràng với thế giới bên ngoài (về báo cáo hàng tồn kho, phiếu thanh toán, v.v.) và chúng có thông tin chi tiết mà chỉ họ cần biết (xe nâng, máy tính).

    Giờ đây, bộ phận tài chính không cần biết về hoạt động chi tiết bên trong của nhà kho. Tuy nhiên, nó cần phải biết một số điều — ví dụ như nó cần biết về lượng hàng tồn kho để giữ cho các tài khoản được cập nhật. Hình 3-1 cho thấy một ví dụ về sơ đồ bối cảnh (context diagram). Chúng tôi thấy các khái niệm nội bộ của nhà kho, như Người chọn (người chọn đơn hàng), giá đang chứa hàng hóa, v.v. Tương tự như vậy, sổ cái tổng của công ty là không thể thiếu đối với tài chính nhưng không được chia sẻ ra bên ngoài ở đây.

    Hình 3-1. Mô hình chia sẻ giữa bộ phận tài chính và kho hàng

    Tuy nhiên, để có thể xác định giá trị của công ty, các nhân viên tài chính cần thông tin về cổ phiếu mà chúng tôi nắm giữ. Mặt hàng tồn kho sau đó trở thành một mô hình được chia sẻ giữa hai bối cảnh. Tuy nhiên, lưu ý rằng chúng ta không cần phải tiết lộ mọi thứ về mặt hàng trong kho từ bối cảnh kho hàng một cách mù quáng. Ví dụ: mặc dù nội bộ chúng tôi lưu giữ hồ sơ về một mặt hàng trong kho về nơi nó sẽ được đặt trong nhà kho, nhưng mặt hàng đó không cần phải được tiết lộ thông tin đó trong mô hình dùng chung. Vì vậy, sẽ có sự phân biệt giữa đại diện chỉ bên trong và đại diện bên ngoài mà chúng tôi đưa ra. Theo nhiều cách, điều này báo trước về cuộc thảo luận xung quanh REST trong Chương 4.

    Đôi khi chúng ta có thể bắt gặp các mô hình có cùng tên nhưng có ý nghĩa rất khác nhau trong các bối cảnh khác nhau. Ví dụ: chúng ta có thể có khái niệm trả lại, đại diện cho việc khách hàng gửi lại thứ gì đó. Trong bối cảnh của khách hàng, trả lại là tất cả những gì liên quan đến việc in nhãn vận chuyển, gửi một gói hàng và chờ hoàn lại tiền. Đối với nhà kho, điều này có thể đại diện cho một gói hàng sắp được chuyển đến và một mặt hàng trong kho cần được bổ sung. Sau đó, trong kho chúng tôi lưu trữ thông tin bổ sung liên quan đến việc trả lại liên quan đến các nhiệm vụ sẽ được thực hiện; ví dụ: chúng tôi có thể tạo một yêu cầu khôi phục lại. Kết quả của mô hình chia sẻ là các quy trình khác nhau sẽ trở nên liên kết và hỗ trợ các thực thể trong mỗi bối cảnh có giới hạn, nhưng đó là mối quan tâm nội bộ trong chính bối cảnh đó.

    Mô-đun và Dịch vụ

    Bằng cách suy nghĩ rõ ràng về những mô hình nào nên được chia sẻ và không chia sẻ các đại diện nội bộ của chúng tôi, chúng tôi tránh được một trong những cạm bẫy tiềm ẩn có thể dẫn đến kết hợp chặt chẽ (ngược lại với những gì chúng tôi muốn). Chúng tôi cũng đã xác định một ranh giới trong lĩnh vực của chúng tôi, nơi tất cả các khả năng kinh doanh có cùng chí hướng sẽ tồn tại, mang lại cho chúng tôi sự gắn kết cao mà chúng tôi mong muốn. Những bối cảnh có giới hạn này, sau đó, tự cho mình là những ranh giới sáng tác cực kỳ tốt.

    Như chúng ta đã thảo luận trong Chương 1, chúng ta có tùy chọn sử dụng các mô-đun trong một ranh giới quy trình để giữ các đoạn mã nguồn liên quan lại với nhau và cố gắng giảm sự ghép nối với các mô-đun khác trong hệ thống. Khi bạn bắt đầu trên một mã nguồn cơ sở mới, đây có là một khởi đầu tốt. Vì vậy, khi bạn đã tìm thấy các bối cảnh trong lĩnh vực của mình, hãy đảm bảo rằng chúng được mô hình hóa trong mã nguồn cơ sở của bạn dưới dạng mô-đun, với các mô hình được chia sẻ và ẩn.

    Các ranh giới mô-đun này sau đó trở thành ứng viên xuất sắc cho các microservice. Nói chung, microservices cần phù hợp trong bối cảnh nhất định. Một khi bạn đã trở nên rất thành thạo, bạn có thể quyết định bỏ qua bước giữ cho bối cảnh có giới hạn được sử dụng như một mô-đun trong một hệ thống monolithic hơn và chuyển thẳng sang tạo ra một dịch vụ riêng biệt. Tuy nhiên, khi bắt đầu, hãy giữ một hệ thống mới ở khía cạnh gần giống monolithic hơn; việc sai lầm trong việc nhìn nhận ranh giới của các dịch vụ có thể gây hậu quả tốn kém, vì vậy hãy chờ đợi mọi thứ ổn định cho đến khi khi bạn nắm bắt được lĩnh vực mới, đó một là điều hợp lý. Chúng ta sẽ thảo luận thêm về vấn đề này trong Chương 5, cùng với các kỹ thuật giúp tách các hệ thống hiện có thành các microservice.

    Vì vậy, nếu các ranh giới dịch vụ của chúng tôi phù hợp với các bối cảnh được giới hạn trong lĩnh vực của chúng tôi và microservices của chúng tôi đại diện cho các bối cảnh, chúng tôi đã có một khởi đầu tuyệt vời trong việc đảm bảo rằng các microservice của chúng tôi được liên kết lỏng lẻo và gắn kết chặt chẽ.

    Phân rã sớm

    Tại ThoughtWorks, bản thân chúng tôi đã trải qua những thách thức khi cố gắng phân chia các microservice quá nhanh. Bên cạnh việc tư vấn, chúng tôi cũng tạo ra một vài sản phẩm. Một trong số đó là SnapCI, một công cụ tích hợp liên tục (Continuous Integration) được cài đặt và phân phối liên tục (Continuous Delivery – Sau đây cũng viết là Continuous Delivery hoặc CD) (chúng ta sẽ thảo luận về các khái niệm đó sau trong Chương 6). Trước đây, nhóm đã làm việc trên một công cụ tương tự khác, Go-CD, một công cụ Continuous Delivery mã nguồn mở hiện có thể được deploy cục bộ thay vì được cài đạt trên nền tảng điện toán đám mây.

    Mặc dù đã có một số mã nguồn được sử dụng lại từ rất sớm giữa các dự án SnapCI và Go-CD, nhưng cuối cùng SnapCI lại trở thành một mã nguồn cơ sở hoàn toàn mới. Tuy nhiên, kinh nghiệm trước đây của nhóm trong lĩnh vực công cụ CD khuyến khích họ tiến nhanh hơn trong việc xác định ranh giới và xây dựng hệ thống của họ như một tập hợp của các microservice.

    Tuy nhiên, sau một vài tháng, rõ ràng là các trường hợp sử dụng của SnapCI đã khác nhau một cách tinh tế đến mức việc tiếp nhận các ranh giới dịch vụ ban đầu là không hoàn toàn đúng. Điều này dẫn đến nhiều thay đổi được thực hiện trên các dịch vụ và tất nhiên chi phí cho sự thay đổi cùng gia tăng. Cuối cùng, nhóm đã hợp nhất các dịch vụ trở lại thành một hệ thống monolithic, cho họ thời gian để hiểu rõ hơn về ranh giới nên tồn tại. Một năm sau, nhóm nghiên cứu đã có thể tách hệ thống monolithic thành các microservices, có ranh giới ổn định hơn nhiều. Đây không phải là ví dụ duy nhất về tình huống này mà tôi đã thấy. Việc phân ra sớm một hệ thống thành microservices có thể tốn kém, đặc biệt nếu bạn là người mới tham gia vào lĩnh vực. Theo nhiều cách, việc có một codebase tồn tại mà bạn muốn phân rã thành microservice dễ dàng hơn nhiều so với việc cố gắng xây dựng microservice ngay từ đầu.

    Năng lực liên quan đến công việc kinh doanh

    Khi bạn bắt đầu nghĩ về các bối cảnh và của chúng giới hạn tồn tại trong tổ chức của mình, bạn không nên nghĩ theo cách dữ liệu được chia sẻ, mà là về khả năng mà các bối cảnh đó cung cấp cho phần còn lại của lĩnh vực. Ví dụ, nhà kho có thể cung cấp khả năng có được danh sách hàng tồn kho hiện tại hoặc bối cảnh tài chính có thể hiển thị các tài khoản cuối tháng hoặc cho phép bạn thiết lập bảng lương cho một đợt tuyển dụng mới. Những khả năng này có thể yêu cầu trao đổi thông tin — các mô hình được chia sẻ — nhưng tôi đã thấy một cách quá thường xuyên việc suy nghĩ về dữ liệu dẫn đến các dịch vụ không có hoạt động gì liên quan đến việc hoạt động của công ty, và chỉ dựa trên các CRUD (tạo, đọc, cập nhật, xóa). Vì vậy, trước tiên hãy đặt hỏi "Bối cảnh này làm gì?", Sau đó là "Vậy nó cần dữ liệu gì để làm điều đó?"

    Khi được mô hình hóa dưới dạng dịch vụ, các khả năng này trở thành các hoạt động chính sẽ được hiển thị qua một đường dây với các thành phần khác

    Một vấn đề lặp lại vô tận

    Nguyên văn: Turtles All the Way Down là một câu thể hiện một vấn đề lặp lại một cách vô tận. https://en.wikipedia.org/wiki/Turtles_all_the_way_down

    Khi bắt đầu, bạn có thể sẽ xác định được các bối cảnh một cách chi tiết. Nhưng những bối cảnh này đến lượt nó có thể chứa những bối cảnh khác. Lấy ví dụ, bạn có thể phân chia bối cảnh của nhà kho thành các khả năng liên quan đến hoàn thành đơn đặt hàng, quản lý hàng tồn kho hoặc nhận hàng hóa. Khi xem xét ranh giới của các microservice, trước tiên hãy nghĩ đến các bối cảnh lớn hơn, ít chi tiết hơn, sau đó chia nhỏ theo các bối cảnh lồng nhau này khi bạn đang tìm kiếm lợi ích của việc tách các đường nối giữa chúng ra.

    Tôi đã gặp các bối cảnh lồng nhau vẫn bị ẩn trong các bối cảnh khác, hãy kết hợp các microservice với nhau để tạo ra hiệu quả tuyệt vời. Đối với thế giới thực, họ vẫn đang sử dụng các năng lực mà nhà kho cung cấp, nhưng họ không biết rằng các yêu cầu của họ đang thực sự được ánh xạ một cách minh bạch tới hai hoặc nhiều dịch vụ riêng biệt, như bạn có thể thấy trong Hình 3-2. Đôi khi, bạn sẽ quyết định rằng sẽ hợp lý hơn khi bối cảnh giới hạn cấp cao hơn không được mô hình hóa một cách rõ ràng như một ranh giới dịch vụ, như trong Hình 3-3, vì vậy thay vì một ranh giới trong bối cảnh nhà kho duy nhất, thay vào đó bạn có thể chia nhỏ ra thành hàng tồn kho, thực hiện đơn đặt hàng, và nhận hàng hóa.

    Hình 3-2. Microservices đại diện cho các bối cảnh được giới hạn lồng nhau ẩn bên trong nhà kho
    Hình 3-3. Các bối cảnh giới hạn bên trong nhà kho được bật lên thành bối cảnh cấp cao nhất của riêng chúng

    Nói chung, không có quy tắc nào quá nghiêm ngặt về cách tiếp cận nào phù hợp nhất. Tuy nhiên, việc bạn chọn cách tiếp cận lồng vào nhau hay tiếp cận theo cách tách biệt hoàn toàn phải dựa trên cơ cấu tổ chức của bạn. Nếu việc thực hiện đơn hàng, quản lý hàng tồn kho và nhận hàng được quản lý bởi các nhóm khác nhau, thì họ có thể nên được coi là microservice cấp cao nhất. Mặt khác, nếu tất cả chúng được quản lý bởi một nhóm, thì mô hình lồng nhau sẽ có ý nghĩa hơn. Điều này là do sự tác động lẫn nhau của cấu trúc tổ chức và kiến trúc phần mềm, mà chúng ta sẽ thảo luận ở phần cuối của cuốn sách trong Chương 10.

    Một lý do khác để cách tiếp cận lồng nhau được ưa thích hơn có thể là chia nhỏ kiến trúc của bạn để đơn giản hóa việc kiểm thử. Ví dụ: khi kiểm thử các dịch vụ sử dụng dịch vụ kho hàng, tôi không phải phân tích từng dịch vụ bên trong bối cảnh của kho hàng, mà chỉ là nhưng API ít chi tiết hơn. Điều này cũng có thể cung cấp cho bạn một đơn vị cô lập khi xem xét các bài kiểm thử trên phạm vi lớn hơn. Ví dụ: tôi có thể quyết định thực hiện các kiểm thử từ đầu đến cuối trong đó tôi khởi chạy tất cả các dịch vụ bên trong bối cảnh kho hàng, nhưng đối với tất cả các service liên quan khác, tôi có thể bỏ qua chúng. Chúng ta sẽ khám phá thêm về kiểm thử và cách ly trong Chương 7.

    Vấn đề giao tiếp trong các hoạt động của doanh nghiệp

    Những thay đổi mà chúng tôi thực hiện đối với hệ thống của mình thường là những thay đổi mà doanh nghiệp muốn thực hiện về cách hệ thống hoạt động. Chúng tôi đang thay đổi chức năng — khả năng — được hiển thị cho khách hàng của chúng tôi. Nếu hệ thống của chúng tôi được phân tách theo các bối cảnh có giới hạn đại diện cho lĩnh vực của chúng tôi, thì những thay đổi chúng tôi muốn thực hiện có nhiều khả năng bị tách biệt trong một ranh giới microservice duy nhất. Điều này làm giảm số lượng điểm mà chúng tôi cần thực hiện thay đổi và cho phép chúng tôi deploy thay đổi đó một cách nhanh chóng.

    Điều quan trọng nữa là phải nghĩ đến sự giao tiếp giữa các dịch vụ nhỏ này theo cùng một cách hiểu về hoạt động trong doanh nghiệp. Việc mô hình hóa phần mềm của bạn và sau đó là toàn bộ công việc kinh doanh không nên dừng lại ở ý tưởng về các bối cảnh có giới hạn. Các định nghĩa và ý tưởng giống nhau được chia sẻ giữa các bộ phận trong tổ chức của bạn phải được phản ánh trong các giao diện. Có thể hữu ích khi nghĩ về các biểu mẫu được gửi giữa các dịch vụ nhỏ này, giống như các biểu mẫu được trong cả tổ chức

    Ranh giới về kỹ thuật

    Việc xem xét những gì có thể xảy ra khi các dịch vụ được mô hình hóa không chính xác là việc làm hữu ích. Trước đây, tôi cùng một số đồng nghiệp đã làm việc với một khách hàng ở California, giúp công ty áp dụng một số phương pháp lập trình rõ ràng hơn và hướng tới tự động kiểm thử nhiều hơn. Chúng tôi đã bắt đầu với một số công việc đơn giản và có đã kết quả, chẳng hạn như phân tích dịch vụ, khi đó chúng tôi nhận thấy điều gì đó đáng lo ngại hơn nhiều. Tôi không thể đi quá chi tiết về những gì ứng dụng đã làm, nhưng đó là một ứng dụng công khai với lượng lớn khách hàng toàn cầu.

    Đội ngũ và hệ thống đã ngày càng lớn mạnh. Ban đầu chỉ là tầm nhìn của một người, hệ thống đã sử dụng ngày càng nhiều tính năng và ngày càng có nhiều người dùng hơn. Cuối cùng, tổ chức quyết định nâng cao năng lực của nhóm bằng cách để một nhóm các developer mới có trụ sở tại Brazil đảm nhận một số công việc. Hệ thống được tách ra, với nửa phía trước của ứng dụng về cơ bản là stateless, deploy một trang web công khai, như trong Hình 3-4. Phần còn lại đơn giản là sử dụng kĩ thuật gọi thủ tục từ xa (RPC) trên một kho dữ liệu. Về cơ bản, hãy tưởng tượng bạn đã sử dụng một lớp kho lưu trữ (repository layer) trong mã nguồn cơ sở của mình và biến đây thành một dịch vụ riêng biệt.

    Hình 3-4. Ranh giới dịch vụ được phân chia qua đường nối kỹ thuật

    Các thay đổi thường xuyên phải được thực hiện cho cả hai dịch vụ. Cả hai dịch vụ đều nói chuyện với nhau qua các lệnh gọi phương thức kiểu RPC cấp thấp, mà kiểu kết nối này rất cứng nhắc nhưng dễ đứt gãy (chúng ta sẽ thảo luận về vấn đề này trong Chương 4). Giao diện dịch vụ cũng được sử dụng theo cách rất tùy tiện, dẫn đến vấn đề về hiệu suất. Điều này dẫn đến nhu cầu về các cơ chế xử lý RPC theo lô rất phức tạp. Tôi gọi đây là kiến trúc củ hành (onion architecture), vì nó có rất nhiều lớp và khiến tôi phải khóc khi phải cắt qua nó.

    Bây giờ về mặt nổi của nó, ý tưởng tách hệ thống monolithic trước đây dưa trên các nhóm địa lý/tổ chức là hoàn toàn hợp lý, vì chúng tôi sẽ mở rộng trong Chương 10. Tuy nhiên, ở đây, thay vì thực hiện theo chiều dọc, tập trung vào các công việc của nghiệp vụ thông qua ngăn xếp, nhóm đã chọn những gì trước đây là đã được tạo ra thành API và tạo ra một lát cắt ngang.

    Không phải lúc nào cũng đưa ra quyết định để lập mô hình ranh giới dịch vụ dọc theo các đường nối kỹ thuật cũng sai lầm. Tôi chắc chắn đã thấy điều này rất có ý nghĩa khi một tổ chức đang tìm cách đạt được các mục tiêu hiệu suất nhất định, chẳng hạn. Tuy nhiên, việc tìm kiếm các đường nối này chỉ là động lực phụ của bạn chứ không phải mục tiêu chính.

    Tóm tắt

    Trong chương này, bạn đã tìm hiểu một chút về điều gì tạo nên một dịch vụ tốt và cách tìm các đường nối trong không gian vấn đề của chúng tôi, mang lại cho chúng tôi lợi ích kép của cả khớp nối lỏng và tính liên kết cao. Các bối cảnh có giới hạn là một công cụ quan trọng giúp chúng ta tìm ra các đường nối này và bằng cách sắp xếp các microservice của chúng ta theo các ranh giới này, chúng ta cần đảm bảo rằng hệ thống cuối cùng có mọi cơ hội để giữ nguyên các tính năng đó. Chúng ta cũng đã có một gợi ý về cách chúng ta có thể chia nhỏ các microservice của mình hơn nữa, điều gì đó chúng ta sẽ khám phá sâu hơn sau. Và chúng ta cũng đã giới thiệu MusicCorp, lĩnh vực ví dụ mà chúng ta sẽ sử dụng trong suốt cuốn sách này.

    Những ý tưởng được trình bày trong Thiết kế theo hướng lĩnh vực – DDD của Eric Evans rất hữu ích cho chúng ta trong việc tìm ra ranh giới hợp lý cho các dịch vụ và tôi mới chỉ sơ lược ở đây. Tôi giới thiệu cuốn sách Implementing Domain-Driven Design (Addison- Wesley) của Vaughn Vernon để giúp bạn hiểu tính thực tiễn của phương pháp này.

    Mặc dù chương này chủ yếu là những thứ mang tính tổng quát, nhưng chúng ta cần đi sâu vào các kỹ thuật hơn trong các chương tiếp theo. Có rất nhiều cạm bẫy liên quan đến việc deploy giao diện giữa các dịch vụ, mà chúng có thể dẫn đến tất cả các loại rắc rối và chúng ta sẽ phải đi sâu vào chủ đề này nếu muốn giữ cho hệ thống của mình không trở thành một gã khổng lồ, rối rắm lộn xộn.

  • Gitlab CI/CD cho người mới bắt đầu – Phần 2: Configure Runner

    Gitlab CI/CD cho người mới bắt đầu – Phần 2: Configure Runner

    Xin chào mọi người,
    Chúng ta đã quá lâu không gặp lại nhau rồi đúng không?
    Như đã nói từ kì trước, để tiếp tục series "Gitlab CI/CD cho người mới bắt đầu", thì ở bài này mình sẽ viết về cách set-up local gitlab runner, cụ thể hơn, chúng ta sẽ setting 1 runner qua Docker.

    Tại sao lại là Docker chứ không phải là trên Window hay MacOS? Mình chọn Docker vì nó đơn giản với những người mới, cộng với tính linh động của nó trên nhiều môi trường khác nhau. Vì vậy, nên để có thể tiếp tục với tutorial này thì việc đầu tiên bạn cần làm là có docker ở trên máy của mình. (Bạn có thể xem hướng dẫn tải và cài đặt tại đây) Nếu bạn đã cài đặt thành công Docker trên máy, thì chúng ta sẽ bắt đầu vào phần chính ngay thôi.

    Về cơ bản thì việc khởi tạo và chạy runner qua Docker này sẽ trải qua 2 bước: (Chi tiết có thể xem tại đây)

    Bước 1: Khởi tạo và chạy gitlab runner

    https://docs.gitlab.com/runner/install/  
    docker run -d --name gitlab-runner --restart always \
      -v gitlab-runner:/etc/gitlab-runner \
      -v /var/run/docker.sock:/var/run/docker.sock \
      gitlab/gitlab-runner:latest 
    

    Bước 2: Đăng kí gitlab runner đó với project mà chúng ta muốn sử dụng

    docker exec -it gitlab-runner bash 
    
    gitlab-runner register -n  --name GitLabRunner  \
    --executor docker  --docker-image docker:latest  \
    --docker-volumes /var/run/docker.sock:/var/run/docker.sock  \
    --url ${GITLAB_URL}  \
    --registration-token ${GITLAB_TOKEN} \
    --tag-list cicd_tag
    

    Ở bước này chúng ta sẽ cần config 2 biến env trong phần variable tại repo gitlab,

    • Biến thứ nhất GITLAB_URL chính là URL tới repo cần config runners,
    • Biến thứ 2 GITLAB_TOKEN là secret token để runner kết nối với repo trên gitlab. Gitlab Token & Gitlab Url

    (Ngoài ra các bạn có thế xem thêm các cài đặt nâng cao khi đăng kí runner tại đây)

    Khi đăng kí runner như trên, bạn sẽ cần chú ý vào tag-list, tại đây thì runner sẽ nhận diện những job nào được gắn tag đó để khởi chạy!

    Đó, và như vậy là chúng ta đã đăng kí thành công local runner cho repo gitlab. Thật đơn giản phải không? =)))

    Stay tuned and keep waiting for our next articles nhớ <3.
    Xin chào thân ái và quyết thắng tới mn!

  • Robustness or Resilience?

    Robustness or Resilience?

    Migrated to https://athena.wingadium.space/writing/robustness_or_resilience

    Nếu phần mềm là một vật thể bạn mong muốn chúng như một chai thủy tinh rất cứng hay một sợi dây cao su có khả năng co giãn?

    Đầu tiên nói đề cập thế này có vẻ hơi khó hiểu, hãy quay trở lại với bài viết: Software Architecture: Bắt đầu từ đâu? – Part 3 Soft Skills – Continuous Delivery. Chúng ta có một vài metric liên quan đến Continuous Delivery, hãy để ý đến 2 cái Mean Time Between Failures (MTBF) và Mean Time To Repair (MTTR).

    Robustness, một quan niệm khá xưa trong thế giới phần mềm, người ta muốn quan tâm đến việc phần mềm ít xảy ra sự cố hơn là thời gian khắc phục nó (ngay cả ở môi trường thương mại – production), tức là MTBF dài hơn được coi trọng hơn MTTR ngắn. Cách tiếp cận này đôi khi được coi là truyền thống, và thường các phần mềm này sẽ không được apply Continuous Delivery trong quy trình phát triển.

    Resilience, khả năng phục hồi, thì ngược lại MTTR sẽ được coi trọng hơn, một cách tiếp cận để hạn chế tác động của sự cố phần mềm và phần mềm có thể hoạt động trong các điều kiện khác nhau về phần cứng cũng như cơ sở hạ tầng.

    Một cách suy nghĩ đơn giản như này, với Robustness phần mềm khó có cơ hội apply Continuous Delivery, vì khi một phần mềm đòi hỏi ít sự cố, tức là số người trong chuỗi ra quyết định sẽ dài hơn (quan liêu), các stage cuối cùng của CD Pipeline thường ít được sử dụng do cần thời gian dài để xử lý qua các stage trước đó.

    Khi một phần mềm có xu hướng chuyển dần sang Resilience thì chúng ta có thể giảm effort để quản lý rủi ro (vì sự cố và lỗi được xử lý liên tục), và dần chuyển sang CD.

    Robustness – Truyền thống

    Dễ thấy là trong 1 2 chục năm trước, phần mềm hoàn hảo luôn là xu thế, các tổ chức luôn muốn phần mềm có độ tin cậy cao, tức là MTBF/MTTR khá lớn. Các dự án phần mềm theo kiểu này luôn muốn duy trì một môi trường production không có sự cố, với một niềm tin rằng môi trường production luôn được xử lý theo các ẩn số đã biết, trong đó các quá trình tương tác với môi trường luôn đồng nhất và có thể dự đoán được. Sự cố trong các phần mềm này luôn được cho là do các thay đổi (source code, biến môi trường, các thông số phần cứng), và có thể dự đoán được.

    Trong một vài dự án với các khách hàng mang xu thế cổ điển, họ thường đòi hỏi chúng tôi chỉ ra tất cả các kịch bản có thể có với hệ thống phần mềm mới được xây dựng???

    Trong trường hợp đó, chúng ta chỉ có thể xử lý theo cách như sau:

    image

    • End-to-end testing để xác minh chức năng trong phiên bản mới mới cùng với các servioce/component phụ thuộc.
    • Change Boards: một bộ sậu các phòng ban để quyết định đưa phiên bản lên môi trường production
    • Freeze: đóng băn phần mềm để hạn chế thay đổi trong một khoảng thời gian.

    Đầu tiên chúng ta có thể nghĩ rằng, tại sao việc chú trọng vào phần mềm không lỗi lại là vấn đề trong khi nó là một yêu cầu khá hợp lý, nhưng hãy xem xét, 3 practice trên đều chậm, đó là vấn đề của việc quản lý rủi ro trong Continuous Delivery. End-to-end testing đòi hỏi chi phí và thời gian dài để vét cạn các lỗi, đồng thời khi đến giai đoạn System test thường sẽ cần thời gian down time đáng kể để cô lập các vấn đề ở môi trường production. Change Boards sẽ dẫn đến một cơ chế quan liêu để đưa ra được quyết định cho phiên bản mới. cả 2 practice đều chậm và vẫn có khả năng sự cố, tức là chúng ta không thể thấy được các ẩn số chưa biết trong hệ thống phức tạp (Complex domain – Cynefin framework, còn chúng ta mới chỉ để ý đến Complex Domain)

    image

    Freeze, đương nhiên rồi, lead time quá dài.

    Thời gian trong việc phát triển phần mềm hiện nay luôn là yếu tố quan trọng, chậm trễ vài tháng thậm chí chỉ là vài tuần sẽ làm lỡ các phiên bản update của library, phần cứng hay ngôn ngữ, việc đó sẽ gia tăng rủi ro khi chúng biến thành các yêu cầu thay đổi về công nghệ, phiên bản vì có quá nhiều việc phải xử lý trong một lần update phần mềm.

    Từ đó có thể thây rằng, Robustness hoàn toàn không thể đáp ứng nhu cầu về mặt kinh doanh của phần mềm, vì sự hạn chế về sự ổn định và số lượng phiên bản, điều đó lý giải tại sao nhiêu dự án phần mềm không thể xử lý được vấn đề Continuous Delivery.

    Khi sự cố là điều không thể tránh khỏi

    Vấn đề là thường khi chúng ta quá chú ý đến Robustness, phần mềm thường quá chặt chẽ và dẫn tới việc phần mềm đó gần như không có khả năng xử lý khi có failure, tức là cả hệ thống sụp đổ, thay vì một phần nhỏ.

    Chúng ta có thể thấy như này khi một dự án chú ý đến Robustness, nhưng họ lại bị thúc ép bởi các nhu cầu mang tính ngắn hạn (nhanh, rẻ, tốt) thì các nhu cầu cơ bản về an toàn, high availability sẽ bị bỏ qua. Dự án sẽ tập trung rất nhiều nguồn lực vào môi trường production, các non-functional requirement thường sẽ bị bỏ qua, không có các dự tính dài hạn cho môi trường thực tế khi user bắt đầu sử dụng, hạ tầng thường dễ bị sụp đổ (fragile) bởi các yếu tố bất ngờ về người dùng (số lượng người dùng gia tăng đột biến). Nhưng ngược lại họ và đối tác thường chấp nhận vì hệ thống hiếm khi xảy ra sự cố.

    Tuy nhiên, thật là production environment không hẳn là một hệ thống phức tạp mà chúng ta có thể dự đoán được. Production environment theo kinh nghiệm hiện tại cho thấy, thường sẽ phải xử lý khối lượng lớn các request không đồng nhất, với các điều kiện không thể lặp lại.

    Ví dụ phần mềm sẽ xử lý thế nào nếu AWS bị sự cố ở cả 1 datacenter (Availability Zone) Nhìn chung các hệ thống này thường sẽ tiềm ẩn nhiều sai sót nhỏ lẻ, nhưng không tạo thành sự cố. Mà thường các sự cố này được tạo ra trong một điều kiện nhiều sai sót nhỏ lẻ kết hợp với nhau.

    Vậy khi có sự cố chúng ta mất những gì

    • Đầu tiên là doanh thu = doanh thu trên một đơn vị thời gian x thời gian để xử lý
    • Chi phí mất mát vì sự cố đến khi chúng được phát hiện: tỉ như việc nhà mạng không thể xử lý kịp khi tài khoản hết tiền mà vẫn nhắn được hàng trăm, hàng chục tin trong vòng vài phút, sự cố huyền thoại này có lẽ mất đến hàng năm để phát hiện. Rõ ràng chi phí cho sự cố tiềm ẩn này quá lớn.
    • Chi phí về cơ hội tiếp cận khách hàng, giả sử như Tiki, họ không thể xử lý được các hot deals trong các đợt sale trong thời gian thực (ref) thì họ sẽ mất đi khá nhiều khách hàng vì niềm tin, hơn nữa với kiến trúc dễ vỡ, disaster blast radius có thể lớn, ảnh hưởng đến các vùng khác trong hệ thống.

    Hãy xem xét bài toán giả thuyết như này:

    Chúng ta có một trang thương mại điện tử, vì một lý do nào đó, database của service Cart bị quá tải, mọi thứ vẫn hoạt động, nhưng người dùng không thể đặt hàng. Đội dự án không phát hiện ra sự cố này cho đến ngày thứ 4, mất 2 ngày để hot fix, và mất 1 ngày tiếp theo để checkout. Mỗi ngày chúng ta mất 80 triệu doanh thu, vậy là tổng cộng chúng ta mầt 560 triệu , trong đó có 240 triệu chi phí cơ hội và 320 triệu chi ví chìm.

    image

    Tại sao sự cố lại xảy ra trong khi tất cả mọi việc đã được tính toán trước? Con người, tất nhiên rồi. Có một lý thuyết có tên gọi là Bad Apple – Quả táo hỏng, trong trường hợp này có thể hiểu là, không phải tất cả các thành viên trong dự án đều hoàn hảo mọi lúc (về công việc của họ trong dự án), hệ thống tin cậy nhưng con người thì không, khi có sự cố xảy ra nó kết hợp với việc tất cả mọi thứ đều được xem như là đã tính toán trước (các ẩn số đã biết), dễ dàng trở thành một kiểu đổ lỗi cho các cá nhân có liên quan, từ đó giảm độ hợp tác và chia sẻ kiến thức chung trong dự án.

    Việc tách biệt các nhiệm vụ đóng vai trò như một rào cản đối với việc chia sẻ kiến ​​thức, phản hồi và cộng tác, đồng thời làm giảm nhận thức về tình huống vốn là yếu tố cần thiết để có phản ứng hiệu quả trong trường hợp xảy ra sự cố. Hơn nữa, thường trong develop team sẽ còn có các hoạt động review code, điều này cũng làm giảm hiệu suất cho việc xử lý/phản hồi về sự cố, và đổ lỗi cho hoạt động review.

    Với đội dự án trong trường hợp thương mại điện tử ở trên, họ sẽ xử lý như nào, có lẽ để xây dựng một phần mềm theo hướng Robustness, họ đã mất cả tháng (Lead Time), để qua tất cả các practice. Thế nhưng họ đã mất 4 ngày doanh thu cho chi phí chìm vì phát hiện chậm, vậy nên 320 triệu đã mất đi khiến họ phải chạy đua với thời gian để giảm ngay lập tức chi phí cơ hội, ở trường hợp này họ sẵn sàng hy sinh chính Robustness để đảm bảo tiến độ, trong đó tất cả các practice của Robustness bị giảm xuống còn giờ hoặc ngày. Vấn đề có thể thấy ngay, cùng một quy trình nhưng đã bị kéo từ hàng tuần xuống hàng ngày hoặc thậm chí vài giờ.

    Continuous Delivery có thể cải thiện độ ổn định của hệ thống và khối lượng release, nhưng để xử lý rất khó. Continuous Delivery cho hệ thống Robustness sẽ gặp vấn đề ngay lập tức khi dự án quá chú trọng vào khối lượng release (đơn giản là khối lượng lớn, nhưng thời gian release quá dài để thông qua các practice). Rõ ràng thời gian development (coding, design, architect, unit-test) khó có thể giảm vậy 3 practice rõ ràng là khả dĩ nhất để thay đổi và giản lược, nhưng End-to-end testing gần như đã ăn sâu vào tiềm thức và gần như mọi người đều công nhận nó. Và Continuous Delivery sẽ bị đổ lỗi vì làm thay đổi văn hóa của dự án ngay lập tức khi có lỗi đầu tiên ở môi trường production sau khi áp dụng, và dự án sẽ quay trở lại với hiện trạng.

    Nếu hệ thống có khả năng phục hồi thì sao

    Hoặc chí ít là phục hồi nhanh, nếu việc này được chú trọng (Resilience), tức là chúng ta muốn có MTTR thấp hơn là việc MTBF cao, bằng cách tối ưu hệ thống để có thể hotfix trên production khi có sự cố. Nhìn chung lỗi có thể phân loại, một số sẽ không bao giờ xảy ra, một số lỗi gây ra sự cố nghiêm trọng hơn các lỗi khác và các lỗi về an toàn thì không được phép xuất hiện, nhưng rõ ràng chúng ta nên có thể nhanh chóng đưa hệ thống trở lại hơn là cố gắng ít sự cố hơn.

    Nếu giả sử trang thương mại điện tử bên trên có khả năng phục hồi (tự động hoặc manual), chúng ta có thể dễ dàng thay đổi hệ thống về phiên bản ổn định trước đó, ngoài ra khi hệ thống gặp vấn đề về performance, việc thay đổi phần cứng tốt hơn sẽ dễ dàng hơn và chi phí phần cứng đôi khi chưa bao giờ là vấn đề khi so sánh với mất mát về doanh thu.

    image

    Vậy xây dựng hệ thống Resilience cần gì, theo Erik Hollnagel trong cuốn Resilience Engineering in Practice có 4 yếu tố:

    • Anticipation: Hiểu hệ thống sẽ đối mặt với những thứ gì: số lượng người dùng đột biến khi có notification mới, một API Get cho hàng triệu người dùng mà không có biện pháp cache sẽ gây quá tải database và web server…
    • Monitoring: biết được cần theo dõi thứ gì trong hệ thống, khi nào hệ thống bất thường
    • Response: sử dụng các hiểu biết và các nguyên tắc cũng như nhận thức chung của cả dự án khi có sự cố để giảm thiểu tác động.
    • Learn: Hiểu các trường hợp sự cố là gì, các trường hợp có cảnh báo, và chia sẻ trong dự án.

    Ngoài ra sẽ có các yếu tố về văn hóa và tổ chức hỗ trợ. Trong một dự án, rõ ràng với hệ thống phức tạp, dự án cần một khối lượng kiến thức và nguồn lực đủ nhiều để ngăn ngừa các lỗi tiềm ẩn hoặc để xử lý sự cố, thường thì các team scrum sẽ có các phương thức hotfix để xử lý sự cố nhỏ hơn là thực hiện theo các operation guideline, và tất cả đều dựa trên sự giao tiếp thuận tiện giữa tất cả các thành viên, clear and transparent information.

    Vậy Resilience system cần gì, chúng ta sẽ có một môi trường production mà trong đó các service/component độc lập, mở rộng theo chiều ngang và dọc tốt để ứng phó các thay đổi bất ngờ trong thực tế. Nhìn chung ngưỡng chịu đựng của các hệ thống Resilience sẽ tốt hơn: ví dụ nếu hệ thống phân tán và phần cart có khả năng mở rộng thì có thể hệ thống thương mại điện tử bên trên có thể serve được đến 10.000 CCU mới xảy ra sự cố thay vì 3.000 CCU, như vậy thời gian down time và chi phí mất đi rõ ràng giảm.

    Vậy thay vì quá chú trọng vào phần DEV, phần OPS trong dự án được đầu tư nguồn lực tốt hơn để giữ cho hệ thống ở trong điều kiện hoạt động an toàn và tin cậy. Tuy nhiên việc xây dựng hệ thống có khả năng chịu đựng tốt với các điều kiện, thường dựa trên các practice sau đây:

    1. Đối với phần developer:
    • Adaptive architecture hay hiểu theo kiểu defensive architecture cũng được: code những gì cần thiết, tính toán các trường hợp, có một bức tranh tổng thể, code dễ hiểu, dễ maintain và có thể mở rộng
    • Feature On/Off, feature không gắn liền với phần kết cấu lõi của phần mềm, ví dụ trong một hệ thống IoT, timeseries có thể lưu trữ bằng nhiều loại DB khác nhau: Timeseries Database ~ Cassandra, hoặc đơn giản hơn lưu trữ vào thẳng một DB quan hệ với index phù hợp, khi Cassandra down, rõ ràng việc dựng lại cả hệ thống DB Cassandra mất nhiều thời gian hơn cho việc chuyển sang SQL, nhưng việc đó cần được chuẩn bị về mặt feature
    1. Test, mình muốn nói đến việc test tình trạng hệ thống nhé, còn feature đương nhiên phải pass all test rồi.
    • Smoke test kết hợp với Chaos Engineering sẽ hiệu quả trong việc kiểm thử về tình trạng sơ bộ của hệ thống hoặc tìm ra các lỗi tiềm ẩn như trong các điều kiện server bất ngờ dừng hoạt động hay ổ cứng bị lỗi, kết nối mạng bị ngắt…
    1. Hạ tầng – Infrastructure: Infrastructure as a service và Auto Recovery, IaaS sẽ đảm bảo việc hạ tầng ở các môi trường giống nhau, giảm tác động của con người với hạ tầng, trong khi đó Auto Recovery sẽ phục hồi hệ thống khi có service bị fail, tất nhiên ở mức độ nào đó, source code của developer sẽ cần đáp ứng được một số yêu cầu.

    Những ai đã tiếp xúc với các kỹ thuật IaaS như terraform, pulimi, cloudformation, k8s yaml đều khá tự tin khi deploy một môi trường tương tự, độ chính xác của các phần code hạ tầng này đều rất cao, công việc còn lại chỉ là đánh giá lại kết quả.

    1. Timeseries/Telemetry: Log, Monitor luôn là yêu cầu bắt buộc, kết hợp với alarm để mọi người có thể được thông báo khi có các sự cố hay lỗi runtime xảy ra. Cuối cùng là User Analytics (có thể lấy từ Firebase analytic, …) dành cho việc theo dõi user experience.
    2. Con người: phần luôn là khó nhất và để nói về văn hóa trong dự án là chính, nhưng có nguyên tắc “You Built It, You Run It”: thông tin trong dự án sẽ cần được transparency, mọi người cần làm mọi việc có thể làm (có thể không thuộc trách nhiệm của mình) để đảm bảo rằng mọi thứ ổn ngay từ đầu, việc minh bạch thông tin sẽ giúp tìm ra nguyên nhân của các vấn đề nhanh hơn nhiêu.

    1 trong 2 yếu tố nữa trong phần con người đó là Blameless PostMortems, mọi người rất dễ mắc sai lầm nhất là khi họ cần cân bằng giữa khả năng và trách nhiệm, khi đó không nên điều tra nguyên nhân và quy kết cho sự cố xuất phát từ sai lầm cá nhân, điều chỉ nên được xem xét như là một nguy cơ tiềm ẩn trong tổ chức, vấn đề này gần như luôn luôn song hành với yếu tố còn lại – Hindsight bias. Thành kiến ​​nhận thức muộn – Khi mà mọi người vẫn tin rằng sau một sự cố trong quá khứ, họ có thể dự đoán được với độ chắc chắn cao về các sự cố có thể xảy ra sau này, điều đó có nghĩa là khi bạn để xảy ra lại những sự cố đó thì đó là thảm họa. Nhìn chung nên nhìn nhận những vấn đề kiểu như Hindsight Bias là không thể tránh khỏi và hãy làm tất cả để tránh cùng với việc hãy để những người mắc sai lầm truyền đạt lại kinh nghiệm của họ để tránh sự cố cho những người khác.

    Hơi lan man một chút, hãy quay trở lại bài toán thương mại điện tử nêu trên. Trong trường hợp build hệ thống đó theo hướng Adaptive và Feature On/Off, rõ ràng phần cart luôn là phần quan trọng trong hệ thống, nó nên được quản lý bởi một Service Registry bên cạnh đó Circuit Breaker sẽ đóng vài trò kiểm tra service có được hoạt động hay không, vậy sẽ không tốn đến 5 phút để phát hiện ra vấn đề, một bản vá có thể được fix trong vòng 3 tiếng, thâm chí là 1 ngày nhưng sẽ được deploy ngay sau đó. Hãy tính toán lại chi phí: như vậy sẽ tốn ~ 80 triệu, trong đó chi phí chìm vì phát hiện ra vấn đề chậm không đáng kể, còn chi phí fix cũng giảm do việc deploy nhanh chóng và thuận tiện hơn.

    Khả năng phục hồi được tối ưu hóa gần như là nhu cầu tất yếu cho một phần mềm hay một tổ chức có thể đổi mới và phát triển, khi đó chúng ta có thể đầu tư vào con người và công nghệ để đạt được Robustness: khả năng thích ứng với các điều kiện bất ngờ có thể xảy đến trong tuơng lai, khi đó các doanh nghiệp, tổ chức sẽ có lợi thế lớn khi đưa ra sản phẩm nhanh hơn các đối thủ cạnh tranh.

    Continuous Delivery hỗ trợ bởi Resilience

    Mọi người sẽ có xu hướng tìm một cách để xử lý Continuous Delivery cho mọi dự án, tổ chức tuy nhiên việc đó quá phức tạp. Bản thân việc xây dựng theo hướng adaptive architect như đã nói ở trên cũng sinh ra các hoàn cảnh và ràng buộc.

    Tuy nhiên nếu một dự án đã tối ưu hóa Robustness và đang không có Continuous Delivery chúng ta có thể xử lý theo các bước sau.

    1. Version hóa mọi thứ: Deployment sẽ ổn định hơn, không có phiên bản nào bất ngờ được deploy mà mọi người không biết.

    Có một vài tình huống như bạn thực hiện hàng loạt thay đổi trong K8S Yaml, nhưng ngày hôm sau sự cố xảy ra, bạn không thể biết được chính xác vấn đề nằm ở đâu khi không versioning các config mà bạn đã làm.

    1. Đo đạc: mục đích đảm bảo việc delivery ổn định hơn khi chúng ta đã biết rõ chúng ta phát triển thứ gì và delpoy/deliery những phần nào.
    2. tăng độ tin cậy cho Production environment: có 2 hoạt động cần làm rearchitect, và thiết lập các hệ thống monitoring. Rearchitect cần làm dần dần và theo hướng adaptive để giảm rủi ro do vẫn là hệ thống production.
    3. Bước cuối cùng, thực ra đây không hẳn là một bước mà khi đến bước này hệ thống và dự án đã đủ các điều kiện cần thiết, việc deployment đã ổn định production environment đã phần nào có khả năng tự phục hồi, việc còn lại là áp dụng như một hệ thống Resilience.

    Sau đó thì sao

    Nhìn chung sau các bước bên trên, điều còn lại cuối cùng là văn hóa. Các tổ chức có xu hướng vẫn giữ lại các practice của robustness để đề phòng rủi ro, việc thay thế nó cần thời gian và một câu chuyện hợp lý bao gồm các lập luận về chi phí, thời gian và tỉ lệ lỗi giảm.

    Nhưng việc đầu tiên đó là thay đổi nhận thức về các vấn đề quản lý rủi ro và robustness. Các bước thay đổi bên trên sẽ cho chúng ta một loạt các thông số về độ ổn định, khối lượng delivery và minh họa chi phí có thể được cải tiến cũng như MTTR và thời gian triển khai. Ngoài ra Chaos Engineering được xem xét cẩn thận sẽ cho chúng ta các practice giúp giảm MTTR xuống vài giờ thậm chí vài phút, bằng cách giả lập và chỉ ra các lỗi có thể xảy ra trong môi trường production.

    Các practice của robustness khi đó sẽ dần được thay thế bằng sự kết hợp giữa Continuous Delivery và các practice trong quá trình vận hành. End-to-end testing sẽ được thay thế dần bởi Test Pyramid, khối lượng test, tần suất test sẽ được phân phối một cách hợp lý để giảm thời gian test và chi phí. Trong đó số UnitTest sẽ được tiến hành thường xuyên và cho môi bản build, Acceptance Test sẽ được pick up (khoảng 100 cases) và cũng được tiến hành như vậy, trong khí đó các loại smoke test sẽ chỉ khoảng 10 case, các kỹ thuật telemetry thì gần như tiến hành hằng ngày hằng giờ.

    Change Boards và Change Freezes hiện tại sẽ trở nên lỗi thời với các kỹ thuật Blue Green Deployments và Canary Deployment, trong đó các phiên bản mới sẽ được triển khai và thay thế dần dần phiên bản cũ, việc deploy sẽ được rollback khi có sự cố. Ngoài ra Facebook cũng đưa ra một kỹ thuật khác – Dark Launch, khi đó Facebook sẽ đưa các tùy chọn cho người dùng có bật hay tắt các tính năng mới hay không. Hiện nay Google hay AWS cũng làm theo cách này, cũng là một cách khá tốt để thử nghiệm tính năng trên môi trường thực tế – điều mà mọi dự án đều mong muốn.

    Kết luận

    Tối ưu hóa cho Robustness có vẻ đã lỗi thời và không thể đáp ứng được nhu cầu công nghệ thông tin hiện nay, cùng với đó việc không áp dụng Continuous Delivery trong một thời gian dài rất dễ dẫn đến những sự cố lớn. Robustness rõ ràng vẫn có giá trị, điều đó là mong muốn tự nhiên của các lập trình viên và dự án, tuy vậy sự ưu tiên nên được thay đổi để hệ thống có khả năng thích ứng cao hơn cả về nghiệp vụ và các yêu cầu từ phía người dùng.

    Mặt khác tối ưu hóa cho khả năng phục hồi của hệ thống có vẻ là một chiến lược có độ tin cậy cao hơn, cho phép phần mềm và dự án có thể mở rộng, tránh tác động của các lỗi và tìm cách làm cho phần mềm có khả năng chịu đựng và thích ứng tốt hơn. Việc này rõ ràng là một sự thay đổi về mô hình tổ chức và văn hóa, trong đó cần mọi người nhận thức rằng hệ thống phần mềm rõ ràng luôn phức tạp và nhiều khi sự cố là không thể tránh khỏi, việc tối ưu khả năng phục hồi sẽ làm giảm chi phí hay tổn thất cho sự cố mà thôi.

    Ngoài ra việc tối ưu hóa cho Robustness sẽ làm giảm hiệu quả của Continuous Delivery, Continuous Delivery có thể cho ta thấy hiệu quả rõ ràng với các hệ thông phục hồi tốt, điều đó giúp chúng ta tăng độ tin cậy từ source code, deployment, delivery và môi trường production ngay từ đầu.

  • Triển khai CI/CD cho iOS – Appstore Distribution

    Triển khai CI/CD cho iOS – Appstore Distribution

    The most powerful tool we have as developers is automation.

    Scott Hanselman

    Overview

    Tiếp tục với series triển khai CD cho iOS, hôm nay chúng ta sẽ tìm hiểu cách chạy CD để đẩy một ứng dụng lên App Store Connect một cách tự động.

    Có rất nhiều doanh nghiệp, dự án chọn App Store Connect & TestFlight làm nền tảng để Test sản phẩm, đăc biệt với các sản phẩm gần thời điểm lên App Store. Thêm một sự tiện lợi nữa đến từ TestFlight, khi có phiên bản mới được upload lên sẽ có thông báo về cho các thiết bị cài đặt TestFlight. Vậy nếu triển khai CD để tự động đưa ứng dụng lên App Store Connect, ta cũng sẽ có một hệ thống thông báo tự động khi quá trình hoàn tất mà không cần triển khai thêm gì.

    Quan trọng: Để sử dụng được phương thức này ta cần chú ý một số điểm sau:

    • Tạo sẵn App trên App Store Connect với đúng bundle id
    • Bản build sau bắt buộc phải có version/build number lớn hơn version/build number của bản trước đó

    Distribute và Upload IPA sử dụng Command Line

    Sẽ không có nhiều khác biệt giữa phương thức Archive và Export của phiên bản App Store với các phiên bản khác, ta vẫn sẽ sử dụng các câu lệnh cũ, tuy nhiên phần export sẽ không cần tham số -exportPath:

    xcodebuild archive -archivePath "cicd-test" -scheme cicd-test
    xcodebuild -exportArchive -archivePath "cicd-test.xcarchive" -exportOptionsPlist ExportOptions.plist -allowProvisioningUpdates

    Với chế độ App Store, chúng ta sẽ sử dụng file ExportOptions.plist với nội dung như sau:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
    	<key>method</key>
    	<string>app-store</string>
    	<key>destination</key>
    	<string>upload</string>
    </dict>
    </plist>

    Cần thêm có key destination với value upload để cấu hình việc tự động upload ipa thay vì export file.

    Cấu hình .gitlab-ci.yml cho GitlabCI

    Với phương thức này, ta chỉ cần đơn giản cấu hình file .gitlab-ci.yml như sau:

    stages:
      - deploy
    
    deploy_appstore:
      stage: deploy
      script:
        - echo 'Hello bitches, welcome to lazy boys world!'
        - echo 'Just commit code, serve yourself a cup of coffee. Let gitlab build your app!'
        - xcodebuild archive -archivePath "cicd-test" -scheme cicd-test
        - xcodebuild -exportArchive  -archivePath "cicd-test.xcarchive" -exportPath "ipa" -exportOptionsPlist ExportOptions.plist -allowProvisioningUpdates
      tags:
        - main

    Kết quả log của Job khi upload thành công trên Gitlab sẽ trông như sau

    Log của quá trình upload App lên App Store Connect

    Như vậy là đã hoàn thành việc cấu hình CD upload ứng dụng lên App Store Connect. Ở bài viết tiếp theo, chúng ta sẽ cùng tìm hiểu cách triển khai CD cho ứng dụng iOS theo phương thức OTA với các Website đang Deploy trên AWS S3 😀

  • Triển khai CI/CD cho iOS – In house Distribution với DeployGate

    Triển khai CI/CD cho iOS – In house Distribution với DeployGate

    Một ngày đẹp trời, anh ấy nhận được một cái mail giới thiệu anh là chuyên gia về tài khoản Apple… đó chính ngày định mệnh mở ra con đường Enterprise. Many thanks anh Vũ Béo, người nhanh tay mang ánh sáng về và đặt những bước chân đầu tiên.

    Sam

    Overview

    DeployGate hỗ trợ rất nhiều chế độ Distribution, tuy nhiên để tạo ra sự đa dạng cho series, ở bài viết này chúng ta sẽ sử dụng Enterprise (In house) kết hợp với DeployGate để tạo thành một quy trình CD.

    Nói thêm một chút về DeployGate, đây là một nền tảng hỗ trợ phân phối ứng dụng di động (iOS & Android). Người dùng có thể upload app package (ipa & apk) lên và chia sẻ link cài đặt cho người khác. Sử dụng DeployGate, Tester sẽ không cần phải cài đặt ứng dụng trực tiếp với ipa hay apk file, và cũng không cần sử dụng Window hoặc MacOS để cài đặt. DeployGate cũng giảm bớt sự phụ thuộc của người dùng trong quá trình test vào Test Flight khi thời gian process một số ứng dụng có thể lên tới hàng chục giờ đồng hồ.

    Bài viết sẽ gồm có 3 phẩn:

    1. Distribute IPA sử dụng Command Line
    2. Cấu hình DeployGate để upload IPA bằng Command Line
    3. Cấu hình file .gitlab-ci.yml cho GitlabCI

    Distribute IPA sử dụng Command Line

    Bước đầu tiên của quá trình vẫn là tạo ra được file IPA để upload lên Deploy Gate. Như ở bài trước đã hướng dẫn, ta sẽ sử dụng các câu lệnh sau để thực hiện tạo ra file IPA.

    xcodebuild archive -archivePath "cicd-test" -scheme cicd-test
    xcodebuild -exportArchive  -archivePath "cicd-test.xcarchive" -exportPath "ipa" -exportOptionsPlist ExportOptions.plist -allowProvisioningUpdates

    Với chế độ In House, chúng ta sẽ sử dụng file ExportOptions.plist với nội dung như sau:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
    	<key>method</key>
    	<string>enterprise</string>
    </dict>
    </plist>

    Sau khi export xong, chúng ta sẽ có một file IPA với đường dẫn tương đối so với vị trí của runner như sau:

    ipa/cicd-test.ipa

    Việc tiếp theo sẽ là upload file IPA lên DeployGate.

    Cấu hình DeployGate để upload IPA bằng Command Line

    Đầu tiên chúng ta cần upload ít nhất một bản IPA lên Deploy Gate, sau đó bật Distribution lên bằng cách ấn vào nút Add a link for sharing trong ảnh sau:

    Ấn vào button [+ Add a link for sharing] phía bên phải

    Sau đó, trang web sẽ điều hướng đến Distribution Page, đừng hoảng, ta chỉ cần copy đường dẫn của trang đó là được. Đường dẫn sẽ có dạng như sau:

    https://deploygate.com/distributions/818c15431b824aac1763f074931ecacaed2a03d5
    DISTRIBUTION_KEY = 818c15431b824aac1763f074931ecacaed2a03d5

    Ta sẽ lưu key phía sau của đường dẫn lại, tạm gọi là DISTRIBUTION_KEY như trên.

    Tiếp đó ta quay lại Dashboard của app như trong ảnh trên và lấy thông tin user từ đường dẫn như sau:

    https://deploygate.com/users/gstdn_test/platforms/ios/apps/com.magz.techover.ios
    USER = gstdn_test
    USER_API_HOST = https://deploygate.com/api/users/gstdn_test/apps

    Cuối cùng của việc cấu hình và thu thập thông tin lên Deploy Gate, ta vào mục Account Setting -> Profile và copy API Key ở dưới cùng của page.

    Copy API Key này nhé bạn

    Ta sẽ có một bộ các thông tin sau:

    USER_API_HOST = https://deploygate.com/api/users/gstdn_test/apps
    DISTRIBUTION_KEY = 818c15431b824aac1763f074931ecacaed2a03d5
    API_KEY = 246147bb-3ab7-4cc8-8fbe-ahihidongoc

    Tiếp đó, ta chỉ cần thực hiện upload IPA file lên Deploy Gate sử dụng các thông tin trên với Command sau:

    Curl -H "Authorization: token $API_KEY" -F "file=@ipa/cicd-test.ipa" -F "message=New distribution" -F "distribution_key=$DISTRIBUTION_KEY" -F "release_note=Release Note" "$USER_API_HOST"

    Response trả về sẽ có dạng Json như sau, nếu error = false thì tức là quá trình upload thành công.

    Response trả về của Curl upload IPA lên Deploy Gate

    Refresh lại Dashboard của app ta sẽ thấy có một bản build mới được upload lên với message “New distribution” như ảnh sau:

    Bản build đã lên với message đi kèm

    Tiếp tục qua trang Update Distribution, ta sẽ thấy bản build mới upload sẽ tự động được cập nhật thành phiên bản được phân phối với Release Notes là “Release Note”.

    Bản build vừa upload được tự động phân phối với Release Note mới

    Như vậy là ta đã hoàn thành việc upload và cập nhật bản build trên Deploy Gate một cách tự động. Tiếp đến là cấu hình các câu lệnh trên vào file .gitlab-ci.yml.

    Cấu hình .gitlab-ci.yml cho GitlabCI

    Rồi, lại tới công chuyện tiếp! Đầu tiên ta cần ném các thông tin Key của Deploy Gate vào mục Variables thay vì setting cứng trong file.

    Luôn đặt các thông tin key, môi trường vào Variables

    Tiếp đến, nội dung của file .gitlab-ci.yml cơ bản sẽ như sau:

    stages:
      - build
    
    build_project:
      stage: build
      script:
        - echo 'Hello bitches, welcome to lazy boys world!'
        - echo 'Just commit code, serve yourself a cup of coffee. Let gitlab build your app!'
        - xcodebuild archive -archivePath "cicd-test" -scheme cicd-test
        - xcodebuild -exportArchive  -archivePath "cicd-test.xcarchive" -exportPath "ipa" -exportOptionsPlist ExportOptions.plist -allowProvisioningUpdates
        - Curl -H "Authorization: token ${API_KEY}" -F "file=@ipa/cicd-test.ipa" -F "message=New distribution" -F "distribution_key=${DISTRIBUTION_KEY}" -F "release_note=Release Note" "${USER_API_HOST}"
      tags:
        - main

    Vậy là xong, sau khi code được commit/merge vào main, GitlabCI sẽ tự động chạy Jobs build IPA và đẩy lên DeployGate. Hẹn gặp lại các bạn ở bài viết tiếp theo!

    P/S: Sẽ rất tiện lợi nếu có thể cấu hình để tuỳ chỉnh Upload Message, Release Note để ghi các thay đổi cho Tester hay thông báo khi có bản build mới lên Deploy Gate. Nên mình sẽ dành riêng một bài trong series để hướng dẫn cách cấu hình các tuỳ chỉnh này.

  • Triển khai CI/CD cho iOS – GitlabCI Artifact

    Triển khai CI/CD cho iOS – GitlabCI Artifact

    Ngày đông giá rét, thay vì ngồi dài cổ đợi ae commit code lên để build cho Tester. Bạn có thể đơn giản add Tester vào Gitlab, cài một con CD Artifact đơn giản và ném vào mặt nó phần việc nhàm chán kia. Còn bạn có thể nhàn nhã mò ra Highlands, nhâm nhi cốc cafe nóng và xem cơm chó của ngày Noel buồn…

    Sam, gửi Cương Good, Anh Mẹc và Very Good Team

    Overview

    Tiếp tục với series CI/CD cho iOS, hôm nay chúng ta sẽ chuyển sang chuyên mục CD với phương thức Deploy đơn giản nhất – GitlabCI Artifact.

    Vì đây là bài viết đầu tiên liên quan đến CD, chúng ta sẽ cần đề cập đến phương thức Archive và Distribute ứng dụng sử dụng command line.

    Distribute IPA sử dụng Command Line

    Để triển khai CD, mọi công việc đều phải thực hiện thông qua Shell Command, do đó từ các công việc Build, Test, Archive hay Distribute IPA đều phải thực hiện bằng các câu lệnh. Rất may, Apple cung cấp sẵn một bộ Command Line Tools được tích hợp kèm với Xcode, ta có thể sử dụng ngay khi máy đã cài đặt Xcode. Các bạn có thể tham khảo hướng dẫn các command tại đây.

    Đầu tiên, chúng ta sẽ cài đặt setting project sang Automatic Signing, đảm bảo thiết bị chạy runner đã đăng nhập tài khoản hợp lệ và build thành công App bằng Xcode.

    Setting Automatically trong Xcode

    Sau khi thành công, ta sẽ thử build project bằng câu lệnh với cú pháp:

    xcodebuild build -scheme cicd-test

    Với tham số -scheme là chỉ định scheme/target để build.

    Build thành công Project trên Terminal

    Sau khi thành công, chứng tỏ là project đã được setting đúng cách, chúng ta sẽ thực hiện archive App bằng câu lệnh sau:

    xcodebuild archive -archivePath "cicd-test" -scheme cicd-test

    Với tham số -archivePath chỉ định tên và đường dẫn export ra .xcarchive file.

    Sau khi chạy xong, chúng ta sẽ thấy có một file được export ra với tên cicd-test.xcarchive trông như ảnh sau:

    Output của quá trình archive app

    Sau khi đã có .xcarchive file, chúng ta cần thực hiện export ra IPA file. Để export được IPA file, chúng ta cần một file ExportOptions.plist, ở đây chúng ta sẽ export bằng chế độ adhoc, file sẽ có nội dung như sau:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
    	<key>method</key>
    	<string>ad-hoc</string>
    </dict>
    </plist>

    Trong trường hợp IPA cần export ở các chế độ khác, ta sẽ thay ad-hoc bằng các keyword tương ứng sau:

    • Development: development
    • Adhoc: ad-hoc
    • AppStore: app-store
    • Enterprise: enterprise

    Sau đó, thực hiện export IPA bằng câu lệnh sau:

    xcodebuild -exportArchive  -archivePath "cicd-test.xcarchive" -exportPath "ipa" -exportOptionsPlist ExportOptions.plist -allowProvisioningUpdates

    Với các tham số sau đây:

    • -exportPath: Chỉ định thư mục sẽ export ra file IPA
    • exportOptionsPlist: Chỉ định file chưa các config export
    • -allowProvisioningUpdates: Cần phải có nếu Project setting chế độ Automatic Signing. Không cần sử dụng nếu setting manual signing.

    Sau khi export xong, chúng ta sẽ nhận được file IPA ở thư mục như hình sau:

    Vị trí file IPA được export ra

    Như vậy là ta đã tạo xong file IPA, tiếp đến sẽ là setting với GitlabCI

    Cấu hình .gitlab-ci.yml cho GitlabCI

    Rồi tới công chuyện, giờ chúng ta chỉ cần chuyển toàn bộ các câu lệnh ở trên vào phần script của file .gitlab-ci.yml và cấu hình để GitlabCI nhận file IPA làm Artifact là xong.

    File .gitlab-ci.yml cơ bản sẽ có nội dung như sau:

    stages:
      - build
    
    build_project:
      stage: build
      script:
        - echo 'Hello bitches, welcome to lazy boys world!'
        - echo 'Just commit code, serve yourself a cup of coffee. Let gitlab build your app!'
        - xcodebuild archive -archivePath "cicd-test" -scheme cicd-test
        - xcodebuild -exportArchive  -archivePath "cicd-test.xcarchive" -exportPath "ipa" -exportOptionsPlist ExportOptions.plist -allowProvisioningUpdates
      tags:
        - main
      artifacts:
        paths:
          - ipa/cicd-test.ipa

    Sau đó, hãy commit các thay đổi lên và chờ đợi thành quả!

    Thành quả của quá trình là đây

    Như bạn thấy trong ảnh trên, phía bên phải của Jobs Detail sẽ có một section để tải về Job Artifacts chứa file IPA có thể cài được. Ở đây, ta chỉ cần ấn nút Download IPA thực hiện cài đặt ứng dụng vào điện thoại.

    Như vậy là ta đã hoàn thành cấu hình CD đơn giản cho iOS với GitlabCI Artifact. Hẹn gặp các bạn ở bài viết tiếp theo.

  • Cách cấu hình Gitlab CI chỉ kiểm tra các file thay đổi với merge request

    Cách cấu hình Gitlab CI chỉ kiểm tra các file thay đổi với merge request

    Chúng ta sẽ thường hay cấu hình chạy kiểm tra code quality cho tất các các nhánh và merge request trên Gitlab CI. Nhưng Gitlab runner job sẽ chạy scan toàn bộ source code nên sẽ mất thời gian đối với merge request. Ngoài ra đôi khi do source code cũ của khách hàng lỏm sẽ dẫn tới lúc nào merge request đó cũng sẽ bị failed.

    Dưới đây là sample về Gitlab CI chạy python lint với cấu hình chạy cho nhánh develop và merge request.

    pylint:
      stage: lint
      before_script:
        - mkdir -p public/badges public/lint
        - echo undefined > public/badges/$CI_JOB_NAME.score
        - pip install pylint-gitlab
      script:
        - pip install -r requirements.txt
        - pylint --rcfile=.pylintrc --exit-zero --output-format=text $(find -type f -name "*.py" ! -path "**/.venv/**") | tee /tmp/pylint.txt
        - sed -n 's/^Your code has been rated at \([-0-9.]*\)\/.*/\1/p' /tmp/pylint.txt > public/badges/$CI_JOB_NAME.score
        - pylint --exit-zero --output-format=pylint_gitlab.GitlabCodeClimateReporter $(find -type f -name "*.py" ! -path "**/.venv/**") > codeclimate.json
        - pylint --exit-zero --output-format=pylint_gitlab.GitlabPagesHtmlReporter $(find -type f -name "*.py" ! -path "**/.venv/**") > public/lint/index.html
      after_script:
        - anybadge --overwrite --label $CI_JOB_NAME --value=$(cat public/badges/$CI_JOB_NAME.score) --file=public/badges/$CI_JOB_NAME.svg 4=red 6=orange 8=yellow 10=green
        - |
          echo "Your score is: $(cat public/badges/$CI_JOB_NAME.score)"
      artifacts:
        paths:
          - public
        reports:
          codequality: codeclimate.json
        when: always
      only:
        - develop
        - merge_requests
      tags:
        - docker
    

    Để giải quyết các vấn đề trên và có thể tập trung vào chất lượng code của các bạn member tốt hơn, chúng ta có thể tạo thêm 1 config mới cho merge request event. Với config mới chỉ cần kiểm tra code quality trên các file thay đổi là được.

    pylint_merge_request:
      stage: lint
      before_script:
        - pip install pylint-gitlab
      script:
        - echo CI_COMMIT_SHA=${CI_COMMIT_SHA}
        - echo CI_MERGE_REQUEST_TARGET_BRANCH_NAME=${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}
        - git fetch origin ${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}
        - FILES=$(git diff --name-only ${CI_COMMIT_SHA} origin/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME} | grep '\.py'$)
        - echo "Changed files are $FILES"
        - pip install -r requirements.txt
        - pylint --rcfile=.pylintrc --output-format=text $FILES | tee /tmp/pylint.txt
      only:
        - merge_requests
      tags:
        - docker
    

    Hi vọng nó hữu ích với mọi người và có thể apply nó vào dự án của mình.

  • Gitlab CI/CD cho người mới bắt đầu

    Gitlab CI/CD cho người mới bắt đầu

    CI-CD in gitlab

    Chắc hẳn mọi người đã từng nghe tới CI-CD, rồi thắc mắc không biết nó là gì và chúng ta có thể làm gì với nó? Mình cũng như vậy, thắc mắc ấy đưa mình đến việc viết chuỗi bài này để chia sẻ về những gì mình đã học được về CI-CD.

    Trong bài này thì mình sẽ tập trung vào CI, cũng như cách config file .gitlab-ci.yml và chạy trên share runner của gitlab.

    I. Vậy CI-CD là gì?

    CI (Coutinous Integration) – tích hợp liên tục, là quá trình mà code của chúng ta được build và test trước khi tích hợp vào nhánh chính.

    CD (Continous Delivery) – truyền tải liên tục, là quá trình mà code được triển khai lên môi trường như development hoặc production, và nó diễn ra ngay sau CI

    II. Triển khai CI như thế nào?

    1. Thành phần

    Để có thể triển khai được CI thì trước hết, chúng ta cần biết về 2 thành phần chính cấu thành nên nó. Đó là file gitlab-ci.ymlgitlab runner.

    Mọi người có thể hiểu nôm na nếu gitlab runner như một anh tài xế mà bạn thuê, anh ta rất nhiệt tình, sẵn sàng lái xe đi bất cứ nơi đâu thay cho bạn, nhưng khổ nỗi anh lại không biết đường. Hmm… Làm sao bây giờ? File gitlab-ci.yml lúc ấy xuất hiện như một tấm bản đồ vạn năng.

    File này sẽ được gitlab tự động nhận diện khi bạn commit code lên từ local, và sẽ cấp cho bạn một anh "tài xế" shared runner để đi làm những việc bạn đã vạch sẵn ở trong gitlab-ci.yml.

    Lưu ý:

    Mặc định, gitlab có một số share runner mà người dùng có thể dùng ngay lập tức. Vì vậy, đối với một project vừa và nhỏ, số lượng job cho runner còn ít, thì bạn có thể tận dụng luôn những runner đã có sẵn này. Nhưng với một project lớn hơn, khi mà số lượng cũng như tần suất chạy job ngày càng tăng lên, thì chúng ta nên config riêng một gitlab-runner trên server của mình.

    2. Template

    
    stages:          
      - build
      - test
      - deploy
    
    build-job:      
      stage: build
      script:
        - echo "Compiling the code..."
        - echo "Compile complete."
    
    unit-test-job:
      stage: test   
      script:
        - echo "Running unit tests... This will take about 60 seconds."
        - sleep 60
        - echo "Code coverage is 90%"
    
    lint-test-job:   
      stage: test   
      script:
        - echo "Linting code... This will take about 10 seconds."
        - sleep 10
        - echo "No lint issues found."
    
    deploy-job:     
      stage: deploy  
      script:
        - echo "Deploying application..."
        - echo "Application successfully deployed."
    

    Templete cho một file gitlab-ci.yml

    Chúng ta sẽ cùng nhau đi qua một lượt xem nó có ý nghĩa gì nhé!

    Đầu tiên là stages: đây sẽ là khi chúng ta định nghĩa ra các giai đoạn mà trong pipeline, hay nói cách khác là các job mà runner sẽ phải thực hiện. Như đa số project, workflow sẽ bao gồm 3 giai đoạn, đầu tiên chúng ta sẽ tạo ra một bản build của project, tiếp sau đó sẽ test, và khi đã qua được các bước trên thì sẽ deploy lên môi trường development, staging hay production.

    stages:    
      - build
      - test
      - deploy
    

    Tiếp theo, chúng ta sẽ đi cụ thể tới các build stage nha (Các giai đoạn sau cũng tương tự).

    • build-job là tên của job, cái này các bạn có thể tự thay đổi theo ý mình nhé.
    • stage chỉ ra job này đang nằm trong giai đoạn nào trong 3 stages mà chúng ta đã định nghĩa trước ở trên.
    • script là những câu lệnh mà chúng ta sẽ chạy trong job này.
    build-job:      
      stage: build
      script:
        - echo "Compiling the code..."
        - echo "Compile complete."
    

    Ngoài ra thì còn rất nhiều những thông tin config khác mà các bạn có thể tìm ở đây nha!

    Và như bạn đã thấy ở templete trên, thì một pipeline có thể có nhiều giai đoạn và một giai đoạn có thể chứa nhiều job khác nhau và các job đó có thể được thực hiện song song.

    3. Ví dụ

    Mình sẽ lấy ví dụ từ bẳng việc check lint của một project nodejs nha!

    Bước 1: Chúng ta sẽ phải cài đặt eslint và babel-eslint cho project của mình.

    npm install eslint babel-eslint --dev
    

    Bước 2: Tạo file .eslintrc để config lint rule mà mình muốn:

    {
      "env": {
        "node": true,
        "es6": true,
        "mocha": true
      },
      "parser": "babel-eslint",
      "parserOptions": {
        "ecmaVersion": 2017
      },
      "extends": "eslint:recommended",
      "rules": {
        "comma-spacing": [
          "error",
          {
            "before": false,
            "after": true
          }
        ],
        "indent": [
          "error",
          2,
          {
            "SwitchCase": 1
          }
        ],
        "linebreak-style": [
          "error",
          "unix"
        ],
        "quotes": [
          "error",
          "double"
        ],
        "semi": [
          "error",
          "always"
        ],
        "no-unused-vars": [
          "off"
        ],
        "no-console": 0
      }
    }
    

    Bước 3: Thêm script cho việc check lỗi và fix lỗi lint trong file package.json

    Trong dấu "{}" ở câu lệnh lint là các folder mà mình muốn check lint, các bạn có thể để folder khác cho phù hợp với project của mình nhé!

    Bước 4: Chạy lần lượt hai câu script trên và đẩy code lên git.

    Bước này là để trước khi các bạn check xem những nhánh phụ merge vào có đúng theo rule lint mình đã định nghĩa không, thì nhánh chính của mình đã thỏa mãn những rule đó từ trước.

    Bước 5: Tạo file .gitlab-ci.yml

    
    
    stages:   
      - checklint
    
    lint-test-job: 
      stage: checklint    
      image: node:12.18-alpine 
      only:           
        refs:
          - merge_request
      script:
        - npm install eslint babel-eslint
        - npm run lint
    
    

    Ở đây sẽ có 2 từ khóa mới:

    image: dùng base image là node:12.18-alpine để tạo một node instance chạy các câu lệnh mình định nghĩa ở phần script

    onlyrefs: giới hạn việc chạy job này chỉ khi có merge request

    Bước 6: Tách nhánh mới, đẩy lại code lên gitlab và tạo merge request

    • Đây là ở merge request trước khi chúng ta đẩy file gitlab-ci.yml lên remote repo

    • Sau khi đẩy file gitlab-ci.yml lên

    Bây giờ nếu như mọi người thấy biểu tượng pipeline running như hình 2 ở trên là mình đã thành công rồi nha!

    Từ bây giờ mỗi khi bạn đẩy code mới lên remote repository thì gitlab runner sẽ tự động chạy test lint cho chúng ta!

    Nếu thích bài viêt này thì các bạn nhớ like and và share để ủng hộ mình có động lực viết phần tiếp theo về gitlab runner và CD nha <3

    P/s: Đây là bài viết đầu của mình nên không thể tránh khỏi những thiếu sót, có gì mong được mọi người bỏ qua và góp ý cho mình nha! Cảm ơn mọi người nhiều <3

  • Triển khai CI/CD cho iOS – SonarQube & Blackduck

    Overview

    Tiếp tục với series CI/CD cho iOS, hôm nay chúng ta sẽ triển khai CI với hai nền tảng kiểm tra source code rất nổi tiếng là SonarQube và Blackduck.

    Triển khai CI với Blackduck

    Khác với SonarQube, Blackduck không đánh giá chất lượng mà giúp chúng ta quản trị open source code và source code từ các thư viện được thêm vào. Giúp chúng ta đánh giá và quản lý được các rủi ro về bản quyền, bảo mật khi sử dụng source code có sẵn trên mạng cũng như lưu hành trong cộng đồng.

    Do Blackduck không có Server public như Sonar, nên mình sẽ giả định chúng ta có một Server Blackduck được đặt tại địa chỉ sau:

    https://blackduck.techover.io

    Sau khi truy cập vào, chúng ta sẽ thấy danh sách các project đã có ở Dashboard. Ở đây mình đã tạo sẵn một Project W95.CICD, nếu muốn tạo mới chúng ta sẽ ấn nút Create Project ở góc trên bên phải.

    Dashboard của Blackduck

    Cũng giống như Sonar, để có thể đồng bộ các dữ liệu Scan chúng ta cần có một Token. Để tạo Blackduck Token, chúng ta sẽ vào màn hình quản lý Access Tokens như sau:

    Ấn vào Profile ở góc trên bên phải, chọn My Access Tokens

    Sau khi chuyển đến màn hình quản lý Access Token, chúng ta ấn Create New Access Token và điền thông tin.

    Màn hình quản lý Access Tokens
    Nhập thông tin và ấn Create

    Sau khi có token, chúng ta sẽ lưu lại để sử dụng sau này. Lưu ý, token sẽ không hiển thị lại lần thứ 2 nên hãy copy và lưu lại ngay và luôn nhé. Ví dụ mình sẽ lưu lại token lại dưới đây:

    YWJmYzc5MDMS05N2VjLWFkNGE4ZMS05N2VjLWFkNGE4Z--=/

    Đối với Blackduck, chúng ta cũng sử dụng gần giống như Sonar nhưng thay vì CLI, chúng ta sẽ sử dụng Java Archive, vì vậy hãy đảm bảo bạn đã cài Java trong thiết bị Runner nhé.

    Tải về phiên bản .jar mới nhất của blackduck ở đây

    Sau khi tải xong, chúng ta sẽ giải nén và lưu và một thư mục trong thiết bị Runner, ở đây mình sẽ lưu ở địa chỉ sau:

    /Users/jena/CICD/synopsys-detect/synopsys-detect-6.4.1.jar

    Rồi xong, tới công chuyện luôn!!

    Tiếp đó chúng ta chỉ cần cấu hình file .gitlab-ci.yml như sau:

    stages:
      - Lint
    blackduck-detect:
      stage: Lint
      only:
          - cicd
      script:
        - java -jar /Users/jena/CICD/synopsys-detect/synopsys-detect-6.4.1.jar \
          --blackduck.url=https://blackduck.techover.io \
          --blackduck.api.token=YWJmYzc5MDMS05N2VjLWFkNGE4ZMS05N2VjLWFkNGE4Z--=/ \
          --detect.project.name=W95.CICD
      tags:
        - w95

    Vậy là xong, mỗi khi có commit/merge lên nhánh cicd (hãy thay bằng master/main/develop) thì hệ thống sẽ tự động scan source code và gửi kết quả lên Server.

    Quên mất, còn một bước cuối cùng nữa là bạn có thể che đi các thông tin nhạy cảm trong file cấu hình .gitlab-ci.yml, đề phòng trong trường hợp file cấu hình bị rò rỉ, các thông tin về Server Blackduck cũng như Token cũng sẽ không bị ảnh hưởng. Để làm việc này chúng ta sẽ cấu hình một số thông tin sau vào biến môi trường của Gitlab-CI

    BLACKDUCK_SERVER = https://blackduck.techover.io
    BLACKDUCK_TOKEN = YWJmYzc5MDMS05N2VjLWFkNGE4ZMS05N2VjLWFkNGE4Z--=/

    Và kết quả, file .gitlab-ci.yml sẽ trông như sau:

    stages:
      - Lint
    blackduck-detect:
      stage: Lint
      only:
          - cicd
      script:
        - java -jar /Users/jena/CICD/synopsys-detect/synopsys-detect-6.4.1.jar \
          --blackduck.url=${BLACKDUCK_SERVER} \
          --blackduck.api.token=${BLACKDUCK_TOKEN} \
          --detect.project.name=W95.CICD
      tags:
        - w95

    Rồi xong, tới công chuyện luôn!! Như vậy là chúng ta đã hoàn thành cấu hình CI với Blackduck để scan các lỗi hổng bảo mật, bản quyền. Mặc dù liên quan đến CI còn rất nhiều section như Coverity, Build & Compile nhưng mình xin phép tạm dừng hạng mục CI và chuyển sang CD. Rất mong được các bạn ủng hộ.

  • Triển khai CI/CD cho iOS – SonarQube & Blackduck

    Triển khai CI/CD cho iOS – SonarQube & Blackduck

    Overview

    Tiếp tục với series CI/CD cho iOS, hôm nay chúng ta sẽ triển khai CI với hai nền tảng kiểm tra source code rất nổi tiếng là SonarQube và Blackduck.

    Triển khai CI với SonarQube

    Đôi chút về SonarQube, đây là một nền tảng mã nguồn mở sử dụng để kiểm tra chất lượng của source code, đánh giá các lỗi ở nhiều mức độ và tiêu chí khác nhau. Mục đích cuối cùng là để thống kê và cải thiện chất lượng của source code theo mọi mặt cũng như giúp lập trình viên đánh giá chất lượng của chính mình. Vì vậy, việc sử dụng SonarQube để hỗ trợ quá trình phát triển, nâng cao chất lượng source code luôn được các doanh nghiệp lớn áp dụng.

    Chúng ta sẽ thực hiện CI với SonarQube server ở địa chỉ sau: https://sonarcloud.io, bạn có thể sử dụng tài khoản GitLab của mình để đăng nhập và tạo project ở đây. Sau một vài bước đăng nhập, tạo organization và chọn plan thì chúng ta sẽ đến với step đầu tiên.

    Đối với một số trường hợp có Server riêng để host SonarQube, các bạn hãy truy cập host và sử dụng tài khoản/mật khẩu được cung cấp bởi admin.

    Cấu hình SonarQube

    Việc đầu tiên, chúng ta sẽ cần tạo một project, ở đây mình sẽ tạo một project như hình sau:

    Điền Project Key và Display name sau đó ấn Set Up

    Trong một số trường hợp, bước config tạo Project sẽ do admin tạo, các bạn chỉ cần đăng nhập là xem được các project mình được phân quyền.

    Sau khi ấn Set Up, chúng ta sẽ được suggest 3 lựa chọn, ở đây mình sẽ chọn Manually để có thể sử dụng ở nhiều nền tảng khác nhau (Không chỉ riêng GitLab-CI).

    Lựa chọn Manually

    Tiếp đó chúng ta sẽ chọn các lựa chọn như hình sau:

    Chọn Other(…) -> macOS

    Tiếp đó chúng ta ấn Download để tải CLI của SonarQube về, giải nén và lưu vào một thư mục trong thiết bị runner. Giả sử mình sẽ lưu ở địa chỉ sau:

    /Users/jena/Projects/sonar-scanner/bin

    Mình sẽ thêm thư mục bin vào trong PATH của macOS bằng câu lệnh sau

    export PATH=$PATH:/Users/jena/Projects/sonar-scanner/bin

    Sau đó, bạn có thể chạy lệnh sau để kiểm tra xem cli đã được nhận vào PATH chưa. Sẽ có một số lỗi yêu cầu cấp quyền để chạy CLI, bạn hãy vào System Preference -> Security & Privacy -> Tab General -> Allow Anyway tất cả

    sonar-scanner -v
    Sau khi cli được add vào PATH, bạn có thể chạy bằng lệnh sonar-scanner

    Tiếp đó, chúng ta sẽ vào GitLab và cài đặt biến môi trường SONAR_TOKEN như hình sau

    Tiếp đó, chúng ta sẽ cấu hình CI ở file .gitlab-ci.yml như sau, phần script sẽ được gen cùng với SONAR_TOKEN, các bạn chỉ cần copy và paster vào là được:

    stages:
      - Lint
    sonar-scanner:
      stage: Lint
      only:
          - cicd
      script:
        - sonar-scanner -Dsonar.organization=w95 -Dsonar.projectKey=CICD.iOS -Dsonar.sources=. -Dsonar.host.url=https://sonarcloud.io -Dsonar.branch=master
      tags:
        - w95

    Sau khi commit file .gitlab-ci.yml lên branch cicd, chúng ta sẽ có kết quả như sau:

    Job Sonar Scanner chạy thành công với log như trên
    Màn hình thống kê trên SonarCloud.io cũng sẽ hiển thị các thông số của source code

    Theo như ảnh trên, Quality Gate đang đánh giá Passed tức là source code đạt chất lượng, nhưng thật ra mình scan Starter Project của iOS nên mới không có lỗi, còn code của mình thì lắm lỗi lắm :p

    Như vậy là chúng ta đã hoàn thành bước cấu hình CI sử dụng SonarQube cho một project iOS. Mỗi khi có commit, merge hay sự thay đổi trên branch cicd (bạn sẽ đổi thành master/develop/main) thì hệ thống sẽ tự động chạy CI và đẩy thống kê lên Sonar server. Chúng ta chỉ cần lên đó, tracking các thông số và sửa các lỗi bị cảnh báo là được.

    Do bài viết hơi dài, nên mình sẽ để phần Blackduck sang bài viết sau. Cảm ơn các bạn đã đọc!

    Authors

    LinhNB1