+2

🚀 Tilt – Tối Ưu Quy Trình Dev Trên K8s Cho Team

1. Giới thiệu tổng quan về Tilt

Chào mọi người,

Sau bài viết hướng dẫn làm quen với k8s, mình đã gặp phải một vấn đề khá "đau đầu": vòng lặp phát triển (development loop) quá chậm và tốn thời gian. Mỗi khi chỉnh sửa một vài dòng code, lại phải lặp đi lặp lại một chuỗi các thao tác thủ công (build lại image, đẩy lên registry, update k8s yaml, kubectl apply,...) , và điều này thực sự làm giảm năng suất và sự tập trung.

May mắn thay, mình đã tìm thấy Tilt, một công cụ mã nguồn mở đã thay đổi hoàn toàn trải nghiệm làm việc. Vì thấy nó quá hữu ích, hôm nay mình muốn chia sẻ lại những gì mình đã tìm hiểu và áp dụng được.

Tilt là gì?

Hiểu một cách đơn giản, Tilt là một công cụ dành cho các lập trình viên để tự động hóa và tăng tốc quá trình phát triển ứng dụng trên Kubernetes. Nó giống như một "phòng điều khiển trung tâm" cho môi trường phát triển local.

Thay vì phải tự tay build Docker image, đẩy lên registry, rồi cập nhật Kubernetes cluster, Tilt sẽ theo dõi sự thay đổi trong mã nguồn và tự động thực hiện tất cả các bước đó. Không chỉ vậy, nó còn cung cấp một giao diện Web UI trực quan để bạn có thể xem log, trạng thái của tất cả các services ở cùng một nơi.

Điều đặc biệt, Giao diện Web UI của Tilt giúp quản lý các services một cách trực quan.

Lý do chọn Tilt

Để thấy rõ sự cần thiết của Tilt, hãy tưởng tượng chúng ta đang phát triển một ứng dụng web dạng microservice với 2 service như ở ví dụ trước: web, backend. Tất cả đều được đóng gói trong container và chạy trên một cluster Kubernetes local (ví dụ: Minikube, Docker Desktop).

Quy trình làm việc "truyền thống" khi bạn muốn sửa một lỗi nhỏ ở backend sẽ như sau:

  1. Chỉnh sửa code: mở file nào đó của service backend và sửa một dòng code.

  2. Build lại Docker image: Mở terminal, chạy lệnh docker build -t todoapp-backend:v2

  3. Đẩy image lên registry: Chạy docker push todoapp-backend:v2. (Bước này có thể bỏ qua nếu dùng cluster local nhưng vẫn cần load image vào cluster).

  4. Cập nhật Kubernetes: Chạy lệnh kubectl rollout restart backend-deployment để pod sử dụng image mới.

  5. Chờ đợi: Ngồi chờ pod cũ dừng, pod mới được tạo và khởi động xong. Quá trình này có thể mất từ vài chục giây đến vài phút.

  6. Kiểm tra kết quả: Mở một terminal khác, chạy kubectl logs -f <tên-pod-mới> để theo dõi log và xem lỗi đã được sửa chưa. Nếu chưa, bạn phải lặp lại từ bước 1.

Có thể thấy chỉ để sửa một lỗi nhỏ mà bạn phải thực hiện 5-6 bước thủ công và chờ đợi. Nếu một ngày bạn phải làm việc này hàng chục lần, chưa kể là sửa vài service cùng một lúc, nó sẽ trở nên cực kỳ mệt mỏi, dễ gây lỗi, và làm gián đoạn luồng suy nghĩ của bạn.

Đây chính là lúc Tilt tỏa sáng.

Với Tilt, quy trình sẽ là:

  1. Chạy tilt up một lần duy nhất khi bắt đầu ngày làm việc.

  2. Chỉnh sửa code: Sửa file nào đó.

  3. Lưu file.

Hết! Tilt sẽ tự động phát hiện thay đổi, build lại image (hoặc thông minh hơn là chỉ đồng bộ file đã thay đổi vào container đang chạy - tính năng Live Update), và khởi động lại service cho bạn. Tất cả log từ các services sẽ được hiển thị gọn gàng trên giao diện web. Vòng lặp phát triển của bạn từ vài phút giờ đây chỉ còn vài giây.

Hy vọng phần giới thiệu này đã giúp bạn có cái nhìn tổng quan về Tilt và vấn đề mà nó giải quyết. Trong các phần tiếp theo, mình sẽ đi sâu hơn vào các tính năng, lợi ích của Tilt, và cách cài đặt, setup trong 1 dự án cụ thể.

2. Các tính năng nổi bật

Theo dõi thay đổi mã nguồn:

Tự động phát hiện mọi thay đổi trong source code local và tự động build, đẩy image, triển khai lại lên Kubernetes.

Quản lý phụ thuộc build:

Có thể theo dõi và xử lý các mối quan hệ phụ thuộc giữa các ứng dụng

Hỗ trợ microservices:

Tối ưu cho dự án nhiều service

Web UI mạnh mẽ:

Cung cấp giao diện theo dõi toàn bộ quá trình build, deploy và log từng ứng dụng.

Tối ưu tốc độ build & deploy:

Rút ngắn thời gian build và triển khai nhờ hệ thống cache thông minh, cho phép thực thi trực tiếp các lệnh build ngay trên cluster.

Hai ưu điểm lớn nhất của Tilt:

Giao diện Web trực quan:

Tilt cung cấp một giao diện điều khiển rất mạnh mẽ, cho phép bạn dễ dàng theo dõi toàn bộ quá trình build và deploy ứng dụng mà không cần thực hiện thêm bất kỳ thao tác nào khác.

UI này hiển thị đầy đủ thông tin về từng pod được deploy trong cluster bằng Tilt, trạng thái deploy, lịch sử deploy, và log của từng pod (bao gồm cả log khi build image lẫn log khi container đang chạy).

Điều này đặc biệt hữu ích khi bạn phát triển ứng dụng có nhiều microservices liên quan với nhau.

Tối ưu hóa thời gian build và deploy:

Nhờ cơ chế build và deploy nhanh, Tilt giúp giảm đáng kể thời gian chờ khi phát triển. Hệ thống hỗ trợ build lũy tiến (incremental build) giúp dev có thể cập nhật thay đổi mà không phải build lại toàn bộ.

3. Hướng dẫn cài đặt

Cài đặt Tilt.dev trên Linux

Các bước cài đặt:

  1. Tải về phiên bản Tilt.dev mới nhất

  2. Giải nén và di chuyển file thực thi vào thư mục $PATH

  3. Kiểm tra lại việc cài đặt và phiên bản Tilt

curl -fsSL https://github.com/tilt-dev/tilt/releases/download/v0.33.12/tilt.0.33.12.linux.x86_64.tar.gz | tar -xzv
sudo mv tilt /usr/local/bin/
tilt version

Ở bài hướng dẫn làm quen với k8s, mình đã cung cấp chi tiết các file k8s yaml, ở bài này mình sẽ chỉ cung cấp thêm phần code đơn giản của Dockerfile và Tiltfile

Dockerfile của web:

FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .

EXPOSE 3000

CMD ["npm", "run", "dev"]

Dockerfile của backend

FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .

EXPOSE 3000

CMD ["npm", "run", "start:dev"]

cuối cùng là file Tiltfile

Note: tilt cung cấp rất nhiều option, các hàm khác nhau cho việc setup Tiltfile này (Xem đầy đủ ở đây), tuy nhiên mình cố gắng thu gọn lại, dùng các hàm tối thiểu nhất để đạt được các yêu cầu cơ bản cho việc cải thiện quy trình làm việc với minikube ở local, một số hàm mà mình đã áp dụng, mình sẽ trình bày đan xem cùng từng phần trong file Tiltfile luôn để tiện theo dõi:

# 1. KHAI BÁO RESOURCE CHUNG (Database/Postgres, PVC, ...)
# Các file YAML này sẽ luôn được apply cho mọi service (tạo DB, volume, v.v.)
k8s_yaml([
    "k8s/postgres-pvc.yaml",
    "k8s/postgres-statefulset.yaml",
    "k8s/postgres-service.yaml",
])
  • tự apply k8s yaml với k8s_yaml
# 2. ĐỊNH NGHĨA LIVE_UPDATE STEP THEO SERVICE DƯỚI DẠNG FUNCTION
# Mỗi service nên có một hàm riêng để trả về danh sách step live_update (đảm bảo DRY code)
def web_live_update():
    return [
        sync("web", "/app"),
        run("npm ci", trigger=["package.json", "package-lock.json"])
    ]

def backend_live_update():
    return [
        sync("backend", "/app"),
        run("npm ci", trigger=["package.json", "package-lock.json"])
    ]
 
# -------------------------
# 3. KHAI BÁO CÁC SERVICE CẦN QUẢN LÝ
# Chỉ cần bổ sung thêm entry vào dict SERVICES là mở rộng được project
SERVICES = {
    "todoapp-web": {
        "dockerfile": "web/Dockerfile",
        "context": "web",
        "yamls": ["k8s/web-service.yaml", "k8s/web-deployment.yaml"],
        "live_update": web_live_update,
        "port_forward": 3000,
        "resource_name": "todoapp-web",
    },
    "todoapp-backend": {
        "dockerfile": "backend/Dockerfile",
        "context": "backend",
        "yamls": ["k8s/backend-service.yaml", "k8s/backend-deployment.yaml"],
        "live_update": backend_live_update,
        "port_forward": "3001:3000",  # local:container
        "resource_name": "todoapp-backend",
    },
}

# -------------------------
# 4. CHỌN SERVICE MUỐN "UP" DỰA THEO BIẾN MÔI TRƯỜNG TILT_SERVICES
# VD: TILT_SERVICES=todoapp-web tilt up
def get_selected_services():
    env = os.getenv('TILT_SERVICES', '')
    if env:
        return [s.strip() for s in env.split(',') if s.strip() in SERVICES]
    return SERVICES.keys()

selected_services = get_selected_services()
  • dùng live_update: chạy khi container phát hiện có thay đổi file code (JS, TS, SCSS, v.v.) trong thư mục context được khai báo trong docker_build, hoặc trigger thay đổi file trong run. Trong live_update định nghĩa các step cần chạy.
  • sync: step quan trọng trong live_update của Tilt, giúp tự động đồng bộ (copy) file hoặc thư mục từ máy local của bạn vào trong container (pod) mà không cần build lại image
  • run (ở đây mình dùng npm ci để cài lại toàn bộ dependencies đúng y như trong package-lock.json)
  • trigger=["package.json", "package-lock.json"] nghĩa là chỉ chạy lệnh này khi file package.json hoặc package-lock.json đổi (không chạy khi sửa code JS/TS thông thường).
  • mình định nghĩa SERVICES để file dễ đọc và clean hơn
  • port_forward: mở (forward) một cổng từ container/pod trong Kubernetes ra cổng trên máy local, ví dụ cho phép thực hiện test với backend-deployment, không cần thông qua backend-service nữa
# -------------------------
# 5. AUTO UPDATE SECRET TỪ FILE .env VÀ RESTART POD KHI ĐỔI ENV
local_resource(
    'update-env-secret',
    '''
    kubectl create secret generic todoapp-secret --from-env-file=.env --dry-run=client -o yaml | kubectl apply -f - &&
    kubectl rollout restart deployment todoapp-web todoapp-backend
    ''',
    deps=['.env'],
    resource_deps=[SERVICES[s]["resource_name"] for s in selected_services if s in SERVICES],
)

# -------------------------
# 6. ĐỊNH NGHĨA LOCAL RESOURCE CHO TESTING (CHẠY TEST LOCAL HOẶC TRONG POD)
# -- Test local (máy dev)
local_resource(
    'backend-tests-local',
    'cd backend && npm run test',
    deps=['backend/src', 'backend/package.json'],
    auto_init=False,           # Chỉ chạy khi trigger tay (bấm nút trên UI hoặc lệnh CLI)
    allow_parallel=True
)
# -- Test in-pod (chạy test trực tiếp trong container đang chạy)
local_resource(
    'backend-tests-in-k8s',
    '''
    POD=$(kubectl get pod -l app=todoapp-backend -o jsonpath='{.items[0].metadata.name}');
    kubectl exec $POD -- npm run test
    ''',
    deps=['backend/src', 'backend/package.json'],
    auto_init=False,
    allow_parallel=True
)
  • local_resource: cho phép tích hợp bất kỳ task, script, hoặc server local vào flow phát triển K8s bằng Tilt. Tham số thứ nhất là tên resource, tham số thứ 2 là lệnh muốn thực thi, deps: là các file, folder mà tilt sẽ theo dõi cho resource này
  • auto_init: False thì resource sẽ ko tự chạy, cần phải trigger
# 7. BUILD VÀ DEPLOY TỪNG SERVICE ĐƯỢC CHỌN (chỉ các service trong selected_services)
for name in selected_services:
    s = SERVICES[name]
    docker_build(
        name,
        s["context"],
        dockerfile=s["dockerfile"],
        live_update=s["live_update"](),     # GỌI FUNCTION ở đây để tạo step đúng service cần thiết!
    )
    k8s_yaml(s["yamls"])
    k8s_resource(s["resource_name"], port_forwards=s["port_forward"])
  • docker_build: dùng để build Docker image, dựa trên context & Dockerfile chỉ định → Khi chạy, Tilt sẽ tạo image local (với tên là name), dùng context và Dockerfile truyền vào.
  • sau khi build image thì apply các file k8s yaml cho service
  • k8s_resource: chỉ định cổng sẽ được port-forward ra local

Web UI (sau khi tilt up)

image.png

image.png

4. Luồng hoạt động cơ bản của Tilt

image.png

a. Lần đầu start (tilt up)

Tilt đọc Tiltfile → Parse ra các resource, xác định cần build những gì

Build image cho từng service → Build docker image, push vào local registry của Minikube/K8s

Apply YAML lên K8s → Tạo pod, deployment, service, statefulset, pvc, secret

Port-forward & log → Expose port, theo dõi log từng resource trong UI (nếu cần)

Setup live_update → Sync code & ready cho hot reload

Biểu hiện trên Minikube:

  • Sẽ thấy các pod FE, BE, Postgres đều lên trạng thái Running

  • Có thể truy cập FE, BE qua port-forward/local domain đã cấu hình

  • Nhìn Tilt UI sẽ thấy từng resource ready

b. Luồng dev khi thay đổi code

Thay đổi code trong src/ → Tilt sync code vào pod qua live_update, app FE/BE tự reload (không build lại image, không restart pod)

Thay đổi Dockerfile → Tilt build lại image, rollout lại pod (cập nhật app mới)

Thay đổi yaml manifest → Tilt apply lại yaml, rollout lại pod nếu cần (ví dụ đổi env, replica, volume...)

Thay đổi .env → Tilt chạy local_resource update secret, rollout lại pod để nhận env mới (với cách setup của mình thì khi .env thay đổi sẽ tự động apply lại secret và rollout lại các devployment)

Biểu hiện trên K8s:

  • Pod không bị xóa/trả lại trừ khi có build/image mới

  • Trạng thái luôn thấy Running, log cập nhật liên tục

  • FE/BE response thay đổi code gần như instant

c. Khi chỉ up 1 hoặc một vài service

Dùng biến TILT_SERVICES để chỉ bật service cần thiết

Các pod service không thuộc filter sẽ không chạy, giúp tiết kiệm tài nguyên cho dev

d. Khi test/lint/migrate

Chạy test/lint/migrate local hoặc trong container bằng local_resource, trigger bằng UI hoặc CLI (vd: tilt trigger backend-tests-in-k8s)

Tài liệu tham khảo

  1. Tilt docs

  2. Local Kubernetes development with Tilt.dev


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí