Ngừng chờ đợi Docker Build: Live-Reload Kubernetes với Skaffold

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

Thực tế đầy khó khăn khi phát triển trên Kubernetes

Phát triển ứng dụng cho Kubernetes thường mang lại cảm giác như đang chạy marathon bằng giày leo núi. Nếu bạn đã quen với việc phát triển local truyền thống, bạn sẽ kỳ vọng rằng chỉ cần lưu file là thay đổi sẽ xuất hiện ngay lập tức. Kubernetes phá vỡ luồng làm việc đó. Chu kỳ tiêu chuẩn cực kỳ tẻ nhạt: bạn sửa code, build một Docker image mới, gắn tag, push nó lên registry, cập nhật các file manifest YAML, và cuối cùng chạy kubectl apply. Sau đó, bạn phải chờ Pod dừng và khởi động lại.

Làm việc này một lần thì không sao. Nhưng làm năm mươi lần một ngày sẽ là kẻ thù của năng suất. Tôi đã chứng kiến nhiều đội ngũ mất hơn 90 phút tập trung mỗi ngày chỉ để nhìn chằm chằm vào thanh tiến trình của docker build. Sự cản trở này khiến các lập trình viên ngại kiểm thử thường xuyên. Nó dẫn đến các commit khổng lồ đầy rủi ro và một vòng lặp phản hồi có cảm giác như đang bị kẹt ở năm 2005.

Tại sao “Inner Loop” lại có cảm giác bị lỗi thời

Sự chậm chạp này bắt nguồn từ sự ngắt kết nối giữa hệ thống file local của bạn và môi trường container của cluster. Kubernetes được xây dựng để điều phối (orchestrate) các workload thực tế (production), chứ không phải để phục vụ như một dev server hỗ trợ hot-reloading. Nó coi các container là các artifact bất biến (immutable). Nếu bạn thay đổi chỉ một dòng CSS, hệ thống vẫn mong đợi một image 200MB hoàn toàn mới.

Các pipeline CI/CD tiêu chuẩn rất tuyệt vời cho sự ổn định của production, nhưng chúng quá nặng nề cho “Inner Loop”. Đây là chu kỳ code và debug nhanh chóng ngay trên máy của bạn. Bạn cần một cách để bỏ qua việc push lên registry và cập nhật manifest thủ công mà không bị tách rời khỏi môi trường giống như production.

Cách Skaffold tinh gọn quy trình

Skaffold là một công cụ dòng lệnh giúp tự động hóa quy trình build, push và deploy ứng dụng lên Kubernetes. Nó hoạt động âm thầm giữa IDE và cluster của bạn. Thay vì phải xoay xở với năm câu lệnh CLI khác nhau, bạn hãy để Skaffold theo dõi mã nguồn của mình. Nó sẽ phát hiện các thay đổi và chỉ kích hoạt những cập nhật cần thiết.

Theo kinh nghiệm của tôi, đây là một bước chuyển mình mang tính đột phá. Tôi từng làm việc trong một dự án microservices mà việc xác minh một lỗi nhỏ mất tới 12 phút. Sau khi tích hợp Skaffold, khoảng thời gian đó giảm xuống còn dưới 15 giây. Kubernetes không còn là rào cản triển khai nữa mà trở thành một phần minh bạch của môi trường phát triển.

Điều kiện tiên quyết

Bạn sẽ cần một vài công cụ để bắt đầu:

  • Kubectl: CLI tiêu chuẩn để tương tác với cluster.
  • Skaffold: Công cụ điều khiển việc tự động hóa.
  • Một cluster local: Minikube, Kind, hoặc Docker Desktop (đã bật K8s).
  • Docker: Để xử lý việc build image local.

Thiết lập một ví dụ thực tế

Hãy xây dựng một ứng dụng Node.js đơn giản để xem cách hoạt động. Mặc dù chúng ta sử dụng Node ở đây, các khái niệm này đều áp dụng tương tự cho Go, Python hoặc Java.

Bước 1: Mã nguồn ứng dụng

Tạo một thư mục tên là skaffold-demo và thêm file app.js:

Hãy xây dựng một ứng dụng Node.js đơn giản để xem cách hoạt động. Mặc dù chúng ta sử dụng Node ở đây, các khái niệm này đều áp dụng tương tự cho Go, Python hoặc Java.

const http = require('http');

const requestListener = function (req, res) {
  res.writeHead(200);
  res.end('Xin chào từ Skaffold! Phiên bản 1');
};

const server = http.createServer(requestListener);
server.listen(8080);
console.log('Server đang chạy tại cổng 8080');

Và một file package.json cơ bản:

{
  "name": "skaffold-demo",
  "version": "1.0.0",
  "main": "app.js",
  "scripts": {
    "start": "node app.js"
  }
}

Bước 2: Container và Manifest

Chúng ta cần một Dockerfile tiêu chuẩn để container hóa ứng dụng:

FROM node:18-slim
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 8080
CMD ["node", "app.js"]

Tiếp theo, tạo một thư mục k8s và thêm deployment.yaml. Lưu ý rằng chúng ta sử dụng một tên image giả định; Skaffold sẽ quản lý việc gắn tag và chèn image thực tế cho chúng ta.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: node-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: node-app
  template:
    metadata:
      labels:
        app: node-app
    spec:
      containers:
      - name: node-app
        image: node-app-image
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: node-app-service
spec:
  selector:
    app: node-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080
  type: LoadBalancer

Bước 3: Cấu hình Skaffold

Đây là nơi quy trình tự động hóa được định nghĩa. Trong thư mục gốc của dự án, hãy tạo skaffold.yaml. Bạn có thể tạo file này tự động bằng lệnh skaffold init, nhưng dưới đây là phiên bản rút gọn cho bản demo này:

apiVersion: skaffold/v4beta3
kind: Config
build:
  artifacts:
    - image: node-app-image
      docker:
        dockerfile: Dockerfile
deploy:
  kubectl:
    manifests:
      - k8s/*.yaml

Quy trình hoạt động thực tế: skaffold dev

Chạy lệnh sau trong terminal của bạn:

skaffold dev

Skaffold bắt đầu một vòng lặp liên tục. Nó build image, gắn tag bằng một mã hash duy nhất, cập nhật các manifest YAML trong bộ nhớ và áp dụng chúng vào cluster. Cuối cùng, nó truyền log từ Pod trực tiếp về terminal của bạn. Cảm giác giống như đang chạy một tiến trình local, mặc dù nó đang nằm trong một cluster.

Nếu bạn sửa file app.js, Skaffold sẽ kích hoạt việc rebuild. Tuy nhiên, việc rebuild toàn bộ image cho một thay đổi nhỏ vẫn chưa thực sự hiệu quả. Để khắc phục điều này, chúng ta sử dụng File Sync.

Cập nhật tức thì với File Sync

File Sync cho phép Skaffold chèn trực tiếp các file đã thay đổi vào một container đang chạy. Nó bỏ qua hoàn toàn việc build image và khởi động lại Pod. Đối với các ngôn ngữ thông dịch như Node.js, đây là một lợi thế cực kỳ lớn.

Cập nhật file skaffold.yaml để bao gồm phần sync:

build:
  artifacts:
    - image: node-app-image
      docker:
        dockerfile: Dockerfile
      sync:
        manual:
          - src: 'app.js'
            dest: .
          - src: '*.json'
            dest: .

Giờ đây, khi bạn lưu app.js, Skaffold sử dụng kubectl exec để copy file vào container. Nếu bạn sử dụng một công cụ như nodemon, ứng dụng sẽ tự khởi động lại bên trong container trong chưa đầy một giây. Điều này mang lại trải nghiệm “live-reload” sánh ngang với tốc độ phát triển local.

Tự động Port Forwarding

Việc kiểm thử service thường yêu cầu bạn phải tìm địa chỉ IP hoặc cấu hình Ingress. Skaffold xử lý việc này tự động. Thêm khối mã này vào skaffold.yaml của bạn:

portForward:
  - resourceType: service
    resourceName: node-app-service
    port: 80
    localPort: 9000

Trong khi skaffold dev đang hoạt động, bạn chỉ cần truy cập localhost:9000. Skaffold sẽ điều hướng lưu lượng đó trực tiếp đến container của bạn trong cluster.

Tổng kết

Chuyển từ các lệnh docker build thủ công sang quy trình tự động với Skaffold giống như việc chuyển từ máy đánh chữ sang một IDE hiện đại. Nó loại bỏ gánh nặng tư duy về việc triển khai, giúp bạn tập trung vào logic ứng dụng. Bằng cách tận dụng tính năng sync, bạn sẽ có một chu kỳ phát triển nhanh nhạy, phản hồi tức thì và hoạt động chính xác như môi trường production. Nếu bạn đang xây dựng microservices, một công cụ như Skaffold không chỉ là một tiện ích — đó là một yêu cầu bắt buộc để duy trì tốc độ phát triển cao.

Share: