zalo-icon
facebook-icon
phone-icon
Project Linux Production: Triển khai ETL Pipeline trên Ubuntu Server | End-to-End Project
Hướng dẫn triển khai ETL Pipeline production trên Ubuntu Server với PostgreSQL, systemd, kiểm thử dữ liệu, monitoring, backup và rollback.

Project cuối kết nối các kỹ năng Linux thành một sản phẩm hoàn chỉnh: từ user, thư mục, dependency, database đến service, scheduler, monitoring, backup và rollback.

Bài viết xây ETL nhận CSV, kiểm tra dữ liệu, nạp PostgreSQL và xuất báo cáo. Mục tiêu không phải demo chạy một lần mà là hệ thống có thể bàn giao và vận hành.

Sản phẩm người đọc sẽ hoàn thành

Sau bài viết, người học hoàn thành một ETL production trên Ubuntu gồm source, config, systemd service/timer, PostgreSQL, log, test, dashboard metric và runbook. Project được tổ chức thành các thư mục src, config, logs, tests và evidence. README ghi rõ phiên bản Ubuntu, kiến trúc máy, user thực thi, cách chạy, cách kiểm thử và cách khôi phục.

Observability backup restore và rollback
Systemd service và data quality
Database idempotent cho ETL
Đóng gói ứng dụng cấu hình và secret
Yêu cầu SLO user và directory

Bộ dữ liệu hoặc workload thực hành là batch sales ngày có 3.840 đơn, doanh thu thuần 11,60 tỷ và file đến trước 01:30. Trước khi bắt đầu, tạo snapshot hoặc bản sao dữ liệu, xác nhận còn đủ dung lượng và mở một terminal thứ hai để cứu hộ nếu phiên làm việc chính gặp lỗi. Những nguyên tắc này giúp bài tập gần với cách vận hành Linux trong doanh nghiệp.

Môi trường và quy ước an toàn

mkdir -p ~/linux-lab/{src,config,logs,tests,evidence}
cd ~/linux-lab
printf "started_at=%s\n" "$(date -Is)" | tee evidence/session.txt
uname -a | tee -a evidence/session.txt
cat /etc/os-release | tee -a evidence/session.txt

Bài thực hành dùng Ubuntu Server LTS trên máy ảo tối thiểu 2 vCPU, 4 GB RAM và 20 GB ổ đĩa. Không chạy trực tiếp trên máy production. Những lệnh có khả năng xóa dữ liệu, sửa quyền hoặc khởi động lại service phải được thử trên thư mục lab trước, đồng thời ghi lại trạng thái trước và sau.

1. Chốt yêu cầu và SLO

Kỹ thuật bắt đầu từ tiêu chí nghiệp vụ đo được. Trong bối cảnh doanh nghiệp cần pipeline doanh thu hằng ngày chạy ổn định trên Ubuntu, bước này không chỉ nhằm ghi nhớ câu lệnh mà còn giúp hiểu trạng thái của hệ thống trước khi thay đổi. Mọi thao tác đều cần có đầu vào, kết quả mong đợi và cách quay lại trạng thái an toàn nếu xảy ra lỗi.

Thực hành. Ghi input SLA, output KPI, freshness, duration, RPO và RTO. Chạy lệnh bằng tài khoản không có quyền root, quan sát mã thoát với echo $?, rồi lưu kết quả vào thư mục evidence/1. Không sao chép lệnh có sudo trước khi hiểu chính xác file, service hoặc quyền sẽ bị thay đổi.

printf "input_by=01:30 output_by=03:00 rpo=24h rto=2h\n" | tee evidence/slo.txt

Kết quả cần thấy. SLO có owner và cách đo cụ thể. Nếu đầu ra khác dự kiến, cần đọc thông báo lỗi từ trái sang phải, xác nhận đường dẫn tuyệt đối, quyền truy cập, biến môi trường và trạng thái tiến trình. Đây là thói quen quan trọng khi làm việc với server thật, nơi một lệnh chạy thành công chưa chắc tạo ra kết quả nghiệp vụ đúng.

Góc nhìn production. SLO được review với người dùng và phản ánh trong alert. Với Linux production project, tiêu chí hoàn thành gồm khả năng chạy lại, log đủ để điều tra, quyền tối thiểu, cấu hình có phiên bản và bằng chứng kiểm thử. Một thao tác thủ công chỉ được chấp nhận khi có lý do, người phê duyệt và kế hoạch tự động hóa sau đó.

2. Thiết kế user và directory

Boundary hệ điều hành giảm phạm vi lỗi. Trong bối cảnh doanh nghiệp cần pipeline doanh thu hằng ngày chạy ổn định trên Ubuntu, bước này không chỉ nhằm ghi nhớ câu lệnh mà còn giúp hiểu trạng thái của hệ thống trước khi thay đổi. Mọi thao tác đều cần có đầu vào, kết quả mong đợi và cách quay lại trạng thái an toàn nếu xảy ra lỗi.

Thực hành. Tạo service account, /opt cho code, /etc cho config, /var/lib cho state và /var/log. Chạy lệnh bằng tài khoản không có quyền root, quan sát mã thoát với echo $?, rồi lưu kết quả vào thư mục evidence/2. Không sao chép lệnh có sudo trước khi hiểu chính xác file, service hoặc quyền sẽ bị thay đổi.

sudo install -d -o etl -g etl -m 0750 /var/lib/sales-etl /var/log/sales-etl
namei -l /var/lib/sales-etl

Kết quả cần thấy. Owner mode và mục đích từng path rõ. Nếu đầu ra khác dự kiến, cần đọc thông báo lỗi từ trái sang phải, xác nhận đường dẫn tuyệt đối, quyền truy cập, biến môi trường và trạng thái tiến trình. Đây là thói quen quan trọng khi làm việc với server thật, nơi một lệnh chạy thành công chưa chắc tạo ra kết quả nghiệp vụ đúng.

Góc nhìn production. Code read-only với service; data, log có volume và backup policy riêng. Với Linux production project, tiêu chí hoàn thành gồm khả năng chạy lại, log đủ để điều tra, quyền tối thiểu, cấu hình có phiên bản và bằng chứng kiểm thử. Một thao tác thủ công chỉ được chấp nhận khi có lý do, người phê duyệt và kế hoạch tự động hóa sau đó.

3. Đóng gói ứng dụng

Deploy bằng git pull trên production khó rollback. Trong bối cảnh doanh nghiệp cần pipeline doanh thu hằng ngày chạy ổn định trên Ubuntu, bước này không chỉ nhằm ghi nhớ câu lệnh mà còn giúp hiểu trạng thái của hệ thống trước khi thay đổi. Mọi thao tác đều cần có đầu vào, kết quả mong đợi và cách quay lại trạng thái an toàn nếu xảy ra lỗi.

Thực hành. Build artifact có version và checksum. Chạy lệnh bằng tài khoản không có quyền root, quan sát mã thoát với echo $?, rồi lưu kết quả vào thư mục evidence/3. Không sao chép lệnh có sudo trước khi hiểu chính xác file, service hoặc quyền sẽ bị thay đổi.

tar -czf sales-etl-1.0.0.tgz src config
sha256sum sales-etl-1.0.0.tgz | tee evidence/release.sha256

Kết quả cần thấy. Artifact bất biến và checksum xác minh. Nếu đầu ra khác dự kiến, cần đọc thông báo lỗi từ trái sang phải, xác nhận đường dẫn tuyệt đối, quyền truy cập, biến môi trường và trạng thái tiến trình. Đây là thói quen quan trọng khi làm việc với server thật, nơi một lệnh chạy thành công chưa chắc tạo ra kết quả nghiệp vụ đúng.

Góc nhìn production. Lưu artifact registry, SBOM và chữ ký khi quy mô yêu cầu. Với Linux production project, tiêu chí hoàn thành gồm khả năng chạy lại, log đủ để điều tra, quyền tối thiểu, cấu hình có phiên bản và bằng chứng kiểm thử. Một thao tác thủ công chỉ được chấp nhận khi có lý do, người phê duyệt và kế hoạch tự động hóa sau đó.

4. Quản lý cấu hình secret

Cấu hình thay đổi theo môi trường; secret không vào source. Trong bối cảnh doanh nghiệp cần pipeline doanh thu hằng ngày chạy ổn định trên Ubuntu, bước này không chỉ nhằm ghi nhớ câu lệnh mà còn giúp hiểu trạng thái của hệ thống trước khi thay đổi. Mọi thao tác đều cần có đầu vào, kết quả mong đợi và cách quay lại trạng thái an toàn nếu xảy ra lỗi.

Thực hành. Tạo env file mode 600 và inject vào unit. Chạy lệnh bằng tài khoản không có quyền root, quan sát mã thoát với echo $?, rồi lưu kết quả vào thư mục evidence/4. Không sao chép lệnh có sudo trước khi hiểu chính xác file, service hoặc quyền sẽ bị thay đổi.

install -m 0600 /dev/null config/etl.env
printf "DB_HOST=127.0.0.1\nDB_NAME=sales\n" > config/etl.env
stat -c "%a %n" config/etl.env

Kết quả cần thấy. File chỉ owner đọc ghi và không chứa password mẫu trong Git. Nếu đầu ra khác dự kiến, cần đọc thông báo lỗi từ trái sang phải, xác nhận đường dẫn tuyệt đối, quyền truy cập, biến môi trường và trạng thái tiến trình. Đây là thói quen quan trọng khi làm việc với server thật, nơi một lệnh chạy thành công chưa chắc tạo ra kết quả nghiệp vụ đúng.

Góc nhìn production. Dùng secret manager, rotation và audit access. Với Linux production project, tiêu chí hoàn thành gồm khả năng chạy lại, log đủ để điều tra, quyền tối thiểu, cấu hình có phiên bản và bằng chứng kiểm thử. Một thao tác thủ công chỉ được chấp nhận khi có lý do, người phê duyệt và kế hoạch tự động hóa sau đó.

5. Thiết kế database idempotent

Rerun cùng ngày không được nhân bản giao dịch. Trong bối cảnh doanh nghiệp cần pipeline doanh thu hằng ngày chạy ổn định trên Ubuntu, bước này không chỉ nhằm ghi nhớ câu lệnh mà còn giúp hiểu trạng thái của hệ thống trước khi thay đổi. Mọi thao tác đều cần có đầu vào, kết quả mong đợi và cách quay lại trạng thái an toàn nếu xảy ra lỗi.

Thực hành. Dùng business key, staging table, transaction và ON CONFLICT. Chạy lệnh bằng tài khoản không có quyền root, quan sát mã thoát với echo $?, rồi lưu kết quả vào thư mục evidence/5. Không sao chép lệnh có sudo trước khi hiểu chính xác file, service hoặc quyền sẽ bị thay đổi.

psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "BEGIN; INSERT INTO sales(id,business_date,revenue) SELECT id,business_date,revenue FROM sales_stage ON CONFLICT(id) DO UPDATE SET revenue=EXCLUDED.revenue; COMMIT;"

Kết quả cần thấy. Rerun giữ số dòng ổn định và cập nhật bản sửa hợp lệ. Nếu đầu ra khác dự kiến, cần đọc thông báo lỗi từ trái sang phải, xác nhận đường dẫn tuyệt đối, quyền truy cập, biến môi trường và trạng thái tiến trình. Đây là thói quen quan trọng khi làm việc với server thật, nơi một lệnh chạy thành công chưa chắc tạo ra kết quả nghiệp vụ đúng.

Góc nhìn production. Theo dõi inserted updated rejected và lưu batch lineage. Với Linux production project, tiêu chí hoàn thành gồm khả năng chạy lại, log đủ để điều tra, quyền tối thiểu, cấu hình có phiên bản và bằng chứng kiểm thử. Một thao tác thủ công chỉ được chấp nhận khi có lý do, người phê duyệt và kế hoạch tự động hóa sau đó.

6. Systemd service

Service định nghĩa user, working directory, environment, restart và limit. Trong bối cảnh doanh nghiệp cần pipeline doanh thu hằng ngày chạy ổn định trên Ubuntu, bước này không chỉ nhằm ghi nhớ câu lệnh mà còn giúp hiểu trạng thái của hệ thống trước khi thay đổi. Mọi thao tác đều cần có đầu vào, kết quả mong đợi và cách quay lại trạng thái an toàn nếu xảy ra lỗi.

Thực hành. Tạo oneshot unit và kiểm tra security score. Chạy lệnh bằng tài khoản không có quyền root, quan sát mã thoát với echo $?, rồi lưu kết quả vào thư mục evidence/6. Không sao chép lệnh có sudo trước khi hiểu chính xác file, service hoặc quyền sẽ bị thay đổi.

systemd-analyze verify config/sales-etl.service
systemd-analyze security sales-etl.service | head -30

Kết quả cần thấy. Unit hợp lệ, chạy bằng etl và có hardening. Nếu đầu ra khác dự kiến, cần đọc thông báo lỗi từ trái sang phải, xác nhận đường dẫn tuyệt đối, quyền truy cập, biến môi trường và trạng thái tiến trình. Đây là thói quen quan trọng khi làm việc với server thật, nơi một lệnh chạy thành công chưa chắc tạo ra kết quả nghiệp vụ đúng.

Góc nhìn production. Dùng ProtectSystem, PrivateTmp, NoNewPrivileges theo compatibility. Với Linux production project, tiêu chí hoàn thành gồm khả năng chạy lại, log đủ để điều tra, quyền tối thiểu, cấu hình có phiên bản và bằng chứng kiểm thử. Một thao tác thủ công chỉ được chấp nhận khi có lý do, người phê duyệt và kế hoạch tự động hóa sau đó.

7. Timer và dependency

Pipeline chỉ chạy khi network và database sẵn sàng. Trong bối cảnh doanh nghiệp cần pipeline doanh thu hằng ngày chạy ổn định trên Ubuntu, bước này không chỉ nhằm ghi nhớ câu lệnh mà còn giúp hiểu trạng thái của hệ thống trước khi thay đổi. Mọi thao tác đều cần có đầu vào, kết quả mong đợi và cách quay lại trạng thái an toàn nếu xảy ra lỗi.

Thực hành. Tạo timer, dependency và persistent catch-up. Chạy lệnh bằng tài khoản không có quyền root, quan sát mã thoát với echo $?, rồi lưu kết quả vào thư mục evidence/7. Không sao chép lệnh có sudo trước khi hiểu chính xác file, service hoặc quyền sẽ bị thay đổi.

systemd-analyze calendar "02:00"
systemctl list-timers sales-etl.timer --all

Kết quả cần thấy. Lần chạy kế tiếp đúng và missed run được xử lý theo policy. Nếu đầu ra khác dự kiến, cần đọc thông báo lỗi từ trái sang phải, xác nhận đường dẫn tuyệt đối, quyền truy cập, biến môi trường và trạng thái tiến trình. Đây là thói quen quan trọng khi làm việc với server thật, nơi một lệnh chạy thành công chưa chắc tạo ra kết quả nghiệp vụ đúng.

Góc nhìn production. Không dùng sleep để chờ dependency; dùng health và retry giới hạn. Với Linux production project, tiêu chí hoàn thành gồm khả năng chạy lại, log đủ để điều tra, quyền tối thiểu, cấu hình có phiên bản và bằng chứng kiểm thử. Một thao tác thủ công chỉ được chấp nhận khi có lý do, người phê duyệt và kế hoạch tự động hóa sau đó.

8. Data quality và reconciliation

Job success kỹ thuật chưa chắc KPI đúng. Trong bối cảnh doanh nghiệp cần pipeline doanh thu hằng ngày chạy ổn định trên Ubuntu, bước này không chỉ nhằm ghi nhớ câu lệnh mà còn giúp hiểu trạng thái của hệ thống trước khi thay đổi. Mọi thao tác đều cần có đầu vào, kết quả mong đợi và cách quay lại trạng thái an toàn nếu xảy ra lỗi.

Thực hành. Đối chiếu file count, rejected, tổng revenue và database độc lập. Chạy lệnh bằng tài khoản không có quyền root, quan sát mã thoát với echo $?, rồi lưu kết quả vào thư mục evidence/8. Không sao chép lệnh có sudo trước khi hiểu chính xác file, service hoặc quyền sẽ bị thay đổi.

wc -l data/raw/sales.csv
psql "$DATABASE_URL" -Atc "SELECT count(*),sum(net_revenue) FROM sales WHERE business_date=CURRENT_DATE;"

Kết quả cần thấy. Kỳ vọng 3.840 đơn và 11,60 tỷ theo dataset test. Nếu đầu ra khác dự kiến, cần đọc thông báo lỗi từ trái sang phải, xác nhận đường dẫn tuyệt đối, quyền truy cập, biến môi trường và trạng thái tiến trình. Đây là thói quen quan trọng khi làm việc với server thật, nơi một lệnh chạy thành công chưa chắc tạo ra kết quả nghiệp vụ đúng.

Góc nhìn production. Lưu expected actual difference và chặn publish nếu vượt tolerance. Với Linux production project, tiêu chí hoàn thành gồm khả năng chạy lại, log đủ để điều tra, quyền tối thiểu, cấu hình có phiên bản và bằng chứng kiểm thử. Một thao tác thủ công chỉ được chấp nhận khi có lý do, người phê duyệt và kế hoạch tự động hóa sau đó.

9. Observability và alert

Cần liên kết log, metric và batch ID. Trong bối cảnh doanh nghiệp cần pipeline doanh thu hằng ngày chạy ổn định trên Ubuntu, bước này không chỉ nhằm ghi nhớ câu lệnh mà còn giúp hiểu trạng thái của hệ thống trước khi thay đổi. Mọi thao tác đều cần có đầu vào, kết quả mong đợi và cách quay lại trạng thái an toàn nếu xảy ra lỗi.

Thực hành. Xuất duration, rows, freshness, rejected và status; tạo alert test. Chạy lệnh bằng tài khoản không có quyền root, quan sát mã thoát với echo $?, rồi lưu kết quả vào thư mục evidence/9. Không sao chép lệnh có sudo trước khi hiểu chính xác file, service hoặc quyền sẽ bị thay đổi.

journalctl -u sales-etl --since today --no-pager | tail -100
cat /var/lib/sales-etl/metrics.prom

Kết quả cần thấy. Log và metric cùng run_id, alert trỏ tới runbook. Nếu đầu ra khác dự kiến, cần đọc thông báo lỗi từ trái sang phải, xác nhận đường dẫn tuyệt đối, quyền truy cập, biến môi trường và trạng thái tiến trình. Đây là thói quen quan trọng khi làm việc với server thật, nơi một lệnh chạy thành công chưa chắc tạo ra kết quả nghiệp vụ đúng.

Góc nhìn production. Dashboard theo SLO, giảm alert không hành động được. Với Linux production project, tiêu chí hoàn thành gồm khả năng chạy lại, log đủ để điều tra, quyền tối thiểu, cấu hình có phiên bản và bằng chứng kiểm thử. Một thao tác thủ công chỉ được chấp nhận khi có lý do, người phê duyệt và kế hoạch tự động hóa sau đó.

10. Backup restore rollback

Release không có rollback và backup không restore đều chưa an toàn. Trong bối cảnh doanh nghiệp cần pipeline doanh thu hằng ngày chạy ổn định trên Ubuntu, bước này không chỉ nhằm ghi nhớ câu lệnh mà còn giúp hiểu trạng thái của hệ thống trước khi thay đổi. Mọi thao tác đều cần có đầu vào, kết quả mong đợi và cách quay lại trạng thái an toàn nếu xảy ra lỗi.

Thực hành. Dump database, backup config, deploy version mới rồi diễn tập rollback. Chạy lệnh bằng tài khoản không có quyền root, quan sát mã thoát với echo $?, rồi lưu kết quả vào thư mục evidence/10. Không sao chép lệnh có sudo trước khi hiểu chính xác file, service hoặc quyền sẽ bị thay đổi.

pg_dump "$DATABASE_URL" -Fc -f evidence/sales.dump
tar -czf evidence/config-backup.tgz config
sha256sum evidence/*.{dump,tgz}

Kết quả cần thấy. Backup có checksum, restore test và phiên bản trước còn sẵn. Nếu đầu ra khác dự kiến, cần đọc thông báo lỗi từ trái sang phải, xác nhận đường dẫn tuyệt đối, quyền truy cập, biến môi trường và trạng thái tiến trình. Đây là thói quen quan trọng khi làm việc với server thật, nơi một lệnh chạy thành công chưa chắc tạo ra kết quả nghiệp vụ đúng.

Góc nhìn production. Định kỳ game day, đo RTO thật và cập nhật runbook. Với Linux production project, tiêu chí hoàn thành gồm khả năng chạy lại, log đủ để điều tra, quyền tối thiểu, cấu hình có phiên bản và bằng chứng kiểm thử. Một thao tác thủ công chỉ được chấp nhận khi có lý do, người phê duyệt và kế hoạch tự động hóa sau đó.

Kiểm thử cuối bài

Kiểm thử gồm bốn lớp. Lớp chức năng xác nhận pipeline tạo đúng KPI, rerun không duplicate, lỗi rollback, SLA đạt và restore từ backup thành công. Lớp dữ liệu đối chiếu số file, kích thước, checksum hoặc số bản ghi trước và sau. Lớp vận hành kiểm tra retry, log, thời gian chạy và mức sử dụng tài nguyên. Lớp bảo mật xác nhận service không chạy bằng root nếu không cần thiết và file nhạy cảm không mở quyền cho group hoặc other.

set -euo pipefail
test -d ~/linux-lab/logs
test -s ~/linux-lab/evidence/session.txt
find ~/linux-lab -type f -perm -0002 -print | tee evidence/world_writable.txt
df -h | tee evidence/disk_after.txt
free -h | tee evidence/memory_after.txt

Một bài lab chỉ đạt khi lệnh có thể chạy lại mà không tạo trạng thái hỏng, lỗi trả mã thoát khác 0, log có timestamp và người khác làm theo README có thể tái tạo kết quả. Nếu kiểm thử chỉ dựa trên việc “màn hình không báo lỗi”, sản phẩm chưa đủ tiêu chuẩn production.

Những lỗi thường gặp và cách xử lý

Các lỗi lớn gồm xây trên tài khoản cá nhân, hard-code secret, không có khóa idempotency, service chạy root, kiểm tra bằng chính output cần kiểm tra, không thử rollback và bàn giao thiếu runbook. Cách xử lý có hệ thống là tái hiện lỗi bằng đầu vào nhỏ, thu thập trạng thái bằng lệnh chỉ đọc, xác định lớp gây lỗi rồi mới thay đổi. Không sửa nhiều cấu hình cùng lúc vì sẽ mất khả năng xác định thay đổi nào thực sự giải quyết vấn đề.

Khi cần trợ giúp, cung cấp phiên bản hệ điều hành, câu lệnh đầy đủ đã che dữ liệu nhạy cảm, output, mã thoát, quyền của file liên quan và log trong đúng khoảng thời gian xảy ra lỗi. Mô tả tốt giúp rút ngắn thời gian điều tra và tránh các lời khuyên nguy hiểm như mở quyền 777 hoặc tắt firewall.

Đưa kỹ năng vào môi trường production

Giải pháp triển khai qua mã, phân tách Dev Test Prod, artifact bất biến, secret injection, approval và post-deployment verification. Mỗi metric có owner, alert và SLO.

Trước mỗi thay đổi, cần có ticket, phạm vi ảnh hưởng, cửa sổ triển khai, backup, tiêu chí thành công, tiêu chí rollback và người chịu trách nhiệm. Sau thay đổi, lưu evidence, cập nhật runbook và theo dõi ít nhất một chu kỳ workload. Linux ổn định không đến từ một câu lệnh thần kỳ mà từ quy trình có thể kiểm chứng.

Bài tập mở rộng

  1. Viết một script chỉ đọc để thu thập trạng thái liên quan đến chủ đề và xuất báo cáo có timestamp.
  2. Tạo một lỗi có kiểm soát trong máy ảo, ghi triệu chứng, giả thuyết, bằng chứng và cách khôi phục.
  3. Chuyển ba thao tác thủ công thành script có set -euo pipefail và kiểm tra đầu vào.
  4. Viết runbook một trang để người khác thực hiện mà không cần hỏi lại tác giả.
  5. Đánh giá thay đổi cần thiết nếu workload tăng gấp 100 lần.

Tài liệu tham khảo

  • Ubuntu Server Documentation.
  • GNU Coreutils và Bash Reference Manual.
  • Linux man-pages project.
  • systemd và OpenSSH documentation, sử dụng theo nội dung từng bài.

Kết luận

Project cho thấy Linux là nền vận hành liên kết code, dữ liệu và hạ tầng. Sản phẩm đạt chuẩn khi người khác có thể triển khai, kiểm thử, giám sát và phục hồi mà không phụ thuộc tác giả.

TechData.AI - Leading the Future.
Tham khảo các khoá học theo link: https://techdata.ai/techdata-ai-course/
Hoàng Minh.

Scroll to Top