Một trong những khía cạnh khó chịu nhất của CI/CD là xử lý các bản dựng chậm do liên tục tải xuống cùng một phần phụ thuộc hoặc tệ hơn là các bản dựng bị lỗi do kết nối mạng không ổn định trong quá trình cài đặt phần phụ thuộc. Có thể bạn đã sốt ruột chờ đợi khi npm tải xuống hàng trăm gói lần thứ ba trong ngày hôm nay hoặc chứng kiến quá trình triển khai không thành công do sổ đăng ký gói tạm thời không khả dụng.

GitHub Actions cung cấp các công cụ mạnh mẽ để loại bỏ những vấn đề này thông qua bộ nhớ đệm thông minh và quản lý artifact. Khi bạn hiểu cách duy trì kết quả đầu ra của bản dựng và chia sẻ dữ liệu hiệu quả giữa các công việc, quy trình làm việc của bạn sẽ trở nên nhanh hơn, đáng tin cậy hơn và ít gặp phải các lỗi bên ngoài hơn.

Hiểu thách thức cô lập

Mọi công việc của GitHub Actions đều chạy trong một máy ảo hoàn toàn mới và không có bộ nhớ về các lần thực thi trước đó. Sự tách biệt này mang lại sự nhất quán và ngăn chặn các quy trình công việc can thiệp lẫn nhau, nhưng nó tạo ra một thách thức: làm cách nào để bạn chia sẻ dữ liệu giữa các công việc cần làm việc cùng nhau?

Đây là cách luồng dữ liệu giữa các công việc bị cô lập bằng artifact:

Job 1: Build Application
┌─────────────────────────┐
│  Fresh VM Environment   │
│  ┌─────────────────┐    │
│  │ 1. Checkout     │    │
│  │ 2. Install deps │    │
│  │ 3. Run tests    │    │
│  │ 4. Build app    │    │
│  │ 5. Create files │    │
│  └─────────────────┘    │
│           │             │
│           ▼             │
│    ┌─────────────┐      │
│    │   dist/     │      │ ← Build outputs
│    │   reports/  │      │
│    └─────────────┘      │
└─────────────────────────┘
           │
           ▼ Upload Artifact
    ┌─────────────────┐
    │  GitHub Storage │  ← Persistent storage
    │   "build-123"   │
    └─────────────────┘
           │
           ▼ Download Artifact
Job 2: Deploy Application
┌─────────────────────────┐
│  Fresh VM Environment   │  ← Completely new VM
│  ┌─────────────────┐    │
│  │ 1. Checkout     │    │
│  │ 2. Download     │    │
│  │    artifacts    │    │
│  │ 3. Deploy files │    │
│  └─────────────────┘    │
│    ┌─────────────┐      │
│    │   dist/     │      │ ← Same files as Job 1
│    │   reports/  │      │   but in new VM
│    └─────────────┘      │
└─────────────────────────┘

Nếu không có artifact, mỗi công việc sẽ phải xây dựng lại mọi thứ từ đầu:

Without Artifacts (Inefficient):
Job 1: Test        Job 2: Build       Job 3: Deploy
┌──────────────┐   ┌──────────────┐   ┌──────────────┐
│ 1. Checkout  │   │ 1. Checkout  │   │ 1. Checkout  │
│ 2. Install   │   │ 2. Install   │   │ 2. Install   │
│ 3. Test      │   │ 3. Build     │   │ 3. Build     │ ← Rebuild again!
└──────────────┘   └──────────────┘   │ 4. Deploy    │
                                      └──────────────┘

With Artifacts (Efficient):
Job 1: Test & Build    Job 2: Deploy
┌──────────────────┐   ┌──────────────┐
│ 1. Checkout      │   │ 1. Download  │
│ 2. Install       │   │    artifacts │
│ 3. Test          │   │ 2. Deploy    │ ← Use existing build
│ 4. Build         │───┤──────────────│
│ 5. Upload        │   └──────────────┘
└──────────────────┘

Hãy coi artifact như một cách để bảo toàn các kết quả đầu ra có giá trị trong quá trình xây dựng của bạn - mã được biên dịch, báo cáo thử nghiệm, tài liệu hoặc gói triển khai thể hiện kết quả hữu hình của quá trình tự động hóa của bạn.

Tạo và sử dụng Build artifact

Hãy bắt đầu với một ví dụ thực tế cho thấy cách tạo và sử dụng bản dựng artifact trong quy trình làm việc thực tế:

name: Build and Deploy Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Build application
        run: npm run build

      # Create a deployment package with everything needed
      - name: Create deployment package
        run: |
          mkdir -p deploy-package
          cp -r dist/* deploy-package/
          cp package.json deploy-package/
          cp package-lock.json deploy-package/

          # Add deployment metadata
          echo "{
            \"version\": \"${{ github.sha }}\",
            \"buildTime\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
            \"branch\": \"${{ github.ref_name }}\"
          }" > deploy-package/build-info.json

      # Upload the complete deployment package
      - name: Upload deployment package
        uses: actions/upload-artifact@v4
        with:
          name: deployment-package
          path: deploy-package/
          retention-days: 30

      # Also upload test results for analysis
      - name: Upload test results
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: test-results
          path: |
            coverage/
            test-results.xml
          retention-days: 7

  deploy:
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/main'
    environment: staging

    steps:
      - name: Download deployment package
        uses: actions/download-artifact@v4
        with:
          name: deployment-package
          path: ./package

      - name: Verify package contents
        run: |
          echo "Deployment package contents:"
          ls -la package/

          echo "Build information:"
          cat package/build-info.json

          # Verify required files exist
          if [ ! -f "package/index.html" ]; then
            echo "Error: Missing index.html in deployment package"
            exit 1
          fi

      - name: Deploy to staging
        run: |
          echo "Deploying to staging environment..."
          # Your deployment commands would go here
          # rsync -avz package/ user@staging-server:/var/www/app/

Quy trình công việc này thể hiện một số khái niệm chính:

  • Công việc xây dựng sẽ tạo một gói triển khai hoàn chỉnh với tất cả các tệp cần thiết
  • Siêu dữ liệu được bao gồm trong gói để theo dõi những gì đang được triển khai
  • artifact khác nhau có thời gian lưu giữ khác nhau tùy theo mục đích của chúng
  • Công việc triển khai tải xuống và xác minh gói trước khi triển khai

Bộ nhớ đệm phụ thuộc thông minh

Bộ nhớ đệm phụ thuộc có thể cải thiện đáng kể hiệu suất của bản dựng, nhưng nó cần phải được thực hiện chính xác để tránh các vấn đề về vô hiệu hóa bộ nhớ đệm hoặc các phần phụ thuộc cũ. Dưới đây là cách triển khai bộ nhớ đệm thông minh:

name: Optimized Build with Smart Caching

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'
          # Built-in npm cache that handles most scenarios
          cache: 'npm'

      # For more complex caching scenarios
      - name: Cache node modules
        uses: actions/cache@v3
        with:
          path: node_modules
          # Cache key includes package-lock.json hash
          # When dependencies change, cache automatically invalidates
          key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
          # Fallback to partial cache matches if exact key not found
          restore-keys: |
            ${{ runner.os }}-node-

      - name: Install dependencies
        run: |
          # Only install if node_modules doesn't exist or is outdated
          if [ ! -d "node_modules" ] || [ "package-lock.json" -nt "node_modules/.package-lock.json" ]; then
            echo "Installing dependencies..."
            npm ci
            cp package-lock.json node_modules/.package-lock.json
          else
            echo "Using cached dependencies"
          fi

      # Cache other build artifacts that are expensive to recreate
      - name: Cache build cache
        uses: actions/cache@v3
        with:
          path: .next/cache
          key: ${{ runner.os }}-nextjs-${{ hashFiles('package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }}
          restore-keys: |
            ${{ runner.os }}-nextjs-${{ hashFiles('package-lock.json') }}-
            ${{ runner.os }}-nextjs-

Chìa khóa để lưu vào bộ nhớ đệm hiệu quả là hiểu được những gì thay đổi và khi nào. Bộ đệm của trình quản lý gói (như của npm) sẽ bị vô hiệu khi tệp khóa thay đổi. Bộ nhớ đệm bản dựng có thể cần các khóa phức tạp hơn bao gồm mã băm mã nguồn.

Xử lý nhiều môi trường

Các dự án thực tế thường cần tạo các bản dựng khác nhau cho các môi trường khác nhau. Đây là cách xử lý việc này một cách hiệu quả:

name: Multi-Environment Build

on:
  push:
    branches: [main, develop]

jobs:
  build:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        environment: [staging, production]
        include:
          - environment: staging
            api_url: https://api.staging.example.com
            debug: true
          - environment: production
            api_url: https://api.example.com
            debug: false

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Build for ${{ matrix.environment }}
        run: npm run build
        env:
          NODE_ENV: production
          API_URL: ${{ matrix.api_url }}
          DEBUG: ${{ matrix.debug }}

      - name: Upload ${{ matrix.environment }} build
        uses: actions/upload-artifact@v4
        with:
          name: build-${{ matrix.environment }}
          path: dist/

Chiến lược ma trận tạo song song các bản dựng riêng biệt cho từng môi trường, với cấu hình dành riêng cho môi trường được áp dụng trong quá trình xây dựng.

Chia sẻ dữ liệu giữa các quy trình công việc

Đôi khi bạn cần chia sẻ artifact giữa các quy trình công việc khác nhau. Ví dụ: quy trình xây dựng có thể tạo artifact mà quy trình triển khai riêng biệt cần:

# build.yml workflow
name: Build Application

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      # ... build steps ...

      - name: Upload build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: build-${{ github.sha }}
          path: dist/
          retention-days: 90
# deploy.yml workflow
name: Deploy Application

on:
  workflow_dispatch:
    inputs:
      build_sha:
        description: 'Git SHA of build to deploy'
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      # Download artifacts from a different workflow run
      - name: Download build artifacts
        uses: actions/download-artifact@v4
        with:
          name: build-${{ github.event.inputs.build_sha }}
          path: dist/
          run-id: ${{ github.event.inputs.build_sha }}

      # ... deployment steps ...

Mẫu này tách biệt các mối quan tâm về xây dựng và triển khai trong khi vẫn đảm bảo bạn có thể triển khai bất kỳ phiên bản nào được xây dựng trước đó.

Tối ưu hóa bộ nhớ artifact

artifact tiêu tốn dung lượng lưu trữ và có thể ảnh hưởng đến giới hạn sử dụng Tác vụ GitHub của bạn. Dưới đây là cách tối ưu hóa quản lý artifact:

- name: Upload artifacts with smart retention
  uses: actions/upload-artifact@v4
  with:
    name: build-artifacts
    path: dist/
    # Keep production builds longer than development builds
    retention-days: ${{ github.ref == 'refs/heads/main' && 90 || 7 }}
    # Compress artifacts to save space
    compression-level: 9

# Clean up old artifacts automatically
- name: Clean up old artifacts
  uses: actions/github-script@v7
  with:
    script: |
      const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
        owner: context.repo.owner,
        repo: context.repo.repo,
        run_id: context.runId
      });

      // Delete artifacts older than 30 days
      const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);

      for (const artifact of artifacts.data.artifacts) {
        if (new Date(artifact.created_at) < thirtyDaysAgo) {
          await github.rest.actions.deleteArtifact({
            owner: context.repo.owner,
            repo: context.repo.repo,
            artifact_id: artifact.id
          });
        }
      }

Xử lý các phụ thuộc lớn

Một số dự án có các bộ dữ liệu hoặc bộ phụ thuộc lớn bất thường nên không phù hợp với các chiến lược lưu vào bộ nhớ đệm thông thường. Đây là cách xử lý chúng:

- name: Cache large dependencies
  uses: actions/cache@v3
  with:
    path: |
      large-dataset/
      ml-models/
    key: large-deps-${{ hashFiles('data-version.txt') }}
    restore-keys: large-deps-

- name: Download large dependencies if needed
  run: |
    if [ ! -d "large-dataset" ]; then
      echo "Downloading large dataset..."
      wget -O dataset.tar.gz https://example.com/large-dataset.tar.gz
      tar -xzf dataset.tar.gz
      rm dataset.tar.gz
    else
      echo "Using cached large dataset"
    fi

Đối với các tệp rất lớn, hãy cân nhắc sử dụng Git LFS hoặc các dịch vụ lưu trữ bên ngoài như AWS S3, sau đó chỉ tải xuống khi cần.

Gỡ lỗi sự cố artifact

Khi artifact không hoạt động như mong đợi, đây là cách khắc phục sự cố:

- name: Debug artifact creation
  run: |
    echo "Files to be uploaded:"
    find dist/ -type f -ls

    echo "Total size:"
    du -sh dist/

- name: Upload with debugging
  uses: actions/upload-artifact@v4
  with:
    name: debug-build
    path: dist/
  continue-on-error: true

- name: Check upload status
  run: |
    if [ $? -eq 0 ]; then
      echo "Artifact upload successful"
    else
      echo "Artifact upload failed"
      ls -la dist/
    fi

Thực tiễn tốt nhất về hiệu suất

Dưới đây là các mẹo thiết thực để giúp quản lý phần phụ thuộc và artifact của bạn nhanh hơn và đáng tin cậy hơn:

Hoạt động song song artifact:

- name: Upload multiple artifacts in parallel
  uses: actions/upload-artifact@v4
  with:
    name: logs
    path: logs/

- name: Upload test results
  uses: actions/upload-artifact@v4
  with:
    name: test-results
    path: test-results/

Khóa bộ đệm thông minh:

# Good: includes all relevant files in cache key
key: ${{ runner.os }}-deps-${{ hashFiles('package-lock.json', 'requirements.txt') }}

# Better: includes environment variables that affect dependencies
key: ${{ runner.os }}-deps-${{ env.NODE_VERSION }}-${{ hashFiles('package-lock.json') }}

ATránh đập bộ nhớ đệm:

# Use restore-keys to provide fallback cache options
restore-keys: |
  ${{ runner.os }}-deps-${{ env.NODE_VERSION }}-
  ${{ runner.os }}-deps-

Hiểu artifact và quản lý phụ thuộc sẽ biến các quy trình công việc chậm, không đáng tin cậy thành tự động hóa nhanh, có thể dự đoán được. Trong phần tiếp theo, chúng ta sẽ khám phá cách xử lý các biến môi trường và bí mật một cách an toàn, điều này rất quan trọng để xây dựng quy trình công việc hoạt động an toàn trên các môi trường khác nhau.