zalo-icon
facebook-icon
phone-icon
Cron Job, Log và Monitoring: Vận hành Data Pipeline trên Linux | Operations Guide
Hướng dẫn Cron Job, systemd timer, log và monitoring để vận hành Data Pipeline trên Linux ổn định, có cảnh báo và runbook xử lý lỗi.

Một pipeline chỉ thực sự tồn tại khi được lập lịch, ghi log, giám sát và có người chịu trách nhiệm khi thất bại. Cron chạy đúng giờ chỉ là một phần nhỏ của vận hành.

Bài viết triển khai cron job an toàn, so sánh systemd timer, thiết kế log, rotation, metric, alert và runbook cho ETL hằng ngày.

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 scheduled ETL có lock, timeout, structured log, logrotate, health metric và alert test. 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.

Alert và runbook vận hành
Freshness duration và row count
Structured logging và logrotate
Systemd timer và wrapper script
Cron environment và lịch chạy

Bộ dữ liệu hoặc workload thực hành là script ingest_sales.sh xử lý batch ngày, SLA hoàn tất trước 03:00. 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. Cron environment

Cron có PATH và environment tối giản khác interactive shell. Trong bối cảnh ETL doanh thu chạy mỗi ngày lúc 02:00 và phải cảnh báo khi chậm hoặc lỗi, 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 env từ cron lab và dùng đường dẫn tuyệt đối. 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.

env -i HOME="$HOME" PATH=/usr/bin:/bin bash -c '/home/$USER/linux-lab/src/ingest_sales.sh --help'

Kết quả cần thấy. Script không phụ thuộc alias hoặc virtualenv tự kích hoạt. 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. Khai báo environment trong wrapper hoặc unit, không dựa .bashrc. Với lập lịch và giám sát pipeline, 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. Crontab rõ ràng

Biểu thức lịch cần timezone và owner. Trong bối cảnh ETL doanh thu chạy mỗi ngày lúc 02:00 và phải cảnh báo khi chậm hoặc lỗi, 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 entry có comment, flock, timeout và redirect. 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.

crontab -l 2>/dev/null
printf "0 2 * * * flock -n /tmp/etl.lock timeout 55m /home/%s/linux-lab/src/run_etl.sh >> /home/%s/linux-lab/logs/cron.log 2>&1\n" "$USER" "$USER"

Kết quả cần thấy. Entry biểu diễn 02:00 mỗi ngày và cả stdout stderr vào log. 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. Quản lý crontab bằng code và peer review. Với lập lịch và giám sát pipeline, 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. Systemd timer

Timer cung cấp dependency, trạng thái và journal tốt hơn cron. Trong bối cảnh ETL doanh thu chạy mỗi ngày lúc 02:00 và phải cảnh báo khi chậm hoặc lỗi, 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. Xem cấu trúc unit/timer và kiểm tra calendar. 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.

systemd-analyze calendar "daily"
systemctl list-timers --all | head

Kết quả cần thấy. Biết lần chạy kế tiếp và lần gần nhất. 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 Persistent=true khi cần chạy bù sau downtime. Với lập lịch và giám sát pipeline, 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. Wrapper script

Wrapper thiết lập context trước khi gọi ứng dụng. Trong bối cảnh ETL doanh thu chạy mỗi ngày lúc 02:00 và phải cảnh báo khi chậm hoặc lỗi, 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 umask, PATH, cwd, run ID và virtualenv rõ. 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.

umask 027
cd "$HOME/linux-lab"
export PATH="$HOME/venvs/etl/bin:/usr/bin:/bin"
run_id="$(date -u +%Y%m%dT%H%M%SZ)"

Kết quả cần thấy. Mọi run có context nhất quán và ID duy nhất. 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. Wrapper nhỏ, versioned và không chứa business logic trùng. Với lập lịch và giám sát pipeline, 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. Structured logging

Log cần máy và người đều đọc được. Trong bối cảnh ETL doanh thu chạy mỗi ngày lúc 02:00 và phải cảnh báo khi chậm hoặc lỗi, 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 key=value với timestamp, level, run_id, duration và rows. 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.

printf "ts=%s level=INFO run_id=%s event=completed rows=%d duration_s=%d\n" "$(date -u +%FT%TZ)" "$run_id" 3840 125 | tee -a logs/etl.log

Kết quả cần thấy. Một dòng log có đủ trường đối chiếu. 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 ghi PII, token hoặc password vào log. Với lập lịch và giám sát pipeline, 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. Logrotate

Log tăng vô hạn sẽ làm đầy filesystem. Trong bối cảnh ETL doanh thu chạy mỗi ngày lúc 02:00 và phải cảnh báo khi chậm hoặc lỗi, 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. Viết policy rotate theo ngày, giữ 14 bản và kiểm tra dry-run. 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.

logrotate -d config/etl-logrotate.conf
logrotate -f config/etl-logrotate.conf

Kết quả cần thấy. Policy parse thành công và archive được tạo. 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. Ứng dụng cần reopen file hoặc dùng copytruncate có cân nhắc. Với lập lịch và giám sát pipeline, 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. Freshness metric

Process success nhưng output cũ vẫn là lỗi nghiệp vụ. Trong bối cảnh ETL doanh thu chạy mỗi ngày lúc 02:00 và phải cảnh báo khi chậm hoặc lỗi, 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. Đo tuổi file hoặc max business_date và xuất metric. 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.

now=$(date +%s)
mtime=$(stat -c %Y data/processed/latest.parquet)
echo "etl_output_age_seconds $((now-mtime))"

Kết quả cần thấy. Metric phản ánh freshness độc lập với PID. 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. Alert theo SLA và business calendar. Với lập lịch và giám sát pipeline, 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. Duration và row count

Biến động duration hoặc số dòng báo hiệu upstream. Trong bối cảnh ETL doanh thu chạy mỗi ngày lúc 02:00 và phải cảnh báo khi chậm hoặc lỗi, 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. So actual với baseline và ngưỡng có lý do. 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.

start=$(date +%s)
# run workload
duration=$(( $(date +%s)-start ))
printf "duration=%s rows=%s\n" "$duration" 3840

Kết quả cần thấy. Duration và rows được lưu theo run. 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 percentile, seasonality và tolerance tránh alert nhiễu. Với lập lịch và giám sát pipeline, 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. Alert có thể hành động

Thông báo failed thiếu run ID và log làm chậm xử lý. Trong bối cảnh ETL doanh thu chạy mỗi ngày lúc 02:00 và phải cảnh báo khi chậm hoặc lỗi, 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 alert gồm service, environment, run, impact, evidence, owner và runbook. 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.

printf "service=etl env=prod run_id=%s status=failed log=%s owner=data-platform\n" "$run_id" "logs/etl.log"

Kết quả cần thấy. Người trực biết mức ảnh hưởng và bước đầu tiê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. Test alert định kỳ và theo dõi acknowledgement, resolution time. Với lập lịch và giám sát pipeline, 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. Runbook và diễn tập

Runbook chưa diễn tập thường thiếu quyền hoặc bước rollback. Trong bối cảnh ETL doanh thu chạy mỗi ngày lúc 02:00 và phải cảnh báo khi chậm hoặc lỗi, 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. Mô phỏng missing input, disk full bằng quota lab và timeout. 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.

mv data/raw/sales.csv data/raw/sales.csv.missing
./src/run_etl.sh || echo "expected failure"
mv data/raw/sales.csv.missing data/raw/sales.csv

Kết quả cần thấy. Failure được phát hiện, không tạo output giả và recovery 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. Game day có phạm vi an toàn, observer và postmortem không đổ lỗi. Với lập lịch và giám sát pipeline, 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 job chạy đúng lịch, không chạy chồng, lỗi trả mã khác 0, log rotate và cảnh báo chứa run ID. 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ý

Lỗi gồm cron thiếu PATH, working directory khác terminal, redirect mất stderr, job chạy chồng, log đầy disk, alert không có owner và chỉ kiểm tra process thay vì freshness đầu ra. 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

Scheduler có SLA, retry policy, concurrency policy, dependency và backfill. Monitoring đo outcome nghiệp vụ, freshness, duration và row count; alert gắn owner và runbook.

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

Vận hành tốt biến một script thành dịch vụ dữ liệu có thể tin cậy. Lịch chạy, log và alert phải được thiết kế cùng lúc với logic xử lý.

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