Categories
DevOps

System Design (Thiết kế hệ thống)

Trong bài chia sẻ về kiến trúc Microservices cho các bạn sinh viên tuần trước, mình có nói rằng ở công ty công nghệ thông thường thì có thể có hàng chục developer, nhưng chỉ có 1 hoặc 2 system designer (Nhà thiết kế hệ thống) và tất nhiên vị trí này nhìn chung sẽ được săn đón và bổng lộc cao hơn developer.

“Từ một developer trở thành một system designer mất bao lâu?” là câu hỏi mà một bạn sinh viên đã đặt ra cho Tuấn trong buổi chia sẻ này và thiết nghĩ đây là một câu hỏi hay và câu trả lời của mình sẽ giúp các bạn định hướng được chặn đường phát triển của một developer trong tương lai.

Một developer có thể có nhiều con đường phát triển sự nghiệp, trở thành một system designer là một lựa chọn không tệ. Hiện nay, bạn có thể khá dễ dàng để tuyển một developer, nhưng tuyển những vị trí system designer rất là khó, và thông thường ở những vị trí như CTO (giống Tứng) mới có skill này (flex tí ^^!).

Kiến thức của một system designer sẽ rất là dàn trải và đòi hỏi nghiên cứu, thử nghiệm không ngừng nghỉ vì số lượng những công nghệ mới sinh ra liên tục, phải phân tích, đánh giá, so sánh các công nghệ và tìm ra những công nghệ phù hợp với lộ trình phát triển của công ty và tốt nhất là đi lên từ một developer.

Cho nên để từ một developer trở thành một system designer thì cần khoảng 5 năm theo đuổi lĩnh vực này, am hiểu về cách hệ thống vận hành và các thành phần trong một hệ thống liên kết với nhau thế nào, cũng như điểm mạnh, điểm yếu của các thành phần trong tổng thể hệ thống.

Do đó, nếu muốn xây dựng một đội ngũ công nghệ, trước tiên bạn phải tìm system designer, đừng tìm developer nếu không muốn nhiều vấn đề nhức đầu về sau.

Liệu mở một lớp dạy về System Design dành cho developer thì có ai học không ta ?!!!

Categories
DevOps Miscellaneous

Ghi chép về Devops #1

Sau 2 tuần mò mẫm trong Google Cloud, PHP, Apache, Go, Angular … để hỗ trợ một ông bạn kiểm soát lại toàn bộ hệ thống cũ do team dev quăng bom để lại, thì thấy nếu sản phẩm tech hay đội code trong nhà của mấy sếp mà có một trong bảy triệu chứng dưới đây thì nên bắt đầu lo sợ là vừa, vì có thể một ngày nào đó lại inbox Tứng và hỏi thuốc giải.

Các ý tưởng này chỉ là góc nhìn cá nhân dựa trên đống shit mà Tứng ngụp lặn bữa giờ, mong ace đừng ném đá và kì thị:

🚩 – Không có CI/CD, code được pull trực tiếp trên production server thông qua Git và chỉnh sửa trực tiếp bằng tay (rồi không biết có bị quên push ngược lên repo hay không)
🚩 – Không có tài liệu cài đặt hệ thống
🚩 – Cấu hình như URL, Key, IP… được set cứng trong source code (Ví dụ các so sánh if … else, switch…case…) hoặc nằm rải rác ở rất nhiều file khác nhau
🚩 – Không có Load Balancer routing vào các server bên trong (Ví dụ Haproxy, Traefik, Cloud Load Balancer..)
🚩 – Không sử dụng Docker (hay công nghệ tương tự) hoặc thực hiện `docker build…` tại máy của cha nội dev hoặc trên server rồi push trực tiếp lên Registry
🚩 – Source code được đóng gói và publish công khai trên Docker Hub.
🚩 – Web server (Apache, Nginx..) tự handle các xử lý liên quan SSL Certificate

Hy vọng sau status này thì team DevOps của mấy sếp có một vài ngày làm việc bận rộn hoặc…bỏ của chạy lấy người.

Categories
DevOps

WEBSOCKET: TỪ SOCKETIO ĐẾN SOKETI

Khoảng 8 năm trước, từ lúc bỏ mô hình Server Side Rendering (jQuery, Ajax…) và bắt đầu xây dựng hệ thống dựa trên mô hình Microservices & Single Page App (SPA) và sử dụng hoàn toàn frontend bằng Reactjs thì cũng là lúc bắt đầu chơi với WebSocket cho các tính năng tương tác hai chiều (Client < — > Server) theo thời gian thực.

Hoạt động của WebSocket khá đơn giản: trình duyệt kết nối tới một Socket server, lắng nghe trên 1 (hoặc nhiều) channel mình khai báo rồi…chờ message trên channel đó. Server muốn gửi dữ liệu cho trình duyệt của user thì chỉ cần đẩy message vào channel này, trình duyệt sẽ tự động nhận message và xử lý (ví dụ như hiển thị thông báo trên trình duyệt hoặc một hành động khác.)

Nhiều người khi nghe nói đến Websocket thì chỉ nghĩ đến các tính năng realtime chat, nhưng Websocket có nhiều ứng dụng, trong đó mình sử dụng nhiều nhất cho 3 chỗ trên trình duyệt: Nhận thông báo mới (Notification), Cập nhật dữ liệu cache trên trình duyệt (Realtime Data Update) và Trạng thái online của user (Online Presence).

Các phần mềm hỗ trợ WebSocket Server

Thời bấy giờ không có nhiều lựa chọn, ứng cử viên sáng giá nhất là Socket.IO: Open Source – Miễn phí, tự cài đặt trên server riêng và Pusher: Cloud, không cần cài đặt gì hết và trả phí.

Socket.IO thì hầu như không có nhiều đồ chơi, ngoài việc chỉ hỗ trợ trình duyệt kết nối socket tới server, không có cơ chế xác thực (Authentication), muốn làm gì thì phải chủ động code tính năng hết. Do mình làm ứng dụng cho dự án SaaS và multi-tenant nên khối lượng code viết thêm nếu làm trên SocketIO là quá lớn nên buộc phải tìm giải pháp khác, và lúc này Pusher đã xuất hiện.

Pusher cung cấp cơ chế WebSocket với nhiều tính năng, trong đó có 2 tính năng nổi bật nhất là hỗ trợ Authentication xác thực user kết nối và Presence Channel để biết ai đang online/offline. Ngoài ra, Pusher còn cung cấp các thư viện tích hợp cho cả Javascript (gắn ở trình duyệt) và PHP (gắn ở server) để xác thực và trao đổi dữ liệu realtime giữa client và server được đơn giản và nhanh chóng, rút ngắn thời gian phát triển.

Có một bất lợi duy nhất của Pusher là…chi phí. Do đặc thù của một hệ thống Realtime là số lượng kết nối thường xuyên đến socket server, nên chi phí tính dựa vào số lượng kết nối. Sau khi đánh giá thì với chi phí của Pusher so với những lợi ích nó mang lại thì quyết định chọn Pusher.

Thay thế Pusher bằng Soketi

Mười ngày trước, nằm trong tuần lễ cải tổ hệ thống, trong đó có hạng mục nâng cấp phân hệ Pusher sau một thời gian dài sử dụng, thì thấy chi phí hiện tại không ổn nếu scale lên vài ngàn socket connection dựa trên Pusher, cũng như áp dụng cho các khách hàng outsourcing.

Thật may mắn là đã tìm ra Soketi, một open source cài đặt trên server của mình và cung cấp các tính năng gần như tương tự của Pusher. Và đặc biệt quan trọng là tương thích hoàn toàn với các thư viện kết nối ở phía Javascript và PHP, tức là không phải thay đổi cơ chế kết nối và lắng nghe message, chỉ cần cài Soketi (có hỗ trợ docker), sau đó thì config trỏ lại kết nối đến Soketi Websocket server vừa cài là xong.

Sau một tuần triển khai lên các môi trường dev và production cho các dự án thì thấy hoàn toàn ổn định, kết quả này cũng là cảm hứng cho bài viết chia sẻ công nghệ này. Hy vọng anh chị em có một hệ thống Websocket ngon, bổ rẻ để xây dựng các tính năng realtime cho môi trường web.

Categories
DevOps Technology

CHỌN CX HAY DX?

Dạo gần đây trên twitter có rất nhiều chỉ trích từ cộng đồng làm frontend về việc bản #Nextjs 13.4 cho tới 13.6 có trải nghiệm compile ở máy rất tệ khi sử dụng kiến trúc mới là App Router. Thời gian mỗi lần hot reload tính bằng giây và bản thân Tuấn đang phát triển cho hệ thống IDP của #cropany cũng thấy trải nghiệm tương tự, gần đây mỗi lần nhấn lưu là banh xác, chờ vài giây và compile vài ngàn module.

Việc này ảnh hưởng lớn đến #DX – hay gọi dễ hiểu là Trải nghiệm của lập trình viên (Developer Experience), cái được cộng đồng đánh giá công ty Vercel (hay dự án Nextjs) là một công ty có DX rất tốt, riêng mình nói về vụ DX thì Vercel là dẫn đầu thị trường thì nay lại bị chê bai rất nhiều.

Tuy nhiên, giá trị của kiến trúc App Router từ bản Nextjs 13.4 là không thể chối từ, nó đem lại trải nghiệm tốt hơn về thiết kế UI, Render flow (page, layout, template, server component, server action…) và cải thiện SEO ở mức tốt nhất cho các dự án CMS/Ecommerce. Đây cũng là lý do chính mà cả bộ sậu Vercel đầu tư rất nhiều vào kiến trúc App Router (thư mục “app”) để thay thế cho kiến trúc thư mục “pages” cũ. Những lợi ích này có giá trị rất lớn cho #CX – hay gọi dễ hiểu là Trải nghiệm của khách hàng (Customer Experience) 👍.

Các cuộc tranh cãi nổ ra kiểu nếu DX của App Router còn tệ hại như vậy thì chúng tôi sẽ bỏ đi, sẽ Vite + ReactJs, sẽ Angular, sẽ Gatsby 🤭…mà hoàn toàn phủ nhận giá trị của CX mà Nextjs mang lại.

Lịch sử đã cho thấy, chúng ta (developer) sẽ sẵn sàng đánh đổi 1 chút DX của mình để khách hàng được trải nghiệm tốt nhất. Chúng ta đã từng có giai đoạn dành cả tiếng đồng hồ để compile một phần mềm trong mỗi lần thử, dành cả ngày để render cái scene 3D cho đẹp hơn xí,… mà giờ có vài giây cũng kêu ca. Tuy nhiên, nếu sự chờ đợi dành cho quá trình phát triển để đem lại trải nghiệm cho khách hàng (CX) tốt hơn thì hoàn toàn xứng đáng. Vì trải nghiệm khách hàng khi sử dụng website tốt sẽ đem lại tiền cho doanh nghiệp.

Nói đi cũng phải nói lại, nếu DX của hệ thống tệ và đối thủ làm tốt hơn thì cộng đồng có thể quay lưng (như cách chúng ta đã quay lưng với nhiều thứ để đến với Nextjs). Hoặc nếu trong một dự án mà DX tệ thì sẽ ảnh hưởng không tốt đến nguồn lực dev của công ty. Mọi người sẽ tiếp cận dự án khó hơn, phát triển tính năng lâu hơn, chỉnh sửa, nâng cấp cũng vất vả…

Và sáng nay thật nhanh chóng, NextJs đã ra bản 13.4.8 với rất nhiều cải thiện để tối ưu cho DX sau rất nhiều càm ràm (và đòi bỏ đi) trên mạng. Quả thực, đi theo Nextjs từ bản 8.x tới giờ thì Nextjs không bao giờ làm ta thất vọng và phải nói trải nghiệm DX và CX từ Nextjs luôn làm ta ngạc nhiên và xứng đáng đầu tư toàn bộ stack ở cms/ecommerce đi theo Nextjs. Và việc release này cũng làm cảm hứng cho bài chia sẻ này.

Categories
DevOps

Công bằng khi xử lý bất đồng bộ ở Microservice

Suốt tuần qua mình tập trung xử lý một bài toán khá “kinh điển” trong mô hình multi-tenant là đem đến “sự công bằng” (fairness) đối với các xử lý bất đồng bộ (async process). Nhận thấy vấn đề và giải pháp này sẽ giúp ích nhiều cho các anh em làm SaaS nên mình chia sẻ cách triển khai trong bài viết này.

Bài viết này dành cho những bạn đã từng thiết kế hệ thống theo mô hình bất đồng bộ như sử dụng Job Queue và đang có mô hình kinh doanh Multitenancy.

Vấn đề

Nếu bạn xây dựng hệ thống theo kiến trúc trúc Microservice thì bạn sẽ thường xuyên đối mặt với mô hình xử lý bất đồng bộ (async processing). Bởi vì đặc thù các service/process hoạt động tách biệt, để tránh ảnh hưởng tới thời gian chờ xử lý thì hầu hết hết các business logic phức tạp đều chạy bất đồng bộ (async). Trong trường hợp của Cropany là tính năng cập nhật tồn kho cho sản phẩm.

Theo như thiết kế hiện tại, tính năng “Số lượng tồn kho sản phẩm” được thiết kế theo pattern CQRS/ES. Tức là mọi thao tác liên quan đến tồn kho như Nhập kho (mua hàng), Xuất kho (bán hàng) đều có những record ghi nhận thao tác nhập xuất sản phẩm, sau đó sẽ kích hoạt API bất đồng bộ xử lý tính toán số tồn riêng của từng sản phẩm vào database (elasticsearch).

Trước đây, để tính toán tồn kho của sản phẩm, thì hệ thống có 1 queue chứa danh sách ID các sản phẩm cần cập nhật tồn kho. Khi 1 sản phẩm có tương tác nhập xuất thì ID sản phẩm này sẽ được đẩy vào queue, và worker sẽ lắng nghe trên queue này, các worker sẽ lấy đều đặn một số lượng nhất định ID (ví dụ: 10 ID/lần xử lý) và tiến hành tính toán số tồn và lưu vào database.

(Hình minh hoạ quá trình xử lý bất đồng bộ. Nguồn: Medium)

Do theo kiến trúc queue FIFO nên ID sản phẩm nào được đưa vào trước thì sẽ được worker xử lý trước và xử lý tuần tự cho tới khi queue này không còn message nào. Nếu mô hình của bạn là Single-tenancy thì sẽ không có vấn đề gì, nhưng nếu bạn đang theo mô hình Multi-tenancy và số lượng tenant khá lớn (ví dụ hơn 10,000 tenant) thì vấn đề có thể sẽ khá lớn.

Hãy hình dung, trong số các tenant, vào 1 thời điểm sẽ có tenant (gọi là Tenant-A) đẩy vào queue 5000 ID sản phẩm (họ đang nhập kho) và liền ngay sau đó có tenant khác (gọi là Tenant-B) chỉ xuất kho 1 sản phẩm (bán hàng). Theo như kiến trúc ban đầu thì queue sẽ có 5001 ID sản phẩm, và worker sẽ tiến hành lấy tuần tự số lượng ID cho phép xử lý (ví dụ 10 ID/lần xử lý) và như vậy, Tenant-B sẽ phải chờ worker xử lý hết 5000 message của Tenant-A mới đến lượt mình. Hiện tại không có sự ưu tiên nào giữa các tenant nên Tenant-B có thể bị coi là “thiệt thòi” hơn so với Tenant-A.

(Hình minh hoạ các message của các tenant được đẩy tuần tự vào queue để xử lý tuần tự. Nguồn: medium)

Trong ví dụ trên thì chỉ có 2 tenant, giả sử hệ thống multi-tenancy của bạn có 10,000 khách hàng và 10,000 tenant này đều nhập xuất kho với số lượng chênh lệch thì tenant cuối cùng đẩy ID sản phẩm vào queue sẽ phải chờ lâu nhất. Giải pháp hướng đến là đem lại “sự công bằng” cho các tenant nếu như mọi tenant đều có đặc quyền như nhau. Trong trường hợp của Cropany là khách hàng nào cũng miễn phí, dù dùng ít hay nhiều đều phải có sự công bằng và độ trễ ngang nhau khi sử dụng.

Giải pháp không hiệu quả

Giải pháp ban đầu có thể nghĩ ngay tới là đối với mỗi tenant thì mình sẽ có 1 queue riêng và tương ứng với mỗi queue sẽ có 1 worker lắng nghe và xử lý.

Về mặt performance thì giải pháp này không hiệu quả vì đồng thời có hàng ngàn worker tính toán tồn kho thì bottleneck sẽ rơi vào database chứa các giao dịch nhập xuất. Thay vì giải pháp cũ thì mỗi lần worker chỉ vào xử lý 1 số lượng nhất định và kiểm soát được (ví dụ 10 ID Sản phẩm / lần) thì bây giờ số lượng worker lớn làm tăng tải của quá trình xử lý, trong khi throughput của hệ thống chỉ cho phép 10 ID Sản phẩm / lần.

Về mặt kinh tế thì hoàn toàn không khả thi, tài nguyên để hỗ trợ chạy hàng ngàn worker và hàng ngàn queue không hề nhỏ và nó lại tăng tịnh tiến (linear) theo số lượng tăng thêm của tenant. Trong trường hợp của Cropany thì càng không thể vì chúng mình không thu phí khách hàng. Ngoài ra, sẽ có những tenant rất ít hoạt động, worker vẫn phải duy trì trong khi không có xử lý thì là một sự lãng phí.

Giải pháp của Cropany

Sau khi cân nhắc về hiệu suất, kinh tế và khả năng bảo trì nâng cấp thì mình quyết định sử dụng mô hình 1 queue, một worker như ban đầu để đảm bảo worker hoạt động đúng công suất cho phép và không gây áp lực lên hệ thống chung (các tính năng khác liên quan đến database). Tuy nhiên, khi phát sinh nhập xuất kho sản phẩm thì thay vì đẩy trực tiếp vào queue, các ID này sẽ được đưa vào một SET trong Redis, và mỗi tenant sẽ có một SET riêng (key riêng).

Giới thiệu nhanh về SET trong Redis, SET cho phép lưu trữ một danh sách các value không trùng. Trong trường hợp này, nếu ID sản phẩm đã tồn tại trong SET của tenant nào rồi thì khi phát sinh giao dịch, thêm 1 value vào SET mà đã tồn tại value này thì SET sẽ không lưu thêm, luôn đảm bảo value là không trùng.

Sau khi các tenant có các SET riêng thì mình viết một chương trình điều hướng nho nhỏ (nodejs), đều đặn đọc tất cả các SET này ra và cắt lát theo chiều dọc qua toàn bộ các tenant và đẩy vào queue thông thường. Theo cách này thì tenant dù có ít hay nhiều đều sẽ được xử lý song song theo từng phiên. Giả sử có 10k tenant và tenant nào cũng có ID sản phẩm cần xử lý thì bộ điều hướng này sẽ lấy mỗi tenant 1 ID và đẩy vào queue. Queue có thể chứa hàng triệu message, tuy nhiên, thứ tự các message khi thêm vào đã đảm bảo gần như công bằng cho các tenant.

(Hình minh hoạ bộ điều hướng cần tiền xử lý các message trước khi đẩy vào queue)

Sau khi đẩy vào queue thì bộ điều hướng này sẽ xoá ID khỏi SET trong Redis để tránh quá trình xử lý trùng lập.

Có một lưu ý nho nhỏ là bộ điều hướng này cần chạy theo interval (đơn vị giây) để đảm bảo có các tenant mới phát sinh SET mới thì vẫn được đưa vào queue chính. Hình minh hoạ bên dưới là quá trình mình visualize bộ điều hướng để debug quá trình cắt lát cho các bạn dễ hình dung.

Trong hình là bộ điều hướng chạy 2 phiên. Mỗi phiên tìm thấy 13 tenant (13 màu), và chỉ có 6 tenant có message cần xử lý, mỗi phiên bộ điều hướng sẽ lấy 6 message từ 6 tenant và đẩy vào queue.

Mở rộng

Như vậy, với kiến trúc này thì vẫn đảm bảo cơ chế scale hệ thống như là tăng cường throughput của worker, tăng số lượng worker nếu muốn xử lý nhanh hơn khi hệ thống cho phép.

Bên cạnh đó, nếu có cơ chế ưu tiên giữa các tenant thì có thể tăng cường queue riêng để áp dụng 1 số quyền lợi cho các tenant được ưu tiên.

UPDATED: Sau khi update lên UI thì mình chuyển sang SORTED SET thay vì SET nhé các đồng code. Sorted set sẽ lưu thêm score để đảm bảo độ ưu tiên và mình lấy timestamp làm score, còn SET thì không lưu thứ tự nên sẽ ko thể hiện rõ FIFO khi thêm vào SET. 


Bài viết sử dụng một số hình ảnh từ bài viết https://medium.com/thron-tech/multi-tenancy-and-fairness-in-the-context-of-microservices-sharded-queues-e32ee89723fc cũng nói về đề tài fairness, các bạn có thể tham khảo cách tiếp cận khác của tác giả bài viết này.

Categories
DevOps

Speed up Microservices 4: Asynchronous Big Task

Nối tiếp nội dung về tối ưu cho kiến trúc Microservices, lần này mình sẽ đề cập đến một bài toán cũng gặp rất nhiều và có thể coi là giải pháp khác của bài trước là Export dữ liệu. Nếu như ở bài trước, việc export dữ liệu được diễn ra ở Frontend (JS) bao gồm tải dữ liệu có phân trang và đóng gói excel thì có những hạn chế sau:

  • Nếu dữ liệu nhiều sẽ ảnh hưởng đến frontend vì memory có giới hạn
  • Quá trình export này là đồng bộ, tức là nếu đóng tab hoặc reload trang là tải lại từ đầu
  • Tính năng cần xuất dữ liệu này chưa có giao diện frontend, kéo theo thêm việc làm (làm trang danh sách) nếu chỉ muốn export.

Do những hạn chế trên nên cách đây ít lâu vẫn còn 1 vài tính năng Export dữ liệu phải được thực hiện ở phía server và rất khó đưa xuống Frontend vì yếu tố phức tạp của lấy dữ liệu và số lượng dữ liệu lớn. Những tính năng export kiểu cũ này kéo theo hạn chế rõ ràng là script sẽ chạy lâu và luôn kích hoạt cơ chế slow log vì có những script chạy cả tiếng đồng hồ chưa xong file excel để export và Memory chiếm vài GB cho 1 process. Nếu chỉ cần vài khách hàng vào thực hiện tính năng Export thì sẽ dẫn đến hệ thống treo, và trong môi trường không kiểm soát được thì làm sao có những yêu cầu dạng “cậu chỉ được export vào lúc X giờ”…

Do những hạn chế của cả 2 hình thức công việc như trên nên Teamcrop đã hoàn thiện giải pháp khá tối ưu, hiệu quả và an toàn cho người và của, và đó chính là nội dung của bài chia sẻ này.

Big Task là gì?

Trước tiên, cần phải định nghĩa Big Task là gì để dễ dàng thảo luận. Big Task ám chỉ những task khi thực hiện sẽ tốn khá nhiều thời gian và tài nguyên (Memory, CPU..). Ví dụ như những tính năng export tồn kho hàng chục ngàn sản phẩm, SKU, import hàng ngàn sản phẩm, đơn hàng, hoặc đi spam email, sms…

Một số đặc điểm của hệ thống BigTask mà Teamcrop đã xây dựng là:

  • Có thể chia nhỏ
  • Kiểm soát được tiến độ công việc (Bao nhiêu % hoàn thành..)
  • Bất đồng bộ: người dùng có tắt trình duyệt hay tắt máy thì hệ thống vẫn cứ làm việc
  • Có khả năng retry

Big Task được vận hành thế nào?

Tất nhiên, mỗi task bây giờ được định nghĩa là một row trong Database, mỗi task sẽ có 3 giai đoạn:

  • Bước 1: Chuẩn bị
  • Bước 2: Xử lý dữ liệu
  • Bước 3: Kết thúc

Bước chuẩn bị thường được trigger bởi người dùng và từ giao diện frontend, như submit form chứa thông tin. Ở bước chuẩn bị này chính là giai đoạn quan trọng nhất, bao gồm khởi tạo tổng số record cần xử lý (để kiểm soát progress), ghi nhận các cấu hình cần thiết cho task như số lượng record cần xử lý 1 trang, các dữ liệu cài đặt khi người dùng submit form và một bước quan trọng là khởi tạo 1 file excel chỉ có header (không có dữ liệu) ở môi trường local.

Bước xử lý dữ liệu là công việc chính liên quan đến nhiệm vụ, như lấy dữ liệu để insert vào file excel theo từng batch, ví dụ nếu task được giao mỗi trang lấy 100 record thì chỉ lấy 100 record và đẩy nhiệm vụ trang tiếp theo vào Messsage Queue (ví dụ RabbitMQ), hệ thống Queue worker sẽ chịu trách nhiệm thực thi trang tiếp theo dựa vào nội dung trong message đã được đẩy vào queue.

Bước kết thúc chính là đảm bảo dữ liệu đã đầy đủ và đưa các file cần thiết vào hệ thống, đối với Teamcrop là gọi File Service để lưu file tạm ở trên, thông báo và kết thúc task để đánh dấu task hoàn tất.

Như vậy, với sự giúp đỡ của kiến trúc Message Queue thì những task lớn có thể chia nhỏ và tiến trình được kiểm soát, đặc biệt là giúp cho hệ thống không có những script chạy quá lâu và tốn quá nhiều tài nguyên. Hiện tại bên Tuấn đã bắt đầu migrate dần các task dạng này vào mô hình Big Task của mình để tăng performance của hệ thống.

Hy vọng bài viết này sẽ giúp các bạn đối phó với một trong những bài toán đau đầu khi scale.

 

Categories
DevOps

Speed up Microservices 3: Export dữ liệu ra Excel

Chào mọi người, nối tiếp bài trước là tận dụng caching ở trình duyệt để tăng tốc cho microservices. Ở bài này mình sẽ chia sẻ một case study khá thông dụng trong các dự án “vừa vừa”, đó là nhu cầu export dữ liệu ra file excel. Nếu như các bạn thường xuyên làm việc liên quan đến các hệ thống quản lý dữ liệu thì data table / grid sẽ xuất hiện khá nhiều, kéo theo tính năng thường xuyên được yêu cầu là export dữ liệu (ra file Excel).

Categories
DevOps

Speed up Microservices 2: Tận dụng trình duyệt và cache

Cũng gần 9 tháng kể từ bài 1 ra mắt, trong bài này mình sẽ chia sẻ cách hệ thống Teamcrop đã tối ưu thế nào để tăng tốc cho hệ thống Microservices của mình. Cũng như bài 1 có đề cập, dữ liệu trong hệ thống microservices khá phân tán và khi cần lấy dữ liệu nếu không thiết kế tốt thì sẽ tốn rất nhiều thời gian cho các request lấy dữ liệu. Ví dụ lấy danh sách đơn hàng là một ví dụ rõ ràng nhất có nêu ở bài trước.

Teamcrop cũng lưu dữ liệu dạng này, trong một đơn hàng được lưu trong database thì chỉ lưu ID (Khóa ngoại) của các bảng như nhân viên, cửa hàng, nguồn đơn hàng…mà các thông tin này chỉ được cung cấp bởi các service liên quan như service Nhân viên, Store và Metadata. Để hiển thị thông tin như giao diện thì cần phải gọi thêm nhiều request (Restful) để fetch data từ các nguồn này và đây là cách hoàn toàn không hiệu quả bởi ảnh hưởng đến thời gian chờ của người dùng.

Trong kiến trúc của Teamcrop, các thông tin như nhân viên, cửa hàng, nhà kho, sản phẩm, … này được gọi là Tài nguyên công ty (Company Resource – CR) và hiện Teamcrop có khoảng gần 20 company resource.

Cách hoạt động của các Company Resource:

Các company resource cũng là dữ liệu và tất nhiên vẫn có các API như các resource khác, hầu hết đều được quản lý (CRUD) và có giao diện tương ứng để quản lý. Chỉ có một khác biệt là các company resource sẽ có thêm 1 web service cho phép lấy toàn bộ dữ liệu, gọi là api /fulldata. API này cho phép lấy toàn bộ dữ liệu resource, không phân trang.

Khi có một user truy cập vào website thì sẽ load toàn bộ các company resource và lưu trữ trong trình duyệt. Mình có thể setup thêm giao diện để thể hiện progress bar, khi nào toàn bộ company resource được load hết thì cơ chế router của web sẽ bắt đầu và tiến hành load giao diện tính năng đang truy cập. Hình dưới là ví dụ một action viết bằng PHP của việc lấy một company resource là Vendor (thương hiệu):

Company Resource sẽ ảnh hưởng thời gian tải trang

Nếu đọc đến đây thì sẽ có nhiều bạn thắc mắc là nếu mỗi truy cập đều phải load toàn bộ Company Resource thì sẽ chờ khá lâu bởi vì mỗi CR sẽ cần 1 request đến /fulldata và API này cũng phải lấy toàn bộ data của resource và response về.

Đối với phía server thì sẽ không có vấn đề nhiều vì mình sẽ tận dụng cache (Redis) để lưu kết quả vào memory và khi có request thì mình sẽ lấy từ cache ra mà thôi.

Ở phía client (trình duyệt) thì mới là vấn đề lớn, bởi dù server có nhanh thì có bao nhiêu company resource thì cũng sẽ tốn ngần ấy số request lên server để lấy /fulldata. Tuy nhiên, Teamcrop hầu như không tốn 1 request nào cho các company resource khi sử dụng…Service Worker của trình duyệt.

Đối với các API /fulldata thì script trong service worker của mình sẽ lấy từ cache trên trình duyệt ra mà không phải tốn 1 request nào lên server. Như vậy là tiết kiệm được mấy chục request và có được một số lượng data để hỗ trợ lấy dữ liệu.

Company Resource sẽ được lưu trữ thế nào?

Sau khi lấy được các dữ liệu company resource thì việc lưu trữ và sử dụng các dữ liệu này cũng là một vấn đề lớn. Bởi các company resource này sẽ được dùng cho việc tìm kiếm theo 1 ID (getById), tìm theo một số tiêu chí (search) và lấy toàn bộ danh sách (getList).

Để đảm bảo quá trình truy xuất, tìm kiếm thì nó phải nhanh cũng như gần gũi, dễ sử dụng nên mình đã chọn TaffyDB, được coi là một thư viện nhỏ gọn, In-memory Database, hỗ trợ tìm kiếm theo ID, điều kiện..và khá dễ dùng. Mỗi Company Resource sau khi lấy về sẽ trở thành 1 database và có thể truy xuất data thoải mái. Khá nhanh bởi vì nó là in-memory.

Như vậy thì từ giờ, mỗi khi có 1 dữ liệu có ID là ID của một company resource thì có thể tìm kiếm và lấy thông tin một cách nhanh nhất từ bộ nhớ của trình duyệt mà không phải tốn công JOIN bảng, request giữa các service hoặc client gửi request để lấy detail.

Tuy nhiên, một vấn đề lớn hơn đã xuất hiện đó là nếu dữ liệu company resource đã cache hoàn toàn ở trình duyệt thì khi có dữ liệu mới thì làm sao cập nhật (hay xóa cache). Đối với vấn đề này phát sinh 2 tình huống từ dễ đến khó, tình huống đầu tiên là xóa cache đối với người refresh lại trang web (reload) và tình huống thứ 2, đau đầu hơn là bởi vì Teamcrop là dạng Single Page App (SPA), nếu người dùng không reload trang thì làm sao cập nhật dữ liệu mới nhất. Mình sẽ xử lý lần lượt từng tình huống.

Xóa cache của Company Resource khi reload trang

Đối với trường hợp người dùng reload lại trang web thì chỉ cần thêm cơ chế versioning vào các company resource là giải quyết được.

Những thao tác thay đổi dữ liệu liên quan đến Company Resource (thêm, xóa, sửa) thì đều được gọi 1 API để nâng version của resource tương ứng.

Trước khi request các /fulldata API, hệ thống sẽ gửi request lấy thông tin version của các company resource, từ đó mới request để lấy full data. Do version được nối vào URL của fulldata nên Service Worker sẽ kích hoạt cơ chế cache theo URL, nếu version không đổi thì sẽ không request lên server, ngược lại thì sẽ request lên server.

Thậm chí còn làm tính năng nho nhỏ trên giao diện để debug version của các resource trong trường hợp không thể inspect được.

Xóa cache của Company Resource khi đang sử dụng và không reload

Đối với trường hợp một user vẫn “miệt mài” làm việc và không reload trang, bởi đặc thù của Single Page App là không reload mỗi khi chuyển tính năng nên xóa cache của company resource cho đối tượng này sẽ không theo cách thông thường. Bài toán đặt ra là làm sao trigger (thông báo) cho trình duyệt biết là có sự thay đổi là được bởi vì nếu được trigger thì chỉ cần gọi ajax lên Company Resource API sẽ lấy được bảng version của các resource và đối chiếu cái nào tăng thì lấy bản mới của /fulldata.

Suy nghĩ một hồi và nghĩ ngay đến giải pháp sử dụng Socket. Và Teamcrop sử dụng Pusher để cung cấp giải pháp socket bởi vì đây là một trong những dịch vụ tốt nhất hiện nay đối với dịch vụ socket.

Nhằm tận dụng cơ chế web socket sẵn có của Teamcrop (dùng cho cơ chế web notification) nên trình duyệt tiến hành “subscribe” để nghe trên một channel và mỗi khi version tăng thì server (PHP) của Teamcrop gửi một message cho trình duyệt đang lắng nghe ở một channel chung của công ty, tất nhiên thông qua Pusher API.

Sample code ở client (Javascript):

Sample code ở server (PHP):

Như vậy, mỗi khi company resource service nhận được yêu cầu nâng version thì sẽ trigger và client sẽ tự động cập nhật dữ liệu đang cache trên trình duyệt. Và giải quyết ổn thỏa bài toán cache data cho Company Resource.

Tiêu chí nào để trở thành Company Resource?

Như từ đầu đã chia sẻ, Company Resource sẽ lưu ở máy giúp  việc “phân giải” các ID khóa ngoại thành dữ liệu để hiển thị nhằm cải tiến performance chung của toàn hệ thống. Tuy nhiên, không phải tất cả tài nguyên đều có thể trở thành Company Resource.

Trước tiên, cần xác định rõ là Company Resource không phải là Transaction Data, ví dụ Đơn hàng, Khách hàng…không thể nào là ứng cử viên trở thành Company Resource bởi tính thay đổi cũng như số lượng của nó. Chỉ nên chọn những dữ liệu thường xuyên được tham chiếu, số lượng dữ liệu ít, tránh làm trình duyệt lưu quá nhiều dữ liệu vào bộ nhớ.

Lời kết, như vậy cùng với việc thay đổi mô hình microservice thì cũng phát sinh nhiều vấn đề mới liên quan đến kiến trúc này. Trên đây chỉ là một giải pháp cho một bài toán cụ thể mà Teamcrop gặp phải trong quá trình vận hành hệ thống của mình. Kỳ tiếp theo (kỳ 3) mình sẽ đề cập đến một bài toán cụ thể hơn là Export dữ liệu ra file (vd Excel). Bởi vì đặc thù là dữ liệu phân tán, nên export dữ liệu trong hệ thống cũng là một bài toán khá thú vị.

Tuấn có nhận tư vấn giải pháp liên quan đến triển khai, chuyển đổi kiến trúc sang Microservices. Ai cần thì đừng ngần ngại liên hệ nhé.

Categories
DevOps

Triển khai CI/CD với Gitlab 9

Cũng hơn một tháng kể từ bài viết gần nhất, nay mới có thời gian ngồi viết lách tiếp. Dạo gần đây thường release các dự án outsource nên cũng hay làm documentation cũng như  mở các dự án mới nên việc setup CI/CD thường xuyên hơn và chân tay hơn. Thấy các kiến thức này hay nên hôm nay mình sẽ chia sẻ mọi người quy trình CI/CD bên mình áp dụng cho “đại dự án” Teamcrop cũng như các dự án outsourcing mà Moout thực hiện.

CI/CD là gì?

Bạn sẽ thấy có nhiều định nghĩa từ hai lúa cho đến hàn lâm cho khái niệm CI/CD. Mình sẽ dùng cách định nghĩa của mình để mọi người dễ hiểu theo cách thông thường nhất. CI/CD là một bộ đôi công việc, bao gồm CI (Continuous Integration) và CD (Continuous Delivery), ý nói là quá trình tích hợp (integration) thường xuyên, nhanh chóng hơn khi code cũng như thường xuyên cập nhật phiên bản mới (delivery).

Tại sao phải quan tâm đến CI/CD?

Ngày nay, với xu hướng agile/lean dẫn đến việc phát triển tính năng là điều bình thường, quan trọng phải là thần thái, ý lộn, quan trọng là phải nhanh. Nếu một tính năng mà mất 2, 3 tháng mới release thì dẫn đến nhiều hệ lụy như làm không phù hợp nhu cầu khách hàng, hoặc đối thủ đã ra mắt trước đó, mất đi cái lợi thế dẫn đầu. Do đó, việc làm ra một sản phẩm, tính năng đòi hỏi thần tốc là ưu tiên số một hiện nay.

Bên cạnh đó, để nhanh chóng ra mắt một tính năng, phiên bản mới nếu theo cách cổ điển sẽ mất nhiều thời gian bởi công việc chân tay khá nhiều và mỗi lần release cũng huy động một cơ số người không nhỏ để cập nhật một thay đổi dù là nhỏ nhất. Bởi vậy, xu hướng CI/CD giúp cung cấp các framework, workflow giúp tiết kiệm thời gian, nguồn lực của quá trình release (delivery).

Quy trình CI/CD tham khảo

Có rất nhiều quy trình, công cụ khác nhau để ứng dụng CI/CD vào dự án. Nội dung của bài viết này dựa trên kinh nghiệm cho các dự án của mình triển khai cũng như đặc thù là các hệ thống web, và viết bằng PHP (PHP hoặc Python hoặc abc…không quá quan trọng trong ngữ cảnh chia sẻ về CI/CD).

Dưới đây là các bước thông thường của quá trình release tính năng trong một dự án thuộc hệ thống Teamcrop.
Bước 1: [Manual] Khởi tạo repository và có branch default là master và dev. Cài đặt trên Gitlab 9.
Bước 2: [Manual] Trừ owner ra, thì các coder sẽ push code tính năng lên branch dev
Bước 3: [Auto] Hệ thống tự động thực hiện test source code, nếu PASS thì sẽ deploy tự động (rsync) code lên server beta.
Bước 4: [Manual] Tester/QA sẽ vào hệ thống beta để làm UAT (User Acceptance Testing) và confirm là mọi thứ OK.
Bước 5: [Manual] Coder hoặc owner sẽ vào tạo Merge Request, và merge từ branch dev sang branch master.
Bước 6: [Manual] Owner sẽ accept merge request.
Bước 7: [Auto] Hệ thống sẽ tự động thực hiện test source code, nếu PASS sẽ enable tính năng cho phép deploy lên production server.
Bước 8: [Manual] Owner review là merge request OK, test OK. Tiến hành nhấn nút để deploy các thay đổi lên môi trường production.
Bước 9: [Manual] Tester/QA sẽ vào hệ thống production để làm UAT và confirm mọi thứ OK. Nếu không OK, Owner có thể nhấn nút Deploy phiên bản master trước đó để rollback hệ thống về trạng thái stable trước đó.
Bước 10: [Manual] Thắp nhang và hy vọng khách hàng không chửi rủa hoặc email complain.

Áp dụng CI/CD với Gitlab 9

Trong khuôn khổ bài viết này, mình sẽ hướng dẫn mọi người cài đặt Gitlab 9 để quản lý source code, và trên công nghệ Git. Đòi hỏi của tất cả setup này là trên server đã cài Docker, nếu các bạn chưa có docker trên server thì có thể tham khảo các bài viết về docker trên bloghoctap cũng như tìm kiếm thêm trên google.

Tại sao phải là Gitlab 9?

Đây là câu hỏi hay, bởi vì trước đó bên mình sử dụng Gitlab 8, tuy nhiên, do các hạn chế về UI/UX cũng như không tích hợp ngon lành vụ CI/CD nên khi bản 9 ra mắt, mình đã thử sử dụng và thấy bản 9 phù hợp hơn cho workflow nên quyết định dọn dẹp sang Gitlab 9.

Cài đặt Gitlab 9 với docker

Bạn chạy câu lệnh sau để tạo một container chứa Gitlab 9.

docker run --detach \
    --hostname code.teamcrop.com \
    --publish 8080:80 --publish 2222:22 \
    --name gitlab9 \
    --restart=always \
    --volume /gitlab9/config:/etc/gitlab \
    --volume /gitlab9/logs:/var/log/gitlab \
    --volume /gitlab9/data:/var/opt/gitlab \
    gitlab/gitlab-ce:9.0.3-ce.0

Nếu ai từng dùng docker sẽ hiểu ý nghĩa câu lệnh trên. Đơn giản là mình sử dụng image gitlab/gitlab-ce:9.0.3-ce.0. Có mount ra 3 thư mục bên ngoài máy ở thục mục /gitlab9 để lỡ có chuyện gì chỉ cần stop, remove container, khi chạy docker run lại thì không bị mất dữ liệu, source code. Câu lệnh trên có map 2 port là 8080 và 2222 tương ứng tới 2 port 80 và 22 trong container. Mình mapping port vậy bởi vì trên server dev này có rất nhiều service khác và đã chiếm port 80 và 22 (ssh ^^!).

Sau khi bạn start container thì có thể truy cập vào từ ip hoặc domain (mà bạn đã trỏ DNS), ví dụ: http://code.teamcrop.com:8080 là có thể vào gitlab 9, tài khoản mặc định là `root`.

Không có gì cao siêu ở cài đặt này, có thể tham khảo thêm ở trang chủ của Gitlab.com nhé.

Quản lý sourcecode bằng Gitlab

Về phần này thì mình không cần nói dài dòng, cũng như một hệ thống git thông thường (như github..), bạn có thể tìm hiểu thêm về git và Gitlab để team có thể cùng làm việc và quản lý sourcecode trên Gitlab.

CI/CD với Gitlab CI

Thông thường, các hệ thống quản lý sourcecode không kèm theo cơ chế CI/CD. Nếu bạn muốn triển khai thì buộc phải liên kết đến repository, phân quyền đủ kiểu để hệ thống đó có thể lấy source code từ respository. Trước đây bên mình sử dụng Jenkins cho việc này. Tuy nhiên, từ khi Gitlab ra mắt tính năng Gitlab CI, kèm theo sự chậm chạp, rắc rối và rề rề của Jenkins thì mình quyết định chia tay với Jenkins và đến với Gitlab CI luôn, và quả là một bộ đôi hoàn hảo. Code để ở Gitlab, rồi trong đó có cho cài đặt CI/CD để test và deploy code tự động.

Cũng như một số bạn mới lần đầu tiếp xúc với Gitlab CI, mình đã từng thấy nó khó hiểu và cao siêu vì setup tùm lum. Rồi setup xong lại không biết nó chạy thế nào, cơ chế deploy source code ra sao. Tuy nhiên, sau một vài “va chạm” đầy mồ hôi và nước mắt thì cũng nắm và hiểu được cách Gitlab CI vận hành, và nay chia sẻ cho mọi người để vận dụng cho workflow của mình.

Để dùng được Gitlab CI thì bạn cần có 2 thành phần sau: file `.gitlab-ci.yml` nằm ở thư mục gốc của dự án và Gitlab Runner.

File .gitlab-ci.yml là gì?

Mặc định Gitlab không có cơ chế nào về CI cho dự án của bạn, chỉ khi nào dự án của bạn có file .gitlab-ci.yml nằm ở thư mục gốc thì Gitlab mới nhận dạng được dự án của bạn muốn áp dụng Gitlab CI. File này có định dạng và cần hợp lệ thì mới có thể hoạt động được, không thì khi bạn push code lên thì Gitlab sẽ báo lỗi file định dạng nội dung của file cấu hình không hợp lệ. Tham khảo cú pháp của cấu hình này tại https://docs.gitlab.com/ce/ci/yaml/

Trong file này có gì? File này có một số section để khai báo như trước khi chạy test thì làm gì, khi test thì thực hiện lệnh gì (vd chạy linter check cú pháp, chạy PHPUnit test…), test xong rồi thì thực hiện deploy đi đâu (beta, production..) với câu lệnh gì (vd: rsync..). Tùy đặc thù ngôn ngữ lập trình, cách đóng gói của dự án mà sẽ có các lệnh tương ứng thực hiện.

Tới đây các bạn sẽ có câu hỏi là vậy cái gì sẽ chạy, thực thi các câu lệnh, chỉ dẫn trong file config trên? Hay là Gitlab Server sẽ chạy. Nếu là Gitlab server chạy thì nếu dự án mình thực hiện những lệnh không có thì sao, vì gitlab server thì cũng chỉ chứa gitlab và các program cho nó chứ đâu thể cài sẵn các program? Bên cạnh đó, mỗi lần chạy thì các thông tin liên quan đến file tạm có bị reset lại hay không?

Nếu bạn đi đến đây thì bạn đã đoán được là thực ra “cái thứ” thực thi các chỉ dẫn, câu lệnh trong file .gitlab-ci.yml không phải là Gitlab Server (là cái container đang chạy gitlab 9 mình start ở trên), mà đó chính là Gitlab Runner. Wow! Welcome to matrix!

Gitlab Runner là gì?

Gitlab Runner là thành phần cực kỳ quan trọng trong workflow Gitlab CI. Nếu không có Runner thì sẽ không có lệnh test, deploy nào được thực thi. Runner có nhiều loại, phân biệt dựa vào cái gọi là executor. Khi khởi tạo runner, bạn sẽ phải chọn nó là loại executor nào, và nó sẽ quyết định môi trường thực thi các câu lệnh trong file config ở trên. Bạn có thể tham khảo link https://docs.gitlab.com/runner/executors/ để biết sự khác nhau của các executor cũng như cách cài đặt, cấu hình chúng.

Do đặc thù hệ thống đã có docker, nên bên mình chỉ sử dụng executor loại Docker mà thôi. Và bên dưới là câu lệnh docker để start một Gitlab Runner.

docker run -d --name gitlab-runner --restart always \
  -v /srv/gitlab-runner/config:/etc/gitlab-runner \
  -v /var/run/docker.sock:/var/run/docker.sock \
  gitlab/gitlab-runner:latest

Ở đây bạn sẽ thấy container này mount thư mục config ra ngoài, bởi vì mình muốn các cấu hình của runner không bị mất khi stop/remove container. Chỉ cần start lại là giữ được cấu hình. Ngoài ra, nó còn mount docker.sock vào bên trong container, đây là cách để executor loại docker có thể tận dụng lệnh docker bên ngoài host để thực hiện lệnh tạo container phụ trong quá trình runner chạy (test, deploy).

Start container lên chỉ là bước đầu, bởi vì lưu ý là tới thời điểm này, Runner này không có liên quan gì đến Gitlab server của chúng ta. Cần một bước link lại (gọi là register) runner này vào trong Gitlab server để mình có thể cho phép các dự án dùng runner trong quá trình CI/CD.

Xem link này https://docs.gitlab.com/runner/register/index.html để biết cách register runner này vào Gitlab Server.

Dưới đây là hình ảnh tham khảo bạn có thể dùng trong quá trình register 1 runner. Có 2 thông tin quan trọng là 1 cái URL và một random token. Và cái URL đặc biệt lưu ý là thường thêm /ci sau domain. Ví dụ ở trường hợp của mình setup là http://code.teamcrop.com/ci

Sau khi Runner đã được gán vào Gitlab Server, bạn có thể enable runner này cho một hoặc nhiều dự án trong Gitlab. Hình bên dưới minh họa việc gán Runner vào dự án trong phần cài đặt Pipeline của Gitlab 9.

Đến đây hầu như đã cấu hình xong. Dự án đã kích hoạt 1 runner, và dự án đã có file .gitlab-ci.yml. Từ bây giờ, mỗi lần code được đưa lên thì runner sẽ thực thi test cũng như deploy dựa trên các câu lệnh được khai báo trong file cấu hình.

Khai báo biến để dùng trong các câu lệnh

Trong một số trường hợp, bạn có thể khai báo biến để có thể dùng trong các lệnh của runner. Có 3 nơi có thể cấu hình biến:
1. Cấu hình ngay bên trong file .gitlab-ci.yml
2. Cấu hình trong dự án. Vào Settings // CI/CD Pipelines, phần Secret variables (xem hình)

3. Cấu hình bên trong file config của runner. Bạn có nhớ lúc mình khởi tạo runner, có chỉ định một thư mục chứa config không, đây chính là nơi cấu hình chung cho runner này. Trong thư mục này sẽ có file là config.toml. Và bạn có thể gán biến trong cấu hình của từng runner. Cấu hình ở đây có một lợi thế là cứ runner này chạy sẽ nhận được biến đã cấu hình. Bạn không cần phải cấu hình nhiều lần ở từng dự án.

Ví dụ về một file .gitlab-ci.yml

Bên dưới là file cấu hình của một dự án trong hệ thống Microservices thuộc Teamcrop:

before_script:
- export "PATH=$PATH:/vendor/bin"
# Install ssh-agent if not already installed, it is required by Docker.
# (change apt-get to yum if you use a CentOS-based image)
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'

# Run ssh-agent (inside the build environment)
- eval $(ssh-agent -s)

# For Docker builds disable host key checking. Be aware that by adding that
# you are suspectible to man-in-the-middle attacks.
# WARNING: Use this only with the Docker executor, if you use it with shell
# you will overwrite your user's SSH config.
- mkdir -p ~/.ssh
- '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'

variables:
  # Change this base on project name
  DEPLOYMENT_FOLDER_NAME: "tc-file"

test:
  image: voduytuan/gitlab-php-ci
  script:
  - bash ./ci/phplint.sh ./src/
  - phpcs --config-set ignore_errors_on_exit 1
  - phpcs --config-set ignore_warnings_on_exit 1
  - phpcs --standard=PSR2 --ignore=./src/index.php --error-severity=1 --warning-severity=8 -w --colors ./src/
  - phpunit --configuration ci/phpunit.xml

dev:
  image: voduytuan/gitlab-php-ci
  stage: deploy
  script:
  - ssh-add <(echo "$DEPLOYER_BETA_KEY")
  - echo "Deploy to $DEPLOYMENT_FOLDER_NAME"
  - rsync -avuz -e "ssh -p 22" --exclude-from="ci/deploy_exclude.txt" $CI_PROJECT_DIR/src/ $DEPLOYER_BETA_USER@$DEPLOYER_BETA_IP:/teamcrop/services/$DEPLOYMENT_FOLDER_NAME/src
  only:
  - dev

production:
  image: voduytuan/gitlab-php-ci
  stage: deploy
  script:
  - ssh-add <(echo "$DEPLOYER_PRODUCTION_KEY")
  - echo "Deploy to $DEPLOYMENT_FOLDER_NAME"
  - rsync -avuz -e "ssh -p 22" --exclude-from="ci/deploy_exclude.txt" $CI_PROJECT_DIR/src/ $DEPLOYER_PRODUCTION_USER@$DEPLOYER_PRODUCTION_IP:/teamcrop/services/$DEPLOYMENT_FOLDER_NAME/src
  only:
  - master
  when: manual

Trong ví dụ trên, phần test bên mình làm 3 việc:
– Chạy linter để đảm bảo sourcecode không bị lỗi cú pháp (phplint)
– Kiểm tra source code có theo chuẩn PSR2 hay không.
– Chạy PHPUnit

Còn về phần deploy thì có cấu hình 2 task là deploy dev và production. Ở task dev thì auto và lấy code từ branch dev. CÒn task production deploy từ branch master, tuy nhiên, có chế độ deploy manual, tức là nhấn thì mới deploy.

Về phần deploy source code thì sử dụng rsync để đẩy code từ repo sang server. Bạn sẽ thấy cú pháp giống nhau, chỉ khác là cấu hình đẩy đi đâu, với user nào và private key nào.

Do đặc thù của commandline nên sử dụng privatekey để đồng bộ code thông qua rsync. Do đó, trong project mình có cấu hình privatekey của user. Và bên server nhận (beta, production) mình đã đưa public key vào file authorized_keys. Bạn có thể tìm hiểu thêm về setup và generate cặp public/private key cho user deploy để hỗ trợ quá trình này tại link https://www.digitalocean.com/community/tutorials/how-to-set-up-ssh-keys–2. Hay ngắn gọn là thực hiện câu lệnh “ssh-keygen -t rsa -C “[email protected]” -b 4096″, nhập vài thông tin là bạn đã có public key (id_rsa.pub) để đem bỏ lên server (beta, production) và private key (id_rsa) đem bỏ vào setting biến môi trường.

—-
Dựa trên những kinh nghiệm CI/CD cho hệ thống Teamcrop.com theo mô hình microservice với hơn 40 repository lớn nhỏ, hy vọng bài viết này sẽ giúp được cho quá trình setup CI/CD cho hệ thống của bạn, cũng như tăng tốc quá trình phát triển dự án. Nếu thấy bài viết hay và hữu ích, hãy chia sẻ cho các anh em khác để cùng trao đổi và giao lưu.

Categories
DevOps

Speed up Microservices 1: Tác dụng phụ và một số chiến lược cơ bản


Chào mọi người, nếu như các bạn cũng biết thì dự án Teamcrop của mình xây dựng và chạy hoàn toàn trên kiến trúc Microservices và sau hơn 2 năm triển khai thì có một số vấn đề liên quan đến kiến trúc này, thiết nghĩ cần chia sẻ thêm với mọi người để mọi người thấy được rằng Microservices không phải là chìa khóa vạn năng như vẫn hay nghe quảng cáo, dụ dỗ. Mọi kiến trúc đều có đánh đổi và Microservices cũng vậy.