Tôi Triển Khai Đường Ống Dữ Liệu Lên AWS. Và Mọi Thứ 'Cục Bộ' Đều Vỡ Tan.

25 tháng 8, 2026·9 phút đọc

Bài viết kể lại hành trình đầy thử thách khi tác giả chuyển một đường ống dữ liệu (data pipeline) Docker từ máy tính xách tay lên máy chủ EC2 của AWS. Hành trình này phơi bày những giả định ngầm về môi trường 'cùng một máy' và các lỗi kỹ thuật im lặng, từ đó đúc kết những bài học quý giá về container, mạng và cách vận hành hệ thống thực tế trên cloud.

Tôi Triển Khai Đường Ống Dữ Liệu Lên AWS. Và Mọi Thứ 'Cục Bộ' Đều Vỡ Tan.

Triển Khai Đường Ống Dữ Liệu Lên AWS: Khi Giả Định “Cục Bộ” Hóa Ra Lại Là Rào Cản Lớn Nhất

Việc chuyển một hệ thống chạy hoàn hảo trên máy tính cá nhân lên một máy chủ đám mây không chỉ là sao chép code. Nó là quá trình đối mặt với những giả định ngầm mà bạn không hề hay biết mình đã xây dựng, và bài viết này là câu chuyện chi tiết về cách tác giả vượt qua từng lớp “bức tường vô hình” đó trên AWS.

Từ việc thiếu gói phần mềm trong container, quyền truy cập Docker socket, cho đến khái niệm localhost không còn đúng, mỗi lỗi đều là một bài học về kiến trúc hệ thống và thực tiễn vận hành. Đây là bản tường thuật trung thực về một quá trình học hỏi đắt giá nhưng vô cùng bổ ích.

Giấc Mơ Cục Bộ

Trong vài tháng, tôi đã phát triển một đường ống dữ liệu nhỏ trên chiếc máy tính Windows của mình. Mọi thứ chạy trơn tru: RSS ingestion trong WSL2, Postgres trong Docker, Kestra điều phối, và dbt biến đổi dữ liệu. Tôi có một biểu đồ lineage hoàn chỉnh, các bài test đạt yêu cầu, và một luồng lịch trình tự động lấy bài viết và lưu vào cơ sở dữ liệu.

Tất cả đều hoạt động vì mọi thứ nằm trong một nơi duy nhất: chiếc laptop của tôi. Bài viết này kể về những gì xảy ra khi tôi cố gắng chuyển nó lên một máy chủ thực sự.

Thiết Lập Máy Chủ: Phần Dễ Nhất

Tạo tài khoản AWS, thiết lập IAM user cho CLI (thay vì dùng root), và khởi chạy một instance t3.small với Ubuntu 22.04 — đây là phần dễ nhất của cả quá trình. Tuy nhiên, vài bất ngờ nhỏ đã sớm xuất hiện, đủ để nhắc tôi rằng máy chủ đám mây không chỉ là chiếc máy tính của mình ở một nơi khác.

Ổ đĩa mặc định 8GB quá nhỏ. Việc thay đổi kích thước đòi hỏi phải chạy growpartresize2fs. Các hướng dẫn tìm thấy trên mạng liên tục nhắc đến device xvda, nhưng instance của tôi lại không có. Các instance mới dùng hệ thống Nitro, đặt tên ổ đĩa kiểu nvme0n1. Khác biệt nhỏ về tên gọi, nhưng nó khiến tôi nghi ngờ mọi lệnh mình sắp gõ.

Tôi cũng từng thấy instance rơi vào trạng thái "impaired" khi Kestra kéo Docker image lần đầu. Nguyên nhân có lẽ là thiếu bộ nhớ. Tôi thêm một file swap 1GB để tạo thêm không gian thở — một cách dùng ổ đĩa làm bộ nhớ chậm hơn khi RAM đầy. Đây là lời nhắc đầu tiên rằng máy chủ thuê không có "khoảng không" như laptop của tôi.

Mang Đường Ống Qua Sông

Tôi đã cài Docker, dùng rsync để chuyển project (thay vì git, vì repo public và file .env chứa secret thật), và chạy docker compose up -d. Postgres và Kestra đều khởi động thành công. Một điều bất ngờ: các flow của Kestra không nằm trong file tự đồng bộ khi bạn copy project. Chúng nằm trong database nội bộ của Kestra. Flow fetch_rss của tôi không tồn tại trên máy chủ cho đến khi tôi tự dán YAML vào giao diện.

Khi bấm Execute, nó thất bại ngay lập tức.

Cái "Hộp Đồ Nghề" Không Có Ở Đó

Flow của tôi dùng Process task runner để xây dựng Python virtual environment, cài dependencies và chạy script ETL. Lỗi báo về một package bị thiếu: python3.12-venv. Điều tôi không lường trước: Process task runner không chạy trên host EC2, mà chạy bên trong container của Kestra. Container đó có Python, nhưng không có phần cần thiết để tạo virtual environment.

Tôi sửa tạm bằng cách chạy apt-get install trực tiếp trong container, nhưng biết rằng thay đổi này sẽ biến mất sau khi restart. Đây là lúc tôi phải đưa ra quyết định quan trọng: manager và worker không nên là một. Kestra không nên là nơi xây dựng Python environment và cài package thủ công.

Tôi chuyển sang dùng Docker task runner của Kestra: Kestra sẽ khởi chạy một container riêng cho công việc Python, chạy script, rồi dọn dẹp container sau khi xong.

Trao Chìa Khóa Mà Manager Không Ngờ Cần

Để Kestra khởi chạy container worker, nó cần nói chuyện với Docker daemon trên host. Cách thực hiện: mount Docker socket vào container Kestra.

kestra:
  volumes:
    - /var/run/docker.sock:/var/run/docker.sock

Đây là một quyết định không hề trung lập — bất kỳ ai có quyền truy cập socket này đều có thể yêu cầu Docker làm bất cứ điều gì trên host. Trên laptop, điều này không đáng ngại. Trên máy chủ kết nối internet công cộng, đó là một rủi ro thực sự. Tôi chấp nhận cho dự án học tập, nhưng đó là quyết định nên được đưa ra một cách có chủ đích.

Sau khi mount socket, lỗi tiếp theo là Permission denied. Hóa ra socket được mount là chưa đủ — process của Kestra trong container không chạy với quyền root. Thêm user: "0:0" vào service definition đã khắc phục vấn đề. Tôi cũng phát hiện docker compose up -d không phải lúc nào cũng rebuild container khi thay đổi config — cần dùng --force-recreate.

Volume Không Thực Sự Tồn Tại

Flow chạy tiếp và gặp lỗi: Could not open requirements file. Tôi đã mount project folder vào worker container, và chạy thủ công lệnh docker run tương tự bên ngoài Kestra — nó hoạt động hoàn hảo. Nhưng qua Kestra thì không.

Đây là cuộc dạo chơi dài nhất và khó chịu nhất. Tôi thử setting volume-enabled, rồi volumeEnabled, đặt vào đúng chỗ. Vẫn không có lỗi, không cảnh báo — nó cứ im lặng bỏ qua.

Giải pháp cuối cùng: host-folder mounting cho Docker task runner yêu cầu bản Enterprise của Kestra. Tôi đã cố mở một cánh cửa mà tôi không biết là đã khóa. Kestra không bao giờ làm rõ điều đó.

Bài học: khi một setting dường như không hoạt động, dù bạn viết syntax đúng đến đâu, hãy dừng lại và kiểm tra xem tính năng đó có tồn tại trong phiên bản bạn đang chạy hay không.

Cách Đúng Đắn

Giải pháp không phải là ép volume hoạt động, mà là ngừng mount hoàn toàn. Kestra có tính năng Namespace Files — một file store nội bộ trong Kestra. Bạn upload file project vào đó một lần, và Kestra tự động chuyển chúng cho worker containers khi chạy, không cần truy cập host folder.

Upload file từ máy chủ cũng gặp trục trặc: tôi cần dùng POST với multipart form data (không phải PUT), và phải thêm header xác thực (Basic auth) — curl không có "bộ nhớ" như trình duyệt. curl -i để xem response headers đã giúp tôi phát hiện lỗi 401 Unauthorized.

Sau khi upload thành công tất cả sáu file, tôi viết lại flow dùng namespaceFiles: enabled: true và đổi đường dẫn từ tuyệt đối (/workspace/python/...) sang tương đối (python/...).

Lần này, script thực sự chạy: lấy 25 bài viết, parse chúng — rồi gặp lỗi mới:

psycopg.OperationalError: connection failed: connection to server at "127.0.0.1", port 5432 failed: Connection refused

Giả Định Cuối Cùng: Localhost Không Phải Là Một Nơi

Đây là sửa lỗi nhỏ nhất nhưng cũng trung thực nhất. Config database của tôi đặt DB_HOST=localhost theo mặc định. Trên laptop, điều này hoàn toàn hợp lý — mọi thứ đều chạy trên cùng một máy.

Nhưng trên AWS, worker của Kestra chạy trong container riêng. Khi nó kết nối tới localhost, nó đang nhìn vào bên trong container của chính mình. Postgres nằm ở một container khác, có thể truy cập qua mạng Docker chung bằng tên service postgres.

Tôi truyền connection details thực tế qua environment variables, và pipeline chạy từ đầu đến cuối thành công. Việc cuối cùng: thay password dạng văn bản thuần bằng Kestra KV store — lưu giá trị một lần và tham chiếu qua {{ kv('DB_PASSWORD') }}.

Điều Gì Thực Sự Vỡ, Và Tại Sao Điều Đó Quan Trọng

Nhìn lại, mọi thất bại đều xuất phát từ một trong hai thứ:

Những thứ hoạt động cục bộ nhưng vỡ khi chuyển qua container: "Database ở localhost", "Python environment tôi tự tay thiết lập vẫn còn". Trên cùng một máy, bạn không cần coi những thứ này là yêu cầu — chúng cứ ở đó và mọi thứ hoạt động. Khi chuyển sang container riêng biệt, những giả định này biến mất.

Những thứ thất bại im lặng thay vì ồn ào: Một config property gõ sai. Một tính năng không tồn tại trong phiên bản bạn đang chạy. Một request không xác thực trả về không gì thay vì lỗi. Không cái nào crash — chúng chỉ lặng lẽ làm ít hơn những gì tôi yêu cầu, điều này khó debug hơn nhiều vì không có stack trace chỉ ra khoảng trống.

Lời khuyên cho người làm lần đầu: khi chuyển thứ gì đó từ local lên server thật, hãy coi mọi giả định về "mọi thứ ở cùng một chỗ" là một tuyên bố cần chứng minh lại, không phải sự thật được giữ nguyên.

Tiếp Theo Là Gì?

Pipeline đã hoàn thành và chạy — lưu dữ liệu thật vào database cloud. Đó là mục tiêu ban đầu, và thật sự mãn nguyện khi viết được câu này.

Tôi đang phân vân giữa việc hoàn thiện vòng lặp (kết nối dbt project với database cloud) hay bắt đầu một dự án mới. Nhưng thành thật mà nói, dự án này đã dạy tôi hầu hết những gì nó cần dạy: từ "container là gì" đến debug quyền Docker socket, lỗi cấu hình im lặng và mạng container trên máy chủ thật.

Dù chọn gì, tôi sẽ tiếp tục ghi lại quá trình một cách trung thực, kể cả những sai lầm.

Đây là một phần trong chuỗi bài viết về hành trình chuyển từ systems analyst sang data engineer của tác giả.

Chia sẻ:FacebookX
Nội dung tổng hợp bằng AI, mang tính tham khảo. Xem bài gốc ↗