Categories
Technology

Xây dựng hệ thống Log cho Microservices

Hôm qua mình có làm speaker chia sẻ về kiến trúc hệ thống Log của Teamcrop.com, thấy nhiều bạn hưởng ứng và quan tâm quá nên mình khai trương blog năm mới bằng một bài viết ngắn nói về chủ đề này.

Xây dựng hệ thống log được đánh giá rất quan trọng, đặc biệt đối với các hệ thống đang trong giai đoạn tăng trưởng người dùng và mở rộng tính năng. Có nhiều hướng tiếp cận khác nhau để triển khai hệ thống log, từ việc sử dụng các dịch vụ Saas có sẵn, không cần cài đặt hay server gì như các hệ thống Loggly, DataDog, Papertrail…cho tới các open source như Graylog, ELK (ElasticSearch, Logstash, Kibana) Stack, Grafana…Tuy nhiên, do đặc thù của dữ liệu, số lượng log và tốc độ truy xuất mà Teamcrop buộc phải thiết kế một hệ thống log riêng để giải quyết các vấn đề về chi phí lưu trữ và vận hành.

Để xây dựng hoặc tìm hiểu một hệ thống Log, chúng ta cần thấy được các thành phần của hệ thống Logging bởi performance và tính tiện dụng đều dựa vào mô hình hệ thống Log. Mình tạm chia một hệ thống Log thành ba thành phần cơ bản là Collector, Storage và Dashboard.

  • Collector là phân hệ chịu trách nhiệm thu thập dữ liệu log từ các nguồn cung cấp (syslog, access log, error log, api request, sql query..)
  • Storage là thành phần sẽ lưu trữ dữ liệu log, có thể là tạm thời hoặc vĩnh viễn, ví dụ như RabbitMQ (tạm thời), File System (tạm thời), MySQL, Clickhouse…
  • Dashboard là thành phần sẽ truy xuất vào storage và hiển thị dữ liệu bao gồm data table, biểu đồ, dashboard…đến đối tượng cần coi log.

Để dễ dàng tiếp cận với các vấn đề của Log, trước tiên chúng ta cần phân loại log vì mỗi loại log sẽ có đặc tính khác nhau về số lượng cũng như cách truy xuất, sử dụng. Mình tạm phân loại Log thành 2 loại: Business và Operation Log.

Business Log

Business Log là những loại log giúp hỗ trợ các hoạt động và quyết định của doanh nghiệp. Như các Usage Log liên quan đến sử dụng các tính năng trên giao diện để bộ phận marketing, product dễ dàng ra quyết định liên quan đến kinh doanh và phát triển sản phẩm. Đối với loại log này thì hiện tại có ông trùm và được sử dụng khá nhiều là Google Analytics. Nếu cần open source thì có thể sử dụng Matomo (tiền thân là Piwik) để cài đặt trên server của bạn, tính năng của Matomo cũng gần như Google Analtyics.

Một loại Business Log khác mà mình đề cập trong phần chia sẻ là Critical Function Log dành cho những nghiệp vụ quan trọng, giúp đảm bảo an toàn cho hệ thống và khôi phục được trong trường hợp sự cố xảy ra và có thể tạo được dữ liệu. Log này quan trọng vì trong một kiến trúc Microservices thì có thể do cấu hình, cài đặt hoặc lỗi hệ thống khiến cho 1 service có thể bị cô lập và không gọi được các service khác trong quá trình thực thi. Ví dụ như tạo đơn hàng (service Đơn hàng) sẽ gọi các service như Sản phẩm, Tồn kho, Khách hàng, Thu chi…để hỗ trợ quá trình tạo đơn hàng. Nếu trong quá trình tạo đơn hàng mà toàn bộ các service khác không thể truy cập thì service Đơn hàng sẽ có một cơ chế an toàn riêng để đảm bảo dữ liệu request lên (raw POST data) được backup tạm thời phòng trường hợp cực hạn này. Số lượng tính năng áp dụng cơ chế log này là không lớn.

Loại log cuối cùng trong nhóm Business Log mà mình muốn đề cập là Auditing Log. Loại log này khá là quan trọng và nếu bạn đang phát triển các hệ thống Enterprise không thể bỏ qua. Auditing Log giúp doanh nghiệp có thể dễ dàng phát hiện các hoạt động bất thường, quan trọng (như xóa, import, export dữ liệu…) để kịp thời có hướng xử lý. Auditing Log còn có thể thiết kế như một hệ thống Change Log để lưu lại lịch sử thay đổi dữ liệu của 1 record (như đơn hàng, khách hàng…). Nếu có tìm hiểu về Microservices thì các bạn sẽ thấy cách thức lưu change log này của Auditing Log khá giống với pattern Event Sourcing (ES) trong mô hình CQRS/ES.

Trong hầu hết trường hợp thì chúng ta ít khi tập trung vào tự xây dựng business log bởi đối với loại này phải được phân tích kỹ và chỉ định cụ thể dịch vụ nào, tính năng nào cần được log (Auditting, Critical, Usage) để tiến hành lập trình nâng cấp.

Operation Log

Nhóm log thứ hai được đánh giá là có tác động to lớn đến quá trình tối ưu, cải tiến hệ thống là Operation Log. Một số loại log thuộc nhóm này bao gồm: Operating System, General Log, API Request, SQL Query và Distributed Tracing.

Operating System Log là nhóm log cơ bản giúp theo dõi và đánh giá tình hình chung của hệ thống như ổ cứng, memory, cpu, network IO…đối với nhóm log này thường đi kèm với hệ thống monitoring và bên Teamcrop sử dụng NodeQuery.com để theo dõi vì hệ thống NodeQuery khá tốt và đơn giản, kèm với giao diện dashboard rất dễ dùng và cơ chế alert hiệu quả. NodeQuery cũng được rất nhiều công ty trên thế giới sử dụng.

Loại log tiếp theo trong nhóm Operation Log là General Log, bao gồm những Log liên quan đến hoạt động của Nginx, PHP…và những custom log message do bạn ghi xuống trong quá trình vận hành hoặc debug hệ thống. Cùng với API Request Log, SQL Query Log thì mình tự xây dựng cơ chế log riêng bởi số lượng và tần suất log rất cao, việc sử dụng các hệ thống khác hoàn toàn không hiệu quả về hiệu suất và kinh tế.

API Request Log là loại log để theo dõi toàn bộ các request vào hệ thống Microservices. Giúp bộ phận lập trình biết được performance của các request như thời gian thực thi một tính năng (execution time) cũng như memory cấp phát cho các tính năng.

SQL Query Log cũng là loại log có mục đích khá giống với API Request Log, giúp lập trình viên và DB Admin có thể theo dõi được tình hình truy vấn database, câu truy vấn nào chưa tối ưu, table nào được truy cập nhiều, tình trạng tỷ lệ master/slave trong mô hình replicate có duy trì ở mức ổn định hay không…

Cuối cùng là một loại log cực kỳ cần thiết trong quá trình vận hành một hệ thống Microservices, đó là Distributed Tracing. Hầu hết các bạn khi chuyển sang kiến trúc Microservices, sẽ hay bị trình trạng service gọi lồng vào nhau, A –> B —> C —> D thì khi có một lỗi hoặc bottleneck nào đó ở một service bên trong (vd service C) thì rất khó phát hiện và debug bởi đặc thù phân tán của kiến trúc Microservices. Để giải quyết vấn đề này thì kỹ thuật Distributed Tracing sẽ giúp ích rất nhiều. Teamcrop sử dụng Zipkin Library PHP dùng để format dữ liệu theo đặc tả OpenTracing và backend sử dụng Jaeger (dự án Open source của Uber).

Đó là toàn bộ những chia sẻ của Tuấn tại buổi meetup đầu năm 2020 ngày 09/01/2020. Bên dưới là phần Slide trình bày (có cải tiến cho dễ hiểu hơn).


Dưới đây là schema của 3 table tương tứng với 3 loại log (General Log, API Request Log và SQL Query Log) được tạo trong database ClickHouse:

CREATE TABLE log_request (
  lr_date Date,
  lr_datetime DateTime,
  lr_hour UInt8,
  lr_minute UInt8,
  lr_service String,
  lr_method String,
  lr_controller String,
  lr_action String,
  lr_companyid UInt32,
  lr_userid UInt32,
  lr_status UInt16,
  lr_exectime Float32,
  lr_memory Float32,
  lr_ip String,
  lr_depth UInt8,
  lr_source String,
  lr_traceid String,
  lr_tracespanid String
) ENGINE = MergeTree(lr_date, (lr_datetime, lr_hour, lr_minute, lr_service, lr_method, lr_controller, lr_action, lr_companyid, lr_userid, lr_status, lr_exectime, lr_memory, lr_ip, lr_depth, lr_source, lr_traceid, lr_tracespanid), 8192);;


CREATE TABLE log_sql (
  ls_date Date,
  ls_datetime DateTime,
  ls_hour UInt8,
  ls_minute UInt8,
  ls_hosttype String,
  ls_host String,
  ls_querytype String,
  ls_table String,
  ls_exectime Float32,
  ls_companyid UInt32,
  ls_userid UInt32,
  ls_traceid String,
  ls_tracespanid String
) ENGINE = MergeTree(ls_date, (ls_datetime, ls_hour, ls_minute, ls_hosttype, ls_host, ls_querytype, ls_table, ls_exectime, ls_companyid, ls_userid, ls_traceid, ls_tracespanid), 8192);


CREATE TABLE log_syslog (
  ls_date Date,
  ls_datetime DateTime,
  ls_hour UInt8,
  ls_minute UInt8,
  ls_message String,
  ls_tag String,
  ls_host String,
  ls_source String
) ENGINE = MergeTree(ls_date, (ls_datetime, ls_hour, ls_minute, ls_message, ls_tag, ls_host, ls_source), 8192);

P.S: Teamcrop.com là hệ thống phần mềm quản lý bán hàng và nhân sự, nếu bạn nào quan tâm thì có thể dùng thử và ủng hộ một startup hoàn toàn Việt Nam nhé. Nếu bạn nào hứng thú với PHP hoặc kiến trúc Microservices, thì có thể cùng tham gia xây dựng Teamcrop với Tuấn, mọi thông tin gửi vào email trong trang cuối của Slide ở trên nhé.

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.

Categories
DevOps Technology

Git submodule cho người bận rộn

Số là hôm nay vừa chuyển 2 repo framework và restful sdk của Teamcrop từ composer về submodule nên chia sẻ nhanh 1 số bước để sử dụng submodule cho repo của bạn. Một phần để lưu lại sau này dùng vì kiến thức rồi sẽ ra đi, chỉ có blog là ở lại và cũng chia sẻ cho bạn nào chưa biết.

Nói một cách ngắn gọn thì submodule giúp bạn mang 1 repo khác bỏ vào repo đang làm việc, giúp việc tái sử dụng code hiệu quả hơn. Ví dụ như đối với dự án Teamcrop là muốn nhúng core framework và restful sdk dùng cho tất cả microservices. Còn vì sao mình dùng submodule mà không dùng composer hoặc các hình thức khác thì mình thấy submodule khá phù hợp với mô hình private repository.

Categories
Tech Startup

7 tháng, chặng đầu gian khổ đã qua

chang-duong-dau-gian-kho

Khi viết những dòng này thì đồng hồ vừa chuyển sang ngày 1/8/2014. Đối với mọi người thì đây cũng chỉ là một mốc bình thường nhưng hôm nay mình tự cho là một ngày đặc biệt và nó đánh dấu chặng đường gian khổ đầu tiên của Spiral. Ngày 31/7 cũng chính là ngày đầu tiên Tuấn mở lời nhờ một số anh em ở lại Overtime vài tiếng để kịp hoàn thành những dự án dây dưa, dang dở.