Tự động hóa sao lưu dữ liệu định kỳ với Bash, Cron và Ansible
Trong môi trường quản trị hệ thống hiện đại, việc sao lưu dữ liệu không chỉ là một nhiệm vụ lặp đi lặp lại mà còn là cột sống đảm bảo tính liên tục của doanh nghiệp. Một quy trình sao lưu thủ công dễ mắc sai sót, thiếu nhất quán và thường bị bỏ qua khi áp lực công việc tăng cao. Bài viết này sẽ hướng dẫn bạn xây dựng một giải pháp tự động hóa toàn diện, kết hợp sức mạnh của Bash scripting để xử lý logic tại chỗ, Cron để lên lịch thực thi, và Ansible để triển khai đồng bộ hóa quy trình này đến hàng loạt máy chủ một cách an toàn và có kiểm soát.
Chúng ta sẽ tập trung vào kịch bản cụ thể: Thiết lập một hệ thống sao lưu tự động các thư mục quan trọng của ứng dụng web lên một máy chủ lưu trữ riêng biệt (backup server), nén dữ liệu trước khi truyền, lưu trữ theo định dạng ngày tháng và tự động xóa các bản sao lưu cũ hơn một khoảng thời gian nhất định (chẳng hạn 7 ngày). Giải pháp này không chỉ giúp tiết kiệm thời gian mà còn đảm bảo tính nhất quán và bảo mật cho toàn bộ hạ tầng.
Phần 1: Xây dựng script Bash xử lý logic sao lưu
Trước khi bàn đến việc tự động hóa, chúng ta cần một script Bash vững chắc để thực hiện các tác vụ cụ thể. Script này sẽ đóng vai trò là động cơ, chịu trách nhiệm định dạng tên file, nén dữ liệu, truyền tải qua mạng và dọn dẹp dữ liệu cũ. Điều quan trọng khi viết script Bash cho sản xuất là phải xử lý lỗi ngay từ đầu, sử dụng các biến môi trường rõ ràng và đảm bảo script có thể chạy độc lập hoặc khi được gọi từ cron.
Bước đầu tiên là xác định các biến cấu hình cố định. Chúng ta sẽ sử dụng một cấu trúc script theo phong cách "configuration at the top", giúp người quản trị dễ dàng thay đổi các thông số như đường dẫn thư mục nguồn, đích và thời gian lưu trữ mà không cần hiểu sâu về logic bên trong. Script cần sử dụng lệnh tar để nén dữ liệu với định dạng gzip vì tính tương thích cao và tốc độ nén nhanh. Để truyền tải dữ liệu giữa các máy chủ, chúng ta sử dụng scp hoặc rsync. Trong ví dụ này, rsync được ưu tiên vì khả năng truyền tải chỉ những phần dữ liệu thay đổi (incremental sync) và hỗ trợ xác thực bằng SSH key an toàn.
Để xử lý logic dọn dẹp, script cần có khả năng liệt kê các file trong thư mục đích, kiểm tra ngày tạo của file và xóa những file vượt quá ngưỡng thời gian quy định. Việc này thường được thực hiện bằng vòng lặp for kết hợp với lệnh find hoặc stat để lấy thông tin thời gian. Một yếu tố không thể thiếu là ghi nhật ký (logging). Thay vì chỉ in ra màn hình, script nên ghi mọi hành động thành công hoặc thất bại vào một file log riêng biệt với dấu thời gian cụ thể. Điều này giúp việc debug sau này trở nên dễ dàng, đặc biệt là khi script chạy tự động vào ban đêm mà không ai giám sát trực tiếp.
Dưới đây là một mẫu script hoàn chỉnh thực hiện các chức năng trên. Script này giả định rằng SSH key đã được cấu hình sẵn giữa máy nguồn và máy đích để không cần nhập password mỗi lần chạy. Nó cũng bao gồm các lệnh kiểm tra lỗi cơ bản để dừng script ngay khi gặp sự cố nghiêm trọng.
#!/bin/bash
# Cấu hình chung
BACKUP_SOURCE="/var/www/html"
BACKUP_DEST="/backup/www"
REMOTE_HOST="backup-server.example.com"
REMOTE_USER="backup_admin"
LOG_FILE="/var/log/backup_script.log"
RETENTION_DAYS=7
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
BACKUP_FILE="web_backup_${TIMESTAMP}.tar.gz"
# Hàm ghi log
log() {
echo "[$(date '+%F %T')] $1" | tee -a $LOG_FILE
}
log "Bắt đầu quá trình sao lưu..."
# Kiểm tra xem thư mục nguồn có tồn tại không
if [ ! -d "$BACKUP_SOURCE" ]; then
log "Lỗi: Thư mục nguồn $BACKUP_SOURCE không tồn tại."
exit 1
fi
# Tạo file nén tại chỗ
log "Đang nén dữ liệu tại $BACKUP_SOURCE..."
tar -czf "$BACKUP_FILE" -C "$(dirname $BACKUP_SOURCE)" "$(basename $BACKUP_SOURCE)"
if [ $? -ne 0 ]; then
log "Lỗi: Không thể tạo file nén."
exit 1
fi
# Truyền file sang máy chủ lưu trữ
log "Đang truyền file $BACKUP_FILE sang $REMOTE_HOST..."
rsync -e ssh "$BACKUP_FILE" "${REMOTE_USER}@${REMOTE_HOST}:${BACKUP_DEST}/"
if [ $? -ne 0 ]; then
log "Lỗi: Không thể truyền file sang máy chủ đích."
rm -f "$BACKUP_FILE"
exit 1
fi
# Xóa file tạm tại nguồn sau khi truyền thành công
rm -f "$BACKUP_FILE"
log "Đã xóa file nén tại nguồn."
# Dọn dẹp các file cũ trên máy chủ đích (thực hiện trên máy đích thông qua SSH)
log "Đang dọn dẹp các bản sao lưu cũ hơn $RETENTION_DAYS ngày trên máy chủ đích..."
ssh "${REMOTE_USER}@${REMOTE_HOST}" "cd ${BACKUP_DEST} && find . -type f -name 'web_backup_*.tar.gz' -mtime +${RETENTION_DAYS} -exec rm -f {} \;"
if [ $? -ne 0 ]; then
log "Cảnh báo: Không thể thực hiện lệnh dọn dẹp trên máy chủ đích."
else
log "Hoàn tất dọn dẹp dữ liệu cũ."
fi
log "Quá trình sao lưu kết thúc thành công."
exit 0
Khi đã có script, hãy đảm bảo file này có quyền thực thi bằng lệnh chmod +x backup_script.sh. Một lưu ý quan trọng là đường dẫn trong script phải là đường dẫn tuyệt đối. Nếu bạn chạy script qua Cron, biến môi trường PATH thường bị giới hạn, do đó việc sử dụng đường dẫn đầy đủ (ví dụ /usr/bin/rsync) hoặc khai báo PATH rõ ràng trong script sẽ tránh được lỗi "command not found".
Phần 2: Thiết lập lịch chạy tự động với Cron
Sau khi script Bash hoạt động ổn định, bước tiếp theo là gắn nó vào một lịch trình cố định. Cron là công cụ quản lý thời gian chạy định kỳ mặc định trên hầu hết các hệ điều hành Unix/Linux. Để cấu hình Cron, bạn cần hiểu rõ cấu trúc của dòng lệnh crontab, bao gồm phút, giờ, ngày trong tháng, tháng và ngày trong tuần.
Để thêm tác vụ, bạn sử dụng lệnh crontab -e để mở trình soạn thảo mặc định (thường là nano hoặc vim). Tại đây, bạn sẽ thêm một dòng chỉ định thời gian và đường dẫn đến script. Ví dụ, nếu bạn muốn chạy script vào lúc 2 giờ sáng hàng ngày, cấu trúc sẽ là 0 2 * * *. Sau chuỗi thời gian này là các biến môi trường (nếu cần), sau đó là đường dẫn tuyệt đối đến script và các tham số (nếu có).
Một vấn đề thường gặp với Cron là việc xử lý đầu ra (stdout/stderr). Nếu script ghi log vào một file như chúng ta đã làm ở phần trước, thì bạn không cần lo lắng về việc Cron sẽ gửi email thông báo lỗi mỗi khi chạy. Tuy nhiên, để an toàn tuyệt đối, bạn có thể điều hướng đầu ra về file null hoặc file log riêng của cron bằng cách sử dụng toán tử >> và 2>&1. Trong kịch bản này, vì script đã tự ghi log nội bộ, ta có thể để Cron mặc định hoặc hướng tất cả output về một file chung để dễ theo dõi các lỗi cấp hệ thống.
Đoạn cấu hình cron cho script của chúng ta sẽ trông như sau:
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
0 2 * * * /opt/scripts/backup_script.sh >> /var/log/cron_backup.log 2>&1
Ở dòng trên, ta khai báo rõ biến PATH để đảm bảo các lệnh như date, tar, ssh đều được tìm thấy mà không cần đường dẫn tuyệt đối cho từng lệnh bên trong script. Dòng bắt đầu bằng 0 2 là thời gian chạy, và đường dẫn /opt/scripts/backup_script.sh là vị trí đặt script. Việc thêm >> giúp ghi lại thông tin chạy của chính Cron vào file /var/log/cron_backup.log, giúp ta biết được Cron có thực sự gọi script hay không, tách biệt với log nội bộ của script.
Cần lưu ý rằng khi chạy qua Cron, môi trường rất nghèo nàn. Nếu script của bạn phụ thuộc vào các biến môi trường như HOME hoặc USER, bạn nên khai báo chúng trong file crontab hoặc trong chính file script. Ngoài ra, nếu bạn cần xác thực SSH khi truyền file, hãy đảm bảo SSH key đã được setup và máy chủ đích đã nằm trong danh sách known_hosts của tài khoản đang chạy cron, nếu không script sẽ bị treo chờ nhập "yes/no" khi lần đầu gặp server lạ.
Phần 3: Triển khai quy trình trên hàng loạt máy chủ với Ansible
Khi bạn chỉ quản lý một máy chủ, việc chạy lệnh thủ công là chấp nhận được. Nhưng khi có 10, 50 hay 100 máy chủ, việc vào từng máy để chỉnh sửa crontab và copy script là phi thực tế và đầy rủi ro. Đây là lúc Ansible phát huy sức mạnh. Ansible cho phép bạn mô tả trạng thái mong muốn của hệ thống thông qua các playbook (tệp YAML), và nó sẽ tự động cấu hình tất cả các máy chủ để đạt được trạng thái đó.
Chúng ta sẽ tạo một playbook Ansible để thực hiện ba việc chính: (1) Copy file script Bash từ máy quản trị (control node) về các máy chủ mục tiêu (target nodes), (2) Cấp quyền thực thi cho script, và (3) Cài đặt tác vụ cron. Ansible có các module rất tiện lợi cho việc này như copy, file và cron. Module cron đặc biệt hữu ích vì nó tự động tạo hoặc cập nhật crontab mà không cần bạn phải can thiệp thủ công vào file crontab, giúp tránh xung đột cấu hình.
Trước khi viết playbook, bạn cần đặt file script đã viết ở phần 1 vào thư mục files trong cấu trúc playbook của mình (thường là roles/my_role/files hoặc playbook_directory/files). Giả sử tên file là backup_script.sh. Playbook sẽ sử dụng biến để linh hoạt hóa các thông số như đường dẫn sao lưu hay tên miền máy chủ lưu trữ, giúp cùng một playbook áp dụng cho nhiều môi trường khác nhau.
Dưới đây là một ví dụ về playbook Ansible sử dụng module cron để thiết lập lịch chạy:
- name: Tự động hóa sao lưu dữ liệu trên Web Servers
hosts: webservers
become: true
vars:
backup_script_path: /opt/scripts/backup_script.sh
backup_source: /var/www/html
backup_dest: /backup/www
remote_host: backup-server.example.com
retention_days: 7
cron_minute: "0"
cron_hour: "2"
tasks:
- name: Copy script sao lưu về máy chủ đích
copy:
src: files/backup_script.sh
dest: "{{ backup_script_path }}"
owner: root
group: root
mode: '0755'
backup: yes
- name: Cập nhật cron job để chạy script
cron:
name: "Daily Web Backup"
minute: "{{ cron_minute }}"
hour: "{{ cron_hour }}"
job: "{{ backup_script_path }}"
user: root
state: present
env:
PATH: "/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin"
- name: Tạo thư mục đích sao lưu nếu chưa tồn tại
file:
path: "{{ backup_dest }}"
state: directory
owner: root
group: root
mode: '0755'
- name: Đảm bảo SSH key đã được setup (giả định key đã tồn tại)
lineinfile:
path: /root/.ssh/known_hosts
regexp: "{{ remote_host }}"
state: absent
when: remote_host is defined
ignore_errors: yes
Trong playbook trên, biến become: true cho phép Ansible thực thi các tác vụ với quyền root, điều cần thiết để ghi vào crontab của root và truy cập các thư mục hệ thống. Module copy đảm bảo file script được chuyển đến đúng vị trí và có quyền 0755 (chủ sở hữu đọc/ghi/xử lý, người khác đọc/xử lý). Tham số backup: yes trong module copy là một thực hành tốt, giúp lưu lại phiên bản cũ của file script trong trường hợp có xung đột hoặc cần phục hồi.
Module cron ở đây không chỉ thêm dòng lệnh mà còn quản lý trạng thái. Nếu bạn chạy playbook lần 2, nó sẽ không tạo thêm dòng cron mới mà chỉ đảm bảo dòng cron đã tồn tại với đúng nội dung. Nếu bạn thay đổi biến cron_hour thành 3 và chạy lại, Ansible sẽ tự động cập nhật dòng cron đó mà không cần xóa rồi tạo mới, giúp quy trình an toàn hơn.
Để chạy playbook này, bạn sử dụng lệnh ansible-playbook -i inventory.ini backup_playbook.yml trên máy quản trị. Ansible sẽ tự động kết nối SSH, đẩy file script, cấu hình cron và kiểm tra các thư mục cần thiết. Sau khi hoàn thành, bạn có thể kiểm tra trên máy chủ đích bằng lệnh crontab -l để xác nhận tác vụ đã được thêm đúng như ý muốn.
Lưu ý quan trọng về bảo mật và vận hành
Khi triển khai giải pháp tự động hóa sao lưu, bảo mật là yếu tố không thể xem nhẹ. Trước hết, tuyệt đối không lưu trữ mật khẩu hoặc passphrase của SSH key trực tiếp trong script hoặc file playbook. Hãy sử dụng xác thực bằng cặp khóa SSH (public/private key) và cấu hình AuthorizedKeys trên máy chủ đích. Đảm bảo private key trên máy nguồn chỉ có quyền truy cập của chủ sở hữu (chmod 600).
Thứ hai, về quyền truy cập file. Script và các file log nên được đặt trong các thư mục có quyền kiểm soát chặt chẽ. Nếu script chạy dưới quyền root, hãy cân nhắc tạo một user riêng (ví dụ: backup-user) chỉ có quyền thực hiện các tác vụ sao lưu cần thiết, thay vì luôn chạy với quyền root cao nhất. Điều này tuân thủ nguyên tắc "least privilege" (quyền hạn tối thiểu).
Thứ ba, hãy luôn kiểm tra dung lượng ổ cứng. Tự động hóa có thể dẫn đến tình trạng ổ cứng bị đầy nếu không có cơ chế xóa file cũ hiệu quả. Script của chúng ta đã có phần xóa file cũ, nhưng bạn nên thiết lập cảnh báo (alerting) nếu dung lượng sử dụng vượt quá 80-90%. Bạn có thể mở rộng script Bash để gửi email cảnh báo hoặc log lên hệ thống giám sát trung tâm (như Prometheus/Grafana) khi gặp sự cố.
Thứ tư, về tính nhất quán của dữ liệu. Khi sao lưu các ứng dụng đang chạy (ví dụ: database MySQL), việc nén file trực tiếp có thể dẫn đến dữ liệu không nhất quán (inconsistent data) vì file đang bị ghi đè trong lúc nén. Đối với các hệ thống phức tạp, bạn cần thêm bước "lock" dữ liệu hoặc sử dụng snapshot (nếu dùng LVM hoặc Cloud Block Storage) trước khi bắt đầu sao lưu, và "unlock" sau khi hoàn tất. Script hiện tại chỉ phù hợp cho các file tĩnh như code, hình ảnh, cấu hình.
Kết luận
Việc kết hợp Bash scripting, Cron và Ansible tạo nên một giải pháp tự động hóa sao lưu mạnh mẽ, linh hoạt và đáng tin cậy. Bash cung cấp logic xử lý chi tiết và khả năng tùy biến cao tại chỗ; Cron đảm bảo quy trình được thực thi đúng thời gian một cách thụ động và bền bỉ; còn Ansible giúp nhân rộng quy trình đó ra toàn bộ hạ tầng với độ chính xác cao và giảm thiểu lỗi con người.
Giải pháp này không chỉ áp dụng cho việc sao lưu dữ liệu mà còn là mẫu hình cơ bản cho nhiều tác vụ bảo trì khác như cập nhật hệ thống, xoay vòng log, hay kiểm tra sức khỏe dịch vụ. Khi bạn đã nắm vững cách xây dựng một pipeline tự động hóa như vậy, bạn đã bước vào một giai đoạn mới trong quản trị hệ thống, nơi bạn tập trung vào việc thiết kế kiến trúc và giải quyết vấn đề thay vì lặp lại các thao tác thủ công. Hãy luôn nhớ rằng, tự động hóa không phải là "cài đặt xong và quên", mà cần được giám sát, kiểm tra định kỳ và tinh chỉnh liên tục để thích ứng với sự thay đổi của môi trường.