0

[Microservices Series] Bài 12: Deploy Microservices – Từ "Vầng trăng khóc" đến Zero Downtime với Docker & CI/CD

Chào anh em! Ở bài 11, chúng ta đã bàn về cách giăng bẫy bảo mật (Zero Trust) để các service không bị "đâm sau lưng". Nhưng khi bạn đã code xong 20 cái service, bảo mật tận răng, test mượt mà trên localhost, thì một bài toán "đẫm nước mắt" khác hiện ra: Làm sao để vác khối tài sản khổng lồ này lên Server?

Nếu anh em vẫn giữ thói quen của thời Monolith — SSH vào VPS, gõ git pull, chạy composer install, rồi systemctl restart nginx — thì với Microservices, anh em sẽ phải lặp lại thao tác đó 20 lần cho mỗi đợt release. Quên restart một con Supervisor chạy queue thôi là hệ thống lăn ra chết.

Hôm nay, chúng ta sẽ thiết lập một dây chuyền tự động (CI/CD) và kỹ thuật thay code mà không làm rớt mạng bất kỳ khách hàng nào (Zero Downtime).

1. Docker – Đóng gói mọi thứ vào "Thùng container"

Trong Microservices, sự đa dạng công nghệ (Polyglot) là một sức mạnh, nhưng lại là ác mộng của Ops. Service bằng Laravel thì cần PHP-FPM, Nginx, Supervisor để chạy Job. Service bằng Go hay C++ thì lại cần build ra file binary thực thi. Service bằng Node.js lại xài PM2.

Để tự động hóa được, chúng ta bắt buộc phải "chuẩn hóa" đầu ra bằng Docker.

Dù bên trong anh em viết bằng ngôn ngữ quái thai gì đi nữa, thì đầu ra cuối cùng phải là một Docker Image.

  • Thằng Ops không cần biết server có cài sẵn PHP 8 hay Go 1.21 không.

  • Nó chỉ việc gõ docker run... là app tự động chạy lên, môi trường bên trong container đã được đóng gói y hệt như lúc dev code trên máy tính. Tạm biệt vĩnh viễn câu nói kinh điển: "Ơ kìa, trên máy em chạy bình thường mà!".

2. CI/CD Pipeline – Dây chuyền sản xuất tự động

Khi đã có Docker, anh em không bao giờ được phép tự tay build image trên máy cá nhân rồi đẩy lên server. Mọi thứ phải được giao cho CI/CD (Continuous Integration / Continuous Deployment) như GitLab CI, GitHub Actions hoặc Jenkins.

Một luồng tự động chuẩn sẽ chạy qua các bước:

  1. Commit Code: Dev push code lên nhánh main.

  2. Build & Test (CI): Máy chủ tự động tải code về, chạy Unit Test, quét lỗi bảo mật.

  3. Bake Image: Tự động build ra một Docker Image mới, đánh tag theo phiên bản (VD: billing-service:v1.2.0).

  4. Push: Đẩy Image này lên một kho chứa (Docker Registry).

  5. Deploy (CD): Ra lệnh cho Server (thông qua Docker Swarm, Kubernetes hoặc các tool như Railway) tải Image mới về và chạy.

Quá trình này diễn ra hoàn toàn tự động trong vài phút. Anh em Dev chỉ việc merge code, sau đó ra pha ly cà phê, quay lại là code đã lên Production.

3. Zero Downtime – Thay lốp khi xe đang chạy trên cao tốc

Tự động hóa xong rồi, nhưng có một vấn đề cốt tử: Khi tắt container cũ đi để bật container mới lên, hệ thống sẽ bị "chết lâm sàng" mất vài giây (hoặc vài chục giây với các service nặng).

Với hệ thống điều hành Metro, vài giây downtime giữa giờ cao điểm đồng nghĩa với hàng trăm hành khách quẹt thẻ bị báo lỗi, cửa không mở, đám đông ùn ứ. Chúng ta phải dùng kỹ thuật Rolling Update hoặc Blue/Green Deployment.

Rolling Update hoạt động như thế nào? Giả sử bạn đang chạy 4 container của Gate Service (để xử lý mở cổng vé).

  • Hệ thống sẽ không tắt cả 4 cái cũ đi.

  • Đầu tiên, nó khởi động 1 container phiên bản MỚI.

  • Nó chờ cho container MỚI này boot xong hoàn toàn (thông qua cơ chế Health Check).

  • Khi container MỚI báo "Tôi đã sẵn sàng nhận traffic", hệ thống mới từ từ điều hướng luồng mạng sang nó, và tiến hành "khai tử" 1 container CŨ.

  • Cứ cuốn chiếu như vậy cho đến khi toàn bộ 4 container được thay máu.

Bằng cách này, vào bất kỳ thời điểm nào, luôn có các container đang sống để phục vụ hành khách. Việc cập nhật phần mềm diễn ra hoàn toàn vô hình với người dùng.

Thực chiến: Kinh nghiệm xử lý Database khi Deploy

Code thay đổi mượt mà không rớt mạng, nhưng Database thì không "thần kỳ" như vậy. Lỗi tồi tệ nhất anh em thường gặp là: Code mới thì yêu cầu một cột mới trong Database, nhưng lệnh Migrate DB lại chạy sau khi code đã deploy lên. Hoặc ngược lại, DB migrate xong rồi, code cũ đang chạy chưa kịp tắt gọi vào bị lỗi.

Kinh nghiệm "xương máu":

  1. Tách rời việc Migrate DB và Deploy Code: Luôn luôn chạy script migrate database trước khi deploy code mới.

  2. Không bao giờ sửa/xóa cột đang dùng: Nếu muốn đổi tên cột status thành payment_status, đừng chạy lệnh RENAME COLUMN. Hãy tạo cột payment_status mới, cho code mới ghi dữ liệu vào cả 2 cột. Đợi vài ngày hệ thống ổn định, release một bản cập nhật khác để xóa cột status cũ đi. Kỹ thuật này gọi là Backward Compatible Database Change.

Tạm kết

Làm chủ được Docker và CI/CD, anh em sẽ có những đêm ngủ ngon giấc, không còn cảnh thức đến 2h sáng thứ Bảy để copy từng file lên server và run tay từng dòng lệnh nữa.

Nhưng chờ chút! Khi anh em có 20 cái Microservices, đồng nghĩa với việc anh em có 20 cái file .env. Lỡ công ty đổi mật khẩu Database, chẳng lẽ anh em phải chui vào 20 cái kho lưu trữ code để sửa lại .env rồi deploy lại toàn bộ hệ thống?

Ở bài tiếp theo (Bài 13), chúng ta sẽ giải quyết bài toán đau đầu này với Centralized Configuration – Nghệ thuật quản lý cấu hình tập trung (Khi Consul/Vault vào việc).

Team anh em hiện tại đang deploy bằng cách nào? Kéo thả file qua FTP, chạy script tay, hay đã có dàn CI/CD xịn sò? Có từng làm sập hệ thống vì quên chạy migrate chưa? Chia sẻ dưới comment nhé!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí