Cơn ác mộng “Chạy tốt trên máy tôi”
Tôi nhớ mình từng ngồi trong văn phòng tối om lúc 2 giờ sáng, dán mắt vào một pipeline Jenkins cứ mãi không chịu hiện màu xanh. Ở máy cục bộ, binary Go được biên dịch chỉ trong 14 giây, các bài kiểm tra đều vượt qua và Docker image đã sẵn sàng để triển khai. Nhưng trên CI runner, nó bị crash với một lỗi khó hiểu về việc thiếu header libssl. Hóa ra máy Ubuntu cục bộ của tôi đã cài sẵn libssl-dev, trong khi Jenkins agent—được quản lý bởi một đội ngũ nềnảng riêng biệt—lại chạy một image Alpine rút gọn.
Đây chính là vấn đề sai lệch môi trường (environment drift) kinh điển. Trong nhiều thập kỷ, chúng ta đã dựa vào Makefile hoặc Bash script để gắn kết các bước build. Make là một công cụ huyền thoại để thực thi tác vụ cục bộ, nhưng nó coi máy chủ của bạn như một bữa tiệc buffet; nó mặc định rằng mọi dependency đã được sắp xếp sẵn đúng ý nó.
Khi bạn chuyển logic đó sang GitHub Actions hoặc GitLab CI, bạn thường kết thúc với một file YAML dài 300 dòng chỉ để tái tạo lại môi trường cục bộ. Việc lặp lại logic này là một cái bẫy bảo trì. Đó là lý do số một khiến các pipeline CI/CD thất bại.
Tại sao các Build Script truyền thống thất bại trong DevOps hiện đại
Sau khi kiểm tra hàng chục dự án doanh nghiệp, tôi tìm thấy hai khiếm khuyết cấu trúc khiến các hệ thống build trở nên mong manh. Thứ nhất là Thiếu tính kín (Lack of Hermeticity). Một bản build được coi là kín nếu nó tạo ra kết quả đầu ra khớp từng bit (bit-for-bit) bất kể máy chủ nào. Makefile hiếm khi đạt được tính kín vì chúng làm rò rỉ các biến môi trường của máy chủ, đường dẫn file cục bộ và các phiên bản binary cấp hệ thống. Nếu một lập trình viên dùng Node v20 trong khi runner bị kẹt ở v18, bản build có thể vượt qua ở máy cục bộ nhưng lại thất bại trên production mà không có bất kỳ cảnh báo nào.
Khiếm khuyết thứ hai là Sự phức tạp của Caching. Các pipeline CI tiêu chuẩn thường chậm vì chúng bắt đầu từ một trạng thái trống rỗng. Chúng ta cố gắng vá lỗi này bằng các công cụ như actions/cache, nhưng chúng tồn tại bên ngoài logic build. Nếu bạn cập nhật một dependency nhỏ trong một dự án 4GB, việc vô hiệu hóa cache (cache invalidation) thường quá thô bạo, buộc phải build lại toàn bộ. Quy tắc vàng của tôi rất đơn giản: logic build của bạn phải được tách biệt khỏi hạ tầng runner thì mới thực sự đáng tin cậy.
Makefile vs. Docker vs. Earthly: Một so sánh quan trọng
Việc chọn đúng công cụ tùy thuộc vào nhu cầu cô lập của bạn. Makefile mang lại sự đơn giản nhưng không có tính cô lập. Docker-in-Docker (DinD) cung cấp sự cô lập nhưng nổi tiếng là khó thực hiện cache hiệu quả trong môi trường CI. Mỗi layer cần được push và pull thủ công, điều này thường triệt tiêu thời gian tiết kiệm được từ việc caching.
Earthly lấp đầy khoảng trống này. Nó hoạt động như một sự kết hợp giữa Dockerfile và Makefile. Nó áp dụng cú pháp quen thuộc của Docker (FROM, RUN, COPY) nhưng giới thiệu các target theo kiểu Make. Mọi bước trong một bản build Earthly đều thực thi bên trong một container. Nếu nó chạy được trên MacBook Pro của bạn, nó sẽ chạy y hệt như vậy trên một Linux CI agent. Earthly cũng quản lý distributed caching (cache phân tán) một cách tự nhiên. Nó chia sẻ các build layer giữa terminal cục bộ và các CI runner của bạn mà không yêu cầu bạn phải quản lý các cache key phức tạp.
Cách tiếp cận của Earthly: Build một lần, chạy mọi nơi
Bắt đầu với Earthly khá đơn giản. Earthly chỉ yêu cầu một Docker daemon đang chạy trên máy chủ. Sau khi cài đặt CLI, logic build của dự án sẽ chuyển vào một file Earthfile duy nhất.
# Cài đặt Earthly trên Linux
sudo /bin/sh -c 'wget https://github.com/earthly/earthly/releases/latest/download/earthly-linux-amd64 -O /usr/local/bin/earthly && chmod +x /usr/local/bin/earthly'
Hãy xem xét một ứng dụng Python. Trong thiết lập tiêu chuẩn, bạn sẽ quản lý một file requirements.txt và một Makefile riêng biệt. Với Earthly, bạn định nghĩa các target đại diện cho các giai đoạn cụ thể của vòng đời ứng dụng.
# Earthfile
VERSION 0.8
FROM python:3.11-slim
WORKDIR /app
setup:
COPY requirements.txt .
RUN pip install -r requirements.txt
test:
FROM +setup
COPY . .
RUN pytest .
build:
FROM +setup
COPY . .
RUN python setup.py build
SAVE ARTIFACT ./dist /dist AS LOCAL ./dist
Target test phụ thuộc vào +setup. Nếu bạn sửa một dòng code nhưng giữ nguyên requirements.txt, Earthly sẽ bỏ qua bước pip install. Nó tính toán checksum của layer và lấy kết quả từ cache. Vì việc này diễn ra bên trong container nên phiên bản Python trên máy chủ của bạn không còn quan trọng nữa.
Triển khai thực tế: Chuyển từ Makefile sang Earthfile
Đừng xóa Makefile của bạn ngay lập tức khi di chuyển. Thay vào đó, hãy sử dụng nó như một lớp bọc mỏng (wrapper). Điều này cho phép các lập trình viên tiếp tục dùng lệnh make test trong khi Earthly xử lý các công việc nặng nhọc thực sự ở phía sau.
Satellite Builds là một tính năng thay đổi cuộc chơi cho các đội ngũ lớn. Nếu máy cục bộ của bạn đang quá tải khi build microservices, bạn có thể hướng Earthly tới một “Satellite”—một build server 32-core chuyên dụng. CLI sẽ tự động chuyển build context tới server từ xa này, thực thi build và truyền log ngược lại terminal của bạn. Nó mang lại cho mỗi kỹ sư sức mạnh của một trung tâm dữ liệu ngay trong quá trình phát triển cục bộ.
# Chạy một target cục bộ nhưng sử dụng remote caching
earthly --remote-cache=my-registry.com/project-cache +build
Tận dụng Distributed Caching để tăng tốc CI/CD
Sức mạnh thực sự của Earthly được thể hiện rõ nhất khi tích hợp CI. Bạn không còn cần 50 dòng YAML để cấu hình môi trường Node, Python hay Go. Cấu hình GitHub Action của bạn thường sẽ rút gọn lại thành một bước duy nhất, dễ đọc.
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Earthly
uses: earthly/actions-setup@v1
- name: Run Build
run: earthly --push +all
Sử dụng flag --push yêu cầu Earthly xuất các artifact tới registry của bạn đồng thời upload build cache. Khi một đồng nghiệp chạy earthly +all năm phút sau đó, máy của họ sẽ tải về chính xác các cache layer đã được tạo ra bởi CI runner. Chúng tôi đã thấy thời gian build cho các ứng dụng monolithic giảm từ 15 phút xuống còn dưới 120 giây nhờ tận dụng bộ nhớ cache chia sẻ này.
Lời kết về kỹ thuật Build
Áp dụng Earthly đòi hỏi một sự thay đổi về tư duy. Bạn phải coi bản build của mình như một đồ thị có hướng không chu trình (DAG) của các container bị cô lập thay vì một chuỗi các lệnh shell tuyến tính. Thiết lập ban đầu tốn nhiều công sức hơn một bản Bash script nhanh, nhưng độ tin cậy lâu dài là không thể phủ nhận.
Các lỗi đặc thù của môi trường sẽ biến mất. Những thiết lập cục bộ không nhất quán sẽ trở thành quá khứ. Nếu bạn đang quản lý các pipeline phức tạp, hãy bắt đầu nhỏ: chọn bước build kém ổn định nhất, bọc nó trong một target Earthly và theo dõi sự cải thiện về độ ổn định. Một khi bạn trải nghiệm một bản build thực sự chạy giống hệt nhau trên mọi máy, bạn sẽ không bao giờ muốn quay lại với Makefile thuần túy nữa.

