4.3. Makefile#

4.3.1. Giới thiệu về Makefile#

4.3.1.1. Make là gì?#

  • Make là một công cụ tự động hoá quá trình xây dựng (build) và quản lý các dự án phần mềm, thường được sử dụng trong các dự án C/C++.

  • Makefile là tập tin cấu hình chứa tập hợp các quy tắc (rule) mà make sẽ sử dụng để biết cách và thời điểm cần (re)build các thành phần.

  • Mặc dù ra đời ban đầu cho việc build chương trình C/C++, Makefile có thể được ứng dụng rộng rãi để tự động hoá bất kỳ quy trình nào, đặc biệt hữu ích trong MLOps khi ta cần chạy nhiều tác vụ (tải data, tiền xử lý, huấn luyện, đóng gói, v.v.).

4.3.1.2. Vì sao nên sử dụng Makefile trong MLOps?#

  • Tái sử dụng và kiểm soát phiên bản: Dễ dàng quản lý các bước trong pipeline, nhất là khi dùng chung Git.

  • Tự động hoá: Giúp tự động hoá mọi tác vụ lặp lại như cài dependencies, chạy training, evaluate, v.v.

  • Tính module hoá: Chia nhỏ các tác vụ phức tạp thành các rule nhỏ, dễ bảo trì.

  • Tích hợp CI/CD: Makefile có thể chạy trong các pipeline CI/CD (GitHub Actions, GitLab CI, Jenkins, v.v.) một cách dễ dàng.


4.3.2. Cài đặt và thiết lập môi trường#

4.3.2.1. Kiểm tra Make đã có sẵn#

Trước hết, hãy kiểm tra xem make đã được cài trên hệ thống chưa:

make --version
  • Nếu có, bạn sẽ thấy phiên bản Make được in ra, ví dụ: GNU Make 4.3 Built for x86_64-pc-linux-gnu

  • Nếu chưa được cài đặt, hãy cài theo hướng dẫn bên dưới.

4.3.2.2. Cài đặt Make trên các hệ điều hành#

  • Ubuntu/Debian:

sudo apt-get update
sudo apt-get install build-essential
  • CentOS/Fedora/RHEL:

sudo yum groupinstall "Development Tools"
  • macOS:

    brew install make
    
  • Windows:

    • Sử dụng MSYS2 hoặc Cygwin để có lệnh make.

    • Hoặc dùng WSL (Windows Subsystem for Linux), cài như trên Ubuntu/Debian.


4.3.3. Cấu trúc cơ bản của Makefile#

Dưới đây là ví dụ tối giản:

# Tên target: Danh sách dependencies
#     Lệnh recipe (thường cần dấu tab ở đầu dòng)
all: main.py data.csv                     # chạy trước 2 dependencies là main.py và data.csv
    python main.py

main.py:
    echo "print('Hello, MLOps!')" > main.py

data.csv:
    echo "col1,col2" > data.csv
    echo "1,2" >> data.csv

4.3.3.1. Targets và Dependencies#

  • Target: Thường là file đầu ra (lúc này target đại diện cho file output sau khi chạy lệnh xong), hoặc tên nhiệm vụ (phím tắt). Ví dụ: all, train, data.csv.

    • mỗi target mặc định được hiểu là một tệp tin (file) đầu ra — tức là, nếu bạn định nghĩa một target có tên trùng với một file, Make sẽ so sánh thời gian sửa đổi (timestamp) của target đó với các dependencies để quyết định xem có cần chạy recipe (các lệnh để tạo/update target) hay không.

  • Dependencies: Những thứ target cần có trước khi chạy recipe. Nếu dependencies đã sẵn sàng và “mới hơn” target, lệnh recipe sẽ chạy để cập nhật target.

  • Phony target .PHONY là một danh sách chỉ định những target nào không liên quan đến file trên đĩa. Thông báo cho Make rằng đây chỉ là target mang tính “chức năng” (task), chứ không gắn với một file cụ thể.

.PHONY: all clean train

all: train
    @echo "All tasks done!"

train:
    python train.py --data data.csv --model model.pkl
    @echo "Training completed."

clean:
    rm -f data.csv processed.csv model.pkl
    @echo "Cleaned up."
  • Nếu không.PHONY: clean, Make sẽ kiểm tra xem có file tên clean trong thư mục hay không. Nếu có một file tên clean, Make có thể so sánh timestamp giữa file đó và dependencies (nếu có) để quyết định có chạy recipe hay không, hoặc thậm chí nhầm lẫn rằng “file clean đang mới hơn, ta không cần chạy lệnh rm… nữa”.

  • Khai báo .PHONY: clean sẽ giúp Make luôn chạy recipe của clean khi bạn gõ make clean. Make không tìm file clean thực trên ổ đĩa để so sánh timestamp nữa.

  • Lợi ích của việc khai báo .PHONY

    1. Tránh trùng tên file: Đảm bảo target luôn được thực thi, kể cả khi có file trùng tên.

    2. Phản ánh rõ mục đích: Cho Make biết những target nào là “chức năng” (task), không phải file đầu ra.

    3. Tăng tính nhất quán: Khi hợp tác với nhiều người, định nghĩa .PHONY giúp người khác nhanh chóng hiểu logic Makefile hơn.

Nên đặt target all hoặc help lên đầu để làm mặc định.

4.3.3.2. Phần thân (Recipe)#

  • Là các lệnh shell sẽ được chạy khi target cần cập nhật.

  • Mỗi lệnh bắt buộc phải bắt đầu bằng dấu tab (thay vì dấu cách).

  • Có thể chứa nhiều lệnh shell liên tiếp.

4.3.3.3. Biến (Variables)#

  • Giúp tái sử dụng các giá trị và tránh lặp code.

PYTHON = python3
DATA = data.csv

preprocess:
	$(PYTHON) preprocess_pipeline.py --data=$(DATA)

train: preprocess                      # để chạy train cần preprocess chạy trước
    $(PYTHON) train.py --data=$(DATA)
  • Sử dụng cú pháp $(BIEN) để gọi biến.

4.3.3.4. Hàm (Functions) trong Makefile#

  • Make hỗ trợ một số hàm tiện ích như shell, wildcard, patsubst, v.v.

  • Ví dụ, muốn lấy danh sách file .py:

SRC = $(wildcard *.py)
  • Hoặc gọi lệnh shell:

GIT_COMMIT := $(shell git rev-parse HEAD)

4.3.4. Các lệnh và tùy chọn quan trọng trong Make#

4.3.4.1. make#

  • Lệnh mặc định để chạy. make sẽ tìm file Makefile hoặc makefile trong thư mục hiện tại.

  • Khi gọi không kèm target, make sẽ chạy target đầu tiên trong Makefile.

4.3.4.2. make -f#

  • Chỉ định tệp Makefile cụ thể: make -f MyMakefile

4.3.4.3. make -j#

  • Chạy song song nhiều job (ví dụ, build nhiều file cùng lúc). Rất hữu ích khi build các project lớn: make -j 4

4.3.4.4. make clean#

  • Thường được định nghĩa như một target để dọn dẹp file tạm, file build cũ. Ví dụ:

clean:     
	rm -rf *.o *.pyc
  • Để chạy: make clean

4.3.4.5. make help#

  • Hay được sử dụng để liệt kê các target kèm mô tả. Ví dụ:

help:
    @echo "Possible targets:"
    @echo "  train   : Train the model"
    @echo "  clean   : Remove temporary files"

4.3.5. Ví dụ Makefile cơ bản cho MLOps#

.PHONY: all data preprocess train docker-build docker-run clean

all: train

data:
    wget https://example.com/dataset.csv -O data.csv
    echo "Data downloaded."

preprocess: data
    python3 preprocess.py --input data.csv --output processed.csv
    echo "Data preprocessed."

train: preprocess
    python3 train.py --data processed.csv --model model.pkl
    echo "Model trained."

docker-build: train
    docker build -t my-ml-model:latest .
    echo "Docker image built."

docker-run:
    docker run -p 8080:8080 my-ml-model:latest
    echo "Docker container is running."

clean:
    rm -f data.csv processed.csv model.pkl
    echo "Cleaned up."
  • make data tải dữ liệu từ nguồn nào đó (s3, http,…).

  • make preprocess: trước khi chạy preprocess, Make sẽ kiểm tra và gọi data (nếu chưa có hoặc cũ hơn).

  • make train (tự động gọi preprocess)

  • make docker-build (tự động gọi train): docker-build phụ thuộc vào train để đảm bảo model đã được huấn luyện trước khi đóng gói vào container.

  • make docker-run chạy container đã build

  • make all chạy target train (vì all: train)


4.3.6. Các kỹ thuật nâng cao#

4.3.6.1. Phân chia Makefile nhiều file#

Khi dự án lớn, có thể chia Makefile thành nhiều file để dễ quản lý:

  • Makefile chính: bao gồm rule cơ bản và lệnh include:

include Makefile.data
include Makefile.model
include Makefile.deploy
  • Makefile.data: chứa các rule liên quan đến dữ liệu.

  • Makefile.model: chứa các rule liên quan đến huấn luyện, test, v.v.

  • Makefile.deploy: chứa các rule liên quan đến Docker, k8s, deploy, v.v.

4.3.6.2. Sử dụng Makefile trong CI/CD pipeline#

  • GitHub Actions:

name: CI

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Set up Python
        uses: actions/setup-python@v2
        with:
          python-version: '3.8'
      - name: Install dependencies
        run: make install  # Giả sử có target install
      - name: Run training
        run: make train
      - name: Build Docker
        run: make docker-build
  • GitLab CI hay Jenkins cũng tương tự, ta chỉ cần chạy các lệnh make tương ứng.

4.3.6.3. Tự động hoá với Shell script và Makefile#

  • Kết hợp lệnh Shell script phức tạp trong *.sh với Makefile, khi Makefile chỉ gọi shell script tương ứng.

.PHONY: train
train:
    bash scripts/train.sh
  • Giúp tổ chức code sạch hơn, tách logic dài ra khỏi Makefile.


4.3.7. Tổng kết và tài nguyên tham khảo#

  • Makefile là một công cụ mạnh mẽ để tự động hoáquản lý quy trình MLOps.

  • Với một cấu trúc rule - dependencies - recipe đơn giản, ta có thể thiết lập và duy trì pipeline MLOps hiệu quả, từ tải dữ liệu, tiền xử lý, huấn luyện cho đến đóng gói, triển khai.

  • Kết hợp Makefile với các CI/CD platform (GitHub Actions, GitLab CI, Jenkins,…) sẽ giúp tự động hoá mọi thứ, giảm thiểu sai sót, rút ngắn thời gian phát triển và triển khai.