Chuyển đến nội dung
MINH VO Ghi chép từ công việc
của một kỹ sư tại Việt Nam
Cơ bản11 phút đọc

Artifact và cache dependency đảm nhận hai việc khác nhau

Phân biệt đầu ra cần chuyển giữa các job với dữ liệu có thể dựng lại; kiểm tra trường hợp thiếu cache, thiếu artifact và checksum không khớp.

Mô hình dây chuyền băng tải với ba trạm và một nhánh vòng bên cạnh

Artifact của workflow mang theo một đầu ra mà job khác hoặc người dùng cần xem hay sử dụng. Cache dependency, tức bộ nhớ đệm cho các thành phần phụ thuộc, giúp tránh lặp lại công việc có thể thực hiện lại từ đầu vào đã khai báo. Nếu coi hai thứ này là một, bản phát hành có thể phụ thuộc vào những tệp tình cờ còn trong cache.

Bài này dành cho lập trình viên đang nối job tạo bản dựng với job triển khai, đã biết YAML và lệnh shell cơ bản. Câu hỏi cần trả lời là job tiếp theo có cần đúng đầu ra này hay có thể dựng lại một đầu vào. Hãy giữ các gói tải về trong cache có thể bỏ đi, còn đầu ra đã kiểm thử cần được ghi nhận riêng. Ví dụ dưới đây chỉ dùng tệp văn bản vô hại và chuỗi byte Python chạy cục bộ.

Theo một đầu ra từ bên tạo đến bên nhận

Đoạn workflow sau tạo một artifact văn bản cùng checksum của nó. Job thứ hai tải artifact về rồi kiểm tra các byte nhận được:

jobs:
  build:
    runs-on: ubuntu-24.04
    steps:
      - run: |
          printf '%s\n' 'example release bytes' > app.txt
          sha256sum app.txt > app.sha256
      - uses: actions/upload-artifact@v4
        with:
          name: example-release
          path: |
            app.txt
            app.sha256
  inspect:
    needs: build
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: example-release
      - run: sha256sum -c app.sha256

Các tham chiếu action dùng thẻ của một dòng phiên bản để dễ đọc. Workflow phát hành được duy trì lâu dài nên dùng tham chiếu bất biến đã được rà soát theo chính sách chuỗi cung ứng của dự án. Ví dụ chỉ chuyển dữ liệu minh họa, không tạo ứng dụng có thể triển khai hay bản phát hành đã ký.

Quan hệ phụ thuộc giữa hai job buộc bên nhận chạy sau bên tạo. Nó không làm ổ đĩa của job đầu tự xuất hiện trong job sau. Bước tải lên và tải xuống tạo kênh chuyển dữ liệu rõ ràng giữa hai bên.

Cache dependency cung cấp đầu vào có thể dùng lại; artifact chuyển các byte đầu ra cụ thể đến bên nhận để kiểm tra.Mở ảnh kích thước đầy đủ ↗

Đầu ra ứng dụng đi qua kho artifact. Cache các gói tải về cung cấp đầu vào theo một đường riêng, không phải danh tính của bản phát hành đang được chuyển sang môi trường tiếp theo.

Checksum kiểm tra một mối quan hệ cụ thể

Lệnh checksum ở bên nhận kiểm tra xem tệp tải về có khớp giá trị đã ghi hay không. Nếu một bên có thể thay cả tệp lẫn checksum, phép so sánh đó chưa xác minh được ai phát hành tệp. Danh tính workflow đáng tin cậy, bằng chứng về nguồn gốc bản dựng hoặc chính sách chữ ký có thể bổ sung thông tin về bên đã tạo ra các byte.

Cần giữ đúng phạm vi của checksum. Nó phát hiện việc tệp không khớp một giá trị kỳ vọng đã biết, nhưng không xác nhận hành vi của ứng dụng. Khi cần bảo đảm đầu ra được kiểm thử chính là đầu ra sẽ phát hành, hãy chạy kiểm thử trên đúng đối tượng đó.

Tài liệu về workflow artifact mô tả cách lưu và chia sẻ đầu ra của workflow. Định danh artifact, lần chạy workflow và thời hạn lưu giữ đều thuộc giao ước này. Chỉ một tên được dùng lại qua nhiều lần chạy chưa đủ xác định bản phát hành nào đáng để quy trình phía sau tin cậy.

Cache phải là dữ liệu có thể bỏ đi

Cache dependency có thể chứa gói phần mềm hoặc công cụ mà trình quản lý gói sẽ phải tải lại nếu không có sẵn. Khi không tìm thấy cache, quá trình cài đặt vẫn phải tạo được kết quả dự kiến từ lockfile và nguồn gói đã được xác thực.

Nếu bản dựng chỉ hoạt động khi một cache cũ còn tồn tại, nó đang phụ thuộc vào đầu vào chưa được khai báo. Kiểm tra trên môi trường dựng sạch giúp tìm ra những đầu vào ẩn đó. Cách sửa có thể là thêm bước tạo một tệp còn thiếu hoặc khai báo đúng dependency, thay vì cố giữ cache mãi mãi.

Khóa cache mô tả điều kiện mà dữ liệu cũ có thể còn tương thích. Hệ điều hành, kiến trúc, bộ công cụ và nội dung lockfile đều có thể ảnh hưởng. Khóa dự phòng rộng giúp dùng lại một phần các gói đã tải, nhưng trình quản lý gói vẫn phải xác định và kiểm tra các dependency mà lần dựng hiện tại yêu cầu.

Một tệp nhị phân phát hành được chọn qua khóa cache rộng có rủi ro khác. Lấy bất kỳ tệp nhị phân nào khớp khóa có thể bỏ qua bước dựng hoặc kiểm thử mà quy trình phát hành yêu cầu. Khi đối tượng cần rà soát chính là đầu ra đó, hãy tham chiếu artifact cụ thể.

Lưu giữ và khôi phục có yêu cầu khác nhau

Cache có thể bị loại bỏ theo giới hạn nền tảng và lịch sử truy cập. Artifact của workflow cũng có quy tắc về thời hạn lưu và xóa dữ liệu. Không nên cho rằng một trong hai luôn còn đó chỉ vì một lần chạy từng thành công.

Nếu cần artifact để quay lại bản cũ trong production hoặc phục vụ kiểm toán lâu hơn thời hạn lưu workflow thông thường, hãy chuyển nó sang registry hay kho phát hành có vòng đời phù hợp. Giữ cả digest, thông tin nguồn gốc và yêu cầu tương thích. Một đường dẫn đến artifact đã hết hạn không thể thay thế kế hoạch khôi phục.

Chỉ lưu những đầu ra bên nhận thực sự cần. Tải lên toàn bộ thư mục làm việc có thể đưa cả dữ liệu quản lý mã nguồn, thông tin xác thực, cache hoặc dữ liệu kiểm thử tạm thời vào artifact. Danh sách đường dẫn hẹp giúp rà soát cả chi phí lưu giữ lẫn phạm vi dữ liệu được chia sẻ.

Xác định đối tượng job tiếp theo được phép dùng

Bên nhận nên gắn đầu vào với lần chạy workflow, phiên bản mã nguồn và định danh artifact dự kiến. Kích hoạt workflow khác hoặc nhận tên artifact do một sự kiện không đáng tin cậy cung cấp làm xuất hiện thêm câu hỏi về quyền: bên tạo nào được phép cung cấp bản phát hành?

Ví dụ checksum không mô hình hóa ranh giới xác thực bên tạo giữa hai workflow: hai job được khai báo cùng nhau và chỉ chuyển dữ liệu vô hại. Điều đó không chứng minh mọi job trong một workflow thực tế đều đáng tin. Quy trình triển khai có đặc quyền nhận đầu ra từ workflow khác cần xác minh bối cảnh và quyền của bên tạo trước khi sử dụng các byte.

Có thể thấy sự khác nhau qua phép thử lỗi. Bỏ cache có thể khiến bản dựng phải tải hoặc tạo lại thêm dữ liệu nhưng vẫn phải cho ra kết quả dự kiến. Đây là yêu cầu về tính đúng, không phải lời bảo đảm về thời gian chạy. Bỏ artifact bắt buộc phải làm bên nhận báo lỗi rõ ràng vì đầu vào phát hành đã khai báo không còn. Hai phản ứng khác nhau cho thấy dữ liệu phục vụ tối ưu và bằng chứng về đầu ra có vai trò riêng.

Thử hai đường lỗi ngay trên máy

Mô hình nhỏ sau giúp quan sát sự khác biệt mà không cần tài khoản CI. Dependency đã khai báo là một chuỗi byte; digest kỳ vọng của nó đại diện cho danh tính dependency được chọn độc lập. Hàm nguồn đếm số lần được gọi. Nó không liên hệ registry, biên dịch mã hay đo độ trễ mạng.

from hashlib import sha256

canonical_dependency = b"dependency-v1\n"
expected_dependency = sha256(canonical_dependency).hexdigest()
source_calls = 0

def source():
    global source_calls
    source_calls += 1
    return canonical_dependency

def build(cached):
    dependency = source() if cached is None else cached
    if sha256(dependency).hexdigest() != expected_dependency:
        raise ValueError("dependency mismatch")
    return b"release:" + dependency

def consume(artifact, expected):
    if artifact is None:
        raise FileNotFoundError("required artifact missing")
    if sha256(artifact).hexdigest() != expected:
        raise ValueError("artifact mismatch")
    return artifact

cold_output = build(None)
print("cold source calls:", source_calls)
source_calls = 0
warm_output = build(canonical_dependency)
print("warm source calls:", source_calls)
print("same output:", cold_output == warm_output)
release_digest = sha256(cold_output).hexdigest()
print("consumer accepted:", consume(warm_output, release_digest) == cold_output)

for label, operation in [
    ("bad cache", lambda: build(b"different dependency\n")),
    ("missing artifact", lambda: consume(None, release_digest)),
    ("changed artifact", lambda: consume(b"different release\n", release_digest)),
]:
    try:
        operation()
    except (ValueError, FileNotFoundError) as error:
        print(label + ":", type(error).__name__, str(error))

Đường chạy khi chưa có cache gọi nguồn một lần; đường có cache hợp lệ không gọi nguồn. Hai đường tạo ra cùng các byte, và bên nhận chấp nhận chúng khi kiểm tra với cùng digest phát hành. Ba dòng cuối lần lượt báo dependency không khớp, thiếu artifact bắt buộc và artifact không khớp. Kết quả chỉ mô tả mô hình cục bộ đã khai báo. Nó không chứng minh hai lần biên dịch thực tế tái tạo đúng từng byte, cũng không chứng minh một cache cụ thể sẽ tiết kiệm thời gian.

Cache sai nội dung được chủ ý xử lý bằng lỗi thay vì âm thầm chuyển sang nguồn. Công cụ quản lý dependency thực tế có thể bỏ gói hỏng và tải lại từ nguồn đã xác thực. Đó là chính sách khôi phục khác, cần có bước kiểm tra rõ ràng của riêng nó. Chấp nhận dữ liệu sai chỉ để giữ một lần cache hit sẽ vi phạm giao ước đầu vào. Ngược lại, không có cache là tình huống thiếu dữ liệu bình thường và đi theo đường dựng lại đã được định nghĩa.

Bảng lỗi cục bộ so sánh cache hợp lệ, cache bị thiếu, cache sai nội dung và artifact bắt buộc bị thiếu.Mở ảnh kích thước đầy đủ ↗

Hai hàng đầu đi đến các byte phát hành giống nhau. Hai hàng sau dừng ở ranh giới khác nhau: xác minh dependency trước khi tạo đầu ra, và kiểm tra artifact có tồn tại trước khi sử dụng. Thông báo lỗi nên chỉ đúng ranh giới này thay vì yêu cầu mọi bên gọi chạy lại toàn bộ workflow.

Một thư mục được khôi phục chứng minh điều gì

Tài liệu cache dependency phân biệt khóa khớp chính xác với phép khớp dự phòng. Một thư mục được khôi phục có thể chứa các gói tải cũ hữu ích nhưng chưa đáp ứng đầy đủ tập dependency hiện tại. Hãy để công cụ phân giải dependency kiểm tra lockfile hiện hành và những đối tượng có sẵn. Không nên suy ra rằng luôn có thể bỏ qua cài đặt hay xác minh chỉ vì một thư mục đã được khôi phục.

Nền tảng không cho sửa trực tiếp nội dung của một mục cache đã tồn tại; khóa mới tạo một mục khác. Tuy vậy, quy tắc chọn rộng không tự trở thành danh tính của bản phát hành. Tính tương thích phụ thuộc vào các byte được chọn và đầu vào đã khai báo, không chỉ ở việc dịch vụ đã trả về một mục dữ liệu.

Phần mở rộng của tệp không phải tiêu chí quyết định. Đầu ra trung gian của trình biên dịch có thể là cache hợp lệ nếu hệ thống dựng xác định đúng danh tính đầu vào và có thể tạo lại khi thiếu. Cùng định dạng nhị phân ấy lại có thể là artifact khi người rà soát cần đúng những byte đã được kiểm thử. Trước tiên hãy xác định bên nhận dựa vào lời bảo đảm nào, rồi chọn quy tắc lưu giữ, tra cứu và xác minh duy trì được lời bảo đảm đó.

Trong bài tập cuối, chỉ thay các byte của artifact sau khi chạy lệnh tạo tệp bằng shell, giữ nguyên checksum cũ. Bên nhận phải báo lỗi. Sau đó tạo lại checksum cho tệp đã sửa; phép so sánh sẽ thành công, cho thấy checksum được chuyển cùng tệp không phải bằng chứng cấp quyền độc lập. Với mô hình Python, thay cả artifact lẫn digest kỳ vọng do bên gọi truyền vào cũng làm thay đổi dữ liệu được chấp nhận. Giữ một digest kỳ vọng chỉ có ý nghĩa khi cách chọn digest ấy đáng tin.

Tiếp theo, sửa hàm nguồn để nó trả về dependency sai. Bản dựng khi không có cache phải thất bại ở cùng bước kiểm tra digest như khi cache sai nội dung. Nếu chọn chính sách bỏ cache rồi tải lại, vẫn phải giữ bước kiểm tra nguồn đó. Cuối cùng, bỏ artifact bắt buộc nhưng giữ cache dependency: bên nhận có nhiệm vụ phát hành một đầu ra đã được kiểm thử phải báo thiếu đầu vào, thay vì tự tạo bản phát hành thay thế từ những tệp tình cờ tìm thấy.

Nguồn tham khảo & đọc thêm

  1. GitHub workflow artifacts
  2. GitHub dependency caching reference
← Về trang bài viết

Hình minh họa

100%