Thực hành: tách dev, staging, prod cho pipeline khi khách chỉ có dữ liệu thật
Khi khách chỉ có một database chứa đầy thông tin cá nhân, việc của bạn là dựng staging mà không để dữ liệu nhạy cảm nào ra khỏi production.
- 1Phân loại cộtĐánh dấu cột PII cùng khách, đừng trông vào công cụ tự phát hiện PII
- 2Định nghĩa môi trường bằng codeMột module dùng chung, mỗi môi trường dev/staging/prod chỉ có file tham số riêng
- 3Mask trong productionTạo view đã mask, băm có khoá, để PII không bao giờ rời hạ tầng production
- 4Tạo staging và devTạo branch hoặc snapshot từ bản mask; làm mới bằng cách tạo lại
- 5Triển khai qua GitMỗi môi trường trỏ tới một thư mục; thay đổi lên prod phải đi qua pull request
PII chỉ nằm trong production; mọi môi trường khác sinh ra từ bản đã mask và được triển khai qua Git.
Đồ hoạ: FDE Times
Tóm tắt nhanh
- Đừng copy thẳng dữ liệu production sang staging: thông tin cá nhân (PII) sẽ đi theo.
- Mask trước khi dữ liệu rời production, rồi tạo staging và dev từ bản đã mask.
- Mô tả cả ba môi trường bằng code và đưa lên qua Git để chúng giống nhau, chỉ khác tham số.
Thử hình dung tuần đầu ở site khách. Bạn hỏi staging ở đâu và được trả lời rằng chỉ có một database, cũng chính là production, chứa tên, số điện thoại và địa chỉ của mọi khách hàng. Pipeline bạn sắp viết phải chạy trên dữ liệu đó, nhưng bạn không thể thử nghiệm trên nó.
Gặp tình huống này, phản xạ đầu tiên thường là “copy production sang một database khác cho an toàn”, và đó lại là phản xạ sai. Neon, nhà cung cấp Postgres serverless, cảnh báo thẳng rằng không thể copy dữ liệu production sang staging hay môi trường test một cách an toàn, vì thông tin cá nhân đi theo bản copy.
Hướng dẫn này đi qua năm bước để dựng ba môi trường từ con số không. Cả ba được mô tả bằng code, dữ liệu được mask từ trước khi rời production, và việc triển khai đi qua Git. Code trong bài là bản minh hoạ giản lược để bạn nắm cấu trúc, không phải cấu hình chạy được ngay.
Bạn sẽ dựng gì, và cần chuẩn bị những gì?
Ví dụ xuyên suốt là một công ty giao hàng giả định với bảng orders. Bảng có các cột id, customer_name, phone, address, order_value, created_at. Kết quả cuối cùng là ba môi trường dev, staging, prod dùng chung một định nghĩa, trong đó dev và staging chỉ thấy dữ liệu đã mask.
Bạn cần quyền đọc schema của production, một công cụ hạ tầng dạng code và một repo Git. Về công cụ: OpenTofu dùng được cho cả cloud lẫn on-premises. AWS CDK cho phép viết hạ tầng bằng ngôn ngữ lập trình rồi triển khai qua CloudFormation. Trên GCP, Infrastructure Manager chạy Terraform configuration như một dịch vụ managed. Nếu khách dùng Kubernetes, thêm Argo CD.
Bước 1: Bảng nào có PII thì đánh dấu ngay
Trước khi viết dòng hạ tầng nào, hãy ngồi với người hiểu dữ liệu của khách và phân loại từng cột. Ở ví dụ trên, customer_name, phone và address là PII. order_value và created_at thì cần giữ nguyên, vì pipeline của bạn tính toán trên chính các cột này.
Đừng bỏ qua bước này. Neon nói rõ rằng họ không tự phát hiện PII và không mask gì nếu người dùng không tự khai báo rule. Vì thế, thay vì trông vào công cụ tự phát hiện, hãy coi file phân loại của bạn là đầu vào chính cho mọi rule mask.
Kiểm tra: bạn có một file phân loại, mỗi cột thuộc một trong hai nhóm: giữ nguyên hoặc mask. Khách đã xem và đồng ý với file này.
Bước 2: Một định nghĩa, ba bộ tham số
Environment as Code nghĩa là mô tả toàn bộ môi trường bằng code, gồm service, cấu hình và các phụ thuộc. Theo Bunnyshell, cách này giúp mỗi môi trường là bản sao của production, nhờ đó hạn chế tình huống “chạy được trên máy tôi”. Muốn vậy, phần định nghĩa chỉ được viết một lần.
# Minh hoạ cấu trúc repo, không gắn với công cụ cụ thể
pipeline-infra/
modules/pipeline/ # định nghĩa duy nhất: DB, job, scheduler
envs/
dev.yaml # chỉ chứa tham số
staging.yaml
prod.yaml
Nếu dùng CDK, bạn có sẵn tham số, điều kiện và vòng lặp của một ngôn ngữ lập trình thật. Đoạn dưới đây là phác thảo bằng Python thường, không phải API của CDK:
# Phác thảo: cùng một hàm, khác tham số
ENVS = {
"dev": {"data_source": "masked", "size": "small"},
"staging": {"data_source": "masked", "size": "medium"},
"prod": {"data_source": "live", "size": "large"},
}
for name, params in ENVS.items():
build_pipeline(name, **params) # hàm do bạn tự viết
Kiểm tra: so file tham số thì vô ích, vì theo thiết kế chúng chỉ chứa tham số. Hãy so thứ được sinh ra: danh sách tài nguyên sau khi render cấu hình đầy đủ cho staging và prod, hoặc danh sách tài nguyên đã triển khai thật. Nếu khác nhau ở thứ gì ngoài kích thước và nguồn dữ liệu, chẳng hạn một service chỉ có ở prod, thì staging không còn là bản sao đúng nữa.
Bước 3: Mask ở đâu mới là câu hỏi quan trọng nhất
Có hai cách đặt bước mask, và rủi ro của chúng khác nhau rất xa. Tài liệu của Database Lab Engine (Postgres.ai) mô tả cả hai.
Mask trước khi rời production
- PII chỉ nằm trong production
- Môi trường non-prod chỉ nhận bản đã mask
- Rule mask chạy trên hạ tầng của khách
Mask ở môi trường non-prod
- PII bị sao chép vật lý ra ngoài production
- Dữ liệu thật nằm trên hạ tầng non-prod
- Chỉ cần sai một quyền truy cập là lộ dữ liệu
Ở site khách, nên chọn cột bên trái. Một cách đơn giản là tạo một view mask ngay trong production, và chỉ cấp cho tiến trình xuất dữ liệu quyền đọc view đó:
-- Minh hoạ, Postgres với extension pgcrypto;
-- khoá bí mật chỉ nằm trong production, không đi theo bản xuất
CREATE VIEW orders_masked AS
SELECT id,
'customer_' || id AS customer_name,
encode(hmac(phone, current_setting('app.mask_key'), 'sha256'), 'hex') AS phone,
'redacted' AS address,
order_value,
created_at
FROM orders;
Hãy để ý cách chọn hàm mask. Số điện thoại có không gian giá trị nhỏ, nên nếu chỉ băm bằng md5(phone) không kèm khoá, ai có bản xuất cũng có thể băm thử mọi số điện thoại rồi so ngược lại.
Băm có khoá (HMAC) vẫn giữ tính duy nhất để pipeline join hay đếm khách quay lại, nhưng không có khoá thì không dò ngược được. Còn order_value giữ nguyên để kết quả tính toán ở staging khớp với thực tế.
Kiểm tra: chạy trên bản xuất một truy vấn tìm chuỗi có dạng số điện thoại. Kết quả phải bằng 0. Hãy giữ truy vấn này lại để chạy mỗi lần làm mới dữ liệu.
Bước 4: Staging và dev đều sinh ra từ bản đã mask
Khi đã có bản mask, mọi môi trường non-prod đều lấy dữ liệu từ đó. Neon mô tả cách làm theo kiểu branching: các branch con thừa hưởng dữ liệu đã mask, và muốn làm mới staging thì chỉ cần tạo lại branch. Nếu khách không dùng Neon, bạn vẫn áp dụng được nguyên tắc này bằng snapshot của bản mask.
Lợi ích thực tế là dev của từng kỹ sư có thể bị xoá và tạo lại bất cứ lúc nào mà không ai phải đụng vào production. Một bug chỉ xuất hiện với dữ liệu thật giờ có thể tái hiện trên dữ liệu có cùng hình dạng, chỉ là không còn tên thật.
Kiểm tra: xoá branch hoặc database dev của bạn rồi tạo lại. Nếu mất hơn vài phút hoặc cần ai đó cấp quyền vào production, quy trình vẫn còn chỗ hở.
Bước 5: Đưa môi trường lên qua Git, không qua tay
Bước cuối giữ cho ba môi trường không lệch nhau theo thời gian. Argo CD là công cụ continuous delivery theo kiểu GitOps cho Kubernetes. Nguyên tắc của nó là định nghĩa ứng dụng, cấu hình và môi trường đều phải declarative và nằm dưới version control. Argo CD cũng quản lý được nhiều cluster.
# Minh hoạ: mỗi môi trường trỏ tới một thư mục trong repo
deploy/
dev/ -> cluster dev
staging/ -> cluster staging
prod/ -> cluster prod
Muốn đưa thay đổi từ staging lên prod, bạn mở pull request sửa thư mục prod/, không chạy lệnh tay trên máy mình. Với hạ tầng phi Kubernetes, OpenTofu cũng hướng tới đúng điều này: một workflow thống nhất để quản lý hạ tầng suốt vòng đời của nó.
Kiểm tra: chọn một thay đổi bất kỳ đang chạy ở prod và truy ngược xem nó đến từ commit nào. Nếu không tìm ra, nghĩa là đang có người sửa tay.
Ba lỗi hay gặp ở site khách
Lỗi đầu tiên là mask bằng script chạy trên laptop của kỹ sư. Dữ liệu thật đã kịp nằm trên laptop, tức là rơi đúng vào cột bên phải của bảng so sánh, chỉ là trên một máy còn khó kiểm soát hơn cả server non-prod.
Lỗi thứ hai là quên cột mới. Thử hình dung khách thêm cột email vào tháng sau mà rule mask không được cập nhật. Với một công cụ chỉ mask theo rule khai báo tay như Neon, cột đó sẽ lọt thẳng sang staging.
Truy vấn quét số điện thoại ở bước 3 không bắt được lỗi này, vì nó không nhìn vào schema.
Cách chữa là thêm một kiểm tra riêng, tạm gọi là kiểm tra schema: đọc danh sách cột hiện tại của bảng trong production, so với file phân loại ở bước 1, và báo lỗi khi thấy cột chưa được phân loại.
Chạy nó cùng truy vấn quét mỗi lần làm mới dữ liệu.
Lỗi thứ ba là để staging lệch khỏi prod vì “chỉ sửa nhanh một chút”. Sau vài tuần, staging không còn chứng minh được điều gì về prod nữa.
Kỹ năng này xuất hiện trong buổi phỏng vấn FDE ra sao?
Khi đọc job description FDE, hãy để ý các cụm như “deploy in customer environments”, “regulated data” hay “on-prem”. Gặp những cụm này, bạn nên chuẩn bị sẵn câu trả lời cho câu hỏi xử lý dữ liệu thật ra sao, vì công việc nhiều khả năng sẽ đặt bạn vào đúng bài toán trong bài.
Trong CV, đừng chỉ ghi “dùng Terraform, Argo CD”. Hãy viết theo kết quả, ví dụ: dựng ba môi trường từ một định nghĩa duy nhất, mask PII ngay trong production, có kiểm tra tự động chạy mỗi lần làm mới dữ liệu. Ba ý đó cho thấy bạn hiểu rủi ro mà một khách đang giữ dữ liệu thật phải gánh.
Ở buổi đầu với khách, đừng mở đầu bằng việc xin quyền admin production. Hãy xin ngồi phân loại cột cùng người giữ dữ liệu của họ. Đó là bước rẻ nhất, và cũng là cách tốt để bắt đầu xây lòng tin.
7 nguồn
- What Is Environment as Code (EaaC)? · 2025-02-06
- Argo CD - Declarative GitOps CD for Kubernetes
- OpenTofu Docs
- What is the AWS CDK? - AWS Cloud Development Kit (AWS CDK) v2
- Infrastructure Manager overview · 2026-10-07
- How to Handle PII in Staging Databases Without Losing Realistic Data · 2025-11-17
- Database Lab Engine: data masking (Postgres.ai docs)