Chiến lược CI/CD cho phần mềm tự lưu trữ: Quản lý cập nhật khi bạn không nắm giữ máy chủ
Việc phát hành NavEngine v4 gặp thách thức lớn khi không có quyền truy cập vào hạ tầng của khách hàng. Bài viết này chia sẻ cách xây dựng pipeline CI/CD dựa trên cơ chế "Pull" với Watchtower và floating tags để liên tục phân phối bản cập nhật cho phần mềm tự lưu trữ một cách an toàn và hiệu quả.

Chúng tôi đang cách thời điểm phát hành NavEngine v4 đúng ba tuần, một sự tiếp nối cho bài viết trước về Phát triển phần mềm dựa trên nghiệp vụ.
Tôi dùng từ "phát hành" (shipping) một cách thoáng. Ở đây không có script triển khai nào để chạy, không có phiên SSH nào cần mở và không có quá trình triển khai Kubernetes nào để theo dõi. Phần mềm sống trên cơ sở hạ tầng mà tôi chưa từng nhìn thấy, nằm sau các tường lửa không thể tiếp cận, trên những chiếc máy có cấu hình mà tôi không hề biết. Trong bối cảnh này, "phát hành" có nghĩa là đẩy một image (định dạng) vào registry và tin tưởng rằng một quy trình đang chạy trong container trên máy chủ của khách hàng sẽ sớm nhận ra và thực hiện việc cập nhật.
Khoảng cách đó — giữa những gì tôi đẩy lên và những gì khách hàng đang chạy — chính là chủ đề của bài viết này.
Sự sụp đổ của các giả định
Mọi công cụ CI/CD mà tôi từng sử dụng đều được xây dựng trên một tiền đề nền tảng đến mức không ai nghĩ cần phải nêu ra: bạn kiểm soát mục tiêu triển khai. Bạn sở hữu máy chủ. Bạn nắm giữ các khóa. Triển khai, theo nghĩa thông thường, chỉ là tự động hóa bao quanh quyền truy cập mà bạn đã có.
NavEngine thì hoàn toàn ngược lại. Nó tồn tại dưới dạng một image tùy chỉnh — qcow2 — được chuyển đến cơ sở hạ tầng của khách hàng. Khách hàng là người sở hữu máy chủ. Tôi không có quyền truy cập SSH trừ khi đi qua DWService, và ngay cả khi đó, đó cũng là kênh hỗ trợ chứ không phải kênh triển khai. Tuy nhiên, bằng cách nào đó, chúng tôi cần liên tục phân phối các bản cập nhật lên những máy không thể tiếp cận, qua các kết nối không thể đảm bảo, mà không làm hỏng phần mềm đang hoạt động.
Vậy câu hỏi trở thành: nếu bạn không thể đẩy (push), làm thế nào để bạn phân phối?
Chiêm nghiệm
Khoảnh khắc bạn quyết định phân phối phần mềm mà bạn không lưu trữ, bạn đã đưa ra một quyết định có những hệ quả sẽ theo đuổi bạn trong suốt vòng đời của sản phẩm. Không chỉ về mặt vận hành. Mà còn về mặt kiến trúc. Mọi giả định mà codebase của bạn đưa ra về môi trường nó chạy hiện nay đều thuộc về cơ sở hạ tầng của người khác. Đó không phải là vấn đề triển khai. Đó là một vấn đề thiết kế xuất hiện tại thời điểm triển khai.
Câu trả lời là "Pull"
Watchtower. Nó chạy dưới dạng container song song với phần còn lại của ngăn xếp, thăm dò Docker registry theo khoảng thời gian cấu hình, và khi phát hiện ra digest image mới trên thẻ (tag) mà nó đang theo dõi, nó sẽ kéo và khởi động lại các container liên quan. Không webhook, không push, không SSH. Bản cài đặt sẽ "gọi về nhà" để cập nhật và nhận lấy những gì nó tìm thấy.
Quyết định thiết kế then chốt ở đây là floating tag (tag động). Cấu hình Butane của mỗi khách hàng được shipped với core:stable. Không phải core:v3.0.39. Không phải là một digest cố định. Là stable. Khi Watchtower thăm dò registry và thấy rằng stable giờ đây phân giải thành một digest khác với những gì đang chạy, nó sẽ kéo. Việc stable trỏ đến đâu hoàn toàn nằm dưới sự kiểm soát của tôi, từ phía registry, mà không cần chạm vào bất cứ thứ gì trên máy của khách hàng.
Nghe có vẻ hiển nhiên một khi bạn nói ra. Nhưng đã mất một thời gian dài hơn mức tôi muốn thừa nhận để đến được đó.
Hai Registry, Hai tag động, Một cổng kiểm soát
Dưới đây là pipeline đầy đủ thực sự chạy.
Mỗi lần đẩy (push) tới nhánh main sẽ kích hoạt một bản build trên dev registry. Image được gán thẻ định danh phiên bản và một thẻ động — staging. Một môi trường staging — chạy cùng ngăn xếp Compose, cùng cấu hình Butane, cùng cấu trúc với bản cài đặt của khách hàng — sẽ kéo từ staging. Đây là nơi image sống cho đến khi tôi hài lòng rằng nó hoạt động tốt.
Khi staging trông ổn thỏa, tôi tạo một bản phát hành (release). Production registry sẽ build từ release đó, gán thẻ cho image với phiên bản (ví dụ: v3.0.40) và ghi đè thẻ động stable. Các bản cài đặt của khách hàng, vào lần thăm dò Watchtower tiếp theo, sẽ thấy một digest mới trên stable và cập nhật.
Chi tiết quan trọng: stable không bao giờ bị ghi đè bởi một lần push tới main. Chỉ bởi một release. Cổng kiểm soát staging là thứ duy nhất đứng giữa một image bị hỏng và bản cài đặt đang chạy của khách hàng. Không có tỷ lệ triển khai tự động, không có đội tàu canary, không có chuyển đổi traffic dần dần. Cổng kiểm soát là một quyết định của con người, được đưa ra sau khi xem staging chạy và quyết định rằng nó đã sẵn sàng.
Đối với một sản phẩm vận hành một mình ở giai đoạn này, đó là một quyết định đúng đắn. Sự phức tạp trong hạ tầng phát hành mà bạn không cần thực chất chỉ là diện tích bề mặt để mọi thứ có thể sai sót.
Chiêm nghiệm
Một môi trường staging không phản ánh chính xác production là một giả dược rất đắt đỏ. Nó mang lại sự tự tin nhưng không cung cấp thông tin. Điều khó khăn nhất khi shipping phần mềm tự lưu trữ là môi trường staging của bạn chạy trên hạ tầng bạn hiểu, với dữ liệu bạn tạo ra, trên mạng bạn kiểm soát. Môi trường của khách hàng không có bất cứ điều nào trong số đó. Không pipeline nào có thể đóng lại hoàn toàn khoảng cách đó. Điều tốt nhất bạn có thể làm là biết chính xác nơi niềm tin của bạn kết thúc.
Sơ đồ Pipeline CI/CD
Xảy ra gì khi "stable" bị hỏng?
Nó sẽ xảy ra. Một image vượt qua staging sẽ bị hỏng trong môi trường của khách hàng vì một lý do mà staging không làm nổi bật — một di chuyển schema giả định một cơ sở dữ liệu sạch, một dependency hoạt động khác nhau trên phần cứng cũ hơn, một giá trị cấu hình có mặt trong staging nhưng lại thiếu trong thực tế.
Quy trình phục hồi là: sửa trên main, xem nó vượt qua staging, tạo một release mới. v3.0.41 ghi đè stable. Watchtower sẽ nhận diện nó vào lần thăm dò tiếp theo. Khách hàng, có thể đã nhận thấy hoặc không, hiện đang chạy image đã được sửa.
Khoảng thời gian giữa việc image bị hỏng tiếp đất và bản sửa lỗi đến là có thật. Tùy thuộc vào tốc độ hotfix di chuyển qua staging và khoảng thời gian thăm dò của Watchtower, một khách hàng có thể chạy phần mềm bị hỏng từ vài phút đến vài giờ. Không có công tắc tiêu diệt từ xa (remote kill switch). Không có cách nào để truy cập vào và khởi động lại dịch vụ. Chỉ có DWService nếu tình huống đủ tệ để đáng làm như vậy, nhưng đó là sự leo thang hỗ trợ, không phải công cụ triển khai.
Đây là cái giá thực sự khi không kiểm soát mục tiêu triển khai. Bạn chấp nhận độ trễ phục hồi mà bạn không thể nén dưới một mức sàn nhất định. Biện pháp giảm thiểu không phải là một pipeline thông minh hơn. Đó là đầu tư sâu vào độ trung thực của staging và đảm bảo rằng image bị lỗi một cách rõ ràng (fails loudly) thay vì âm thầm — các kiểm tra sức khỏe (health checks) làm nổi bật vấn đề ngay lập tức, xác thực khởi động từ chối chạy trên cấu hình sai thay vì chạy sai cách.
Một hệ thống bị lỗi rõ ràng là một hệ thống có thể sửa chữa. Một hệ thống suy giảm âm thầm là một hệ thống làm xói mòn niềm tin trước khi ai đó biết có vấn đề.
Doanh nghiệp và Tiêu chuẩn: Nhịp độ khác nhau, cùng mô hình
Chúng tôi quản lý khách hàng như thế nào khi họ tách ra khỏi dòng sản phẩm chính với giấy phép doanh nghiệp (Enterprise)?
NavEngine có hai cấp giấy phép. Khách hàng Enterprise có lịch phát hành riêng biệt với khách hàng tiêu chuẩn. Cơ chế rất đơn giản: các thẻ động riêng biệt trên production registry. core-enterprise:stable và core-standard:stable. Cấu hình Butane được gửi đến mỗi khách hàng sẽ trỏ đến thẻ tương ứng. Các bản phát hành Enterprise có thể đi ra theo lịch trình khác nhau, mang các bộ tính năng khác nhau và di chuyển thận trọng hơn so với bản phát hành tiêu chuẩn.
Điều gì ngăn khách hàng tiêu chuẩn hướng Watchtower đến thẻ enterprise? Chủ yếu là ma sát. Tệp Compose được nướng (baked) vào cấu hình Butane tại thời điểm cấp phát. Không có quyền truy cập SSH để thay đổi nó. Một khách hàng sẽ cần quyền truy cập console và động lực để đi tìm — điều khó xảy ra với đa số, và không thể loại trừ với tất cả.
Câu trả lời đúng đắn là kiểm soát truy cập ở cấp registry: các pull token được giới hạn trong các thẻ mà mỗi khách hàng có quyền sử dụng, được cấp tại thời gian kích hoạt giấy phép và thu hồi khi hết hạn. Điều này có nghĩa là registry thực thi quyền lợi, không chỉ là ứng dụng. Giấy phép hết hạn nghĩa là pull token hết hạn nghĩa là không có bản cập nhật, được thực thi tại điểm giao hàng thay vì sau sự việc.
Điều này đang trong lộ trình. Đối với v4, câu trả lời là ma sát và niềm tin.
Chiêm nghiệm
Việc thực thi giấy phép trong phần mềm tự lưu trữ là một cuộc đàm phán giữa những gì bạn có thể kiểm soát về mặt kỹ thuật và những gì bạn phải tin tưởng. Bạn không thể kiểm soát hoàn toàn những gì chạy trên một máy mà bạn không sở hữu. Tại một điểm nào đó, một khách hàng đủ động lực có thể lướt qua hầu hết mọi cơ chế thực thi mà bạn xây dựng. Mục tiêu không phải là làm cho việc lướt qua trở nên bất khả thi. Mà là làm cho việc tuân thủ dễ dàng hơn việc lướt qua, và làm cho giá trị cốt lõi đủ mạnh để câu hỏi hiếm khi được đưa ra.
Server bản quyền không nằm trên đường cập nhật
Một quyết định mà tôi rất vui vì chúng tôi đã đưa ra sớm: server cấp phép và Docker registry là cơ sở hạ tầng riêng biệt. Chúng không chia sẻ miền lỗi (failure domain).
Watchtower thăm dò registry. Server cấp phép được gọi từ bên trong một trong các container đang chạy như một phần của hoạt động ứng dụng bình thường. Nếu registry không thể truy cập được, phần mềm tiếp tục chạy. Nếu server cấp phép không thể truy cập được, backend sẽ quay lại trạng thái đã biết cuối cùng — được lưu trữ trên đĩa, không giữ trong bộ nhớ, vì vậy nó sống sót qua việc khởi động lại container. Việc kiểm tra chạy định kỳ. Cửa sổ nặc danh (grace window) đủ hào phóng để việc mất điện của server cấp phép không ảnh hưởng ngay lập tức đến khách hàng, nhưng không quá hào phóng để giấy phép hết hạn có thể chạy mãi mãi.
Điều này quan trọng vì các chế độ lỗi cộng hưởng. Một bản cập nhật sản phẩm yêu cầu kiểm tra giấy phép để tiếp tục vừa biến server cấp phép thành một phần phụ thuộc của pipeline triển khai của bạn. Bất kỳ sự cố nào đánh vào hạ tầng cấp phép của bạn cũng sẽ tấn công khả năng gửi bản cập nhật cho khách hàng trả tiền. Việc giữ các đường dẫn này riêng biệt có nghĩa là chúng thất bại độc lập, và các thất bại độc lập có thể phục hồi theo những cách mà các thất bại dây chuyền không thể.
Pipeline thực sự trông như thế nào
Các thẻ phiên bản bất biến (immutable version tags) không chỉ để kiểm toán. Chúng là tham chiếu để rollback. Nếu v3.0.40 làm hỏng mọi thứ, v3.0.39 vẫn tồn tại trong registry. Tôi có thể gắn thẻ lại nó thành stable theo cách thủ công và khách hàng sẽ rollback vào lần thăm dò tiếp theo. Việc này chưa từng cần thiết cho đến nay. Nó ở đó cho ngày mà nó sẽ cần đến.
Chiêm nghiệm
Đa số các bài viết về CI/CD coi việc triển khai là kết thúc của câu chuyện. Ship nó, xem các chỉ số, và chuyển sang việc khác. Phần mềm tự lưu trữ đảo ngược điều này. Triển khai là sự bắt đầu của một giai đoạn mà phần mềm bạn không thể tiếp cận đang chạy trong một môi trường bạn không thể nhìn thấy, thay mặt cho một khách hàng mà bạn chỉ sẽ nghe về trải nghiệm của họ nếu có gì đó sai sót. Pipeline không phải là cơ chế giao hàng. Nó là cơ chế niềm tin. Mọi quyết định trong nó là một quyết định về mức độ bạn tin tưởng image trước khi nó rời tay bạn.
Còn ba tuần nữa
NavEngine v4 còn ba tuần nữa. Pipeline đang chạy. Staging đã giữ vững. Các thẻ đã ở vị trí.
Không điều nào trong số đó trả lời câu hỏi duy nhất quan trọng: điều gì xảy ra khi phần mềm rời xa bạn?
Nó sẽ chạy trên những máy bạn chưa từng chạm vào, chống lại dữ liệu bạn chưa từng thấy, trong những môi trường không quan tâm đến các giả định của bạn. vào thời điểm nó bị lỗi, nếu nó bị lỗi, nó đã trở thành vấn đề của người khác — và vẫn hoàn toàn là của bạn. Khách hàng nhận ra trước bạn. Đó là vấn đề. Tôi sợ hãi tột độ.
Tôi cho rằng đây là bản chất của những ghi chú này — để tài liệu hóa các hệ thống thực theo thời gian thực. Đây là sự đảo ngược mà các hệ thống tự lưu trữ buộc bạn phải chịu: coi việc triển khai không phải là kết thúc của sự kiểm soát mà là sự bắt đầu của sự vắng mặt nó.
Vì vậy bạn thiết kế cho sự vắng mặt đó.
Bạn thiết kế cho sự phục hồi thay vì phòng ngừa.
Cho tính minh bạch thay vì sự chắc chắn và niềm tin thay vì sự kiểm soát.
Mọi thứ khác chỉ là những gì cần thiết để làm cho điều đó khả thi.
Bài viết liên quan

Phần mềm
Anthropic ra mắt Claude Opus 4.7: Nâng cấp mạnh mẽ cho lập trình nhưng vẫn thua Mythos Preview
16 tháng 4, 2026

Công nghệ
Qwen3.6-35B-A3B: Quyền năng Lập trình Agentic, Nay Đã Mở Cửa Cho Tất Cả
16 tháng 4, 2026

Công nghệ
Spotify thắng kiện 322 triệu USD từ nhóm pirate Anna's Archive nhưng đối mặt với bài toán thu hồi
16 tháng 4, 2026
