# Máy chủ của khách không có internet thì pipeline phải kết thúc bằng một file tar

> docker save, docker load, một registry nội bộ và một người ký duyệt là bốn thứ đưa bản build của bạn vào mạng cách ly của khách.

Bản gốc: https://fdetimes.net/vi/bach-khoa/thuc-hanh-ci-cd-trien-khai-vao-moi-truong-khach/

Pipeline của bạn chạy xanh, image đã nằm trên registry, và kỹ sư vận hành phía khách nhắn lại một câu: máy chủ bên này không ra được internet. Toàn bộ chuỗi `build → push → pull` mà bạn quen dùng gãy đúng ở mắt xích cuối.

Thử hình dung bạn đang triển khai cho một ngân hàng hay một nhà máy có mạng nội bộ cách ly. Tài liệu offline của GitLab nói rõ hướng dẫn của họ dành cho những mạng bị ngắt kết nối về mặt vật lý. Ở những nơi như vậy, không có proxy hay ngoại lệ tường lửa nào để xin.

Bài này hướng dẫn bạn dựng lại mắt xích cuối đó trên laptop: đóng image thành file, mang vào mạng cách ly, nạp lại bên trong, và đặt một cổng duyệt đúng chỗ. Làm xong, bạn có một quy trình có thể mang theo khi đến site khách.

## Bạn sẽ dựng gì, và cần chuẩn bị gì?

Sản phẩm cuối là một quy trình bốn bước: build image trên máy có mạng, xuất ra file tar, chuyển file đó vào mạng của khách, rồi nạp vào Docker daemon hoặc registry nội bộ. Bạn sẽ mô phỏng việc “mang vào trong” bằng cách xoá image khỏi máy rồi nạp lại từ file.

Bạn cần Docker cài sẵn, một image bất kỳ để thử (image của ứng dụng bạn đang làm là tốt nhất), và một repo trên GitHub nếu muốn thử phần runner. Không cần cluster để làm theo.

Cách làm này chạy được nhờ chính bản chất của container. Theo Docker, container gói code và mọi thứ nó phụ thuộc vào cùng một chỗ, nên ứng dụng chạy giống nhau dù hạ tầng bên dưới là gì.

Khi bạn không được cài gì từ internet ở phía khách, việc mọi thứ đã nằm sẵn trong image không còn là tiện lợi nữa, mà là điều kiện bắt buộc.

## Bước 1: Pipeline nên dừng ở đâu?

IBM tách hai khái niệm hay bị gộp làm một. Continuous delivery tự động khâu đóng gói và bàn giao, sau khi thay đổi đã qua integration test. Continuous deployment đi xa hơn: mọi thay đổi đã duyệt tự lên production mà không ai phải can thiệp.

Ở site khách, nhất là site đã cắt internet, bạn gần như luôn dừng ở continuous delivery. Tự động hoá mọi thứ cho đến khi ra được một artifact sạch, có phiên bản rõ ràng. Việc đưa artifact đó lên production là một quyết định có người ký, thường là người phía khách.

**Điểm mấu chốt:** Ở mạng của khách, pipeline tự động làm ra artifact; còn con người quyết định khi nào artifact đó được chạy.

Việc cần kiểm tra sau bước này rất đơn giản: bạn chỉ ra được đúng một chỗ trong quy trình mà ai đó phải bấm duyệt, và biết tên người đó.

## Bước 2: Đóng image thành file tar

Quy trình offline của GitLab bắt đầu bằng việc đóng gói Docker image thành file tar. Elastic dùng cùng mẫu này cho bản cài offline của Elastic Cloud Enterprise: chạy `docker save -o` trên một máy có kết nối, với tên file tar mang số phiên bản của image.

Áp vào ứng dụng của bạn, tên file nên mang đúng phiên bản của image. Ví dụ dưới đây dùng tên giả định:

```bash
docker save -o myapp.1.0.0.tar myapp:1.0.0
```

Kiểm tra: file tar xuất hiện trong thư mục và có dung lượng tương xứng với image. Chi tiết nhỏ nhưng quan trọng là đặt phiên bản vào cả tag lẫn tên file. Ở phía bên trong, người vận hành chỉ nhìn thấy tên file, không thấy lịch sử commit của bạn.

## Bước 3: Mang vào mạng cách ly và nạp lại

GitLab mô tả rằng các file tar này có thể được chuyển sang nơi khác rồi nạp vào một Docker daemon. Trên laptop, bạn mô phỏng bằng cách xoá image cục bộ rồi nạp lại từ file. Cú pháp dưới đây là dạng thường dùng; hãy đối chiếu với `docker load --help` trên máy bạn:

```bash
docker load -i myapp.1.0.0.tar
```

Kiểm tra: image `myapp:1.0.0` xuất hiện lại trong danh sách image, và container khởi động được mà máy không cần kéo gì từ mạng. Muốn chắc hơn, ngắt wifi trước khi chạy container. Nếu nó vẫn lên được, image của bạn thật sự không phụ thuộc mạng.

## Bước 4: Nạp vào từng host hay vào registry?

Đây là quyết định kiến trúc mà nhiều người chỉ phát hiện ra khi đã đứng ở site khách. Elastic ghi rõ rằng nếu không có private registry, bạn phải đưa image lên từng host một. GitLab thì kết thúc quy trình bằng việc nạp image vào registry offline, để registry nội bộ trở thành điểm phân phối.

Thử hình dung một khách hàng có bốn host chạy ứng dụng. Không có registry, mỗi lần phát hành bạn phải copy file tar vào cả bốn máy và chạy `docker load` bốn lần. Chỉ cần sót một máy là có bốn host chạy hai phiên bản khác nhau. Có registry nội bộ, bạn nạp một lần, và bốn host cùng lấy từ một nguồn.

| Khía cạnh | Không có registry nội bộ | Có registry offline |
|---|---|---|
| Số lần nạp mỗi bản phát hành | Một lần cho mỗi host | Một lần vào registry |
| Rủi ro lệch phiên bản | Cao, dễ sót host | Thấp, một nguồn duy nhất |
| Phù hợp khi | Ít host, thử nghiệm ban đầu | Nhiều host, phát hành đều đặn |

Với lựa chọn có registry, luồng làm việc bên trong mạng khách thường gồm ba thao tác: nạp file tar vào một máy trung chuyển, gắn lại tag theo địa chỉ registry nội bộ, rồi đẩy lên.

Ví dụ dưới đây được giản lược, với địa chỉ `registry.noibo:5000` là giả định; hãy đối chiếu `docker tag --help`, `docker push --help` và hỏi khách địa chỉ, cách xác thực của registry thật:

```bash
# Trên máy trung chuyển bên trong mạng khách (giản lược)
docker load -i myapp.1.0.0.tar
docker tag myapp:1.0.0 registry.noibo:5000/myapp:1.0.0
docker push registry.noibo:5000/myapp:1.0.0
```

Sau đó, mỗi host chỉ cần kéo image từ registry nội bộ thay vì nhận file tar riêng:

```bash
# Trên từng host (giản lược)
docker pull registry.noibo:5000/myapp:1.0.0
```

Kiểm tra: cả bốn host báo cùng một tag `1.0.0` khi bạn liệt kê image, và không host nào cần file tar nằm trên đĩa của mình. Lời khuyên: ở buổi làm việc đầu tiên, hỏi ngay khách có registry nội bộ hay không. Câu trả lời quyết định bạn viết runbook theo kiểu nào.

## Khi nào nên đặt runner bên trong mạng khách?

Không phải site nào cũng cắt mạng hoàn toàn. Có nơi máy nội bộ vẫn gọi được ra một số dịch vụ nhất định. Khi đó, GitHub Actions cho phép bạn tự host runner trên máy của mình và tuỳ biến môi trường chạy job, nên job có thể chạy trên một máy nằm sẵn trong mạng của khách, trong khi workflow vẫn được quản lý ngay trong repo.

Bộ khung dưới đây được giản lược để minh hoạ ý tưởng, không phải cấu hình hoàn chỉnh. Hãy đối chiếu cú pháp với tài liệu GitHub Actions trước khi dùng:

```yaml
# Giản lược: job chạy trên runner tự host đặt trong mạng khách
jobs:
load-release:
runs-on: self-hosted
steps:
- run: docker load -i myapp.1.0.0.tar
```

Với mạng bị ngắt về mặt vật lý, runner cũng không liên lạc được về GitHub, nên bạn quay lại đúng con đường của bước 2 và 3.

## Những lỗi hay gặp

Lỗi đầu tiên là dùng tag `latest`. Khi file tar đã được mang vào mạng cách ly, không ai có cách gì biết `latest` của tuần này khác tuần trước ở đâu. Luôn gắn phiên bản cụ thể.

Lỗi thứ hai là image vẫn phụ thuộc mạng: container lúc khởi động lại tải thêm model, package hay font từ internet. Bài test ngắt wifi ở bước 3 sinh ra để bắt lỗi này, và nên chạy trước khi bạn ra sân bay chứ không phải sau.

Lỗi thứ ba là quên host. Nếu khách không có registry, hãy ghi danh sách host vào runbook và đánh dấu từng máy sau khi nạp xong.

## Đưa kỹ năng này vào CV thế nào?

Khi đọc JD của các vị trí FDE, hãy để ý những từ như “on-prem”, “air-gapped”, “customer environment” hay “self-hosted”. Đó là dấu hiệu khá rõ rằng công ty bán vào những nơi mà quy trình ở trên không còn là chuyện lý thuyết.

Trong CV, đừng chỉ viết “có kinh nghiệm CI/CD”. Hãy mô tả một lần cụ thể: bạn đóng gói gì, chuyển vào đâu, nạp ra sao, kiểm tra thế nào sau khi nạp. Một câu chuyện như vậy chứng minh được điều mà danh sách công cụ không làm được: bạn đã đưa phần mềm chạy trên hạ tầng không thuộc quyền kiểm soát của mình.

Pipeline đẹp nhất trên GitHub cũng chỉ có giá trị khi bản build chạy được trên máy của khách. Ở những site không có internet, một file tar đặt tên đúng, một danh sách host và một người ký duyệt thường quyết định thành bại nhiều hơn bất kỳ file YAML nào.

**Thử ngay tuần này:**

- Chọn một image bạn đang dùng, chạy docker save -o ra file tar, xoá image khỏi máy rồi docker load lại và chạy thử container.
- Viết một runbook một trang cho việc chuyển bản build vào mạng offline: tên file, phiên bản, ai duyệt, nạp vào host hay registry nào.
- Thêm vào CV một dòng mô tả cụ thể việc bạn đã bàn giao phần mềm vào môi trường không có internet, kèm cách bạn kiểm tra sau khi nạp.

## Nguồn

- [What is CI/CD? (IBM)](https://www.ibm.com/think/topics/ci-cd)

- [What is a Container? (Docker)](https://www.docker.com/resources/what-container/)

- [GitHub Actions documentation](https://docs.github.com/en/actions)

- [Offline environments (GitLab Docs)](https://docs.gitlab.com/user/application_security/offline_deployments/)

- [Install ECE offline without a private Docker registry (Elastic)](https://www.elastic.co/docs/deploy-manage/deploy/cloud-enterprise/ece-install-offline-no-registry)
