Đa số các nhóm đều viết thẳng các bước build và test vào tệp YAML của GitHub Actions. Cách này vẫn chạy tốt, cho đến khi bạn muốn chạy lại đúng bản build đó trên laptop của mình, hoặc có người đề xuất chuyển sang GitLab, và lúc đó bạn mới nhận ra cả pipeline đã dính chặt vào định dạng cấu hình của một nhà cung cấp duy nhất. Cách tôi chọn thì khá nhàm: toàn bộ logic thật sự nằm trong Docker Compose, còn công cụ CI chỉ có mỗi việc gọi nó, ngoài ra không làm gì thêm.
Vì sao tôi không để logic build trong YAML của Actions#
Vấn đề dễ thấy nhất là bị phụ thuộc vào nhà cung cấp (vendor lock-in). GitLab, Bitbucket hay Jenkins đều không hiểu workflow của Actions, nên nếu các bước build nằm trong tệp workflow thì đổi nhà cung cấp cũng có nghĩa là viết lại từ đầu. Mà có ai lên kế hoạch chừa thời gian cho việc đó bao giờ đâu.
Vấn đề thứ hai ít ai để ý hơn: bạn không chạy được pipeline trên máy mình. Nếu muốn thử một thay đổi trong bước build mà cách duy nhất là push commit lên rồi ngồi canh tab Actions, thì lịch sử git của bạn sẽ toàn những commit kiểu “fix ci”, “fix ci again”, “please”. Còn tệp Compose thì chạy trên laptop của tôi giống hệt như trên runner, nhờ vậy phần lớn những màn cãi nhau “máy tôi chạy được mà” bị dập tắt ngay từ đầu.
Chưa hết, tệp YAML còn cứ thế phình ra. Hai mươi dòng gọn gàng ban đầu sẽ thành ba trăm dòng khi các mẹo cache và điều kiện rẽ nhánh lần lượt được thêm vào, và giờ lập trình viên nào cũng phải học thêm một “phương ngữ” CI nữa, bên cạnh Git và đủ thứ khác. Một script chỉ nên làm một việc; còn một tệp workflow cồng kềnh thì rốt cuộc sẽ ôm đồm mọi việc cùng lúc.
Với Compose, trách nhiệm được chia rất rạch ròi: tệp Compose lo phần build và test, còn nền tảng CI chỉ đơn giản là nơi chạy nó mỗi lần có push.
Cách thiết lập#
- Bắt đầu với một
Dockerfile, để khâu build image chỉ nằm ở đúng một chỗ:
# Use an official Node.js runtime as the base image
FROM node:14
# Set the working directory in the container
WORKDIR /app
# Copy the package.json and package-lock.json files to the container
COPY package*.json ./
# Install the dependencies
RUN npm install
# Copy the rest of the application code to the container
COPY . .
# Expose the port the app runs on
EXPOSE 3000
# Define the command to run the application
CMD ["npm", "start"]Sau đó tạo tệp
docker-compose.build.yaml, khai báo các service cần cho việc build và test:services: app: image: myprivaterepo.com/image:latest build: . volumes: - .:/app command: sh -c "npm install && npm test"Chạy thử trên máy mình trước. Cả bài này thật ra chỉ xoay quanh bước này:
docker compose -f docker-compose.build.yaml buildKhi bước trên đã chạy ổn, workflow của GitHub Actions (hay thứ tương đương trên bất kỳ nền tảng nào khác) chỉ còn đóng vai trò điều phối:
name: CI/CD Pipeline on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 - name: Build and Test with Docker Compose run: docker compose -f docker-compose.build.yaml build - name: Push the image referenced in the docker compose file run: docker compose -f docker-compose.build.yaml push
Như vậy công cụ CI giờ chỉ làm hai việc là build và push. Mọi thứ đáng để hiểu đều nằm trong các tệp mà nền tảng nào cũng chạy trực tiếp được, và thành viên mới vào nhóm cũng vậy.
Push image#
Một cái lợi đi kèm của tệp Compose là push image trở nên cực kỳ đơn giản. Docker tự biết image sẽ được đẩy lên đâu dựa vào tên của nó: image: myprivaterepo.com/image:latest nghĩa là image sẽ được push lên myprivaterepo.com. Nếu dùng registry riêng thì bạn sẽ cần access token, phần này bạn xem thêm tài liệu của registry mình đang dùng nhé.
Nếu bạn đang dùng GitHub Actions và thấy ổn thì cũng chẳng có gì phải vội. Nhưng đến ngày cần đổi nhà cung cấp, việc chuyển đổi chỉ là chép lại hai câu lệnh, chứ không phải chuyển ba trăm dòng YAML sang định dạng mới. Còn từ giờ đến lúc đó, ít ra bạn cũng được gỡ lỗi bản build ngay trên máy mình, đàng hoàng như một người văn minh.

