Việc dev và test Cloudflare Worker dưới local là vô cùng cần thiết. Nó sẽ giúp developer tiết kiệm khá nhiều thời gian cho quá trình dev nhờ việc sử dụng một số tính năng của wrangler như hot reload, logging, v.v.

Gần đây Cloudflare đã hỗ trợ chạy Worker và các dịch vụ liên quan (KV, R2, D3, v.v.) trên máy local. Thay vì cần tạo Worker, KV, v.v. thay đổi code, deploy, tail log và test; thì đơn giản bạn chỉ cần chạy wrangler dev --local.

Trong bài viết này, mình sẽ chia sẻ về Wrangler v3 và cách để dev/test Cloudflare Worker và Cloudflare KV mà không cần đẩy code lên CF.

Ngắn gọn về Wrangler v3

Ở phiên bản v2, Wrangler sử dụng Miniflare v2 để giả lập Cloudflare(CF) KV và các dịch vụ khác trên môi trường local. Điều này mang lại nhiều cải thiện so với bản v1: cho phép giả lập KV ở local và cho phép worker code có thể thêm, sửa, xoá dữ liệu trong local KV mà không cần deploy code lên production.

Tuy nhiên, vì Miniflare v2 là một tool được phát triển bởi bên thứ ba (sau này được CF mua lại); trong một số trường hợp, code dưới local và trên production có những hành vi không đồng nhất với nhau.

Đến phiên bản v3, Wrangler sử dụng Miniflare v3, và Workerd - môi trường runtime được CF sử dụng để chạy worker trên môi trường production. Điều này giúp cho việc chạy worker trên local gần như giống hoàn toàn môi trường production. Ngoài ra Wrangler v3 còn cung cấp thêm một số tính năng khác như:

  • KV / D1 / R2 persistent - giúp cho data trên các service này trên local không bị mất đi sau mỗi lần stop wrangler.
  • Hiệu năng tốt hơn Wrangler v2.
  • Debugging, logging dễ dàng, cụ thể hơn.
  • Các bạn có thể đọc thêm tại đây.

Ví dụ minh hoạ

Trong các phần tiếp theo của bài viết, mình sẽ chia sẻ cách sử dụng Wrangler để test ứng dụng Hello World (CF Worker + KV) trên môi trường local. Tính năng của ứng dụng này chỉ đơn giản là dịch từ hello world sang ngôn ngữ mà người dùng đang sử dụng (detect thông qua http header Accept-Language của request).

Ứng dụng dịch 'Hello World' ra ngôn ngữ mà trình duyệt người dùng đang sử dụng.
Ứng dụng dịch ‘Hello World’ ra ngôn ngữ mà trình duyệt người dùng đang sử dụng.

Chúng ta sẽ cùng thử cả hai cách sử dụng Worker + KV trên CF và Worker + KV giả lập dưới local để tiện so sánh sự khác biệt giữa hai cách này.

Tất cả code trong bài viết mình để ở đây:

https://github.com/phongvq/cloudflare-worker-helloworld

Bạn nào muốn thử tạo project từ đầu thì có thể làm theo hướng dẫn trong hướng dẫn này của Cloudflare.

Sử dụng KV + Worker trên Cloudflare

Trong phần này, chúng ta sẽ sử dụng wrangler dev --remote để test worker + KV trên môi trường runtime thật của CF.

Đầu tiên, chúng ta sẽ clone sample code về.

$ git clone https://github.com/phongvq/cloudflare-worker-helloworld.git
cd cloudflare-worker-helloworld

Để sử dụng KV trên CF, chúng ta cần dùng lệnh wrangler để tạo KV. Khi chạy CF Worker (thông qua Wrangler), người dùng bắt buộc phải sử dụng một KV riêng biệt, gọi là “preview KV”, để tránh “đụng chạm” vào dữ liệu trên production gây hậu quả nghiêm trọng. Vì vậy, chúng ta cần tạo KV “assets” và KV “assets preview”.

# Authenticate nếu cần
$ npx wrangler login

# Tạo KV 'assets'
$ npx wrangler kv namespace create assets

🌀 Creating namespace with title "miniflare-kv-langparser-assets"
✨ Success!
Add the following to your configuration file in your kv_namespaces array:
[[kv_namespaces]]
binding = "assets"
id = "babdd407ea874119874d54a39adedeec"

$ npx wrangler kv namespace create assets --preview
 ⛅️ wrangler 3.84.1 (update available 3.86.1)
-------------------------------------------------------

🌀 Creating namespace with title "miniflare-kv-langparser-assets_preview"
✨ Success!
Add the following to your configuration file in your kv_namespaces array:
[[kv_namespaces]]
binding = "assets"
preview_id = "de6667c02cc849f0b3ff5f35672126ac"

Thêm id của KV đã tạo vào phần config binding trong wrangler.toml :

kv_namespaces = [
  { binding = "assets", id = "babdd407ea874119874d54a39adedeec", preview_id = "de6667c02cc849f0b3ff5f35672126ac" }
]

Upload dữ liệu trong hello-world.json lên KV assets (cả KV thật và KV preview):

$ npx wrangler kv key put hello-world.json --path hello-world.json --binding assets

 ⛅️ wrangler 3.84.1 (update available 3.86.1)
-------------------------------------------------------

Writing the contents of hello-world.json to the key "hello-world.json" on namespace babdd407ea874119874d54a39adedeec.

$ npx wrangler kv key put hello-world.json --path hello-world.json --binding assets --preview

 ⛅️ wrangler 3.84.1 (update available 3.86.1)
-------------------------------------------------------

Writing the contents of hello-world.json to the key "hello-world.json" on namespace de6667c02cc849f0b3ff5f35672126ac.

Các bạn có thể kiểm tra lại trên dashboard của CF:

Non-preview 'assets' KV
Non-preview ‘assets’ KV
Preview 'assets' KV
Preview ‘assets’ KV

Như vậy là phần data đã xong. Giờ đến phần chạy CF worker trên local, sử dụng KV đã tạo trên CF.

npx wranger --remote sẽ đẩy code worker lên CF, chạy trực tiếp trên môi trường runtime của CF và sử dụng KV đã tạo trên đó.

> npx wrangler dev --remote

 ⛅️ wrangler 3.84.1 (update available 3.86.1)
-------------------------------------------------------

Your worker has access to the following bindings:
- KV Namespaces:
  - assets: de6667c02cc849f0b3ff5f35672126ac
⎔ Starting remote preview...
[wrangler:inf] Ready on http://localhost:8787
Total Upload: 94.23 KiB / gzip: 21.64 KiB

Các bạn có thể thấy, wrangler sử dụng KV preview (assets: de6667c02cc849f0b3ff5f35672126ac) để tránh động chạm tới dữ liệu thật (trong KV babdd407ea874119874d54a39adedeec).

Test ứng dụng với curl:

# tiếng Đức
$ curl 'http://localhost:8787/hello-world' -H 'Accept-Language: de-AT,de;q=0.5'
# tiếng Anh
$ curl 'http://localhost:8787/hello-world' -H 'Accept-Language: en-EN,en;q=0.5'
Test ứng dụng bằng tiếng Anh và tiếng Đức.
Test ứng dụng bằng tiếng Anh và tiếng Đức.

Ưu điểm:

  • wrangler dev --remote chạy worker trên runtime của CF, tương tác với KV thật, mọi thứ giống môi trường chạy thật

Nhược điểm:

  • Mỗi lần thay đổi code cần đợi wrangler upload code lên CF → người dùng phải đợi, trải nghiệm dev/test không được mượt mà lắm.
Mỗi lần thay đổi code của worker, người dùng cần đợi wrangler đẩy code mới lên CF
Mỗi lần thay đổi code của worker, người dùng cần đợi wrangler đẩy code mới lên CF
  • Không thuận tiện cho việc xem log để debug - Mình cũng chưa biết tail log như thế nào, wrangler tail cũng không work, do Worker chưa được deploy lên CF. Xin các cao nhân chỉ giáo 🙏!
Log khá chung chung, không có message và stacktrace cụ thể.
Log khá chung chung, không có message và stacktrace cụ thể.
Nguồn: xkcd

Chạy KV + Worker trên môi trường local

Chính vì wrangler dev --remote có nhiều bất cập, nên CF liên tục cải thiện workerd , miniflare, v.v. để việc dev/test CF Worker dưới local trơn tru hơn. Tất cả sẽ chạy trên local (máy tính của bạn):

  • Data trong KV và các service khác sẽ được giả lập thông qua SQLite trên Local.
  • Cloudflare Worker sẽ được chạy thông qua workerd (đằng sau là miniflare v3).
  • Tất cả mọi thứ đều được xử lý bởi Wrangler, người dùng chỉ cần thao tác ở mức high-level.

Giống như ở phần trên, chúng ta cũng cần cập nhật các config cần thiết trong wrangler.toml .

Lưu ý: Trên lý thuyết, các bạn có thể sử dụng config giống hệt phần trên, chỉ cần thêm --local vào các lệnh wrangler, mọi thứ vẫn sẽ hoạt động bình thường.
Tuy nhiên nếu làm vậy, mọi thông tin liên quan tới KV (name, id, preview_id) sẽ được sử dụng lại, chỉ khác là data được đọc ghi từ máy local. Điều này rất dễ gây nhầm lẫn trong quá trình phát triển. Cá nhân mình thường chỉ định config dưới local ở trong một môi trường (Wrangler env) riêng.

Chúng ta cần cập nhật config của môi trường local trong wrangler.toml như dưới đây:

# Tách biệt config ra môi trường riêng để tránh nhầm lẫn trong quá trình phát triển
[env.local]

kv_namespaces = [
  { binding = "assets", id = "local", preview_id = "local" }
]

Các bạn có thể chỉ định idpreview_id tuỳ ý, không cần chạy npx wrangler kv namespace create assets (phần này docs của CF cũng không nói rõ, mình cũng đã mò mất một thời gian 🙏)

Sau đó insert data vào KV assets ở local bằng lệnh:

$ npx wrangler kv key put hello-world.json --path hello-world.json --binding assets --preview false --local --env local

 ⛅️ wrangler 3.84.1 (update available 3.86.1)
-------------------------------------------------------

Writing the contents of hello-world.json to the key "hello-world.json" on namespace local.

$ npx wrangler kv key put hello-world.json --path hello-world.json --binding assets --preview  --local --env local

 ⛅️ wrangler 3.84.1 (update available 3.86.1)
-------------------------------------------------------

Writing the contents of hello-world.json to the key "hello-world.json" on namespace local.

Có hai tham số được chỉ định trong các câu lệnh wrangler phía trên:

  • --local: chạy worker và các dịch vụ liên quan trên máy local.
  • --env local: sử dụng các config (KV binding, v.v.) được chỉ định trong môi trường Wrangler local (trong file wrangler.toml)

Toàn bộ data sẽ được lưu vào thư mục .wrangler/state (mặc định):

Data của KV mặc định được lưu ngay trong thư mục .
Data của KV mặc định được lưu ngay trong thư mục wrangler/state.

Dữ liệu local được quản lý bởi SQLite:

$ file .wrangler/state/v3/kv/miniflare-KVNamespaceObject/*

.wrangler/state/v3/kv/miniflare-KVNamespaceObject/2cccf226dfc814e7bbad3020e3117fcb058fe514bb0320c1ae72dfd971
2c89fb.sqlite:     SQLite 3.x database, last written using SQLite version 3044000, writer version 2, read ve
rsion 2, file counter 1, database pages 1, cookie 0, schema 0, unknown 0 encoding, version-valid-for 1
.wrangler/state/v3/kv/miniflare-KVNamespaceObject/2cccf226dfc814e7bbad3020e3117fcb058fe514bb0320c1ae72dfd971
2c89fb.sqlite-shm: data
.wrangler/state/v3/kv/miniflare-KVNamespaceObject/2cccf226dfc814e7bbad3020e3117fcb058fe514bb0320c1ae72dfd971
2c89fb.sqlite-wal: SQLite Write-Ahead Log, version 3007000

Và cuối cùng là chạy worker và kiểm tra kết quả:

> npx wrangler dev --local --env local

 ⛅️ wrangler 3.84.1 (update available 3.86.1)
-------------------------------------------------------

Your worker has access to the following bindings:
- KV Namespaces:
  - assets: local (local)
⎔ Starting local server...
[wrangler:inf] Ready on http://localhost:8787

Dữ liệu liên quan tới translation sẽ được đọc từ KV namespace local, trên môi trường local (thay vì KV có id dài loằng ngoằng trên CF như khi chạy wrangler dev --remote).

Tadaaaa!

$ curl 'http://localhost:8787/hello-world' -H 'Accept-Language: en-EN,en;q=0.5'
<!DOCTYPE html>
      <html>
        <head>
          <title>Hello World translation</title>
        </head>
        <body>
          <h1>Hello World!</h1>
        </body>
      </html>
      %                                                                                                                                                                           $ curl 'http://localhost:8787/hello-world' -H 'Accept-Language: de-AT,de;q=0.5'

<!DOCTYPE html>
      <html>
        <head>
          <title>Hello World translation</title>
        </head>
        <body>
          <h1>Hallo Welt!</h1>
        </body>
      </html>                                                                                                                                                                     

Ưu điểm:

  • Logs lỗi tường minh hơn - do chạy trực tiếp trên máy nên có thể xem được stacktrace rõ ràng.
Lỗi khi chạy local có stacktrace rõ ràng, chi tiết.
Lỗi khi chạy local có stacktrace rõ ràng, chi tiết.
  • Hot reload tức thì, mỗi khi có code change.
  • Có thể đặt debug point (điểm này mình chưa thử).
  • Toàn bộ data quản lý trên máy local, không ảnh hưởng tới data chung của team trên CF. Ngoài ra, data được lưu trên disk nên persisted giữa các lần chạy worker.

Nhược điểm:

  • Vì worker và các dịch vụ liên quan đều chạy theo kiểu simulated, nên khó tránh khỏi số ít trường hợp worker local chạy khác với worker trên CF. (trường hợp này cá nhân mình chưa gặp).
  • Hiện tại CF chưa support local dev cho toàn bộ các service, và có một số hạn chế nhỏ khi thao tác với data của một số service trên local.

Kết luận

Bản thân mình thấy, từ khi CF support chạy worker và một số dịch vụ phổ biến trên local, việc dev/test worker trở nên dễ dàng hơn rất nhiều, không còn phải đẩy code lên môi trường trên CF rồi ngồi tail log như trước đây nữa.

Tuy nhiên nếu CF viết document cho phần này tường minh hơn một chút thì tuyệt vời!!

Docs của CF nói chung và của phần worker này nói riêng khá mập mờ, người dùng cần thu thập thông tin nhỏ lẻ từ nhiều phần khác nhau để có thể làm được điều mình muốn. Đổi lại thì chất lượng và tính dễ sử dụng của sản phẩm (cụ thể là CF worker) gần đây được cải thiện rất nhiều. Hy vọng thời gian tới sẽ có nhiều tính năng thú vị liên quan đến CF Worker và CF Page.

Cảm ơn các bạn đã đọc hết bài chia sẻ này của mình!

Peace ❤️


Một số bài viết của mình về chủ đề Cloudflare: